react-native-nitro-auth 0.5.5 → 0.5.7
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 +30 -19
- package/android/proguard-rules.pro +7 -1
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/cpp/PlatformAuth+Android.cpp +261 -68
- package/android/src/main/java/com/auth/AuthAdapter.kt +250 -157
- 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 +208 -66
- package/ios/PlatformAuth+iOS.mm +30 -4
- 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 +25 -19
- 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 +34 -10
- package/lib/commonjs/use-auth.js.map +1 -1
- package/lib/commonjs/utils/auth-error.js +1 -1
- package/lib/commonjs/utils/auth-error.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 +25 -19
- 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 +34 -10
- package/lib/module/use-auth.js.map +1 -1
- package/lib/module/utils/auth-error.js +1 -1
- package/lib/module/utils/auth-error.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.nitro.d.ts +2 -2
- package/lib/typescript/commonjs/Auth.nitro.d.ts.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/auth-error.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
- package/lib/typescript/module/Auth.nitro.d.ts +2 -2
- package/lib/typescript/module/Auth.nitro.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/auth-error.d.ts.map +1 -1
- package/lib/typescript/module/utils/logger.d.ts.map +1 -1
- package/nitro.json +4 -1
- package/nitrogen/generated/ios/NitroAuth+autolinking.rb +2 -0
- package/package.json +3 -4
- package/src/Auth.nitro.ts +3 -1
- 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 +26 -19
- 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 +65 -9
- package/src/utils/auth-error.ts +2 -0
- 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")
|
|
@@ -23,12 +25,11 @@ public class AuthAdapter: NSObject {
|
|
|
23
25
|
let serverClientId = Bundle.main.object(forInfoDictionaryKey: "GIDServerClientID") as? String
|
|
24
26
|
|
|
25
27
|
DispatchQueue.main.async {
|
|
26
|
-
guard let
|
|
27
|
-
|
|
28
|
-
completion(nil, "No root view controller found")
|
|
28
|
+
guard let rootVC = presentingViewController() else {
|
|
29
|
+
completion(nil, "no_window")
|
|
29
30
|
return
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
let config = GIDConfiguration(clientID: clientId, serverClientID: serverClientId)
|
|
33
34
|
GIDSignIn.sharedInstance.configuration = config
|
|
34
35
|
|
|
@@ -58,7 +59,17 @@ public class AuthAdapter: NSObject {
|
|
|
58
59
|
let delegate = AppleSignInDelegate(completion: completion)
|
|
59
60
|
controller.delegate = delegate
|
|
60
61
|
objc_setAssociatedObject(controller, &delegateHandle, delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
61
|
-
|
|
62
|
+
|
|
63
|
+
DispatchQueue.main.async {
|
|
64
|
+
guard let window = activeWindow() else {
|
|
65
|
+
completion(nil, "no_window")
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
let contextProvider = AppleSignInContextProvider(anchor: window)
|
|
69
|
+
controller.presentationContextProvider = contextProvider
|
|
70
|
+
objc_setAssociatedObject(controller, &contextProviderHandle, contextProvider, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
71
|
+
controller.performRequests()
|
|
72
|
+
}
|
|
62
73
|
} else if provider == "microsoft" {
|
|
63
74
|
loginMicrosoft(scopes: scopes, loginHint: loginHint, tenant: tenant, prompt: prompt, completion: completion)
|
|
64
75
|
} else {
|
|
@@ -78,15 +89,27 @@ public class AuthAdapter: NSObject {
|
|
|
78
89
|
let effectiveScopes = scopes.isEmpty ? ["openid", "email", "profile", "offline_access", "User.Read"] : scopes
|
|
79
90
|
let effectivePrompt = prompt ?? "select_account"
|
|
80
91
|
|
|
81
|
-
let codeVerifier = generateCodeVerifier()
|
|
82
|
-
|
|
92
|
+
guard let codeVerifier = generateCodeVerifier() else {
|
|
93
|
+
completion(nil, "configuration_error")
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
guard let codeChallenge = generateCodeChallenge(codeVerifier) else {
|
|
97
|
+
completion(nil, "configuration_error")
|
|
98
|
+
return
|
|
99
|
+
}
|
|
83
100
|
let state = UUID().uuidString
|
|
84
101
|
let nonce = UUID().uuidString
|
|
85
102
|
|
|
86
103
|
let b2cDomain = Bundle.main.object(forInfoDictionaryKey: "MSALB2cDomain") as? String
|
|
87
|
-
let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: effectiveTenant, b2cDomain: b2cDomain)
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: effectiveTenant, b2cDomain: b2cDomain) else {
|
|
105
|
+
completion(nil, "configuration_error")
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
guard var urlComponents = URLComponents(string: "\(authBaseUrl)oauth2/v2.0/authorize") else {
|
|
110
|
+
completion(nil, "configuration_error")
|
|
111
|
+
return
|
|
112
|
+
}
|
|
90
113
|
urlComponents.queryItems = [
|
|
91
114
|
URLQueryItem(name: "client_id", value: clientId),
|
|
92
115
|
URLQueryItem(name: "redirect_uri", value: redirectUri),
|
|
@@ -166,14 +189,12 @@ public class AuthAdapter: NSObject {
|
|
|
166
189
|
completion: completion
|
|
167
190
|
)
|
|
168
191
|
}
|
|
169
|
-
|
|
170
|
-
guard let
|
|
171
|
-
|
|
172
|
-
completion(nil, "No root view controller found")
|
|
192
|
+
|
|
193
|
+
guard let window = activeWindow() else {
|
|
194
|
+
completion(nil, "no_window")
|
|
173
195
|
return
|
|
174
196
|
}
|
|
175
|
-
|
|
176
|
-
let contextProvider = WebAuthContextProvider(anchor: rootVC.view.window!)
|
|
197
|
+
let contextProvider = WebAuthContextProvider(anchor: window)
|
|
177
198
|
session.presentationContextProvider = contextProvider
|
|
178
199
|
objc_setAssociatedObject(session, &contextProviderHandle, contextProvider, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
179
200
|
session.prefersEphemeralWebBrowserSession = false
|
|
@@ -181,17 +202,19 @@ public class AuthAdapter: NSObject {
|
|
|
181
202
|
}
|
|
182
203
|
}
|
|
183
204
|
|
|
184
|
-
private static func generateCodeVerifier() -> String {
|
|
205
|
+
private static func generateCodeVerifier() -> String? {
|
|
185
206
|
var bytes = [UInt8](repeating: 0, count: 32)
|
|
186
|
-
|
|
207
|
+
guard SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) == errSecSuccess else {
|
|
208
|
+
return nil
|
|
209
|
+
}
|
|
187
210
|
return Data(bytes).base64EncodedString()
|
|
188
211
|
.replacingOccurrences(of: "+", with: "-")
|
|
189
212
|
.replacingOccurrences(of: "/", with: "_")
|
|
190
213
|
.replacingOccurrences(of: "=", with: "")
|
|
191
214
|
}
|
|
192
215
|
|
|
193
|
-
private static func generateCodeChallenge(_ verifier: String) -> String {
|
|
194
|
-
guard let data = verifier.data(using: .ascii) else { return
|
|
216
|
+
private static func generateCodeChallenge(_ verifier: String) -> String? {
|
|
217
|
+
guard let data = verifier.data(using: .ascii) else { return nil }
|
|
195
218
|
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
196
219
|
data.withUnsafeBytes {
|
|
197
220
|
_ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
|
|
@@ -213,13 +236,16 @@ public class AuthAdapter: NSObject {
|
|
|
213
236
|
scopes: [String],
|
|
214
237
|
completion: @escaping (NSDictionary?, String?) -> Void
|
|
215
238
|
) {
|
|
216
|
-
let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain)
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain),
|
|
240
|
+
let tokenUrl = URL(string: "\(authBaseUrl)oauth2/v2.0/token") else {
|
|
241
|
+
DispatchQueue.main.async { completion(nil, "configuration_error") }
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
219
245
|
var request = URLRequest(url: tokenUrl)
|
|
220
246
|
request.httpMethod = "POST"
|
|
221
247
|
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
|
222
|
-
|
|
248
|
+
|
|
223
249
|
let bodyParams = [
|
|
224
250
|
"client_id": clientId,
|
|
225
251
|
"code": code,
|
|
@@ -236,18 +262,17 @@ public class AuthAdapter: NSObject {
|
|
|
236
262
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
237
263
|
DispatchQueue.main.async {
|
|
238
264
|
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
|
-
}
|
|
265
|
+
completion(nil, "network_error")
|
|
245
266
|
return
|
|
246
267
|
}
|
|
247
268
|
|
|
248
269
|
guard let data = data,
|
|
249
270
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
250
|
-
|
|
271
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
272
|
+
completion(nil, "network_error")
|
|
273
|
+
} else {
|
|
274
|
+
completion(nil, "parse_error")
|
|
275
|
+
}
|
|
251
276
|
return
|
|
252
277
|
}
|
|
253
278
|
|
|
@@ -256,6 +281,11 @@ public class AuthAdapter: NSObject {
|
|
|
256
281
|
return
|
|
257
282
|
}
|
|
258
283
|
|
|
284
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
285
|
+
completion(nil, "network_error")
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
259
289
|
guard let idToken = json["id_token"] as? String else {
|
|
260
290
|
completion(nil, "no_id_token")
|
|
261
291
|
return
|
|
@@ -269,13 +299,15 @@ public class AuthAdapter: NSObject {
|
|
|
269
299
|
|
|
270
300
|
let accessToken = json["access_token"] as? String ?? ""
|
|
271
301
|
let refreshToken = json["refresh_token"] as? String ?? ""
|
|
272
|
-
let expiresIn = json["expires_in"] as? Double ?? 0
|
|
302
|
+
let expiresIn = (json["expires_in"] as? Double).flatMap { $0 > 0 ? $0 : nil } ?? 3600.0
|
|
273
303
|
let expirationTime = Date().timeIntervalSince1970 * 1000 + expiresIn * 1000
|
|
274
304
|
|
|
305
|
+
tokenStoreLock.lock()
|
|
275
306
|
if !refreshToken.isEmpty {
|
|
276
307
|
inMemoryMicrosoftRefreshToken = refreshToken
|
|
277
308
|
}
|
|
278
309
|
inMemoryMicrosoftScopes = scopes.isEmpty ? defaultMicrosoftScopes : scopes
|
|
310
|
+
tokenStoreLock.unlock()
|
|
279
311
|
|
|
280
312
|
let resultData: [String: Any] = [
|
|
281
313
|
"provider": "microsoft",
|
|
@@ -285,6 +317,7 @@ public class AuthAdapter: NSObject {
|
|
|
285
317
|
"idToken": idToken,
|
|
286
318
|
"accessToken": accessToken,
|
|
287
319
|
"serverAuthCode": "",
|
|
320
|
+
"scopes": scopes,
|
|
288
321
|
"expirationTime": expirationTime,
|
|
289
322
|
"underlyingError": ""
|
|
290
323
|
]
|
|
@@ -331,7 +364,9 @@ public class AuthAdapter: NSObject {
|
|
|
331
364
|
}
|
|
332
365
|
|
|
333
366
|
let serverAuthCode = result?.serverAuthCode ?? ""
|
|
367
|
+
tokenStoreLock.lock()
|
|
334
368
|
inMemoryGoogleServerAuthCode = serverAuthCode.isEmpty ? nil : serverAuthCode
|
|
369
|
+
tokenStoreLock.unlock()
|
|
335
370
|
|
|
336
371
|
let data: [String: Any] = [
|
|
337
372
|
"provider": "google",
|
|
@@ -349,11 +384,14 @@ public class AuthAdapter: NSObject {
|
|
|
349
384
|
|
|
350
385
|
static func mapError(_ error: Error) -> String {
|
|
351
386
|
let nsError = error as NSError
|
|
387
|
+
if nsError.domain == NSURLErrorDomain {
|
|
388
|
+
return "network_error"
|
|
389
|
+
}
|
|
352
390
|
// GIDSignIn error codes
|
|
353
391
|
if nsError.domain == "com.google.GIDSignIn" {
|
|
354
392
|
switch nsError.code {
|
|
355
393
|
case -5: return "cancelled" // GIDSignInErrorCodeCanceled
|
|
356
|
-
case -4: return "
|
|
394
|
+
case -4: return "not_signed_in" // GIDSignInErrorCodeNoCurrentUser
|
|
357
395
|
default: break
|
|
358
396
|
}
|
|
359
397
|
}
|
|
@@ -386,9 +424,8 @@ public class AuthAdapter: NSObject {
|
|
|
386
424
|
public static func addScopes(scopes: [String], completion: @escaping (NSDictionary?, String?) -> Void) {
|
|
387
425
|
if let currentUser = GIDSignIn.sharedInstance.currentUser {
|
|
388
426
|
DispatchQueue.main.async {
|
|
389
|
-
guard let
|
|
390
|
-
|
|
391
|
-
completion(nil, "No root view controller found")
|
|
427
|
+
guard let rootVC = presentingViewController() else {
|
|
428
|
+
completion(nil, "no_window")
|
|
392
429
|
return
|
|
393
430
|
}
|
|
394
431
|
currentUser.addScopes(scopes, presenting: rootVC) { result, error in
|
|
@@ -397,11 +434,17 @@ public class AuthAdapter: NSObject {
|
|
|
397
434
|
}
|
|
398
435
|
return
|
|
399
436
|
}
|
|
400
|
-
|
|
401
|
-
|
|
437
|
+
tokenStoreLock.lock()
|
|
438
|
+
let hasRefreshToken = inMemoryMicrosoftRefreshToken != nil
|
|
439
|
+
let currentScopes = inMemoryMicrosoftScopes
|
|
440
|
+
tokenStoreLock.unlock()
|
|
441
|
+
guard hasRefreshToken else {
|
|
442
|
+
completion(nil, "not_signed_in")
|
|
402
443
|
return
|
|
403
444
|
}
|
|
404
|
-
let mergedScopes =
|
|
445
|
+
let mergedScopes = (currentScopes + scopes).reduce(into: [String]()) { acc, s in
|
|
446
|
+
if !acc.contains(s) { acc.append(s) }
|
|
447
|
+
}
|
|
405
448
|
loginMicrosoft(scopes: mergedScopes, loginHint: nil, tenant: nil, prompt: nil, completion: completion)
|
|
406
449
|
}
|
|
407
450
|
|
|
@@ -410,7 +453,7 @@ public class AuthAdapter: NSObject {
|
|
|
410
453
|
if let currentUser = GIDSignIn.sharedInstance.currentUser {
|
|
411
454
|
currentUser.refreshTokensIfNeeded { user, error in
|
|
412
455
|
if let error = error {
|
|
413
|
-
completion(nil, error
|
|
456
|
+
completion(nil, mapError(error))
|
|
414
457
|
return
|
|
415
458
|
}
|
|
416
459
|
guard let user = user else {
|
|
@@ -435,6 +478,9 @@ public class AuthAdapter: NSObject {
|
|
|
435
478
|
if Bundle.main.object(forInfoDictionaryKey: "GIDClientID") != nil {
|
|
436
479
|
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
|
|
437
480
|
if let user = user {
|
|
481
|
+
tokenStoreLock.lock()
|
|
482
|
+
let cachedServerAuthCode = inMemoryGoogleServerAuthCode
|
|
483
|
+
tokenStoreLock.unlock()
|
|
438
484
|
let data: [String: Any] = [
|
|
439
485
|
"provider": "google",
|
|
440
486
|
"email": user.profile?.email ?? "",
|
|
@@ -442,7 +488,7 @@ public class AuthAdapter: NSObject {
|
|
|
442
488
|
"photo": user.profile?.imageURL(withDimension: 300)?.absoluteString ?? "",
|
|
443
489
|
"idToken": user.idToken?.tokenString ?? "",
|
|
444
490
|
"accessToken": user.accessToken.tokenString,
|
|
445
|
-
"serverAuthCode":
|
|
491
|
+
"serverAuthCode": cachedServerAuthCode ?? "",
|
|
446
492
|
"expirationTime": (user.accessToken.expirationDate?.timeIntervalSince1970 ?? 0) * 1000
|
|
447
493
|
]
|
|
448
494
|
completion(data as NSDictionary)
|
|
@@ -456,7 +502,11 @@ public class AuthAdapter: NSObject {
|
|
|
456
502
|
}
|
|
457
503
|
|
|
458
504
|
private static func tryMicrosoftSilentRefresh(completion: @escaping (NSDictionary?) -> Void) {
|
|
459
|
-
|
|
505
|
+
tokenStoreLock.lock()
|
|
506
|
+
let refreshToken = inMemoryMicrosoftRefreshToken
|
|
507
|
+
let currentScopes = inMemoryMicrosoftScopes
|
|
508
|
+
tokenStoreLock.unlock()
|
|
509
|
+
guard let refreshToken = refreshToken else {
|
|
460
510
|
completion(nil)
|
|
461
511
|
return
|
|
462
512
|
}
|
|
@@ -468,13 +518,16 @@ public class AuthAdapter: NSObject {
|
|
|
468
518
|
|
|
469
519
|
let tenant = Bundle.main.object(forInfoDictionaryKey: "MSALTenant") as? String ?? "common"
|
|
470
520
|
let b2cDomain = Bundle.main.object(forInfoDictionaryKey: "MSALB2cDomain") as? String
|
|
471
|
-
let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain)
|
|
472
|
-
|
|
473
|
-
|
|
521
|
+
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain),
|
|
522
|
+
let tokenUrl = URL(string: "\(authBaseUrl)oauth2/v2.0/token") else {
|
|
523
|
+
completion(nil)
|
|
524
|
+
return
|
|
525
|
+
}
|
|
526
|
+
|
|
474
527
|
var request = URLRequest(url: tokenUrl)
|
|
475
528
|
request.httpMethod = "POST"
|
|
476
529
|
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
|
477
|
-
|
|
530
|
+
|
|
478
531
|
let bodyParams = [
|
|
479
532
|
"client_id": clientId,
|
|
480
533
|
"grant_type": "refresh_token",
|
|
@@ -488,23 +541,42 @@ public class AuthAdapter: NSObject {
|
|
|
488
541
|
|
|
489
542
|
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
490
543
|
DispatchQueue.main.async {
|
|
544
|
+
if let error = error {
|
|
545
|
+
#if DEBUG
|
|
546
|
+
print("[NitroAuth] Microsoft silent refresh network error: \(error.localizedDescription)")
|
|
547
|
+
#endif
|
|
548
|
+
completion(nil)
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
552
|
+
#if DEBUG
|
|
553
|
+
print("[NitroAuth] Microsoft silent refresh HTTP \(httpResponse.statusCode)")
|
|
554
|
+
#endif
|
|
555
|
+
completion(nil)
|
|
556
|
+
return
|
|
557
|
+
}
|
|
491
558
|
guard let data = data,
|
|
492
559
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
493
560
|
let idToken = json["id_token"] as? String else {
|
|
561
|
+
#if DEBUG
|
|
562
|
+
print("[NitroAuth] Microsoft silent refresh: failed to parse token response")
|
|
563
|
+
#endif
|
|
494
564
|
completion(nil)
|
|
495
565
|
return
|
|
496
566
|
}
|
|
497
|
-
|
|
567
|
+
|
|
498
568
|
let claims = decodeJwt(idToken)
|
|
499
569
|
let accessToken = json["access_token"] as? String ?? ""
|
|
500
570
|
let newRefreshToken = json["refresh_token"] as? String ?? ""
|
|
501
|
-
let expiresIn = json["expires_in"] as? Double ?? 0
|
|
571
|
+
let expiresIn = (json["expires_in"] as? Double).flatMap { $0 > 0 ? $0 : nil } ?? 3600.0
|
|
502
572
|
let expirationTime = Date().timeIntervalSince1970 * 1000 + expiresIn * 1000
|
|
503
|
-
|
|
573
|
+
|
|
574
|
+
tokenStoreLock.lock()
|
|
504
575
|
if !newRefreshToken.isEmpty {
|
|
505
576
|
inMemoryMicrosoftRefreshToken = newRefreshToken
|
|
506
577
|
}
|
|
507
|
-
|
|
578
|
+
tokenStoreLock.unlock()
|
|
579
|
+
|
|
508
580
|
let resultData: [String: Any] = [
|
|
509
581
|
"provider": "microsoft",
|
|
510
582
|
"email": claims["preferred_username"] ?? claims["email"] ?? "",
|
|
@@ -513,6 +585,7 @@ public class AuthAdapter: NSObject {
|
|
|
513
585
|
"idToken": idToken,
|
|
514
586
|
"accessToken": accessToken,
|
|
515
587
|
"serverAuthCode": "",
|
|
588
|
+
"scopes": currentScopes,
|
|
516
589
|
"expirationTime": expirationTime
|
|
517
590
|
]
|
|
518
591
|
completion(resultData as NSDictionary)
|
|
@@ -521,8 +594,11 @@ public class AuthAdapter: NSObject {
|
|
|
521
594
|
}
|
|
522
595
|
|
|
523
596
|
private static func tryMicrosoftRefreshForTokenRefresh(completion: @escaping (NSDictionary?, String?) -> Void) {
|
|
524
|
-
|
|
525
|
-
|
|
597
|
+
tokenStoreLock.lock()
|
|
598
|
+
let refreshToken = inMemoryMicrosoftRefreshToken
|
|
599
|
+
tokenStoreLock.unlock()
|
|
600
|
+
guard let refreshToken = refreshToken else {
|
|
601
|
+
completion(nil, "not_signed_in")
|
|
526
602
|
return
|
|
527
603
|
}
|
|
528
604
|
guard let clientId = Bundle.main.object(forInfoDictionaryKey: "MSALClientID") as? String, !clientId.isEmpty else {
|
|
@@ -531,8 +607,11 @@ public class AuthAdapter: NSObject {
|
|
|
531
607
|
}
|
|
532
608
|
let tenant = Bundle.main.object(forInfoDictionaryKey: "MSALTenant") as? String ?? "common"
|
|
533
609
|
let b2cDomain = Bundle.main.object(forInfoDictionaryKey: "MSALB2cDomain") as? String
|
|
534
|
-
let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain)
|
|
535
|
-
|
|
610
|
+
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: tenant, b2cDomain: b2cDomain),
|
|
611
|
+
let tokenUrl = URL(string: "\(authBaseUrl)oauth2/v2.0/token") else {
|
|
612
|
+
completion(nil, "configuration_error")
|
|
613
|
+
return
|
|
614
|
+
}
|
|
536
615
|
var request = URLRequest(url: tokenUrl)
|
|
537
616
|
request.httpMethod = "POST"
|
|
538
617
|
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
|
|
@@ -553,21 +632,31 @@ public class AuthAdapter: NSObject {
|
|
|
553
632
|
}
|
|
554
633
|
guard let data = data,
|
|
555
634
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
556
|
-
|
|
635
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
636
|
+
completion(nil, "network_error")
|
|
637
|
+
} else {
|
|
638
|
+
completion(nil, "parse_error")
|
|
639
|
+
}
|
|
557
640
|
return
|
|
558
641
|
}
|
|
559
642
|
if let errorCode = json["error"] as? String {
|
|
560
643
|
completion(nil, AuthAdapter.mapOAuthError(errorCode))
|
|
561
644
|
return
|
|
562
645
|
}
|
|
646
|
+
if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
|
|
647
|
+
completion(nil, "network_error")
|
|
648
|
+
return
|
|
649
|
+
}
|
|
563
650
|
let idToken = json["id_token"] as? String ?? ""
|
|
564
651
|
let accessToken = json["access_token"] as? String ?? ""
|
|
565
652
|
let newRefreshToken = json["refresh_token"] as? String ?? ""
|
|
566
|
-
let expiresIn = json["expires_in"] as? Double ?? 0
|
|
653
|
+
let expiresIn = (json["expires_in"] as? Double).flatMap { $0 > 0 ? $0 : nil } ?? 3600.0
|
|
567
654
|
let expirationTime = Date().timeIntervalSince1970 * 1000 + expiresIn * 1000
|
|
655
|
+
tokenStoreLock.lock()
|
|
568
656
|
if !newRefreshToken.isEmpty {
|
|
569
657
|
inMemoryMicrosoftRefreshToken = newRefreshToken
|
|
570
658
|
}
|
|
659
|
+
tokenStoreLock.unlock()
|
|
571
660
|
let tokensData: [String: Any] = [
|
|
572
661
|
"accessToken": accessToken,
|
|
573
662
|
"idToken": idToken,
|
|
@@ -579,24 +668,63 @@ public class AuthAdapter: NSObject {
|
|
|
579
668
|
}.resume()
|
|
580
669
|
}
|
|
581
670
|
|
|
582
|
-
private static func getMicrosoftAuthBaseUrl(tenant: String, b2cDomain: String?) -> String {
|
|
583
|
-
|
|
584
|
-
|
|
671
|
+
private static func getMicrosoftAuthBaseUrl(tenant: String, b2cDomain: String?) -> String? {
|
|
672
|
+
let trimmedTenant = tenant.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
673
|
+
guard !trimmedTenant.isEmpty else { return nil }
|
|
674
|
+
|
|
675
|
+
if trimmedTenant.hasPrefix("https://") {
|
|
676
|
+
guard URL(string: trimmedTenant) != nil else { return nil }
|
|
677
|
+
return trimmedTenant.hasSuffix("/") ? trimmedTenant : "\(trimmedTenant)/"
|
|
585
678
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return "https://\(domain)/tfp/\(tenant)/"
|
|
589
|
-
} else {
|
|
590
|
-
return "https://login.microsoftonline.com/\(tenant)/"
|
|
679
|
+
if let domain = b2cDomain?.trimmingCharacters(in: .whitespacesAndNewlines), !domain.isEmpty {
|
|
680
|
+
return "https://\(domain)/tfp/\(trimmedTenant)/"
|
|
591
681
|
}
|
|
682
|
+
return "https://login.microsoftonline.com/\(trimmedTenant)/"
|
|
592
683
|
}
|
|
593
684
|
|
|
594
685
|
@objc
|
|
595
686
|
public static func logout() {
|
|
596
687
|
GIDSignIn.sharedInstance.signOut()
|
|
688
|
+
tokenStoreLock.lock()
|
|
597
689
|
inMemoryMicrosoftRefreshToken = nil
|
|
598
690
|
inMemoryMicrosoftScopes = defaultMicrosoftScopes
|
|
599
691
|
inMemoryGoogleServerAuthCode = nil
|
|
692
|
+
tokenStoreLock.unlock()
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
private static func activeWindow() -> UIWindow? {
|
|
696
|
+
let windowScenes = UIApplication.shared.connectedScenes
|
|
697
|
+
.compactMap { $0 as? UIWindowScene }
|
|
698
|
+
.filter {
|
|
699
|
+
$0.activationState == .foregroundActive ||
|
|
700
|
+
$0.activationState == .foregroundInactive
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
for scene in windowScenes {
|
|
704
|
+
if let keyWindow = scene.windows.first(where: { $0.isKeyWindow }) {
|
|
705
|
+
return keyWindow
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return windowScenes.lazy.compactMap { $0.windows.first }.first
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
private static func presentingViewController() -> UIViewController? {
|
|
713
|
+
guard let rootViewController = activeWindow()?.rootViewController else {
|
|
714
|
+
return nil
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
var current = rootViewController
|
|
718
|
+
while let presented = current.presentedViewController {
|
|
719
|
+
current = presented
|
|
720
|
+
}
|
|
721
|
+
if let navigationController = current as? UINavigationController {
|
|
722
|
+
return navigationController.visibleViewController ?? navigationController
|
|
723
|
+
}
|
|
724
|
+
if let tabBarController = current as? UITabBarController {
|
|
725
|
+
return tabBarController.selectedViewController ?? tabBarController
|
|
726
|
+
}
|
|
727
|
+
return current
|
|
600
728
|
}
|
|
601
729
|
}
|
|
602
730
|
|
|
@@ -625,14 +753,28 @@ class AppleSignInDelegate: NSObject, ASAuthorizationControllerDelegate {
|
|
|
625
753
|
"underlyingError": ""
|
|
626
754
|
]
|
|
627
755
|
completion(data as NSDictionary, nil)
|
|
756
|
+
} else {
|
|
757
|
+
completion(nil, "unknown")
|
|
628
758
|
}
|
|
629
759
|
}
|
|
630
|
-
|
|
760
|
+
|
|
631
761
|
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
|
|
632
762
|
completion(nil, AuthAdapter.mapError(error))
|
|
633
763
|
}
|
|
634
764
|
}
|
|
635
765
|
|
|
766
|
+
class AppleSignInContextProvider: NSObject, ASAuthorizationControllerPresentationContextProviding {
|
|
767
|
+
let anchor: ASPresentationAnchor
|
|
768
|
+
|
|
769
|
+
init(anchor: ASPresentationAnchor) {
|
|
770
|
+
self.anchor = anchor
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
|
|
774
|
+
return anchor
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
636
778
|
class WebAuthContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
|
|
637
779
|
let anchor: ASPresentationAnchor
|
|
638
780
|
|
package/ios/PlatformAuth+iOS.mm
CHANGED
|
@@ -16,9 +16,25 @@
|
|
|
16
16
|
|
|
17
17
|
namespace margelo::nitro::NitroAuth {
|
|
18
18
|
|
|
19
|
-
inline std::string nsToStd(NSString* _Nullable ns) {
|
|
20
|
-
if (ns == nil) return
|
|
21
|
-
|
|
19
|
+
inline std::optional<std::string> nsToStd(NSString* _Nullable ns) {
|
|
20
|
+
if (ns == nil) return std::nullopt;
|
|
21
|
+
std::string value([ns UTF8String]);
|
|
22
|
+
if (value.empty()) return std::nullopt;
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
inline std::optional<std::vector<std::string>> nsArrayToStd(NSArray<NSString*>* _Nullable nsArray) {
|
|
27
|
+
if (nsArray == nil || nsArray.count == 0) return std::nullopt;
|
|
28
|
+
|
|
29
|
+
std::vector<std::string> values;
|
|
30
|
+
values.reserve(nsArray.count);
|
|
31
|
+
for (NSString* value in nsArray) {
|
|
32
|
+
if (value.length == 0) continue;
|
|
33
|
+
values.emplace_back([value UTF8String]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (values.empty()) return std::nullopt;
|
|
37
|
+
return values;
|
|
22
38
|
}
|
|
23
39
|
|
|
24
40
|
std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
|
|
@@ -85,6 +101,7 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
|
|
|
85
101
|
user.idToken = nsToStd([data objectForKey:@"idToken"]);
|
|
86
102
|
if ([data objectForKey:@"accessToken"]) user.accessToken = nsToStd([data objectForKey:@"accessToken"]);
|
|
87
103
|
if ([data objectForKey:@"serverAuthCode"]) user.serverAuthCode = nsToStd([data objectForKey:@"serverAuthCode"]);
|
|
104
|
+
if ([data objectForKey:@"scopes"]) user.scopes = nsArrayToStd([data objectForKey:@"scopes"]);
|
|
88
105
|
if ([data objectForKey:@"expirationTime"]) user.expirationTime = [[data objectForKey:@"expirationTime"] doubleValue];
|
|
89
106
|
if ([data objectForKey:@"underlyingError"]) user.underlyingError = nsToStd([data objectForKey:@"underlyingError"]);
|
|
90
107
|
|
|
@@ -109,13 +126,21 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::requestScopes(const std::vector
|
|
|
109
126
|
}
|
|
110
127
|
|
|
111
128
|
AuthUser user;
|
|
112
|
-
|
|
129
|
+
NSString *providerStr = [data objectForKey:@"provider"];
|
|
130
|
+
if ([providerStr isEqualToString:@"microsoft"]) {
|
|
131
|
+
user.provider = AuthProvider::MICROSOFT;
|
|
132
|
+
} else if ([providerStr isEqualToString:@"apple"]) {
|
|
133
|
+
user.provider = AuthProvider::APPLE;
|
|
134
|
+
} else {
|
|
135
|
+
user.provider = AuthProvider::GOOGLE;
|
|
136
|
+
}
|
|
113
137
|
user.email = nsToStd([data objectForKey:@"email"]);
|
|
114
138
|
user.name = nsToStd([data objectForKey:@"name"]);
|
|
115
139
|
user.photo = nsToStd([data objectForKey:@"photo"]);
|
|
116
140
|
user.idToken = nsToStd([data objectForKey:@"idToken"]);
|
|
117
141
|
if ([data objectForKey:@"accessToken"]) user.accessToken = nsToStd([data objectForKey:@"accessToken"]);
|
|
118
142
|
if ([data objectForKey:@"serverAuthCode"]) user.serverAuthCode = nsToStd([data objectForKey:@"serverAuthCode"]);
|
|
143
|
+
if ([data objectForKey:@"scopes"]) user.scopes = nsArrayToStd([data objectForKey:@"scopes"]);
|
|
119
144
|
if ([data objectForKey:@"expirationTime"]) user.expirationTime = [[data objectForKey:@"expirationTime"] doubleValue];
|
|
120
145
|
if ([data objectForKey:@"underlyingError"]) user.underlyingError = nsToStd([data objectForKey:@"underlyingError"]);
|
|
121
146
|
promise->resolve(user);
|
|
@@ -161,6 +186,7 @@ std::shared_ptr<Promise<std::optional<AuthUser>>> PlatformAuth::silentRestore()
|
|
|
161
186
|
user.idToken = nsToStd([data objectForKey:@"idToken"]);
|
|
162
187
|
if ([data objectForKey:@"accessToken"]) user.accessToken = nsToStd([data objectForKey:@"accessToken"]);
|
|
163
188
|
if ([data objectForKey:@"serverAuthCode"]) user.serverAuthCode = nsToStd([data objectForKey:@"serverAuthCode"]);
|
|
189
|
+
if ([data objectForKey:@"scopes"]) user.scopes = nsArrayToStd([data objectForKey:@"scopes"]);
|
|
164
190
|
if ([data objectForKey:@"expirationTime"]) user.expirationTime = [[data objectForKey:@"expirationTime"] doubleValue];
|
|
165
191
|
if ([data objectForKey:@"underlyingError"]) user.underlyingError = nsToStd([data objectForKey:@"underlyingError"]);
|
|
166
192
|
promise->resolve(user);
|