mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-10 10:52:42 -04:00
Refactor (#520)
This commit is contained in:
@@ -19,77 +19,29 @@ public class CredentialIdentityStore {
|
||||
/// - credentials: The credentials to register
|
||||
/// - passkeyOnly: If true, only register passkey identities (skip passwords). Default is false.
|
||||
public func saveCredentialIdentities(_ credentials: [Credential], passkeyOnly: Bool = false) async throws {
|
||||
// TODO: improve implementation to better separate password and passkey identities.
|
||||
// As if a record has both a password and passkey, it will not only show up as a password identity.
|
||||
var allIdentities: [ASCredentialIdentity] = []
|
||||
|
||||
// Create password identities (skip if passkeyOnly mode)
|
||||
let passwordIdentities: [ASPasswordCredentialIdentity] = passkeyOnly ? [] : credentials.compactMap { credential in
|
||||
guard let urlString = credential.service.url,
|
||||
let url = URL(string: urlString),
|
||||
let host = url.host else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use the same logic as the UI for determining the identifier
|
||||
let identifier = usernameOrEmail(credential: credential)
|
||||
guard !identifier.isEmpty else {
|
||||
return nil // Skip credentials with no identifier
|
||||
}
|
||||
|
||||
guard let password = credential.password, !password.value.isEmpty else {
|
||||
return nil // Skip credentials with no password (e.g. applies when this record is a passkey)
|
||||
}
|
||||
|
||||
let effectiveDomain = Self.effectiveDomain(from: host)
|
||||
|
||||
return ASPasswordCredentialIdentity(
|
||||
serviceIdentifier: ASCredentialServiceIdentifier(identifier: effectiveDomain, type: .domain),
|
||||
user: identifier,
|
||||
recordIdentifier: credential.id.uuidString
|
||||
)
|
||||
}
|
||||
|
||||
allIdentities.append(contentsOf: passwordIdentities)
|
||||
|
||||
// Create passkey identities
|
||||
let passkeyIdentities: [ASPasskeyCredentialIdentity] = credentials.flatMap { credential -> [ASPasskeyCredentialIdentity] in
|
||||
guard let passkeys = credential.passkeys else {
|
||||
return []
|
||||
}
|
||||
|
||||
return passkeys.filter { !($0.isDeleted) }
|
||||
.compactMap { passkey in
|
||||
// Get the userName for display in iOS AutoFill UI
|
||||
// Passkeys don't store userName in the database, so we use the credential's username or email
|
||||
let userName = passkey.userName ?? usernameOrEmail(credential: credential)
|
||||
|
||||
// Convert passkey.Id to bytes for credentialID
|
||||
let credentialId = try? PasskeyHelper.guidToBytes(passkey.id.uuidString)
|
||||
|
||||
// For passkeys, we use the rpId from the passkey itself, not the service URL
|
||||
// This is because passkeys are tied to the RP ID, which may differ from the service URL
|
||||
return ASPasskeyCredentialIdentity(
|
||||
relyingPartyIdentifier: passkey.rpId,
|
||||
userName: userName,
|
||||
credentialID: credentialId ?? Data(), // WebAuthn credential ID (16-byte GUID)
|
||||
userHandle: passkey.userHandle ?? Data(),
|
||||
recordIdentifier: passkey.id.uuidString
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
allIdentities.append(contentsOf: passkeyIdentities)
|
||||
|
||||
guard !allIdentities.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
let state = await storeState()
|
||||
guard state.isEnabled else {
|
||||
return
|
||||
}
|
||||
|
||||
// Only save passwords into autofill store on iOS 26+, as it works buggy on older iOS versions.
|
||||
if #available(iOS 26.0, *) {
|
||||
if !passkeyOnly {
|
||||
let passwordIdentities = createPasswordIdentities(from: credentials)
|
||||
allIdentities.append(contentsOf: passwordIdentities)
|
||||
}
|
||||
}
|
||||
|
||||
// Create passkey identities
|
||||
let passkeyIdentities = createPasskeyIdentities(from: credentials)
|
||||
allIdentities.append(contentsOf: passkeyIdentities)
|
||||
|
||||
guard !allIdentities.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
// First, remove all existing credential identities to ensure a clean replacement
|
||||
try await store.removeAllCredentialIdentities()
|
||||
@@ -116,7 +68,7 @@ public class CredentialIdentityStore {
|
||||
)
|
||||
|
||||
// Use the same logic as the UI for determining the identifier
|
||||
let identifier = usernameOrEmail(credential: credential)
|
||||
let identifier = Helpers.usernameOrEmail(credential: credential)
|
||||
guard !identifier.isEmpty else {
|
||||
return nil // Skip credentials with no identifier
|
||||
}
|
||||
@@ -144,15 +96,62 @@ public class CredentialIdentityStore {
|
||||
guard parts.count >= 2 else { return host }
|
||||
return parts.suffix(2).joined(separator: ".")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns username or email depending on if they are not null
|
||||
public func usernameOrEmail(credential: Credential) -> String {
|
||||
if let username = credential.username, !username.isEmpty {
|
||||
return username
|
||||
/// Create password credential identities from credentials
|
||||
private func createPasswordIdentities(from credentials: [Credential]) -> [ASPasswordCredentialIdentity] {
|
||||
return credentials.compactMap { credential in
|
||||
guard let urlString = credential.service.url,
|
||||
let url = URL(string: urlString),
|
||||
let host = url.host else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use the same logic as the UI for determining the identifier
|
||||
let identifier = Helpers.usernameOrEmail(credential: credential)
|
||||
guard !identifier.isEmpty else {
|
||||
return nil // Skip credentials with no identifier
|
||||
}
|
||||
|
||||
guard let password = credential.password, !password.value.isEmpty else {
|
||||
return nil // Skip credentials with no password (e.g. applies when this record is a passkey)
|
||||
}
|
||||
|
||||
let effectiveDomain = Self.effectiveDomain(from: host)
|
||||
|
||||
return ASPasswordCredentialIdentity(
|
||||
serviceIdentifier: ASCredentialServiceIdentifier(identifier: effectiveDomain, type: .domain),
|
||||
user: identifier,
|
||||
recordIdentifier: credential.id.uuidString
|
||||
)
|
||||
}
|
||||
}
|
||||
if let email = credential.alias?.email, !email.isEmpty {
|
||||
return email
|
||||
|
||||
/// Create passkey credential identities from credentials
|
||||
private func createPasskeyIdentities(from credentials: [Credential]) -> [ASPasskeyCredentialIdentity] {
|
||||
return credentials.flatMap { credential -> [ASPasskeyCredentialIdentity] in
|
||||
guard let passkeys = credential.passkeys else {
|
||||
return []
|
||||
}
|
||||
|
||||
return passkeys.filter { !($0.isDeleted) }
|
||||
.compactMap { passkey in
|
||||
// Get the userName for display in iOS AutoFill UI
|
||||
// Passkeys don't store userName in the database, so we use the credential's username or email
|
||||
let userName = passkey.userName ?? Helpers.usernameOrEmail(credential: credential)
|
||||
|
||||
// Convert passkey.Id to bytes for credentialID
|
||||
let credentialId = try? PasskeyHelper.guidToBytes(passkey.id.uuidString)
|
||||
|
||||
// For passkeys, we use the rpId from the passkey itself, not the service URL
|
||||
// This is because passkeys are tied to the RP ID, which may differ from the service URL
|
||||
return ASPasskeyCredentialIdentity(
|
||||
relyingPartyIdentifier: passkey.rpId,
|
||||
userName: userName,
|
||||
credentialID: credentialId ?? Data(), // WebAuthn credential ID (16-byte GUID)
|
||||
userHandle: passkey.userHandle ?? Data(),
|
||||
recordIdentifier: passkey.id.uuidString
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
20
apps/mobile-app/ios/VaultStoreKit/Utils/Helpers.swift
Normal file
20
apps/mobile-app/ios/VaultStoreKit/Utils/Helpers.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
import VaultModels
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
* -------------------------
|
||||
* Generic utility class for general helper functions.
|
||||
*/
|
||||
public class Helpers {
|
||||
/// Returns username or email depending on if they are not null
|
||||
public static func usernameOrEmail(credential: Credential) -> String {
|
||||
if let username = credential.username, !username.isEmpty {
|
||||
return username
|
||||
}
|
||||
if let email = credential.alias?.email, !email.isEmpty {
|
||||
return email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user