ios_app/Shared/Network/AuthService.swift
2025-07-12 02:41:46 +03:00

295 lines
15 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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("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)/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("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?
}