Hello I am encountering a very odd issue with iOS keychain in terms of updating log in information stored in keychain. So if there are no saved credentials then running the save function correctly saves the log in info. If log in info already exists and the user updates their password then the update function correctly updates just the password. But if log in info exists and I try to change the email (while keeping or changing password) then the first update is unsuccessful. I have to manually click the update log in twice for the log in info to update. I tried the below code by just forcing the delete and save funcs to run twice while adding a delay between but this didn't work. The only thing that works is pressing "update" twice. Id appreciate any help. Thanks.
delete(email: result.0)
save(email: email, password: password)
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in
delete(email: result.0)
save(email: email, password: password)
}
func save(email: String, password: String) {
let passwordData = password.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "https://hustle.page",
kSecAttrAccount as String: email,
kSecValueData as String: passwordData
]
let saveStatus = SecItemAdd(query as CFDictionary, nil)
if saveStatus == errSecDuplicateItem {
update(email: email, password: password)
}
}
func update(email: String, password: String) {
if let result = read(service: "https://hustle.page"){
if result.0 == email {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "https://hustle.page",
kSecAttrAccount as String: email
]
let passwordData = password.data(using: .utf8)!
let updatedData: [String: Any] = [
kSecValueData as String: passwordData
]
SecItemUpdate(query as CFDictionary, updatedData as CFDictionary)
} else {
delete(email: result.0)
save(email: email, password: password)
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in
delete(email: result.0)
save(email: email, password: password)
}
}
}
}
func delete(email: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "https://hustle.page",
kSecAttrAccount as String: email
]
SecItemDelete(query as CFDictionary)
}
func read(service: String) -> (String, String)? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecReturnAttributes as String: true,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecSuccess, let item = result as? [String: Any] {
if let account = item[kSecAttrAccount as String] as? String,
let passwordData = item[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: .utf8) {
return (account, password)
}
}
return nil
}
in the view:
Button {
save(email: email, password: password)
} label: {
Text("Update")
}