Skip to content

Commit 65cc0a0

Browse files
authored
[PM-3553] Support SimpleLogin self hosted servers (#4723)
1 parent 0ba67f5 commit 65cc0a0

File tree

13 files changed

+344
-6
lines changed

13 files changed

+344
-6
lines changed

app/src/main/java/com/x8bit/bitwarden/data/tools/generator/repository/model/UsernameGenerationOptions.kt

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ data class UsernameGenerationOptions(
5252
@SerialName("simpleLoginApiKey")
5353
val simpleLoginApiKey: String? = null,
5454

55+
@SerialName("simpleLoginSelfHostServerUrl")
56+
val simpleLoginSelfHostServerUrl: String? = null,
57+
5558
@SerialName("duckDuckGoApiKey")
5659
val duckDuckGoApiKey: String? = null,
5760

app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt

+22
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,8 @@ private fun CoachMarkScope<ExploreGeneratorCoachMark>.ScrollContent(
530530
randomWordHandlers = randomWordHandlers,
531531
shouldShowSelfHostServerUrlField =
532532
state.shouldShowAnonAddySelfHostServerUrlField,
533+
shouldShowSimpleLoginSelfHostServerUrlField =
534+
state.shouldShowSimpleLoginSelfHostServerField,
533535
)
534536
}
535537
}
@@ -1104,6 +1106,7 @@ private fun UsernameTypeItems(
11041106
catchAllEmailHandlers: CatchAllEmailHandlers,
11051107
randomWordHandlers: RandomWordHandlers,
11061108
shouldShowSelfHostServerUrlField: Boolean,
1109+
shouldShowSimpleLoginSelfHostServerUrlField: Boolean,
11071110
modifier: Modifier = Modifier,
11081111
) {
11091112
Column(modifier = modifier) {
@@ -1129,6 +1132,8 @@ private fun UsernameTypeItems(
11291132
usernameTypeState = selectedType,
11301133
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
11311134
shouldShowSelfHostServerUrlField = shouldShowSelfHostServerUrlField,
1135+
shouldShowSimpleLoginSelfHostServerUrlField =
1136+
shouldShowSimpleLoginSelfHostServerUrlField,
11321137
)
11331138
}
11341139

@@ -1190,6 +1195,7 @@ private fun ForwardedEmailAliasTypeContent(
11901195
usernameTypeState: GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias,
11911196
forwardedEmailAliasHandlers: ForwardedEmailAliasHandlers,
11921197
shouldShowSelfHostServerUrlField: Boolean,
1198+
shouldShowSimpleLoginSelfHostServerUrlField: Boolean,
11931199
modifier: Modifier = Modifier,
11941200
) {
11951201
Column(modifier = modifier) {
@@ -1329,6 +1335,22 @@ private fun ForwardedEmailAliasTypeContent(
13291335
.standardHorizontalMargin()
13301336
.fillMaxWidth(),
13311337
)
1338+
1339+
if (shouldShowSimpleLoginSelfHostServerUrlField) {
1340+
Spacer(modifier = Modifier.height(8.dp))
1341+
1342+
BitwardenTextField(
1343+
label = stringResource(id = R.string.self_host_server_url),
1344+
value = usernameTypeState.selectedServiceType.selfHostServerUrl,
1345+
onValueChange =
1346+
forwardedEmailAliasHandlers.onSimpleLoginSelfHostServerUrlChange,
1347+
textFieldTestTag = "SimpleLoginSelfHostServerUrlEntry",
1348+
cardStyle = CardStyle.Full,
1349+
modifier = Modifier
1350+
.standardHorizontalMargin()
1351+
.fillMaxWidth(),
1352+
)
1353+
}
13321354
}
13331355

13341356
null -> {

app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt

+67
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ class GeneratorViewModel @Inject constructor(
115115
shouldShowAnonAddySelfHostServerUrlField = featureFlagManager.getFeatureFlag(
116116
FlagKey.AnonAddySelfHostAlias,
117117
),
118+
shouldShowSimpleLoginSelfHostServerField =
119+
featureFlagManager.getFeatureFlag(FlagKey.SimpleLoginSelfHostAlias),
118120
)
119121
},
120122
) {
@@ -152,6 +154,15 @@ class GeneratorViewModel @Inject constructor(
152154
}
153155
.onEach(::sendAction)
154156
.launchIn(viewModelScope)
157+
158+
featureFlagManager.getFeatureFlagFlow(FlagKey.SimpleLoginSelfHostAlias)
159+
.map { shouldShowSimpleLoginSelfHostServerField ->
160+
GeneratorAction.Internal.ShouldShowSimpleLoginSelfHostValueChangeReceive(
161+
shouldShowSelfHostServerField = shouldShowSimpleLoginSelfHostServerField,
162+
)
163+
}
164+
.onEach(::sendAction)
165+
.launchIn(viewModelScope)
155166
}
156167

157168
override fun handleAction(action: GeneratorAction) {
@@ -249,6 +260,10 @@ class GeneratorViewModel @Inject constructor(
249260
is GeneratorAction.MainType.Username.UsernameType.RandomWord -> {
250261
handleRandomWordSpecificAction(action)
251262
}
263+
264+
is GeneratorAction.MainType.Username.UsernameType.ForwardedEmailAlias.SimpleLogin.SelfHostServerUrlChange -> {
265+
handleSimpleLoginSelfHostServerUrlChange(action)
266+
}
252267
}
253268
}
254269

@@ -289,6 +304,10 @@ class GeneratorViewModel @Inject constructor(
289304
is GeneratorAction.Internal.ShouldShowAnonAddySelfHostValueChangeReceive -> {
290305
handleShouldShowAnonAddySelfHostValueChange(action)
291306
}
307+
308+
is GeneratorAction.Internal.ShouldShowSimpleLoginSelfHostValueChangeReceive -> {
309+
handleShouldShowSimpleLoginSelfHostValueChange(action)
310+
}
292311
}
293312
}
294313

