From bc6f492208d43e5cc2ea30588735b9bd4e2b24a6 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 9 Oct 2025 16:17:52 +0200 Subject: [PATCH] Update local passkey create logic with proper date formatting (#520) --- ...entialProviderViewController+Passkey.swift | 149 +++++++++--------- .../VaultStoreKit/VaultStore+Passkey.swift | 18 ++- 2 files changed, 91 insertions(+), 76 deletions(-) diff --git a/apps/mobile-app/ios/Autofill/CredentialProviderViewController+Passkey.swift b/apps/mobile-app/ios/Autofill/CredentialProviderViewController+Passkey.swift index 93acd000d..9615bda4a 100644 --- a/apps/mobile-app/ios/Autofill/CredentialProviderViewController+Passkey.swift +++ b/apps/mobile-app/ios/Autofill/CredentialProviderViewController+Passkey.swift @@ -115,17 +115,6 @@ extension CredentialProviderViewController: PasskeyProviderDelegate { } // MARK: - Passkey Registration - - /** - * Handle passkey registration request from the system - */ - func handlePasskeyRegistration(_ registrationRequest: ASCredentialRequest) { - // Set flag to prevent normal credential view from loading - self.isPasskeyRegistrationMode = true - - - } - override public func prepareInterface(forPasskeyRegistration registrationRequest: any ASCredentialRequest) { self.isPasskeyRegistrationMode = true @@ -255,80 +244,94 @@ extension CredentialProviderViewController: PasskeyProviderDelegate { clientDataHash: Data, vaultStore: VaultStore ) { - do { - print("PasskeyRegistration: Creating passkey directly in Swift layer") + // Create a Task to handle async operations + Task { + do { + print("PasskeyRegistration: Creating passkey directly in Swift layer") - // Generate new credential ID (UUID that will be used as the passkey ID) - let passkeyId = UUID() - let credentialId = try PasskeyHelper.guidToBytes(passkeyId.uuidString) + // Generate new credential ID (UUID that will be used as the passkey ID) + let passkeyId = UUID() + let credentialId = try PasskeyHelper.guidToBytes(passkeyId.uuidString) - // Create the passkey using PasskeyAuthenticator - let passkeyResult = try PasskeyAuthenticator.createPasskey( - credentialId: credentialId, - clientDataHash: clientDataHash, - rpId: rpId, - userId: userId, - userName: userName, - userDisplayName: userDisplayName, - uvPerformed: true, - enablePrf: false - ) + // Create the passkey using PasskeyAuthenticator + let passkeyResult = try PasskeyAuthenticator.createPasskey( + credentialId: credentialId, + clientDataHash: clientDataHash, + rpId: rpId, + userId: userId, + userName: userName, + userDisplayName: userDisplayName, + uvPerformed: true, + enablePrf: false + ) - print("PasskeyRegistration: Passkey created successfully") + print("PasskeyRegistration: Passkey created successfully") - // Create a Passkey model object - let now = Date() - let passkey = Passkey( - id: passkeyId, - parentCredentialId: UUID(), // Will be set by createCredentialWithPasskey - rpId: rpId, - userHandle: userId, - userName: userName, - publicKey: passkeyResult.publicKey, - privateKey: passkeyResult.privateKey, - prfKey: passkeyResult.prfSecret, - displayName: userDisplayName ?? userName ?? rpId, - createdAt: now, - updatedAt: now, - isDeleted: false - ) + // Create a Passkey model object + let now = Date() + let passkey = Passkey( + id: passkeyId, + parentCredentialId: UUID(), // Will be set by createCredentialWithPasskey + rpId: rpId, + userHandle: userId, + userName: userName, + publicKey: passkeyResult.publicKey, + privateKey: passkeyResult.privateKey, + prfKey: passkeyResult.prfSecret, + displayName: userDisplayName ?? userName ?? rpId, + createdAt: now, + updatedAt: now, + isDeleted: false + ) - // Begin transaction - try vaultStore.beginTransaction() + // Begin transaction + try vaultStore.beginTransaction() - // Store credential with passkey in database - let credential = try vaultStore.createCredentialWithPasskey( - rpId: rpId, - userName: userName, - userDisplayName: userDisplayName, - passkey: passkey - ) + // Store credential with passkey in database + let credential = try vaultStore.createCredentialWithPasskey( + rpId: rpId, + userName: userName, + userDisplayName: userDisplayName, + passkey: passkey + ) - // Commit transaction to persist the data - try vaultStore.commitTransaction() + // Commit transaction to persist the data + try vaultStore.commitTransaction() - print("PasskeyRegistration: Credential and passkey stored in database - credentialId=\(credential.id.uuidString)") + print("PasskeyRegistration: Credential and passkey stored in database - credentialId=\(credential.id.uuidString)") - // Create the ASPasskeyRegistrationCredential to return to the system - let asCredential = ASPasskeyRegistrationCredential( - relyingParty: rpId, - clientDataHash: clientDataHash, - credentialID: credentialId, - attestationObject: passkeyResult.attestationObject - ) + // Update the IdentityStore with the new credential (async call) + let credentials = try vaultStore.getAllCredentials() + try await CredentialIdentityStore.shared.saveCredentialIdentities(credentials) - print("PasskeyRegistration: Completing registration request") + print("PasskeyRegistration: Updated CredentialIdentityStore") - // Complete the registration request - extensionContext.completeRegistrationRequest(using: asCredential) + // Create the ASPasskeyRegistrationCredential to return to the system + let asCredential = ASPasskeyRegistrationCredential( + relyingParty: rpId, + clientDataHash: clientDataHash, + credentialID: credentialId, + attestationObject: passkeyResult.attestationObject + ) - } catch { - print("PasskeyRegistration error: \(error)") - extensionContext.cancelRequest(withError: NSError( - domain: ASExtensionErrorDomain, - code: ASExtensionError.failed.rawValue, - userInfo: [NSLocalizedDescriptionKey: "Failed to create passkey: \(error.localizedDescription)"] - )) + print("PasskeyRegistration: Completing registration request") + + // Complete the registration request (must be on main thread) + await MainActor.run { + self.extensionContext.completeRegistrationRequest(using: asCredential) + } + + } catch { + print("PasskeyRegistration error: \(error)") + // Cancel request (must be on main thread) + await MainActor.run { + self.extensionContext.cancelRequest(withError: NSError( + domain: ASExtensionErrorDomain, + code: ASExtensionError.failed.rawValue, + userInfo: [NSLocalizedDescriptionKey: "Failed to create passkey: \(error.localizedDescription)"] + )) + } + } } } diff --git a/apps/mobile-app/ios/VaultStoreKit/VaultStore+Passkey.swift b/apps/mobile-app/ios/VaultStoreKit/VaultStore+Passkey.swift index f78be86ba..63fed0de4 100644 --- a/apps/mobile-app/ios/VaultStoreKit/VaultStore+Passkey.swift +++ b/apps/mobile-app/ios/VaultStoreKit/VaultStore+Passkey.swift @@ -372,6 +372,18 @@ extension VaultStore { // MARK: - Passkey Storage + /** + * Format a date for database insertion + * Format: yyyy-MM-dd HH:mm:ss.SSS + */ + private func formatDateForDatabase(_ date: Date) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + return formatter.string(from: date) + } + /** * Create a credential with a passkey (proof of concept for passkey registration) * This creates a minimal credential record and links the passkey to it @@ -390,7 +402,7 @@ extension VaultStore { let credentialId = passkey.parentCredentialId let now = Date() - let timestamp = ISO8601DateFormatter().string(from: now) + let timestamp = formatDateForDatabase(now) // Create a minimal service for the RP let serviceId = UUID() @@ -469,8 +481,8 @@ extension VaultStore { Self.colPrivateKey <- String(data: passkey.privateKey, encoding: .utf8)!, Self.colPrfKey <- passkey.prfKey.map { Blob(bytes: [UInt8]($0)) }, Self.colDisplayName <- passkey.displayName, - Self.colCreatedAt <- ISO8601DateFormatter().string(from: passkey.createdAt), - Self.colUpdatedAt <- ISO8601DateFormatter().string(from: passkey.updatedAt), + Self.colCreatedAt <- formatDateForDatabase(passkey.createdAt), + Self.colUpdatedAt <- formatDateForDatabase(passkey.updatedAt), Self.colIsDeleted <- Int64(passkey.isDeleted ? 1 : 0) )