295 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						||
//  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 accessToken = KeychainService.shared.get(forKey: "access_token", service: currentUser),
 | 
						||
           let refreshToken = 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)/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("AuthService_error_serialization", 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("AuthService_error_network", comment: ""), error.localizedDescription)
 | 
						||
                    completion(false, errorMessage)
 | 
						||
                    return
 | 
						||
                }
 | 
						||
                
 | 
						||
                guard let httpResponse = response as? HTTPURLResponse else {
 | 
						||
                    completion(false, NSLocalizedString("AuthService_error_invalid_response", comment: ""))
 | 
						||
                    return
 | 
						||
                }
 | 
						||
                
 | 
						||
                guard (200...299).contains(httpResponse.statusCode) else {
 | 
						||
                    if httpResponse.statusCode == 401{
 | 
						||
                        completion(false, NSLocalizedString("AuthService_error_invalid_credentials", comment: ""))
 | 
						||
                    } else if httpResponse.statusCode == 502{
 | 
						||
                        completion(false, NSLocalizedString("AuthService_error_server_unavailable", comment: ""))
 | 
						||
                    } else if httpResponse.statusCode == 429 {
 | 
						||
                        completion(false, NSLocalizedString("AuthService_error_too_many_requests", comment: ""))
 | 
						||
                    } else {
 | 
						||
                        let errorMessage = String(format: NSLocalizedString("AuthService_error_server_error", comment: ""), "\(httpResponse.statusCode)")
 | 
						||
                        completion(false, errorMessage)
 | 
						||
                    }
 | 
						||
                    return
 | 
						||
                }
 | 
						||
 | 
						||
                guard let data = data else {
 | 
						||
                    completion(false, NSLocalizedString("AuthService_error_empty_response", comment: ""))
 | 
						||
                    return
 | 
						||
                }
 | 
						||
 | 
						||
                do {
 | 
						||
                    let decoder = JSONDecoder()
 | 
						||
                    let loginResponse = try decoder.decode(LoginResponse.self, from: data)
 | 
						||
                    
 | 
						||
                    // Сохраняем токены в Keychain
 | 
						||
                    KeychainService.shared.save(loginResponse.access_token, forKey: "access_token", service: username)
 | 
						||
                    KeychainService.shared.save(loginResponse.refresh_token, forKey: "refresh_token", service: username)
 | 
						||
                    UserDefaults.standard.set(username, forKey: "currentUser")
 | 
						||
                    
 | 
						||
                    completion(true, nil)
 | 
						||
                } catch {
 | 
						||
                    completion(false, NSLocalizedString("AuthService_error_parsing_response", comment: ""))
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
        task.resume()
 | 
						||
    }
 | 
						||
 | 
						||
    func register(username: String, password: String, invite: String?, completion: @escaping (Bool, String?) -> Void) {
 | 
						||
        let url = URL(string: "\(AppConfig.API_SERVER)/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("AuthService_error_serialization", 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("AuthService_error_network", comment: ""), error.localizedDescription)
 | 
						||
                    completion(false, errorMessage)
 | 
						||
                    return
 | 
						||
                }
 | 
						||
                
 | 
						||
                guard let httpResponse = response as? HTTPURLResponse else {
 | 
						||
                    completion(false, NSLocalizedString("AuthService_error_invalid_response", comment: ""))
 | 
						||
                    return
 | 
						||
                }
 | 
						||
                
 | 
						||
                guard let data = data else {
 | 
						||
                    completion(false, NSLocalizedString("AuthService_error_empty_response", comment: ""))
 | 
						||
                    return
 | 
						||
                }
 | 
						||
                
 | 
						||
                let decoder = JSONDecoder()
 | 
						||
                
 | 
						||
                if (200...299).contains(httpResponse.statusCode) {
 | 
						||
                    do {
 | 
						||
                        let _ = try decoder.decode(RegisterResponse.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("AuthService_login_success_but_failed", comment: ""))
 | 
						||
                            }
 | 
						||
                        }
 | 
						||
                    } catch {
 | 
						||
                        completion(false, NSLocalizedString("AuthService_error_parsing_response", comment: ""))
 | 
						||
                    }
 | 
						||
                } else {
 | 
						||
                    // Ошибка сервера — пробуем распарсить message
 | 
						||
                    if let errorResponseMessage = try? decoder.decode(ErrorResponseMessage.self, from: data),
 | 
						||
                       let message = errorResponseMessage.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("AuthService_error_invalid_invitation_code", comment: ""))
 | 
						||
                            } else if message.contains("This invitation is not active") {
 | 
						||
                                completion(false, NSLocalizedString("AuthService_error_invitation_not_active", comment: ""))
 | 
						||
                            } else if message.contains("This invitation has reached its usage limit") {
 | 
						||
                                completion(false, NSLocalizedString("AuthService_error_invitation_usage_limit", comment: ""))
 | 
						||
                            } else if message.contains("This invitation has expired") {
 | 
						||
                                completion(false, NSLocalizedString("AuthService_error_invitation_expired", comment: ""))
 | 
						||
                            } else if message.contains("Login already registered") {
 | 
						||
                                completion(false, NSLocalizedString("AuthService_error_login_already_registered", comment: ""))
 | 
						||
                            } else {
 | 
						||
                                completion(false, message)
 | 
						||
                            }
 | 
						||
                        } else if httpResponse.statusCode == 403 {
 | 
						||
                            if message.contains("Registration is currently disabled") {
 | 
						||
                                completion(false, NSLocalizedString("AuthService_error_registration_disabled", comment: ""))
 | 
						||
                            } else {
 | 
						||
                                completion(false, message)
 | 
						||
                            }
 | 
						||
                        } else if httpResponse.statusCode == 429 {
 | 
						||
                            completion(false, NSLocalizedString("AuthService_error_too_many_requests", comment: ""))
 | 
						||
                        } else if httpResponse.statusCode == 502{
 | 
						||
                            completion(false, NSLocalizedString("AuthService_error_server_unavailable", comment: ""))
 | 
						||
                        } else {
 | 
						||
                            let errorMessage = String(format: NSLocalizedString("AuthService_error_server_error", comment: ""), "\(httpResponse.statusCode)")
 | 
						||
                            completion(false, errorMessage)
 | 
						||
                        }
 | 
						||
                    } else {
 | 
						||
                        // Не удалось распарсить JSON — fallback
 | 
						||
                        if httpResponse.statusCode == 400 {
 | 
						||
                            completion(false, NSLocalizedString("AuthService_error_invalid_request", comment: ""))
 | 
						||
                        } else if httpResponse.statusCode == 403 {
 | 
						||
                            completion(false, NSLocalizedString("AuthService_error_registration_forbidden", comment: ""))
 | 
						||
                        } else if httpResponse.statusCode == 429 {
 | 
						||
                            completion(false, NSLocalizedString("AuthService_error_too_many_requests", comment: ""))
 | 
						||
                        } else if httpResponse.statusCode == 502{
 | 
						||
                            completion(false, NSLocalizedString("AuthService_error_server_unavailable", comment: ""))
 | 
						||
                        } else {
 | 
						||
                            let errorMessage = String(format: NSLocalizedString("AuthService_error_server_error", 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)
 | 
						||
        
 | 
						||
        // Сбрасываем текущего пользователя
 | 
						||
        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 LoginResponse: Decodable {
 | 
						||
    let status: String
 | 
						||
    let access_token: String
 | 
						||
    let refresh_token: String
 | 
						||
    let token_type: String
 | 
						||
}
 | 
						||
 | 
						||
struct TokenRefreshResponse: Decodable {
 | 
						||
    let status: String
 | 
						||
    let access_token: String
 | 
						||
    let token_type: String
 | 
						||
}
 | 
						||
 | 
						||
struct RegisterResponse: Decodable {
 | 
						||
    let status: String
 | 
						||
    let message: String
 | 
						||
}
 | 
						||
 | 
						||
struct ErrorResponseMessage: Decodable {
 | 
						||
    let status: String?
 | 
						||
    let message: String?
 | 
						||
}
 |