Login screens: persist all data during login or registration

This commit is contained in:
Benoit Marty 2019-11-27 14:26:06 +01:00
parent 0c4e0890b1
commit 2e3763e8b4
13 changed files with 417 additions and 89 deletions

View file

@ -38,14 +38,19 @@ interface AuthenticationService {
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
/**
* Return a LoginWizard, to login to the homeserver
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
*/
fun createLoginWizard(): LoginWizard
fun getLoginWizard(): LoginWizard
/**
* Return a RegistrationWizard, to create an matrix account on the homeserver
* Return a RegistrationWizard, to create an matrix account on the homeserver. The login flow has to be retrieved first.
*/
fun getOrCreateRegistrationWizard(): RegistrationWizard
fun getRegistrationWizard(): RegistrationWizard
/**
* Cancel pending login or pending registration
*/
fun cancelPendingLoginOrRegistration()
/**
* Check if there is an authenticated [Session].

View file

@ -22,6 +22,7 @@ import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.AuthDatabase
@ -50,6 +51,7 @@ internal abstract class AuthModule {
}
.name("matrix-sdk-auth.realm")
.modules(AuthRealmModule())
// TODO Migration !!!! (test it)
.deleteRealmIfMigrationNeeded()
.build()
}
@ -58,6 +60,9 @@ internal abstract class AuthModule {
@Binds
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
@Binds
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
@Binds
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService

View file

@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
import im.vector.matrix.android.internal.di.Unauthenticated
@ -46,10 +47,14 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager,
private val sessionCreator: SessionCreator
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore
) : AuthenticationService {
private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
private var currentLoginWizard: LoginWizard? = null
private var currentRegistrationWizard: RegistrationWizard? = null
override fun hasAuthenticatedSessions(): Boolean {
return sessionParamsStore.getLast() != null
@ -67,9 +72,9 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
}
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
currentHomeServerConnectionConfig = null
return GlobalScope.launch(coroutineDispatchers.main) {
pendingSessionStore.delete()
val result = runCatching {
getLoginFlowInternal(homeServerConnectionConfig)
}
@ -77,7 +82,8 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
{
if (it is LoginFlowResult.Success) {
// The homeserver exists and up to date, keep the config
currentHomeServerConnectionConfig = homeServerConnectionConfig
pendingSessionData = PendingSessionData(homeServerConnectionConfig)
.also { data -> pendingSessionStore.savePendingSessionData(data) }
}
callback.onSuccess(it)
},
@ -109,29 +115,55 @@ internal class DefaultAuthenticationService @Inject constructor(@Unauthenticated
}
}
override fun getOrCreateRegistrationWizard(): RegistrationWizard {
currentHomeServerConnectionConfig?.let {
// TODO Persist the wizard?
return DefaultRegistrationWizard(
it,
okHttpClient,
retrofitFactory,
coroutineDispatchers,
sessionCreator
)
} ?: error("Please call getLoginFlow() with success first")
override fun getRegistrationWizard(): RegistrationWizard {
return currentRegistrationWizard
?: let {
pendingSessionData?.homeServerConnectionConfig?.let {
DefaultRegistrationWizard(
okHttpClient,
retrofitFactory,
coroutineDispatchers,
sessionCreator,
pendingSessionStore
).also {
currentRegistrationWizard = it
}
} ?: error("Please call getLoginFlow() with success first")
}
}
override fun createLoginWizard(): LoginWizard {
currentHomeServerConnectionConfig?.let {
return DefaultLoginWizard(
it,
okHttpClient,
retrofitFactory,
coroutineDispatchers,
sessionCreator
)
} ?: error("Please call getLoginFlow() with success first")
override fun getLoginWizard(): LoginWizard {
return currentLoginWizard
?: let {
pendingSessionData?.homeServerConnectionConfig?.let {
DefaultLoginWizard(
okHttpClient,
retrofitFactory,
coroutineDispatchers,
sessionCreator,
pendingSessionStore
).also {
currentLoginWizard = it
}
} ?: error("Please call getLoginFlow() with success first")
}
}
override fun cancelPendingLoginOrRegistration() {
currentLoginWizard = null
currentRegistrationWizard = null
GlobalScope.launch(coroutineDispatchers.main) {
// Keep only the home sever config
pendingSessionData?.homeServerConnectionConfig
?.let {
pendingSessionStore.savePendingSessionData(PendingSessionData(it))
}
?: run {
// Should not happen
pendingSessionStore.delete()
}
}
}
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,

View file

@ -0,0 +1,31 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.internal.auth.db.PendingSessionData
/**
* Store for elements when doing login or registration
*/
internal interface PendingSessionStore {
suspend fun savePendingSessionData(pendingSessionData: PendingSessionData)
fun getPendingSessionData(): PendingSessionData?
suspend fun delete()
}

View file

@ -31,7 +31,8 @@ internal interface SessionCreator {
internal class DefaultSessionCreator @Inject constructor(
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager
private val sessionManager: SessionManager,
private val pendingSessionStore: PendingSessionStore
) : SessionCreator {
/**
@ -39,6 +40,9 @@ internal class DefaultSessionCreator @Inject constructor(
* identity server url if provided in the credentials
*/
override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
// We can cleanup the pending session params
pendingSessionStore.delete()
val sessionParams = SessionParams(
credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.copy(

View file

@ -23,6 +23,7 @@ import io.realm.annotations.RealmModule
*/
@RealmModule(library = true,
classes = [
SessionParamsEntity::class
SessionParamsEntity::class,
PendingSessionEntity::class
])
internal class AuthRealmModule

View file

@ -0,0 +1,49 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
import im.vector.matrix.android.internal.auth.registration.ThreePidData
import java.util.*
/**
* This class holds all pending data when creating a session, either by login or by register
*/
internal data class PendingSessionData(
val homeServerConnectionConfig: HomeServerConnectionConfig,
/* ==========================================================================================
* Common
* ========================================================================================== */
val clientSecret: String = UUID.randomUUID().toString(),
val sendAttempt: Int = 0,
/* ==========================================================================================
* For login
* ========================================================================================== */
val resetPasswordData: ResetPasswordData? = null,
/* ==========================================================================================
* For register
* ========================================================================================== */
val currentSession: String? = null,
val currentThreePidData: ThreePidData? = null
)

View file

@ -0,0 +1,28 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
import io.realm.RealmObject
internal open class PendingSessionEntity(
var homeServerConnectionConfigJson: String = "",
var clientSecret: String = "",
var sendAttempt: Int = 0,
var resetPasswordDataJson: String? = null,
var currentSession: String? = null,
var currentThreePidDataJson: String? = null
) : RealmObject()

View file

@ -0,0 +1,67 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
import im.vector.matrix.android.internal.auth.registration.ThreePidData
import javax.inject.Inject
internal class PendingSessionMapper @Inject constructor(moshi: Moshi) {
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
private val resetPasswordDataAdapter = moshi.adapter(ResetPasswordData::class.java)
private val threePidDataAdapter = moshi.adapter(ThreePidData::class.java)
fun map(entity: PendingSessionEntity?): PendingSessionData? {
if (entity == null) {
return null
}
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(entity.homeServerConnectionConfigJson)!!
val resetPasswordData = entity.resetPasswordDataJson?.let { resetPasswordDataAdapter.fromJson(it) }
val threePidData = entity.currentThreePidDataJson?.let { threePidDataAdapter.fromJson(it) }
return PendingSessionData(
homeServerConnectionConfig = homeServerConnectionConfig,
clientSecret = entity.clientSecret,
sendAttempt = entity.sendAttempt,
resetPasswordData = resetPasswordData,
currentSession = entity.currentSession,
currentThreePidData = threePidData)
}
fun map(sessionData: PendingSessionData?): PendingSessionEntity? {
if (sessionData == null) {
return null
}
val homeServerConnectionConfigJson = homeServerConnectionConfigAdapter.toJson(sessionData.homeServerConnectionConfig)
val resetPasswordDataJson = resetPasswordDataAdapter.toJson(sessionData.resetPasswordData)
val currentThreePidDataJson = threePidDataAdapter.toJson(sessionData.currentThreePidData)
return PendingSessionEntity(
homeServerConnectionConfigJson = homeServerConnectionConfigJson,
clientSecret = sessionData.clientSecret,
sendAttempt = sessionData.sendAttempt,
resetPasswordDataJson = resetPasswordDataJson,
currentSession = sessionData.currentSession,
currentThreePidDataJson = currentThreePidDataJson
)
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper,
@AuthDatabase
private val realmConfiguration: RealmConfiguration
) : PendingSessionStore {
override suspend fun savePendingSessionData(pendingSessionData: PendingSessionData) {
awaitTransaction(realmConfiguration) { realm ->
val entity = mapper.map(pendingSessionData)
if (entity != null) {
realm.where(PendingSessionEntity::class.java)
.findAll()
.deleteAllFromRealm()
realm.insert(entity)
}
}
}
override fun getPendingSessionData(): PendingSessionData? {
return Realm.getInstance(realmConfiguration).use { realm ->
realm
.where(PendingSessionEntity::class.java)
.findAll()
.map { mapper.map(it) }
.firstOrNull()
}
}
override suspend fun delete() {
awaitTransaction(realmConfiguration) {
it.where(PendingSessionEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
}
}

View file

@ -17,19 +17,21 @@
package im.vector.matrix.android.internal.auth.login
import android.util.Patterns
import com.squareup.moshi.JsonClass
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.NoOpCancellable
import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.SessionCreator
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask
@ -40,28 +42,25 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import java.util.*
// Container to store the data when a reset password is in the email validation step
@JsonClass(generateAdapter = true)
internal data class ResetPasswordData(
val newPassword: String,
val addThreePidRegistrationResponse: AddThreePidRegistrationResponse
)
internal class DefaultLoginWizard(
private val homeServerConnectionConfig: HomeServerConnectionConfig,
okHttpClient: Lazy<OkHttpClient>,
retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore
) : LoginWizard {
private var clientSecret = UUID.randomUUID().toString()
private var sendAttempt = 0
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
private var resetPasswordData: ResetPasswordData? = null
private val authAPI = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
private val authAPI = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
.create(AuthAPI::class.java)
override fun login(login: String,
@ -85,7 +84,7 @@ internal class DefaultLoginWizard(
apiCall = authAPI.login(loginParams)
}
sessionCreator.createSession(credentials, homeServerConnectionConfig)
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
@ -97,17 +96,25 @@ internal class DefaultLoginWizard(
private suspend fun resetPasswordInternal(email: String, newPassword: String) {
val param = RegisterAddThreePidTask.Params(
RegisterThreePid.Email(email),
clientSecret,
sendAttempt++
pendingSessionData.clientSecret,
pendingSessionData.sendAttempt
)
pendingSessionData = pendingSessionData.copy(
sendAttempt = pendingSessionData.sendAttempt + 1
).also { pendingSessionStore.savePendingSessionData(it) }
val result = executeRequest<AddThreePidRegistrationResponse> {
apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
}
resetPasswordData = ResetPasswordData(newPassword, result)
pendingSessionData = pendingSessionData.copy(
resetPasswordData = ResetPasswordData(newPassword, result)
).also { pendingSessionStore.savePendingSessionData(it) }
}
override fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable {
val safeResetPasswordData = resetPasswordData ?: run {
val safeResetPasswordData = pendingSessionData.resetPasswordData ?: run {
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
return NoOpCancellable
}
@ -118,7 +125,7 @@ internal class DefaultLoginWizard(
private suspend fun resetPasswordMailConfirmedInternal(resetPasswordData: ResetPasswordData) {
val param = ResetPasswordMailConfirmed.create(
clientSecret,
pendingSessionData.clientSecret,
resetPasswordData.addThreePidRegistrationResponse.sid,
resetPasswordData.newPassword
)

View file

@ -16,9 +16,9 @@
package im.vector.matrix.android.internal.auth.registration
import com.squareup.moshi.JsonClass
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.matrix.android.api.auth.registration.RegistrationResult
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
@ -27,50 +27,74 @@ import im.vector.matrix.android.api.failure.Failure.RegistrationFlowError
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.NoOpCancellable
import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.SessionCreator
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import okhttp3.OkHttpClient
import java.util.*
// Container to store the data when a three pid is in validation step
@JsonClass(generateAdapter = true)
internal data class ThreePidData(
val threePid: RegisterThreePid,
val email: String,
val msisdn: String,
val country: String,
val addThreePidRegistrationResponse: AddThreePidRegistrationResponse,
val registrationParams: RegistrationParams
)
) {
val threePid: RegisterThreePid
get() {
return if (email.isNotBlank()) {
RegisterThreePid.Email(email)
} else {
RegisterThreePid.Msisdn(msisdn, country)
}
}
companion object {
fun from(threePid: RegisterThreePid,
addThreePidRegistrationResponse: AddThreePidRegistrationResponse,
registrationParams: RegistrationParams): ThreePidData {
return when (threePid) {
is RegisterThreePid.Email ->
ThreePidData(threePid.email, "", "", addThreePidRegistrationResponse, registrationParams)
is RegisterThreePid.Msisdn ->
ThreePidData("", threePid.msisdn, threePid.countryCode, addThreePidRegistrationResponse, registrationParams)
}
}
}
}
/**
* This class execute the registration request and is responsible to keep the session of interactive authentication
*/
internal class DefaultRegistrationWizard(private val homeServerConnectionConfig: HomeServerConnectionConfig,
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator) : RegistrationWizard {
private var clientSecret = UUID.randomUUID().toString()
private var sendAttempt = 0
internal class DefaultRegistrationWizard(
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionCreator: SessionCreator,
private val pendingSessionStore: PendingSessionStore
) : RegistrationWizard {
private var currentSession: String? = null
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
private val authAPI = buildAuthAPI()
private val registerTask = DefaultRegisterTask(authAPI)
private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
private val validateCodeTask = DefaultValidateCodeTask(authAPI)
private var currentThreePidData: ThreePidData? = null
override val currentThreePid: String?
get() {
return when (val threePid = currentThreePidData?.threePid) {
return when (val threePid = pendingSessionData.currentThreePidData?.threePid) {
is RegisterThreePid.Email -> threePid.email
is RegisterThreePid.Msisdn -> {
// Take formatted msisdn if provided by the server
currentThreePidData?.addThreePidRegistrationResponse?.formattedMsisdn?.takeIf { it.isNotBlank() } ?: threePid.msisdn
pendingSessionData.currentThreePidData?.addThreePidRegistrationResponse?.formattedMsisdn?.takeIf { it.isNotBlank() } ?: threePid.msisdn
}
null -> null
}
@ -98,7 +122,7 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
}
override fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = currentSession ?: run {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
@ -109,7 +133,7 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
}
override fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = currentSession ?: run {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
@ -120,14 +144,16 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
}
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
currentThreePidData = null
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
.also { pendingSessionStore.savePendingSessionData(it) }
sendThreePid(threePid)
}
}
override fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeCurrentThreePid = currentThreePidData?.threePid ?: run {
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
@ -137,33 +163,43 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
}
private suspend fun sendThreePid(threePid: RegisterThreePid): RegistrationResult {
val safeSession = currentSession ?: throw IllegalStateException("developer error, call createAccount() method first")
val response = registerAddThreePidTask.execute(RegisterAddThreePidTask.Params(threePid, clientSecret, sendAttempt++))
val safeSession = pendingSessionData.currentSession ?: throw IllegalStateException("developer error, call createAccount() method first")
val response = registerAddThreePidTask.execute(
RegisterAddThreePidTask.Params(
threePid,
pendingSessionData.clientSecret,
pendingSessionData.sendAttempt))
pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1)
.also { pendingSessionStore.savePendingSessionData(it) }
val params = RegistrationParams(
auth = if (threePid is RegisterThreePid.Email) {
AuthParams.createForEmailIdentity(safeSession,
ThreePidCredentials(
clientSecret = clientSecret,
clientSecret = pendingSessionData.clientSecret,
sid = response.sid
)
)
} else {
AuthParams.createForMsisdnIdentity(safeSession,
ThreePidCredentials(
clientSecret = clientSecret,
clientSecret = pendingSessionData.clientSecret,
sid = response.sid
)
)
}
)
// Store data
currentThreePidData = ThreePidData(threePid, response, params)
pendingSessionData = pendingSessionData.copy(currentThreePidData = ThreePidData.from(threePid, response, params))
.also { pendingSessionStore.savePendingSessionData(it) }
// and send the sid a first time
return performRegistrationRequest(params)
}
override fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeParam = currentThreePidData?.registrationParams ?: run {
val safeParam = pendingSessionData.currentThreePidData?.registrationParams ?: run {
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
return NoOpCancellable
}
@ -179,11 +215,12 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
}
private suspend fun validateThreePid(code: String): RegistrationResult {
val registrationParams = currentThreePidData?.registrationParams ?: throw IllegalStateException("developer error, no pending three pid")
val safeCurrentData = currentThreePidData ?: throw IllegalStateException("developer error, call createAccount() method first")
val registrationParams = pendingSessionData.currentThreePidData?.registrationParams
?: throw IllegalStateException("developer error, no pending three pid")
val safeCurrentData = pendingSessionData.currentThreePidData ?: throw IllegalStateException("developer error, call createAccount() method first")
val url = safeCurrentData.addThreePidRegistrationResponse.submitUrl ?: throw IllegalStateException("Missing url the send the code")
val validationBody = ValidationCodeBody(
clientSecret = clientSecret,
clientSecret = pendingSessionData.clientSecret,
sid = safeCurrentData.addThreePidRegistrationResponse.sid,
code = code
)
@ -199,7 +236,7 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
}
override fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = currentSession ?: run {
val safeSession = pendingSessionData.currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
@ -216,19 +253,20 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
registerTask.execute(RegisterTask.Params(registrationParams))
} catch (exception: Throwable) {
if (exception is RegistrationFlowError) {
currentSession = exception.registrationFlowResponse.session
pendingSessionData = pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session)
.also { pendingSessionStore.savePendingSessionData(it) }
return RegistrationResult.FlowResponse(exception.registrationFlowResponse.toFlowResult())
} else {
throw exception
}
}
val session = sessionCreator.createSession(credentials, homeServerConnectionConfig)
val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return RegistrationResult.Success(session)
}
private fun buildAuthAPI(): AuthAPI {
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
val retrofit = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)
}
}

View file

@ -73,8 +73,11 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
var isRegistrationStarted: Boolean = false
private set
private var registrationWizard: RegistrationWizard? = null
private var loginWizard: LoginWizard? = null
private val registrationWizard: RegistrationWizard?
get() = authenticationService.getRegistrationWizard()
private val loginWizard: LoginWizard?
get() = authenticationService.getLoginWizard()
private var loginConfig: LoginConfig? = null
@ -254,8 +257,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
}
}
LoginAction.ResetHomeServerUrl -> {
registrationWizard = null
loginWizard = null
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(
@ -449,13 +451,12 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
)
}
registrationWizard = authenticationService.getOrCreateRegistrationWizard()
currentTask = registrationWizard?.getRegistrationFlow(registrationCallback)
}
private fun startAuthenticationFlow() {
loginWizard = authenticationService.createLoginWizard()
// No op
loginWizard
}
private fun onFlowResponse(flowResult: FlowResult) {
@ -507,8 +508,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
} else {
currentTask?.cancel()
currentTask = null
loginWizard = null
registrationWizard = null
authenticationService.cancelPendingLoginOrRegistration()
setState {
copy(