fix(02-04): harden Android OIDC token result mapping

- Treat missing access tokens as auth failures
- Preserve AppAuth discovery errors for correct network/auth classification
This commit is contained in:
2026-04-28 16:00:20 +02:00
parent 6385453653
commit 11a5eeb3ff

View File

@@ -28,8 +28,11 @@ actual class OidcClient {
get() = GlobalContext.get().get<Context>().applicationContext get() = GlobalContext.get().get<Context>().applicationContext
actual suspend fun login(): OidcResult { actual suspend fun login(): OidcResult {
val configuration = fetchConfiguration() val configuration =
?: return OidcResult.NetworkError when (val outcome = fetchConfiguration()) {
is ConfigurationOutcome.Success -> outcome.configuration
is ConfigurationOutcome.Error -> return outcome.exception.toOidcError()
}
val request = val request =
AuthorizationRequest AuthorizationRequest
@@ -87,14 +90,19 @@ actual class OidcClient {
} }
} }
private suspend fun fetchConfiguration(): AuthorizationServiceConfiguration? = private suspend fun fetchConfiguration(): ConfigurationOutcome =
suspendCancellableCoroutine { continuation -> suspendCancellableCoroutine { continuation ->
AuthorizationServiceConfiguration.fetchFromIssuer(Uri.parse(Constants.OIDC_ISSUER)) { configuration, exception -> AuthorizationServiceConfiguration.fetchFromIssuer(Uri.parse(Constants.OIDC_ISSUER)) { configuration, exception ->
if (!continuation.isActive) return@fetchFromIssuer if (!continuation.isActive) return@fetchFromIssuer
when { when {
configuration != null -> continuation.resume(configuration) configuration != null -> continuation.resume(ConfigurationOutcome.Success(configuration))
exception != null && exception.isNetworkFailure() -> continuation.resume(null) exception != null -> continuation.resume(ConfigurationOutcome.Error(exception))
else -> continuation.resume(null) else ->
continuation.resume(
ConfigurationOutcome.Error(
AuthorizationException.GeneralErrors.INVALID_DISCOVERY_DOCUMENT,
),
)
} }
} }
} }
@@ -110,6 +118,8 @@ actual class OidcClient {
when { when {
exception != null -> continuation.resume(exception.toOidcError()) exception != null -> continuation.resume(exception.toOidcError())
tokenResponse == null -> continuation.resume(OidcResult.AuthError("Token exchange returned no response")) tokenResponse == null -> continuation.resume(OidcResult.AuthError("Token exchange returned no response"))
tokenResponse.accessToken.isNullOrBlank() ->
continuation.resume(OidcResult.AuthError("Token exchange returned no access token"))
else -> { else -> {
authState.update(tokenResponse, null) authState.update(tokenResponse, null)
continuation.resume(authState.toSuccess(tokenResponse)) continuation.resume(authState.toSuccess(tokenResponse))
@@ -276,4 +286,10 @@ actual class OidcClient {
data object Cancelled : AuthorizationOutcome data object Cancelled : AuthorizationOutcome
} }
private sealed interface ConfigurationOutcome {
data class Success(val configuration: AuthorizationServiceConfiguration) : ConfigurationOutcome
data class Error(val exception: AuthorizationException) : ConfigurationOutcome
}
} }