feat(auth): SSO-Anmeldung ueber OpenID Connect (AppAuth)

Zusaetzlicher Anmeldeweg neben Benutzername/Passwort. Der Login-Screen holt
die konfigurierten Provider von GET /api/auth/oidc/providers; ohne Provider
oder gegen einen aelteren Server bleibt der Screen unveraendert.

Die App laeuft als Public Client: Authorization Code Flow mit PKCE in einem
Chrome Custom Tab (nie ein WebView), ohne Client Secret. Das ID-Token wird
anschliessend an POST /api/auth/oidc/exchange gegen ein Calendarr-Token
getauscht; die Antwort hat dieselbe Form wie der Passwort-Login, sodass der
bestehende Session-Pfad unveraendert weiterlaeuft.

- OidcAuthenticator kapselt Discovery, PKCE-Request und Code-Tausch.
- Die angefragten Scopes kommen vom Server (mobile_scopes) — ein Scope, den
  der Provider nicht kennt, wuerde die ganze Anfrage scheitern lassen.
- AuthState mit dem Refresh-Token liegt in den EncryptedSharedPreferences,
  nie im Klartext, und wird beim Logout mitgeloescht.
- Provider und Client-ID werden vor dem Browser-Start persistiert, weil der
  Custom Tab in einer eigenen Task laeuft und dieser Prozess dabei getoetet
  werden kann; der Nonce kommt aus AuthorizationResponse.request, das AppAuth
  ueber den Redirect zurueckreicht.
- Abbruch im Browser meldet AppAuth als regulaeres Ergebnis mit
  USER_CANCELED_AUTH_FLOW und wird still behandelt, nicht als Fehler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-09 18:25:15 +02:00
co-authored by Claude Opus 5
parent aeb50c593c
commit 8b0af0f44a
10 changed files with 482 additions and 5 deletions
+10
View File
@@ -29,6 +29,13 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { useSupportLibrary = true }
// AppAuth merges its RedirectUriReceiverActivity into the manifest and
// needs the custom scheme for the OIDC redirect. Reverse-DNS on the
// application id, per RFC 8252; the same value must be registered as a
// redirect URI in the identity provider:
// com.scarriffle.calendarr:/oauth2redirect
manifestPlaceholders["appAuthRedirectScheme"] = "com.scarriffle.calendarr"
}
signingConfigs {
@@ -109,6 +116,9 @@ dependencies {
// Secure credential storage
implementation("androidx.security:security-crypto:1.1.0-alpha06")
// OIDC / SSO — Authorization Code Flow with PKCE in a Custom Tab
implementation("net.openid:appauth:0.11.1")
// Async
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")