import Foundation import ManaCore import WebKit /// Reicht den mana-auth-JWT als Cookie an den `WKWebView` weiter, sodass /// eingeloggte `(app)`-Routen auf `app.zitare.com` ohne zweiten Login /// erreichbar sind. /// /// **Phase ζ-1: Skeleton.** Methoden existieren, werden aber heute /// nicht aufgerufen — bevor sie scharfgeschaltet werden, muss der /// Cookie-SSO-Pfad auf der Web-Seite (`zitare/apps/api/src/auth/` /// und `apps/zitare/src/lib/auth/token-helper.ts`) gegen einen /// *echten* mana-auth-Token End-to-End getestet sein (Verifikations- /// Lücke in `zitare/STATUS.md`). /// /// **Cross-Domain-Flow (Cutover 2026-05-20):** Der WKWebView lädt /// `app.zitare.com` (Brand-Domain). Die SvelteKit-App ruft beim /// Boot Cross-Origin `POST auth.mana.how/api/v1/auth/refresh` mit /// `credentials: 'include'` und erwartet einen Refresh-Cookie auf /// `.mana.how`. Der Cookie-Domain-Wert hier (`.mana.how`) ist /// genau richtig — der Browser sendet ihn an das XHR-Ziel /// (auth.mana.how), unabhängig vom Source-Page-Host. Identisches /// Pattern wie im Web-Client (`apps/zitare/src/lib/auth.ts`). /// /// **Cookie-Schema** (gespiegelt zu mana-auth `better-auth.config.ts`): /// - Name: `mana.access` (JWT) und optional `mana.refresh` (Opaque) /// - Domain: `.mana.how` (Cookie wird an auth.mana.how-XHR mitgesendet) /// - Path: `/` /// - Secure: true, HTTPOnly: false (WebView muss lesen können) /// - SameSite: **None** — mana-auth setzt für Cross-Subdomain-SSO /// `sameSite: 'none'` (better-auth.config.ts), ohne das wird der /// Cookie bei Cross-Origin-POST nicht mitgesendet. Foundation /// `HTTPCookieStringPolicy` hat dafür keinen Konstanten-Wert → /// `cookieAttributesNone` als Roh-String über die initWithProperties- /// `String`-Variante. enum CookieBridge { /// Setzt den `mana.access`-Cookie im geteilten `WKHTTPCookieStore`, /// wenn der `AuthClient` einen gültigen JWT hält. No-op sonst. @MainActor static func installManaAccess(from auth: AuthClient) async { guard case .signedIn = auth.status, let token = currentAccessToken(from: auth) else { Log.web.debug("CookieBridge: kein signedIn-Token, no-op") return } guard let cookie = makeAccessCookie(token: token) else { Log.web.warning("CookieBridge: konnte Cookie-Properties nicht bauen") return } let store = WKWebsiteDataStore.default().httpCookieStore await store.setCookie(cookie) Log.web.info("CookieBridge: mana.access für .mana.how gesetzt") } /// Entfernt den `mana.access`-Cookie wieder — etwa nach Logout. @MainActor static func removeManaAccess() async { let store = WKWebsiteDataStore.default().httpCookieStore let cookies = await store.allCookies() for cookie in cookies where cookie.name == "mana.access" { await store.deleteCookie(cookie) } Log.web.info("CookieBridge: mana.access entfernt") } private static func currentAccessToken(from auth: AuthClient) -> String? { // ManaCore hält den JWT im Keychain. In ζ-3 ersetzt durch die // tatsächliche `auth.currentAccessToken()`-API; heute nur // Skelett-Hook, damit Cookie-Setup und API in Reichweite sind. // Linter beruhigen ohne unused warning: _ = auth return nil } private static func makeAccessCookie(token: String) -> HTTPCookie? { // SameSite=None: Foundation hat keinen Konstanten-Wert für // "None", aber HTTPCookie akzeptiert beliebige Strings für // .sameSitePolicy. Cross-Origin-POST von app.zitare.com → // auth.mana.how braucht None, sonst kein Cookie-Versand. HTTPCookie(properties: [ .name: "mana.access", .value: token, .domain: ".mana.how", .path: "/", .secure: true, .sameSitePolicy: "None" ]) } }