initial commit

This commit is contained in:
Jon Vanvik 2026-05-31 14:11:29 +02:00
commit 8de962eeb8
44 changed files with 4843 additions and 0 deletions

54
SessionStore.swift Normal file
View file

@ -0,0 +1,54 @@
import Foundation
/// Holds session state for an authenticated MCMS connection.
///
/// Cookie handling: `URLSession` stores the `sessionid` and `csrftoken` cookies
/// automatically and replays them. Our job is to (a) read the CSRF cookie value
/// so we can echo it in the `X-CSRFToken` header on writes (Django-style), and
/// (b) clear cookies on logout.
@MainActor
final class SessionStore {
private(set) var isAuthenticated = false
/// The cookie storage backing the session. Injected so a proxy deployment
/// can use an isolated storage if needed.
let cookieStorage: HTTPCookieStorage
private let config: APIConfiguration
init(config: APIConfiguration, cookieStorage: HTTPCookieStorage = .shared) {
self.config = config
self.cookieStorage = cookieStorage
}
func markAuthenticated() { isAuthenticated = true }
/// Current CSRF token (the value of the CSRF cookie), to be sent as the
/// `X-CSRFToken` header on mutating requests. Returns nil before login.
func csrfToken() -> String? {
guard let cookies = cookieStorage.cookies(for: config.baseURL) else { return nil }
return cookies.first { Self.matches($0.name, config.csrfCookieName) }?.value
}
/// Whether a session cookie is currently present.
var hasSessionCookie: Bool {
guard let cookies = cookieStorage.cookies(for: config.baseURL) else { return false }
return cookies.contains { Self.matches($0.name, config.sessionCookieName) }
}
/// Modern MCMS 6.2 builds emit `__Host-csrftoken` / `__Host-sessionid`;
/// older ones use the bare names. Accept either form (also `__Secure-`).
static func matches(_ cookieName: String, _ base: String) -> Bool {
cookieName == base
|| cookieName == "__Host-" + base
|| cookieName == "__Secure-" + base
}
/// Drop all cookies for the configured host (call after logout / on reset).
func clear() {
if let cookies = cookieStorage.cookies(for: config.baseURL) {
cookies.forEach { cookieStorage.deleteCookie($0) }
}
isAuthenticated = false
}
}