Login screens: Add 3Pid step 1

This commit is contained in:
Benoit Marty 2019-11-20 15:51:31 +01:00
parent 23315ede92
commit 1f161b7e23
11 changed files with 277 additions and 42 deletions

View File

@ -0,0 +1,22 @@
/*
* 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.api.auth.registration
sealed class RegisterThreePid {
data class Email(val email: String) : RegisterThreePid()
data class Msisdn(val msisdn: String, val countryCode: String) : RegisterThreePid()
}

View File

@ -31,9 +31,7 @@ interface RegistrationWizard {
fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable
fun addEmail(email: String, callback: MatrixCallback<RegistrationResult>): Cancelable
fun addMsisdn(msisdn: String, callback: MatrixCallback<RegistrationResult>): Cancelable
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<Unit>): Cancelable
fun confirmMsisdn(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable

View File

@ -19,13 +19,12 @@ 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.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.*
/**
* The login REST API.
@ -39,6 +38,14 @@ internal interface AuthAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
fun register(@Body registrationParams: RegistrationParams): Call<Credentials>
/**
* Add 3Pid during registration
* Ref: https://gist.github.com/jryans/839a09bf0c5a70e2f36ed990d50ed928
* https://github.com/matrix-org/matrix-doc/pull/2290
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken")
fun add3Pid(@Path("threePid") threePid: String, @Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse>
/**
* Get the supported login flow
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login

View File

@ -0,0 +1,101 @@
/*
* 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
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
/**
* Add a three Pid during authentication
*/
@JsonClass(generateAdapter = true)
internal data class AddThreePidRegistrationParams(
/**
* Required. A unique string generated by the client, and used to identify the validation attempt.
* It must be a string consisting of the characters [0-9a-zA-Z.=_-]. Its length must not exceed 255 characters and it must not be empty.
*/
@Json(name = "client_secret")
val clientSecret: String,
/**
* Required. The server will only send an email if the send_attempt is a number greater than the most recent one which it has seen,
* scoped to that email + client_secret pair. This is to avoid repeatedly sending the same email in the case of request retries between
* the POSTing user and the identity server. The client should increment this value if they desire a new email (e.g. a reminder) to be sent.
* If they do not, the server should respond with success but not resend the email.
*/
@Json(name = "send_attempt")
val sendAttempt: Int,
/**
* Optional. When the validation is completed, the identity server will redirect the user to this URL. This option is ignored when
* submitting 3PID validation information through a POST request.
*/
@Json(name = "next_link")
val nextLink: String? = null,
/**
* Required. The hostname of the identity server to communicate with. May optionally include a port.
* This parameter is ignored when the homeserver handles 3PID verification.
*/
@Json(name = "id_server")
val id_server: String? = null,
/* ==========================================================================================
* For emails
* ========================================================================================== */
/**
* Required. The email address to validate.
*/
@Json(name = "email")
val email: String? = null,
/* ==========================================================================================
* For Msisdn
* ========================================================================================== */
/**
* Required. The two-letter uppercase ISO country code that the number in phone_number should be parsed as if it were dialled from.
*/
@Json(name = "country")
val countryCode: String? = null,
/**
* Required. The phone number to validate.
*/
@Json(name = "phone_number")
val msisdn: String? = null
) {
companion object {
fun from(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationParams {
return when (params.threePid) {
is RegisterThreePid.Email -> AddThreePidRegistrationParams(
email = params.threePid.email,
clientSecret = params.clientSecret,
sendAttempt = params.sendAttempt
)
is RegisterThreePid.Msisdn -> AddThreePidRegistrationParams(
msisdn = params.threePid.msisdn,
countryCode = params.threePid.countryCode,
clientSecret = params.clientSecret,
sendAttempt = params.sendAttempt
)
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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)
internal data class AddThreePidRegistrationResponse(
/**
* Required. The session ID. Session IDs are opaque strings that must consist entirely of the characters [0-9a-zA-Z.=_-].
* Their length must not exceed 255 characters and they must not be empty.
*/
@Json(name = "sid")
val sid: String,
/**
* An optional field containing a URL where the client must submit the validation token to, with identical parameters to the Identity
* Service API's POST /validate/email/submitToken endpoint. The homeserver must send this token to the user (if applicable),
* who should then be prompted to provide it to the client.
*
* If this field is not present, the client can assume that verification will happen without the client's involvement provided
* the homeserver advertises this specification version in the /versions response (ie: r0.5.0).
*/
@Json(name = "submit_url")
val submitUrl: String? = null
)

View File

@ -20,6 +20,7 @@ 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.data.SessionParams
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
import im.vector.matrix.android.api.failure.Failure
@ -35,17 +36,25 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import java.util.*
/**
* 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 sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager) : RegistrationWizard {
private var clientSecret = UUID.randomUUID().toString()
private var sendAttempt = 0
private var currentSession: String? = null
private val authAPI = buildAuthAPI()
private val registerTask = DefaultRegisterTask(authAPI)
private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
return performRegistrationRequest(RegistrationParams(), callback)
@ -89,32 +98,27 @@ internal class DefaultRegistrationWizard(private val homeServerConnectionConfig:
), callback)
}
override fun addEmail(email: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = currentSession ?: run {
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<Unit>): Cancelable {
if (currentSession == null) {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
}
// TODO
return performRegistrationRequest(
RegistrationParams(
// TODO
auth = AuthParams.createForEmailIdentity(safeSession, ThreePidCredentials(email))
), callback)
}
override fun addMsisdn(msisdn: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
val safeSession = currentSession ?: run {
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
return NoOpCancellable
val job = GlobalScope.launch(coroutineDispatchers.main) {
val result = runCatching {
registerAddThreePidTask.execute(RegisterAddThreePidTask.Params(threePid, clientSecret, sendAttempt++))
}
result.fold(
{
// TODO Do something with the data return by the hs?
callback.onSuccess(Unit)
},
{
callback.onFailure(it)
}
)
}
// TODO
return performRegistrationRequest(
RegistrationParams(
// TODO
auth = AuthParams.createForEmailIdentity(safeSession, ThreePidCredentials(msisdn))
), callback)
return CancelableCoroutine(job)
}
override fun confirmMsisdn(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {

View File

@ -0,0 +1,47 @@
/*
* 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.api.auth.registration.RegisterThreePid
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 RegisterAddThreePidTask : Task<RegisterAddThreePidTask.Params, AddThreePidRegistrationResponse> {
data class Params(
val threePid: RegisterThreePid,
val clientSecret: String,
val sendAttempt: Int
)
}
internal class DefaultRegisterAddThreePidTask(private val authAPI: AuthAPI)
: RegisterAddThreePidTask {
override suspend fun execute(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationResponse {
return executeRequest {
apiCall = authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params))
}
}
private fun RegisterThreePid.toPath(): String {
return when (this) {
is RegisterThreePid.Email -> "email"
is RegisterThreePid.Msisdn -> "msisdn"
}
}
}

View File

@ -22,7 +22,6 @@ import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
internal interface RegisterTask : Task<RegisterTask.Params, Credentials> {
data class Params(
@ -30,7 +29,7 @@ internal interface RegisterTask : Task<RegisterTask.Params, Credentials> {
)
}
internal class DefaultRegisterTask @Inject constructor(private val authAPI: AuthAPI)
internal class DefaultRegisterTask(private val authAPI: AuthAPI)
: RegisterTask {
override suspend fun execute(params: RegisterTask.Params): Credentials {

View File

@ -17,6 +17,7 @@
package im.vector.riotx.features.login
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.riotx.core.platform.VectorViewModelAction
sealed class LoginAction : VectorViewModelAction {
@ -32,8 +33,8 @@ sealed class LoginAction : VectorViewModelAction {
open class RegisterAction : LoginAction()
data class RegisterWith(val username: String, val password: String, val initialDeviceName: String) : RegisterAction()
data class AddEmail(val email: String) : RegisterAction()
data class AddMsisdn(val msisdn: String) : RegisterAction()
data class AddThreePid(val threePid: RegisterThreePid) : RegisterAction()
// TODO Confirm Email (from link in the email)
data class ConfirmMsisdn(val code: String) : RegisterAction()
data class CaptchaDone(val captchaResponse: String) : RegisterAction()
object AcceptTerms : RegisterAction()

View File

@ -24,6 +24,7 @@ import androidx.core.view.isVisible
import butterknife.OnClick
import com.airbnb.mvrx.args
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
import im.vector.riotx.R
import im.vector.riotx.core.error.ErrorFormatter
import kotlinx.android.parcel.Parcelize
@ -112,10 +113,11 @@ class LoginGenericTextInputFormFragment @Inject constructor(private val errorFor
} else {
when (params.mode) {
TextInputFormFragmentMode.SetEmail -> {
loginViewModel.handle(LoginAction.AddEmail(text))
loginViewModel.handle(LoginAction.AddThreePid(RegisterThreePid.Email(text)))
}
TextInputFormFragmentMode.SetMsisdn -> {
loginViewModel.handle(LoginAction.AddMsisdn(text))
// TODO Country code
loginViewModel.handle(LoginAction.AddThreePid(RegisterThreePid.Msisdn(text, "TODO")))
}
TextInputFormFragmentMode.ConfirmMsisdn -> {
loginViewModel.handle(LoginAction.ConfirmMsisdn(text))

View File

@ -106,8 +106,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
is LoginAction.CaptchaDone -> handleCaptchaDone(action)
is LoginAction.AcceptTerms -> handleAcceptTerms()
is LoginAction.RegisterDummy -> handleRegisterDummy()
is LoginAction.AddEmail -> handleAddEmail(action)
is LoginAction.AddMsisdn -> handleAddMsisdn(action)
is LoginAction.AddThreePid -> handleAddThreePid(action)
is LoginAction.ConfirmMsisdn -> handleConfirmMsisdn(action)
}
}
@ -149,14 +148,28 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
}
}
private fun handleAddMsisdn(action: LoginAction.AddMsisdn) {
private fun handleAddThreePid(action: LoginAction.AddThreePid) {
// TODO Use the same async?
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.addMsisdn(action.msisdn, registrationCallback)
}
currentTask = registrationWizard?.addThreePid(action.threePid, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// TODO Notify the View
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
private fun handleAddEmail(action: LoginAction.AddEmail) {
setState { copy(asyncRegistration = Loading()) }
currentTask = registrationWizard?.addEmail(action.email, registrationCallback)
override fun onFailure(failure: Throwable) {
_viewEvents.post(LoginViewEvents.RegistrationError(failure))
setState {
copy(
asyncRegistration = Uninitialized
)
}
}
})
}
private fun handleAcceptTerms() {