diff --git a/.planning/phases/02-authentication-foundation/02-03-PLAN.md b/.planning/phases/02-authentication-foundation/02-03-PLAN.md index 322d3c3..fad34c4 100644 --- a/.planning/phases/02-authentication-foundation/02-03-PLAN.md +++ b/.planning/phases/02-authentication-foundation/02-03-PLAN.md @@ -17,7 +17,9 @@ files_modified: - iosApp/iosApp/iOSApp.swift - iosApp/Podfile - composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.jvm.kt + - composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.jvm.kt - composeApp/src/webMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.wasmJs.kt + - composeApp/src/webMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.wasmJs.kt - composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStoreContractTest.kt autonomous: true requirements: [AUTH-01, AUTH-02, AUTH-04, AUTH-05] @@ -26,6 +28,7 @@ must_haves: - "iOS and Android login use AppAuth authorization-code flow with PKCE through system browser and recipe://callback" - "Requested scopes are exactly openid profile email offline_access" - "AuthState JSON is stored through explicit iOS Keychain and Android EncryptedSharedPreferences-backed stores" + - "Every configured KMP target has a SecureAuthStateStore actual, including JVM and Wasm stubs" - "JVM target has DEV_AUTH_TOKEN dev stub; Wasm target throws NotImplementedError(\"Wasm OIDC: v2\")" - "Logout platform clients support RP-initiated end-session and local store clearing" artifacts: @@ -38,6 +41,12 @@ must_haves: - path: "composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.ios.kt" provides: "iOS Keychain storage with AfterFirstUnlockThisDeviceOnly per D-14" contains: "kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly" + - path: "composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.jvm.kt" + provides: "JVM dev-only in-memory AuthState store actual so desktop tests compile" + contains: "actual class SecureAuthStateStore" + - path: "composeApp/src/webMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.wasmJs.kt" + provides: "Wasm non-persistent AuthState store actual so wasm target compiles while OIDC remains v2" + contains: "actual class SecureAuthStateStore" - path: "iosApp/iosApp/Info.plist" provides: "recipe URL scheme registration" contains: "CFBundleURLSchemes" @@ -148,18 +157,24 @@ Output: expect/actual OIDC client, explicit secure auth state store, URL callbac - composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/main.kt - composeApp/src/webMain/kotlin/dev/ulfrx/recipe/main.kt - composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.jvm.kt, composeApp/src/webMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.wasmJs.kt + composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.jvm.kt, composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.jvm.kt, composeApp/src/webMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.wasmJs.kt, composeApp/src/webMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.wasmJs.kt JVM actual reads `DEV_AUTH_TOKEN` from environment and returns `OidcResult.Success(authStateJson = "dev:$token", accessToken = token, idToken = null, expiresAtEpochMillis = Long.MAX_VALUE)` when present. If missing, return `OidcResult.AuthError("DEV_AUTH_TOKEN is not set")`; do not hardcode a usable bearer token. + JVM `SecureAuthStateStore` actual must compile for desktop dev/tests without pretending to be production secure storage. Implement `actual class SecureAuthStateStore` with a private nullable in-memory `authStateJson` property and exact methods `read()`, `write(authStateJson: String)`, and `clear()`. This store is dev-only and process-local; do not use it for mobile targets. + Wasm actual throws exactly `NotImplementedError("Wasm OIDC: v2")` for login/refresh/logout per D-03. + + Wasm `SecureAuthStateStore` actual must also exist so `:composeApp:compileKotlinWasmJs` compiles. Implement `actual class SecureAuthStateStore` with the same private nullable in-memory `authStateJson` property and `read()`, `write(authStateJson: String)`, `clear()` methods. Because Wasm OIDC itself throws `NotImplementedError("Wasm OIDC: v2")`, this store is non-persistent and only satisfies the KMP actual contract. ./gradlew :composeApp:jvmTest :composeApp:compileKotlinWasmJs - `grep -q 'DEV_AUTH_TOKEN' composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.jvm.kt` + - `grep -q 'actual class SecureAuthStateStore' composeApp/src/jvmMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.jvm.kt` - `grep -q 'NotImplementedError("Wasm OIDC: v2")' composeApp/src/webMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.wasmJs.kt` + - `grep -q 'actual class SecureAuthStateStore' composeApp/src/webMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.wasmJs.kt` - `./gradlew :composeApp:jvmTest :composeApp:compileKotlinWasmJs` exits 0 Secondary targets compile without expanding Phase 2 scope into real Desktop/Wasm OIDC.