mana-swift-core/CHANGELOG.md
Till JS fe607c15d2 v1.5.0 — getProfile() + ProfileInfo
Apps können den 2FA-Status des eingeloggten Users lesen, damit
AccountView entscheidet ob "Zwei-Faktor aktivieren" oder
"Zwei-Faktor deaktivieren" angezeigt wird.

ProfileInfo (public struct) — id, email, name, emailVerified,
twoFactorEnabled.

AuthClient.getProfile() -> ProfileInfo — lädt das Profil vom
Server (GET /api/v1/auth/profile → Better Auths /api/auth/get-session).
Nutzt Session-Token als Bearer (Wire-Konvention).

4 neue Tests, 70/70 grün.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 01:06:50 +02:00

302 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog
Alle Änderungen werden hier dokumentiert. Format orientiert an
[Keep a Changelog](https://keepachangelog.com), Versionierung nach
[Semver](https://semver.org).
## [1.5.0] — 2026-05-14
Minor — `getProfile()` + `ProfileInfo`. Apps können den 2FA-Status
des eingeloggten Users lesen, damit AccountView entscheidet ob
"Aktivieren" oder "Deaktivieren" angezeigt wird.
### Neu
- `ProfileInfo` (public struct) — `id`, `email`, `name`,
`emailVerified`, `twoFactorEnabled`.
- `AuthClient.getProfile() -> ProfileInfo` — lädt aktuelles Profil
vom Server (`GET /api/v1/auth/profile` → Better Auths
`/api/auth/get-session`). Nutzt Session-Token als Bearer.
### Tests
- 4 neue Tests (twoFactor-on, twoFactor-off, ohne Session,
unauthorized). 70/70 grün.
## [1.4.0] — 2026-05-14
Minor — 2FA-Enrollment (Mini-Sprint B). Setzt Mini-Sprint A
(`v1.3.0`) voraus. Komplett additiv.
### ManaCore — 2FA-Enrollment
- `TotpEnrollment` (public struct) — `totpURI` (für QR-Code-Display)
+ `backupCodes` (Liste).
- `AuthClient.enrollTotp(password:) -> TotpEnrollment` — aktiviert
TOTP-2FA; Server generiert Secret + Backup-Codes.
- `AuthClient.disableTotp(password:)` — deaktiviert wieder.
- `AuthClient.getTotpUri(password:) -> String` — Re-Display für
zweites Authenticator-Gerät.
- `AuthClient.regenerateBackupCodes(password:) -> [String]` — neue
Codes, alte werden ungültig.
Alle vier Methoden senden Bearer-Header mit Session-Token (Wire-
Konvention für mana-auth-Account-Endpoints).
### Server-Side Voraussetzung
`mana-auth` ≥ Commit der Wrapper-Endpoints:
- `POST /api/v1/auth/two-factor/enable`
- `POST /api/v1/auth/two-factor/disable`
- `POST /api/v1/auth/two-factor/get-totp-uri`
- `POST /api/v1/auth/two-factor/generate-backup-codes`
### Tests
- 7 neue Tests (Success-Pfade aller vier Methoden, leeres Passwort,
ohne Session, falsches Passwort). 66/66 grün.
## [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`.