Add password repository to interact with keychain
This commit is contained in:
parent
5e05d2f0ed
commit
3e2ad444bd
1 changed files with 121 additions and 0 deletions
121
password/data/repository/PasswordRepository.swift
Normal file
121
password/data/repository/PasswordRepository.swift
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// PasswordRepository.swift
|
||||
// password
|
||||
//
|
||||
// Created by Markus Thielker on 16.01.25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
enum KeychainError: Error {
|
||||
case unknown
|
||||
case duplicateItem
|
||||
case itemNotFound
|
||||
case unexpectedPasswordData
|
||||
}
|
||||
|
||||
protocol PasswordRepository {
|
||||
func getAllPasswords() -> [Password]
|
||||
func getPassword(withID id: UUID) -> Password?
|
||||
func savePassword(_ password: Password) throws
|
||||
func deletePassword(withID id: UUID) throws
|
||||
}
|
||||
|
||||
class KeychainPasswordRepository: PasswordRepository {
|
||||
|
||||
private let serviceName = "dev.thielker.password"
|
||||
|
||||
func getAllPasswords() -> [Password] {
|
||||
|
||||
// TODO: fix query to work with 'kSecMatchLimit as String: kSecMatchLimitAll'
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: serviceName,
|
||||
kSecMatchLimit as String: 100,
|
||||
kSecReturnAttributes as String: true,
|
||||
kSecReturnData as String: true,
|
||||
]
|
||||
|
||||
var result: AnyObject?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
||||
|
||||
if status != errSecSuccess {
|
||||
print("Error retrieving passwords: \(SecCopyErrorMessageString(status, nil) ?? "Unknown error" as CFString)")
|
||||
return []
|
||||
}
|
||||
|
||||
guard let items = result as? [[String: Any]] else {
|
||||
print("No passwords found.")
|
||||
return []
|
||||
}
|
||||
|
||||
var passwords: [Password] = []
|
||||
for item in items {
|
||||
if let data = item[kSecValueData as String] as? Data,
|
||||
let password = try? JSONDecoder().decode(Password.self, from: data) {
|
||||
passwords.append(password)
|
||||
}
|
||||
}
|
||||
return passwords
|
||||
}
|
||||
|
||||
func getPassword(withID id: UUID) -> Password? {
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: serviceName,
|
||||
kSecAttrAccount as String: id.uuidString,
|
||||
kSecReturnData as String: true
|
||||
]
|
||||
|
||||
var result: AnyObject?
|
||||
SecItemCopyMatching(query as CFDictionary, &result)
|
||||
|
||||
guard let data = result as? Data,
|
||||
let password = try? JSONDecoder().decode(Password.self, from: data) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return password
|
||||
}
|
||||
|
||||
func savePassword(_ password: Password) throws {
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword, // Generic password item
|
||||
kSecAttrService as String: serviceName, // Service name for your app
|
||||
kSecAttrAccount as String: password.id.uuidString, // Unique identifier
|
||||
kSecValueData as String: try JSONEncoder().encode(password) // Encode password data
|
||||
]
|
||||
|
||||
let status = SecItemAdd(query as CFDictionary, nil)
|
||||
|
||||
guard status == errSecSuccess else {
|
||||
if status == errSecDuplicateItem {
|
||||
throw KeychainError.duplicateItem
|
||||
} else {
|
||||
throw KeychainError.unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deletePassword(withID id: UUID) throws {
|
||||
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: serviceName,
|
||||
kSecAttrAccount as String: id.uuidString
|
||||
]
|
||||
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
|
||||
guard status == errSecSuccess else {
|
||||
if status == errSecItemNotFound {
|
||||
throw KeychainError.itemNotFound
|
||||
} else {
|
||||
throw KeychainError.unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue