mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
21 Commits
app-layout
...
feature/dl
Author | SHA1 | Date | |
---|---|---|---|
|
1d5b19e963 | ||
|
de24d73b95 | ||
|
a34a7be78d | ||
|
dc43479699 | ||
|
ed4606b113 | ||
|
87201cc972 | ||
|
883174852d | ||
|
b03d1a6270 | ||
|
4b1a3dc42e | ||
|
b3ccbdf66c | ||
|
d29a0dcbba | ||
|
9fa5722158 | ||
|
e6f6883e8e | ||
|
d086a32959 | ||
|
74669da5e5 | ||
|
c0e4edfb63 | ||
|
66511200ee | ||
|
cb31c2c83a | ||
|
e3ad5ff308 | ||
|
c8d2682e0d | ||
|
3d25d72f2a |
1
changelog.d/4943.feature
Normal file
1
changelog.d/4943.feature
Normal file
@@ -0,0 +1 @@
|
||||
Support for refresh token auth.
|
@@ -53,15 +53,19 @@ interface AuthenticationService {
|
||||
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||
*
|
||||
* See [LoginWizard] for more details
|
||||
* @param enableRefreshTokenAuth whether to enable refresh token based auth (in beta, hence default is false for now)
|
||||
* instead of long lived access tokens.
|
||||
*/
|
||||
fun getLoginWizard(): LoginWizard
|
||||
fun getLoginWizard(enableRefreshTokenAuth: Boolean = false): LoginWizard
|
||||
|
||||
/**
|
||||
* Return a RegistrationWizard, to create an matrix account on the homeserver. The login flow has to be retrieved first.
|
||||
*
|
||||
* See [RegistrationWizard] for more details.
|
||||
* @param enableRefreshTokenAuth whether to enable refresh token based auth (in beta, hence default is false for now)
|
||||
* instead of long lived access tokens.
|
||||
*/
|
||||
fun getRegistrationWizard(): RegistrationWizard
|
||||
fun getRegistrationWizard(enableRefreshTokenAuth: Boolean = false): RegistrationWizard
|
||||
|
||||
/**
|
||||
* True when login and password has been sent with success to the homeserver
|
||||
@@ -109,11 +113,16 @@ interface AuthenticationService {
|
||||
* @param matrixId the matrixId of the user
|
||||
* @param password the password of the account
|
||||
* @param initialDeviceName the initial device name
|
||||
* @param enableRefreshTokenAuth whether to enable refresh token based auth (in beta, hence default is false for now)
|
||||
* instead of long lived access tokens.
|
||||
* @param deviceId the device id, optional. If not provided or null, the server will generate one.
|
||||
*/
|
||||
suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
deviceId: String? = null): Session
|
||||
suspend fun directAuthentication(
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
enableRefreshTokenAuth: Boolean = false,
|
||||
deviceId: String? = null,
|
||||
): Session
|
||||
}
|
||||
|
@@ -36,6 +36,15 @@ data class Credentials(
|
||||
* An access token for the account. This access token can then be used to authorize other requests.
|
||||
*/
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
/**
|
||||
* The interval in milliseconds that the access token will expire in.
|
||||
*/
|
||||
@Json(name = "expires_in_ms") val expiresInMs: Long?,
|
||||
/**
|
||||
* The timestamp that indicates when the access token will expire.
|
||||
* It is estimated using `expiresInMs` when the SDK retrieves it from the homeserver.
|
||||
*/
|
||||
@Json(name = "expiry_ts") val expiryTs: Long?,
|
||||
/**
|
||||
* Not documented
|
||||
*/
|
||||
|
@@ -24,6 +24,11 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import java.io.IOException
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
fun Throwable.isTokenUnknownError() =
|
||||
this is Failure.ServerError &&
|
||||
httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED &&
|
||||
error.code == MatrixError.M_UNKNOWN_TOKEN
|
||||
|
||||
fun Throwable.is401() =
|
||||
this is Failure.ServerError &&
|
||||
httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED && /* 401 */
|
||||
|
@@ -20,7 +20,7 @@ import org.matrix.android.sdk.internal.network.ssl.Fingerprint
|
||||
|
||||
// This class will be sent to the bus
|
||||
sealed class GlobalError {
|
||||
data class InvalidToken(val softLogout: Boolean) : GlobalError()
|
||||
data class InvalidToken(val softLogout: Boolean, val refreshTokenAuth: Boolean, val errorCode: String, val errorReason: String) : GlobalError()
|
||||
data class ConsentNotGivenError(val consentUri: String) : GlobalError()
|
||||
data class CertificateError(val fingerprint: Fingerprint) : GlobalError()
|
||||
object ExpiredAccount : GlobalError()
|
||||
|
@@ -27,7 +27,7 @@ interface SignOutService {
|
||||
* Ask the homeserver for a new access token.
|
||||
* The same deviceId will be used
|
||||
*/
|
||||
suspend fun signInAgain(password: String)
|
||||
suspend fun signInAgain(password: String, enableRefreshTokenAuth: Boolean)
|
||||
|
||||
/**
|
||||
* Update the session with credentials received after SSO
|
||||
|
@@ -60,7 +60,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val getWellknownTask: GetWellknownTask,
|
||||
private val directLoginTask: DirectLoginTask
|
||||
private val directLoginTask: DirectLoginTask,
|
||||
) : AuthenticationService {
|
||||
|
||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||
@@ -308,14 +308,15 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
override fun getRegistrationWizard(): RegistrationWizard {
|
||||
override fun getRegistrationWizard(enableRefreshTokenAuth: Boolean): RegistrationWizard {
|
||||
return currentRegistrationWizard
|
||||
?: let {
|
||||
pendingSessionData?.homeServerConnectionConfig?.let {
|
||||
DefaultRegistrationWizard(
|
||||
buildAuthAPI(it),
|
||||
sessionCreator,
|
||||
pendingSessionStore
|
||||
pendingSessionStore,
|
||||
enableRefreshTokenAuth
|
||||
).also {
|
||||
currentRegistrationWizard = it
|
||||
}
|
||||
@@ -326,14 +327,15 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
override val isRegistrationStarted: Boolean
|
||||
get() = currentRegistrationWizard?.isRegistrationStarted == true
|
||||
|
||||
override fun getLoginWizard(): LoginWizard {
|
||||
override fun getLoginWizard(enableRefreshTokenAuth: Boolean): LoginWizard {
|
||||
return currentLoginWizard
|
||||
?: let {
|
||||
pendingSessionData?.homeServerConnectionConfig?.let {
|
||||
DefaultLoginWizard(
|
||||
buildAuthAPI(it),
|
||||
sessionCreator,
|
||||
pendingSessionStore
|
||||
pendingSessionStore,
|
||||
enableRefreshTokenAuth
|
||||
).also {
|
||||
currentLoginWizard = it
|
||||
}
|
||||
@@ -385,17 +387,21 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
deviceId: String?): Session {
|
||||
override suspend fun directAuthentication(
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
enableRefreshTokenAuth: Boolean,
|
||||
deviceId: String?,
|
||||
): Session {
|
||||
return directLoginTask.execute(DirectLoginTask.Params(
|
||||
homeServerConnectionConfig = homeServerConnectionConfig,
|
||||
userId = matrixId,
|
||||
password = password,
|
||||
deviceName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
deviceId = deviceId,
|
||||
refreshToken = enableRefreshTokenAuth
|
||||
))
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.auth
|
||||
|
||||
import org.matrix.android.sdk.internal.auth.data.RefreshParams
|
||||
import org.matrix.android.sdk.internal.auth.data.RefreshResult
|
||||
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* The refresh token REST API.
|
||||
*/
|
||||
internal interface RefreshTokenAPI {
|
||||
/**
|
||||
* Refresh the access token given a refresh token.
|
||||
* @param refreshParams the refresh parameters
|
||||
*/
|
||||
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_V1 + "refresh")
|
||||
suspend fun refreshToken(@Body refreshParams: RefreshParams): RefreshResult
|
||||
}
|
@@ -18,4 +18,5 @@ package org.matrix.android.sdk.internal.auth.data
|
||||
|
||||
internal interface LoginParams {
|
||||
val type: String
|
||||
val enableRefreshTokenAuth: Boolean
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ internal data class PasswordLoginParams(
|
||||
@Json(name = "identifier") val identifier: Map<String, String>,
|
||||
@Json(name = "password") val password: String,
|
||||
@Json(name = "type") override val type: String,
|
||||
@Json(name = "refresh_token") override val enableRefreshTokenAuth: Boolean,
|
||||
@Json(name = "initial_device_display_name") val deviceDisplayName: String?,
|
||||
@Json(name = "device_id") val deviceId: String?) : LoginParams {
|
||||
|
||||
@@ -50,7 +51,8 @@ internal data class PasswordLoginParams(
|
||||
fun userIdentifier(user: String,
|
||||
password: String,
|
||||
deviceDisplayName: String?,
|
||||
deviceId: String?): PasswordLoginParams {
|
||||
deviceId: String?,
|
||||
enableRefreshTokenAuth: Boolean): PasswordLoginParams {
|
||||
return PasswordLoginParams(
|
||||
identifier = mapOf(
|
||||
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_USER,
|
||||
@@ -58,6 +60,7 @@ internal data class PasswordLoginParams(
|
||||
),
|
||||
password = password,
|
||||
type = LoginFlowTypes.PASSWORD,
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
@@ -67,7 +70,8 @@ internal data class PasswordLoginParams(
|
||||
address: String,
|
||||
password: String,
|
||||
deviceDisplayName: String?,
|
||||
deviceId: String?): PasswordLoginParams {
|
||||
deviceId: String?,
|
||||
enableRefreshTokenAuth: Boolean): PasswordLoginParams {
|
||||
return PasswordLoginParams(
|
||||
identifier = mapOf(
|
||||
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_THIRD_PARTY,
|
||||
@@ -76,6 +80,7 @@ internal data class PasswordLoginParams(
|
||||
),
|
||||
password = password,
|
||||
type = LoginFlowTypes.PASSWORD,
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
@@ -85,7 +90,8 @@ internal data class PasswordLoginParams(
|
||||
phone: String,
|
||||
password: String,
|
||||
deviceDisplayName: String?,
|
||||
deviceId: String?): PasswordLoginParams {
|
||||
deviceId: String?,
|
||||
enableRefreshTokenAuth: Boolean): PasswordLoginParams {
|
||||
return PasswordLoginParams(
|
||||
identifier = mapOf(
|
||||
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_PHONE,
|
||||
@@ -94,6 +100,7 @@ internal data class PasswordLoginParams(
|
||||
),
|
||||
password = password,
|
||||
type = LoginFlowTypes.PASSWORD,
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
|
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class RefreshParams(
|
||||
@Json(name = "refresh_token") val refreshToken: String
|
||||
)
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.auth.data
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RefreshResult(
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "expires_in_ms") val expiresInMs: Long,
|
||||
@Json(name = "refresh_token") val refreshToken: String
|
||||
)
|
@@ -23,5 +23,6 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class TokenLoginParams(
|
||||
@Json(name = "type") override val type: String = LoginFlowTypes.TOKEN,
|
||||
@Json(name = "refresh_token") override val enableRefreshTokenAuth: Boolean,
|
||||
@Json(name = "token") val token: String
|
||||
) : LoginParams
|
||||
|
@@ -17,6 +17,7 @@
|
||||
package org.matrix.android.sdk.internal.auth.login
|
||||
|
||||
import android.util.Patterns
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
@@ -38,7 +39,8 @@ import org.matrix.android.sdk.internal.session.contentscanner.DisabledContentSca
|
||||
internal class DefaultLoginWizard(
|
||||
private val authAPI: AuthAPI,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val enableRefreshTokenAuth: Boolean
|
||||
) : LoginWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
@@ -62,14 +64,16 @@ internal class DefaultLoginWizard(
|
||||
address = login,
|
||||
password = password,
|
||||
deviceDisplayName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
deviceId = deviceId,
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth
|
||||
)
|
||||
} else {
|
||||
PasswordLoginParams.userIdentifier(
|
||||
user = login,
|
||||
password = password,
|
||||
deviceDisplayName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
deviceId = deviceId,
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth
|
||||
)
|
||||
}
|
||||
val credentials = executeRequest(null) {
|
||||
@@ -84,7 +88,8 @@ internal class DefaultLoginWizard(
|
||||
*/
|
||||
override suspend fun loginWithToken(loginToken: String): Session {
|
||||
val loginParams = TokenLoginParams(
|
||||
token = loginToken
|
||||
token = loginToken,
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth
|
||||
)
|
||||
val credentials = executeRequest(null) {
|
||||
authAPI.login(loginParams)
|
||||
@@ -94,8 +99,10 @@ internal class DefaultLoginWizard(
|
||||
}
|
||||
|
||||
override suspend fun loginCustom(data: JsonDict): Session {
|
||||
val loginParams = data.toMutableMap()
|
||||
loginParams["refresh_token"] = enableRefreshTokenAuth
|
||||
val credentials = executeRequest(null) {
|
||||
authAPI.login(data)
|
||||
authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
|
@@ -38,7 +38,8 @@ internal interface DirectLoginTask : Task<DirectLoginTask.Params, Session> {
|
||||
val userId: String,
|
||||
val password: String,
|
||||
val deviceName: String,
|
||||
val deviceId: String?
|
||||
val deviceId: String?,
|
||||
val refreshToken: Boolean
|
||||
)
|
||||
}
|
||||
|
||||
@@ -60,7 +61,8 @@ internal class DefaultDirectLoginTask @Inject constructor(
|
||||
user = params.userId,
|
||||
password = params.password,
|
||||
deviceDisplayName = params.deviceName,
|
||||
deviceId = params.deviceId
|
||||
deviceId = params.deviceId,
|
||||
enableRefreshTokenAuth = params.refreshToken
|
||||
)
|
||||
|
||||
val credentials = try {
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.auth.refresh
|
||||
|
||||
import org.matrix.android.sdk.internal.auth.RefreshTokenAPI
|
||||
import org.matrix.android.sdk.internal.auth.data.RefreshParams
|
||||
import org.matrix.android.sdk.internal.auth.data.RefreshResult
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface RefreshTokenTask : Task<RefreshTokenTask.Params, RefreshResult> {
|
||||
data class Params(
|
||||
val refreshToken: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultRefreshTokenTask @Inject constructor(
|
||||
private val refreshTokenAPI: RefreshTokenAPI
|
||||
) : RefreshTokenTask {
|
||||
override suspend fun execute(params: RefreshTokenTask.Params): RefreshResult {
|
||||
return executeRequest(null) {
|
||||
refreshTokenAPI.refreshToken(RefreshParams(params.refreshToken))
|
||||
}
|
||||
}
|
||||
}
|
@@ -36,7 +36,8 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData
|
||||
internal class DefaultRegistrationWizard(
|
||||
authAPI: AuthAPI,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val enableRefreshTokenAuth: Boolean
|
||||
) : RegistrationWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
@@ -72,7 +73,8 @@ internal class DefaultRegistrationWizard(
|
||||
val params = RegistrationParams(
|
||||
username = userName,
|
||||
password = password,
|
||||
initialDeviceDisplayName = initialDeviceDisplayName
|
||||
initialDeviceDisplayName = initialDeviceDisplayName,
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth
|
||||
)
|
||||
return performRegistrationRequest(params)
|
||||
.also {
|
||||
@@ -85,7 +87,10 @@ internal class DefaultRegistrationWizard(
|
||||
val safeSession = pendingSessionData.currentSession
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
||||
val params = RegistrationParams(
|
||||
auth = AuthParams.createForCaptcha(safeSession, response),
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth
|
||||
)
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
@@ -93,7 +98,10 @@ internal class DefaultRegistrationWizard(
|
||||
val safeSession = pendingSessionData.currentSession
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
||||
val params = RegistrationParams(
|
||||
auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession),
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth
|
||||
)
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
@@ -137,7 +145,8 @@ internal class DefaultRegistrationWizard(
|
||||
sid = response.sid
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
enableRefreshTokenAuth = enableRefreshTokenAuth
|
||||
)
|
||||
// Store data
|
||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = ThreePidData.from(threePid, response, params))
|
||||
|
@@ -42,5 +42,9 @@ internal data class RegistrationParams(
|
||||
// Temporary flag to notify the server that we support msisdn flow. Used to prevent old app
|
||||
// versions to end up in fallback because the HS returns the msisdn flow which they don't support
|
||||
@Json(name = "x_show_msisdn")
|
||||
val xShowMsisdn: Boolean? = null
|
||||
val xShowMsisdn: Boolean? = null,
|
||||
|
||||
// whether the session created should use refresh-token-based auth
|
||||
@Json(name = "refresh_token")
|
||||
val enableRefreshTokenAuth: Boolean? = null
|
||||
)
|
||||
|
@@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||
import org.matrix.android.sdk.internal.network.parsing.CipherSuiteMoshiAdapter
|
||||
import org.matrix.android.sdk.internal.network.parsing.CredentialsResponseAdapter
|
||||
import org.matrix.android.sdk.internal.network.parsing.ForceToBooleanJsonAdapter
|
||||
import org.matrix.android.sdk.internal.network.parsing.RuntimeJsonAdapterFactory
|
||||
import org.matrix.android.sdk.internal.network.parsing.TlsVersionMoshiAdapter
|
||||
@@ -44,6 +45,7 @@ object MoshiProvider {
|
||||
.add(ForceToBooleanJsonAdapter())
|
||||
.add(CipherSuiteMoshiAdapter())
|
||||
.add(TlsVersionMoshiAdapter())
|
||||
.add(CredentialsResponseAdapter())
|
||||
// Use addLast here so we can inject a SplitLazyRoomSyncJsonAdapter later to override the default parsing.
|
||||
.addLast(DefaultLazyRoomSyncEphemeralJsonAdapter())
|
||||
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
|
||||
|
@@ -109,6 +109,8 @@ internal class DefaultLegacySessionImporter @Inject constructor(
|
||||
userId = legacyConfig.credentials.userId,
|
||||
accessToken = legacyConfig.credentials.accessToken,
|
||||
refreshToken = legacyConfig.credentials.refreshToken,
|
||||
expiresInMs = null,
|
||||
expiryTs = null,
|
||||
homeServer = legacyConfig.credentials.homeServer,
|
||||
deviceId = legacyConfig.credentials.deviceId,
|
||||
discoveryInformation = legacyConfig.credentials.wellKnown?.let { wellKnown ->
|
||||
|
@@ -16,22 +16,44 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.network
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.isTokenUnknownError
|
||||
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
|
||||
|
||||
internal class AccessTokenInterceptor(private val accessTokenProvider: AccessTokenProvider) : Interceptor {
|
||||
internal class AccessTokenInterceptor(
|
||||
private val accessTokenProvider: AccessTokenProvider,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver?,
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
var request = chain.request()
|
||||
// Attempt to get the latest access token before the request token might be expiring soon.
|
||||
val response = attemptRequestWithLatestToken(chain)
|
||||
|
||||
// Add the access token to all requests if it is set
|
||||
accessTokenProvider.getToken()?.let { token ->
|
||||
val newRequestBuilder = request.newBuilder()
|
||||
newRequestBuilder.header(HttpHeaders.Authorization, "Bearer $token")
|
||||
request = newRequestBuilder.build()
|
||||
if (!accessTokenProvider.supportsRefreshTokens) {
|
||||
return response
|
||||
}
|
||||
|
||||
val serverError = response.peekFailure(globalErrorReceiver) as? Failure.ServerError
|
||||
|
||||
if (serverError == null || !serverError.isTokenUnknownError()) {
|
||||
return response
|
||||
}
|
||||
// Server is the source of truth on token validity, if it is no longer valid we should refresh and retry the original request.
|
||||
return attemptRequestWithLatestToken(chain, serverError)
|
||||
}
|
||||
|
||||
private fun attemptRequestWithLatestToken(chain: Interceptor.Chain, serverError: Failure.ServerError? = null): Response {
|
||||
var request = chain.request()
|
||||
runBlocking {
|
||||
accessTokenProvider.getToken(serverError)?.let { token ->
|
||||
val newRequestBuilder = request.newBuilder()
|
||||
newRequestBuilder.header(HttpHeaders.Authorization, "Bearer $token")
|
||||
request = newRequestBuilder.build()
|
||||
}
|
||||
}
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ internal object NetworkConstants {
|
||||
private const val URI_API_PREFIX_PATH = "_matrix/client"
|
||||
const val URI_API_PREFIX_PATH_ = "$URI_API_PREFIX_PATH/"
|
||||
const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/"
|
||||
const val URI_API_PREFIX_PATH_V1 = "$URI_API_PREFIX_PATH/v1/"
|
||||
const val URI_API_PREFIX_PATH_UNSTABLE = "$URI_API_PREFIX_PATH/unstable/"
|
||||
|
||||
// Media
|
||||
|
@@ -21,7 +21,6 @@ package org.matrix.android.sdk.internal.network
|
||||
import com.squareup.moshi.JsonEncodingException
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.ResponseBody
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
@@ -30,7 +29,6 @@ import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
@@ -73,6 +71,18 @@ internal fun okhttp3.Response.toFailure(globalErrorReceiver: GlobalErrorReceiver
|
||||
return toFailure(body, code, globalErrorReceiver)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError.
|
||||
* This is used to check error responses in interceptors as it does not consume the body but uses `Response.peekBody` instead.
|
||||
*/
|
||||
internal fun okhttp3.Response.peekFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure? {
|
||||
if (isSuccessful) {
|
||||
return null
|
||||
}
|
||||
val megabyteInBytes = (1024 * 1024).toLong()
|
||||
return toFailure(this.peekBody(megabyteInBytes), code, globalErrorReceiver)
|
||||
}
|
||||
|
||||
private fun toFailure(errorBody: ResponseBody?, httpCode: Int, globalErrorReceiver: GlobalErrorReceiver?): Failure {
|
||||
if (errorBody == null) {
|
||||
return Failure.Unknown(RuntimeException("errorBody should not be null"))
|
||||
@@ -91,10 +101,6 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int, globalErrorReceiv
|
||||
matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank() -> {
|
||||
globalErrorReceiver?.handleGlobalError(GlobalError.ConsentNotGivenError(matrixError.consentUri))
|
||||
}
|
||||
httpCode == HttpURLConnection.HTTP_UNAUTHORIZED && /* 401 */
|
||||
matrixError.code == MatrixError.M_UNKNOWN_TOKEN -> {
|
||||
globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout.orFalse()))
|
||||
}
|
||||
matrixError.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT -> {
|
||||
globalErrorReceiver?.handleGlobalError(GlobalError.ExpiredAccount)
|
||||
}
|
||||
|
@@ -19,17 +19,21 @@ package org.matrix.android.sdk.internal.network.httpclient
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.internal.network.AccessTokenInterceptor
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor
|
||||
import org.matrix.android.sdk.internal.network.ssl.CertUtil
|
||||
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun OkHttpClient.Builder.addAccessTokenInterceptor(accessTokenProvider: AccessTokenProvider): OkHttpClient.Builder {
|
||||
internal fun OkHttpClient.Builder.addAccessTokenInterceptor(
|
||||
accessTokenProvider: AccessTokenProvider,
|
||||
globalErrorReceiver: GlobalErrorReceiver? = null
|
||||
): OkHttpClient.Builder {
|
||||
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
|
||||
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
|
||||
interceptors().removeAll(existingCurlInterceptors)
|
||||
|
||||
addInterceptor(AccessTokenInterceptor(accessTokenProvider))
|
||||
addInterceptor(AccessTokenInterceptor(accessTokenProvider, globalErrorReceiver))
|
||||
|
||||
// Re add eventually the curl logging interceptors
|
||||
existingCurlInterceptors.forEach {
|
||||
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.network.parsing
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
|
||||
/**
|
||||
* Used when we receive a new credential from the serer that contains an access token expiry. We want to
|
||||
* estimate the expiry date as soon as possible using `expiresInMs`.
|
||||
*/
|
||||
class CredentialsResponseAdapter {
|
||||
@FromJson
|
||||
fun credentialsFromResponse(responseCredentials: Credentials): Credentials {
|
||||
if (responseCredentials.expiresInMs == null || responseCredentials.expiryTs != null) {
|
||||
// expiryTs already estimated or doesn't apply, return existing credential.
|
||||
return responseCredentials
|
||||
}
|
||||
// We have received a credential response from the server, estimate the expiry datetime.
|
||||
return responseCredentials.copy(expiryTs = System.currentTimeMillis() + responseCredentials.expiresInMs)
|
||||
}
|
||||
}
|
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.network.token
|
||||
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
|
||||
internal interface AccessTokenProvider {
|
||||
fun getToken(): String?
|
||||
val supportsRefreshTokens: Boolean
|
||||
suspend fun getToken(serverError: Failure.ServerError? = null): String?
|
||||
}
|
||||
|
@@ -16,13 +16,107 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.network.token
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.failure.MatrixError.Companion.M_FORBIDDEN
|
||||
import org.matrix.android.sdk.api.failure.MatrixError.Companion.M_UNKNOWN_TOKEN
|
||||
import org.matrix.android.sdk.api.failure.isTokenUnknownError
|
||||
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
||||
import org.matrix.android.sdk.internal.auth.data.RefreshResult
|
||||
import org.matrix.android.sdk.internal.auth.refresh.RefreshTokenTask
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class HomeserverAccessTokenProvider @Inject constructor(
|
||||
@SessionId private val sessionId: String,
|
||||
private val sessionParamsStore: SessionParamsStore
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val refreshTokenTask: RefreshTokenTask,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : AccessTokenProvider {
|
||||
override fun getToken() = sessionParamsStore.get(sessionId)?.credentials?.accessToken
|
||||
|
||||
companion object {
|
||||
private val mutex = Mutex()
|
||||
|
||||
/**
|
||||
* The time interval before the access token expires that we will start trying to refresh the token.]
|
||||
* This avoids us having to block other users requests while the token refreshes.
|
||||
* Choosing a value larger than DEFAULT_DELAY_MILLIS + DEFAULT_LONG_POOL_TIMEOUT_SECONDS guarantees we will at least have attempted it before expiry.
|
||||
*/
|
||||
private const val PREEMPT_REFRESH_EXPIRATION_INTERVAL = 60000
|
||||
}
|
||||
|
||||
override val supportsRefreshTokens = true
|
||||
|
||||
override suspend fun getToken(serverError: Failure.ServerError?): String? {
|
||||
var accessToken: String?
|
||||
// We synchronise here so that when refresh is required, a single request becomes the leader.
|
||||
// On successful refresh the leader saves the credential and the new access token then becomes available to other requests when they are unblocked.
|
||||
// We should never send multiple refresh requests as refresh tokens are single-use(they rotate with a new one returned in the response).
|
||||
// Mishandled via race conditions and we could become unauthenticated.
|
||||
mutex.withLock {
|
||||
accessToken = verifyAccessTokenAndRefreshIfStale(serverError)
|
||||
}
|
||||
return accessToken
|
||||
}
|
||||
|
||||
private suspend fun verifyAccessTokenAndRefreshIfStale(serverError: Failure.ServerError?): String? {
|
||||
val credentials = sessionParamsStore.get(sessionId)?.credentials ?: return null
|
||||
val receivedTokenUnknown = serverError?.isTokenUnknownError().orFalse()
|
||||
|
||||
if (credentials.refreshToken.isNullOrEmpty() && serverError != null && receivedTokenUnknown) {
|
||||
Timber.d("## HomeserverAccessTokenProvider: accessToken-based auth failed, requires logout.")
|
||||
globalErrorReceiver.handleGlobalError(invalidToken(serverError, false))
|
||||
}
|
||||
|
||||
if (credentials.refreshToken.isNullOrEmpty() || (!receivedTokenUnknown && expiryIsValid(credentials.expiryTs))) {
|
||||
// Existing access token is valid
|
||||
return credentials.accessToken
|
||||
}
|
||||
|
||||
Timber.d("## HomeserverAccessTokenProvider: Refreshing access token...")
|
||||
|
||||
var result: RefreshResult? = null
|
||||
try {
|
||||
result = refreshTokenTask.execute(RefreshTokenTask.Params(credentials.refreshToken))
|
||||
} catch (throwable: Throwable) {
|
||||
if (throwable is Failure.ServerError) {
|
||||
Timber.d("## HomeserverAccessTokenProvider: Failed to refresh access token. error: $throwable")
|
||||
if (throwable.error.code == M_UNKNOWN_TOKEN || throwable.error.code == M_FORBIDDEN) {
|
||||
Timber.d("## HomeserverAccessTokenProvider: refreshToken-based auth failed, requires logout.")
|
||||
globalErrorReceiver.handleGlobalError(invalidToken(throwable, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val updatedCredentials = credentials.copy(
|
||||
accessToken = result.accessToken,
|
||||
expiresInMs = result.expiresInMs,
|
||||
expiryTs = System.currentTimeMillis() + result.expiresInMs,
|
||||
refreshToken = result.refreshToken
|
||||
)
|
||||
|
||||
sessionParamsStore.updateCredentials(updatedCredentials)
|
||||
|
||||
Timber.d("## HomeserverAccessTokenProvider: Tokens refreshed and saved.")
|
||||
|
||||
return result.accessToken
|
||||
}
|
||||
|
||||
private fun expiryIsValid(expiryTs: Long?) = expiryTs == null || System.currentTimeMillis() < (expiryTs - PREEMPT_REFRESH_EXPIRATION_INTERVAL)
|
||||
|
||||
private fun invalidToken(serverError: Failure.ServerError, refreshTokenAuth: Boolean) = GlobalError.InvalidToken(
|
||||
softLogout = serverError.error.isSoftLogout.orFalse(),
|
||||
refreshTokenAuth = refreshTokenAuth,
|
||||
errorCode = serverError.error.code,
|
||||
errorReason = serverError.error.message
|
||||
)
|
||||
}
|
||||
|
@@ -45,6 +45,9 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
||||
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
|
||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
|
||||
import org.matrix.android.sdk.internal.auth.RefreshTokenAPI
|
||||
import org.matrix.android.sdk.internal.auth.refresh.DefaultRefreshTokenTask
|
||||
import org.matrix.android.sdk.internal.auth.refresh.RefreshTokenTask
|
||||
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
||||
@@ -222,10 +225,11 @@ internal abstract class SessionModule {
|
||||
fun providesOkHttpClient(@UnauthenticatedWithCertificate okHttpClient: OkHttpClient,
|
||||
@Authenticated accessTokenProvider: AccessTokenProvider,
|
||||
@SessionId sessionId: String,
|
||||
@MockHttpInterceptor testInterceptor: TestInterceptor?): OkHttpClient {
|
||||
@MockHttpInterceptor testInterceptor: TestInterceptor?,
|
||||
globalErrorReceiver: GlobalErrorReceiver): OkHttpClient {
|
||||
return okHttpClient
|
||||
.newBuilder()
|
||||
.addAccessTokenInterceptor(accessTokenProvider)
|
||||
.addAccessTokenInterceptor(accessTokenProvider, globalErrorReceiver)
|
||||
.apply {
|
||||
if (testInterceptor != null) {
|
||||
testInterceptor.sessionId = sessionId
|
||||
@@ -285,6 +289,19 @@ internal abstract class SessionModule {
|
||||
fun providesMxCryptoConfig(matrixConfiguration: MatrixConfiguration): MXCryptoConfig {
|
||||
return matrixConfiguration.cryptoConfig
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
@SessionScope
|
||||
fun providesRefreshTokenAPI(
|
||||
@Unauthenticated okHttpClient: Lazy<OkHttpClient>,
|
||||
retrofitFactory: RetrofitFactory,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig
|
||||
): RefreshTokenAPI {
|
||||
val homeServerUrl = homeServerConnectionConfig.homeServerUriBase.toString()
|
||||
return retrofitFactory.create(okHttpClient, homeServerUrl)
|
||||
.create(RefreshTokenAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
@@ -390,4 +407,7 @@ internal abstract class SessionModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor
|
||||
|
||||
@Binds
|
||||
abstract fun bindRefreshTokenTask(task: DefaultRefreshTokenTask): RefreshTokenTask
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.session.identity
|
||||
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
|
||||
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
|
||||
import javax.inject.Inject
|
||||
@@ -23,5 +24,6 @@ import javax.inject.Inject
|
||||
internal class IdentityAccessTokenProvider @Inject constructor(
|
||||
private val identityStore: IdentityStore
|
||||
) : AccessTokenProvider {
|
||||
override fun getToken() = identityStore.getIdentityData()?.token
|
||||
override val supportsRefreshTokens = false
|
||||
override suspend fun getToken(serverError: Failure.ServerError?): String? = identityStore.getIdentityData()?.token
|
||||
}
|
||||
|
@@ -26,8 +26,8 @@ internal class DefaultSignOutService @Inject constructor(private val signOutTask
|
||||
private val sessionParamsStore: SessionParamsStore
|
||||
) : SignOutService {
|
||||
|
||||
override suspend fun signInAgain(password: String) {
|
||||
signInAgainTask.execute(SignInAgainTask.Params(password))
|
||||
override suspend fun signInAgain(password: String, enableRefreshTokenAuth: Boolean) {
|
||||
signInAgainTask.execute(SignInAgainTask.Params(password, enableRefreshTokenAuth))
|
||||
}
|
||||
|
||||
override suspend fun updateCredentials(credentials: Credentials) {
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.session.signout
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
||||
import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
|
||||
@@ -26,7 +27,8 @@ import javax.inject.Inject
|
||||
|
||||
internal interface SignInAgainTask : Task<SignInAgainTask.Params, Unit> {
|
||||
data class Params(
|
||||
val password: String
|
||||
val password: String,
|
||||
val enableRefreshTokenAuth: Boolean
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,7 +36,8 @@ internal class DefaultSignInAgainTask @Inject constructor(
|
||||
private val signOutAPI: SignOutAPI,
|
||||
private val sessionParams: SessionParams,
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||
private val matrixConfiguration: MatrixConfiguration
|
||||
) : SignInAgainTask {
|
||||
|
||||
override suspend fun execute(params: SignInAgainTask.Params) {
|
||||
@@ -49,7 +52,8 @@ internal class DefaultSignInAgainTask @Inject constructor(
|
||||
// but https://github.com/matrix-org/synapse/issues/6525
|
||||
deviceDisplayName = null,
|
||||
// Reuse the same deviceId
|
||||
deviceId = sessionParams.deviceId
|
||||
deviceId = sessionParams.deviceId,
|
||||
enableRefreshTokenAuth = params.enableRefreshTokenAuth
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@@ -159,6 +159,8 @@ android {
|
||||
buildConfigField "Boolean", "enableLocationSharing", "true"
|
||||
buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\""
|
||||
|
||||
buildConfigField "Boolean", "enableRefreshTokenAuth", "false"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// Keep abiFilter for the universalApk
|
||||
|
@@ -68,6 +68,7 @@ import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import im.vector.app.features.analytics.plan.UnauthenticatedError
|
||||
import im.vector.app.features.configuration.VectorConfiguration
|
||||
import im.vector.app.features.consent.ConsentNotGivenHelper
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
@@ -297,6 +298,11 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
return
|
||||
}
|
||||
|
||||
val errorCode: UnauthenticatedError.ErrorCode = UnauthenticatedError.ErrorCode.values().firstOrNull { it.name == globalError.errorCode }
|
||||
?: UnauthenticatedError.ErrorCode.M_UNKNOWN
|
||||
val unauthenticatedError = UnauthenticatedError(errorCode, globalError.errorReason, globalError.refreshTokenAuth, globalError.softLogout)
|
||||
analyticsTracker.capture(unauthenticatedError)
|
||||
|
||||
mainActivityStarted = true
|
||||
|
||||
MainActivity.restartApp(this,
|
||||
|
@@ -26,6 +26,7 @@ import com.airbnb.mvrx.Uninitialized
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
@@ -99,10 +100,10 @@ class LoginViewModel @AssistedInject constructor(
|
||||
get() = authenticationService.isRegistrationStarted
|
||||
|
||||
private val registrationWizard: RegistrationWizard?
|
||||
get() = authenticationService.getRegistrationWizard()
|
||||
get() = authenticationService.getRegistrationWizard(BuildConfig.enableRefreshTokenAuth)
|
||||
|
||||
private val loginWizard: LoginWizard?
|
||||
get() = authenticationService.getLoginWizard()
|
||||
get() = authenticationService.getLoginWizard(BuildConfig.enableRefreshTokenAuth)
|
||||
|
||||
private var loginConfig: LoginConfig? = null
|
||||
|
||||
@@ -617,7 +618,9 @@ class LoginViewModel @AssistedInject constructor(
|
||||
alteredHomeServerConnectionConfig,
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName)
|
||||
action.initialDeviceName,
|
||||
BuildConfig.enableRefreshTokenAuth
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
return
|
||||
|
@@ -24,6 +24,7 @@ import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
@@ -101,10 +102,10 @@ class LoginViewModel2 @AssistedInject constructor(
|
||||
get() = authenticationService.isRegistrationStarted
|
||||
|
||||
private val registrationWizard: RegistrationWizard?
|
||||
get() = authenticationService.getRegistrationWizard()
|
||||
get() = authenticationService.getRegistrationWizard(BuildConfig.enableRefreshTokenAuth)
|
||||
|
||||
private val loginWizard: LoginWizard?
|
||||
get() = authenticationService.getLoginWizard()
|
||||
get() = authenticationService.getLoginWizard(BuildConfig.enableRefreshTokenAuth)
|
||||
|
||||
private var loginConfig: LoginConfig? = null
|
||||
|
||||
|
@@ -26,6 +26,7 @@ import com.airbnb.mvrx.Uninitialized
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
@@ -112,10 +113,10 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
get() = authenticationService.isRegistrationStarted
|
||||
|
||||
private val registrationWizard: RegistrationWizard?
|
||||
get() = authenticationService.getRegistrationWizard()
|
||||
get() = authenticationService.getRegistrationWizard(BuildConfig.enableRefreshTokenAuth)
|
||||
|
||||
private val loginWizard: LoginWizard?
|
||||
get() = authenticationService.getLoginWizard()
|
||||
get() = authenticationService.getLoginWizard(BuildConfig.enableRefreshTokenAuth)
|
||||
|
||||
private var loginConfig: LoginConfig? = null
|
||||
|
||||
@@ -656,7 +657,9 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||
alteredHomeServerConnectionConfig,
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName)
|
||||
action.initialDeviceName,
|
||||
BuildConfig.enableRefreshTokenAuth
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
return
|
||||
|
@@ -73,7 +73,10 @@ class SoftLogoutActivity : LoginActivity() {
|
||||
)
|
||||
}
|
||||
is SoftLogoutViewEvents.ClearData -> {
|
||||
MainActivity.restartApp(this, MainActivityArgs(clearCredentials = true))
|
||||
MainActivity.restartApp(this, MainActivityArgs(
|
||||
clearCredentials = true,
|
||||
isUserLoggedOut = true
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
@@ -177,7 +178,7 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
||||
}
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
session.signInAgain(action.password)
|
||||
session.signInAgain(action.password, BuildConfig.enableRefreshTokenAuth)
|
||||
onSessionRestored()
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
|
Reference in New Issue
Block a user