mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Let users enter their full Matrix user ID on the account provider screen
This commit is contained in:
+16
-2
@@ -30,6 +30,7 @@ import io.element.android.features.login.impl.login.LoginModeEvent
|
|||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
|
import io.element.android.libraries.matrix.api.core.MatrixPatterns
|
||||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
@@ -87,6 +88,11 @@ class ConfirmAccountProviderPresenter(
|
|||||||
var submittedAccountProvider by remember { mutableStateOf<String?>(null) }
|
var submittedAccountProvider by remember { mutableStateOf<String?>(null) }
|
||||||
val latestSubmittedAccountProvider by rememberUpdatedState(submittedAccountProvider)
|
val latestSubmittedAccountProvider by rememberUpdatedState(submittedAccountProvider)
|
||||||
|
|
||||||
|
// A login hint (the full Matrix user ID) captured when the user enters one, so the authentication
|
||||||
|
// server can pre-fill their identity. Null when a plain account provider was entered.
|
||||||
|
var submittedLoginHint by remember { mutableStateOf<String?>(null) }
|
||||||
|
val latestSubmittedLoginHint by rememberUpdatedState(submittedLoginHint)
|
||||||
|
|
||||||
// Once the chosen account provider has been validated and persisted, proceed with the actual sign in / sign up.
|
// Once the chosen account provider has been validated and persisted, proceed with the actual sign in / sign up.
|
||||||
// Reuse the details resolved while validating, so login does not configure (and re-network) the homeserver again.
|
// Reuse the details resolved while validating, so login does not configure (and re-network) the homeserver again.
|
||||||
LaunchedEffect(changeServerState.changeServerAction) {
|
LaunchedEffect(changeServerState.changeServerAction) {
|
||||||
@@ -97,7 +103,7 @@ class ConfirmAccountProviderPresenter(
|
|||||||
isAccountCreation = params.isAccountCreation,
|
isAccountCreation = params.isAccountCreation,
|
||||||
homeserverUrl = latestSubmittedAccountProvider.orEmpty().trim(),
|
homeserverUrl = latestSubmittedAccountProvider.orEmpty().trim(),
|
||||||
resolvedHomeserverUrl = null,
|
resolvedHomeserverUrl = null,
|
||||||
loginHint = null,
|
loginHint = latestSubmittedLoginHint,
|
||||||
preConfiguredDetails = homeServerDetails,
|
preConfiguredDetails = homeServerDetails,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -116,8 +122,16 @@ class ConfirmAccountProviderPresenter(
|
|||||||
// renders the full server rather than the typed prefix, and survives the OAuth round-trip.
|
// renders the full server rather than the typed prefix, and survives the OAuth round-trip.
|
||||||
val accountProvider = event.accountProvider.trim()
|
val accountProvider = event.accountProvider.trim()
|
||||||
userInput = accountProvider
|
userInput = accountProvider
|
||||||
|
// If the user entered a full Matrix user ID (e.g. "@alice:example.org"), sign in to its
|
||||||
|
// homeserver and pass the ID as a login hint so the authentication server can pre-fill it.
|
||||||
|
// Otherwise treat the input as the account provider host. Only the homeserver is ever
|
||||||
|
// persisted to history, never the user ID.
|
||||||
|
val isUserId = MatrixPatterns.isUserId(accountProvider)
|
||||||
|
// For a user ID the homeserver is the domain part (after the ':'), keeping any port.
|
||||||
|
val host = if (isUserId) accountProvider.substringAfter(":") else accountProvider
|
||||||
|
submittedLoginHint = "mxid:$accountProvider".takeIf { isUserId && !params.isAccountCreation }
|
||||||
// Default the scheme to https:// so entering a bare host (e.g. "matrix.org") works.
|
// Default the scheme to https:// so entering a bare host (e.g. "matrix.org") works.
|
||||||
val accountProviderUrl = accountProvider.ensureProtocol()
|
val accountProviderUrl = host.ensureProtocol()
|
||||||
submittedAccountProvider = accountProviderUrl
|
submittedAccountProvider = accountProviderUrl
|
||||||
changeServerState.eventSink(
|
changeServerState.eventSink(
|
||||||
ChangeServerEvents.ChangeServer(AccountProvider(url = accountProviderUrl))
|
ChangeServerEvents.ChangeServer(AccountProvider(url = accountProviderUrl))
|
||||||
|
|||||||
+72
@@ -460,6 +460,78 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - continue with a full matrix id signs in to its homeserver and passes a login hint`() = runTest {
|
||||||
|
val submittedUrls = mutableListOf<String>()
|
||||||
|
val authenticationService = FakeMatrixAuthenticationService(
|
||||||
|
setHomeserverResult = { url ->
|
||||||
|
submittedUrls.add(url)
|
||||||
|
Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org"))
|
||||||
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
|
assertThat(submittedUrls.first()).isEqualTo("https://example.org")
|
||||||
|
assertThat(authenticationService.getOAuthUrlLoginHint).isEqualTo("mxid:@alice:example.org")
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - continue with a matrix id keeps the port in the homeserver`() = runTest {
|
||||||
|
val submittedUrls = mutableListOf<String>()
|
||||||
|
val authenticationService = FakeMatrixAuthenticationService(
|
||||||
|
setHomeserverResult = { url ->
|
||||||
|
submittedUrls.add(url)
|
||||||
|
Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org:8448"))
|
||||||
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
|
assertThat(submittedUrls.first()).isEqualTo("https://example.org:8448")
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - continue with a plain account provider passes no login hint`() = runTest {
|
||||||
|
val authenticationService = FakeMatrixAuthenticationService(
|
||||||
|
setHomeserverResult = { Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true)) },
|
||||||
|
)
|
||||||
|
val presenter = createConfirmAccountProviderPresenter(matrixAuthenticationService = authenticationService)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue("matrix.org"))
|
||||||
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
|
assertThat(authenticationService.getOAuthUrlLoginHint).isNull()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - continue with a matrix id during account creation passes no login hint`() = runTest {
|
||||||
|
val authenticationService = FakeMatrixAuthenticationService(
|
||||||
|
setHomeserverResult = { Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true)) },
|
||||||
|
)
|
||||||
|
val presenter = createConfirmAccountProviderPresenter(
|
||||||
|
params = ConfirmAccountProviderPresenter.Params(isAccountCreation = true),
|
||||||
|
matrixAuthenticationService = authenticationService,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue("@alice:example.org"))
|
||||||
|
awaitLoginMode { it is AsyncData.Success }
|
||||||
|
assertThat(authenticationService.getOAuthUrlLoginHint).isNull()
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Awaits until the emitted state's login mode matches [predicate], skipping the intermediate
|
* Awaits until the emitted state's login mode matches [predicate], skipping the intermediate
|
||||||
* account-provider validation states, and returns that state.
|
* account-provider validation states, and returns that state.
|
||||||
|
|||||||
+5
@@ -70,10 +70,15 @@ class FakeMatrixAuthenticationService(
|
|||||||
return importCreatedSessionLambda(externalSession)
|
return importCreatedSessionLambda(externalSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The login hint passed to the most recent [getOAuthUrl] call. */
|
||||||
|
var getOAuthUrlLoginHint: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
override suspend fun getOAuthUrl(
|
override suspend fun getOAuthUrl(
|
||||||
prompt: OAuthPrompt,
|
prompt: OAuthPrompt,
|
||||||
loginHint: String?,
|
loginHint: String?,
|
||||||
): Result<OAuthDetails> = simulateLongTask {
|
): Result<OAuthDetails> = simulateLongTask {
|
||||||
|
getOAuthUrlLoginHint = loginHint
|
||||||
oAuthError?.let { Result.failure(it) } ?: Result.success(AN_OAUTH_DATA)
|
oAuthError?.let { Result.failure(it) } ?: Result.success(AN_OAUTH_DATA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user