54 lines
2.1 KiB
Swift
54 lines
2.1 KiB
Swift
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
|
|
}
|
|
}
|