Skip to content

Commit 9efa5aa

Browse files
authored
Merge pull request #5693 from element-hq/feature/bma/loginLinkPassword
Fix password flow when using a login link
2 parents 77bc9b8 + 87e7c64 commit 9efa5aa

File tree

10 files changed

+88
-38
lines changed

10 files changed

+88
-38
lines changed

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ import io.element.android.libraries.architecture.NodeInputs
4343
import io.element.android.libraries.architecture.callback
4444
import io.element.android.libraries.architecture.createNode
4545
import io.element.android.libraries.architecture.inputs
46+
import io.element.android.libraries.di.annotations.AppCoroutineScope
4647
import io.element.android.libraries.matrix.api.auth.OidcDetails
4748
import io.element.android.libraries.oidc.api.OidcAction
4849
import io.element.android.libraries.oidc.api.OidcActionFlow
50+
import kotlinx.coroutines.CoroutineScope
4951
import kotlinx.coroutines.delay
5052
import kotlinx.coroutines.launch
5153
import kotlinx.parcelize.Parcelize
@@ -57,6 +59,8 @@ class LoginFlowNode(
5759
@Assisted plugins: List<Plugin>,
5860
private val accountProviderDataSource: AccountProviderDataSource,
5961
private val oidcActionFlow: OidcActionFlow,
62+
@AppCoroutineScope
63+
private val appCoroutineScope: CoroutineScope,
6064
) : BaseFlowNode<LoginFlowNode.NavTarget>(
6165
backstack = BackStack(
6266
initialElement = NavTarget.OnBoarding,
@@ -268,7 +272,9 @@ class LoginFlowNode(
268272
DisposableEffect(Unit) {
269273
onDispose {
270274
activity = null
271-
accountProviderDataSource.reset()
275+
appCoroutineScope.launch {
276+
accountProviderDataSource.reset()
277+
}
272278
}
273279
}
274280
BackstackView()

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,34 @@ import kotlinx.coroutines.flow.asStateFlow
2121
class AccountProviderDataSource(
2222
enterpriseService: EnterpriseService,
2323
) {
24-
private val defaultAccountProvider =
25-
(enterpriseService.defaultHomeserverList().firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER } ?: AuthenticationConfig.MATRIX_ORG_URL)
26-
.let { url ->
27-
AccountProvider(
28-
url = url,
29-
subtitle = null,
30-
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
31-
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
32-
)
33-
}
34-
35-
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(
36-
defaultAccountProvider
24+
private val defaultAccountProvider = createAccountProvider(
25+
url = enterpriseService.defaultHomeserverList()
26+
.firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER }
27+
?: AuthenticationConfig.MATRIX_ORG_URL
3728
)
3829

30+
private val accountProvider: MutableStateFlow<AccountProvider> = MutableStateFlow(defaultAccountProvider)
31+
3932
val flow: StateFlow<AccountProvider> = accountProvider.asStateFlow()
4033

41-
fun reset() {
42-
accountProvider.tryEmit(defaultAccountProvider)
34+
suspend fun reset() {
35+
accountProvider.emit(defaultAccountProvider)
36+
}
37+
38+
suspend fun setUrl(url: String) {
39+
setAccountProvider(createAccountProvider(url))
40+
}
41+
42+
suspend fun setAccountProvider(data: AccountProvider) {
43+
accountProvider.emit(data)
4344
}
4445

45-
fun userSelection(data: AccountProvider) {
46-
accountProvider.tryEmit(data)
46+
private fun createAccountProvider(url: String): AccountProvider {
47+
return AccountProvider(
48+
url = url,
49+
subtitle = null,
50+
isPublic = url == AuthenticationConfig.MATRIX_ORG_URL,
51+
isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL,
52+
)
4753
}
4854
}

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class ChangeServerPresenter(
6565
throw ChangeServerError.UnsupportedServer
6666
}
6767
// Homeserver is valid, remember user choice
68-
accountProviderDataSource.userSelection(data)
68+
accountProviderDataSource.setAccountProvider(data)
6969
}.runCatchingUpdatingState(changeServerAction, errorTransform = ChangeServerError::from)
7070
}
7171
}

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
2525
import io.element.android.libraries.matrix.api.auth.OidcPrompt
2626
import io.element.android.libraries.oidc.api.OidcAction
2727
import io.element.android.libraries.oidc.api.OidcActionFlow
28-
import kotlinx.coroutines.CoroutineScope
29-
import kotlinx.coroutines.launch
3028

3129
/**
3230
* This class is responsible for managing the login flow, including handling OIDC actions and
@@ -58,12 +56,11 @@ class LoginHelper(
5856
loginModeState.value = AsyncData.Uninitialized
5957
}
6058

61-
fun submit(
62-
coroutineScope: CoroutineScope,
59+
suspend fun submit(
6360
isAccountCreation: Boolean,
6461
homeserverUrl: String,
6562
loginHint: String?,
66-
) = coroutineScope.launch {
63+
) {
6764
suspend {
6865
authenticationService.setHomeserver(homeserverUrl).map { matrixHomeServerDetails ->
6966
if (matrixHomeServerDetails.supportsOidcLogin) {

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import io.element.android.libraries.architecture.AsyncData
2222
import io.element.android.libraries.architecture.Presenter
2323
import io.element.android.libraries.core.uri.ensureProtocol
2424
import kotlinx.collections.immutable.toImmutableList
25+
import kotlinx.coroutines.launch
2526

2627
@Inject
2728
class ChooseAccountProviderPresenter(
@@ -37,10 +38,9 @@ class ChooseAccountProviderPresenter(
3738

3839
fun handleEvent(event: ChooseAccountProviderEvents) {
3940
when (event) {
40-
ChooseAccountProviderEvents.Continue -> {
41+
ChooseAccountProviderEvents.Continue -> localCoroutineScope.launch {
4142
selectedAccountProvider?.let {
4243
loginHelper.submit(
43-
coroutineScope = localCoroutineScope,
4444
isAccountCreation = false,
4545
homeserverUrl = it.url,
4646
loginHint = null,

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import dev.zacsweers.metro.AssistedInject
1717
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
1818
import io.element.android.features.login.impl.login.LoginHelper
1919
import io.element.android.libraries.architecture.Presenter
20+
import kotlinx.coroutines.launch
2021

2122
@AssistedInject
2223
class ConfirmAccountProviderPresenter(
@@ -42,9 +43,8 @@ class ConfirmAccountProviderPresenter(
4243

4344
fun handleEvents(event: ConfirmAccountProviderEvents) {
4445
when (event) {
45-
ConfirmAccountProviderEvents.Continue -> {
46+
ConfirmAccountProviderEvents.Continue -> localCoroutineScope.launch {
4647
loginHelper.submit(
47-
coroutineScope = localCoroutineScope,
4848
isAccountCreation = params.isAccountCreation,
4949
homeserverUrl = accountProvider.url,
5050
loginHint = null,

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ import io.element.android.appconfig.OnBoardingConfig
2323
import io.element.android.features.enterprise.api.EnterpriseService
2424
import io.element.android.features.enterprise.api.canConnectToAnyHomeserver
2525
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
26+
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
2627
import io.element.android.features.login.impl.login.LoginHelper
2728
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
2829
import io.element.android.libraries.architecture.Presenter
2930
import io.element.android.libraries.core.meta.BuildMeta
3031
import io.element.android.libraries.sessionstorage.api.SessionStore
3132
import io.element.android.libraries.ui.utils.MultipleTapToUnlock
33+
import kotlinx.coroutines.launch
3234

3335
@AssistedInject
3436
class OnBoardingPresenter(
@@ -40,6 +42,7 @@ class OnBoardingPresenter(
4042
private val loginHelper: LoginHelper,
4143
private val onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider,
4244
private val sessionStore: SessionStore,
45+
private val accountProviderDataSource: AccountProviderDataSource,
4346
) : Presenter<OnBoardingState> {
4447
@AssistedFactory
4548
interface Factory {
@@ -97,12 +100,15 @@ class OnBoardingPresenter(
97100

98101
fun handleEvent(event: OnBoardingEvents) {
99102
when (event) {
100-
is OnBoardingEvents.OnSignIn -> loginHelper.submit(
101-
coroutineScope = localCoroutineScope,
102-
isAccountCreation = false,
103-
homeserverUrl = event.defaultAccountProvider,
104-
loginHint = params.loginHint?.takeIf { forcedAccountProvider == null },
105-
)
103+
is OnBoardingEvents.OnSignIn -> localCoroutineScope.launch {
104+
// Ensure that the current account provider is set
105+
accountProviderDataSource.setUrl(event.defaultAccountProvider)
106+
loginHelper.submit(
107+
isAccountCreation = false,
108+
homeserverUrl = event.defaultAccountProvider,
109+
loginHint = params.loginHint?.takeIf { forcedAccountProvider == null },
110+
)
111+
}
106112
OnBoardingEvents.ClearError -> loginHelper.clearError()
107113
OnBoardingEvents.OnVersionClick -> {
108114
if (canReportBug) {

features/login/impl/src/test/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPointTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderDat
1717
import io.element.android.libraries.oidc.test.customtab.FakeOidcActionFlow
1818
import io.element.android.tests.testutils.lambda.lambdaError
1919
import io.element.android.tests.testutils.node.TestParentNode
20+
import kotlinx.coroutines.test.runTest
2021
import org.junit.Rule
2122
import org.junit.Test
2223

@@ -28,14 +29,15 @@ class DefaultLoginEntryPointTest {
2829
val mainDispatcherRule = MainDispatcherRule()
2930

3031
@Test
31-
fun `test node builder`() {
32+
fun `test node builder`() = runTest {
3233
val entryPoint = DefaultLoginEntryPoint()
3334
val parentNode = TestParentNode.create { buildContext, plugins ->
3435
LoginFlowNode(
3536
buildContext = buildContext,
3637
plugins = plugins,
3738
accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
3839
oidcActionFlow = FakeOidcActionFlow(),
40+
appCoroutineScope = backgroundScope,
3941
)
4042
}
4143
val callback = object : LoginEntryPoint.Callback {

features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,30 @@ class AccountProviderDataSourceTest {
8686
sut.flow.test {
8787
val initialState = awaitItem()
8888
assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
89-
sut.userSelection(AccountProvider(url = "https://example.com"))
89+
sut.setAccountProvider(AccountProvider(url = "https://example.com"))
90+
val changedState = awaitItem()
91+
assertThat(changedState).isEqualTo(
92+
AccountProvider(
93+
url = "https://example.com",
94+
title = "example.com",
95+
subtitle = null,
96+
isPublic = false,
97+
isMatrixOrg = false,
98+
)
99+
)
100+
sut.reset()
101+
val resetState = awaitItem()
102+
assertThat(resetState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
103+
}
104+
}
105+
106+
@Test
107+
fun `present - set url and reset`() = runTest {
108+
val sut = AccountProviderDataSource(FakeEnterpriseService())
109+
sut.flow.test {
110+
val initialState = awaitItem()
111+
assertThat(initialState.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
112+
sut.setUrl(url = "https://example.com")
90113
val changedState = awaitItem()
91114
assertThat(changedState).isEqualTo(
92115
AccountProvider(

features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
package io.element.android.features.login.impl.screens.onboarding
99

1010
import com.google.common.truth.Truth.assertThat
11+
import io.element.android.appconfig.AuthenticationConfig
1112
import io.element.android.appconfig.OnBoardingConfig
1213
import io.element.android.features.enterprise.api.EnterpriseService
1314
import io.element.android.features.enterprise.test.FakeEnterpriseService
1415
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
16+
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
1517
import io.element.android.features.login.impl.login.LoginHelper
1618
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
1719
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
@@ -24,6 +26,7 @@ import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
2426
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_3
2527
import io.element.android.libraries.matrix.test.AN_EXCEPTION
2628
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
29+
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2
2730
import io.element.android.libraries.matrix.test.A_LOGIN_HINT
2831
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
2932
import io.element.android.libraries.matrix.test.core.aBuildMeta
@@ -36,6 +39,7 @@ import io.element.android.libraries.wellknown.api.WellknownRetriever
3639
import io.element.android.tests.testutils.WarmUpRule
3740
import io.element.android.tests.testutils.test
3841
import kotlinx.coroutines.flow.Flow
42+
import kotlinx.coroutines.flow.first
3943
import kotlinx.coroutines.flow.flowOf
4044
import kotlinx.coroutines.test.runTest
4145
import org.junit.Rule
@@ -219,6 +223,7 @@ class OnBoardingPresenterTest {
219223
Result.failure(AN_EXCEPTION)
220224
},
221225
)
226+
val accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService())
222227
val presenter = createPresenter(
223228
params = OnBoardingNode.Params(
224229
accountProvider = A_HOMESERVER_URL,
@@ -230,14 +235,17 @@ class OnBoardingPresenterTest {
230235
loginHelper = createLoginHelper(
231236
authenticationService = authenticationService,
232237
),
238+
accountProviderDataSource = accountProviderDataSource,
233239
)
234240
presenter.test {
235241
skipItems(3)
236242
awaitItem().also {
237243
assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL)
238-
it.eventSink(OnBoardingEvents.OnSignIn(A_HOMESERVER_URL))
244+
assertThat(accountProviderDataSource.flow.first().url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
245+
it.eventSink(OnBoardingEvents.OnSignIn(A_HOMESERVER_URL_2))
239246
skipItems(1) // Loading
240-
247+
// Account data source has been updated
248+
assertThat(accountProviderDataSource.flow.first().url).isEqualTo(A_HOMESERVER_URL_2)
241249
// Check an error was returned
242250
val submittedState = awaitItem()
243251
assertThat(submittedState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
@@ -260,6 +268,7 @@ private fun createPresenter(
260268
loginHelper: LoginHelper = createLoginHelper(),
261269
onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider = OnBoardingLogoResIdProvider { null },
262270
sessionStore: SessionStore = InMemorySessionStore(),
271+
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
263272
) = OnBoardingPresenter(
264273
params = params,
265274
buildMeta = buildMeta,
@@ -272,6 +281,7 @@ private fun createPresenter(
272281
loginHelper = loginHelper,
273282
onBoardingLogoResIdProvider = onBoardingLogoResIdProvider,
274283
sessionStore = sessionStore,
284+
accountProviderDataSource = accountProviderDataSource,
275285
)
276286

277287
fun createLoginHelper(

0 commit comments

Comments
 (0)