| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- import Foundation
- /// Internal struct to persist user credentials locally.
- private struct LocalUserRecord: Codable {
- var user: UserAccount
- var passwordHash: String
- }
- /// Concrete local implementation of `AuthServiceProtocol`.
- /// Handles registration, authentication, profile updates, and password changes purely in local storage (UserDefaults / sandbox).
- final class LocalAuthRepository: AuthServiceProtocol {
-
- private let usersKey = "com.celestia.trace.registered_users_db"
- private let currentUserKey = "com.celestia.trace.current_user_session"
-
- init() {}
-
- // MARK: - AuthServiceProtocol
-
- func login(request: LoginRequest, completion: @escaping (AuthResult<UserAccount>) -> Void) {
- let identifier = request.identifier.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
- let password = request.password
-
- guard !identifier.isEmpty, password.count >= 6 else {
- completion(.failure(.invalidInput("请输入有效账号与不少于 6 位的密码")))
- return
- }
-
- var records = loadAllRecords()
-
- // Match identifier by email, phone, or matching username/id
- guard let matchingIndex = records.firstIndex(where: { rec in
- rec.user.email?.lowercased() == identifier ||
- rec.user.phoneNumber == identifier ||
- rec.user.username.lowercased() == identifier ||
- rec.user.id == identifier
- }) else {
- // Auto-fallback for demo convenience if no users registered yet, or error out:
- completion(.failure(.invalidCredentials))
- return
- }
-
- let record = records[matchingIndex]
- if record.passwordHash == hashPassword(password) {
- var updatedUser = record.user
- updatedUser.token = "jwt_token_" + UUID().uuidString.prefix(12)
- records[matchingIndex].user = updatedUser
- saveAllRecords(records)
- saveCurrentSession(updatedUser)
- completion(.success(updatedUser))
- } else {
- completion(.failure(.invalidCredentials))
- }
- }
-
- func register(request: RegisterRequest, completion: @escaping (AuthResult<UserAccount>) -> Void) {
- let username = request.username.trimmingCharacters(in: .whitespacesAndNewlines)
- let identifier = request.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
- let password = request.password
-
- guard !username.isEmpty else {
- completion(.failure(.invalidInput("请输入用户名/昵称")))
- return
- }
-
- guard !identifier.isEmpty else {
- completion(.failure(.invalidInput("请输入手机号或邮箱")))
- return
- }
-
- guard password.count >= 6 else {
- completion(.failure(.invalidInput("密码长度不能小于 6 位")))
- return
- }
-
- var records = loadAllRecords()
- let identifierLower = identifier.lowercased()
-
- let alreadyExists = records.contains { rec in
- rec.user.email?.lowercased() == identifierLower ||
- rec.user.phoneNumber == identifierLower ||
- rec.user.username.lowercased() == username.lowercased()
- }
-
- if alreadyExists {
- completion(.failure(.userAlreadyExists))
- return
- }
-
- let isEmail = identifier.contains("@")
- let newUser = UserAccount(
- id: "usr_" + UUID().uuidString.prefix(8),
- username: username,
- email: isEmail ? identifier : nil,
- phoneNumber: isEmail ? nil : identifier,
- avatarURL: "preset_avatar_\((records.count % 5) + 1)",
- token: "jwt_token_" + UUID().uuidString.prefix(12),
- registeredAt: Date()
- )
-
- let record = LocalUserRecord(user: newUser, passwordHash: hashPassword(password))
- records.append(record)
-
- saveAllRecords(records)
- saveCurrentSession(newUser)
- completion(.success(newUser))
- }
-
- func updateProfile(request: UpdateProfileRequest, completion: @escaping (AuthResult<UserAccount>) -> Void) {
- guard var currentUser = getCurrentUser() else {
- completion(.failure(.userNotFound))
- return
- }
-
- if let username = request.username, !username.trimmingCharacters(in: .whitespaces).isEmpty {
- currentUser.username = username
- }
- if let email = request.email {
- currentUser.email = email.isEmpty ? nil : email
- }
- if let phone = request.phoneNumber {
- currentUser.phoneNumber = phone.isEmpty ? nil : phone
- }
- if let avatar = request.avatarURL {
- currentUser.avatarURL = avatar
- }
-
- var records = loadAllRecords()
- if let idx = records.firstIndex(where: { $0.user.id == currentUser.id }) {
- records[idx].user = currentUser
- saveAllRecords(records)
- }
-
- saveCurrentSession(currentUser)
- completion(.success(currentUser))
- }
-
- func changePassword(request: ChangePasswordRequest, completion: @escaping (AuthResult<Bool>) -> Void) {
- guard let currentUser = getCurrentUser() else {
- completion(.failure(.userNotFound))
- return
- }
-
- guard request.newPassword.count >= 6 else {
- completion(.failure(.invalidInput("新密码长度不能少于 6 位")))
- return
- }
-
- var records = loadAllRecords()
- guard let idx = records.firstIndex(where: { $0.user.id == currentUser.id }) else {
- completion(.failure(.userNotFound))
- return
- }
-
- if records[idx].passwordHash != hashPassword(request.oldPassword) {
- completion(.failure(.incorrectPassword))
- return
- }
-
- records[idx].passwordHash = hashPassword(request.newPassword)
- saveAllRecords(records)
- completion(.success(true))
- }
-
- func logout(completion: @escaping () -> Void) {
- UserDefaults.standard.removeObject(forKey: currentUserKey)
- completion()
- }
-
- func getCurrentUser() -> UserAccount? {
- guard let data = UserDefaults.standard.data(forKey: currentUserKey),
- let user = try? JSONDecoder().decode(UserAccount.self, from: data) else {
- return nil
- }
- return user
- }
-
- // MARK: - Private Helpers
-
- private func loadAllRecords() -> [LocalUserRecord] {
- guard let data = UserDefaults.standard.data(forKey: usersKey),
- let records = try? JSONDecoder().decode([LocalUserRecord].self, from: data) else {
- return []
- }
- return records
- }
-
- private func saveAllRecords(_ records: [LocalUserRecord]) {
- if let data = try? JSONEncoder().encode(records) {
- UserDefaults.standard.set(data, forKey: usersKey)
- }
- }
-
- private func saveCurrentSession(_ user: UserAccount) {
- if let data = try? JSONEncoder().encode(user) {
- UserDefaults.standard.set(data, forKey: currentUserKey)
- }
- }
-
- private func hashPassword(_ raw: String) -> String {
- return "hashed_" + String(raw.reversed()) + "_salt"
- }
- }
|