63 lines
2.3 KiB
Swift
63 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
public struct SessionMetadata: Equatable, Sendable {
|
|
public let instanceID: UUID
|
|
public let authenticated: Bool
|
|
public let checkedAt: Date
|
|
|
|
public init(instanceID: UUID, authenticated: Bool, checkedAt: Date) {
|
|
self.instanceID = instanceID
|
|
self.authenticated = authenticated
|
|
self.checkedAt = checkedAt
|
|
}
|
|
}
|
|
|
|
public struct InstanceSessionService: Sendable {
|
|
private let store: InstanceSessionStore
|
|
private let client: any SimAdminClientProtocol
|
|
|
|
public init(store: InstanceSessionStore, client: any SimAdminClientProtocol) {
|
|
self.store = store
|
|
self.client = client
|
|
}
|
|
|
|
public func login(_ instance: Instance, password: String) async throws -> SessionMetadata {
|
|
do {
|
|
let response = try await client.send(
|
|
.authLogin(password: password),
|
|
baseURL: instance.origin,
|
|
cookie: nil
|
|
)
|
|
if let cookie = Self.sessionCookie(response.setCookie) {
|
|
await store.set(instance.id, origin: instance.origin, cookie: cookie)
|
|
return SessionMetadata(instanceID: instance.id, authenticated: true, checkedAt: Date())
|
|
}
|
|
} catch {
|
|
// Rejected credentials fall through to the same explicit unauthenticated state.
|
|
}
|
|
await store.clear(instance.id)
|
|
return SessionMetadata(instanceID: instance.id, authenticated: false, checkedAt: Date())
|
|
}
|
|
|
|
public func logout(_ instance: Instance) async throws -> SessionMetadata {
|
|
if let cookie = await store.cookie(for: instance.id, origin: instance.origin) {
|
|
_ = try? await client.send(.authLogout, baseURL: instance.origin, cookie: cookie)
|
|
}
|
|
await store.clear(instance.id)
|
|
return SessionMetadata(instanceID: instance.id, authenticated: false, checkedAt: Date())
|
|
}
|
|
|
|
public func cookie(for instance: Instance) async -> String? {
|
|
await store.cookie(for: instance.id, origin: instance.origin)
|
|
}
|
|
|
|
static func sessionCookie(_ header: String?) -> String? {
|
|
guard let header,
|
|
let range = header.range(of: "simadmin_session=([^;\\s,]+)", options: .regularExpression)
|
|
else {
|
|
return nil
|
|
}
|
|
return String(header[range])
|
|
}
|
|
}
|