react-native-nitro-auth 0.5.3 → 0.5.5
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 +60 -30
- package/android/build.gradle +2 -5
- package/android/src/main/cpp/JniOnLoad.cpp +3 -1
- package/android/src/main/cpp/PlatformAuth+Android.cpp +95 -29
- package/android/src/main/java/com/auth/AuthAdapter.kt +124 -126
- package/android/src/main/java/com/auth/NitroAuthModule.kt +8 -1
- package/cpp/AuthCache.cpp +0 -44
- package/cpp/AuthCache.hpp +0 -7
- package/cpp/HybridAuth.cpp +20 -2
- package/cpp/HybridAuth.hpp +1 -0
- package/ios/AuthAdapter.swift +64 -28
- package/lib/commonjs/Auth.web.js +96 -43
- package/lib/commonjs/Auth.web.js.map +1 -1
- package/lib/commonjs/index.js +23 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/service.js +33 -8
- package/lib/commonjs/service.js.map +1 -1
- package/lib/commonjs/use-auth.js +51 -54
- package/lib/commonjs/use-auth.js.map +1 -1
- package/lib/commonjs/utils/auth-error.js +37 -0
- package/lib/commonjs/utils/auth-error.js.map +1 -0
- package/lib/module/Auth.web.js +96 -43
- package/lib/module/Auth.web.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/service.js +33 -8
- package/lib/module/service.js.map +1 -1
- package/lib/module/use-auth.js +51 -54
- package/lib/module/use-auth.js.map +1 -1
- package/lib/module/utils/auth-error.js +30 -0
- package/lib/module/utils/auth-error.js.map +1 -0
- package/lib/typescript/commonjs/Auth.web.d.ts +7 -0
- package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/service.d.ts.map +1 -1
- package/lib/typescript/commonjs/use-auth.d.ts +2 -1
- package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/auth-error.d.ts +16 -0
- package/lib/typescript/commonjs/utils/auth-error.d.ts.map +1 -0
- package/lib/typescript/module/Auth.web.d.ts +7 -0
- package/lib/typescript/module/Auth.web.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/service.d.ts.map +1 -1
- package/lib/typescript/module/use-auth.d.ts +2 -1
- package/lib/typescript/module/use-auth.d.ts.map +1 -1
- package/lib/typescript/module/utils/auth-error.d.ts +16 -0
- package/lib/typescript/module/utils/auth-error.d.ts.map +1 -0
- package/nitrogen/generated/android/NitroAuthOnLoad.cpp +22 -17
- package/nitrogen/generated/android/NitroAuthOnLoad.hpp +13 -4
- package/package.json +7 -7
- package/react-native-nitro-auth.podspec +1 -1
- package/src/Auth.web.ts +124 -50
- package/src/index.ts +1 -0
- package/src/service.ts +34 -8
- package/src/use-auth.ts +81 -114
- package/src/utils/auth-error.ts +49 -0
- package/ios/KeychainStore.swift +0 -43
package/cpp/AuthCache.cpp
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
#include "AuthCache.hpp"
|
|
2
2
|
|
|
3
|
-
#ifdef __APPLE__
|
|
4
|
-
#include <CoreFoundation/CoreFoundation.h>
|
|
5
|
-
#include <Security/Security.h>
|
|
6
|
-
#endif
|
|
7
|
-
|
|
8
3
|
#ifdef __ANDROID__
|
|
9
4
|
#include <jni.h>
|
|
10
5
|
#include <fbjni/fbjni.h>
|
|
@@ -12,34 +7,10 @@
|
|
|
12
7
|
|
|
13
8
|
namespace margelo::nitro::NitroAuth {
|
|
14
9
|
|
|
15
|
-
#ifdef __APPLE__
|
|
16
|
-
static std::string sInMemoryUserJson;
|
|
17
|
-
|
|
18
|
-
void AuthCache::setUserJson(const std::string& json) {
|
|
19
|
-
sInMemoryUserJson = json;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
std::optional<std::string> AuthCache::getUserJson() {
|
|
23
|
-
if (sInMemoryUserJson.empty()) {
|
|
24
|
-
return std::nullopt;
|
|
25
|
-
}
|
|
26
|
-
return sInMemoryUserJson;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
void AuthCache::clear() {
|
|
30
|
-
sInMemoryUserJson.clear();
|
|
31
|
-
}
|
|
32
|
-
#endif
|
|
33
|
-
|
|
34
10
|
#ifdef __ANDROID__
|
|
35
11
|
using namespace facebook::jni;
|
|
36
12
|
|
|
37
|
-
struct JContext : JavaClass<JContext> {
|
|
38
|
-
static constexpr auto kJavaDescriptor = "Landroid/content/Context;";
|
|
39
|
-
};
|
|
40
|
-
|
|
41
13
|
static facebook::jni::global_ref<jobject> gContext;
|
|
42
|
-
static std::string sInMemoryUserJson;
|
|
43
14
|
|
|
44
15
|
void AuthCache::setAndroidContext(void* context) {
|
|
45
16
|
gContext = facebook::jni::make_global(static_cast<jobject>(context));
|
|
@@ -48,21 +19,6 @@ void AuthCache::setAndroidContext(void* context) {
|
|
|
48
19
|
void* AuthCache::getAndroidContext() {
|
|
49
20
|
return gContext.get();
|
|
50
21
|
}
|
|
51
|
-
|
|
52
|
-
void AuthCache::setUserJson(const std::string& json) {
|
|
53
|
-
sInMemoryUserJson = json;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
std::optional<std::string> AuthCache::getUserJson() {
|
|
57
|
-
if (sInMemoryUserJson.empty()) {
|
|
58
|
-
return std::nullopt;
|
|
59
|
-
}
|
|
60
|
-
return sInMemoryUserJson;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
void AuthCache::clear() {
|
|
64
|
-
sInMemoryUserJson.clear();
|
|
65
|
-
}
|
|
66
22
|
#endif
|
|
67
23
|
|
|
68
24
|
} // namespace margelo::nitro::NitroAuth
|
package/cpp/AuthCache.hpp
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
#include <string>
|
|
4
|
-
#include <optional>
|
|
5
|
-
|
|
6
3
|
namespace margelo::nitro::NitroAuth {
|
|
7
4
|
|
|
8
5
|
class AuthCache {
|
|
9
6
|
public:
|
|
10
|
-
static void setUserJson(const std::string& json);
|
|
11
|
-
static std::optional<std::string> getUserJson();
|
|
12
|
-
static void clear();
|
|
13
|
-
|
|
14
7
|
#ifdef __ANDROID__
|
|
15
8
|
static void setAndroidContext(void* context);
|
|
16
9
|
static void* getAndroidContext();
|
package/cpp/HybridAuth.cpp
CHANGED
|
@@ -209,7 +209,16 @@ std::shared_ptr<Promise<std::optional<std::string>>> HybridAuth::getAccessToken(
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
|
|
212
|
-
|
|
212
|
+
std::shared_ptr<Promise<AuthTokens>> promise;
|
|
213
|
+
{
|
|
214
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
215
|
+
if (_refreshInFlight) {
|
|
216
|
+
return _refreshInFlight;
|
|
217
|
+
}
|
|
218
|
+
promise = Promise<AuthTokens>::create();
|
|
219
|
+
_refreshInFlight = promise;
|
|
220
|
+
}
|
|
221
|
+
|
|
213
222
|
auto refreshPromise = PlatformAuth::refreshToken();
|
|
214
223
|
refreshPromise->addOnResolvedListener([this, promise](const AuthTokens& tokens) {
|
|
215
224
|
{
|
|
@@ -228,13 +237,22 @@ std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
|
|
|
228
237
|
_currentUser->expirationTime = tokens.expirationTime;
|
|
229
238
|
}
|
|
230
239
|
}
|
|
240
|
+
if (_refreshInFlight == promise) {
|
|
241
|
+
_refreshInFlight = nullptr;
|
|
242
|
+
}
|
|
231
243
|
}
|
|
232
244
|
notifyTokensRefreshed(tokens);
|
|
233
245
|
notifyAuthStateChanged();
|
|
234
246
|
promise->resolve(tokens);
|
|
235
247
|
});
|
|
236
248
|
|
|
237
|
-
refreshPromise->addOnRejectedListener([promise](const std::exception_ptr& error) {
|
|
249
|
+
refreshPromise->addOnRejectedListener([this, promise](const std::exception_ptr& error) {
|
|
250
|
+
{
|
|
251
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
252
|
+
if (_refreshInFlight == promise) {
|
|
253
|
+
_refreshInFlight = nullptr;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
238
256
|
promise->reject(error);
|
|
239
257
|
});
|
|
240
258
|
return promise;
|
package/cpp/HybridAuth.hpp
CHANGED
package/ios/AuthAdapter.swift
CHANGED
|
@@ -114,37 +114,43 @@ public class AuthAdapter: NSObject {
|
|
|
114
114
|
DispatchQueue.main.async {
|
|
115
115
|
let session = ASWebAuthenticationSession(url: authUrl, callbackURLScheme: callbackScheme) { callbackURL, error in
|
|
116
116
|
if let error = error {
|
|
117
|
-
|
|
117
|
+
let nsError = error as NSError
|
|
118
|
+
if nsError.code == ASWebAuthenticationSessionError.canceledLogin.rawValue {
|
|
118
119
|
completion(nil, "cancelled")
|
|
120
|
+
} else if nsError.domain.lowercased().contains("network") || nsError.code == NSURLErrorNotConnectedToInternet {
|
|
121
|
+
completion(nil, "network_error")
|
|
119
122
|
} else {
|
|
120
|
-
completion(nil,
|
|
123
|
+
completion(nil, "unknown")
|
|
121
124
|
}
|
|
122
125
|
return
|
|
123
126
|
}
|
|
124
|
-
|
|
127
|
+
|
|
125
128
|
guard let callbackURL = callbackURL,
|
|
126
129
|
let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false) else {
|
|
127
|
-
completion(nil, "
|
|
130
|
+
completion(nil, "unknown")
|
|
128
131
|
return
|
|
129
132
|
}
|
|
130
|
-
|
|
133
|
+
|
|
131
134
|
var params: [String: String] = [:]
|
|
132
135
|
for item in components.queryItems ?? [] {
|
|
133
136
|
params[item.name] = item.value
|
|
134
137
|
}
|
|
135
|
-
|
|
138
|
+
|
|
136
139
|
if let errorCode = params["error"] {
|
|
137
|
-
|
|
140
|
+
// OAuth error codes are already structured (e.g. "access_denied").
|
|
141
|
+
// Map well-known ones; fall back to "unknown".
|
|
142
|
+
let mapped = mapOAuthError(errorCode)
|
|
143
|
+
completion(nil, mapped)
|
|
138
144
|
return
|
|
139
145
|
}
|
|
140
|
-
|
|
146
|
+
|
|
141
147
|
guard let returnedState = params["state"], returnedState == state else {
|
|
142
|
-
completion(nil, "
|
|
148
|
+
completion(nil, "invalid_state")
|
|
143
149
|
return
|
|
144
150
|
}
|
|
145
|
-
|
|
151
|
+
|
|
146
152
|
guard let code = params["code"] else {
|
|
147
|
-
completion(nil, "
|
|
153
|
+
completion(nil, "unknown")
|
|
148
154
|
return
|
|
149
155
|
}
|
|
150
156
|
|
|
@@ -230,30 +236,34 @@ public class AuthAdapter: NSObject {
|
|
|
230
236
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
231
237
|
DispatchQueue.main.async {
|
|
232
238
|
if let error = error {
|
|
233
|
-
|
|
239
|
+
let nsError = error as NSError
|
|
240
|
+
if nsError.code == NSURLErrorNotConnectedToInternet || nsError.code == NSURLErrorTimedOut {
|
|
241
|
+
completion(nil, "network_error")
|
|
242
|
+
} else {
|
|
243
|
+
completion(nil, "network_error")
|
|
244
|
+
}
|
|
234
245
|
return
|
|
235
246
|
}
|
|
236
|
-
|
|
247
|
+
|
|
237
248
|
guard let data = data,
|
|
238
249
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
239
|
-
completion(nil, "
|
|
250
|
+
completion(nil, "parse_error")
|
|
240
251
|
return
|
|
241
252
|
}
|
|
242
|
-
|
|
253
|
+
|
|
243
254
|
if let errorCode = json["error"] as? String {
|
|
244
|
-
|
|
245
|
-
completion(nil, desc)
|
|
255
|
+
completion(nil, mapOAuthError(errorCode))
|
|
246
256
|
return
|
|
247
257
|
}
|
|
248
|
-
|
|
258
|
+
|
|
249
259
|
guard let idToken = json["id_token"] as? String else {
|
|
250
|
-
completion(nil, "
|
|
260
|
+
completion(nil, "no_id_token")
|
|
251
261
|
return
|
|
252
262
|
}
|
|
253
|
-
|
|
263
|
+
|
|
254
264
|
let claims = decodeJwt(idToken)
|
|
255
265
|
guard claims["nonce"] == expectedNonce else {
|
|
256
|
-
completion(nil, "
|
|
266
|
+
completion(nil, "invalid_nonce")
|
|
257
267
|
return
|
|
258
268
|
}
|
|
259
269
|
|
|
@@ -288,6 +298,8 @@ public class AuthAdapter: NSObject {
|
|
|
288
298
|
guard parts.count >= 2 else { return [:] }
|
|
289
299
|
|
|
290
300
|
var base64 = parts[1]
|
|
301
|
+
.replacingOccurrences(of: "-", with: "+")
|
|
302
|
+
.replacingOccurrences(of: "_", with: "/")
|
|
291
303
|
let remainder = base64.count % 4
|
|
292
304
|
if remainder > 0 {
|
|
293
305
|
base64 += String(repeating: "=", count: 4 - remainder)
|
|
@@ -309,7 +321,7 @@ public class AuthAdapter: NSObject {
|
|
|
309
321
|
|
|
310
322
|
private static func handleGoogleResult(_ result: GIDSignInResult?, error: Error?, completion: @escaping (NSDictionary?, String?) -> Void) {
|
|
311
323
|
if let error = error {
|
|
312
|
-
completion(nil, error
|
|
324
|
+
completion(nil, mapError(error))
|
|
313
325
|
return
|
|
314
326
|
}
|
|
315
327
|
|
|
@@ -337,15 +349,39 @@ public class AuthAdapter: NSObject {
|
|
|
337
349
|
|
|
338
350
|
static func mapError(_ error: Error) -> String {
|
|
339
351
|
let nsError = error as NSError
|
|
352
|
+
// GIDSignIn error codes
|
|
340
353
|
if nsError.domain == "com.google.GIDSignIn" {
|
|
341
|
-
|
|
354
|
+
switch nsError.code {
|
|
355
|
+
case -5: return "cancelled" // GIDSignInErrorCodeCanceled
|
|
356
|
+
case -4: return "network_error" // GIDSignInErrorCodeNoCurrentUser (used for network issues)
|
|
357
|
+
default: break
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// ASAuthorizationError codes (Apple Sign-In / ASWebAuthenticationSession)
|
|
361
|
+
if nsError.domain == ASAuthorizationError.errorDomain {
|
|
362
|
+
switch nsError.code {
|
|
363
|
+
case ASAuthorizationError.canceled.rawValue: return "cancelled"
|
|
364
|
+
case ASAuthorizationError.invalidResponse.rawValue: return "configuration_error"
|
|
365
|
+
default: return "unknown"
|
|
366
|
+
}
|
|
342
367
|
}
|
|
343
368
|
let msg = error.localizedDescription.lowercased()
|
|
344
369
|
if msg.contains("cancel") { return "cancelled" }
|
|
345
|
-
if msg.contains("network") { return "network_error" }
|
|
370
|
+
if msg.contains("network") || msg.contains("internet") || msg.contains("offline") { return "network_error" }
|
|
346
371
|
return "unknown"
|
|
347
372
|
}
|
|
348
373
|
|
|
374
|
+
/// Maps OAuth 2.0 error codes (returned in query params or JSON) to AuthErrorCode values.
|
|
375
|
+
private static func mapOAuthError(_ oauthCode: String) -> String {
|
|
376
|
+
switch oauthCode {
|
|
377
|
+
case "access_denied": return "cancelled"
|
|
378
|
+
case "invalid_client", "unauthorized_client", "invalid_scope": return "configuration_error"
|
|
379
|
+
case "invalid_grant", "invalid_request": return "token_error"
|
|
380
|
+
case "temporarily_unavailable", "server_error": return "network_error"
|
|
381
|
+
default: return "unknown"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
349
385
|
@objc
|
|
350
386
|
public static func addScopes(scopes: [String], completion: @escaping (NSDictionary?, String?) -> Void) {
|
|
351
387
|
if let currentUser = GIDSignIn.sharedInstance.currentUser {
|
|
@@ -512,16 +548,16 @@ public class AuthAdapter: NSObject {
|
|
|
512
548
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
513
549
|
DispatchQueue.main.async {
|
|
514
550
|
if let error = error {
|
|
515
|
-
completion(nil,
|
|
551
|
+
completion(nil, "network_error")
|
|
516
552
|
return
|
|
517
553
|
}
|
|
518
554
|
guard let data = data,
|
|
519
555
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
520
|
-
completion(nil, "
|
|
556
|
+
completion(nil, "parse_error")
|
|
521
557
|
return
|
|
522
558
|
}
|
|
523
559
|
if let errorCode = json["error"] as? String {
|
|
524
|
-
completion(nil, (
|
|
560
|
+
completion(nil, AuthAdapter.mapOAuthError(errorCode))
|
|
525
561
|
return
|
|
526
562
|
}
|
|
527
563
|
let idToken = json["id_token"] as? String ?? ""
|
|
@@ -593,7 +629,7 @@ class AppleSignInDelegate: NSObject, ASAuthorizationControllerDelegate {
|
|
|
593
629
|
}
|
|
594
630
|
|
|
595
631
|
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
|
|
596
|
-
completion(nil, error
|
|
632
|
+
completion(nil, AuthAdapter.mapError(error))
|
|
597
633
|
}
|
|
598
634
|
}
|
|
599
635
|
|
package/lib/commonjs/Auth.web.js
CHANGED
|
@@ -91,7 +91,9 @@ class AuthWeb {
|
|
|
91
91
|
_grantedScopes = [];
|
|
92
92
|
_listeners = [];
|
|
93
93
|
_tokenListeners = [];
|
|
94
|
+
_browserStorageResolved = false;
|
|
94
95
|
constructor() {
|
|
96
|
+
this._config = getConfig();
|
|
95
97
|
this.loadFromCache();
|
|
96
98
|
}
|
|
97
99
|
isPromiseLike(value) {
|
|
@@ -127,21 +129,27 @@ class AuthWeb {
|
|
|
127
129
|
if (this._storageAdapter) {
|
|
128
130
|
return true;
|
|
129
131
|
}
|
|
130
|
-
return
|
|
132
|
+
return this._config.nitroAuthPersistTokensOnWeb === true;
|
|
131
133
|
}
|
|
132
134
|
getWebStorageMode() {
|
|
133
|
-
const configuredMode =
|
|
135
|
+
const configuredMode = this._config.nitroAuthWebStorage;
|
|
134
136
|
if (configuredMode && WEB_STORAGE_MODES.has(configuredMode)) {
|
|
135
137
|
return configuredMode;
|
|
136
138
|
}
|
|
137
139
|
return STORAGE_MODE_SESSION;
|
|
138
140
|
}
|
|
139
141
|
getBrowserStorage() {
|
|
142
|
+
if (this._browserStorageResolved) {
|
|
143
|
+
return this._browserStorageCache;
|
|
144
|
+
}
|
|
145
|
+
this._browserStorageResolved = true;
|
|
140
146
|
if (typeof window === "undefined") {
|
|
147
|
+
this._browserStorageCache = undefined;
|
|
141
148
|
return undefined;
|
|
142
149
|
}
|
|
143
150
|
const mode = this.getWebStorageMode();
|
|
144
151
|
if (mode === STORAGE_MODE_MEMORY) {
|
|
152
|
+
this._browserStorageCache = undefined;
|
|
145
153
|
return undefined;
|
|
146
154
|
}
|
|
147
155
|
const storage = mode === STORAGE_MODE_LOCAL ? window.localStorage : window.sessionStorage;
|
|
@@ -149,12 +157,14 @@ class AuthWeb {
|
|
|
149
157
|
const testKey = "__nitro_auth_storage_probe__";
|
|
150
158
|
storage.setItem(testKey, "1");
|
|
151
159
|
storage.removeItem(testKey);
|
|
160
|
+
this._browserStorageCache = storage;
|
|
152
161
|
return storage;
|
|
153
162
|
} catch (error) {
|
|
154
163
|
_logger.logger.warn("Configured web storage is unavailable; using in-memory fallback", {
|
|
155
164
|
mode,
|
|
156
165
|
error: String(error)
|
|
157
166
|
});
|
|
167
|
+
this._browserStorageCache = undefined;
|
|
158
168
|
return undefined;
|
|
159
169
|
}
|
|
160
170
|
}
|
|
@@ -371,6 +381,20 @@ class AuthWeb {
|
|
|
371
381
|
return this._currentUser?.accessToken;
|
|
372
382
|
}
|
|
373
383
|
async refreshToken() {
|
|
384
|
+
if (this._refreshPromise) {
|
|
385
|
+
return this._refreshPromise;
|
|
386
|
+
}
|
|
387
|
+
const refreshPromise = this.performRefreshToken();
|
|
388
|
+
this._refreshPromise = refreshPromise;
|
|
389
|
+
try {
|
|
390
|
+
return await refreshPromise;
|
|
391
|
+
} finally {
|
|
392
|
+
if (this._refreshPromise === refreshPromise) {
|
|
393
|
+
this._refreshPromise = undefined;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async performRefreshToken() {
|
|
374
398
|
if (!this._currentUser) {
|
|
375
399
|
throw new Error("No user logged in");
|
|
376
400
|
}
|
|
@@ -380,13 +404,12 @@ class AuthWeb {
|
|
|
380
404
|
if (!refreshToken) {
|
|
381
405
|
throw new Error("No refresh token available");
|
|
382
406
|
}
|
|
383
|
-
const
|
|
384
|
-
const clientId = config.microsoftClientId;
|
|
407
|
+
const clientId = this._config.microsoftClientId;
|
|
385
408
|
if (!clientId) {
|
|
386
409
|
throw new Error("Microsoft Client ID not configured. Add 'microsoftClientId' to expo.extra in your app.config.js");
|
|
387
410
|
}
|
|
388
|
-
const tenant =
|
|
389
|
-
const b2cDomain =
|
|
411
|
+
const tenant = this._config.microsoftTenant ?? "common";
|
|
412
|
+
const b2cDomain = this._config.microsoftB2cDomain;
|
|
390
413
|
const authBaseUrl = this.getMicrosoftAuthBaseUrl(tenant, b2cDomain);
|
|
391
414
|
const tokenUrl = `${authBaseUrl}oauth2/v2.0/token`;
|
|
392
415
|
const body = new URLSearchParams({
|
|
@@ -480,7 +503,9 @@ class AuthWeb {
|
|
|
480
503
|
if (!payload) {
|
|
481
504
|
throw new Error("Invalid JWT payload");
|
|
482
505
|
}
|
|
483
|
-
const
|
|
506
|
+
const normalizedPayload = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
507
|
+
const padding = "=".repeat((4 - normalizedPayload.length % 4) % 4);
|
|
508
|
+
const decoded = JSON.parse(atob(`${normalizedPayload}${padding}`));
|
|
484
509
|
if (!isJsonObject(decoded)) {
|
|
485
510
|
throw new Error("Expected JWT payload to be an object");
|
|
486
511
|
}
|
|
@@ -531,8 +556,7 @@ class AuthWeb {
|
|
|
531
556
|
});
|
|
532
557
|
}
|
|
533
558
|
async loginGoogle(scopes, loginHint) {
|
|
534
|
-
const
|
|
535
|
-
const clientId = config.googleWebClientId;
|
|
559
|
+
const clientId = this._config.googleWebClientId;
|
|
536
560
|
if (!clientId) {
|
|
537
561
|
throw new Error("Google Web Client ID not configured. Add 'GOOGLE_WEB_CLIENT_ID' to your .env file.");
|
|
538
562
|
}
|
|
@@ -603,13 +627,12 @@ class AuthWeb {
|
|
|
603
627
|
}
|
|
604
628
|
}
|
|
605
629
|
async loginMicrosoft(scopes, loginHint, tenant, prompt) {
|
|
606
|
-
const
|
|
607
|
-
const clientId = config.microsoftClientId;
|
|
630
|
+
const clientId = this._config.microsoftClientId;
|
|
608
631
|
if (!clientId) {
|
|
609
632
|
throw new Error("Microsoft Client ID not configured. Add 'microsoftClientId' to expo.extra in your app.config.js");
|
|
610
633
|
}
|
|
611
|
-
const effectiveTenant = tenant ??
|
|
612
|
-
const b2cDomain =
|
|
634
|
+
const effectiveTenant = tenant ?? this._config.microsoftTenant ?? "common";
|
|
635
|
+
const b2cDomain = this._config.microsoftB2cDomain;
|
|
613
636
|
const authBaseUrl = this.getMicrosoftAuthBaseUrl(effectiveTenant, b2cDomain);
|
|
614
637
|
const effectiveScopes = scopes.length ? scopes : ["openid", "email", "profile", "offline_access", "User.Read"];
|
|
615
638
|
const codeVerifier = this.generateCodeVerifier();
|
|
@@ -680,8 +703,7 @@ class AuthWeb {
|
|
|
680
703
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
681
704
|
}
|
|
682
705
|
async exchangeMicrosoftCodeForTokens(code, codeVerifier, clientId, redirectUri, tenant, expectedNonce, scopes) {
|
|
683
|
-
const
|
|
684
|
-
const authBaseUrl = this.getMicrosoftAuthBaseUrl(tenant, config.microsoftB2cDomain);
|
|
706
|
+
const authBaseUrl = this.getMicrosoftAuthBaseUrl(tenant, this._config.microsoftB2cDomain);
|
|
685
707
|
const tokenUrl = `${authBaseUrl}oauth2/v2.0/token`;
|
|
686
708
|
const body = new URLSearchParams({
|
|
687
709
|
client_id: clientId,
|
|
@@ -753,45 +775,76 @@ class AuthWeb {
|
|
|
753
775
|
return {};
|
|
754
776
|
}
|
|
755
777
|
}
|
|
756
|
-
async
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
if (!clientId) {
|
|
760
|
-
throw new Error("Apple Web Client ID not configured. Add 'APPLE_WEB_CLIENT_ID' to your .env file.");
|
|
778
|
+
async ensureAppleSdkLoaded() {
|
|
779
|
+
if (window.AppleID) {
|
|
780
|
+
return;
|
|
761
781
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
782
|
+
if (this._appleSdkLoadPromise) {
|
|
783
|
+
return this._appleSdkLoadPromise;
|
|
784
|
+
}
|
|
785
|
+
this._appleSdkLoadPromise = new Promise((resolve, reject) => {
|
|
786
|
+
const scriptId = "nitro-auth-apple-sdk";
|
|
787
|
+
const existingScript = document.getElementById(scriptId);
|
|
788
|
+
if (existingScript) {
|
|
789
|
+
if (window.AppleID) {
|
|
790
|
+
resolve();
|
|
769
791
|
return;
|
|
770
792
|
}
|
|
771
|
-
|
|
772
|
-
clientId,
|
|
773
|
-
scope: "name email",
|
|
774
|
-
redirectURI: window.location.origin,
|
|
775
|
-
usePopup: true
|
|
776
|
-
});
|
|
777
|
-
window.AppleID.auth.signIn().then(response => {
|
|
778
|
-
const user = {
|
|
779
|
-
provider: "apple",
|
|
780
|
-
idToken: response.authorization.id_token,
|
|
781
|
-
email: response.user?.email,
|
|
782
|
-
name: response.user?.name ? `${response.user.name.firstName} ${response.user.name.lastName}`.trim() : undefined
|
|
783
|
-
};
|
|
784
|
-
this.updateUser(user);
|
|
793
|
+
existingScript.addEventListener("load", () => {
|
|
785
794
|
resolve();
|
|
786
|
-
}
|
|
787
|
-
|
|
795
|
+
}, {
|
|
796
|
+
once: true
|
|
797
|
+
});
|
|
798
|
+
existingScript.addEventListener("error", () => {
|
|
799
|
+
this._appleSdkLoadPromise = undefined;
|
|
800
|
+
reject(new Error("Failed to load Apple SDK"));
|
|
801
|
+
}, {
|
|
802
|
+
once: true
|
|
788
803
|
});
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const script = document.createElement("script");
|
|
807
|
+
script.id = scriptId;
|
|
808
|
+
script.src = "https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js";
|
|
809
|
+
script.async = true;
|
|
810
|
+
script.onload = () => {
|
|
811
|
+
resolve();
|
|
789
812
|
};
|
|
790
813
|
script.onerror = () => {
|
|
814
|
+
this._appleSdkLoadPromise = undefined;
|
|
791
815
|
reject(new Error("Failed to load Apple SDK"));
|
|
792
816
|
};
|
|
793
817
|
document.head.appendChild(script);
|
|
794
818
|
});
|
|
819
|
+
return this._appleSdkLoadPromise;
|
|
820
|
+
}
|
|
821
|
+
async loginApple() {
|
|
822
|
+
const clientId = this._config.appleWebClientId;
|
|
823
|
+
if (!clientId) {
|
|
824
|
+
throw new Error("Apple Web Client ID not configured. Add 'APPLE_WEB_CLIENT_ID' to your .env file.");
|
|
825
|
+
}
|
|
826
|
+
await this.ensureAppleSdkLoaded();
|
|
827
|
+
if (!window.AppleID) {
|
|
828
|
+
throw new Error("Apple SDK not loaded");
|
|
829
|
+
}
|
|
830
|
+
window.AppleID.auth.init({
|
|
831
|
+
clientId,
|
|
832
|
+
scope: "name email",
|
|
833
|
+
redirectURI: window.location.origin,
|
|
834
|
+
usePopup: true
|
|
835
|
+
});
|
|
836
|
+
try {
|
|
837
|
+
const response = await window.AppleID.auth.signIn();
|
|
838
|
+
const user = {
|
|
839
|
+
provider: "apple",
|
|
840
|
+
idToken: response.authorization.id_token,
|
|
841
|
+
email: response.user?.email,
|
|
842
|
+
name: response.user?.name ? `${response.user.name.firstName} ${response.user.name.lastName}`.trim() : undefined
|
|
843
|
+
};
|
|
844
|
+
this.updateUser(user);
|
|
845
|
+
} catch (error) {
|
|
846
|
+
throw this.mapError(error);
|
|
847
|
+
}
|
|
795
848
|
}
|
|
796
849
|
async silentRestore() {
|
|
797
850
|
_logger.logger.log("Attempting silent restore...");
|