adding support for signing in to homeservers without wellknown setup

This commit is contained in:
Adam Brown 2022-04-13 21:04:23 +01:00
parent 32494b961b
commit e1fd79de02
14 changed files with 218 additions and 68 deletions

View File

@ -22,7 +22,7 @@
### Feature list ### Feature list
- Login with username/password (homeservers must serve `https://${domain}/.well-known/matrix/client`) - Login with Matrix ID/Password
- Combined Room and DM interface - Combined Room and DM interface
- End to end encryption - End to end encryption
- Message bubbles, supporting text, replies and edits - Message bubbles, supporting text, replies and edits

View File

@ -1,5 +1,6 @@
package app.dapk.st.login package app.dapk.st.login
import android.widget.Toast
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -7,6 +8,7 @@ import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.filled.Web
import androidx.compose.material.icons.outlined.Lock import androidx.compose.material.icons.outlined.Lock
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@ -14,6 +16,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -37,9 +40,10 @@ fun LoginScreen(loginViewModel: LoginViewModel, onLoggedIn: () -> Unit) {
var userName by rememberSaveable { mutableStateOf("") } var userName by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") }
var serverUrl by rememberSaveable { mutableStateOf("") }
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
when (loginViewModel.state) { when (val state = loginViewModel.state) {
is Error -> { is Error -> {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
@ -58,7 +62,7 @@ fun LoginScreen(loginViewModel: LoginViewModel, onLoggedIn: () -> Unit) {
CircularProgressIndicator() CircularProgressIndicator()
} }
} }
Idle -> is Content ->
Row { Row {
Spacer(modifier = Modifier.weight(0.1f)) Spacer(modifier = Modifier.weight(0.1f))
Column( Column(
@ -91,7 +95,11 @@ fun LoginScreen(loginViewModel: LoginViewModel, onLoggedIn: () -> Unit) {
keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Email, imeAction = ImeAction.Next) keyboardOptions = KeyboardOptions(autoCorrect = false, keyboardType = KeyboardType.Email, imeAction = ImeAction.Next)
) )
val canDoLoginAttempt = userName.isNotEmpty() && password.isNotEmpty() val canDoLoginAttempt = if (state.showServerUrl) {
userName.isNotEmpty() && password.isNotEmpty() && serverUrl.isNotEmpty()
} else {
userName.isNotEmpty() && password.isNotEmpty()
}
TextField( TextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -102,10 +110,13 @@ fun LoginScreen(loginViewModel: LoginViewModel, onLoggedIn: () -> Unit) {
leadingIcon = { leadingIcon = {
Icon(imageVector = Icons.Outlined.Lock, contentDescription = null) Icon(imageVector = Icons.Outlined.Lock, contentDescription = null)
}, },
keyboardActions = KeyboardActions(onDone = { loginViewModel.login(userName, password) }), keyboardActions = KeyboardActions(
onDone = { loginViewModel.login(userName, password, serverUrl) },
onNext = { focusManager.moveFocus(FocusDirection.Down) },
),
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
autoCorrect = false, autoCorrect = false,
imeAction = ImeAction.Done.takeIf { canDoLoginAttempt } ?: ImeAction.None, imeAction = ImeAction.Done.takeIf { canDoLoginAttempt } ?: ImeAction.Next.takeIf { state.showServerUrl } ?: ImeAction.None,
keyboardType = KeyboardType.Password keyboardType = KeyboardType.Password
), ),
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(), visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
@ -117,13 +128,32 @@ fun LoginScreen(loginViewModel: LoginViewModel, onLoggedIn: () -> Unit) {
} }
) )
if (state.showServerUrl) {
TextField(
modifier = Modifier.fillMaxWidth(),
value = serverUrl,
onValueChange = { serverUrl = it },
label = { Text("Server URL") },
singleLine = true,
leadingIcon = {
Icon(imageVector = Icons.Default.Web, contentDescription = null)
},
keyboardActions = KeyboardActions(onDone = { loginViewModel.login(userName, password, serverUrl) }),
keyboardOptions = KeyboardOptions(
autoCorrect = false,
imeAction = ImeAction.Done.takeIf { canDoLoginAttempt } ?: ImeAction.None,
keyboardType = KeyboardType.Uri
),
)
}
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onClick = { onClick = {
keyboardController?.hide() keyboardController?.hide()
loginViewModel.login(userName, password) loginViewModel.login(userName, password, serverUrl)
}, },
enabled = canDoLoginAttempt enabled = canDoLoginAttempt
) { ) {
@ -137,10 +167,14 @@ fun LoginScreen(loginViewModel: LoginViewModel, onLoggedIn: () -> Unit) {
@Composable @Composable
private fun LoginViewModel.ObserveEvents(onLoggedIn: () -> Unit) { private fun LoginViewModel.ObserveEvents(onLoggedIn: () -> Unit) {
val context = LocalContext.current
StartObserving { StartObserving {
this@ObserveEvents.events.launch { this@ObserveEvents.events.launch {
when (it) { when (it) {
LoginComplete -> onLoggedIn() LoginComplete -> onLoggedIn()
LoginEvent.WellKnownMissing -> {
Toast.makeText(context, "Couldn't find the homeserver, please enter the server URL", Toast.LENGTH_LONG).show()
}
} }
} }
} }

View File

@ -2,12 +2,13 @@ package app.dapk.st.login
sealed interface LoginScreenState { sealed interface LoginScreenState {
object Idle : LoginScreenState data class Content(val showServerUrl: Boolean) : LoginScreenState
object Loading : LoginScreenState object Loading : LoginScreenState
data class Error(val cause: Throwable) : LoginScreenState data class Error(val cause: Throwable) : LoginScreenState
} }
sealed interface LoginEvent { sealed interface LoginEvent {
object LoginComplete : LoginEvent object LoginComplete : LoginEvent
object WellKnownMissing : LoginEvent
} }

View File

@ -19,33 +19,45 @@ class LoginViewModel(
private val profileService: ProfileService, private val profileService: ProfileService,
private val errorTracker: ErrorTracker, private val errorTracker: ErrorTracker,
) : DapkViewModel<LoginScreenState, LoginEvent>( ) : DapkViewModel<LoginScreenState, LoginEvent>(
initialState = Idle initialState = Content(showServerUrl = false)
) { ) {
fun login(userName: String, password: String) { private var previousState: LoginScreenState? = null
fun login(userName: String, password: String, serverUrl: String?) {
state = Loading state = Loading
viewModelScope.launch { viewModelScope.launch {
kotlin.runCatching {
logP("login") { logP("login") {
authService.login(userName, password).also { when (val result = authService.login(AuthService.LoginRequest(userName, password, serverUrl.takeIfNotEmpty()))) {
is AuthService.LoginResult.Success -> {
runCatching {
listOf( listOf(
async { firebasePushTokenUseCase.registerCurrentToken() }, async { firebasePushTokenUseCase.registerCurrentToken() },
async { preloadMe() }, async { preloadMe() },
).awaitAll() ).awaitAll()
} }
}
}.onFailure {
errorTracker.track(it)
state = Error(it)
}.onSuccess {
_events.tryEmit(LoginComplete) _events.tryEmit(LoginComplete)
} }
is AuthService.LoginResult.Error -> {
errorTracker.track(result.cause)
state = Error(result.cause)
}
AuthService.LoginResult.MissingWellKnown -> {
_events.tryEmit(LoginEvent.WellKnownMissing)
state = Content(showServerUrl = true)
}
}
}
} }
} }
private suspend fun preloadMe() = profileService.me(forceRefresh = false) private suspend fun preloadMe() = profileService.me(forceRefresh = false)
fun start() { fun start() {
state = Idle val showServerUrl = previousState?.let { it is Content && it.showServerUrl } ?: false
state = Content(showServerUrl = showServerUrl)
} }
} }
private fun String?.takeIfNotEmpty() = this?.takeIf { it.isNotEmpty() }

View File

@ -4,6 +4,6 @@ fun String.ensureTrailingSlash(): String {
return if (this.endsWith("/")) this else "$this/" return if (this.endsWith("/")) this else "$this/"
} }
fun String.ensureHttps(): String { fun String.ensureHttpsIfMissing(): String {
return if (this.startsWith("https")) this else "https://$this" return if (this.startsWith("http")) this else "https://$this"
} }

View File

@ -10,16 +10,24 @@ import app.dapk.st.matrix.common.UserCredentials
private val SERVICE_KEY = AuthService::class private val SERVICE_KEY = AuthService::class
interface AuthService : MatrixService { interface AuthService : MatrixService {
suspend fun login(userName: String, password: String): UserCredentials suspend fun login(request: LoginRequest): LoginResult
suspend fun register(userName: String, password: String, homeServer: String): UserCredentials suspend fun register(userName: String, password: String, homeServer: String): UserCredentials
sealed interface LoginResult {
data class Success(val userCredentials: UserCredentials) : LoginResult
object MissingWellKnown : LoginResult
data class Error(val cause: Throwable) : LoginResult
}
data class LoginRequest(val userName: String, val password: String, val serverUrl: String?)
} }
fun MatrixServiceInstaller.installAuthService( fun MatrixServiceInstaller.installAuthService(
credentialsStore: CredentialsStore, credentialsStore: CredentialsStore,
authConfig: AuthConfig = AuthConfig(),
) { ) {
this.install { (httpClient, json) -> this.install { (httpClient, json) ->
SERVICE_KEY to DefaultAuthService(httpClient, credentialsStore, json, authConfig) SERVICE_KEY to DefaultAuthService(httpClient, credentialsStore, json)
} }
} }

View File

@ -49,6 +49,10 @@ internal fun wellKnownRequest(baseUrl: String) = httpRequest<String>(
authenticated = false, authenticated = false,
) )
@JvmInline
@Serializable
internal value class RawResponse(val value: String)
internal data class Auth( internal data class Auth(
val session: String, val session: String,
val type: String, val type: String,

View File

@ -1,25 +1,33 @@
package app.dapk.st.matrix.auth.internal package app.dapk.st.matrix.auth.internal
import app.dapk.st.matrix.auth.AuthConfig
import app.dapk.st.matrix.auth.AuthService import app.dapk.st.matrix.auth.AuthService
import app.dapk.st.matrix.common.CredentialsStore import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.common.HomeServerUrl
import app.dapk.st.matrix.common.UserCredentials import app.dapk.st.matrix.common.UserCredentials
import app.dapk.st.matrix.http.MatrixHttpClient import app.dapk.st.matrix.http.MatrixHttpClient
import app.dapk.st.matrix.http.ensureHttpsIfMissing
import app.dapk.st.matrix.http.ensureTrailingSlash
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
internal class DefaultAuthService( internal class DefaultAuthService(
httpClient: MatrixHttpClient, httpClient: MatrixHttpClient,
credentialsStore: CredentialsStore, credentialsStore: CredentialsStore,
json: Json, json: Json,
authConfig: AuthConfig,
) : AuthService { ) : AuthService {
private val fetchWellKnownUseCase = FetchWellKnownUseCaseImpl(httpClient, json) private val fetchWellKnownUseCase = FetchWellKnownUseCaseImpl(httpClient, json)
private val loginUseCase = LoginUseCase(httpClient, credentialsStore, fetchWellKnownUseCase, authConfig) private val loginUseCase = LoginWithUserPasswordUseCase(httpClient, credentialsStore, fetchWellKnownUseCase)
private val registerCase = RegisterUseCase(httpClient, credentialsStore, json, fetchWellKnownUseCase, authConfig) private val loginServerUseCase = LoginWithUserPasswordServerUseCase(httpClient, credentialsStore)
private val registerCase = RegisterUseCase(httpClient, credentialsStore, json, fetchWellKnownUseCase)
override suspend fun login(userName: String, password: String): UserCredentials { override suspend fun login(request: AuthService.LoginRequest): AuthService.LoginResult {
return loginUseCase.login(userName, password) return when {
request.serverUrl == null -> loginUseCase.login(request.userName, request.password)
else -> {
val serverUrl = HomeServerUrl(request.serverUrl.ensureHttpsIfMissing().ensureTrailingSlash())
loginServerUseCase.login(request.userName, request.password, serverUrl)
}
}
} }
override suspend fun register(userName: String, password: String, homeServer: String): UserCredentials { override suspend fun register(userName: String, password: String, homeServer: String): UserCredentials {

View File

@ -1,19 +1,50 @@
package app.dapk.st.matrix.auth.internal package app.dapk.st.matrix.auth.internal
import app.dapk.st.matrix.http.MatrixHttpClient import app.dapk.st.matrix.http.MatrixHttpClient
import io.ktor.client.plugins.*
import io.ktor.http.*
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.net.UnknownHostException
internal typealias FetchWellKnownUseCase = suspend (String) -> ApiWellKnown internal typealias FetchWellKnownUseCase = suspend (String) -> WellKnownResult
internal class FetchWellKnownUseCaseImpl( internal class FetchWellKnownUseCaseImpl(
private val httpClient: MatrixHttpClient, private val httpClient: MatrixHttpClient,
private val json: Json, private val json: Json,
) : FetchWellKnownUseCase { ) : FetchWellKnownUseCase {
override suspend fun invoke(domainUrl: String): ApiWellKnown { override suspend fun invoke(domainUrl: String): WellKnownResult {
// workaround for matrix.org not returning a content-type return runCatching {
val raw = httpClient.execute(wellKnownRequest(domainUrl)) val rawResponse = httpClient.execute(rawWellKnownRequestForServersWithoutContentTypes(domainUrl))
return json.decodeFromString(ApiWellKnown.serializer(), raw) json.decodeFromString(ApiWellKnown.serializer(), rawResponse)
}
.fold(
onSuccess = { WellKnownResult.Success(it) },
onFailure = {
when (it) {
is UnknownHostException -> WellKnownResult.MissingWellKnown
is ClientRequestException -> when {
it.response.status.is404() -> WellKnownResult.MissingWellKnown
else -> WellKnownResult.Error(it)
}
is SerializationException -> WellKnownResult.InvalidWellKnown
else -> WellKnownResult.Error(it)
}
},
)
} }
private fun rawWellKnownRequestForServersWithoutContentTypes(domainUrl: String) = wellKnownRequest(domainUrl)
} }
sealed interface WellKnownResult {
data class Success(val wellKnown: ApiWellKnown) : WellKnownResult
object MissingWellKnown : WellKnownResult
object InvalidWellKnown : WellKnownResult
data class Error(val cause: Throwable) : WellKnownResult
}
fun HttpStatusCode.is404() = this.value == 404

View File

@ -0,0 +1,33 @@
package app.dapk.st.matrix.auth.internal
import app.dapk.st.matrix.auth.AuthService
import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.common.HomeServerUrl
import app.dapk.st.matrix.common.UserCredentials
import app.dapk.st.matrix.common.UserId
import app.dapk.st.matrix.http.MatrixHttpClient
class LoginWithUserPasswordServerUseCase(
private val httpClient: MatrixHttpClient,
private val credentialsProvider: CredentialsStore,
) {
suspend fun login(userName: String, password: String, serverUrl: HomeServerUrl): AuthService.LoginResult {
return runCatching {
authenticate(serverUrl, UserId(userName.substringBefore(":")), password)
}.fold(
onSuccess = { AuthService.LoginResult.Success(it) },
onFailure = { AuthService.LoginResult.Error(it) }
)
}
private suspend fun authenticate(baseUrl: HomeServerUrl, fullUserId: UserId, password: String): UserCredentials {
val authResponse = httpClient.execute(loginRequest(fullUserId, password, baseUrl.value))
return UserCredentials(
authResponse.accessToken,
baseUrl,
authResponse.userId,
authResponse.deviceId,
).also { credentialsProvider.update(it) }
}
}

View File

@ -1,6 +1,6 @@
package app.dapk.st.matrix.auth.internal package app.dapk.st.matrix.auth.internal
import app.dapk.st.matrix.auth.AuthConfig import app.dapk.st.matrix.auth.AuthService
import app.dapk.st.matrix.common.CredentialsStore import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.common.HomeServerUrl import app.dapk.st.matrix.common.HomeServerUrl
import app.dapk.st.matrix.common.UserCredentials import app.dapk.st.matrix.common.UserCredentials
@ -10,23 +10,27 @@ import app.dapk.st.matrix.http.ensureTrailingSlash
private const val MATRIX_DOT_ORG_DOMAIN = "matrix.org" private const val MATRIX_DOT_ORG_DOMAIN = "matrix.org"
class LoginUseCase( class LoginWithUserPasswordUseCase(
private val httpClient: MatrixHttpClient, private val httpClient: MatrixHttpClient,
private val credentialsProvider: CredentialsStore, private val credentialsProvider: CredentialsStore,
private val fetchWellKnownUseCase: FetchWellKnownUseCase, private val fetchWellKnownUseCase: FetchWellKnownUseCase,
private val authConfig: AuthConfig
) { ) {
suspend fun login(userName: String, password: String): UserCredentials { suspend fun login(userName: String, password: String): AuthService.LoginResult {
val (domainUrl, fullUserId) = generateUserAccessInfo(userName) val (domainUrl, fullUserId) = generateUserAccessInfo(userName)
val baseUrl = fetchWellKnownUseCase(domainUrl).homeServer.baseUrl.ensureTrailingSlash() return when (val wellKnownResult = fetchWellKnownUseCase(domainUrl)) {
val authResponse = httpClient.execute(loginRequest(fullUserId, password, baseUrl.value)) is WellKnownResult.Success -> {
return UserCredentials( runCatching {
authResponse.accessToken, authenticate(wellKnownResult.wellKnown.homeServer.baseUrl.ensureTrailingSlash(), fullUserId, password)
baseUrl, }.fold(
authResponse.userId, onSuccess = { AuthService.LoginResult.Success(it) },
authResponse.deviceId, onFailure = { AuthService.LoginResult.Error(it) }
).also { credentialsProvider.update(it) } )
}
WellKnownResult.InvalidWellKnown -> AuthService.LoginResult.MissingWellKnown
WellKnownResult.MissingWellKnown -> AuthService.LoginResult.MissingWellKnown
is WellKnownResult.Error -> AuthService.LoginResult.Error(wellKnownResult.cause)
}
} }
private fun generateUserAccessInfo(userName: String): Pair<String, UserId> { private fun generateUserAccessInfo(userName: String): Pair<String, UserId> {
@ -37,14 +41,20 @@ class LoginUseCase(
return Pair(domainUrl, UserId(fullUserId)) return Pair(domainUrl, UserId(fullUserId))
} }
private suspend fun authenticate(baseUrl: HomeServerUrl, fullUserId: UserId, password: String): UserCredentials {
val authResponse = httpClient.execute(loginRequest(fullUserId, password, baseUrl.value))
return UserCredentials(
authResponse.accessToken,
baseUrl,
authResponse.userId,
authResponse.deviceId,
).also { credentialsProvider.update(it) }
}
private fun String.findDomain(fallback: String) = this.substringAfter(":", missingDelimiterValue = fallback) private fun String.findDomain(fallback: String) = this.substringAfter(":", missingDelimiterValue = fallback)
private fun String.asHttpsUrl(): String { private fun String.asHttpsUrl(): String {
val schema = when (authConfig.forceHttp) { return "https://$this".ensureTrailingSlash()
true -> "http://"
false -> "https://"
}
return "$schema$this".ensureTrailingSlash()
} }
} }

View File

@ -1,6 +1,5 @@
package app.dapk.st.matrix.auth.internal package app.dapk.st.matrix.auth.internal
import app.dapk.st.matrix.auth.AuthConfig
import app.dapk.st.matrix.common.CredentialsStore import app.dapk.st.matrix.common.CredentialsStore
import app.dapk.st.matrix.common.UserCredentials import app.dapk.st.matrix.common.UserCredentials
import app.dapk.st.matrix.http.MatrixHttpClient import app.dapk.st.matrix.http.MatrixHttpClient
@ -16,7 +15,6 @@ class RegisterUseCase(
private val credentialsProvider: CredentialsStore, private val credentialsProvider: CredentialsStore,
private val json: Json, private val json: Json,
private val fetchWellKnownUseCase: FetchWellKnownUseCase, private val fetchWellKnownUseCase: FetchWellKnownUseCase,
private val authConfig: AuthConfig,
) { ) {
suspend fun register(userName: String, password: String, homeServer: String): UserCredentials { suspend fun register(userName: String, password: String, homeServer: String): UserCredentials {
@ -46,7 +44,12 @@ class RegisterUseCase(
registerRequest(userName, password, baseUrl, Auth(session, "m.login.dummy")) registerRequest(userName, password, baseUrl, Auth(session, "m.login.dummy"))
) )
val homeServerUrl = when (authResponse.wellKnown == null) { val homeServerUrl = when (authResponse.wellKnown == null) {
true -> fetchWellKnownUseCase(baseUrl).homeServer.baseUrl true -> when (val wellKnownResult = fetchWellKnownUseCase(baseUrl)) {
is WellKnownResult.Error, -> TODO()
WellKnownResult.InvalidWellKnown -> TODO()
WellKnownResult.MissingWellKnown -> TODO()
is WellKnownResult.Success -> wellKnownResult.wellKnown.homeServer.baseUrl
}
false -> authResponse.wellKnown.homeServer.baseUrl false -> authResponse.wellKnown.homeServer.baseUrl
} }
return UserCredentials( return UserCredentials(

View File

@ -1,3 +1,4 @@
import app.dapk.st.matrix.auth.AuthService
import app.dapk.st.matrix.auth.authService import app.dapk.st.matrix.auth.authService
import app.dapk.st.matrix.common.HomeServerUrl import app.dapk.st.matrix.common.HomeServerUrl
import app.dapk.st.matrix.common.RoomId import app.dapk.st.matrix.common.RoomId
@ -11,6 +12,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeInstanceOf
import org.amshove.kluent.shouldNotBeEqualTo import org.amshove.kluent.shouldNotBeEqualTo
import org.junit.jupiter.api.MethodOrderer import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.Order import org.junit.jupiter.api.Order
@ -131,13 +133,17 @@ private suspend fun login(user: TestUser) {
val result = testMatrix val result = testMatrix
.client .client
.authService() .authService()
.login(userName = user.roomMember.id.value, password = user.password) .login(AuthService.LoginRequest(userName = user.roomMember.id.value, password = user.password, serverUrl = null))
result.accessToken shouldNotBeEqualTo null
result.homeServer shouldBeEqualTo HomeServerUrl(HTTPS_TEST_SERVER_URL)
result.userId shouldBeEqualTo user.roomMember.id
testMatrix.saveLogin(result) result shouldBeInstanceOf AuthService.LoginResult.Success::class.java
(result as AuthService.LoginResult.Success).userCredentials.let { credentials ->
credentials.accessToken shouldNotBeEqualTo null
credentials.homeServer shouldBeEqualTo HomeServerUrl(HTTPS_TEST_SERVER_URL)
credentials.userId shouldBeEqualTo user.roomMember.id
testMatrix.saveLogin(credentials)
}
} }
object SharedState { object SharedState {

View File

@ -6,7 +6,7 @@ import app.dapk.st.core.CoroutineDispatchers
import app.dapk.st.core.SingletonFlows import app.dapk.st.core.SingletonFlows
import app.dapk.st.domain.StoreModule import app.dapk.st.domain.StoreModule
import app.dapk.st.matrix.MatrixClient import app.dapk.st.matrix.MatrixClient
import app.dapk.st.matrix.auth.AuthConfig import app.dapk.st.matrix.auth.AuthService
import app.dapk.st.matrix.auth.authService import app.dapk.st.matrix.auth.authService
import app.dapk.st.matrix.auth.installAuthService import app.dapk.st.matrix.auth.installAuthService
import app.dapk.st.matrix.common.* import app.dapk.st.matrix.common.*
@ -79,7 +79,7 @@ class TestMatrix(
logger logger
).also { ).also {
it.install { it.install {
installAuthService(storeModule.credentialsStore(), AuthConfig(forceHttp = false)) installAuthService(storeModule.credentialsStore())
installEncryptionService(storeModule.knownDevicesStore()) installEncryptionService(storeModule.knownDevicesStore())
val base64 = JavaBase64() val base64 = JavaBase64()
@ -255,7 +255,7 @@ class TestMatrix(
suspend fun newlogin() { suspend fun newlogin() {
client.authService() client.authService()
.login(user.roomMember.id.value, user.password) .login(AuthService.LoginRequest(user.roomMember.id.value, user.password, null))
} }
suspend fun restoreLogin() { suspend fun restoreLogin() {