Login screens: add MSISDN

This commit is contained in:
Benoit Marty 2019-11-20 18:58:47 +01:00
parent 248a584e1a
commit 127916a8d9
13 changed files with 179 additions and 32 deletions

View file

@ -33,9 +33,9 @@ interface RegistrationWizard {
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable
fun confirmMsisdn(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
fun validateEmail(callback: MatrixCallback<RegistrationResult>): Cancelable
fun checkIfEmailHasBeenValidated(callback: MatrixCallback<RegistrationResult>): Cancelable
val currentThreePid: String?
}

View file

@ -34,6 +34,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
// When server send an error, but it cannot be interpreted as a MatrixError
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException(errorBody))

View file

@ -19,9 +19,7 @@ package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
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.RegistrationParams
import im.vector.matrix.android.internal.auth.registration.*
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.*
@ -46,6 +44,12 @@ internal interface AuthAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken")
fun add3Pid(@Path("threePid") threePid: String, @Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse>
/**
* Validate 3pid
*/
@POST
fun validate3Pid(@Url url: String, @Body params: ValidationCodeBody): Call<SuccessResult>
/**
* Get the supported login flow
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login

View file

@ -61,6 +61,10 @@ internal data class AuthParams(
)
}
/**
* Note that there is a bug in Synapse (I have to investigate where), but if we pass LoginFlowTypes.MSISDN,
* the homeserver answer with the login flow with MatrixError fields and not with a simple MatrixError 401.
*/
fun createForMsisdnIdentity(session: String, threePidCredentials: ThreePidCredentials): AuthParams {
return AuthParams(
type = LoginFlowTypes.MSISDN,

View file

@ -63,6 +63,7 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
private val authAPI = buildAuthAPI()
private val registerTask = DefaultRegisterTask(authAPI)
private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
private val validateCodeTask = DefaultValidateCodeTask(authAPI)
private var currentThreePidData: ThreePidData? = null
@ -168,7 +169,7 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
return CancelableCoroutine(job)
}
override fun validateEmail(callback: MatrixCallback<RegistrationResult>): Cancelable {
override fun checkIfEmailHasBeenValidated(callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeParam = currentThreePidData?.registrationParams ?: run {
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
return NoOpCancellable
@ -178,18 +179,49 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
return performRegistrationRequest(safeParam, callback, 10_000)
}
override fun confirmMsisdn(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = currentSession ?: run {
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeParam = currentThreePidData?.registrationParams ?: run {
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
return NoOpCancellable
}
val safeCurrentData = currentThreePidData ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
// TODO
return performRegistrationRequest(
RegistrationParams(
// TODO
auth = AuthParams.createForEmailIdentity(safeSession, ThreePidCredentials(code))
), callback)
val url = safeCurrentData.addThreePidRegistrationResponse.submitUrl ?: run {
callback.onFailure(IllegalStateException("Missing url the send the code"))
return NoOpCancellable
}
val params = ValidationCodeBody(
clientSecret = clientSecret,
sid = safeCurrentData.addThreePidRegistrationResponse.sid,
code = code
)
val job = GlobalScope.launch(coroutineDispatchers.main) {
runCatching {
validateCodeTask.execute(ValidateCodeTask.Params(url, params))
}
.fold(
{
if (it.success == true) {
// The entered code is correct
// Same that validate email
performRegistrationRequest(safeParam, callback, 3_000)
} else {
// The code is not correct
callback.onFailure(Failure.SuccessError)
}
},
{
callback.onFailure(it)
}
)
}
return CancelableCoroutine(job)
}
override fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable {

View file

@ -0,0 +1,26 @@
/*
* 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.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SuccessResult(
@Json(name = "success")
val success: Boolean?
)

View file

@ -0,0 +1,38 @@
/*
* 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.registration
import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
internal interface ValidateCodeTask : Task<ValidateCodeTask.Params, SuccessResult> {
data class Params(
val url: String,
val body: ValidationCodeBody
)
}
internal class DefaultValidateCodeTask(private val authAPI: AuthAPI)
: ValidateCodeTask {
override suspend fun execute(params: ValidateCodeTask.Params): SuccessResult {
return executeRequest {
apiCall = authAPI.validate3Pid(params.url, params.body)
}
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This object is used to send a code received by SMS to validate Msisdn ownership
*/
@JsonClass(generateAdapter = true)
data class ValidationCodeBody(
@Json(name = "client_secret")
val clientSecret: String,
@Json(name = "sid")
val sid: String,
@Json(name = "token")
val code: String
)

View file

@ -35,8 +35,9 @@ sealed class LoginAction : VectorViewModelAction {
data class RegisterWith(val username: String, val password: String, val initialDeviceName: String) : RegisterAction()
data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction()
// TODO Confirm Email (from link in the email, open in the phone, intercepted by RiotX)
data class ConfirmMsisdn(val code: String) : RegisterAction()
object ValidateEmail : RegisterAction()
data class ValidateThreePid(val code: String) : RegisterAction()
object CheckIfEmailHasBeenValidated : RegisterAction()
data class CaptchaDone(val captchaResponse: String) : RegisterAction()
object AcceptTerms : RegisterAction()

View file

@ -121,10 +121,10 @@ class LoginGenericTextInputFormFragment @Inject constructor(private val errorFor
}
TextInputFormFragmentMode.SetMsisdn -> {
// TODO Country code
loginViewModel.handle(LoginAction.AddThreePid(RegisterThreePid.Msisdn(text, "TODO")))
loginViewModel.handle(LoginAction.AddThreePid(RegisterThreePid.Msisdn(text, "FR")))
}
TextInputFormFragmentMode.ConfirmMsisdn -> {
loginViewModel.handle(LoginAction.ConfirmMsisdn(text))
loginViewModel.handle(LoginAction.ValidateThreePid(text))
}
}
}
@ -163,7 +163,12 @@ class LoginGenericTextInputFormFragment @Inject constructor(private val errorFor
}
}
TextInputFormFragmentMode.ConfirmMsisdn -> {
// TODO
if (throwable is Failure.SuccessError) {
// The entered code is not correct
loginGenericTextInputFormTil.error = getString(R.string.login_validation_code_is_not_correct)
} else {
loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
}
}
}
}

View file

@ -105,25 +105,25 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
private fun handleRegisterAction(action: LoginAction.RegisterAction) {
when (action) {
is LoginAction.RegisterWith -> handleRegisterWith(action)
is LoginAction.CaptchaDone -> handleCaptchaDone(action)
is LoginAction.AcceptTerms -> handleAcceptTerms()
is LoginAction.RegisterDummy -> handleRegisterDummy()
is LoginAction.AddThreePid -> handleAddThreePid(action)
is LoginAction.ConfirmMsisdn -> handleConfirmMsisdn(action)
is LoginAction.ValidateEmail -> handleValidateEmail()
is LoginAction.RegisterWith -> handleRegisterWith(action)
is LoginAction.CaptchaDone -> handleCaptchaDone(action)
is LoginAction.AcceptTerms -> handleAcceptTerms()
is LoginAction.RegisterDummy -> handleRegisterDummy()
is LoginAction.AddThreePid -> handleAddThreePid(action)
is LoginAction.ValidateThreePid -> handleValidateThreePid(action)
is LoginAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailHasBeenValidated()
}
}
private fun handleValidateEmail() {
private fun handleCheckIfEmailHasBeenValidated() {
// We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
currentTask?.cancel()
currentTask = registrationWizard?.validateEmail(registrationCallback)
currentTask = registrationWizard?.checkIfEmailHasBeenValidated(registrationCallback)
}
private fun handleConfirmMsisdn(action: LoginAction.ConfirmMsisdn) {
private fun handleValidateThreePid(action: LoginAction.ValidateThreePid) {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.confirmMsisdn(action.code, registrationCallback)
currentTask = registrationWizard?.handleValidateThreePid(action.code, registrationCallback)
}
private val registrationCallback = object : MatrixCallback<RegistrationResult> {

View file

@ -49,7 +49,7 @@ class LoginWaitForEmailFragment @Inject constructor(private val errorFormatter:
setupUi()
loginViewModel.handle(LoginAction.ValidateEmail)
loginViewModel.handle(LoginAction.CheckIfEmailHasBeenValidated)
}
private fun setupUi() {
@ -59,7 +59,7 @@ class LoginWaitForEmailFragment @Inject constructor(private val errorFormatter:
override fun onRegistrationError(throwable: Throwable) {
if (throwable.is401()) {
// Try again, with a delay
loginViewModel.handle(LoginAction.ValidateEmail)
loginViewModel.handle(LoginAction.CheckIfEmailHasBeenValidated)
} else {
AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title_error)

View file

@ -110,5 +110,6 @@
<string name="login_wait_for_email_title">Please check your email</string>
<string name="login_wait_for_email_notice">We just sent an email to %1$s.\nPlease click on the link it contains to continue the account creation.</string>
<string name="login_validation_code_is_not_correct">The entered code is not correct. Please check.</string>
</resources>