@@ -594,6 +613,8 @@ class GeneratorViewModel @Inject constructor(
594613
type = UsernameGenerationOptions.UsernameType.FORWARDED_EMAIL_ALIAS,
595614
serviceType = UsernameGenerationOptions.ForwardedEmailServiceType.SIMPLE_LOGIN,
596615
simpleLoginApiKey = forwardedEmailAlias.selectedServiceType.apiKey,
616+
simpleLoginSelfHostServerUrl =
617+
forwardedEmailAlias.selectedServiceType.selfHostServerUrl,
597618
)
598619

599620
is ForwardEmail -> options.copy(
@@ -643,6 +664,7 @@ class GeneratorViewModel @Inject constructor(
643664
catchAllEmailDomain = "",
644665
firefoxRelayApiAccessToken = "",
645666
simpleLoginApiKey = "",
667+
simpleLoginSelfHostServerUrl = "",
646668
duckDuckGoApiKey = "",
647669
fastMailApiKey = "",
648670
anonAddyApiAccessToken = "",
@@ -813,6 +835,14 @@ class GeneratorViewModel @Inject constructor(
813835
}
814836
}
815837

838+
private fun handleShouldShowSimpleLoginSelfHostValueChange(
839+
action: GeneratorAction.Internal.ShouldShowSimpleLoginSelfHostValueChangeReceive,
840+
) {
841+
mutableStateFlow.update {
842+
it.copy(shouldShowSimpleLoginSelfHostServerField = action.shouldShowSelfHostServerField)
843+
}
844+
}
845+
816846
private fun handlePasswordGeneratorPolicyReceive(
817847
action: GeneratorAction.Internal.PasswordGeneratorPolicyReceive,
818848
) {
@@ -1392,6 +1422,21 @@ class GeneratorViewModel @Inject constructor(
13921422
}
13931423
}
13941424

1425+
private fun handleSimpleLoginSelfHostServerUrlChange(
1426+
action: GeneratorAction
1427+
.MainType
1428+
.Username
1429+
.UsernameType
1430+
.ForwardedEmailAlias
1431+
.SimpleLogin
1432+
.SelfHostServerUrlChange,
1433+
) {
1434+
updateSimpleLoginServiceType { simpleLoginServiceType ->
1435+
val newServerUrl = action.url
1436+
simpleLoginServiceType.copy(selfHostServerUrl = newServerUrl)
1437+
}
1438+
}
1439+
13951440
//endregion SimpleLogin Service Specific Handlers
13961441

13971442
//region Plus Addressed Email Specific Handlers
@@ -1538,6 +1583,7 @@ class GeneratorViewModel @Inject constructor(
15381583
?.toUsernameGeneratorRequest(
15391584
website = state.website,
15401585
allowAddyIoSelfHostUrl = state.shouldShowAnonAddySelfHostServerUrlField,
1586+
allowSimpleLoginSelfHostUrl = state.shouldShowSimpleLoginSelfHostServerField,
15411587
)
15421588
?: run {
15431589
mutableStateFlow.update { it.copy(generatedText = NO_GENERATED_TEXT) }
@@ -1840,6 +1886,7 @@ data class GeneratorState(
18401886
var passcodePolicyOverride: PasscodePolicyOverride? = null,
18411887
private val shouldShowCoachMarkTour: Boolean,
18421888
val shouldShowAnonAddySelfHostServerUrlField: Boolean,
1889+
val shouldShowSimpleLoginSelfHostServerField: Boolean,
18431890
) : Parcelable {
18441891

18451892
/**
@@ -2243,9 +2290,15 @@ data class GeneratorState(
22432290
@Parcelize
22442291
data class SimpleLogin(
22452292
val apiKey: String = "",
2293+
val selfHostServerUrl: String = "",
22462294
) : ServiceType(), Parcelable {
22472295
override val displayStringResId: Int
22482296
get() = ServiceTypeOption.SIMPLE_LOGIN.labelRes
2297+
2298+
@Suppress("UndocumentedPublicClass")
2299+
companion object {
2300+
const val DEFAULT_SIMPLE_LOGIN_URL = "https://app.simplelogin.io"
2301+
}
22492302
}
22502303
}
22512304
}
@@ -2586,6 +2639,13 @@ sealed class GeneratorAction {
25862639
* @property apiKey The new api key text.
25872640
*/
25882641
data class ApiKeyTextChange(val apiKey: String) : SimpleLogin()
2642+
2643+
/**
2644+
* Fired when the self host server url input text is changed.
2645+
*
2646+
* @property url The new self host server url text.
2647+
*/
2648+
data class SelfHostServerUrlChange(val url: String) : SimpleLogin()
25892649
}
25902650
}
25912651

@@ -2704,6 +2764,13 @@ sealed class GeneratorAction {
27042764
data class ShouldShowAnonAddySelfHostValueChangeReceive(
27052765
val shouldShowAnonAddySelfHostServerUrlField: Boolean,
27062766
) : Internal()
2767+
2768+
/**
2769+
* The value for the shouldShowSimpleLoginSelfHostServerField has changed.
2770+
*/
2771+
data class ShouldShowSimpleLoginSelfHostValueChangeReceive(
2772+
val shouldShowSelfHostServerField: Boolean,
2773+
) : Internal()
27072774
}
27082775
}
27092776

app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/handlers/ForwardedEmailAliasHandlers.kt

+9-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorAction.MainType.U
1212
* Each lambda corresponds to a specific user action, allowing for easy delegation of
1313
* logic when user input is detected.
1414
*/
15-
@Suppress("LongParameterList")
15+
@Suppress("LongParameterList", "LongMethod")
1616
data class ForwardedEmailAliasHandlers(
1717
val onServiceChange: (ForwardedEmailAlias.ServiceTypeOption) -> Unit,
1818
val onAddyIoAccessTokenTextChange: (String) -> Unit,
@@ -24,6 +24,7 @@ data class ForwardedEmailAliasHandlers(
2424
val onForwardEmailApiKeyTextChange: (String) -> Unit,
2525
val onForwardEmailDomainNameTextChange: (String) -> Unit,
2626
val onSimpleLoginApiKeyTextChange: (String) -> Unit,
27+
val onSimpleLoginSelfHostServerUrlChange: (String) -> Unit,
2728
) {
2829
@Suppress("UndocumentedPublicClass")
2930
companion object {
@@ -93,6 +94,13 @@ data class ForwardedEmailAliasHandlers(
9394
ForwardedEmailAliasAction.SimpleLogin.ApiKeyTextChange(apiKey = newApiKey),
9495
)
9596
},
97+
onSimpleLoginSelfHostServerUrlChange = { newServerUrl ->
98+
viewModel.trySendAction(
99+
ForwardedEmailAliasAction.SimpleLogin.SelfHostServerUrlChange(
100+
url = newServerUrl,
101+
),
102+
)
103+
},
96104
)
97105
}
98106
}

