95 lines
3.1 KiB
Swift
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
|
|
}
|
|
}
|