193 lines
6.0 KiB
Swift
193 lines
6.0 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
@Observable
|
|
final class AuthViewModel {
|
|
var email = ""
|
|
var password = ""
|
|
var confirmPassword = ""
|
|
var username = ""
|
|
var confirmationCode = ""
|
|
|
|
var isLoading = false
|
|
var errorMessage: String?
|
|
var showConfirmation = false
|
|
var registrationMessage: String?
|
|
|
|
var serverHost = Constants.defaultHost
|
|
var serverPort = String(Constants.defaultPort)
|
|
|
|
var hasSavedCredentials = false
|
|
var isBiometricLoading = false
|
|
|
|
enum AuthMode {
|
|
case login, register, pairing
|
|
}
|
|
var mode: AuthMode = .login
|
|
|
|
func checkSavedCredentials() {
|
|
hasSavedCredentials = KeychainService.hasSavedCredentials() && KeychainService.isBiometricAvailable()
|
|
}
|
|
|
|
func login(appState: AppState) async {
|
|
guard !email.isEmpty, !password.isEmpty else {
|
|
errorMessage = "Email and password are required"
|
|
return
|
|
}
|
|
|
|
isLoading = true
|
|
errorMessage = nil
|
|
|
|
// Only connect if not already connected
|
|
if await !appState.chatClient.isConnected {
|
|
do {
|
|
let port = UInt16(serverPort) ?? Constants.defaultPort
|
|
try await appState.chatClient.connect(host: serverHost, port: port)
|
|
} catch {
|
|
isLoading = false
|
|
errorMessage = "Connection failed: \(error.localizedDescription)"
|
|
return
|
|
}
|
|
}
|
|
|
|
let (success, message) = await appState.chatClient.login(email: email, password: password)
|
|
isLoading = false
|
|
|
|
if success {
|
|
appState.email = email
|
|
appState.isLoggedIn = true
|
|
appState.connectionStatus = .connected
|
|
appState.startConnectionMonitor()
|
|
if let userId = await appState.chatClient.userId {
|
|
appState.currentUser = User(id: userId, username: await appState.chatClient.username, email: email)
|
|
}
|
|
|
|
// Save credentials for biometric login next time
|
|
if KeychainService.isBiometricAvailable() {
|
|
let port = UInt16(serverPort) ?? Constants.defaultPort
|
|
try? KeychainService.saveCredentials(
|
|
email: email, password: password,
|
|
host: serverHost, port: port
|
|
)
|
|
}
|
|
|
|
// Clear password from memory after successful login
|
|
password = ""
|
|
confirmPassword = ""
|
|
} else {
|
|
errorMessage = message
|
|
}
|
|
}
|
|
|
|
func biometricLogin(appState: AppState) async {
|
|
isBiometricLoading = true
|
|
errorMessage = nil
|
|
|
|
do {
|
|
let creds = try KeychainService.loadCredentials()
|
|
email = creds.email
|
|
password = creds.password
|
|
serverHost = creds.host
|
|
serverPort = String(creds.port)
|
|
isBiometricLoading = false
|
|
|
|
await login(appState: appState)
|
|
|
|
// If login failed, reset to defaults so the form isn't stuck on stale values
|
|
if !appState.isLoggedIn {
|
|
serverHost = Constants.defaultHost
|
|
serverPort = String(Constants.defaultPort)
|
|
password = ""
|
|
KeychainService.deleteCredentials()
|
|
hasSavedCredentials = false
|
|
}
|
|
} catch KeychainService.KeychainError.biometricFailed {
|
|
isBiometricLoading = false
|
|
// User cancelled — just let them type manually
|
|
} catch {
|
|
isBiometricLoading = false
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func register(appState: AppState) async {
|
|
guard !email.isEmpty, !password.isEmpty, !username.isEmpty else {
|
|
errorMessage = "All fields are required"
|
|
return
|
|
}
|
|
guard password == confirmPassword else {
|
|
errorMessage = "Passwords don't match"
|
|
return
|
|
}
|
|
|
|
isLoading = true
|
|
errorMessage = nil
|
|
|
|
#if DEBUG
|
|
print("DEBUG register: connecting to \(serverHost):\(serverPort)")
|
|
#endif
|
|
if await !appState.chatClient.isConnected {
|
|
do {
|
|
let port = UInt16(serverPort) ?? Constants.defaultPort
|
|
try await appState.chatClient.connect(host: serverHost, port: port)
|
|
#if DEBUG
|
|
print("DEBUG register: connected successfully")
|
|
#endif
|
|
} catch {
|
|
isLoading = false
|
|
#if DEBUG
|
|
print("DEBUG register: connection failed - \(error)")
|
|
#endif
|
|
errorMessage = "Connection failed: \(error.localizedDescription)"
|
|
return
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
print("DEBUG register: calling chatClient.register")
|
|
#endif
|
|
let (success, message) = await appState.chatClient.register(username: username, password: password, email: email)
|
|
isLoading = false
|
|
|
|
#if DEBUG
|
|
print("DEBUG AuthViewModel: register returned success=\(success), message=\(message)")
|
|
#endif
|
|
|
|
if success {
|
|
registrationMessage = message
|
|
showConfirmation = true
|
|
#if DEBUG
|
|
print("DEBUG AuthViewModel: showConfirmation set to true")
|
|
#endif
|
|
} else {
|
|
errorMessage = message
|
|
#if DEBUG
|
|
print("DEBUG AuthViewModel: errorMessage set to \(message)")
|
|
#endif
|
|
}
|
|
}
|
|
|
|
func confirmRegistration(appState: AppState) async {
|
|
guard !confirmationCode.isEmpty else {
|
|
errorMessage = "Enter the confirmation code"
|
|
return
|
|
}
|
|
|
|
isLoading = true
|
|
errorMessage = nil
|
|
|
|
let (success, message) = await appState.chatClient.confirmRegistration(
|
|
email: email, username: username, code: confirmationCode
|
|
)
|
|
isLoading = false
|
|
|
|
if success {
|
|
registrationMessage = message
|
|
// Auto-login after registration
|
|
await login(appState: appState)
|
|
} else {
|
|
errorMessage = message
|
|
}
|
|
}
|
|
}
|