app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ForwardedEmailServiceTypeExtensions.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ fun UsernameGenerationOptions.ForwardedEmailServiceType?.toServiceType(
2424
}
2525

2626
UsernameGenerationOptions.ForwardedEmailServiceType.SIMPLE_LOGIN -> {
27-
SimpleLogin(apiKey = options.simpleLoginApiKey.orEmpty())
27+
SimpleLogin(
28+
apiKey = options.simpleLoginApiKey.orEmpty(),
29+
selfHostServerUrl = options.simpleLoginSelfHostServerUrl
30+
?.prefixHttpsIfNecessary()
31+
.orEmpty(),
32+
)
2833
}
2934

3035
UsernameGenerationOptions.ForwardedEmailServiceType.DUCK_DUCK_GO -> {

app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/util/ServiceTypeExtensions.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Us
1313
fun ServiceType.toUsernameGeneratorRequest(
1414
website: String?,
1515
allowAddyIoSelfHostUrl: Boolean,
16+
allowSimpleLoginSelfHostUrl: Boolean,
1617
): UsernameGeneratorRequest.Forwarded? {
1718
return when (this) {
1819
is ServiceType.AddyIo -> {
@@ -80,12 +81,17 @@ fun ServiceType.toUsernameGeneratorRequest(
8081
}
8182

8283
is ServiceType.SimpleLogin -> {
84+
val baseUrl = if (allowSimpleLoginSelfHostUrl && selfHostServerUrl.isNotBlank()) {
85+
selfHostServerUrl.prefixHttpsIfNecessary()
86+
} else {
87+
ServiceType.SimpleLogin.DEFAULT_SIMPLE_LOGIN_URL
88+
}
8389
this
8490
.apiKey
8591
.orNullIfBlank()
8692
?.let {
8793
UsernameGeneratorRequest.Forwarded(
88-
service = ForwarderServiceType.SimpleLogin(apiKey = it),
94+
service = ForwarderServiceType.SimpleLogin(apiKey = it, baseUrl = baseUrl),
8995
website = website,
9096
)
9197
}

app/src/test/java/com/x8bit/bitwarden/data/tools/generator/datasource/disk/GeneratorDiskSourceTest.kt

+3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class GeneratorDiskSourceTest {
5353
catchAllEmailDomain = "example.com",
5454
firefoxRelayApiAccessToken = "access_token_firefox_relay",
5555
simpleLoginApiKey = "api_key_simple_login",
56+
simpleLoginSelfHostServerUrl = "https://simplelogin.local",
5657
duckDuckGoApiKey = "api_key_duck_duck_go",
5758
fastMailApiKey = "api_key_fast_mail",
5859
anonAddyApiAccessToken = "access_token_anon_addy",
@@ -157,6 +158,7 @@ class GeneratorDiskSourceTest {
157158
catchAllEmailDomain = "example.com",
158159
firefoxRelayApiAccessToken = "access_token_firefox_relay",
159160
simpleLoginApiKey = "api_key_simple_login",
161+
simpleLoginSelfHostServerUrl = "https://simplelogin.local",
160162
duckDuckGoApiKey = "api_key_duck_duck_go",
161163
fastMailApiKey = "api_key_fast_mail",
162164
anonAddyApiAccessToken = "access_token_anon_addy",
@@ -195,6 +197,7 @@ class GeneratorDiskSourceTest {
195197
catchAllEmailDomain = "example.com",
196198
firefoxRelayApiAccessToken = "access_token_firefox_relay",
197199
simpleLoginApiKey = "api_key_simple_login",
200+
simpleLoginSelfHostServerUrl = "https://simplelogin.local",
198201
duckDuckGoApiKey = "api_key_duck_duck_go",
199202
fastMailApiKey = "api_key_fast_mail",
200203
anonAddyApiAccessToken = "access_token_anon_addy",

app/src/test/java/com/x8bit/bitwarden/data/tools/generator/repository/GeneratorRepositoryTest.kt

+3
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ class GeneratorRepositoryTest {
645645
catchAllEmailDomain = "example.com",
646646
firefoxRelayApiAccessToken = "access_token_firefox_relay",
647647
simpleLoginApiKey = "api_key_simple_login",
648+
simpleLoginSelfHostServerUrl = "https://simplelogin.local",
648649
duckDuckGoApiKey = "api_key_duck_duck_go",
649650
fastMailApiKey = "api_key_fast_mail",
650651
anonAddyApiAccessToken = "access_token_anon_addy",
@@ -698,6 +699,7 @@ class GeneratorRepositoryTest {
698699
catchAllEmailDomain = "example.com",
699700
firefoxRelayApiAccessToken = "access_token_firefox_relay",
700701
simpleLoginApiKey = "api_key_simple_login",
702+
simpleLoginSelfHostServerUrl = "https://simplelogin.local",
701703
duckDuckGoApiKey = "api_key_duck_duck_go",
702704
fastMailApiKey = "api_key_fast_mail",
703705
anonAddyApiAccessToken = "access_token_anon_addy",
@@ -729,6 +731,7 @@ class GeneratorRepositoryTest {
729731
catchAllEmailDomain = "example.com",
730732
firefoxRelayApiAccessToken = "access_token_firefox_relay",
731733
simpleLoginApiKey = "api_key_simple_login",
734+
simpleLoginSelfHostServerUrl = "https://simplelogin.local",
732735
duckDuckGoApiKey = "api_key_duck_duck_go",
733736
fastMailApiKey = "api_key_fast_mail",
734737
anonAddyApiAccessToken = "access_token_anon_addy",

0 commit comments

Comments
 (0)