mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-21 16:13:27 -04:00
Add iOS suggestion credential insert scaffolding (#771)
This commit is contained in:
@@ -14,21 +14,41 @@ class CredentialIdentityStore {
|
||||
private init() {}
|
||||
|
||||
func saveCredentialIdentities(_ credentials: [Credential]) async throws {
|
||||
let identities = credentials.map { credential in
|
||||
let serviceIdentifier = ASCredentialServiceIdentifier(
|
||||
identifier: credential.service.name ?? "",
|
||||
type: .domain
|
||||
)
|
||||
let identities: [ASPasswordCredentialIdentity] = credentials.compactMap { credential in
|
||||
guard let urlString = credential.service.url,
|
||||
let url = URL(string: urlString),
|
||||
let host = url.host else {
|
||||
return nil
|
||||
}
|
||||
guard let username = credential.username, !username.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let effectiveDomain = Self.effectiveDomain(from: host)
|
||||
|
||||
return ASPasswordCredentialIdentity(
|
||||
serviceIdentifier: serviceIdentifier,
|
||||
user: credential.username ?? "",
|
||||
// TODO: Use the actual record identifier when implementing the actual vault
|
||||
recordIdentifier: UUID().uuidString
|
||||
serviceIdentifier: ASCredentialServiceIdentifier(identifier: effectiveDomain, type: .domain),
|
||||
user: username,
|
||||
recordIdentifier: credential.id.uuidString
|
||||
)
|
||||
}
|
||||
|
||||
try await store.saveCredentialIdentities(identities)
|
||||
guard !identities.isEmpty else {
|
||||
print("No valid identities to save.")
|
||||
return
|
||||
}
|
||||
|
||||
let state = await storeState()
|
||||
guard state.isEnabled else {
|
||||
print("Credential identity store is not enabled.")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try await store.saveCredentialIdentities(identities)
|
||||
} catch {
|
||||
print("Failed to save credential identities to native iOS storage: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllCredentialIdentities() async throws {
|
||||
@@ -52,4 +72,18 @@ class CredentialIdentityStore {
|
||||
|
||||
try await store.removeCredentialIdentities(identities)
|
||||
}
|
||||
|
||||
private func storeState() async -> ASCredentialIdentityStoreState {
|
||||
await withCheckedContinuation { continuation in
|
||||
store.getState { state in
|
||||
continuation.resume(returning: state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func effectiveDomain(from host: String) -> String {
|
||||
let parts = host.split(separator: ".")
|
||||
guard parts.count >= 2 else { return host }
|
||||
return parts.suffix(2).joined(separator: ".")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
|
||||
let viewModel = CredentialProviderViewModel(
|
||||
loader: {
|
||||
try VaultStore.shared.initializeDatabase()
|
||||
return try VaultStore.shared.getAllCredentials()
|
||||
let credentials = try VaultStore.shared.getAllCredentials()
|
||||
await self.registerCredentialIdentities(credentials: credentials)
|
||||
return credentials
|
||||
},
|
||||
selectionHandler: { [weak self] credential in
|
||||
guard let self = self else { return }
|
||||
@@ -85,7 +87,10 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
|
||||
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
|
||||
do {
|
||||
let credentials = try VaultStore.shared.getAllCredentials()
|
||||
if let matchingCredential = credentials.first(where: { $0.service.name ?? "" == credentialIdentity.serviceIdentifier.identifier }) {
|
||||
|
||||
if let matchingCredential = credentials.first(where: { credential in
|
||||
return credential.id.uuidString == credentialIdentity.recordIdentifier
|
||||
}) {
|
||||
let passwordCredential = ASPasswordCredential(
|
||||
user: matchingCredential.username ?? "",
|
||||
password: matchingCredential.password?.value ?? ""
|
||||
@@ -109,4 +114,16 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This registers all known AliasVault credentials into iOS native credential storage, which iOS can then use to suggest autofill credentials when a user
|
||||
* focuses an input field on a login form. These suggestions will then be shown above the iOS keyboard, which saves the user one step.
|
||||
*/
|
||||
private func registerCredentialIdentities(credentials: [Credential]) async {
|
||||
do {
|
||||
try await CredentialIdentityStore.shared.saveCredentialIdentities(credentials)
|
||||
} catch {
|
||||
print("Failed to save credential identities: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user