- Fixed App Store icon display with ios-marketing idiom - Added iPad orientation support for multitasking - Added UILaunchScreen for iPad requirements - Removed unused BLE permissions and files from build Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
95 lines
3.4 KiB
Swift
95 lines
3.4 KiB
Swift
import Foundation
|
|
import Security
|
|
|
|
struct AuthCredentials {
|
|
let userId: Int
|
|
let token: String
|
|
let userName: String?
|
|
let photoUrl: String?
|
|
}
|
|
|
|
actor AuthStorage {
|
|
static let shared = AuthStorage()
|
|
|
|
private let userIdKey = "payfrit_beacon_user_id"
|
|
private let userNameKey = "payfrit_beacon_user_name"
|
|
private let userPhotoKey = "payfrit_beacon_user_photo"
|
|
private let serviceName = "com.payfrit.beacon"
|
|
private let tokenAccount = "auth_token"
|
|
|
|
// MARK: - Save
|
|
|
|
func saveAuth(userId: Int, token: String, userName: String? = nil, photoUrl: String? = nil) {
|
|
UserDefaults.standard.set(userId, forKey: userIdKey)
|
|
// Always overwrite name/photo to prevent stale data from previous user
|
|
if let name = userName, !name.isEmpty {
|
|
UserDefaults.standard.set(name, forKey: userNameKey)
|
|
} else {
|
|
UserDefaults.standard.removeObject(forKey: userNameKey)
|
|
}
|
|
if let photo = photoUrl, !photo.isEmpty {
|
|
UserDefaults.standard.set(photo, forKey: userPhotoKey)
|
|
} else {
|
|
UserDefaults.standard.removeObject(forKey: userPhotoKey)
|
|
}
|
|
saveToKeychain(token)
|
|
}
|
|
|
|
// MARK: - Load
|
|
|
|
func loadAuth() -> AuthCredentials? {
|
|
let userId = UserDefaults.standard.integer(forKey: userIdKey)
|
|
guard userId > 0 else { return nil }
|
|
guard let token = loadFromKeychain(), !token.isEmpty else { return nil }
|
|
let userName = UserDefaults.standard.string(forKey: userNameKey)
|
|
let photoUrl = UserDefaults.standard.string(forKey: userPhotoKey)
|
|
return AuthCredentials(userId: userId, token: token, userName: userName, photoUrl: photoUrl)
|
|
}
|
|
|
|
// MARK: - Clear
|
|
|
|
func clearAuth() {
|
|
UserDefaults.standard.removeObject(forKey: userIdKey)
|
|
UserDefaults.standard.removeObject(forKey: userNameKey)
|
|
UserDefaults.standard.removeObject(forKey: userPhotoKey)
|
|
deleteFromKeychain()
|
|
}
|
|
|
|
// MARK: - Keychain
|
|
|
|
private func saveToKeychain(_ token: String) {
|
|
deleteFromKeychain()
|
|
guard let data = token.data(using: .utf8) else { return }
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: serviceName,
|
|
kSecAttrAccount as String: tokenAccount,
|
|
kSecValueData as String: data,
|
|
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
]
|
|
SecItemAdd(query as CFDictionary, nil)
|
|
}
|
|
|
|
private func loadFromKeychain() -> String? {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: serviceName,
|
|
kSecAttrAccount as String: tokenAccount,
|
|
kSecReturnData as String: true,
|
|
kSecMatchLimit as String: kSecMatchLimitOne
|
|
]
|
|
var result: AnyObject?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
guard status == errSecSuccess, let data = result as? Data else { return nil }
|
|
return String(data: data, encoding: .utf8)
|
|
}
|
|
|
|
private func deleteFromKeychain() {
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: serviceName,
|
|
kSecAttrAccount as String: tokenAccount
|
|
]
|
|
SecItemDelete(query as CFDictionary)
|
|
}
|
|
}
|