# Changelog Alle Änderungen werden hier dokumentiert. Format orientiert an [Keep a Changelog](https://keepachangelog.com), Versionierung nach [Semver](https://semver.org). ## [1.3.0] — 2026-05-14 Minor — 2FA-Login-Challenge (Mini-Sprint A). Apps mit aktiviertem TOTP-2FA können sich jetzt nativ einloggen. Komplett additiv. ### ManaCore — 2FA-Login - `AuthClient.Status.twoFactorRequired(token: String, methods: [String], email: String)` als neuer Case. Tritt nach `signIn(...)` auf, wenn der Account 2FA aktiviert hat. `token` ist der opaque `two_factor`-Cookie-Wert vom Server, den die App bei `verifyTotp(...)` zurückschickt. - `AuthClient.verifyTotp(code:trustDevice:)` — verifiziert 6-stelligen TOTP-Code. Bei Erfolg `.signedIn`, bei Fehler bleibt der Status im Challenge (User kann retry). - `AuthClient.verifyBackupCode(code:trustDevice:)` — Fallback wenn das TOTP-Gerät verloren wurde. Backup-Codes sind einmalig. - `signIn(...)` erkennt den Server-Pfad `{twoFactorRequired: true, ...}` und routet automatisch zu `.twoFactorRequired`. ### Server-Side Voraussetzung Setzt zwei neue Custom-Endpoints in `mana-auth` voraus: - `POST /api/v1/auth/two-factor/verify-totp` - `POST /api/v1/auth/two-factor/verify-backup-code` Plus die `/api/v1/auth/login`-Erweiterung um den `twoFactorRequired`- Pfad. Siehe `mana/services/mana-auth/src/routes/auth.ts`. ### Tests - 5 neue Tests (signIn-Redirect, verifyTotp-Success/-Fail, ohne-Challenge- Guard, verifyBackupCode). 59/59 grün. ### Bewusst NICHT in v1.3.0 - 2FA-**Enrollment** (TOTP-Setup) — eigener Mini-Sprint B mit `enrollTotp()`, `disableTotp()`, `regenerateBackupCodes()`. - Magic-Link, Passkey — eigene Sprints. ## [1.2.0] — 2026-05-13 Minor — Guest-Mode + Auth-Resilience. Native-Apps werden gegen mana-auth- Downtime gehärtet und können jetzt einen anonymen Local-First-Modus anbieten. Komplett additiv — keine Breaking Changes für bestehende Konsumenten (Memoro, Cards, Manaspur, Nutriphi). ### ManaCore — Guest-Identität - `AuthClient.Status` um Case `.guest(id: String)` erweitert. Persistente lokale UUID ohne Server-Account; gleichberechtigt mit `.signedIn` als „App ist nutzbar"-Zustand. Apps können in diesem Modus alles Lokale und alle unauthenticated-Server-Endpoints anbieten, schreibende Endpoints poppen Auth-Sheet. - `AuthClient.enterGuestMode() throws -> String` — idempotent, erzeugt oder reuse die Guest-UUID aus Keychain. Wechselt den Status nur, wenn aktuell `.signedOut`/`.unknown` (eine aktive Session bleibt unangetastet, App kann die Guest-ID parallel lesen). - `AuthClient.currentGuestId() -> String?` — Lookup unabhängig vom Status. Genutzt z.B. um lokale Guest-Daten beim Sign-In dem neuen Server- Account zuzuordnen. - `AuthClient.clearGuestId()` — entfernt die Guest-ID, etwa nach erfolgreicher Migration der lokalen Daten auf einen Server-Account. - `AuthClient.signOut(keepGuestMode: Bool = false)` — Default-Verhalten unverändert (`false` löscht alles, Status `.signedOut`). Mit `true` bleibt die App im anonymen Modus weiter nutzbar. - `KeychainStore.Key.guestId` als neuer Key. `wipe()` löscht jetzt *nur* Session-Felder (accessToken/refreshToken/email) — die Guest-ID überlebt. Für komplettes Vergessen: neue `wipeAll()`. ### ManaCore — Refresh-Resilience - `refreshAccessToken()` wipt nicht mehr blind den Keychain bei jedem Nicht-200. Stattdessen Heuristik via `AuthError.invalidatesSession`: - **Wipe** bei `.invalidCredentials`, `.unauthorized`, `.tokenExpired`, `.tokenInvalid`, `.emailNotVerified` — Session ist tatsächlich tot. - **Behalten** bei `.serviceUnavailable` (503), `.serverInternal` (500), `.networkFailure`, `.rateLimited`, weiteren transienten Fehlern. Apps werden bei mana-auth-Downtime nicht mehr in den Login-Screen geworfen. - Beim Wipe-Pfad fällt der Status auf `.guest(id)` zurück, falls eine Guest-Identität existiert; sonst auf `.signedOut`. - `AuthError.invalidatesSession: Bool` — public computed Property, auch von Apps direkt nutzbar (z.B. um auf Transport-Fehler zu reagieren). ### Tests - 15 neue Tests: Guest-Mode (Idempotenz, Bootstrap-Priorität, Status- Übergänge), signOut(keepGuestMode:) in beiden Modi, Refresh-Verhalten bei 401/429/500/503/Network, invalidatesSession-Partitionierung. ### Migration für Apps Bestehende Apps brauchen **keine** Änderung — Default-Verhalten ist identisch. Wer den anonymen Modus nutzen will: ```swift // Beim App-Start nach bootstrap(): auth.bootstrap() if case .signedOut = auth.status { try? auth.enterGuestMode() // Statt sofort Login-Screen } // In Aktionen, die einen Account brauchen: guard case .signedIn = auth.status else { presentLoginSheet() return } ``` ## [1.1.1] — 2026-05-13 Patch — Wire-Konvention für authenticated Account-Calls geklärt. ### Geändert - `AuthClient.changeEmail`, `changePassword`, `deleteAccount` senden jetzt den Session-Token (`refreshToken`-Feldwert) statt des JWT als `Authorization: Bearer`. Hintergrund: Server-seitig wurde in `mana-auth` Better Auths `bearer`-Plugin aktiviert (`requireSignature: false`), das Session-Tokens zu Session-Cookies konvertiert. Damit funktionieren `auth.api.changeEmail` etc. für Native-Apps ohne Cookie-Container. - `AuthClient.currentSessionToken()` als public Helper hinzu. Symmetrisch zu `currentAccessToken()`. ### Trade-Off bewusst akzeptiert Session-Token wird bei jedem Account-Call versendet (vorher nur beim `/refresh`). Mit TLS-Baseline akzeptables Risiko; Compromise-Surface nicht relevant größer als JWT-Leak. Alternative wäre ein Custom- Bearer-JWT-to-Cookie-Resolver im Server (40+ Zeilen Hono-Middleware, HMAC-Cookie-Synthese) — bewusst nicht gewählt, weil der bearer-Plugin genau für diesen Use-Case existiert. ### Tests - Test `changePassword schickt Bearer-Header` umbenannt auf `schickt Session-Token als Bearer (nicht JWT)` und geupdated. ## [1.1.0] — 2026-05-13 Phase 1 aus dem Native-Auth-Vollausbau-Plan (Option A — alles nativ, siehe `mana/docs/MANA_SWIFT.md`). Erweitert `ManaCore` um die Account-Lifecycle-Methoden, die jede native Verein-App für eine vollständige Auth-Reise braucht. ### ManaCore — Neue API (additiv, keine Breaking Changes) - `AuthClient.register(email:password:name:sourceAppUrl:)` — Sign-Up gegen `POST /api/v1/auth/register`. Persistiert eine Session automatisch, wenn der Server Tokens mitliefert; sonst still und wartend auf Email-Verifikation. - `AuthClient.forgotPassword(email:resetUniversalLink:)` — Passwort- Reset-Mail anfordern gegen `POST /api/v1/auth/forgot-password`. Server antwortet immer 200 (keine User-Enumeration). - `AuthClient.resetPassword(token:newPassword:)` — Passwort mit Token aus Reset-Mail setzen. - `AuthClient.resendVerification(email:sourceAppUrl:)` — Verify-Mail erneut versenden, aufzurufen nach ``AuthError/emailNotVerified``. - `AuthClient.changeEmail(newEmail:callbackUniversalLink:)` — Email ändern (verschickt Verify-Mail an neue Adresse). **Aktuell server- seitig nicht Bearer-fähig** — siehe Doc-Header von `AuthClient+Account.swift`. - `AuthClient.changePassword(currentPassword:newPassword:)` — Passwort ändern. Gleiche Bearer-Einschränkung wie `changeEmail`. - `AuthClient.deleteAccount(password:)` — Account löschen (App-Store-Guideline 5.1.1(v) Pflicht). Wiped Keychain bei Erfolg. Gleiche Bearer-Einschränkung wie oben. ### ManaCore — `AuthError` ausgebaut - Präzise Cases pro Server-`AuthErrorCode`: `.emailNotVerified`, `.emailAlreadyRegistered`, `.weakPassword(message:)`, `.accountLocked(retryAfter:)`, `.signupLimitReached`, `.rateLimited(retryAfter:)`, `.tokenExpired`, `.tokenInvalid`, `.twoFactorRequired`, `.twoFactorFailed`, `.passkeyNotEnabled`, `.passkeyCancelled`, `.passkeyVerificationFailed`, `.validation(message:)`, `.unauthorized`, `.notFound`, `.serviceUnavailable`, `.serverInternal`. - `AuthError.classify(status:data:retryAfterHeader:)` — public, klassifiziert mana-auth-Fehler-Antworten in den passenden Case. Auch genutzt von `signIn` und `refreshAccessToken` (vorher: einfache `.error(String)`-Strings). - `AuthError` ist jetzt `Equatable` — erleichtert UI-Logik und Tests. - Alte Cases `.invalidCredentials`, `.networkFailure`, `.encoding`, `.keychain`, `.decoding`, `.notSignedIn` bleiben unverändert. - **Breaking-Vermeidung:** `serverError(status:message:)` wurde zu `serverError(status:code:message:)` (zusätzliches `code`-Argument). Theoretisch breaking, praktisch nutzt es niemand außerhalb von ManaCore selbst. Wenn ein App-Konsument darauf gepattern-matched hat, ist das ein Compile-Fehler, kein Runtime-Bug. ### Tests - 14 neue Tests für `AuthError.classify` (jeder ErrorCode + Status- Heuristik + Retry-After-Header + kaputter Body). - 12 neue Tests für die neuen `AuthClient`-Methoden via `URLProtocol`-Mock (Wire-Format, Status-Mapping, Bearer-Header, Session-Persistenz bei `register`, Session-Wipe bei `deleteAccount`). ### Bekannte Einschränkungen - `changeEmail`, `changePassword`, `deleteAccount` brauchen Server- seitig den `bearer`-Plugin von Better Auth oder einen Custom- Bearer-Resolver. Heute mountet `mana-auth` nur den Cookie-Pfad. Phase-3-Server-PR im `mana`-Repo dokumentiert. - 2FA-Verify, Magic-Link und Passkey-Flows sind in dieser Version bewusst NICHT enthalten. Laufen Server-seitig über Better-Auth- Native (`/api/auth/*`, Cookie) und brauchen eigene JWT-Pfade. Folgt in v1.2.0 zusammen mit dem Server-PR. ## [1.0.1] — 2026-05-13 ### Behoben - `AuthenticatedTransport`: `URL.appending(path:)` URL-encoded das `?` in Query-Strings zu `%3F`, was den Server-Route-Match brechen ließ (404 für `/healthz?…`). Ersetzt durch String-Concat; Caller liefert den Path inkl. führendem `/` und optionaler Query. ## [1.0.0] — 2026-05-12 Initiale Extraktion aus `memoro-native` (Phase α aus `mana/docs/MANA_SWIFT.md`). ### ManaCore (neu) - `ManaAppConfig`-Protocol für App-injizierbare Konfiguration (`authBaseURL`, `keychainService`, `keychainAccessGroup`). - `AuthClient` — mana-auth-Login per E-Mail+PW, Status-Maschine, Token-Speicherung im Keychain, proaktiver Refresh. - `JWT` — Token-Expiry-Berechnung (lokaler Parse, keine Signatur-Verifikation). - `KeychainStore` — generisches Token-Storage, konfigurierbarer Service-Identifier + Access-Group. - `AuthError` — sprechende Fehlertypen mit `LocalizedError`-Texten. - `AuthenticatedTransport` — URLSession-Wrapper mit Auth-Header und automatischem 401-Retry-mit-Refresh. ### ManaTokens (neu) - Farben, Spacings, Typography, Radius — gespiegelt aus `mana/docs/THEMING.md`.