add 2fa auth

This commit is contained in:
cheykrym 2025-12-03 21:49:17 +03:00
parent 372dc92c8d
commit 1449e003de
2 changed files with 171 additions and 11 deletions

View File

@ -46,6 +46,7 @@ final class AuthService {
NetworkClient.shared.request( NetworkClient.shared.request(
path: "/v1/auth/login/password", path: "/v1/auth/login/password",
method: .post, method: .post,
headers: ["X-Client-Type": "ios"],
body: body, body: body,
requiresAuth: false requiresAuth: false
) { result in ) { result in
@ -84,22 +85,87 @@ final class AuthService {
} }
func requestLoginCode(identifier: String, completion: @escaping (Bool, String?) -> Void) { func requestLoginCode(identifier: String, completion: @escaping (Bool, String?) -> Void) {
if AppConfig.DEBUG { let payload = LoginCodeRequestPayload(login: identifier)
print("[AuthService] requestLoginCode placeholder for \(identifier)")
guard let body = try? JSONEncoder().encode(payload) else {
completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
return
} }
DispatchQueue.global().asyncAfter(deadline: .now() + 0.8) { NetworkClient.shared.request(
completion(true, nil) path: "/v1/auth/login/code",
method: .post,
headers: ["X-Client-Type": "ios"],
body: body,
requiresAuth: false
) { [weak self] result in
guard let self else { return }
switch result {
case .success(let response):
do {
let decoder = JSONDecoder()
let apiResponse = try decoder.decode(APIResponse<MessagePayload>.self, from: response.data)
guard apiResponse.status == "fine" else {
let message = apiResponse.detail ?? apiResponse.data.message
completion(false, message)
return
}
completion(true, nil)
} catch {
completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
}
case .failure(let error):
completion(false, self.passwordlessRequestErrorMessage(for: error))
}
} }
} }
func loginWithCode(identifier: String, code: String, completion: @escaping (Bool, String?) -> Void) { func loginWithCode(identifier: String, code: String, completion: @escaping (Bool, String?) -> Void) {
if AppConfig.DEBUG { let payload = VerifyCodeRequestPayload(login: identifier, otp: code)
print("[AuthService] loginWithCode placeholder for \(identifier) using code \(code)")
guard let body = try? JSONEncoder().encode(payload) else {
completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
return
} }
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { NetworkClient.shared.request(
completion(false, NSLocalizedString("Вход по коду пока недоступен. Заглушка.", comment: "")) path: "/v1/auth/login/verify_code",
method: .post,
headers: ["X-Client-Type": "ios"],
body: body,
requiresAuth: false
) { [weak self] result in
guard let self else { return }
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: identifier)
KeychainService.shared.save(tokens.refresh_token, forKey: "refresh_token", service: identifier)
if let userId = tokens.user_id {
KeychainService.shared.save(userId, forKey: "userId", service: identifier)
}
UserDefaults.standard.set(identifier, forKey: "currentUser")
NotificationCenter.default.post(name: .accessTokenDidChange, object: nil)
completion(true, nil)
} catch {
completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
}
case .failure(let error):
completion(false, self.passwordlessVerifyErrorMessage(for: error))
}
} }
} }
@ -281,6 +347,70 @@ final class AuthService {
} }
} }
private func passwordlessRequestErrorMessage(for error: NetworkError) -> String {
switch error {
case .network(let err):
return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
case .server(let statusCode, let data):
let message = extractMessage(from: data)
switch statusCode {
case 401, 404:
return message ?? NSLocalizedString("Аккаунт не найден.", comment: "")
case 403:
return message ?? NSLocalizedString("Этому аккаунту недоступен вход по коду.", comment: "")
case 422:
return message ?? NSLocalizedString("Неверный логин. Проверьте и попробуйте снова.", comment: "")
case 429:
return NSLocalizedString("Слишком много попыток. Попробуйте позже.", comment: "")
case 502:
return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
default:
if let message {
return message
}
return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
}
case .unauthorized:
return NSLocalizedString("Необходимо авторизоваться заново.", comment: "")
case .invalidURL, .noResponse:
return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
}
}
private func passwordlessVerifyErrorMessage(for error: NetworkError) -> String {
switch error {
case .network(let err):
return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
case .server(let statusCode, let data):
let message = extractMessage(from: data)
switch statusCode {
case 401:
return message ?? NSLocalizedString("Неверный или просроченный код.", comment: "")
case 403:
return message ?? NSLocalizedString("Этот аккаунт недоступен.", comment: "")
case 404:
return message ?? NSLocalizedString("Аккаунт не найден.", comment: "")
case 422:
return message ?? NSLocalizedString("Некорректные данные. Проверьте код и логин.", comment: "")
case 429:
return NSLocalizedString("Слишком много попыток. Попробуйте позже.", comment: "")
case 502:
return NSLocalizedString("Сервер не отвечает. Попробуйте позже.", comment: "")
default:
if let message {
return message
}
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 { private func mappedRegistrationMessage(for message: String, statusCode: Int) -> String {
if statusCode == 400 { if statusCode == 400 {
if message.contains("Invalid invitation code") { if message.contains("Invalid invitation code") {
@ -420,6 +550,15 @@ private struct LoginRequest: Encodable {
let password: String let password: String
} }
private struct LoginCodeRequestPayload: Encodable {
let login: String
}
private struct VerifyCodeRequestPayload: Encodable {
let login: String
let otp: String
}
private struct RegisterRequest: Encodable { private struct RegisterRequest: Encodable {
let login: String let login: String
let password: String let password: String

View File

@ -208,6 +208,9 @@
} }
} }
} }
},
"Аккаунт не найден." : {
}, },
"Активные сессии" : { "Активные сессии" : {
"comment" : "Заголовок экрана активных сессий", "comment" : "Заголовок экрана активных сессий",
@ -394,9 +397,6 @@
}, },
"Вход и защита аккаунта (заглушка)" : { "Вход и защита аккаунта (заглушка)" : {
"comment" : "Раздел настроек безопасности для аутентификации" "comment" : "Раздел настроек безопасности для аутентификации"
},
"Вход по коду пока недоступен. Заглушка." : {
}, },
"Вход по паролю" : { "Вход по паролю" : {
@ -1272,6 +1272,9 @@
} }
} }
} }
},
"Неверный или просроченный код." : {
}, },
"Неверный код" : { "Неверный код" : {
"comment" : "Заголовок ошибки неправильного кода 2FA" "comment" : "Заголовок ошибки неправильного кода 2FA"
@ -1306,6 +1309,9 @@
} }
} }
} }
},
"Неверный логин. Проверьте и попробуйте снова." : {
}, },
"Неверный пароль" : { "Неверный пароль" : {
"comment" : "Неверный пароль", "comment" : "Неверный пароль",
@ -1378,6 +1384,9 @@
} }
} }
} }
},
"Некорректные данные. Проверьте код и логин." : {
}, },
"Некорректный ответ от сервера." : { "Некорректный ответ от сервера." : {
@ -2123,6 +2132,9 @@
} }
} }
} }
},
"Проверьте код и попробуйте снова." : {
}, },
"Проверьте цифры и попробуйте снова." : { "Проверьте цифры и попробуйте снова." : {
"comment" : "Описание ошибки неверного кода 2FA" "comment" : "Описание ошибки неверного кода 2FA"
@ -2376,6 +2388,9 @@
} }
} }
} }
},
"Сессия недействительна. Авторизуйтесь заново." : {
}, },
"Системная" : { "Системная" : {
"localizations" : { "localizations" : {
@ -2783,6 +2798,12 @@
}, },
"Это устройство" : { "Это устройство" : {
"comment" : "Заголовок секции текущего устройства" "comment" : "Заголовок секции текущего устройства"
},
"Этому аккаунту недоступен вход по коду." : {
},
"Этот аккаунт недоступен." : {
}, },
"Я ознакомился и принимаю правила сервиса" : { "Я ознакомился и принимаю правила сервиса" : {