fix: 401 token refresh in MessageRepositoryImpl

fetchUsers() and attemptSendToServer() had no retry logic on 401.
SyncServiceImpl already had this pattern (auto-refresh on 401).

Add private refreshAccessToken(serverUrl) and retry once on 401
in both methods.
This commit is contained in:
Jens Reinemann 2026-05-18 00:44:58 +02:00
parent ea02029dbe
commit a14c40d756

View file

@ -4,6 +4,8 @@ import android.util.Log
import de.bollwerk.app.data.db.dao.MessageDao import de.bollwerk.app.data.db.dao.MessageDao
import de.bollwerk.app.data.db.entity.MessageEntity import de.bollwerk.app.data.db.entity.MessageEntity
import de.bollwerk.app.data.security.E2EEKeyManager import de.bollwerk.app.data.security.E2EEKeyManager
import de.bollwerk.app.data.sync.LoginResponse
import de.bollwerk.app.data.sync.RefreshRequest
import de.bollwerk.app.data.sync.WebSocketClient import de.bollwerk.app.data.sync.WebSocketClient
import de.bollwerk.app.data.sync.WebSocketEvent import de.bollwerk.app.data.sync.WebSocketEvent
import de.bollwerk.app.di.ApplicationScope import de.bollwerk.app.di.ApplicationScope
@ -129,12 +131,19 @@ internal class MessageRepositoryImpl @Inject constructor(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val serverUrl = settingsRepository.getStringOrNull(StringKey.ServerUrl) val serverUrl = settingsRepository.getStringOrNull(StringKey.ServerUrl)
?: return@withContext Result.failure(SyncError.NotConfigured("Server-URL nicht gesetzt")) ?: return@withContext Result.failure(SyncError.NotConfigured("Server-URL nicht gesetzt"))
val token = settingsRepository.getStringOrNull(StringKey.AuthAccessToken) var token = settingsRepository.getStringOrNull(StringKey.AuthAccessToken)
?: return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet")) ?: return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet"))
try { try {
val response = httpClient.get("${serverUrl.trimEnd('/')}/api/users") { var response = httpClient.get("${serverUrl.trimEnd('/')}/api/users") {
header("Authorization", "Bearer $token") header("Authorization", "Bearer $token")
} }
if (response.status == HttpStatusCode.Unauthorized && refreshAccessToken(serverUrl)) {
token = settingsRepository.getStringOrNull(StringKey.AuthAccessToken)
?: return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet"))
response = httpClient.get("${serverUrl.trimEnd('/')}/api/users") {
header("Authorization", "Bearer $token")
}
}
if (response.status == HttpStatusCode.OK) { if (response.status == HttpStatusCode.OK) {
Result.success(response.body<List<UserListItemDto>>()) Result.success(response.body<List<UserListItemDto>>())
} else { } else {
@ -199,6 +208,27 @@ internal class MessageRepositoryImpl @Inject constructor(
} }
} }
private suspend fun refreshAccessToken(serverUrl: String): Boolean {
val refreshToken = settingsRepository.getStringOrNull(StringKey.AuthRefreshToken)
?.takeIf { it.isNotBlank() } ?: return false
return try {
val response = httpClient.post("${serverUrl.trimEnd('/')}/api/auth/refresh") {
contentType(ContentType.Application.Json)
setBody(RefreshRequest(refreshToken))
}
if (response.status == HttpStatusCode.OK) {
val loginResponse: LoginResponse = response.body()
settingsRepository.setString(StringKey.AuthAccessToken, loginResponse.accessToken)
settingsRepository.setString(StringKey.AuthRefreshToken, loginResponse.refreshToken)
true
} else {
false
}
} catch (_: Exception) {
false
}
}
private suspend fun attemptSendToServer( private suspend fun attemptSendToServer(
id: String, id: String,
recipientId: String, recipientId: String,
@ -207,14 +237,23 @@ internal class MessageRepositoryImpl @Inject constructor(
): Result<Unit> = withContext(Dispatchers.IO) { ): Result<Unit> = withContext(Dispatchers.IO) {
val serverUrl = settingsRepository.getStringOrNull(StringKey.ServerUrl) val serverUrl = settingsRepository.getStringOrNull(StringKey.ServerUrl)
?: return@withContext Result.failure(SyncError.NotConfigured("Server-URL nicht gesetzt")) ?: return@withContext Result.failure(SyncError.NotConfigured("Server-URL nicht gesetzt"))
val token = settingsRepository.getStringOrNull(StringKey.AuthAccessToken) var token = settingsRepository.getStringOrNull(StringKey.AuthAccessToken)
?: return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet")) ?: return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet"))
try { try {
val response = httpClient.post("${serverUrl.trimEnd('/')}/api/messages") { var response = httpClient.post("${serverUrl.trimEnd('/')}/api/messages") {
header("Authorization", "Bearer $token") header("Authorization", "Bearer $token")
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(SendMessageRequest(id = id, receiverId = recipientId, body = body, sentAt = sentAt)) setBody(SendMessageRequest(id = id, receiverId = recipientId, body = body, sentAt = sentAt))
} }
if (response.status == HttpStatusCode.Unauthorized && refreshAccessToken(serverUrl)) {
token = settingsRepository.getStringOrNull(StringKey.AuthAccessToken)
?: return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet"))
response = httpClient.post("${serverUrl.trimEnd('/')}/api/messages") {
header("Authorization", "Bearer $token")
contentType(ContentType.Application.Json)
setBody(SendMessageRequest(id = id, receiverId = recipientId, body = body, sentAt = sentAt))
}
}
if (response.status == HttpStatusCode.Created || response.status == HttpStatusCode.OK) { if (response.status == HttpStatusCode.Created || response.status == HttpStatusCode.OK) {
Result.success(Unit) Result.success(Unit)
} else { } else {