react-native-nitro-auth 0.5.12 → 0.6.0
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/CHANGELOG.md +23 -0
- package/README.md +88 -13
- package/android/build.gradle +4 -4
- package/android/gradle.properties +2 -2
- package/android/src/main/cpp/PlatformAuth+Android.cpp +87 -4
- package/android/src/main/java/com/auth/AuthAdapter.kt +85 -48
- package/android/src/main/java/com/auth/GoogleSignInActivity.kt +12 -2
- package/cpp/HybridAuth.cpp +168 -18
- package/cpp/HybridAuth.hpp +7 -0
- package/cpp/PlatformAuth.hpp +1 -0
- package/ios/AuthAdapter.swift +98 -24
- package/ios/PlatformAuth+iOS.mm +37 -1
- package/lib/commonjs/Auth.web.js +74 -21
- package/lib/commonjs/Auth.web.js.map +1 -1
- package/lib/commonjs/create-auth-service.js +10 -0
- package/lib/commonjs/create-auth-service.js.map +1 -1
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +12 -0
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/provider-options.js +6 -0
- package/lib/commonjs/provider-options.js.map +1 -0
- package/lib/commonjs/service.js.map +1 -1
- package/lib/commonjs/service.web.js.map +1 -1
- package/lib/commonjs/use-auth.js +21 -1
- package/lib/commonjs/use-auth.js.map +1 -1
- package/lib/module/Auth.web.js +74 -21
- package/lib/module/Auth.web.js.map +1 -1
- package/lib/module/create-auth-service.js +10 -0
- package/lib/module/create-auth-service.js.map +1 -1
- package/lib/module/global.d.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/provider-options.js +4 -0
- package/lib/module/provider-options.js.map +1 -0
- package/lib/module/service.js.map +1 -1
- package/lib/module/service.web.js.map +1 -1
- package/lib/module/use-auth.js +21 -1
- package/lib/module/use-auth.js.map +1 -1
- package/lib/typescript/commonjs/Auth.nitro.d.ts +11 -0
- package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
- package/lib/typescript/commonjs/Auth.web.d.ts +4 -0
- package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/create-auth-service.d.ts +2 -1
- package/lib/typescript/commonjs/create-auth-service.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/index.web.d.ts +1 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/provider-options.d.ts +23 -0
- package/lib/typescript/commonjs/provider-options.d.ts.map +1 -0
- package/lib/typescript/commonjs/service.d.ts +2 -2
- package/lib/typescript/commonjs/service.d.ts.map +1 -1
- package/lib/typescript/commonjs/service.web.d.ts +2 -2
- package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/use-auth.d.ts +4 -2
- package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
- package/lib/typescript/module/Auth.nitro.d.ts +11 -0
- package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
- package/lib/typescript/module/Auth.web.d.ts +4 -0
- package/lib/typescript/module/Auth.web.d.ts.map +1 -1
- package/lib/typescript/module/create-auth-service.d.ts +2 -1
- package/lib/typescript/module/create-auth-service.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/index.web.d.ts +1 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/provider-options.d.ts +23 -0
- package/lib/typescript/module/provider-options.d.ts.map +1 -0
- package/lib/typescript/module/service.d.ts +2 -2
- package/lib/typescript/module/service.d.ts.map +1 -1
- package/lib/typescript/module/service.web.d.ts +2 -2
- package/lib/typescript/module/service.web.d.ts.map +1 -1
- package/lib/typescript/module/use-auth.d.ts +4 -2
- package/lib/typescript/module/use-auth.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/AuthUser.hpp +17 -1
- package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +1 -0
- package/nitrogen/generated/shared/c++/LoginOptions.hpp +25 -1
- package/package.json +2 -2
- package/src/Auth.nitro.ts +11 -0
- package/src/Auth.web.ts +99 -16
- package/src/create-auth-service.ts +19 -9
- package/src/global.d.ts +2 -1
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
- package/src/provider-options.ts +62 -0
- package/src/service.ts +2 -1
- package/src/service.web.ts +2 -2
- package/src/use-auth.ts +22 -8
package/cpp/HybridAuth.cpp
CHANGED
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
#include <algorithm>
|
|
4
4
|
#include <chrono>
|
|
5
5
|
#include <exception>
|
|
6
|
+
#include <iostream>
|
|
6
7
|
#include <stdexcept>
|
|
7
8
|
#include <unordered_set>
|
|
8
9
|
|
|
10
|
+
#if defined(__ANDROID__)
|
|
11
|
+
#include <android/log.h>
|
|
12
|
+
#endif
|
|
13
|
+
|
|
9
14
|
namespace margelo::nitro::NitroAuth {
|
|
10
15
|
|
|
11
16
|
namespace {
|
|
@@ -20,6 +25,32 @@ void rejectIfPending(const std::shared_ptr<Promise<AuthTokens>>& promise, const
|
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
void rejectIfPending(const std::shared_ptr<Promise<void>>& promise, const char* message) {
|
|
29
|
+
if (promise && promise->isPending()) {
|
|
30
|
+
promise->reject(makeAuthError(message));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void resolveIfPending(const std::shared_ptr<Promise<void>>& promise) {
|
|
35
|
+
if (promise && promise->isPending()) {
|
|
36
|
+
promise->resolve();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void rejectPendingSessionPromises(const std::vector<std::shared_ptr<Promise<void>>>& promises, const char* message) {
|
|
41
|
+
for (const auto& promise : promises) {
|
|
42
|
+
rejectIfPending(promise, message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
void writeNativeLog(const std::string& message) {
|
|
47
|
+
#if defined(__ANDROID__)
|
|
48
|
+
__android_log_print(ANDROID_LOG_DEBUG, "NitroAuth", "%s", message.c_str());
|
|
49
|
+
#else
|
|
50
|
+
std::clog << "[NitroAuth] " << message << std::endl;
|
|
51
|
+
#endif
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
void mergeGrantedScopes(std::vector<std::string>& grantedScopes, const std::vector<std::string>& scopes) {
|
|
24
55
|
std::unordered_set<std::string> knownScopes(grantedScopes.begin(), grantedScopes.end());
|
|
25
56
|
grantedScopes.reserve(grantedScopes.size() + scopes.size());
|
|
@@ -130,25 +161,65 @@ std::shared_ptr<Promise<AuthTokens>> HybridAuth::advanceSessionGenerationLocked(
|
|
|
130
161
|
return refreshInFlight;
|
|
131
162
|
}
|
|
132
163
|
|
|
164
|
+
void HybridAuth::trackSessionPromiseLocked(const std::shared_ptr<Promise<void>>& promise) {
|
|
165
|
+
_sessionPromises.erase(
|
|
166
|
+
std::remove_if(_sessionPromises.begin(), _sessionPromises.end(), [](const std::weak_ptr<Promise<void>>& weak) {
|
|
167
|
+
auto promise = weak.lock();
|
|
168
|
+
return !promise || !promise->isPending();
|
|
169
|
+
}),
|
|
170
|
+
_sessionPromises.end()
|
|
171
|
+
);
|
|
172
|
+
_sessionPromises.push_back(promise);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
std::vector<std::shared_ptr<Promise<void>>> HybridAuth::takePendingSessionPromisesLocked() {
|
|
176
|
+
std::vector<std::shared_ptr<Promise<void>>> pending;
|
|
177
|
+
for (const auto& weak : _sessionPromises) {
|
|
178
|
+
auto promise = weak.lock();
|
|
179
|
+
if (promise && promise->isPending()) {
|
|
180
|
+
pending.push_back(promise);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
_sessionPromises.clear();
|
|
184
|
+
return pending;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
void HybridAuth::log(const std::string& message) {
|
|
188
|
+
bool enabled;
|
|
189
|
+
{
|
|
190
|
+
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
191
|
+
enabled = _loggingEnabled;
|
|
192
|
+
}
|
|
193
|
+
if (enabled) {
|
|
194
|
+
writeNativeLog(message);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
133
198
|
void HybridAuth::logout() {
|
|
199
|
+
log("logout");
|
|
134
200
|
std::shared_ptr<Promise<AuthTokens>> refreshInFlight;
|
|
201
|
+
std::vector<std::shared_ptr<Promise<void>>> sessionPromises;
|
|
135
202
|
{
|
|
136
203
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
204
|
+
sessionPromises = takePendingSessionPromisesLocked();
|
|
137
205
|
refreshInFlight = advanceSessionGenerationLocked();
|
|
138
206
|
_currentUser = std::nullopt;
|
|
139
207
|
_grantedScopes.clear();
|
|
140
208
|
}
|
|
141
209
|
rejectIfPending(refreshInFlight, "not_signed_in");
|
|
210
|
+
rejectPendingSessionPromises(sessionPromises, "cancelled");
|
|
142
211
|
PlatformAuth::logout();
|
|
143
212
|
notifyAuthStateChanged();
|
|
144
213
|
}
|
|
145
214
|
|
|
146
215
|
std::shared_ptr<Promise<void>> HybridAuth::silentRestore() {
|
|
216
|
+
log("silentRestore start");
|
|
147
217
|
auto promise = Promise<void>::create();
|
|
148
218
|
uint64_t generation;
|
|
149
219
|
{
|
|
150
220
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
151
221
|
generation = _sessionGeneration;
|
|
222
|
+
trackSessionPromiseLocked(promise);
|
|
152
223
|
}
|
|
153
224
|
auto silentPromise = PlatformAuth::silentRestore();
|
|
154
225
|
auto self = shared_from_this();
|
|
@@ -162,7 +233,8 @@ std::shared_ptr<Promise<void>> HybridAuth::silentRestore() {
|
|
|
162
233
|
{
|
|
163
234
|
std::lock_guard<std::recursive_mutex> lock(auth->_mutex);
|
|
164
235
|
if (auth->_sessionGeneration != generation) {
|
|
165
|
-
|
|
236
|
+
auth->log("silentRestore cancelled");
|
|
237
|
+
resolveIfPending(promise);
|
|
166
238
|
return;
|
|
167
239
|
}
|
|
168
240
|
refreshInFlight = auth->advanceSessionGenerationLocked();
|
|
@@ -178,42 +250,51 @@ std::shared_ptr<Promise<void>> HybridAuth::silentRestore() {
|
|
|
178
250
|
}
|
|
179
251
|
}
|
|
180
252
|
rejectIfPending(refreshInFlight, "cancelled");
|
|
181
|
-
// Always resolve - no session is not an error, just means user is logged out
|
|
182
253
|
auth->notifyAuthStateChanged();
|
|
183
|
-
|
|
254
|
+
auth->log(user ? "silentRestore resolved with session" : "silentRestore resolved without session");
|
|
255
|
+
resolveIfPending(promise);
|
|
184
256
|
});
|
|
185
257
|
|
|
186
|
-
silentPromise->addOnRejectedListener([promise](const std::exception_ptr&) {
|
|
187
|
-
|
|
188
|
-
|
|
258
|
+
silentPromise->addOnRejectedListener([self, promise](const std::exception_ptr&) {
|
|
259
|
+
auto* auth = dynamic_cast<HybridAuth*>(self.get());
|
|
260
|
+
if (auth) {
|
|
261
|
+
auth->log("silentRestore rejected");
|
|
262
|
+
}
|
|
263
|
+
resolveIfPending(promise);
|
|
189
264
|
});
|
|
190
265
|
return promise;
|
|
191
266
|
}
|
|
192
267
|
|
|
193
268
|
std::shared_ptr<Promise<void>> HybridAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
|
|
269
|
+
log("login start");
|
|
194
270
|
auto promise = Promise<void>::create();
|
|
195
271
|
uint64_t generation;
|
|
196
272
|
std::shared_ptr<Promise<AuthTokens>> refreshInFlight;
|
|
273
|
+
std::vector<std::shared_ptr<Promise<void>>> sessionPromises;
|
|
197
274
|
{
|
|
198
275
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
276
|
+
sessionPromises = takePendingSessionPromisesLocked();
|
|
199
277
|
refreshInFlight = advanceSessionGenerationLocked();
|
|
200
278
|
generation = _sessionGeneration;
|
|
279
|
+
trackSessionPromiseLocked(promise);
|
|
201
280
|
}
|
|
202
281
|
rejectIfPending(refreshInFlight, "cancelled");
|
|
282
|
+
rejectPendingSessionPromises(sessionPromises, "cancelled");
|
|
203
283
|
|
|
204
284
|
auto self = shared_from_this();
|
|
205
285
|
auto loginPromise = PlatformAuth::login(provider, options);
|
|
206
286
|
loginPromise->addOnResolvedListener([self, promise, options, generation](const AuthUser& user) {
|
|
207
287
|
auto* auth = dynamic_cast<HybridAuth*>(self.get());
|
|
208
288
|
if (!auth) {
|
|
209
|
-
promise
|
|
289
|
+
rejectIfPending(promise, "internal_error");
|
|
210
290
|
return;
|
|
211
291
|
}
|
|
212
292
|
std::shared_ptr<Promise<AuthTokens>> refreshInFlight;
|
|
213
293
|
{
|
|
214
294
|
std::lock_guard<std::recursive_mutex> lock(auth->_mutex);
|
|
215
295
|
if (auth->_sessionGeneration != generation) {
|
|
216
|
-
|
|
296
|
+
auth->log("login cancelled");
|
|
297
|
+
rejectIfPending(promise, "cancelled");
|
|
217
298
|
return;
|
|
218
299
|
}
|
|
219
300
|
refreshInFlight = auth->advanceSessionGenerationLocked();
|
|
@@ -233,34 +314,44 @@ std::shared_ptr<Promise<void>> HybridAuth::login(AuthProvider provider, const st
|
|
|
233
314
|
}
|
|
234
315
|
rejectIfPending(refreshInFlight, "cancelled");
|
|
235
316
|
auth->notifyAuthStateChanged();
|
|
236
|
-
|
|
317
|
+
auth->log("login resolved");
|
|
318
|
+
resolveIfPending(promise);
|
|
237
319
|
});
|
|
238
320
|
|
|
239
|
-
loginPromise->addOnRejectedListener([promise](const std::exception_ptr& error) {
|
|
240
|
-
|
|
321
|
+
loginPromise->addOnRejectedListener([self, promise](const std::exception_ptr& error) {
|
|
322
|
+
auto* auth = dynamic_cast<HybridAuth*>(self.get());
|
|
323
|
+
if (auth) {
|
|
324
|
+
auth->log("login rejected");
|
|
325
|
+
}
|
|
326
|
+
if (promise->isPending()) {
|
|
327
|
+
promise->reject(error);
|
|
328
|
+
}
|
|
241
329
|
});
|
|
242
330
|
return promise;
|
|
243
331
|
}
|
|
244
332
|
|
|
245
333
|
std::shared_ptr<Promise<void>> HybridAuth::requestScopes(const std::vector<std::string>& scopes) {
|
|
334
|
+
log("requestScopes start");
|
|
246
335
|
auto promise = Promise<void>::create();
|
|
247
336
|
uint64_t generation;
|
|
248
337
|
{
|
|
249
338
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
250
339
|
generation = _sessionGeneration;
|
|
340
|
+
trackSessionPromiseLocked(promise);
|
|
251
341
|
}
|
|
252
342
|
auto self = shared_from_this();
|
|
253
343
|
auto requestPromise = PlatformAuth::requestScopes(scopes);
|
|
254
344
|
requestPromise->addOnResolvedListener([self, promise, scopes, generation](const AuthUser& user) {
|
|
255
345
|
auto* auth = dynamic_cast<HybridAuth*>(self.get());
|
|
256
346
|
if (!auth) {
|
|
257
|
-
promise
|
|
347
|
+
rejectIfPending(promise, "internal_error");
|
|
258
348
|
return;
|
|
259
349
|
}
|
|
260
350
|
{
|
|
261
351
|
std::lock_guard<std::recursive_mutex> lock(auth->_mutex);
|
|
262
352
|
if (auth->_sessionGeneration != generation) {
|
|
263
|
-
|
|
353
|
+
auth->log("requestScopes cancelled");
|
|
354
|
+
rejectIfPending(promise, "cancelled");
|
|
264
355
|
return;
|
|
265
356
|
}
|
|
266
357
|
auth->_currentUser = user;
|
|
@@ -268,16 +359,24 @@ std::shared_ptr<Promise<void>> HybridAuth::requestScopes(const std::vector<std::
|
|
|
268
359
|
if (auth->_currentUser) auth->_currentUser->scopes = auth->_grantedScopes;
|
|
269
360
|
}
|
|
270
361
|
auth->notifyAuthStateChanged();
|
|
271
|
-
|
|
362
|
+
auth->log("requestScopes resolved");
|
|
363
|
+
resolveIfPending(promise);
|
|
272
364
|
});
|
|
273
365
|
|
|
274
|
-
requestPromise->addOnRejectedListener([promise](const std::exception_ptr& error) {
|
|
275
|
-
|
|
366
|
+
requestPromise->addOnRejectedListener([self, promise](const std::exception_ptr& error) {
|
|
367
|
+
auto* auth = dynamic_cast<HybridAuth*>(self.get());
|
|
368
|
+
if (auth) {
|
|
369
|
+
auth->log("requestScopes rejected");
|
|
370
|
+
}
|
|
371
|
+
if (promise->isPending()) {
|
|
372
|
+
promise->reject(error);
|
|
373
|
+
}
|
|
276
374
|
});
|
|
277
375
|
return promise;
|
|
278
376
|
}
|
|
279
377
|
|
|
280
378
|
std::shared_ptr<Promise<void>> HybridAuth::revokeScopes(const std::vector<std::string>& scopes) {
|
|
379
|
+
log("revokeScopes");
|
|
281
380
|
auto promise = Promise<void>::create();
|
|
282
381
|
{
|
|
283
382
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
@@ -291,7 +390,48 @@ std::shared_ptr<Promise<void>> HybridAuth::revokeScopes(const std::vector<std::s
|
|
|
291
390
|
return promise;
|
|
292
391
|
}
|
|
293
392
|
|
|
393
|
+
std::shared_ptr<Promise<void>> HybridAuth::revokeAccess() {
|
|
394
|
+
log("revokeAccess start");
|
|
395
|
+
auto promise = Promise<void>::create();
|
|
396
|
+
std::shared_ptr<Promise<AuthTokens>> refreshInFlight;
|
|
397
|
+
std::vector<std::shared_ptr<Promise<void>>> sessionPromises;
|
|
398
|
+
{
|
|
399
|
+
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
400
|
+
sessionPromises = takePendingSessionPromisesLocked();
|
|
401
|
+
refreshInFlight = advanceSessionGenerationLocked();
|
|
402
|
+
trackSessionPromiseLocked(promise);
|
|
403
|
+
_currentUser = std::nullopt;
|
|
404
|
+
_grantedScopes.clear();
|
|
405
|
+
}
|
|
406
|
+
rejectIfPending(refreshInFlight, "cancelled");
|
|
407
|
+
rejectPendingSessionPromises(sessionPromises, "cancelled");
|
|
408
|
+
|
|
409
|
+
auto platformPromise = PlatformAuth::revokeAccess();
|
|
410
|
+
auto self = shared_from_this();
|
|
411
|
+
platformPromise->addOnResolvedListener([self, promise]() {
|
|
412
|
+
auto* auth = dynamic_cast<HybridAuth*>(self.get());
|
|
413
|
+
if (!auth) {
|
|
414
|
+
rejectIfPending(promise, "internal_error");
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
auth->notifyAuthStateChanged();
|
|
418
|
+
auth->log("revokeAccess resolved");
|
|
419
|
+
resolveIfPending(promise);
|
|
420
|
+
});
|
|
421
|
+
platformPromise->addOnRejectedListener([self, promise](const std::exception_ptr& error) {
|
|
422
|
+
auto* auth = dynamic_cast<HybridAuth*>(self.get());
|
|
423
|
+
if (auth) {
|
|
424
|
+
auth->log("revokeAccess rejected");
|
|
425
|
+
}
|
|
426
|
+
if (promise->isPending()) {
|
|
427
|
+
promise->reject(error);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
return promise;
|
|
431
|
+
}
|
|
432
|
+
|
|
294
433
|
std::shared_ptr<Promise<std::optional<std::string>>> HybridAuth::getAccessToken() {
|
|
434
|
+
log("getAccessToken");
|
|
295
435
|
auto promise = Promise<std::optional<std::string>>::create();
|
|
296
436
|
bool needsRefresh = false;
|
|
297
437
|
std::optional<std::string> cachedAccessToken;
|
|
@@ -326,6 +466,7 @@ std::shared_ptr<Promise<std::optional<std::string>>> HybridAuth::getAccessToken(
|
|
|
326
466
|
}
|
|
327
467
|
|
|
328
468
|
std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
|
|
469
|
+
log("refreshToken start");
|
|
329
470
|
std::shared_ptr<Promise<AuthTokens>> promise;
|
|
330
471
|
uint64_t generation;
|
|
331
472
|
{
|
|
@@ -380,6 +521,7 @@ std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
|
|
|
380
521
|
}
|
|
381
522
|
auth->notifyTokensRefreshed(tokens);
|
|
382
523
|
auth->notifyAuthStateChanged();
|
|
524
|
+
auth->log("refreshToken resolved");
|
|
383
525
|
promise->resolve(tokens);
|
|
384
526
|
});
|
|
385
527
|
|
|
@@ -402,16 +544,24 @@ std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
|
|
|
402
544
|
}
|
|
403
545
|
}
|
|
404
546
|
if (isStale) {
|
|
547
|
+
auth->log("refreshToken cancelled");
|
|
405
548
|
rejectIfPending(promise, "cancelled");
|
|
406
549
|
return;
|
|
407
550
|
}
|
|
551
|
+
auth->log("refreshToken rejected");
|
|
408
552
|
promise->reject(error);
|
|
409
553
|
});
|
|
410
554
|
return promise;
|
|
411
555
|
}
|
|
412
556
|
|
|
413
|
-
void HybridAuth::setLoggingEnabled(bool
|
|
414
|
-
|
|
557
|
+
void HybridAuth::setLoggingEnabled(bool enabled) {
|
|
558
|
+
{
|
|
559
|
+
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
560
|
+
_loggingEnabled = enabled;
|
|
561
|
+
}
|
|
562
|
+
if (enabled) {
|
|
563
|
+
writeNativeLog("native logging enabled");
|
|
564
|
+
}
|
|
415
565
|
}
|
|
416
566
|
|
|
417
567
|
void HybridAuth::notifyTokensRefreshed(const AuthTokens& tokens) {
|
package/cpp/HybridAuth.hpp
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#include <memory>
|
|
11
11
|
#include <string>
|
|
12
12
|
#include <map>
|
|
13
|
+
#include <vector>
|
|
13
14
|
|
|
14
15
|
namespace margelo::nitro::NitroAuth {
|
|
15
16
|
|
|
@@ -24,6 +25,7 @@ public:
|
|
|
24
25
|
std::shared_ptr<Promise<void>> login(AuthProvider provider, const std::optional<LoginOptions>& options) override;
|
|
25
26
|
std::shared_ptr<Promise<void>> requestScopes(const std::vector<std::string>& scopes) override;
|
|
26
27
|
std::shared_ptr<Promise<void>> revokeScopes(const std::vector<std::string>& scopes) override;
|
|
28
|
+
std::shared_ptr<Promise<void>> revokeAccess() override;
|
|
27
29
|
std::shared_ptr<Promise<std::optional<std::string>>> getAccessToken() override;
|
|
28
30
|
std::shared_ptr<Promise<AuthTokens>> refreshToken() override;
|
|
29
31
|
|
|
@@ -39,6 +41,9 @@ private:
|
|
|
39
41
|
void notifyAuthStateChanged();
|
|
40
42
|
void notifyTokensRefreshed(const AuthTokens& tokens);
|
|
41
43
|
std::shared_ptr<Promise<AuthTokens>> advanceSessionGenerationLocked();
|
|
44
|
+
void trackSessionPromiseLocked(const std::shared_ptr<Promise<void>>& promise);
|
|
45
|
+
std::vector<std::shared_ptr<Promise<void>>> takePendingSessionPromisesLocked();
|
|
46
|
+
void log(const std::string& message);
|
|
42
47
|
|
|
43
48
|
private:
|
|
44
49
|
std::optional<AuthUser> _currentUser;
|
|
@@ -49,7 +54,9 @@ private:
|
|
|
49
54
|
std::map<uint64_t, std::function<void(const AuthTokens&)>> _tokenListeners;
|
|
50
55
|
uint64_t _nextTokenListenerId = 0;
|
|
51
56
|
std::shared_ptr<Promise<AuthTokens>> _refreshInFlight;
|
|
57
|
+
std::vector<std::weak_ptr<Promise<void>>> _sessionPromises;
|
|
52
58
|
uint64_t _sessionGeneration = 0;
|
|
59
|
+
bool _loggingEnabled = false;
|
|
53
60
|
|
|
54
61
|
// recursive_mutex: listeners resolved inside a lock scope may re-enter Auth methods
|
|
55
62
|
// that also acquire _mutex, causing deadlock with a non-recursive mutex.
|
package/cpp/PlatformAuth.hpp
CHANGED
package/ios/AuthAdapter.swift
CHANGED
|
@@ -12,14 +12,42 @@ public class AuthAdapter: NSObject {
|
|
|
12
12
|
private static var inMemoryGoogleServerAuthCode: String?
|
|
13
13
|
private static var activeMicrosoftWebAuthSession: ASWebAuthenticationSession?
|
|
14
14
|
private static let tokenStoreLock = NSLock()
|
|
15
|
+
private static let interactiveAuthLock = NSLock()
|
|
16
|
+
private static var interactiveAuthInProgress = false
|
|
17
|
+
|
|
18
|
+
private static func beginInteractiveAuth() -> Bool {
|
|
19
|
+
interactiveAuthLock.lock()
|
|
20
|
+
defer { interactiveAuthLock.unlock() }
|
|
21
|
+
if interactiveAuthInProgress {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
interactiveAuthInProgress = true
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static func finishInteractiveAuth() {
|
|
29
|
+
interactiveAuthLock.lock()
|
|
30
|
+
interactiveAuthInProgress = false
|
|
31
|
+
interactiveAuthLock.unlock()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private static func completeInteractiveAuth(_ completion: @escaping (NSDictionary?, String?) -> Void) -> (NSDictionary?, String?) -> Void {
|
|
35
|
+
return { data, error in
|
|
36
|
+
finishInteractiveAuth()
|
|
37
|
+
completion(data, error)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
15
40
|
|
|
16
41
|
@objc
|
|
17
|
-
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) {
|
|
18
|
-
// useSheet is accepted for API compatibility with Android but has no effect on iOS.
|
|
19
|
-
// Google Sign-In SDK controls its own presentation style.
|
|
42
|
+
public static func login(provider: String, scopes: [String], loginHint: String?, nonce: String?, useSheet: Bool, forceAccountPicker: Bool = false, tenant: String? = nil, prompt: String? = nil, hostedDomain: String? = nil, openIDRealm: String? = nil, completion: @escaping (NSDictionary?, String?) -> Void) {
|
|
20
43
|
if provider == "google" {
|
|
44
|
+
guard beginInteractiveAuth() else {
|
|
45
|
+
completion(nil, "operation_in_progress")
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
let complete = completeInteractiveAuth(completion)
|
|
21
49
|
guard let clientId = Bundle.main.object(forInfoDictionaryKey: "GIDClientID") as? String, !clientId.isEmpty else {
|
|
22
|
-
|
|
50
|
+
complete(nil, "configuration_error")
|
|
23
51
|
return
|
|
24
52
|
}
|
|
25
53
|
|
|
@@ -27,23 +55,24 @@ public class AuthAdapter: NSObject {
|
|
|
27
55
|
|
|
28
56
|
DispatchQueue.main.async {
|
|
29
57
|
guard let rootVC = presentingViewController() else {
|
|
30
|
-
|
|
58
|
+
complete(nil, "configuration_error")
|
|
31
59
|
return
|
|
32
60
|
}
|
|
33
61
|
|
|
34
|
-
let config = GIDConfiguration(clientID: clientId, serverClientID: serverClientId)
|
|
62
|
+
let config = GIDConfiguration(clientID: clientId, serverClientID: serverClientId, hostedDomain: hostedDomain, openIDRealm: openIDRealm)
|
|
35
63
|
GIDSignIn.sharedInstance.configuration = config
|
|
36
64
|
|
|
37
65
|
let additionalScopes = scopes.isEmpty ? nil : scopes
|
|
38
|
-
let
|
|
66
|
+
let shouldForceAccountPicker = forceAccountPicker || useSheet
|
|
67
|
+
let effectiveHint = shouldForceAccountPicker ? nil : loginHint
|
|
39
68
|
|
|
40
69
|
let performSignIn = {
|
|
41
|
-
GIDSignIn.sharedInstance.signIn(withPresenting: rootVC, hint: effectiveHint, additionalScopes: additionalScopes) { result, error in
|
|
42
|
-
self.handleGoogleResult(result, error: error, completion:
|
|
70
|
+
GIDSignIn.sharedInstance.signIn(withPresenting: rootVC, hint: effectiveHint, additionalScopes: additionalScopes, nonce: nonce) { result, error in
|
|
71
|
+
self.handleGoogleResult(result, error: error, completion: complete)
|
|
43
72
|
}
|
|
44
73
|
}
|
|
45
74
|
|
|
46
|
-
if
|
|
75
|
+
if shouldForceAccountPicker {
|
|
47
76
|
GIDSignIn.sharedInstance.disconnect { _ in
|
|
48
77
|
performSignIn()
|
|
49
78
|
}
|
|
@@ -52,18 +81,34 @@ public class AuthAdapter: NSObject {
|
|
|
52
81
|
}
|
|
53
82
|
}
|
|
54
83
|
} else if provider == "apple" {
|
|
84
|
+
guard beginInteractiveAuth() else {
|
|
85
|
+
completion(nil, "operation_in_progress")
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
let complete = completeInteractiveAuth(completion)
|
|
55
89
|
let appleIDProvider = ASAuthorizationAppleIDProvider()
|
|
56
90
|
let request = appleIDProvider.createRequest()
|
|
57
|
-
request.requestedScopes =
|
|
91
|
+
request.requestedScopes = scopes.isEmpty
|
|
92
|
+
? [.fullName, .email]
|
|
93
|
+
: scopes.compactMap { scope in
|
|
94
|
+
switch scope {
|
|
95
|
+
case "fullName", "name": return .fullName
|
|
96
|
+
case "email": return .email
|
|
97
|
+
default: return nil
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if let nonce = nonce {
|
|
101
|
+
request.nonce = nonce
|
|
102
|
+
}
|
|
58
103
|
|
|
59
104
|
let controller = ASAuthorizationController(authorizationRequests: [request])
|
|
60
|
-
let delegate = AppleSignInDelegate(completion:
|
|
105
|
+
let delegate = AppleSignInDelegate(completion: complete)
|
|
61
106
|
controller.delegate = delegate
|
|
62
107
|
objc_setAssociatedObject(controller, &delegateHandle, delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
63
108
|
|
|
64
109
|
DispatchQueue.main.async {
|
|
65
110
|
guard let window = activeWindow() else {
|
|
66
|
-
|
|
111
|
+
complete(nil, "configuration_error")
|
|
67
112
|
return
|
|
68
113
|
}
|
|
69
114
|
let contextProvider = AppleSignInContextProvider(anchor: window)
|
|
@@ -83,6 +128,11 @@ public class AuthAdapter: NSObject {
|
|
|
83
128
|
completion(nil, "configuration_error")
|
|
84
129
|
return
|
|
85
130
|
}
|
|
131
|
+
guard beginInteractiveAuth() else {
|
|
132
|
+
completion(nil, "operation_in_progress")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
let complete = completeInteractiveAuth(completion)
|
|
86
136
|
|
|
87
137
|
let effectiveTenant = tenant ?? Bundle.main.object(forInfoDictionaryKey: "MSALTenant") as? String ?? "common"
|
|
88
138
|
let bundleId = Bundle.main.bundleIdentifier ?? ""
|
|
@@ -91,11 +141,11 @@ public class AuthAdapter: NSObject {
|
|
|
91
141
|
let effectivePrompt = prompt ?? "select_account"
|
|
92
142
|
|
|
93
143
|
guard let codeVerifier = generateCodeVerifier() else {
|
|
94
|
-
|
|
144
|
+
complete(nil, "configuration_error")
|
|
95
145
|
return
|
|
96
146
|
}
|
|
97
147
|
guard let codeChallenge = generateCodeChallenge(codeVerifier) else {
|
|
98
|
-
|
|
148
|
+
complete(nil, "configuration_error")
|
|
99
149
|
return
|
|
100
150
|
}
|
|
101
151
|
let state = UUID().uuidString
|
|
@@ -103,12 +153,12 @@ public class AuthAdapter: NSObject {
|
|
|
103
153
|
|
|
104
154
|
let b2cDomain = Bundle.main.object(forInfoDictionaryKey: "MSALB2cDomain") as? String
|
|
105
155
|
guard let authBaseUrl = getMicrosoftAuthBaseUrl(tenant: effectiveTenant, b2cDomain: b2cDomain) else {
|
|
106
|
-
|
|
156
|
+
complete(nil, "configuration_error")
|
|
107
157
|
return
|
|
108
158
|
}
|
|
109
159
|
|
|
110
160
|
guard var urlComponents = URLComponents(string: "\(authBaseUrl)oauth2/v2.0/authorize") else {
|
|
111
|
-
|
|
161
|
+
complete(nil, "configuration_error")
|
|
112
162
|
return
|
|
113
163
|
}
|
|
114
164
|
urlComponents.queryItems = [
|
|
@@ -129,7 +179,7 @@ public class AuthAdapter: NSObject {
|
|
|
129
179
|
}
|
|
130
180
|
|
|
131
181
|
guard let authUrl = urlComponents.url else {
|
|
132
|
-
|
|
182
|
+
complete(nil, "configuration_error")
|
|
133
183
|
return
|
|
134
184
|
}
|
|
135
185
|
|
|
@@ -137,13 +187,13 @@ public class AuthAdapter: NSObject {
|
|
|
137
187
|
|
|
138
188
|
DispatchQueue.main.async {
|
|
139
189
|
guard self.activeMicrosoftWebAuthSession == nil else {
|
|
140
|
-
|
|
190
|
+
complete(nil, "operation_in_progress")
|
|
141
191
|
return
|
|
142
192
|
}
|
|
143
193
|
|
|
144
194
|
let completeAndClearSession = { (data: NSDictionary?, error: String?) in
|
|
145
195
|
self.activeMicrosoftWebAuthSession = nil
|
|
146
|
-
|
|
196
|
+
complete(data, error)
|
|
147
197
|
}
|
|
148
198
|
|
|
149
199
|
let session = ASWebAuthenticationSession(url: authUrl, callbackURLScheme: callbackScheme) { callbackURL, error in
|
|
@@ -198,7 +248,7 @@ public class AuthAdapter: NSObject {
|
|
|
198
248
|
b2cDomain: b2cDomain,
|
|
199
249
|
expectedNonce: nonce,
|
|
200
250
|
scopes: effectiveScopes,
|
|
201
|
-
completion:
|
|
251
|
+
completion: complete
|
|
202
252
|
)
|
|
203
253
|
}
|
|
204
254
|
|
|
@@ -391,6 +441,8 @@ public class AuthAdapter: NSObject {
|
|
|
391
441
|
"idToken": user.idToken?.tokenString ?? "",
|
|
392
442
|
"accessToken": user.accessToken.tokenString,
|
|
393
443
|
"serverAuthCode": serverAuthCode,
|
|
444
|
+
"userId": user.userID ?? "",
|
|
445
|
+
"hostedDomain": user.configuration.hostedDomain ?? "",
|
|
394
446
|
"expirationTime": (user.accessToken.expirationDate?.timeIntervalSince1970 ?? 0) * 1000,
|
|
395
447
|
"underlyingError": ""
|
|
396
448
|
]
|
|
@@ -502,9 +554,11 @@ public class AuthAdapter: NSObject {
|
|
|
502
554
|
"name": user.profile?.name ?? "",
|
|
503
555
|
"photo": user.profile?.imageURL(withDimension: 300)?.absoluteString ?? "",
|
|
504
556
|
"idToken": user.idToken?.tokenString ?? "",
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
557
|
+
"accessToken": user.accessToken.tokenString,
|
|
558
|
+
"serverAuthCode": cachedServerAuthCode ?? "",
|
|
559
|
+
"userId": user.userID ?? "",
|
|
560
|
+
"hostedDomain": user.configuration.hostedDomain ?? "",
|
|
561
|
+
"expirationTime": (user.accessToken.expirationDate?.timeIntervalSince1970 ?? 0) * 1000
|
|
508
562
|
]
|
|
509
563
|
completion(data as NSDictionary)
|
|
510
564
|
return
|
|
@@ -704,6 +758,7 @@ public class AuthAdapter: NSObject {
|
|
|
704
758
|
self.activeMicrosoftWebAuthSession?.cancel()
|
|
705
759
|
self.activeMicrosoftWebAuthSession = nil
|
|
706
760
|
}
|
|
761
|
+
finishInteractiveAuth()
|
|
707
762
|
tokenStoreLock.lock()
|
|
708
763
|
inMemoryMicrosoftRefreshToken = nil
|
|
709
764
|
inMemoryMicrosoftScopes = defaultMicrosoftScopes
|
|
@@ -711,6 +766,23 @@ public class AuthAdapter: NSObject {
|
|
|
711
766
|
tokenStoreLock.unlock()
|
|
712
767
|
}
|
|
713
768
|
|
|
769
|
+
@objc
|
|
770
|
+
public static func revokeAccess(completion: @escaping (String?) -> Void) {
|
|
771
|
+
GIDSignIn.sharedInstance.disconnect { error in
|
|
772
|
+
tokenStoreLock.lock()
|
|
773
|
+
inMemoryMicrosoftRefreshToken = nil
|
|
774
|
+
inMemoryMicrosoftScopes = defaultMicrosoftScopes
|
|
775
|
+
inMemoryGoogleServerAuthCode = nil
|
|
776
|
+
tokenStoreLock.unlock()
|
|
777
|
+
|
|
778
|
+
if let error = error {
|
|
779
|
+
completion(mapError(error))
|
|
780
|
+
return
|
|
781
|
+
}
|
|
782
|
+
completion(nil)
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
714
786
|
private static func activeWindow() -> UIWindow? {
|
|
715
787
|
let windowScenes = UIApplication.shared.connectedScenes
|
|
716
788
|
.compactMap { $0 as? UIWindowScene }
|
|
@@ -769,6 +841,8 @@ class AppleSignInDelegate: NSObject, ASAuthorizationControllerDelegate {
|
|
|
769
841
|
"email": email ?? "",
|
|
770
842
|
"name": name,
|
|
771
843
|
"idToken": idToken ?? "",
|
|
844
|
+
"authorizationCode": appleIDCredential.authorizationCode.flatMap { String(data: $0, encoding: .utf8) } ?? "",
|
|
845
|
+
"userId": appleIDCredential.user,
|
|
772
846
|
"underlyingError": ""
|
|
773
847
|
]
|
|
774
848
|
completion(data as NSDictionary, nil)
|