diff --git a/Authsignal.podspec b/Authsignal.podspec index 6af0f05..d8a4db2 100644 --- a/Authsignal.podspec +++ b/Authsignal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Authsignal' - s.version = '1.5.2' + s.version = '1.6.0' s.summary = 'The Authsignal SDK for iOS' s.homepage = 'https://github.com/authsignal/authsignal-ios' diff --git a/Sources/Authsignal/AuthsignalPasskey.swift b/Sources/Authsignal/AuthsignalPasskey.swift index 96c1967..d6d0e21 100644 --- a/Sources/Authsignal/AuthsignalPasskey.swift +++ b/Sources/Authsignal/AuthsignalPasskey.swift @@ -13,7 +13,12 @@ public class AuthsignalPasskey { passkeyManager = PasskeyManager() } - public func signUp(token: String? = nil, username: String? = nil, displayName: String? = nil) async -> AuthsignalResponse { + public func signUp( + token: String? = nil, + username: String? = nil, + displayName: String? = nil, + ignorePasskeyAlreadyExistsError: Bool = false + ) async -> AuthsignalResponse { guard let userToken = token ?? cache.token else { return cache.handleTokenNotSetError() } let optsResponse = await api.registrationOptions(token: userToken, username: username, displayName: displayName) @@ -33,8 +38,12 @@ public class AuthsignalPasskey { existingCredentialIds: optsData.options.excludeCredentials.map { $0.id } ) + if ignorePasskeyAlreadyExistsError && credentialResponse.errorCode == SdkErrorCodes.matchedExcludedCredential { + return AuthsignalResponse(error: nil) + } + if let error = credentialResponse.error { - return AuthsignalResponse(error: error) + return AuthsignalResponse(error: error, errorCode: credentialResponse.errorCode) } guard let credential = credentialResponse.data else { @@ -149,6 +158,24 @@ public class AuthsignalPasskey { } } + public func shouldPromptToCreatePasskey() async -> AuthsignalResponse { + guard let credentialId = UserDefaults.standard.string(forKey: passkeyLocalKey) else { + return AuthsignalResponse(data: true) + } + + let passkeyAuthenticatorResponse = await api.getPasskeyAuthenticator(credentialId: credentialId) + + if passkeyAuthenticatorResponse.errorCode == SdkErrorCodes.invalidCredential { + return AuthsignalResponse(data: true) + } + + return AuthsignalResponse( + data: false, + error: passkeyAuthenticatorResponse.error, + errorCode: passkeyAuthenticatorResponse.errorCode + ) + } + @available(*, deprecated, message: "Use 'preferImmediatelyAvailableCredentials' to control what happens when a passkey isn't available.") public func isAvailableOnDevice() async -> AuthsignalResponse { guard let credentialId = UserDefaults.standard.string(forKey: passkeyLocalKey) else { diff --git a/Sources/Authsignal/Models/SdkErrorCodes.swift b/Sources/Authsignal/Models/SdkErrorCodes.swift new file mode 100644 index 0000000..f121a56 --- /dev/null +++ b/Sources/Authsignal/Models/SdkErrorCodes.swift @@ -0,0 +1,5 @@ +struct SdkErrorCodes { + static let userCanceled = "user_canceled" + static let invalidCredential = "invalid_credential" + static let matchedExcludedCredential = "matched_excluded_credential" +} diff --git a/Sources/Authsignal/Security/PasskeyManager.swift b/Sources/Authsignal/Security/PasskeyManager.swift index 92d017e..4ce7686 100644 --- a/Sources/Authsignal/Security/PasskeyManager.swift +++ b/Sources/Authsignal/Security/PasskeyManager.swift @@ -91,7 +91,7 @@ class PasskeyManager: NSObject { authError.code == .matchedExcludedCredential { return AuthsignalResponse( error: "An existing credential is already available for this device.", - errorCode: "matched_excluded_credential" + errorCode: SdkErrorCodes.matchedExcludedCredential ) } @@ -181,7 +181,7 @@ class PasskeyManager: NSObject { return AuthsignalResponse( error: "The request was canceled by the user or the device has no passkeys available.", - errorCode: "user_canceled" + errorCode: SdkErrorCodes.userCanceled ) } catch { self.controller = nil