ios_app_v2/yobble/Network/AuthService.swift
2025-10-05 06:00:54 +03:00

303 lines
15 KiB
Swift
Raw 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("Не удалось сериализовать данные запроса.", 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<LoginPayload>.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)
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<RegisterPayload>.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<T: Decodable>: 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?
}