73 lines
2.3 KiB
Swift
73 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
enum LoginStatus: String, Decodable, Sendable {
|
|
case authenticated
|
|
case mfaRequired = "mfa_required"
|
|
}
|
|
|
|
struct LoginResponse: Decodable, Sendable {
|
|
let status: LoginStatus
|
|
let user: UserResponse?
|
|
let tokens: TokenPair?
|
|
let mfaToken: String?
|
|
let methods: [String]?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case status, user, tokens, methods
|
|
case mfaToken = "mfa_token"
|
|
}
|
|
}
|
|
|
|
struct MessageResponse: Decodable, Sendable {
|
|
let message: String
|
|
}
|
|
|
|
actor AuthService {
|
|
static let shared = AuthService()
|
|
private let client = HTTPClient.shared
|
|
private let keychain = KeychainService.shared
|
|
|
|
private init() {}
|
|
|
|
func login(email: String, password: String) async throws -> UserResponse {
|
|
let response: LoginResponse = try await client.request(.login(email: email, password: password))
|
|
switch response.status {
|
|
case .authenticated:
|
|
guard let user = response.user, let tokens = response.tokens else {
|
|
throw APIError.serverError("Malformed login response")
|
|
}
|
|
try keychain.saveTokens(tokens)
|
|
return user
|
|
case .mfaRequired:
|
|
throw APIError.mfaRequired
|
|
}
|
|
}
|
|
|
|
func register(email: String, password: String) async throws -> UserResponse {
|
|
let response: UserResponse = try await client.request(.register(email: email, password: password))
|
|
return response
|
|
}
|
|
|
|
func verifyEmail(email: String, code: String) async throws {
|
|
let _: MessageResponse = try await client.request(.verifyEmail(email: email, code: code))
|
|
}
|
|
|
|
func resendCode(email: String) async throws {
|
|
let _: MessageResponse = try await client.request(.resendCode(email: email))
|
|
}
|
|
|
|
func logout() async throws {
|
|
// Always clear local tokens, regardless of whether the network call succeeds,
|
|
// to avoid leaving a stale access token in Keychain.
|
|
defer { keychain.clearTokens() }
|
|
guard let refreshToken = keychain.loadRefreshToken() else { return }
|
|
let _: EmptyResponse = try await client.request(.logout(refreshToken: refreshToken))
|
|
}
|
|
|
|
func getMe() async throws -> UserResponse {
|
|
try await client.request(.getMe)
|
|
}
|
|
}
|
|
|
|
struct EmptyResponse: Decodable, Sendable {}
|