react-native-nitro-auth 0.5.5 → 0.5.6
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 +22 -17
- package/android/proguard-rules.pro +7 -1
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/cpp/PlatformAuth+Android.cpp +260 -67
- package/android/src/main/java/com/auth/AuthAdapter.kt +217 -146
- package/android/src/main/java/com/auth/GoogleSignInActivity.kt +9 -5
- package/cpp/HybridAuth.cpp +79 -64
- package/cpp/HybridAuth.hpp +9 -7
- package/cpp/JSONSerializer.hpp +3 -0
- package/ios/AuthAdapter.swift +173 -60
- package/ios/PlatformAuth+iOS.mm +10 -3
- package/lib/commonjs/Auth.web.js +50 -10
- package/lib/commonjs/Auth.web.js.map +1 -1
- package/lib/commonjs/index.web.js +30 -12
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/service.js +9 -7
- package/lib/commonjs/service.js.map +1 -1
- package/lib/commonjs/service.web.js +65 -13
- package/lib/commonjs/service.web.js.map +1 -1
- package/lib/commonjs/ui/social-button.js +19 -14
- package/lib/commonjs/ui/social-button.js.map +1 -1
- package/lib/commonjs/ui/social-button.web.js +16 -10
- package/lib/commonjs/ui/social-button.web.js.map +1 -1
- package/lib/commonjs/use-auth.js +13 -5
- package/lib/commonjs/use-auth.js.map +1 -1
- package/lib/commonjs/utils/logger.js +1 -0
- package/lib/commonjs/utils/logger.js.map +1 -1
- package/lib/module/Auth.web.js +50 -10
- package/lib/module/Auth.web.js.map +1 -1
- package/lib/module/global.d.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +2 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/service.js +9 -7
- package/lib/module/service.js.map +1 -1
- package/lib/module/service.web.js +65 -13
- package/lib/module/service.web.js.map +1 -1
- package/lib/module/ui/social-button.js +19 -14
- package/lib/module/ui/social-button.js.map +1 -1
- package/lib/module/ui/social-button.web.js +16 -10
- package/lib/module/ui/social-button.web.js.map +1 -1
- package/lib/module/use-auth.js +13 -5
- package/lib/module/use-auth.js.map +1 -1
- package/lib/module/utils/logger.js +1 -0
- package/lib/module/utils/logger.js.map +1 -1
- package/lib/typescript/commonjs/Auth.web.d.ts +5 -1
- package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +2 -1
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/service.d.ts.map +1 -1
- package/lib/typescript/commonjs/service.web.d.ts +2 -18
- package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
- package/lib/typescript/module/Auth.web.d.ts +5 -1
- package/lib/typescript/module/Auth.web.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +2 -1
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/service.d.ts.map +1 -1
- package/lib/typescript/module/service.web.d.ts +2 -18
- package/lib/typescript/module/service.web.d.ts.map +1 -1
- package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
- package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
- package/lib/typescript/module/use-auth.d.ts.map +1 -1
- package/lib/typescript/module/utils/logger.d.ts.map +1 -1
- package/package.json +2 -4
- package/src/Auth.web.ts +77 -11
- package/src/global.d.ts +0 -11
- package/src/index.ts +5 -1
- package/src/index.web.ts +6 -1
- package/src/service.ts +9 -7
- package/src/service.web.ts +84 -15
- package/src/ui/social-button.tsx +21 -9
- package/src/ui/social-button.web.tsx +17 -4
- package/src/use-auth.ts +35 -8
- package/src/utils/logger.ts +1 -0
package/ios/AuthAdapter.swift
CHANGED
|
@@ -2,7 +2,6 @@ import Foundation
|
|
|
2
2
|
import GoogleSignIn
|
|
3
3
|
import AuthenticationServices
|
|
4
4
|
import NitroModules
|
|
5
|
-
import ObjectiveC
|
|
6
5
|
import CommonCrypto
|
|
7
6
|
|
|
8
7
|
@objc
|
|
@@ -11,9 +10,12 @@ public class AuthAdapter: NSObject {
|
|
|
11
10
|
private static var inMemoryMicrosoftRefreshToken: String?
|
|
12
11
|
private static var inMemoryMicrosoftScopes: [String] = defaultMicrosoftScopes
|
|
13
12
|
private static var inMemoryGoogleServerAuthCode: String?
|
|
13
|
+
private static let tokenStoreLock = NSLock()
|
|
14
14
|
|
|
15
15
|
@objc
|
|
16
16
|
public static func login(provider: String, scopes: [String], loginHint: String?, useSheet: Bool, forceAccountPicker: Bool = false, tenant: String? = nil, prompt: String? = nil, completion: @escaping (NSDictionary?, String?) -> Void) {
|
|
17
|
+
// useSheet is accepted for API compatibility with Android but has no effect on iOS.
|
|
18
|
+
// Google Sign-In SDK controls its own presentation style.
|
|
17
19
|
if provider == "google" {
|
|
18
20
|
guard let clientId = Bundle.main.object(forInfoDictionaryKey: "GIDClientID") as? String, !clientId.isEmpty else {
|
|
19
21
|
completion(nil, "configuration_error")
|
|
@@ -25,10 +27,10 @@ public class AuthAdapter: NSObject {
|
|
|
25
27
|
DispatchQueue.main.async {
|
|
26
28
|
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
27
29
|
let rootVC = windowScene.windows.first?.rootViewController else {
|
|
28
|
-
completion(nil, "
|
|
30
|
+
completion(nil, "no_window")
|
|
29
31
|
return
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
|
|
32
34
|
let config = GIDConfiguration(clientID: clientId, serverClientID: serverClientId)
|
|
33
35
|
GIDSignIn.sharedInstance.configuration = config
|
|
34
36
|
|
|
@@ -58,7 +60,18 @@ public class AuthAdapter: NSObject {
|
|
|
58
60
|
let delegate = AppleSignInDelegate(completion: completion)
|
|
59
61
|
controller.delegate = delegate
|
|
60
62
|
objc_setAssociatedObject(controller, &delegateHandle, delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
61
|
-
|
|
63
|
+
|
|
64
|
+
DispatchQueue.main.async {
|
|
65
|
+
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
66
|
+
let window = windowScene.windows.first(where: { $0.isKeyWindow }) ?? windowScene.windows.first else {
|
|
67
|
+
completion(nil, "no_window")
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
let contextProvider = AppleSignInContextProvider(anchor: window)
|
|
71
|
+
controller.presentationContextProvider = contextProvider
|
|
72
|
+
objc_setAssociatedObject(controller, &contextProviderHandle, contextProvider, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
73
|
+
controller.performRequests()
|
|
74
|
+
}
|
|
62
75
|
} else if provider == "microsoft" {
|
|
63
76
|
loginMicrosoft(scopes: scopes, loginHint: loginHint, tenant: tenant, prompt: prompt, completion: completion)
|
|
64
77
|
} else {
|
|
@@ -78,15 +91,27 @@ public class AuthAdapter: NSObject {
|
|
|
78
91
|
let effectiveScopes = scopes.isEmpty ? ["openid", "email", "profile", "offline_access", "User.Read"] : scopes
|
|
79
92
|
let effectivePrompt = prompt ?? "select_account"
|
|
80
93
|
|
|
81
|
-
let codeVerifier = generateCodeVerifier()
|
|
82
|
-
|
|
94
|
+
guard let codeVerifier = generateCodeVerifier() else {
|
|
95
|
+
completion(nil, "configuration_error")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
guard let codeChallenge = generateCodeChallenge(codeVerifier) else {
|
|
99
|
+
completion(nil, "configuration_error")
|
|
100
|
+
return
|
|
101
|
+
}
|
|
83
102
|
let state = UUID().uuidString
|
|
84
103
|
let nonce = UUID().uuidString
|
|
85
104
|
|
|
86
105
|
let b2cDomain = Bundle.main.object(forInfoDictionaryKey: "MSALB2cDomain") as? String
|
|
87
|
-
let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: effectiveTenant, b2cDomain: b2cDomain)
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: effectiveTenant, b2cDomain: b2cDomain) else {
|
|
107
|
+
completion(nil, "configuration_error")
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
guard var urlComponents = URLComponents(string: "\(authBaseUrl)oauth2/v2.0/authorize") else {
|
|
112
|
+
completion(nil, "configuration_error")
|
|
113
|
+
return
|
|
114
|
+
}
|
|
90
115
|
urlComponents.queryItems = [
|
|
91
116
|
URLQueryItem(name: "client_id", value: clientId),
|
|
92
117
|
URLQueryItem(name: "redirect_uri", value: redirectUri),
|
|
@@ -168,12 +193,16 @@ public class AuthAdapter: NSObject {
|
|
|
168
193
|
}
|
|
169
194
|
|
|
170
195
|
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
171
|
-
let
|
|
172
|
-
|
|
196
|
+
let window = windowScene.windows.first(where: { $0.isKeyWindow }) ?? windowScene.windows.first,
|
|
197
|
+
let rootVC = window.rootViewController else {
|
|
198
|
+
completion(nil, "no_window")
|
|
173
199
|
return
|
|
174
200
|
}
|
|
175
|
-
|
|
176
|
-
|
|
201
|
+
guard let window = rootVC.view.window else {
|
|
202
|
+
completion(nil, "no_window")
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
let contextProvider = WebAuthContextProvider(anchor: window)
|
|
177
206
|
session.presentationContextProvider = contextProvider
|
|
178
207
|
objc_setAssociatedObject(session, &contextProviderHandle, contextProvider, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
179
208
|
session.prefersEphemeralWebBrowserSession = false
|
|
@@ -181,17 +210,19 @@ public class AuthAdapter: NSObject {
|
|
|
181
210
|
}
|
|
182
211
|
}
|
|
183
212
|
|
|
184
|
-
private static func generateCodeVerifier() -> String {
|
|
213
|
+
private static func generateCodeVerifier() -> String? {
|
|
185
214
|
var bytes = [UInt8](repeating: 0, count: 32)
|
|
186
|
-
|
|
215
|
+
guard SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) == errSecSuccess else {
|
|
216
|
+
return nil
|
|
217
|
+
}
|
|
187
218
|
return Data(bytes).base64EncodedString()
|
|
188
219
|
.replacingOccurrences(of: "+", with: "-")
|
|
189
220
|
.replacingOccurrences(of: "/", with: "_")
|
|
190
221
|
.replacingOccurrences(of: "=", with: "")
|
|
191
222
|
}
|
|
192
223
|
|
|
193
|
-
private static func generateCodeChallenge(_ verifier: String) -> String {
|
|
194
|
-
guard let data = verifier.data(using: .ascii) else { return
|
|
224
|
+
private static func generateCodeChallenge(_ verifier: String) -> String? {
|
|
225
|
+
guard let data = verifier.data(using: .ascii) else { return nil }
|
|
195
226
|
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
196
227
|
data.withUnsafeBytes {
|
|
197
228
|
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
|
|
@@ -213,13 +244,16 @@ public class AuthAdapter: NSObject {
|
|
|
213
244
|
scopes: [String],
|
|
214
245
|
completion: @escaping (NSDictionary?, String?) -> Void
|
|
215
246
|
) {
|
|
216
|
-
let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain)
|
|
217
|
-
|
|
218
|
-
|
|
247
|
+
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain),
|
|
248
|
+
let tokenUrl = URL(string: "\(authBaseUrl)oauth2/v2.0/token") else {
|
|
249
|
+
DispatchQueue.main.async { completion(nil, "configuration_error") }
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
219
253
|
var request = URLRequest(url: tokenUrl)
|
|
220
254
|
request.httpMethod = "POST"
|
|
221
255
|
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
|
222
|
-
|
|
256
|
+
|
|
223
257
|
let bodyParams = [
|
|
224
258
|
"client_id": clientId,
|
|
225
259
|
"code": code,
|
|
@@ -236,18 +270,17 @@ public class AuthAdapter: NSObject {
|
|
|
236
270
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
237
271
|
DispatchQueue.main.async {
|
|
238
272
|
if let error = error {
|
|
239
|
-
|
|
240
|
-
if nsError.code == NSURLErrorNotConnectedToInternet || nsError.code == NSURLErrorTimedOut {
|
|
241
|
-
completion(nil, "network_error")
|
|
242
|
-
} else {
|
|
243
|
-
completion(nil, "network_error")
|
|
244
|
-
}
|
|
273
|
+
completion(nil, "network_error")
|
|
245
274
|
return
|
|
246
275
|
}
|
|
247
276
|
|
|
248
277
|
guard let data = data,
|
|
249
278
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
250
|
-
|
|
279
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
280
|
+
completion(nil, "network_error")
|
|
281
|
+
} else {
|
|
282
|
+
completion(nil, "parse_error")
|
|
283
|
+
}
|
|
251
284
|
return
|
|
252
285
|
}
|
|
253
286
|
|
|
@@ -256,6 +289,11 @@ public class AuthAdapter: NSObject {
|
|
|
256
289
|
return
|
|
257
290
|
}
|
|
258
291
|
|
|
292
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
293
|
+
completion(nil, "network_error")
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
259
297
|
guard let idToken = json["id_token"] as? String else {
|
|
260
298
|
completion(nil, "no_id_token")
|
|
261
299
|
return
|
|
@@ -269,13 +307,15 @@ public class AuthAdapter: NSObject {
|
|
|
269
307
|
|
|
270
308
|
let accessToken = json["access_token"] as? String ?? ""
|
|
271
309
|
let refreshToken = json["refresh_token"] as? String ?? ""
|
|
272
|
-
let expiresIn = json["expires_in"] as? Double ?? 0
|
|
310
|
+
let expiresIn = (json["expires_in"] as? Double).flatMap { $0 > 0 ? $0 : nil } ?? 3600.0
|
|
273
311
|
let expirationTime = Date().timeIntervalSince1970 * 1000 + expiresIn * 1000
|
|
274
312
|
|
|
313
|
+
tokenStoreLock.lock()
|
|
275
314
|
if !refreshToken.isEmpty {
|
|
276
315
|
inMemoryMicrosoftRefreshToken = refreshToken
|
|
277
316
|
}
|
|
278
317
|
inMemoryMicrosoftScopes = scopes.isEmpty ? defaultMicrosoftScopes : scopes
|
|
318
|
+
tokenStoreLock.unlock()
|
|
279
319
|
|
|
280
320
|
let resultData: [String: Any] = [
|
|
281
321
|
"provider": "microsoft",
|
|
@@ -331,7 +371,9 @@ public class AuthAdapter: NSObject {
|
|
|
331
371
|
}
|
|
332
372
|
|
|
333
373
|
let serverAuthCode = result?.serverAuthCode ?? ""
|
|
374
|
+
tokenStoreLock.lock()
|
|
334
375
|
inMemoryGoogleServerAuthCode = serverAuthCode.isEmpty ? nil : serverAuthCode
|
|
376
|
+
tokenStoreLock.unlock()
|
|
335
377
|
|
|
336
378
|
let data: [String: Any] = [
|
|
337
379
|
"provider": "google",
|
|
@@ -349,11 +391,14 @@ public class AuthAdapter: NSObject {
|
|
|
349
391
|
|
|
350
392
|
static func mapError(_ error: Error) -> String {
|
|
351
393
|
let nsError = error as NSError
|
|
394
|
+
if nsError.domain == NSURLErrorDomain {
|
|
395
|
+
return "network_error"
|
|
396
|
+
}
|
|
352
397
|
// GIDSignIn error codes
|
|
353
398
|
if nsError.domain == "com.google.GIDSignIn" {
|
|
354
399
|
switch nsError.code {
|
|
355
400
|
case -5: return "cancelled" // GIDSignInErrorCodeCanceled
|
|
356
|
-
case -4: return "
|
|
401
|
+
case -4: return "not_signed_in" // GIDSignInErrorCodeNoCurrentUser
|
|
357
402
|
default: break
|
|
358
403
|
}
|
|
359
404
|
}
|
|
@@ -388,7 +433,7 @@ public class AuthAdapter: NSObject {
|
|
|
388
433
|
DispatchQueue.main.async {
|
|
389
434
|
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
|
390
435
|
let rootVC = windowScene.windows.first?.rootViewController else {
|
|
391
|
-
completion(nil, "
|
|
436
|
+
completion(nil, "no_window")
|
|
392
437
|
return
|
|
393
438
|
}
|
|
394
439
|
currentUser.addScopes(scopes, presenting: rootVC) { result, error in
|
|
@@ -397,11 +442,17 @@ public class AuthAdapter: NSObject {
|
|
|
397
442
|
}
|
|
398
443
|
return
|
|
399
444
|
}
|
|
400
|
-
|
|
401
|
-
|
|
445
|
+
tokenStoreLock.lock()
|
|
446
|
+
let hasRefreshToken = inMemoryMicrosoftRefreshToken != nil
|
|
447
|
+
let currentScopes = inMemoryMicrosoftScopes
|
|
448
|
+
tokenStoreLock.unlock()
|
|
449
|
+
guard hasRefreshToken else {
|
|
450
|
+
completion(nil, "not_signed_in")
|
|
402
451
|
return
|
|
403
452
|
}
|
|
404
|
-
let mergedScopes =
|
|
453
|
+
let mergedScopes = (currentScopes + scopes).reduce(into: [String]()) { acc, s in
|
|
454
|
+
if !acc.contains(s) { acc.append(s) }
|
|
455
|
+
}
|
|
405
456
|
loginMicrosoft(scopes: mergedScopes, loginHint: nil, tenant: nil, prompt: nil, completion: completion)
|
|
406
457
|
}
|
|
407
458
|
|
|
@@ -410,7 +461,7 @@ public class AuthAdapter: NSObject {
|
|
|
410
461
|
if let currentUser = GIDSignIn.sharedInstance.currentUser {
|
|
411
462
|
currentUser.refreshTokensIfNeeded { user, error in
|
|
412
463
|
if let error = error {
|
|
413
|
-
completion(nil, error
|
|
464
|
+
completion(nil, mapError(error))
|
|
414
465
|
return
|
|
415
466
|
}
|
|
416
467
|
guard let user = user else {
|
|
@@ -435,6 +486,9 @@ public class AuthAdapter: NSObject {
|
|
|
435
486
|
if Bundle.main.object(forInfoDictionaryKey: "GIDClientID") != nil {
|
|
436
487
|
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
|
|
437
488
|
if let user = user {
|
|
489
|
+
tokenStoreLock.lock()
|
|
490
|
+
let cachedServerAuthCode = inMemoryGoogleServerAuthCode
|
|
491
|
+
tokenStoreLock.unlock()
|
|
438
492
|
let data: [String: Any] = [
|
|
439
493
|
"provider": "google",
|
|
440
494
|
"email": user.profile?.email ?? "",
|
|
@@ -442,7 +496,7 @@ public class AuthAdapter: NSObject {
|
|
|
442
496
|
"photo": user.profile?.imageURL(withDimension: 300)?.absoluteString ?? "",
|
|
443
497
|
"idToken": user.idToken?.tokenString ?? "",
|
|
444
498
|
"accessToken": user.accessToken.tokenString,
|
|
445
|
-
"serverAuthCode":
|
|
499
|
+
"serverAuthCode": cachedServerAuthCode ?? "",
|
|
446
500
|
"expirationTime": (user.accessToken.expirationDate?.timeIntervalSince1970 ?? 0) * 1000
|
|
447
501
|
]
|
|
448
502
|
completion(data as NSDictionary)
|
|
@@ -456,7 +510,10 @@ public class AuthAdapter: NSObject {
|
|
|
456
510
|
}
|
|
457
511
|
|
|
458
512
|
private static func tryMicrosoftSilentRefresh(completion: @escaping (NSDictionary?) -> Void) {
|
|
459
|
-
|
|
513
|
+
tokenStoreLock.lock()
|
|
514
|
+
let refreshToken = inMemoryMicrosoftRefreshToken
|
|
515
|
+
tokenStoreLock.unlock()
|
|
516
|
+
guard let refreshToken = refreshToken else {
|
|
460
517
|
completion(nil)
|
|
461
518
|
return
|
|
462
519
|
}
|
|
@@ -468,13 +525,16 @@ public class AuthAdapter: NSObject {
|
|
|
468
525
|
|
|
469
526
|
let tenant = Bundle.main.object(forInfoDictionaryKey: "MSALTenant") as? String ?? "common"
|
|
470
527
|
let b2cDomain = Bundle.main.object(forInfoDictionaryKey: "MSALB2cDomain") as? String
|
|
471
|
-
let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain)
|
|
472
|
-
|
|
473
|
-
|
|
528
|
+
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain),
|
|
529
|
+
let tokenUrl = URL(string: "\(authBaseUrl)oauth2/v2.0/token") else {
|
|
530
|
+
completion(nil)
|
|
531
|
+
return
|
|
532
|
+
}
|
|
533
|
+
|
|
474
534
|
var request = URLRequest(url: tokenUrl)
|
|
475
535
|
request.httpMethod = "POST"
|
|
476
536
|
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
|
477
|
-
|
|
537
|
+
|
|
478
538
|
let bodyParams = [
|
|
479
539
|
"client_id": clientId,
|
|
480
540
|
"grant_type": "refresh_token",
|
|
@@ -488,23 +548,42 @@ public class AuthAdapter: NSObject {
|
|
|
488
548
|
|
|
489
549
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
490
550
|
DispatchQueue.main.async {
|
|
551
|
+
if let error = error {
|
|
552
|
+
#if DEBUG
|
|
553
|
+
print("[NitroAuth] Microsoft silent refresh network error: \(error.localizedDescription)")
|
|
554
|
+
#endif
|
|
555
|
+
completion(nil)
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
559
|
+
#if DEBUG
|
|
560
|
+
print("[NitroAuth] Microsoft silent refresh HTTP \(httpResponse.statusCode)")
|
|
561
|
+
#endif
|
|
562
|
+
completion(nil)
|
|
563
|
+
return
|
|
564
|
+
}
|
|
491
565
|
guard let data = data,
|
|
492
566
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
493
567
|
let idToken = json["id_token"] as? String else {
|
|
568
|
+
#if DEBUG
|
|
569
|
+
print("[NitroAuth] Microsoft silent refresh: failed to parse token response")
|
|
570
|
+
#endif
|
|
494
571
|
completion(nil)
|
|
495
572
|
return
|
|
496
573
|
}
|
|
497
|
-
|
|
574
|
+
|
|
498
575
|
let claims = decodeJwt(idToken)
|
|
499
576
|
let accessToken = json["access_token"] as? String ?? ""
|
|
500
577
|
let newRefreshToken = json["refresh_token"] as? String ?? ""
|
|
501
|
-
let expiresIn = json["expires_in"] as? Double ?? 0
|
|
578
|
+
let expiresIn = (json["expires_in"] as? Double).flatMap { $0 > 0 ? $0 : nil } ?? 3600.0
|
|
502
579
|
let expirationTime = Date().timeIntervalSince1970 * 1000 + expiresIn * 1000
|
|
503
|
-
|
|
580
|
+
|
|
581
|
+
tokenStoreLock.lock()
|
|
504
582
|
if !newRefreshToken.isEmpty {
|
|
505
583
|
inMemoryMicrosoftRefreshToken = newRefreshToken
|
|
506
584
|
}
|
|
507
|
-
|
|
585
|
+
tokenStoreLock.unlock()
|
|
586
|
+
|
|
508
587
|
let resultData: [String: Any] = [
|
|
509
588
|
"provider": "microsoft",
|
|
510
589
|
"email": claims["preferred_username"] ?? claims["email"] ?? "",
|
|
@@ -521,8 +600,11 @@ public class AuthAdapter: NSObject {
|
|
|
521
600
|
}
|
|
522
601
|
|
|
523
602
|
private static func tryMicrosoftRefreshForTokenRefresh(completion: @escaping (NSDictionary?, String?) -> Void) {
|
|
524
|
-
|
|
525
|
-
|
|
603
|
+
tokenStoreLock.lock()
|
|
604
|
+
let refreshToken = inMemoryMicrosoftRefreshToken
|
|
605
|
+
tokenStoreLock.unlock()
|
|
606
|
+
guard let refreshToken = refreshToken else {
|
|
607
|
+
completion(nil, "not_signed_in")
|
|
526
608
|
return
|
|
527
609
|
}
|
|
528
610
|
guard let clientId = Bundle.main.object(forInfoDictionaryKey: "MSALClientID") as? String, !clientId.isEmpty else {
|
|
@@ -531,8 +613,11 @@ public class AuthAdapter: NSObject {
|
|
|
531
613
|
}
|
|
532
614
|
let tenant = Bundle.main.object(forInfoDictionaryKey: "MSALTenant") as? String ?? "common"
|
|
533
615
|
let b2cDomain = Bundle.main.object(forInfoDictionaryKey: "MSALB2cDomain") as? String
|
|
534
|
-
let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain)
|
|
535
|
-
|
|
616
|
+
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain),
|
|
617
|
+
let tokenUrl = URL(string: "\(authBaseUrl)oauth2/v2.0/token") else {
|
|
618
|
+
completion(nil, "configuration_error")
|
|
619
|
+
return
|
|
620
|
+
}
|
|
536
621
|
var request = URLRequest(url: tokenUrl)
|
|
537
622
|
request.httpMethod = "POST"
|
|
538
623
|
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
|
@@ -553,21 +638,31 @@ public class AuthAdapter: NSObject {
|
|
|
553
638
|
}
|
|
554
639
|
guard let data = data,
|
|
555
640
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
556
|
-
|
|
641
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
642
|
+
completion(nil, "network_error")
|
|
643
|
+
} else {
|
|
644
|
+
completion(nil, "parse_error")
|
|
645
|
+
}
|
|
557
646
|
return
|
|
558
647
|
}
|
|
559
648
|
if let errorCode = json["error"] as? String {
|
|
560
649
|
completion(nil, AuthAdapter.mapOAuthError(errorCode))
|
|
561
650
|
return
|
|
562
651
|
}
|
|
652
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
653
|
+
completion(nil, "network_error")
|
|
654
|
+
return
|
|
655
|
+
}
|
|
563
656
|
let idToken = json["id_token"] as? String ?? ""
|
|
564
657
|
let accessToken = json["access_token"] as? String ?? ""
|
|
565
658
|
let newRefreshToken = json["refresh_token"] as? String ?? ""
|
|
566
|
-
let expiresIn = json["expires_in"] as? Double ?? 0
|
|
659
|
+
let expiresIn = (json["expires_in"] as? Double).flatMap { $0 > 0 ? $0 : nil } ?? 3600.0
|
|
567
660
|
let expirationTime = Date().timeIntervalSince1970 * 1000 + expiresIn * 1000
|
|
661
|
+
tokenStoreLock.lock()
|
|
568
662
|
if !newRefreshToken.isEmpty {
|
|
569
663
|
inMemoryMicrosoftRefreshToken = newRefreshToken
|
|
570
664
|
}
|
|
665
|
+
tokenStoreLock.unlock()
|
|
571
666
|
let tokensData: [String: Any] = [
|
|
572
667
|
"accessToken": accessToken,
|
|
573
668
|
"idToken": idToken,
|
|
@@ -579,24 +674,28 @@ public class AuthAdapter: NSObject {
|
|
|
579
674
|
}.resume()
|
|
580
675
|
}
|
|
581
676
|
|
|
582
|
-
private static func getMicrosoftAuthBaseUrl(tenant: String, b2cDomain: String?) -> String {
|
|
583
|
-
|
|
584
|
-
|
|
677
|
+
private static func getMicrosoftAuthBaseUrl(tenant: String, b2cDomain: String?) -> String? {
|
|
678
|
+
let trimmedTenant = tenant.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
679
|
+
guard !trimmedTenant.isEmpty else { return nil }
|
|
680
|
+
|
|
681
|
+
if trimmedTenant.hasPrefix("https://") {
|
|
682
|
+
guard URL(string: trimmedTenant) != nil else { return nil }
|
|
683
|
+
return trimmedTenant.hasSuffix("/") ? trimmedTenant : "\(trimmedTenant)/"
|
|
585
684
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return "https://\(domain)/tfp/\(tenant)/"
|
|
589
|
-
} else {
|
|
590
|
-
return "https://login.microsoftonline.com/\(tenant)/"
|
|
685
|
+
if let domain = b2cDomain?.trimmingCharacters(in: .whitespacesAndNewlines), !domain.isEmpty {
|
|
686
|
+
return "https://\(domain)/tfp/\(trimmedTenant)/"
|
|
591
687
|
}
|
|
688
|
+
return "https://login.microsoftonline.com/\(trimmedTenant)/"
|
|
592
689
|
}
|
|
593
690
|
|
|
594
691
|
@objc
|
|
595
692
|
public static func logout() {
|
|
596
693
|
GIDSignIn.sharedInstance.signOut()
|
|
694
|
+
tokenStoreLock.lock()
|
|
597
695
|
inMemoryMicrosoftRefreshToken = nil
|
|
598
696
|
inMemoryMicrosoftScopes = defaultMicrosoftScopes
|
|
599
697
|
inMemoryGoogleServerAuthCode = nil
|
|
698
|
+
tokenStoreLock.unlock()
|
|
600
699
|
}
|
|
601
700
|
}
|
|
602
701
|
|
|
@@ -625,14 +724,28 @@ class AppleSignInDelegate: NSObject, ASAuthorizationControllerDelegate {
|
|
|
625
724
|
"underlyingError": ""
|
|
626
725
|
]
|
|
627
726
|
completion(data as NSDictionary, nil)
|
|
727
|
+
} else {
|
|
728
|
+
completion(nil, "unknown")
|
|
628
729
|
}
|
|
629
730
|
}
|
|
630
|
-
|
|
731
|
+
|
|
631
732
|
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
|
|
632
733
|
completion(nil, AuthAdapter.mapError(error))
|
|
633
734
|
}
|
|
634
735
|
}
|
|
635
736
|
|
|
737
|
+
class AppleSignInContextProvider: NSObject, ASAuthorizationControllerPresentationContextProviding {
|
|
738
|
+
let anchor: ASPresentationAnchor
|
|
739
|
+
|
|
740
|
+
init(anchor: ASPresentationAnchor) {
|
|
741
|
+
self.anchor = anchor
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
|
745
|
+
return anchor
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
636
749
|
class WebAuthContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
|
|
637
750
|
let anchor: ASPresentationAnchor
|
|
638
751
|
|
package/ios/PlatformAuth+iOS.mm
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
namespace margelo::nitro::NitroAuth {
|
|
18
18
|
|
|
19
|
-
inline std::string nsToStd(NSString* _Nullable ns) {
|
|
20
|
-
if (ns == nil) return
|
|
19
|
+
inline std::optional<std::string> nsToStd(NSString* _Nullable ns) {
|
|
20
|
+
if (ns == nil) return std::nullopt;
|
|
21
21
|
return std::string([ns UTF8String]);
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -109,7 +109,14 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::requestScopes(const std::vector
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
AuthUser user;
|
|
112
|
-
|
|
112
|
+
NSString *providerStr = [data objectForKey:@"provider"];
|
|
113
|
+
if ([providerStr isEqualToString:@"microsoft"]) {
|
|
114
|
+
user.provider = AuthProvider::MICROSOFT;
|
|
115
|
+
} else if ([providerStr isEqualToString:@"apple"]) {
|
|
116
|
+
user.provider = AuthProvider::APPLE;
|
|
117
|
+
} else {
|
|
118
|
+
user.provider = AuthProvider::GOOGLE;
|
|
119
|
+
}
|
|
113
120
|
user.email = nsToStd([data objectForKey:@"email"]);
|
|
114
121
|
user.name = nsToStd([data objectForKey:@"name"]);
|
|
115
122
|
user.photo = nsToStd([data objectForKey:@"photo"]);
|