// // AuthService.swift // VolnahubApp // // Created by cheykrym on 09/06/2025. // import Foundation class AuthService { func autoLogin(completion: @escaping (Bool, String?) -> Void) { // 1️⃣ Проверяем наличие текущего пользователя if let currentUser = UserDefaults.standard.string(forKey: "currentUser"), let _ = KeychainService.shared.get(forKey: "access_token", service: currentUser), let _ = KeychainService.shared.get(forKey: "refresh_token", service: currentUser) { if AppConfig.DEBUG{ print("AutoLogin: найден текущий пользователь — \(currentUser)")} completion(true, nil) return } // 2️⃣ Текущий пользователь не найден или токены отсутствуют if AppConfig.DEBUG{ print("AutoLogin: текущий пользователь не найден или токены отсутствуют. Пробуем найти другого пользователя...")} let allUsers = KeychainService.shared.getAllServices() for user in allUsers { let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil let hasRefreshToken = KeychainService.shared.get(forKey: "refresh_token", service: user) != nil if hasAccessToken && hasRefreshToken { // Нашли пользователя с токенами — назначаем как currentUser UserDefaults.standard.set(user, forKey: "currentUser") if AppConfig.DEBUG{ print("AutoLogin: переключились на пользователя \(user)")} completion(true, nil) return } } // 3️⃣ Если никто не найден // completion(false, "Не найден авторизованный пользователь. Пожалуйста, войдите снова.") completion(false, nil) } func login(username: String, password: String, completion: @escaping (Bool, String?) -> Void) { let url = URL(string: "\(AppConfig.API_SERVER)/v1/auth/login")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("\(AppConfig.USER_AGENT)", forHTTPHeaderField: "User-Agent") let payload: [String: String] = [ "login": username, "password": password ] do { let jsonData = try JSONEncoder().encode(payload) request.httpBody = jsonData } catch { DispatchQueue.main.async { completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: "")) } return } let task = URLSession.shared.dataTask(with: request) { data, response, error in DispatchQueue.main.async { if let error = error { let errorMessage = String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), error.localizedDescription) completion(false, errorMessage) return } guard let httpResponse = response as? HTTPURLResponse else { completion(false, NSLocalizedString("Некорректный ответ от сервера.", comment: "")) return } guard (200...299).contains(httpResponse.statusCode) else { if httpResponse.statusCode == 401{ completion(false, NSLocalizedString("Неверный логин или пароль.", comment: "")) } else if httpResponse.statusCode == 502{ completion(false, NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")) } else if httpResponse.statusCode == 429 { completion(false, NSLocalizedString("Слишком много запросов.", comment: "")) } else { let errorMessage = String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(httpResponse.statusCode)") completion(false, errorMessage) } return } guard let data = data else { completion(false, NSLocalizedString("Пустой ответ от сервера.", comment: "")) return } do { let decoder = JSONDecoder() let loginResponse = try decoder.decode(APIResponse.self, from: data).data // Сохраняем токены в Keychain KeychainService.shared.save(loginResponse.access_token, forKey: "access_token", service: username) KeychainService.shared.save(loginResponse.refresh_token, forKey: "refresh_token", service: username) print("loginResponse.user_id \(loginResponse.user_id)") KeychainService.shared.save(loginResponse.user_id, forKey: "userId", service: username) UserDefaults.standard.set(username, forKey: "currentUser") completion(true, nil) } catch { completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: "")) } } } task.resume() } func register(username: String, password: String, invite: String?, completion: @escaping (Bool, String?) -> Void) { let url = URL(string: "\(AppConfig.API_SERVER)/v1/auth/register")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue(AppConfig.USER_AGENT, forHTTPHeaderField: "User-Agent") let payload: [String: Any] = [ "login": username, "password": password, "invite": invite ?? NSNull() ] do { let jsonData = try JSONSerialization.data(withJSONObject: payload) request.httpBody = jsonData } catch { DispatchQueue.main.async { completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: "")) } return } let task = URLSession.shared.dataTask(with: request) { data, response, error in DispatchQueue.main.async { if let error = error { let errorMessage = String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), error.localizedDescription) completion(false, errorMessage) return } guard let httpResponse = response as? HTTPURLResponse else { completion(false, NSLocalizedString("Некорректный ответ от сервера.", comment: "")) return } guard let data = data else { completion(false, NSLocalizedString("Пустой ответ от сервера.", comment: "")) return } let decoder = JSONDecoder() if (200...299).contains(httpResponse.statusCode) { do { let _ = try decoder.decode(APIResponse.self, from: data) if AppConfig.DEBUG{ print("Регистрация успешна. Пытаемся сразу войти...")} // Сразу логинимся self.login(username: username, password: password) { loginSuccess, loginMessage in if loginSuccess { completion(true, "Регистрация и вход выполнены успешно.") } else { // Регистрация успешна, но логин не удался — покажем сообщение completion(false, loginMessage ?? NSLocalizedString("Регистрация выполнена, но вход не удался.", comment: "")) } } } catch { completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: "")) } } else { // Ошибка сервера — пробуем распарсить message if let errorResponseMessage = try? decoder.decode(ErrorResponse.self, from: data), let message = errorResponseMessage.data?.message { if let jsonString = String(data: data, encoding: .utf8) { if AppConfig.DEBUG{ print("Raw JSON:", jsonString)} } if AppConfig.DEBUG{ print("message:", message)} if httpResponse.statusCode == 400 { if message.contains("Invalid invitation code") { completion(false, NSLocalizedString("Неверный код приглашения.", comment: "")) } else if message.contains("This invitation is not active") { completion(false, NSLocalizedString("Приглашение не активно.", comment: "")) } else if message.contains("This invitation has reached its usage limit") { completion(false, NSLocalizedString("Приглашение достигло лимита использования.", comment: "")) } else if message.contains("This invitation has expired") { completion(false, NSLocalizedString("Приглашение истекло.", comment: "")) } else if message.contains("Login already registered") { completion(false, NSLocalizedString("Логин уже занят.", comment: "")) } else { completion(false, message) } } else if httpResponse.statusCode == 403 { if message.contains("Registration is currently disabled") { completion(false, NSLocalizedString("Регистрация временно недоступна.", comment: "")) } else { completion(false, message) } } else if httpResponse.statusCode == 429 { completion(false, NSLocalizedString("Слишком много запросов.", comment: "")) } else if httpResponse.statusCode == 502{ completion(false, NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")) } else { let errorMessage = String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(httpResponse.statusCode)") completion(false, errorMessage) } } else { // Не удалось распарсить JSON — fallback if httpResponse.statusCode == 400 { completion(false, NSLocalizedString("Неверный запрос (400).", comment: "")) } else if httpResponse.statusCode == 403 { completion(false, NSLocalizedString("Регистрация запрещена.", comment: "")) } else if httpResponse.statusCode == 429 { completion(false, NSLocalizedString("Слишком много запросов.", comment: "")) } else if httpResponse.statusCode == 502{ completion(false, NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")) } else { let errorMessage = String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(httpResponse.statusCode)") completion(false, errorMessage) } } } } } task.resume() } func logoutCurrentUser(completion: @escaping (Bool, String?) -> Void) { guard let currentUser = UserDefaults.standard.string(forKey: "currentUser") else { completion(false, "Не найден текущий пользователь.") return } // Удаляем токены текущего пользователя KeychainService.shared.delete(forKey: "access_token", service: currentUser) KeychainService.shared.delete(forKey: "refresh_token", service: currentUser) KeychainService.shared.delete(forKey: "userId", service: currentUser) // Сбрасываем текущего пользователя UserDefaults.standard.removeObject(forKey: "currentUser") // Пробуем переключиться на другого пользователя let allUsers = KeychainService.shared.getAllServices() for user in allUsers { let hasAccessToken = KeychainService.shared.get(forKey: "access_token", service: user) != nil let hasRefreshToken = KeychainService.shared.get(forKey: "refresh_token", service: user) != nil if hasAccessToken && hasRefreshToken { UserDefaults.standard.set(user, forKey: "currentUser") if AppConfig.DEBUG{ print("Logout: переключились на пользователя \(user)")} completion(true, nil) return } } // Если пользователей больше нет // completion(false, "Нет доступных пользователей. Пожалуйста, войдите снова.") completion(false, nil) } } struct APIResponse: Decodable { let status: String let data: T } struct LoginPayload: Decodable { let access_token: String let refresh_token: String let user_id: String } struct TokenRefreshPayload: Decodable { let access_token: String let token_type: String } struct RegisterPayload: Decodable { let message: String } struct ErrorPayload: Decodable { let message: String? } struct ErrorResponse: Decodable { let status: String? let data: ErrorPayload? }