Files
furumi_macos/furumi_macos/AuthService.swift
T
Ultradesu 2f8cff528c init
2026-06-08 16:50:16 +01:00

108 lines
3.4 KiB
Swift

//
// AuthService.swift
// furumi_macos
//
import Foundation
struct AuthService {
private let urlSession = URLSession.shared
private let decoder: JSONDecoder = {
let d = JSONDecoder()
d.keyDecodingStrategy = .convertFromSnakeCase
return d
}()
private var deviceName: String {
ProcessInfo.processInfo.hostName.components(separatedBy: ".").first ?? "macOS"
}
// MARK: - URL builders
func ssoStartURL(baseUrl: String) -> URL? {
guard var components = URLComponents(string: "\(baseUrl)/auth/mobile/oidc/start") else { return nil }
components.queryItems = [URLQueryItem(name: "redirect_uri", value: "furumi://auth/callback")]
return components.url
}
// MARK: - API
func login(baseUrl: String, username: String, password: String) async throws -> LoginResponse {
struct Body: Encodable {
let username: String
let password: String
let device_name: String
}
return try await post(
urlString: "\(baseUrl)/api/auth/password",
body: Body(username: username, password: password, device_name: deviceName)
)
}
func ssoExchange(baseUrl: String, code: String) async throws -> LoginResponse {
struct Body: Encodable {
let code: String
let device_name: String
}
return try await post(
urlString: "\(baseUrl)/api/auth/sso/exchange",
body: Body(code: code, device_name: deviceName)
)
}
func refresh(baseUrl: String, refreshToken: String) async throws -> TokenResponse {
struct Body: Encodable { let refresh_token: String }
return try await post(
urlString: "\(baseUrl)/api/auth/refresh",
body: Body(refresh_token: refreshToken)
)
}
func logout(baseUrl: String, authorizationHeader: String, refreshToken: String) async throws {
struct Body: Encodable { let refresh_token: String }
let _: LogoutResponse = try await post(
urlString: "\(baseUrl)/api/auth/logout",
body: Body(refresh_token: refreshToken),
authHeader: authorizationHeader
)
}
// MARK: - Private
private func post<B: Encodable, R: Decodable>(
urlString: String,
body: B,
authHeader: String? = nil
) async throws -> R {
guard let url = URL(string: urlString) else {
throw AuthError.invalidServerUrl("Invalid URL: \(urlString)")
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
if let authHeader { request.setValue(authHeader, forHTTPHeaderField: "Authorization") }
request.httpBody = try JSONEncoder().encode(body)
let (data, response) = try await urlSession.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw AuthError.invalidResponse
}
guard (200..<300).contains(http.statusCode) else {
let msg = (try? decoder.decode(ErrorResponse.self, from: data))?.error ?? ""
throw AuthError.serverError(http.statusCode, msg)
}
do {
return try decoder.decode(R.self, from: data)
} catch {
throw AuthError.invalidResponse
}
}
}