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

252 lines
11 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

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
final class AuthService {
func autoLogin(completion: @escaping (Bool, String?) -> Void) {
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
}
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 {
UserDefaults.standard.set(user, forKey: "currentUser")
if AppConfig.DEBUG { print("AutoLogin: переключились на пользователя \(user)") }
completion(true, nil)
return
}
}
completion(false, nil)
}
func login(username: String, password: String, completion: @escaping (Bool, String?) -> Void) {
let payload = LoginRequest(login: username, password: password)
guard let body = try? JSONEncoder().encode(payload) else {
completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
return
}
NetworkClient.shared.request(
path: "/v1/auth/login",
method: .post,
body: body,
requiresAuth: false
) { result in
switch result {
case .success(let response):
do {
let decoder = JSONDecoder()
let apiResponse = try decoder.decode(APIResponse<TokenPairPayload>.self, from: response.data)
guard apiResponse.status == "fine" else {
let message = apiResponse.detail ?? NSLocalizedString("Неизвестная ошибка", comment: "")
completion(false, message)
return
}
let tokens = apiResponse.data
KeychainService.shared.save(tokens.access_token, forKey: "access_token", service: username)
KeychainService.shared.save(tokens.refresh_token, forKey: "refresh_token", service: username)
if let userId = tokens.user_id {
KeychainService.shared.save(userId, forKey: "userId", service: username)
}
UserDefaults.standard.set(username, forKey: "currentUser")
completion(true, nil)
} catch {
completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
}
case .failure(let error):
let message = self.loginErrorMessage(for: error)
completion(false, message)
}
}
}
func register(username: String, password: String, invite: String?, completion: @escaping (Bool, String?) -> Void) {
let payload = RegisterRequest(login: username, password: password, invite: invite)
guard let body = try? JSONEncoder().encode(payload) else {
completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
return
}
NetworkClient.shared.request(
path: "/v1/auth/register",
method: .post,
body: body,
requiresAuth: false
) { result in
switch result {
case .success(let response):
do {
let decoder = JSONDecoder()
let apiResponse = try decoder.decode(APIResponse<RegisterPayload>.self, from: response.data)
guard apiResponse.status == "fine" else {
let message = apiResponse.detail ?? NSLocalizedString("Неизвестная ошибка", comment: "")
completion(false, message)
return
}
if AppConfig.DEBUG { print("Регистрация успешна. Пытаемся сразу войти...") }
self.login(username: username, password: password) { loginSuccess, loginMessage in
if loginSuccess {
completion(true, NSLocalizedString("Регистрация и вход выполнены успешно.", comment: ""))
} else {
completion(false, loginMessage ?? NSLocalizedString("Регистрация выполнена, но вход не удался.", comment: ""))
}
}
} catch {
completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
}
case .failure(let error):
let message = self.registerErrorMessage(for: error)
completion(false, message)
}
}
}
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, nil)
}
private func loginErrorMessage(for error: NetworkError) -> String {
switch error {
case .network(let err):
return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
case .server(let statusCode, _):
switch statusCode {
case 401:
return NSLocalizedString("Неверный логин или пароль.", comment: "")
case 429:
return NSLocalizedString("Слишком много запросов.", comment: "")
case 502:
return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
default:
return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
}
case .unauthorized:
return NSLocalizedString("Неверный логин или пароль.", comment: "")
case .invalidURL, .noResponse:
return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
}
}
private func registerErrorMessage(for error: NetworkError) -> String {
switch error {
case .network(let err):
return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
case .server(let statusCode, let data):
if let data,
let response = try? JSONDecoder().decode(ErrorResponse.self, from: data),
let message = response.data?.message {
return mappedRegistrationMessage(for: message, statusCode: statusCode)
}
switch statusCode {
case 400:
return NSLocalizedString("Неверный запрос (400).", comment: "")
case 403:
return NSLocalizedString("Регистрация запрещена.", comment: "")
case 429:
return NSLocalizedString("Слишком много запросов.", comment: "")
case 502:
return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
default:
return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
}
case .unauthorized:
return NSLocalizedString("Регистрация запрещена.", comment: "")
case .invalidURL, .noResponse:
return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
}
}
private func mappedRegistrationMessage(for message: String, statusCode: Int) -> String {
if statusCode == 400 {
if message.contains("Invalid invitation code") {
return NSLocalizedString("Неверный код приглашения.", comment: "")
} else if message.contains("This invitation is not active") {
return NSLocalizedString("Приглашение не активно.", comment: "")
} else if message.contains("This invitation has reached its usage limit") {
return NSLocalizedString("Приглашение достигло лимита использования.", comment: "")
} else if message.contains("This invitation has expired") {
return NSLocalizedString("Приглашение истекло.", comment: "")
} else if message.contains("Login already registered") {
return NSLocalizedString("Логин уже занят.", comment: "")
}
}
if statusCode == 403 {
if message.contains("Registration is currently disabled") {
return NSLocalizedString("Регистрация временно недоступна.", comment: "")
}
}
if statusCode == 429 {
return NSLocalizedString("Слишком много запросов.", comment: "")
}
if statusCode == 502 {
return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
}
return message
}
}
private struct LoginRequest: Encodable {
let login: String
let password: String
}
private struct RegisterRequest: Encodable {
let login: String
let password: String
let invite: String?
}