react-native-credentials-manager 0.5.3 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -1
- package/android/build.gradle +6 -5
- package/android/generated/java/com/credentialsmanager/NativeCredentialsManagerSpec.java +5 -1
- package/android/generated/jni/RNCredentialsManagerSpec-generated.cpp +7 -1
- package/android/generated/jni/react/renderer/components/RNCredentialsManagerSpec/RNCredentialsManagerSpecJSI-generated.cpp +8 -2
- package/android/generated/jni/react/renderer/components/RNCredentialsManagerSpec/RNCredentialsManagerSpecJSI.h +151 -3
- package/android/src/main/java/com/credentialsmanager/handlers/CredentialHandler.kt +59 -21
- package/android/src/newarch/java/com/credentialsmanager/CredentialsManagerModule.kt +28 -1
- package/android/src/oldarch/java/com/credentialsmanager/CredentialsManagerModule.kt +30 -1
- package/ios/CredentialsManager.h +8 -2
- package/ios/CredentialsManager.mm +396 -4
- package/ios/generated/RNCredentialsManagerSpec/RNCredentialsManagerSpec-generated.mm +14 -1
- package/ios/generated/RNCredentialsManagerSpec/RNCredentialsManagerSpec.h +38 -1
- package/ios/generated/RNCredentialsManagerSpecJSI-generated.cpp +8 -2
- package/ios/generated/RNCredentialsManagerSpecJSI.h +151 -3
- package/lib/commonjs/NativeCredentialsManager.js +6 -0
- package/lib/commonjs/NativeCredentialsManager.js.map +1 -1
- package/lib/commonjs/index.js +45 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeCredentialsManager.js +13 -0
- package/lib/module/NativeCredentialsManager.js.map +1 -1
- package/lib/module/index.js +44 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/NativeCredentialsManager.d.ts +49 -7
- package/lib/typescript/commonjs/src/NativeCredentialsManager.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +10 -4
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/NativeCredentialsManager.d.ts +49 -7
- package/lib/typescript/module/src/NativeCredentialsManager.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +10 -4
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-credentials-manager.podspec +4 -1
- package/react-native.config.js +0 -1
- package/src/NativeCredentialsManager.ts +67 -8
- package/src/index.tsx +81 -5
|
@@ -49,14 +49,34 @@ class CredentialsManagerModule(
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
override fun signUpWithPassword(credObject: ReadableMap) {
|
|
52
|
+
override fun signUpWithPassword(credObject: ReadableMap, promise: Promise) {
|
|
53
53
|
val username = credObject.getString("username") ?: ""
|
|
54
54
|
val password = credObject.getString("password") ?: ""
|
|
55
|
+
|
|
56
|
+
if (username.isEmpty()) {
|
|
57
|
+
promise.reject("INVALID_USERNAME", "Username cannot be empty")
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (password.isEmpty()) {
|
|
62
|
+
promise.reject("INVALID_PASSWORD", "Password cannot be empty")
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
coroutineScope.launch {
|
|
56
67
|
try {
|
|
57
68
|
credentialHandler.createPassword(username, password)
|
|
69
|
+
|
|
70
|
+
// Create success response
|
|
71
|
+
val result = mapOf(
|
|
72
|
+
"type" to "password",
|
|
73
|
+
"username" to username,
|
|
74
|
+
"success" to true
|
|
75
|
+
)
|
|
76
|
+
promise.resolve(result)
|
|
58
77
|
} catch (e: CreateCredentialException) {
|
|
59
78
|
ErrorHandler.handleCredentialError(e)
|
|
79
|
+
promise.reject("CREDENTIAL_ERROR", e.message.toString())
|
|
60
80
|
}
|
|
61
81
|
}
|
|
62
82
|
}
|
|
@@ -140,4 +160,11 @@ class CredentialsManagerModule(
|
|
|
140
160
|
}
|
|
141
161
|
}
|
|
142
162
|
}
|
|
163
|
+
|
|
164
|
+
override fun signUpWithApple(params: ReadableMap, promise: Promise) {
|
|
165
|
+
promise.reject(
|
|
166
|
+
"PLATFORM_NOT_SUPPORTED",
|
|
167
|
+
"Sign up with Apple is only supported on iOS devices"
|
|
168
|
+
)
|
|
169
|
+
}
|
|
143
170
|
}
|
|
@@ -55,14 +55,34 @@ class CredentialsManagerModule(
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
@ReactMethod
|
|
58
|
-
fun signUpWithPassword(credObject: ReadableMap) {
|
|
58
|
+
fun signUpWithPassword(credObject: ReadableMap, promise: Promise) {
|
|
59
59
|
val username = credObject.getString("username") ?: ""
|
|
60
60
|
val password = credObject.getString("password") ?: ""
|
|
61
|
+
|
|
62
|
+
if (username.isEmpty()) {
|
|
63
|
+
promise.reject("INVALID_USERNAME", "Username cannot be empty")
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (password.isEmpty()) {
|
|
68
|
+
promise.reject("INVALID_PASSWORD", "Password cannot be empty")
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
coroutineScope.launch {
|
|
62
73
|
try {
|
|
63
74
|
credentialHandler.createPassword(username, password)
|
|
75
|
+
|
|
76
|
+
// Create success response
|
|
77
|
+
val result = mapOf(
|
|
78
|
+
"type" to "password",
|
|
79
|
+
"username" to username,
|
|
80
|
+
"success" to true
|
|
81
|
+
)
|
|
82
|
+
promise.resolve(result)
|
|
64
83
|
} catch (e: CreateCredentialException) {
|
|
65
84
|
ErrorHandler.handleCredentialError(e)
|
|
85
|
+
promise.reject("CREDENTIAL_ERROR", e.message.toString())
|
|
66
86
|
}
|
|
67
87
|
}
|
|
68
88
|
}
|
|
@@ -161,4 +181,13 @@ class CredentialsManagerModule(
|
|
|
161
181
|
}
|
|
162
182
|
}
|
|
163
183
|
}
|
|
184
|
+
|
|
185
|
+
@ReactMethod
|
|
186
|
+
fun signUpWithApple(params: ReadableMap, promise: Promise) {
|
|
187
|
+
// Since this is an iOS-specific function, we just reject with an appropriate message on Android
|
|
188
|
+
promise.reject(
|
|
189
|
+
"PLATFORM_NOT_SUPPORTED",
|
|
190
|
+
"Sign up with Apple is only supported on iOS devices"
|
|
191
|
+
)
|
|
192
|
+
}
|
|
164
193
|
}
|
package/ios/CredentialsManager.h
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <AuthenticationServices/AuthenticationServices.h>
|
|
2
3
|
#import "generated/RNCredentialsManagerSpec/RNCredentialsManagerSpec.h"
|
|
3
4
|
|
|
4
|
-
@interface CredentialsManager : NSObject <NativeCredentialsManagerSpec>
|
|
5
|
+
@interface CredentialsManager : NSObject <NativeCredentialsManagerSpec, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
|
|
6
|
+
|
|
7
|
+
@property (nonatomic, strong) ASPresentationAnchor authenticationAnchor;
|
|
8
|
+
@property (nonatomic, copy) RCTPromiseResolveBlock currentResolve;
|
|
9
|
+
@property (nonatomic, copy) RCTPromiseRejectBlock currentReject;
|
|
10
|
+
@property (nonatomic, strong) NSString *relyingPartyIdentifier;
|
|
5
11
|
|
|
6
12
|
@end
|
|
@@ -1,18 +1,410 @@
|
|
|
1
1
|
#import "CredentialsManager.h"
|
|
2
|
+
#import <React/RCTUtils.h>
|
|
3
|
+
#import <React/RCTLog.h>
|
|
4
|
+
#import <AuthenticationServices/AuthenticationServices.h>
|
|
2
5
|
|
|
3
6
|
@implementation CredentialsManager
|
|
4
|
-
RCT_EXPORT_MODULE()
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
NSNumber *result = @(a * b);
|
|
8
|
+
RCT_EXPORT_MODULE()
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
- (instancetype)init {
|
|
11
|
+
self = [super init];
|
|
12
|
+
if (self) {
|
|
13
|
+
// Default relying party identifier - should be configurable
|
|
14
|
+
self.relyingPartyIdentifier = @"www.benjamineruvieru.com";
|
|
15
|
+
|
|
16
|
+
// Get the main window as the authentication anchor
|
|
17
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
18
|
+
UIWindow *keyWindow = nil;
|
|
19
|
+
for (UIWindowScene *windowScene in [UIApplication sharedApplication].connectedScenes) {
|
|
20
|
+
if (windowScene.activationState == UISceneActivationStateForegroundActive) {
|
|
21
|
+
for (UIWindow *window in windowScene.windows) {
|
|
22
|
+
if (window.isKeyWindow) {
|
|
23
|
+
keyWindow = window;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
self.authenticationAnchor = keyWindow;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return self;
|
|
10
33
|
}
|
|
11
34
|
|
|
35
|
+
#pragma mark - TurboModule
|
|
36
|
+
|
|
12
37
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
13
38
|
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
14
39
|
{
|
|
15
40
|
return std::make_shared<facebook::react::NativeCredentialsManagerSpecJSI>(params);
|
|
16
41
|
}
|
|
17
42
|
|
|
43
|
+
#pragma mark - NativeCredentialsManagerSpec
|
|
44
|
+
|
|
45
|
+
- (void)signUpWithPasskeys:(NSDictionary *)requestJson
|
|
46
|
+
preferImmediatelyAvailableCredentials:(BOOL)preferImmediatelyAvailableCredentials
|
|
47
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
48
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
49
|
+
|
|
50
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
51
|
+
self.currentResolve = resolve;
|
|
52
|
+
self.currentReject = reject;
|
|
53
|
+
|
|
54
|
+
// Extract challenge and user info from requestJson
|
|
55
|
+
NSString *challengeString = requestJson[@"challenge"];
|
|
56
|
+
NSDictionary *userInfo = requestJson[@"user"];
|
|
57
|
+
NSDictionary *rpInfo = requestJson[@"rp"];
|
|
58
|
+
|
|
59
|
+
if (!challengeString || !userInfo || !rpInfo) {
|
|
60
|
+
reject(@"INVALID_REQUEST", @"Missing required fields in request JSON", nil);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Decode base64 challenge
|
|
65
|
+
NSData *challenge = [[NSData alloc] initWithBase64EncodedString:challengeString options:0];
|
|
66
|
+
if (!challenge) {
|
|
67
|
+
reject(@"INVALID_CHALLENGE", @"Invalid base64 challenge", nil);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Extract user information
|
|
72
|
+
NSString *userName = userInfo[@"name"];
|
|
73
|
+
NSString *userIdString = userInfo[@"id"];
|
|
74
|
+
|
|
75
|
+
// Decode user ID
|
|
76
|
+
NSData *userId = [[NSData alloc] initWithBase64EncodedString:userIdString options:0];
|
|
77
|
+
if (!userId) {
|
|
78
|
+
// If not base64, use the string directly
|
|
79
|
+
userId = [userIdString dataUsingEncoding:NSUTF8StringEncoding];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Update relying party identifier
|
|
83
|
+
NSString *rpId = rpInfo[@"id"];
|
|
84
|
+
if (rpId) {
|
|
85
|
+
self.relyingPartyIdentifier = rpId;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Create the passkey registration request using Apple's Authentication Services
|
|
89
|
+
ASAuthorizationPlatformPublicKeyCredentialProvider *provider =
|
|
90
|
+
[[ASAuthorizationPlatformPublicKeyCredentialProvider alloc] initWithRelyingPartyIdentifier:self.relyingPartyIdentifier];
|
|
91
|
+
|
|
92
|
+
ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest *registrationRequest =
|
|
93
|
+
[provider createCredentialRegistrationRequestWithChallenge:challenge name:userName userID:userId];
|
|
94
|
+
|
|
95
|
+
// Configure the request
|
|
96
|
+
registrationRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired;
|
|
97
|
+
|
|
98
|
+
// Create and configure the authorization controller
|
|
99
|
+
ASAuthorizationController *authController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[registrationRequest]];
|
|
100
|
+
authController.delegate = self;
|
|
101
|
+
authController.presentationContextProvider = self;
|
|
102
|
+
|
|
103
|
+
// Perform the request
|
|
104
|
+
[authController performRequests];
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
- (void)signUpWithPassword:(JS::NativeCredentialsManager::CredObject &)credObject
|
|
109
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
110
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
111
|
+
// Apple's Authentication Services only supports AutoFill passwords, not manual credential storage
|
|
112
|
+
// Manual keychain storage is not part of Authentication Services framework
|
|
113
|
+
reject(@"UNSUPPORTED_OPERATION", @"Manual password storage is not supported. Use AutoFill passwords through signIn method instead.", nil);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
- (void)signIn:(NSArray *)options
|
|
117
|
+
params:(JS::NativeCredentialsManager::SpecSignInParams &)params
|
|
118
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
119
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
120
|
+
|
|
121
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
122
|
+
self.currentResolve = resolve;
|
|
123
|
+
self.currentReject = reject;
|
|
124
|
+
|
|
125
|
+
NSMutableArray<ASAuthorizationRequest *> *authRequests = [[NSMutableArray alloc] init];
|
|
126
|
+
|
|
127
|
+
for (NSString *option in options) {
|
|
128
|
+
if ([option isEqualToString:@"passkeys"]) {
|
|
129
|
+
// Extract passkeys parameters from the params object
|
|
130
|
+
id passkeyParams = params.passkeys();
|
|
131
|
+
if (!passkeyParams || ![passkeyParams isKindOfClass:[NSDictionary class]]) {
|
|
132
|
+
RCTLogError(@"Missing or invalid passkeys parameters");
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
NSDictionary *passkeyDict = (NSDictionary *)passkeyParams;
|
|
137
|
+
NSString *challengeString = passkeyDict[@"challenge"];
|
|
138
|
+
|
|
139
|
+
if (!challengeString || ![challengeString isKindOfClass:[NSString class]]) {
|
|
140
|
+
RCTLogError(@"Missing or invalid challenge in passkeys parameters");
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
NSData *challenge = [[NSData alloc] initWithBase64EncodedString:challengeString options:0];
|
|
145
|
+
|
|
146
|
+
if (challenge) {
|
|
147
|
+
// Update relying party identifier if provided
|
|
148
|
+
NSString *rpId = passkeyDict[@"rpId"];
|
|
149
|
+
if (rpId && [rpId isKindOfClass:[NSString class]]) {
|
|
150
|
+
self.relyingPartyIdentifier = rpId;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
ASAuthorizationPlatformPublicKeyCredentialProvider *provider =
|
|
154
|
+
[[ASAuthorizationPlatformPublicKeyCredentialProvider alloc] initWithRelyingPartyIdentifier:self.relyingPartyIdentifier];
|
|
155
|
+
|
|
156
|
+
ASAuthorizationPlatformPublicKeyCredentialAssertionRequest *assertionRequest =
|
|
157
|
+
[provider createCredentialAssertionRequestWithChallenge:challenge];
|
|
158
|
+
assertionRequest.userVerificationPreference = ASAuthorizationPublicKeyCredentialUserVerificationPreferenceRequired;
|
|
159
|
+
|
|
160
|
+
[authRequests addObject:assertionRequest];
|
|
161
|
+
} else {
|
|
162
|
+
RCTLogError(@"Failed to decode challenge for passkey authentication");
|
|
163
|
+
}
|
|
164
|
+
} else if ([option isEqualToString:@"password"]) {
|
|
165
|
+
// Use Apple's AutoFill password provider (not manual keychain)
|
|
166
|
+
ASAuthorizationPasswordProvider *passwordProvider = [[ASAuthorizationPasswordProvider alloc] init];
|
|
167
|
+
ASAuthorizationPasswordRequest *passwordRequest = [passwordProvider createRequest];
|
|
168
|
+
[authRequests addObject:passwordRequest];
|
|
169
|
+
} else if ([option isEqualToString:@"apple-signin"]) {
|
|
170
|
+
// Apple Sign In is supported by Authentication Services
|
|
171
|
+
ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
|
|
172
|
+
ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
|
|
173
|
+
|
|
174
|
+
// Default scopes - always use these
|
|
175
|
+
NSArray<ASAuthorizationScope> *defaultScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
|
|
176
|
+
appleIDRequest.requestedScopes = defaultScopes;
|
|
177
|
+
|
|
178
|
+
[authRequests addObject:appleIDRequest];
|
|
179
|
+
} else if ([option isEqualToString:@"google-signin"]) {
|
|
180
|
+
// Google Sign In is not part of Apple's Authentication Services framework
|
|
181
|
+
RCTLogError(@"Google Sign In is not supported on iOS. Use Apple Sign In instead.");
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (authRequests.count == 0) {
|
|
187
|
+
reject(@"NO_AUTH_METHODS", @"No valid authentication methods provided", nil);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Create and configure the authorization controller
|
|
192
|
+
ASAuthorizationController *authController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:authRequests];
|
|
193
|
+
authController.delegate = self;
|
|
194
|
+
authController.presentationContextProvider = self;
|
|
195
|
+
|
|
196
|
+
// Perform the request
|
|
197
|
+
[authController performRequests];
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
- (void)signUpWithGoogle:(JS::NativeCredentialsManager::GoogleSignInParams &)params
|
|
202
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
203
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
204
|
+
// Google Sign In is not part of Apple's Authentication Services framework
|
|
205
|
+
// This should be handled at the JavaScript layer for cross-platform compatibility
|
|
206
|
+
reject(@"UNSUPPORTED_OPERATION", @"Google Sign In is not available on iOS. Use Apple Sign In instead.", nil);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
- (void)signUpWithApple:(JS::NativeCredentialsManager::AppleSignInParams &)params
|
|
210
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
211
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
212
|
+
|
|
213
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
214
|
+
self.currentResolve = resolve;
|
|
215
|
+
self.currentReject = reject;
|
|
216
|
+
|
|
217
|
+
// Apple Sign In is officially supported by Authentication Services framework
|
|
218
|
+
ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
|
|
219
|
+
ASAuthorizationAppleIDRequest *appleIDRequest = [appleIDProvider createRequest];
|
|
220
|
+
|
|
221
|
+
// Always use the default scopes
|
|
222
|
+
appleIDRequest.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
|
|
223
|
+
|
|
224
|
+
ASAuthorizationController *authController = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[appleIDRequest]];
|
|
225
|
+
authController.delegate = self;
|
|
226
|
+
authController.presentationContextProvider = self;
|
|
227
|
+
|
|
228
|
+
[authController performRequests];
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
- (void)signOut:(RCTPromiseResolveBlock)resolve
|
|
233
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
234
|
+
// Apple's Authentication Services doesn't provide a direct sign-out method
|
|
235
|
+
// Sign-out is typically handled at the application level
|
|
236
|
+
// AutoFill passwords and passkeys are managed by the system
|
|
237
|
+
resolve([NSNull null]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#pragma mark - ASAuthorizationControllerDelegate
|
|
241
|
+
|
|
242
|
+
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization {
|
|
243
|
+
if (!self.currentResolve) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
RCTPromiseResolveBlock resolve = self.currentResolve;
|
|
248
|
+
self.currentReject = nil;
|
|
249
|
+
self.currentResolve = nil;
|
|
250
|
+
|
|
251
|
+
if ([authorization.credential isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialRegistration class]]) {
|
|
252
|
+
// Passkey registration - handled by Apple's Authentication Services
|
|
253
|
+
ASAuthorizationPlatformPublicKeyCredentialRegistration *registration = (ASAuthorizationPlatformPublicKeyCredentialRegistration *)authorization.credential;
|
|
254
|
+
|
|
255
|
+
NSDictionary *result = @{
|
|
256
|
+
@"id": registration.credentialID ? [registration.credentialID base64EncodedStringWithOptions:0] : @"",
|
|
257
|
+
@"rawId": registration.credentialID ? [registration.credentialID base64EncodedStringWithOptions:0] : @"",
|
|
258
|
+
@"response": @{
|
|
259
|
+
@"attestationObject": registration.rawAttestationObject ? [registration.rawAttestationObject base64EncodedStringWithOptions:0] : @"",
|
|
260
|
+
@"clientDataJSON": registration.rawClientDataJSON ? [registration.rawClientDataJSON base64EncodedStringWithOptions:0] : @""
|
|
261
|
+
},
|
|
262
|
+
@"type": @"public-key"
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
resolve(result);
|
|
266
|
+
return;
|
|
267
|
+
} else if ([authorization.credential isKindOfClass:[ASAuthorizationPlatformPublicKeyCredentialAssertion class]]) {
|
|
268
|
+
// Passkey authentication - handled by Apple's Authentication Services
|
|
269
|
+
ASAuthorizationPlatformPublicKeyCredentialAssertion *assertion = (ASAuthorizationPlatformPublicKeyCredentialAssertion *)authorization.credential;
|
|
270
|
+
|
|
271
|
+
NSDictionary *result = @{
|
|
272
|
+
@"type": @"passkey",
|
|
273
|
+
@"authenticationResponseJson": [self createAuthenticationResponseJSON:assertion]
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
resolve(result);
|
|
277
|
+
return;
|
|
278
|
+
} else if ([authorization.credential isKindOfClass:[ASPasswordCredential class]]) {
|
|
279
|
+
// AutoFill password authentication - handled by Apple's Authentication Services
|
|
280
|
+
ASPasswordCredential *passwordCredential = (ASPasswordCredential *)authorization.credential;
|
|
281
|
+
|
|
282
|
+
NSDictionary *result = @{
|
|
283
|
+
@"type": @"password",
|
|
284
|
+
@"username": passwordCredential.user,
|
|
285
|
+
@"password": passwordCredential.password
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
resolve(result);
|
|
289
|
+
return;
|
|
290
|
+
} else if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
|
|
291
|
+
// Apple Sign In - officially supported by Authentication Services
|
|
292
|
+
ASAuthorizationAppleIDCredential *appleIDCredential = (ASAuthorizationAppleIDCredential *)authorization.credential;
|
|
293
|
+
|
|
294
|
+
NSMutableDictionary *result = [@{
|
|
295
|
+
@"type": @"apple-signin",
|
|
296
|
+
@"id": appleIDCredential.user,
|
|
297
|
+
@"idToken": appleIDCredential.identityToken ? [[NSString alloc] initWithData:appleIDCredential.identityToken encoding:NSUTF8StringEncoding] : @""
|
|
298
|
+
} mutableCopy];
|
|
299
|
+
|
|
300
|
+
if (appleIDCredential.fullName) {
|
|
301
|
+
if (appleIDCredential.fullName.givenName) {
|
|
302
|
+
result[@"givenName"] = appleIDCredential.fullName.givenName;
|
|
303
|
+
}
|
|
304
|
+
if (appleIDCredential.fullName.familyName) {
|
|
305
|
+
result[@"familyName"] = appleIDCredential.fullName.familyName;
|
|
306
|
+
}
|
|
307
|
+
if (appleIDCredential.fullName.givenName || appleIDCredential.fullName.familyName) {
|
|
308
|
+
NSString *fullName = [NSString stringWithFormat:@"%@ %@",
|
|
309
|
+
appleIDCredential.fullName.givenName ?: @"",
|
|
310
|
+
appleIDCredential.fullName.familyName ?: @""];
|
|
311
|
+
result[@"displayName"] = [fullName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (appleIDCredential.email) {
|
|
316
|
+
result[@"email"] = appleIDCredential.email;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
resolve([result copy]);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// If we reach here, we couldn't handle the credential type
|
|
324
|
+
resolve(@{@"type": @"unknown"});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error {
|
|
328
|
+
if (!self.currentReject) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
RCTPromiseRejectBlock reject = self.currentReject;
|
|
333
|
+
self.currentResolve = nil;
|
|
334
|
+
self.currentReject = nil;
|
|
335
|
+
|
|
336
|
+
NSString *errorCode = @"UNKNOWN_ERROR";
|
|
337
|
+
NSString *errorMessage = error.localizedDescription;
|
|
338
|
+
|
|
339
|
+
if ([error.domain isEqualToString:ASAuthorizationErrorDomain]) {
|
|
340
|
+
switch (error.code) {
|
|
341
|
+
case ASAuthorizationErrorCanceled:
|
|
342
|
+
errorCode = @"USER_CANCELED";
|
|
343
|
+
errorMessage = @"User canceled the authorization request";
|
|
344
|
+
break;
|
|
345
|
+
case ASAuthorizationErrorFailed:
|
|
346
|
+
errorCode = @"AUTHORIZATION_FAILED";
|
|
347
|
+
break;
|
|
348
|
+
case ASAuthorizationErrorInvalidResponse:
|
|
349
|
+
errorCode = @"INVALID_RESPONSE";
|
|
350
|
+
break;
|
|
351
|
+
case ASAuthorizationErrorNotHandled:
|
|
352
|
+
errorCode = @"NOT_HANDLED";
|
|
353
|
+
break;
|
|
354
|
+
case ASAuthorizationErrorUnknown:
|
|
355
|
+
errorCode = @"UNKNOWN_ERROR";
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
reject(errorCode, errorMessage, error);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
#pragma mark - ASAuthorizationControllerPresentationContextProviding
|
|
364
|
+
|
|
365
|
+
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller {
|
|
366
|
+
if (self.authenticationAnchor) {
|
|
367
|
+
return self.authenticationAnchor;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Fallback to finding a key window
|
|
371
|
+
UIWindow *keyWindow = nil;
|
|
372
|
+
for (UIWindowScene *windowScene in [UIApplication sharedApplication].connectedScenes) {
|
|
373
|
+
if (windowScene.activationState == UISceneActivationStateForegroundActive) {
|
|
374
|
+
for (UIWindow *window in windowScene.windows) {
|
|
375
|
+
if (window.isKeyWindow) {
|
|
376
|
+
keyWindow = window;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return keyWindow ?: [[UIApplication sharedApplication] windows].firstObject;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#pragma mark - Helper Methods
|
|
387
|
+
|
|
388
|
+
- (NSString *)createAuthenticationResponseJSON:(ASAuthorizationPlatformPublicKeyCredentialAssertion *)assertion {
|
|
389
|
+
NSDictionary *response = @{
|
|
390
|
+
@"id": assertion.credentialID ? [assertion.credentialID base64EncodedStringWithOptions:0] : @"",
|
|
391
|
+
@"rawId": assertion.credentialID ? [assertion.credentialID base64EncodedStringWithOptions:0] : @"",
|
|
392
|
+
@"response": @{
|
|
393
|
+
@"authenticatorData": assertion.rawAuthenticatorData ? [assertion.rawAuthenticatorData base64EncodedStringWithOptions:0] : @"",
|
|
394
|
+
@"clientDataJSON": assertion.rawClientDataJSON ? [assertion.rawClientDataJSON base64EncodedStringWithOptions:0] : @"",
|
|
395
|
+
@"signature": assertion.signature ? [assertion.signature base64EncodedStringWithOptions:0] : @"",
|
|
396
|
+
@"userHandle": assertion.userID ? [assertion.userID base64EncodedStringWithOptions:0] : @""
|
|
397
|
+
},
|
|
398
|
+
@"type": @"public-key"
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
NSError *error;
|
|
402
|
+
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response options:0 error:&error];
|
|
403
|
+
if (error) {
|
|
404
|
+
return @"{}";
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
408
|
+
}
|
|
409
|
+
|
|
18
410
|
@end
|
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
return facebook::react::managedPointer<JS::NativeCredentialsManager::GoogleSignInParams>(json);
|
|
36
36
|
}
|
|
37
37
|
@end
|
|
38
|
+
@implementation RCTCxxConvert (NativeCredentialsManager_AppleSignInParams)
|
|
39
|
+
+ (RCTManagedPointer *)JS_NativeCredentialsManager_AppleSignInParams:(id)json
|
|
40
|
+
{
|
|
41
|
+
return facebook::react::managedPointer<JS::NativeCredentialsManager::AppleSignInParams>(json);
|
|
42
|
+
}
|
|
43
|
+
@end
|
|
38
44
|
@implementation RCTCxxConvert (NativeCredentialsManager_SpecSignInParams)
|
|
39
45
|
+ (RCTManagedPointer *)JS_NativeCredentialsManager_SpecSignInParams:(id)json
|
|
40
46
|
{
|
|
@@ -48,7 +54,7 @@ namespace facebook::react {
|
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithPassword(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
51
|
-
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt,
|
|
57
|
+
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "signUpWithPassword", @selector(signUpWithPassword:resolve:reject:), args, count);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signIn(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
@@ -59,6 +65,10 @@ namespace facebook::react {
|
|
|
59
65
|
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "signUpWithGoogle", @selector(signUpWithGoogle:resolve:reject:), args, count);
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithApple(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
69
|
+
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "signUpWithApple", @selector(signUpWithApple:resolve:reject:), args, count);
|
|
70
|
+
}
|
|
71
|
+
|
|
62
72
|
static facebook::jsi::Value __hostFunction_NativeCredentialsManagerSpecJSI_signOut(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
63
73
|
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "signOut", @selector(signOut:reject:), args, count);
|
|
64
74
|
}
|
|
@@ -78,6 +88,9 @@ namespace facebook::react {
|
|
|
78
88
|
methodMap_["signUpWithGoogle"] = MethodMetadata {1, __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithGoogle};
|
|
79
89
|
setMethodArgConversionSelector(@"signUpWithGoogle", 0, @"JS_NativeCredentialsManager_GoogleSignInParams:");
|
|
80
90
|
|
|
91
|
+
methodMap_["signUpWithApple"] = MethodMetadata {1, __hostFunction_NativeCredentialsManagerSpecJSI_signUpWithApple};
|
|
92
|
+
setMethodArgConversionSelector(@"signUpWithApple", 0, @"JS_NativeCredentialsManager_AppleSignInParams:");
|
|
93
|
+
|
|
81
94
|
methodMap_["signOut"] = MethodMetadata {0, __hostFunction_NativeCredentialsManagerSpecJSI_signOut};
|
|
82
95
|
|
|
83
96
|
}
|
|
@@ -65,11 +65,28 @@ namespace JS {
|
|
|
65
65
|
@interface RCTCxxConvert (NativeCredentialsManager_GoogleSignInParams)
|
|
66
66
|
+ (RCTManagedPointer *)JS_NativeCredentialsManager_GoogleSignInParams:(id)json;
|
|
67
67
|
@end
|
|
68
|
+
namespace JS {
|
|
69
|
+
namespace NativeCredentialsManager {
|
|
70
|
+
struct AppleSignInParams {
|
|
71
|
+
NSString *nonce() const;
|
|
72
|
+
facebook::react::LazyVector<NSString *> requestedScopes() const;
|
|
73
|
+
|
|
74
|
+
AppleSignInParams(NSDictionary *const v) : _v(v) {}
|
|
75
|
+
private:
|
|
76
|
+
NSDictionary *_v;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@interface RCTCxxConvert (NativeCredentialsManager_AppleSignInParams)
|
|
82
|
+
+ (RCTManagedPointer *)JS_NativeCredentialsManager_AppleSignInParams:(id)json;
|
|
83
|
+
@end
|
|
68
84
|
namespace JS {
|
|
69
85
|
namespace NativeCredentialsManager {
|
|
70
86
|
struct SpecSignInParams {
|
|
71
87
|
id<NSObject> _Nullable passkeys() const;
|
|
72
88
|
std::optional<JS::NativeCredentialsManager::GoogleSignInParams> googleSignIn() const;
|
|
89
|
+
std::optional<JS::NativeCredentialsManager::AppleSignInParams> appleSignIn() const;
|
|
73
90
|
|
|
74
91
|
SpecSignInParams(NSDictionary *const v) : _v(v) {}
|
|
75
92
|
private:
|
|
@@ -87,7 +104,9 @@ namespace JS {
|
|
|
87
104
|
preferImmediatelyAvailableCredentials:(BOOL)preferImmediatelyAvailableCredentials
|
|
88
105
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
89
106
|
reject:(RCTPromiseRejectBlock)reject;
|
|
90
|
-
- (void)signUpWithPassword:(JS::NativeCredentialsManager::CredObject &)credObject
|
|
107
|
+
- (void)signUpWithPassword:(JS::NativeCredentialsManager::CredObject &)credObject
|
|
108
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
109
|
+
reject:(RCTPromiseRejectBlock)reject;
|
|
91
110
|
- (void)signIn:(NSArray *)options
|
|
92
111
|
params:(JS::NativeCredentialsManager::SpecSignInParams &)params
|
|
93
112
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
@@ -95,6 +114,9 @@ preferImmediatelyAvailableCredentials:(BOOL)preferImmediatelyAvailableCredential
|
|
|
95
114
|
- (void)signUpWithGoogle:(JS::NativeCredentialsManager::GoogleSignInParams &)params
|
|
96
115
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
97
116
|
reject:(RCTPromiseRejectBlock)reject;
|
|
117
|
+
- (void)signUpWithApple:(JS::NativeCredentialsManager::AppleSignInParams &)params
|
|
118
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
119
|
+
reject:(RCTPromiseRejectBlock)reject;
|
|
98
120
|
- (void)signOut:(RCTPromiseResolveBlock)resolve
|
|
99
121
|
reject:(RCTPromiseRejectBlock)reject;
|
|
100
122
|
|
|
@@ -143,6 +165,16 @@ inline bool JS::NativeCredentialsManager::GoogleSignInParams::autoSelectEnabled(
|
|
|
143
165
|
id const p = _v[@"autoSelectEnabled"];
|
|
144
166
|
return RCTBridgingToBool(p);
|
|
145
167
|
}
|
|
168
|
+
inline NSString *JS::NativeCredentialsManager::AppleSignInParams::nonce() const
|
|
169
|
+
{
|
|
170
|
+
id const p = _v[@"nonce"];
|
|
171
|
+
return RCTBridgingToString(p);
|
|
172
|
+
}
|
|
173
|
+
inline facebook::react::LazyVector<NSString *> JS::NativeCredentialsManager::AppleSignInParams::requestedScopes() const
|
|
174
|
+
{
|
|
175
|
+
id const p = _v[@"requestedScopes"];
|
|
176
|
+
return RCTBridgingToVec(p, ^NSString *(id itemValue_0) { return RCTBridgingToString(itemValue_0); });
|
|
177
|
+
}
|
|
146
178
|
inline id<NSObject> _Nullable JS::NativeCredentialsManager::SpecSignInParams::passkeys() const
|
|
147
179
|
{
|
|
148
180
|
id const p = _v[@"passkeys"];
|
|
@@ -153,5 +185,10 @@ inline std::optional<JS::NativeCredentialsManager::GoogleSignInParams> JS::Nativ
|
|
|
153
185
|
id const p = _v[@"googleSignIn"];
|
|
154
186
|
return (p == nil ? std::nullopt : std::make_optional(JS::NativeCredentialsManager::GoogleSignInParams(p)));
|
|
155
187
|
}
|
|
188
|
+
inline std::optional<JS::NativeCredentialsManager::AppleSignInParams> JS::NativeCredentialsManager::SpecSignInParams::appleSignIn() const
|
|
189
|
+
{
|
|
190
|
+
id const p = _v[@"appleSignIn"];
|
|
191
|
+
return (p == nil ? std::nullopt : std::make_optional(JS::NativeCredentialsManager::AppleSignInParams(p)));
|
|
192
|
+
}
|
|
156
193
|
NS_ASSUME_NONNULL_END
|
|
157
194
|
#endif // RNCredentialsManagerSpec_H
|