Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b55f3a1

Browse files
committedMay 21, 2025·
feat: add ios support
1 parent 069893e commit b55f3a1

9 files changed

+775
-0
lines changed
 
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#import <React/RCTBridgeModule.h>
5+
#import <AuthenticationServices/AuthenticationServices.h>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#import "AmplifyRtnPasskeys.h"
5+
#import "AmplifyRtnPasskeys-Swift.h"
6+
7+
@implementation AmplifyRtnPasskeys
8+
9+
- (void)createPasskey:
10+
(JS::NativeAmplifyRtnPasskeys::PasskeyCreateOptionsJson &)input
11+
resolve:(nonnull RCTPromiseResolveBlock)resolve
12+
reject:(nonnull RCTPromiseRejectBlock)reject {
13+
14+
NSMutableArray *excludeCredentials = [@[] mutableCopy];
15+
16+
if (input.excludeCredentials().has_value()) {
17+
auto credentials = input.excludeCredentials().value();
18+
for (const auto &credential : credentials) {
19+
[excludeCredentials addObject:credential.id_()];
20+
}
21+
}
22+
23+
return [[AmplifyRtnPasskeysSwift alloc] createPasskey:input.rp().id_()
24+
userId:input.user().id_()
25+
userName:input.user().name()
26+
challenge:input.challenge()
27+
excludeCredentials:excludeCredentials
28+
resolve:resolve
29+
reject:reject];
30+
}
31+
32+
- (void)getPasskey:(JS::NativeAmplifyRtnPasskeys::PasskeyGetOptionsJson &)input
33+
resolve:(nonnull RCTPromiseResolveBlock)resolve
34+
reject:(nonnull RCTPromiseRejectBlock)reject {
35+
36+
NSMutableArray *allowCredentials = [@[] mutableCopy];
37+
38+
if (input.allowCredentials().has_value()) {
39+
auto credentials = input.allowCredentials().value();
40+
for (const auto &credential : credentials) {
41+
[allowCredentials addObject:credential.id_()];
42+
}
43+
}
44+
45+
return [[AmplifyRtnPasskeysSwift alloc] getPasskey:input.rpId()
46+
challenge:input.challenge()
47+
userVerification:input.userVerification()
48+
allowCredentials:allowCredentials
49+
resolve:resolve
50+
reject:reject];
51+
}
52+
53+
- (nonnull NSNumber *)getIsPasskeySupported {
54+
return [[AmplifyRtnPasskeysSwift alloc] getIsPasskeySupported];
55+
}
56+
57+
+ (NSString *)moduleName {
58+
return @"AmplifyRtnPasskeys";
59+
}
60+
61+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
62+
(const facebook::react::ObjCTurboModule::InitParams &)params {
63+
return std::make_shared<facebook::react::NativeAmplifyRtnPasskeysSpecJSI>(
64+
params);
65+
}
66+
67+
@end
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import AuthenticationServices
5+
import Foundation
6+
7+
@objc(AmplifyRtnPasskeysSwift)
8+
public class AmplifyRtnPasskeys: NSObject, AmplifyRtnPasskeysResultHandler {
9+
private var _passkeyDelegate: AmplifyRtnPasskeysDelegate?
10+
private var _promiseHandler: AmplifyRtnPasskeysPromiseHandler?
11+
12+
@objc
13+
@available(iOS 15.0, *)
14+
public func createPasskey(
15+
_ rpId: String,
16+
userId: String,
17+
userName: String,
18+
challenge: String,
19+
excludeCredentials: [String],
20+
resolve: @escaping RCTPromiseResolveBlock,
21+
reject: @escaping RCTPromiseRejectBlock
22+
) {
23+
24+
_promiseHandler = initializePromiseHandler(resolve, reject)
25+
26+
guard self.getIsPasskeySupported() == true else {
27+
handleError(errorName: "NOT_SUPPORTED", errorMessage: nil, error: nil)
28+
return
29+
}
30+
31+
let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(
32+
relyingPartyIdentifier: rpId)
33+
34+
let platformKeyRequest =
35+
platformProvider.createCredentialRegistrationRequest(
36+
challenge: challenge.toBase64UrlDecodedData(),
37+
name: userName,
38+
userID: userId.toBase64UrlDecodedData()
39+
)
40+
41+
if #available(iOS 17.4, *) {
42+
let excludedCredentials:
43+
[ASAuthorizationPlatformPublicKeyCredentialDescriptor] =
44+
excludeCredentials.compactMap { credentialId in
45+
return .init(credentialID: credentialId.toBase64UrlDecodedData())
46+
}
47+
48+
platformKeyRequest.excludedCredentials = excludedCredentials
49+
}
50+
51+
let authController = initializeAuthController(
52+
platformKeyRequest: platformKeyRequest)
53+
54+
let passkeyDelegate = initializePasskeyDelegate(resultHandler: self)
55+
56+
_passkeyDelegate = passkeyDelegate
57+
58+
passkeyDelegate.performAuthForController(authController)
59+
}
60+
61+
@objc
62+
@available(iOS 15.0, *)
63+
public func getPasskey(
64+
_ rpId: String,
65+
challenge: String,
66+
userVerification: String,
67+
allowCredentials: [String],
68+
resolve: @escaping RCTPromiseResolveBlock,
69+
reject: @escaping RCTPromiseRejectBlock
70+
) {
71+
_promiseHandler = initializePromiseHandler(resolve, reject)
72+
73+
guard self.getIsPasskeySupported() == true else {
74+
handleError(errorName: "NOT_SUPPORTED")
75+
return
76+
}
77+
78+
let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(
79+
relyingPartyIdentifier: rpId)
80+
81+
let platformKeyRequest = platformProvider.createCredentialAssertionRequest(
82+
challenge: challenge.toBase64UrlDecodedData()
83+
)
84+
85+
let allowedCredentials:
86+
[ASAuthorizationPlatformPublicKeyCredentialDescriptor] =
87+
allowCredentials.compactMap { credentialId in
88+
return .init(credentialID: credentialId.toBase64UrlDecodedData())
89+
}
90+
91+
platformKeyRequest.allowedCredentials = allowedCredentials
92+
93+
platformKeyRequest.userVerificationPreference =
94+
ASAuthorizationPublicKeyCredentialUserVerificationPreference(
95+
userVerification)
96+
97+
let authController = initializeAuthController(
98+
platformKeyRequest: platformKeyRequest)
99+
100+
let passkeyDelegate = initializePasskeyDelegate(resultHandler: self)
101+
102+
_passkeyDelegate = passkeyDelegate
103+
104+
passkeyDelegate.performAuthForController(authController)
105+
}
106+
107+
func handleSuccess(_ data: NSDictionary) {
108+
guard let handler = _promiseHandler else {
109+
return
110+
}
111+
handler.resolve(data)
112+
_promiseHandler = nil
113+
_passkeyDelegate = nil
114+
}
115+
116+
func handleError(
117+
errorName: String, errorMessage: String? = nil, error: (any Error)? = nil
118+
) {
119+
guard let handler = _promiseHandler else {
120+
return
121+
}
122+
handler.reject(errorName, errorMessage, error)
123+
_promiseHandler = nil
124+
_passkeyDelegate = nil
125+
}
126+
127+
func initializePromiseHandler(
128+
_ resolve: @escaping RCTPromiseResolveBlock,
129+
_ reject: @escaping RCTPromiseRejectBlock
130+
) -> AmplifyRtnPasskeysPromiseHandler {
131+
return AmplifyRtnPasskeysPromiseHandler(resolve, reject)
132+
}
133+
134+
func initializePasskeyDelegate(resultHandler: AmplifyRtnPasskeysResultHandler)
135+
-> AmplifyRtnPasskeysDelegate
136+
{
137+
return AmplifyRtnPasskeysDelegate(resultHandler: resultHandler)
138+
}
139+
140+
func initializeAuthController(platformKeyRequest: ASAuthorizationRequest)
141+
-> ASAuthorizationController
142+
{
143+
return ASAuthorizationController(authorizationRequests: [platformKeyRequest]
144+
)
145+
}
146+
147+
@objc
148+
public func getIsPasskeySupported() -> NSNumber {
149+
if #available(iOS 17.4, *) {
150+
return true
151+
}
152+
return false
153+
}
154+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import AuthenticationServices
5+
import Foundation
6+
7+
class AmplifyRtnPasskeysDelegate: NSObject,
8+
ASAuthorizationControllerDelegate,
9+
ASAuthorizationControllerPresentationContextProviding
10+
{
11+
private static let PUBLIC_KEY_TYPE = "public-key"
12+
private static let PLATFORM_ATTACHMENT = "platform"
13+
private static let INTERNAL_TRANSPORT = "internal"
14+
15+
private static let ERROR_MAP: [Int: String] = [
16+
1000: "UNKNOWN",
17+
1001: "CANCELED",
18+
1002: "INVALID_RESPONSE",
19+
1003: "NOT_HANDLED",
20+
1004: "FAILED",
21+
1005: "NOT_INTERACTIVE",
22+
1006: "DUPLICATE",
23+
]
24+
25+
let _resultHandler: AmplifyRtnPasskeysResultHandler
26+
27+
init(resultHandler: AmplifyRtnPasskeysResultHandler) {
28+
_resultHandler = resultHandler
29+
}
30+
31+
func performAuthForController(_ authController: ASAuthorizationController) {
32+
authController.delegate = self
33+
authController.presentationContextProvider = self
34+
authController.performRequests()
35+
}
36+
37+
func authorizationController(
38+
controller: ASAuthorizationController,
39+
didCompleteWithAuthorization authorization: ASAuthorization
40+
) {
41+
42+
switch authorization.credential {
43+
case let assertionCredential
44+
as ASAuthorizationPlatformPublicKeyCredentialAssertion:
45+
46+
let assertionResult: NSDictionary = [
47+
"id": assertionCredential.credentialID.toBase64UrlEncodedString(),
48+
"rawId": assertionCredential.credentialID.toBase64UrlEncodedString(),
49+
"authenticatorAttachment": AmplifyRtnPasskeysDelegate
50+
.PLATFORM_ATTACHMENT,
51+
"type": AmplifyRtnPasskeysDelegate.PUBLIC_KEY_TYPE,
52+
"response": [
53+
"authenticatorData": assertionCredential.rawAuthenticatorData
54+
.toBase64UrlEncodedString(),
55+
"clientDataJSON": assertionCredential.rawClientDataJSON
56+
.toBase64UrlEncodedString(),
57+
"signature": assertionCredential.signature.toBase64UrlEncodedString(),
58+
"userHandle": assertionCredential.userID.toBase64UrlEncodedString(),
59+
],
60+
]
61+
62+
_resultHandler.handleSuccess(assertionResult)
63+
64+
case let registrationCredential
65+
as ASAuthorizationPlatformPublicKeyCredentialRegistration:
66+
let registrationResult: NSDictionary = [
67+
"id": registrationCredential.credentialID.toBase64UrlEncodedString(),
68+
"rawId": registrationCredential.credentialID.toBase64UrlEncodedString(),
69+
"authenticatorAttachment": AmplifyRtnPasskeysDelegate
70+
.PLATFORM_ATTACHMENT,
71+
"type": AmplifyRtnPasskeysDelegate.PUBLIC_KEY_TYPE,
72+
"response": [
73+
"attestationObject": registrationCredential.rawAttestationObject!
74+
.toBase64UrlEncodedString(),
75+
"clientDataJSON": registrationCredential.rawClientDataJSON
76+
.toBase64UrlEncodedString(),
77+
"transports": [AmplifyRtnPasskeysDelegate.INTERNAL_TRANSPORT],
78+
],
79+
]
80+
81+
_resultHandler.handleSuccess(registrationResult)
82+
83+
default:
84+
_resultHandler.handleError(
85+
errorName: "FAILED", errorMessage: nil, error: nil)
86+
}
87+
}
88+
89+
func authorizationController(
90+
controller: ASAuthorizationController,
91+
didCompleteWithError error: any Error
92+
) {
93+
let errorMessage = error.localizedDescription
94+
95+
var errorName =
96+
AmplifyRtnPasskeysDelegate.ERROR_MAP[(error as NSError).code] ?? "UNKNOWN"
97+
98+
// pre-iOS 18 does not through explicit error for duplicate
99+
if errorMessage.contains(
100+
"credential matches an entry of the excludeCredentials list")
101+
{
102+
errorName = "DUPLICATE"
103+
}
104+
105+
// no explicit error with for SecurityError
106+
if errorMessage.contains("not associated with domain") {
107+
errorName = "RELYING_PARTY_MISMATCH"
108+
}
109+
110+
_resultHandler.handleError(
111+
errorName: errorName, errorMessage: errorMessage, error: error)
112+
}
113+
114+
func presentationAnchor(for controller: ASAuthorizationController)
115+
-> ASPresentationAnchor
116+
{
117+
return ASPresentationAnchor()
118+
}
119+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
extension String {
5+
// Converts base64Url encoded string to base64 Data
6+
func toBase64UrlDecodedData() -> Data {
7+
var base64String = self.replacingOccurrences(of: "_", with: "/")
8+
.replacingOccurrences(of: "-", with: "+")
9+
10+
while base64String.count % 4 != 0 {
11+
base64String.append("=")
12+
}
13+
14+
return Data(base64Encoded: base64String) ?? Data()
15+
}
16+
}
17+
18+
extension Data {
19+
// Converts base64 Data to base64url String
20+
func toBase64UrlEncodedString() -> String {
21+
return self.base64EncodedString()
22+
.replacingOccurrences(of: "/", with: "_")
23+
.replacingOccurrences(of: "+", with: "-")
24+
.replacingOccurrences(of: "=", with: "")
25+
}
26+
}
27+
28+
struct AmplifyRtnPasskeysPromiseHandler {
29+
let resolve: RCTPromiseResolveBlock
30+
let reject: RCTPromiseRejectBlock
31+
32+
init(
33+
_ resolve: @escaping RCTPromiseResolveBlock,
34+
_ reject: @escaping RCTPromiseRejectBlock
35+
) {
36+
self.resolve = resolve
37+
self.reject = reject
38+
}
39+
}
40+
41+
protocol AmplifyRtnPasskeysResultHandler {
42+
func handleSuccess(_ data: NSDictionary)
43+
func handleError(
44+
errorName: String, errorMessage: String?, error: (any Error)?)
45+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import XCTest
5+
6+
@testable import AmplifyRtnPasskeys
7+
8+
class MockASAuthorizationController: ASAuthorizationController {}
9+
class MockASAuthorization: ASAuthorization, @unchecked Sendable {}
10+
11+
class AmplifyRtnPasskeysDelegateTests: XCTestCase,
12+
AmplifyRtnPasskeysResultHandler
13+
{
14+
private var mockResolve: RCTPromiseResolveBlock!
15+
private var mockReject: RCTPromiseRejectBlock!
16+
17+
override func setUp() {
18+
super.setUp()
19+
}
20+
21+
override func tearDown() {
22+
super.tearDown()
23+
}
24+
25+
func handleSuccess(_ data: NSDictionary) {
26+
mockResolve(data)
27+
}
28+
29+
func handleError(
30+
errorName: String, errorMessage: String?, error: (any Error)?
31+
) {
32+
mockReject(errorName, errorMessage, error)
33+
}
34+
35+
func testInitializedWithResultHandler() {
36+
let passkeyDelegate = AmplifyRtnPasskeysDelegate(resultHandler: self)
37+
38+
XCTAssertEqual(
39+
self,
40+
passkeyDelegate._resultHandler as! AmplifyRtnPasskeysDelegateTests)
41+
}
42+
43+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import XCTest
5+
6+
@testable import AmplifyRtnPasskeys
7+
8+
class AmplifyRtnPasskeysHelpersTests: XCTestCase {
9+
10+
override func setUp() {
11+
super.setUp()
12+
}
13+
14+
override func tearDown() {
15+
super.tearDown()
16+
}
17+
18+
func testToBase64UrlDecodedData() {
19+
XCTAssertEqual(
20+
TestFixtures.base64UrlString.toBase64UrlDecodedData(),
21+
Data(base64Encoded: TestFixtures.base64String),
22+
"Base64Url decoding should work correctly")
23+
}
24+
25+
func testToBase64UrlEncodedString() {
26+
XCTAssertEqual(
27+
Data(base64Encoded: TestFixtures.base64String)?.toBase64UrlEncodedString(),
28+
TestFixtures.base64UrlString,
29+
"Base64Url encoding should work correctly")
30+
}
31+
32+
func testBase64UrlTranscodeRoundTrip() {
33+
XCTAssertEqual(
34+
TestFixtures.base64UrlString,
35+
TestFixtures.base64UrlString.toBase64UrlDecodedData().toBase64UrlEncodedString(),
36+
"Base64Url encoding and decoding should work correctly")
37+
}
38+
39+
40+
41+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import AuthenticationServices
5+
import Foundation
6+
7+
struct TestFixtures {
8+
// AmplifyRtnPasskeysHelpersTests
9+
static let base64String = "PDw/Pz8+Pg=="
10+
static let base64UrlString = "PDw_Pz8-Pg"
11+
12+
// AmplifyRtnPasskeysTests
13+
static let validRpId = "example.com"
14+
static let validChallenge = "Y2hhbGxlbmdlLXZhbHVl"
15+
static let invalidChallenge = "invalid-challenge"
16+
17+
static let validUserId = "dXNlci1pZC12YWx1ZQ=="
18+
static let validUserName = "testuser@example.com"
19+
20+
static let validExcludeCredentials = [
21+
"Y3JlZGVudGlhbC1pZC0x",
22+
"Y3JlZGVudGlhbC1pZC0y",
23+
]
24+
25+
static let validUserVerificationRequired = "required"
26+
27+
static let validAllowCredentials = [
28+
"Y3JlZGVudGlhbC1pZC0x",
29+
"Y3JlZGVudGlhbC1pZC0y",
30+
]
31+
32+
static func createPasskeySuccess() -> NSDictionary {
33+
return [
34+
"id": "Y3JlZGVudGlhbC1pZC1uZXc=",
35+
"rawId": "Y3JlZGVudGlhbC1pZC1uZXc=",
36+
"type": "public-key",
37+
"authenticatorAttachment": "platform",
38+
"response": [
39+
"clientDataJSON":
40+
"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiWTJoaGJHeGxibWRsTFhaaGJIVmwiLCJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIn0=",
41+
"attestationObject": "bzJoZWxsb3dvcmxkZw==",
42+
"transports": ["internal"],
43+
],
44+
]
45+
}
46+
47+
static func getPasskeySuccess() -> NSDictionary {
48+
return [
49+
"id": "Y3JlZGVudGlhbC1pZC0x",
50+
"rawId": "Y3JlZGVudGlhbC1pZC0x",
51+
"type": "public-key",
52+
"authenticatorAttachment": "platform",
53+
"response": [
54+
"authenticatorData":
55+
"SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MBAAAACA==",
56+
"clientDataJSON":
57+
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiWTJoaGJHeGxibWRsTFhaaGJIVmwiLCJvcmlnaW4iOiJodHRwczovL2V4YW1wbGUuY29tIn0=",
58+
"signature":
59+
"MEUCIQDxvUz+tIFPrWWRtJJbYHFHNmWdRYPi0EvCEiN+aXiOQQIgEfXxDHbH0q8+htk7qacVFniQKz85KYQMz3HEPDC9hok=",
60+
"userHandle": "dXNlci1pZC12YWx1ZQ==",
61+
],
62+
]
63+
64+
}
65+
}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import XCTest
5+
6+
@testable import AmplifyRtnPasskeys
7+
8+
class AmplifyRtnPasskeysTests: XCTestCase {
9+
private var mockResolve: RCTPromiseResolveBlock!
10+
private var mockReject: RCTPromiseRejectBlock!
11+
12+
private var resolveCalled = false
13+
private var resolveValue: Any?
14+
15+
private var rejectCalled = false
16+
private var rejectErrorName: String?
17+
private var rejectErrorMessage: String?
18+
19+
override func setUp() {
20+
super.setUp()
21+
22+
mockResolve = { value in
23+
self.resolveCalled = true
24+
self.resolveValue = value
25+
}
26+
27+
mockReject = { errorName, errorMessage, error in
28+
self.rejectCalled = true
29+
self.rejectErrorName = errorName
30+
self.rejectErrorMessage = errorMessage
31+
}
32+
}
33+
34+
override func tearDown() {
35+
super.tearDown()
36+
37+
resolveCalled = false
38+
resolveValue = nil
39+
40+
rejectCalled = false
41+
rejectErrorName = nil
42+
rejectErrorMessage = nil
43+
}
44+
45+
func testGetPasskeyShouldRejectWhenNotSupported() {
46+
class MockAmplifyRtnPasskeys: AmplifyRtnPasskeys {
47+
override func getIsPasskeySupported() -> NSNumber {
48+
return false
49+
}
50+
}
51+
52+
let mockInstance = MockAmplifyRtnPasskeys()
53+
54+
mockInstance.getPasskey(
55+
TestFixtures.validRpId,
56+
challenge: TestFixtures.validChallenge,
57+
userVerification: TestFixtures
58+
.validUserVerificationRequired,
59+
allowCredentials: TestFixtures
60+
.validAllowCredentials, resolve: mockResolve, reject: mockReject)
61+
62+
XCTAssertTrue(rejectCalled)
63+
XCTAssertTrue(!resolveCalled)
64+
XCTAssertEqual(rejectErrorName, "NOT_SUPPORTED")
65+
XCTAssertEqual(rejectErrorMessage, nil)
66+
}
67+
68+
func testCreatePasskeyShouldRejectWhenNotSupported() {
69+
class MockAmplifyRtnPasskeys: AmplifyRtnPasskeys {
70+
override func getIsPasskeySupported() -> NSNumber {
71+
return false
72+
}
73+
}
74+
75+
let mockInstance = MockAmplifyRtnPasskeys()
76+
77+
mockInstance.createPasskey(
78+
TestFixtures.validRpId,
79+
userId: TestFixtures.validUserId,
80+
userName: TestFixtures.validUserName,
81+
challenge: TestFixtures.validChallenge,
82+
excludeCredentials: TestFixtures
83+
.validExcludeCredentials, resolve: mockResolve, reject: mockReject)
84+
85+
XCTAssertTrue(rejectCalled)
86+
XCTAssertTrue(!resolveCalled)
87+
XCTAssertEqual(rejectErrorName, "NOT_SUPPORTED")
88+
XCTAssertEqual(rejectErrorMessage, nil)
89+
}
90+
91+
func testGetPasskeyShouldResolveWithAssertionResultFromDelegate() {
92+
class MockAmplifyRtnPasskeys: AmplifyRtnPasskeys {
93+
override func getIsPasskeySupported() -> NSNumber {
94+
return true
95+
}
96+
override func initializePasskeyDelegate(
97+
resultHandler: any AmplifyRtnPasskeysResultHandler
98+
) -> AmplifyRtnPasskeysDelegate {
99+
class MockAmplifyRtnPasskeysDelegate: AmplifyRtnPasskeysDelegate {
100+
override func performAuthForController(
101+
_ authController: ASAuthorizationController
102+
) {
103+
self._resultHandler.handleSuccess(TestFixtures.getPasskeySuccess())
104+
}
105+
}
106+
return MockAmplifyRtnPasskeysDelegate(resultHandler: self)
107+
}
108+
}
109+
110+
let mockInstance = MockAmplifyRtnPasskeys()
111+
112+
mockInstance.getPasskey(
113+
TestFixtures.validRpId,
114+
challenge: TestFixtures.validChallenge,
115+
userVerification: TestFixtures
116+
.validUserVerificationRequired,
117+
allowCredentials: TestFixtures
118+
.validAllowCredentials, resolve: mockResolve, reject: mockReject)
119+
120+
XCTAssertTrue(resolveCalled)
121+
XCTAssertTrue(!rejectCalled)
122+
XCTAssertEqual(
123+
resolveValue as! NSDictionary, TestFixtures.getPasskeySuccess())
124+
125+
}
126+
127+
func testCreatePasskeyShouldResolveWithRegistrationResultFromDelegate() {
128+
class MockAmplifyRtnPasskeys: AmplifyRtnPasskeys {
129+
override func getIsPasskeySupported() -> NSNumber {
130+
return true
131+
}
132+
override func initializePasskeyDelegate(
133+
resultHandler: any AmplifyRtnPasskeysResultHandler
134+
) -> AmplifyRtnPasskeysDelegate {
135+
class MockAmplifyRtnPasskeysDelegate: AmplifyRtnPasskeysDelegate {
136+
override func performAuthForController(
137+
_ authController: ASAuthorizationController
138+
) {
139+
self._resultHandler.handleSuccess(
140+
TestFixtures.createPasskeySuccess())
141+
}
142+
}
143+
return MockAmplifyRtnPasskeysDelegate(resultHandler: self)
144+
}
145+
}
146+
147+
let mockInstance = MockAmplifyRtnPasskeys()
148+
149+
mockInstance.createPasskey(
150+
TestFixtures.validRpId,
151+
userId: TestFixtures.validUserId,
152+
userName: TestFixtures.validUserName,
153+
challenge: TestFixtures.validChallenge,
154+
excludeCredentials: TestFixtures
155+
.validExcludeCredentials, resolve: mockResolve, reject: mockReject)
156+
157+
XCTAssertTrue(resolveCalled)
158+
XCTAssertTrue(!rejectCalled)
159+
XCTAssertEqual(
160+
resolveValue as! NSDictionary, TestFixtures.createPasskeySuccess())
161+
}
162+
163+
func testGetPasskeyShouldRejectWithErrorFromDelegate() {
164+
class MockAmplifyRtnPasskeys: AmplifyRtnPasskeys {
165+
override func getIsPasskeySupported() -> NSNumber {
166+
return true
167+
}
168+
override func initializePasskeyDelegate(
169+
resultHandler: any AmplifyRtnPasskeysResultHandler
170+
) -> AmplifyRtnPasskeysDelegate {
171+
class MockAmplifyRtnPasskeysDelegate: AmplifyRtnPasskeysDelegate {
172+
override func performAuthForController(
173+
_ authController: ASAuthorizationController
174+
) {
175+
self._resultHandler.handleError(
176+
errorName: "SOME_ERROR", errorMessage: "some error message",
177+
error: nil)
178+
}
179+
}
180+
return MockAmplifyRtnPasskeysDelegate(resultHandler: self)
181+
}
182+
}
183+
184+
let mockInstance = MockAmplifyRtnPasskeys()
185+
186+
mockInstance.getPasskey(
187+
TestFixtures.validRpId,
188+
challenge: TestFixtures.validChallenge,
189+
userVerification: TestFixtures
190+
.validUserVerificationRequired,
191+
allowCredentials: TestFixtures
192+
.validAllowCredentials, resolve: mockResolve, reject: mockReject)
193+
194+
XCTAssertTrue(rejectCalled)
195+
XCTAssertTrue(!resolveCalled)
196+
XCTAssertEqual(rejectErrorName, "SOME_ERROR")
197+
XCTAssertEqual(rejectErrorMessage, "some error message")
198+
}
199+
200+
func testCreatePasskeyShouldRejectWithErrorFromDelegate() {
201+
class MockAmplifyRtnPasskeys: AmplifyRtnPasskeys {
202+
override func getIsPasskeySupported() -> NSNumber {
203+
return true
204+
}
205+
override func initializePasskeyDelegate(
206+
resultHandler: any AmplifyRtnPasskeysResultHandler
207+
) -> AmplifyRtnPasskeysDelegate {
208+
class MockAmplifyRtnPasskeysDelegate: AmplifyRtnPasskeysDelegate {
209+
override func performAuthForController(
210+
_ authController: ASAuthorizationController
211+
) {
212+
self._resultHandler.handleError(
213+
errorName: "SOME_ERROR", errorMessage: "some error message",
214+
error: nil)
215+
}
216+
}
217+
return MockAmplifyRtnPasskeysDelegate(resultHandler: self)
218+
}
219+
}
220+
221+
let mockInstance = MockAmplifyRtnPasskeys()
222+
223+
mockInstance.createPasskey(
224+
TestFixtures.validRpId,
225+
userId: TestFixtures.validUserId,
226+
userName: TestFixtures.validUserName,
227+
challenge: TestFixtures.validChallenge,
228+
excludeCredentials: TestFixtures
229+
.validExcludeCredentials, resolve: mockResolve, reject: mockReject)
230+
231+
XCTAssertTrue(rejectCalled)
232+
XCTAssertTrue(!resolveCalled)
233+
XCTAssertEqual(rejectErrorName, "SOME_ERROR")
234+
XCTAssertEqual(rejectErrorMessage, "some error message")
235+
}
236+
}

0 commit comments

Comments
 (0)
Please sign in to comment.