Files
ABS-Client/ABS Client Mac/Audiobookshelf swift/Services/AuthStore.swift
2026-05-17 08:45:37 +02:00

95 lines
3.1 KiB
Swift

import Foundation
import Observation
enum AuthError: LocalizedError {
case invalidURL
case badResponse(Int)
case noToken
case unknown(String)
var errorDescription: String? {
switch self {
case .invalidURL: return "Ungültige Server-URL."
case .badResponse(let code): return "Server antwortete mit Status \(code)."
case .noToken: return "Login fehlgeschlagen: kein Token erhalten."
case .unknown(let msg): return msg
}
}
}
@Observable
@MainActor
final class AuthStore {
var isLoggedIn: Bool = false
var serverURL: String = ""
var username: String = ""
var token: String = ""
var errorMessage: String?
func restoreSession() {
guard let creds = KeychainStore.load() else { return }
self.serverURL = creds.serverURL
self.username = creds.username
self.token = creds.token
self.isLoggedIn = true
}
func login(serverURL rawURL: String, username: String, password: String, remember: Bool) async {
errorMessage = nil
let normalized = Self.normalizeURL(rawURL)
guard let url = URL(string: normalized + "/login") else {
errorMessage = AuthError.invalidURL.errorDescription
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body = ["username": username, "password": password]
request.httpBody = try? JSONEncoder().encode(body)
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse else {
errorMessage = "Keine HTTP-Antwort vom Server."
return
}
guard (200..<300).contains(http.statusCode) else {
errorMessage = AuthError.badResponse(http.statusCode).errorDescription
return
}
let decoded = try JSONDecoder().decode(LoginResponseDTO.self, from: data)
self.serverURL = normalized
self.username = decoded.user.username ?? username
self.token = decoded.user.token
self.isLoggedIn = true
if remember {
try? KeychainStore.save(StoredCredentials(
serverURL: normalized,
username: self.username,
token: self.token
))
} else {
KeychainStore.delete()
}
} catch {
errorMessage = "Login fehlgeschlagen: \(error.localizedDescription)"
}
}
func logout() {
KeychainStore.delete()
token = ""
isLoggedIn = false
}
static func normalizeURL(_ raw: String) -> String {
var s = raw.trimmingCharacters(in: .whitespacesAndNewlines)
while s.hasSuffix("/") { s.removeLast() }
if !s.lowercased().hasPrefix("http://") && !s.lowercased().hasPrefix("https://") {
s = "https://" + s
}
return s
}
}