react-native-nitro-auth 0.5.1 → 0.5.4
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 +362 -190
- package/android/build.gradle +2 -5
- package/android/src/main/cpp/PlatformAuth+Android.cpp +84 -18
- package/android/src/main/java/com/auth/AuthAdapter.kt +82 -182
- package/android/src/main/java/com/auth/NitroAuthPackage.kt +1 -1
- package/app.plugin.js +2 -9
- package/cpp/AuthCache.cpp +0 -134
- package/cpp/AuthCache.hpp +0 -7
- package/cpp/HybridAuth.cpp +57 -63
- package/cpp/HybridAuth.hpp +3 -4
- package/ios/AuthAdapter.swift +23 -25
- package/lib/commonjs/Auth.web.js +523 -201
- package/lib/commonjs/Auth.web.js.map +1 -1
- package/lib/commonjs/index.js +0 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +0 -12
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/js-storage-adapter.js +2 -0
- package/lib/commonjs/js-storage-adapter.js.map +1 -0
- package/lib/commonjs/service.js +9 -86
- package/lib/commonjs/service.js.map +1 -1
- package/lib/commonjs/service.web.js +1 -5
- package/lib/commonjs/service.web.js.map +1 -1
- package/lib/commonjs/ui/social-button.js +44 -29
- package/lib/commonjs/ui/social-button.js.map +1 -1
- package/lib/commonjs/ui/social-button.web.js +44 -29
- package/lib/commonjs/ui/social-button.web.js.map +1 -1
- package/lib/commonjs/use-auth.js +56 -42
- package/lib/commonjs/use-auth.js.map +1 -1
- package/lib/commonjs/utils/logger.js +12 -4
- package/lib/commonjs/utils/logger.js.map +1 -1
- package/lib/module/Auth.web.js +523 -201
- package/lib/module/Auth.web.js.map +1 -1
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +0 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/js-storage-adapter.js +2 -0
- package/lib/module/js-storage-adapter.js.map +1 -0
- package/lib/module/service.js +9 -86
- package/lib/module/service.js.map +1 -1
- package/lib/module/service.web.js +1 -5
- package/lib/module/service.web.js.map +1 -1
- package/lib/module/ui/social-button.js +44 -29
- package/lib/module/ui/social-button.js.map +1 -1
- package/lib/module/ui/social-button.web.js +44 -29
- package/lib/module/ui/social-button.web.js.map +1 -1
- package/lib/module/use-auth.js +56 -42
- package/lib/module/use-auth.js.map +1 -1
- package/lib/module/utils/logger.js +12 -4
- package/lib/module/utils/logger.js.map +1 -1
- package/lib/typescript/commonjs/Auth.nitro.d.ts +3 -3
- package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
- package/lib/typescript/commonjs/Auth.web.d.ts +25 -6
- package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -2
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +0 -1
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/js-storage-adapter.d.ts +6 -0
- package/lib/typescript/commonjs/js-storage-adapter.d.ts.map +1 -0
- package/lib/typescript/commonjs/service.d.ts +1 -8
- package/lib/typescript/commonjs/service.d.ts.map +1 -1
- package/lib/typescript/commonjs/service.web.d.ts +1 -8
- package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/social-button.d.ts +6 -6
- package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/social-button.web.d.ts +6 -6
- package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/use-auth.d.ts +4 -4
- package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/logger.d.ts +4 -4
- package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
- package/lib/typescript/module/Auth.nitro.d.ts +3 -3
- package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
- package/lib/typescript/module/Auth.web.d.ts +25 -6
- package/lib/typescript/module/Auth.web.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -2
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +0 -1
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/js-storage-adapter.d.ts +6 -0
- package/lib/typescript/module/js-storage-adapter.d.ts.map +1 -0
- package/lib/typescript/module/service.d.ts +1 -8
- package/lib/typescript/module/service.d.ts.map +1 -1
- package/lib/typescript/module/service.web.d.ts +1 -8
- package/lib/typescript/module/service.web.d.ts.map +1 -1
- package/lib/typescript/module/ui/social-button.d.ts +6 -6
- package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
- package/lib/typescript/module/ui/social-button.web.d.ts +6 -6
- package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
- package/lib/typescript/module/use-auth.d.ts +4 -4
- package/lib/typescript/module/use-auth.d.ts.map +1 -1
- package/lib/typescript/module/utils/logger.d.ts +4 -4
- package/lib/typescript/module/utils/logger.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroAuth+autolinking.cmake +0 -1
- package/nitrogen/generated/shared/c++/AuthTokens.hpp +5 -1
- package/nitrogen/generated/shared/c++/AuthUser.hpp +5 -1
- package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +0 -1
- package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +0 -5
- package/package.json +11 -8
- package/react-native-nitro-auth.podspec +1 -1
- package/src/Auth.nitro.ts +4 -3
- package/src/Auth.web.ts +700 -246
- package/src/global.d.ts +0 -1
- package/src/index.ts +1 -2
- package/src/index.web.ts +0 -1
- package/src/js-storage-adapter.ts +5 -0
- package/src/service.ts +13 -106
- package/src/service.web.ts +0 -7
- package/src/ui/social-button.tsx +66 -43
- package/src/ui/social-button.web.tsx +67 -44
- package/src/use-auth.ts +116 -72
- package/src/utils/logger.ts +12 -4
- package/ios/KeychainStore.swift +0 -43
- package/lib/commonjs/AuthStorage.nitro.js +0 -6
- package/lib/commonjs/AuthStorage.nitro.js.map +0 -1
- package/lib/module/AuthStorage.nitro.js +0 -4
- package/lib/module/AuthStorage.nitro.js.map +0 -1
- package/lib/typescript/commonjs/AuthStorage.nitro.d.ts +0 -26
- package/lib/typescript/commonjs/AuthStorage.nitro.d.ts.map +0 -1
- package/lib/typescript/module/AuthStorage.nitro.d.ts +0 -26
- package/lib/typescript/module/AuthStorage.nitro.d.ts.map +0 -1
- package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.cpp +0 -23
- package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.hpp +0 -65
- package/src/AuthStorage.nitro.ts +0 -26
package/android/build.gradle
CHANGED
|
@@ -93,7 +93,7 @@ dependencies {
|
|
|
93
93
|
implementation project(":react-native-nitro-modules")
|
|
94
94
|
|
|
95
95
|
// Google Sign-In SDK (full scope support)
|
|
96
|
-
implementation "com.google.android.gms:play-services-auth:21.
|
|
96
|
+
implementation "com.google.android.gms:play-services-auth:21.5.1"
|
|
97
97
|
|
|
98
98
|
// Activity result APIs
|
|
99
99
|
implementation "androidx.activity:activity-ktx:1.9.3"
|
|
@@ -104,8 +104,5 @@ dependencies {
|
|
|
104
104
|
// Google Credential Manager (One-Tap / Passkeys)
|
|
105
105
|
implementation "androidx.credentials:credentials:1.5.0"
|
|
106
106
|
implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
|
|
107
|
-
implementation "com.google.android.libraries.identity.googleid:googleid:1.
|
|
108
|
-
|
|
109
|
-
// Secure storage (EncryptedSharedPreferences)
|
|
110
|
-
implementation "androidx.security:security-crypto:1.0.0"
|
|
107
|
+
implementation "com.google.android.libraries.identity.googleid:googleid:1.2.0"
|
|
111
108
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
#include <fbjni/fbjni.h>
|
|
7
7
|
#include <NitroModules/NitroLogger.hpp>
|
|
8
8
|
#include <NitroModules/Promise.hpp>
|
|
9
|
+
#include <exception>
|
|
10
|
+
#include <stdexcept>
|
|
9
11
|
|
|
10
12
|
namespace margelo::nitro::NitroAuth {
|
|
11
13
|
|
|
@@ -24,6 +26,33 @@ static std::shared_ptr<Promise<AuthUser>> gScopesPromise;
|
|
|
24
26
|
static std::shared_ptr<Promise<AuthTokens>> gRefreshPromise;
|
|
25
27
|
static std::shared_ptr<Promise<std::optional<AuthUser>>> gSilentPromise;
|
|
26
28
|
static std::mutex gMutex;
|
|
29
|
+
static jclass gAuthAdapterClass = nullptr;
|
|
30
|
+
static jmethodID gLoginMethod = nullptr;
|
|
31
|
+
static jmethodID gRequestScopesMethod = nullptr;
|
|
32
|
+
|
|
33
|
+
static void ensureAuthAdapterMethods(JNIEnv* env) {
|
|
34
|
+
if (gAuthAdapterClass != nullptr && gLoginMethod != nullptr && gRequestScopesMethod != nullptr) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
jclass localAdapterClass = env->FindClass("com/auth/AuthAdapter");
|
|
39
|
+
if (localAdapterClass == nullptr) {
|
|
40
|
+
throw std::runtime_error("Unable to resolve com/auth/AuthAdapter");
|
|
41
|
+
}
|
|
42
|
+
gAuthAdapterClass = static_cast<jclass>(env->NewGlobalRef(localAdapterClass));
|
|
43
|
+
env->DeleteLocalRef(localAdapterClass);
|
|
44
|
+
|
|
45
|
+
gLoginMethod = env->GetStaticMethodID(
|
|
46
|
+
gAuthAdapterClass,
|
|
47
|
+
"loginSync",
|
|
48
|
+
"(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;ZZZLjava/lang/String;Ljava/lang/String;)V"
|
|
49
|
+
);
|
|
50
|
+
gRequestScopesMethod = env->GetStaticMethodID(
|
|
51
|
+
gAuthAdapterClass,
|
|
52
|
+
"requestScopesSync",
|
|
53
|
+
"(Landroid/content/Context;[Ljava/lang/String;)V"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
27
56
|
|
|
28
57
|
std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
|
|
29
58
|
auto promise = Promise<AuthUser>::create();
|
|
@@ -35,6 +64,9 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
|
|
|
35
64
|
|
|
36
65
|
{
|
|
37
66
|
std::lock_guard<std::mutex> lock(gMutex);
|
|
67
|
+
if (gLoginPromise) {
|
|
68
|
+
gLoginPromise->reject(std::make_exception_ptr(std::runtime_error("Login request superseded by a newer request")));
|
|
69
|
+
}
|
|
38
70
|
gLoginPromise = promise;
|
|
39
71
|
}
|
|
40
72
|
|
|
@@ -71,30 +103,47 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
|
|
|
71
103
|
}
|
|
72
104
|
|
|
73
105
|
JNIEnv* env = Environment::current();
|
|
106
|
+
try {
|
|
107
|
+
ensureAuthAdapterMethods(env);
|
|
108
|
+
} catch (...) {
|
|
109
|
+
promise->reject(std::current_exception());
|
|
110
|
+
return promise;
|
|
111
|
+
}
|
|
74
112
|
jclass stringClass = env->FindClass("java/lang/String");
|
|
75
113
|
jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
|
|
76
114
|
for (size_t i = 0; i < scopes.size(); i++) {
|
|
77
115
|
env->SetObjectArrayElement(jScopes, i, make_jstring(scopes[i]).get());
|
|
78
116
|
}
|
|
79
117
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
118
|
+
local_ref<JString> providerRef = make_jstring(providerStr);
|
|
119
|
+
local_ref<JString> loginHintRef;
|
|
120
|
+
local_ref<JString> tenantRef;
|
|
121
|
+
local_ref<JString> promptRef;
|
|
122
|
+
|
|
123
|
+
if (loginHint.has_value()) {
|
|
124
|
+
loginHintRef = make_jstring(loginHint.value());
|
|
125
|
+
}
|
|
126
|
+
if (tenant.has_value()) {
|
|
127
|
+
tenantRef = make_jstring(tenant.value());
|
|
128
|
+
}
|
|
129
|
+
if (prompt.has_value()) {
|
|
130
|
+
promptRef = make_jstring(prompt.value());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
env->CallStaticVoidMethod(gAuthAdapterClass, gLoginMethod,
|
|
88
134
|
contextPtr,
|
|
89
|
-
|
|
135
|
+
providerRef.get(),
|
|
90
136
|
nullptr,
|
|
91
137
|
jScopes,
|
|
92
|
-
|
|
138
|
+
loginHintRef.get(),
|
|
93
139
|
(jboolean)useOneTap,
|
|
94
140
|
(jboolean)forceAccountPicker,
|
|
95
141
|
(jboolean)useLegacyGoogleSignIn,
|
|
96
|
-
|
|
97
|
-
|
|
142
|
+
tenantRef.get(),
|
|
143
|
+
promptRef.get());
|
|
144
|
+
|
|
145
|
+
env->DeleteLocalRef(jScopes);
|
|
146
|
+
env->DeleteLocalRef(stringClass);
|
|
98
147
|
|
|
99
148
|
return promise;
|
|
100
149
|
}
|
|
@@ -109,20 +158,28 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::requestScopes(const std::vector
|
|
|
109
158
|
|
|
110
159
|
{
|
|
111
160
|
std::lock_guard<std::mutex> lock(gMutex);
|
|
161
|
+
if (gScopesPromise) {
|
|
162
|
+
gScopesPromise->reject(std::make_exception_ptr(std::runtime_error("Scope request superseded by a newer request")));
|
|
163
|
+
}
|
|
112
164
|
gScopesPromise = promise;
|
|
113
165
|
}
|
|
114
166
|
|
|
115
167
|
JNIEnv* env = Environment::current();
|
|
168
|
+
try {
|
|
169
|
+
ensureAuthAdapterMethods(env);
|
|
170
|
+
} catch (...) {
|
|
171
|
+
promise->reject(std::current_exception());
|
|
172
|
+
return promise;
|
|
173
|
+
}
|
|
116
174
|
jclass stringClass = env->FindClass("java/lang/String");
|
|
117
175
|
jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
|
|
118
176
|
for (size_t i = 0; i < scopes.size(); i++) {
|
|
119
177
|
env->SetObjectArrayElement(jScopes, i, make_jstring(scopes[i]).get());
|
|
120
178
|
}
|
|
121
179
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
env->CallStaticVoidMethod(adapterClass, requestMethod, contextPtr, jScopes);
|
|
180
|
+
env->CallStaticVoidMethod(gAuthAdapterClass, gRequestScopesMethod, contextPtr, jScopes);
|
|
181
|
+
env->DeleteLocalRef(jScopes);
|
|
182
|
+
env->DeleteLocalRef(stringClass);
|
|
126
183
|
return promise;
|
|
127
184
|
}
|
|
128
185
|
|
|
@@ -136,6 +193,9 @@ std::shared_ptr<Promise<AuthTokens>> PlatformAuth::refreshToken() {
|
|
|
136
193
|
|
|
137
194
|
{
|
|
138
195
|
std::lock_guard<std::mutex> lock(gMutex);
|
|
196
|
+
if (gRefreshPromise) {
|
|
197
|
+
gRefreshPromise->reject(std::make_exception_ptr(std::runtime_error("Refresh request superseded by a newer request")));
|
|
198
|
+
}
|
|
139
199
|
gRefreshPromise = promise;
|
|
140
200
|
}
|
|
141
201
|
|
|
@@ -155,6 +215,9 @@ std::shared_ptr<Promise<std::optional<AuthUser>>> PlatformAuth::silentRestore()
|
|
|
155
215
|
|
|
156
216
|
{
|
|
157
217
|
std::lock_guard<std::mutex> lock(gMutex);
|
|
218
|
+
if (gSilentPromise) {
|
|
219
|
+
gSilentPromise->reject(std::make_exception_ptr(std::runtime_error("Silent restore superseded by a newer request")));
|
|
220
|
+
}
|
|
158
221
|
gSilentPromise = promise;
|
|
159
222
|
}
|
|
160
223
|
|
|
@@ -182,8 +245,8 @@ void PlatformAuth::logout() {
|
|
|
182
245
|
logoutMethod(JAuthAdapter::javaClassStatic(), static_ref_cast<JContext>(jContext));
|
|
183
246
|
}
|
|
184
247
|
|
|
185
|
-
extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeInitialize(JNIEnv
|
|
186
|
-
AuthCache::setAndroidContext(
|
|
248
|
+
extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeInitialize(JNIEnv*, jclass, jobject context) {
|
|
249
|
+
AuthCache::setAndroidContext(context);
|
|
187
250
|
}
|
|
188
251
|
|
|
189
252
|
extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess(
|
|
@@ -253,6 +316,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess
|
|
|
253
316
|
const char* s = env->GetStringUTFChars(jstr, nullptr);
|
|
254
317
|
scopeVec.push_back(std::string(s));
|
|
255
318
|
env->ReleaseStringUTFChars(jstr, s);
|
|
319
|
+
env->DeleteLocalRef(jstr);
|
|
256
320
|
}
|
|
257
321
|
user.scopes = scopeVec;
|
|
258
322
|
}
|
|
@@ -260,6 +324,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess
|
|
|
260
324
|
jclass longClass = env->FindClass("java/lang/Long");
|
|
261
325
|
jmethodID longValueMethod = env->GetMethodID(longClass, "longValue", "()J");
|
|
262
326
|
user.expirationTime = (double)env->CallLongMethod(expirationTime, longValueMethod);
|
|
327
|
+
env->DeleteLocalRef(longClass);
|
|
263
328
|
}
|
|
264
329
|
|
|
265
330
|
if (loginPromise) loginPromise->resolve(user);
|
|
@@ -328,6 +393,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnRefreshSucce
|
|
|
328
393
|
jclass longClass = env->FindClass("java/lang/Long");
|
|
329
394
|
jmethodID longValueMethod = env->GetMethodID(longClass, "longValue", "()J");
|
|
330
395
|
tokens.expirationTime = (double)env->CallLongMethod(expirationTime, longValueMethod);
|
|
396
|
+
env->DeleteLocalRef(longClass);
|
|
331
397
|
}
|
|
332
398
|
refreshPromise->resolve(tokens);
|
|
333
399
|
}
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
package com.auth
|
|
4
4
|
|
|
5
5
|
import android.content.Context
|
|
6
|
-
import android.content.SharedPreferences
|
|
7
6
|
import android.os.Bundle
|
|
8
|
-
import android.os.Build
|
|
9
7
|
import android.util.Log
|
|
10
8
|
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
|
11
9
|
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
|
@@ -27,21 +25,20 @@ import android.app.Application
|
|
|
27
25
|
import android.content.Intent
|
|
28
26
|
import android.net.Uri
|
|
29
27
|
import androidx.browser.customtabs.CustomTabsIntent
|
|
30
|
-
import androidx.security.crypto.EncryptedSharedPreferences
|
|
31
|
-
import androidx.security.crypto.MasterKeys
|
|
32
28
|
import java.util.UUID
|
|
33
|
-
import org.json.JSONArray
|
|
34
29
|
import org.json.JSONObject
|
|
35
30
|
import android.util.Base64
|
|
36
31
|
|
|
37
32
|
object AuthAdapter {
|
|
38
33
|
private const val TAG = "AuthAdapter"
|
|
39
|
-
private const val PREF_NAME = "nitro_auth"
|
|
40
|
-
private const val SECURE_PREF_NAME = "nitro_auth_secure"
|
|
41
34
|
|
|
35
|
+
@Volatile
|
|
36
|
+
private var isInitialized = false
|
|
37
|
+
|
|
42
38
|
private var appContext: Context? = null
|
|
43
39
|
private var currentActivity: Activity? = null
|
|
44
40
|
private var googleSignInClient: GoogleSignInClient? = null
|
|
41
|
+
private var lifecycleCallbacks: Application.ActivityLifecycleCallbacks? = null
|
|
45
42
|
private var pendingScopes: List<String> = emptyList()
|
|
46
43
|
private var pendingMicrosoftScopes: List<String> = emptyList()
|
|
47
44
|
|
|
@@ -51,47 +48,10 @@ object AuthAdapter {
|
|
|
51
48
|
private var pendingMicrosoftTenant: String? = null
|
|
52
49
|
private var pendingMicrosoftClientId: String? = null
|
|
53
50
|
private var pendingMicrosoftB2cDomain: String? = null
|
|
54
|
-
|
|
55
|
-
private
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
|
59
|
-
val securePrefs = EncryptedSharedPreferences.create(
|
|
60
|
-
SECURE_PREF_NAME,
|
|
61
|
-
masterKeyAlias,
|
|
62
|
-
context,
|
|
63
|
-
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
64
|
-
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
65
|
-
)
|
|
66
|
-
migrateLegacyPrefsIfNeeded(context, securePrefs)
|
|
67
|
-
securePrefs
|
|
68
|
-
} else {
|
|
69
|
-
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
|
70
|
-
}
|
|
71
|
-
} catch (e: Exception) {
|
|
72
|
-
Log.w(TAG, "Failed to initialize encrypted storage, falling back to SharedPreferences", e)
|
|
73
|
-
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private fun migrateLegacyPrefsIfNeeded(context: Context, securePrefs: SharedPreferences) {
|
|
78
|
-
val legacyPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
|
79
|
-
if (legacyPrefs.all.isEmpty() || securePrefs.all.isNotEmpty()) {
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
val editor = securePrefs.edit()
|
|
83
|
-
for ((key, value) in legacyPrefs.all) {
|
|
84
|
-
when (value) {
|
|
85
|
-
is String -> editor.putString(key, value)
|
|
86
|
-
is Boolean -> editor.putBoolean(key, value)
|
|
87
|
-
is Int -> editor.putInt(key, value)
|
|
88
|
-
is Long -> editor.putLong(key, value)
|
|
89
|
-
is Float -> editor.putFloat(key, value)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
editor.apply()
|
|
93
|
-
legacyPrefs.edit().clear().apply()
|
|
94
|
-
}
|
|
51
|
+
|
|
52
|
+
private var inMemoryMicrosoftRefreshToken: String? = null
|
|
53
|
+
private var inMemoryMicrosoftScopes: List<String> =
|
|
54
|
+
listOf("openid", "email", "profile", "offline_access", "User.Read")
|
|
95
55
|
|
|
96
56
|
@JvmStatic
|
|
97
57
|
private external fun nativeInitialize(context: Context)
|
|
@@ -118,34 +78,43 @@ object AuthAdapter {
|
|
|
118
78
|
@JvmStatic
|
|
119
79
|
private external fun nativeOnRefreshError(error: String, underlyingError: String?)
|
|
120
80
|
|
|
81
|
+
@Synchronized
|
|
121
82
|
fun initialize(context: Context) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
83
|
+
if (isInitialized) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
val applicationContext = context.applicationContext
|
|
88
|
+
appContext = applicationContext
|
|
89
|
+
|
|
90
|
+
val app = applicationContext as? Application
|
|
91
|
+
if (app != null && lifecycleCallbacks == null) {
|
|
92
|
+
lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
|
|
93
|
+
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { currentActivity = activity }
|
|
94
|
+
override fun onActivityStarted(activity: Activity) { currentActivity = activity }
|
|
95
|
+
override fun onActivityResumed(activity: Activity) { currentActivity = activity }
|
|
96
|
+
override fun onActivityPaused(activity: Activity) { if (currentActivity == activity) currentActivity = null }
|
|
97
|
+
override fun onActivityStopped(activity: Activity) { if (currentActivity == activity) currentActivity = null }
|
|
98
|
+
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
|
99
|
+
override fun onActivityDestroyed(activity: Activity) { if (currentActivity == activity) currentActivity = null }
|
|
100
|
+
}
|
|
101
|
+
app.registerActivityLifecycleCallbacks(lifecycleCallbacks)
|
|
102
|
+
}
|
|
134
103
|
|
|
135
104
|
try {
|
|
136
105
|
System.loadLibrary("NitroAuth")
|
|
137
|
-
nativeInitialize(
|
|
106
|
+
nativeInitialize(applicationContext)
|
|
107
|
+
isInitialized = true
|
|
138
108
|
} catch (e: Exception) {
|
|
139
109
|
Log.e(TAG, "Failed to load NitroAuth library", e)
|
|
140
110
|
}
|
|
141
111
|
}
|
|
142
112
|
|
|
143
113
|
fun onSignInSuccess(account: GoogleSignInAccount, scopes: List<String>) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
account.photoUrl?.toString(), account.idToken, account.serverAuthCode, scopes)
|
|
114
|
+
appContext ?: return
|
|
115
|
+
val expirationTime = getJwtExpirationTimeMs(account.idToken)
|
|
147
116
|
nativeOnLoginSuccess("google", account.email, account.displayName,
|
|
148
|
-
account.photoUrl?.toString(), account.idToken, null, account.serverAuthCode, scopes.toTypedArray(),
|
|
117
|
+
account.photoUrl?.toString(), account.idToken, null, account.serverAuthCode, scopes.toTypedArray(), expirationTime)
|
|
149
118
|
}
|
|
150
119
|
|
|
151
120
|
fun onSignInError(errorCode: Int, message: String?) {
|
|
@@ -393,12 +362,11 @@ object AuthAdapter {
|
|
|
393
362
|
val email = claims["preferred_username"] ?: claims["email"]
|
|
394
363
|
val name = claims["name"]
|
|
395
364
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
365
|
+
if (refreshToken.isNotEmpty()) {
|
|
366
|
+
inMemoryMicrosoftRefreshToken = refreshToken
|
|
367
|
+
}
|
|
368
|
+
inMemoryMicrosoftScopes = pendingMicrosoftScopes.ifEmpty {
|
|
369
|
+
listOf("openid", "email", "profile", "offline_access", "User.Read")
|
|
402
370
|
}
|
|
403
371
|
|
|
404
372
|
clearPkceState()
|
|
@@ -419,14 +387,12 @@ object AuthAdapter {
|
|
|
419
387
|
}
|
|
420
388
|
}
|
|
421
389
|
|
|
422
|
-
private fun saveMicrosoftRefreshToken(
|
|
423
|
-
|
|
424
|
-
prefs.edit().putString("microsoft_refresh_token", refreshToken).apply()
|
|
390
|
+
private fun saveMicrosoftRefreshToken(refreshToken: String) {
|
|
391
|
+
inMemoryMicrosoftRefreshToken = refreshToken
|
|
425
392
|
}
|
|
426
393
|
|
|
427
|
-
private fun getMicrosoftRefreshToken(
|
|
428
|
-
|
|
429
|
-
return prefs.getString("microsoft_refresh_token", null)
|
|
394
|
+
private fun getMicrosoftRefreshToken(): String? {
|
|
395
|
+
return inMemoryMicrosoftRefreshToken
|
|
430
396
|
}
|
|
431
397
|
|
|
432
398
|
private fun clearPkceState() {
|
|
@@ -455,6 +421,15 @@ object AuthAdapter {
|
|
|
455
421
|
}
|
|
456
422
|
}
|
|
457
423
|
|
|
424
|
+
private fun getJwtExpirationTimeMs(idToken: String?): Long? {
|
|
425
|
+
if (idToken.isNullOrEmpty()) {
|
|
426
|
+
return null
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
val expSeconds = decodeJwt(idToken)["exp"]?.toLongOrNull() ?: return null
|
|
430
|
+
return expSeconds * 1000
|
|
431
|
+
}
|
|
432
|
+
|
|
458
433
|
private fun getMicrosoftClientIdFromResources(context: Context): String? {
|
|
459
434
|
val resId = context.resources.getIdentifier("nitro_auth_microsoft_client_id", "string", context.packageName)
|
|
460
435
|
return if (resId != 0) context.getString(resId) else null
|
|
@@ -553,6 +528,7 @@ object AuthAdapter {
|
|
|
553
528
|
}
|
|
554
529
|
|
|
555
530
|
if (googleIdTokenCredential != null) {
|
|
531
|
+
val expirationTime = getJwtExpirationTimeMs(googleIdTokenCredential.idToken)
|
|
556
532
|
nativeOnLoginSuccess(
|
|
557
533
|
"google",
|
|
558
534
|
googleIdTokenCredential.id,
|
|
@@ -562,7 +538,7 @@ object AuthAdapter {
|
|
|
562
538
|
null,
|
|
563
539
|
null,
|
|
564
540
|
scopes.toTypedArray(),
|
|
565
|
-
|
|
541
|
+
expirationTime
|
|
566
542
|
)
|
|
567
543
|
} else {
|
|
568
544
|
Log.w(TAG, "Unsupported credential type: ${credential.type}")
|
|
@@ -590,12 +566,8 @@ object AuthAdapter {
|
|
|
590
566
|
ctx.startActivity(intent)
|
|
591
567
|
return
|
|
592
568
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
val currentScopes = extractScopesFromUserJson(userJson)
|
|
596
|
-
val defaultMicrosoftScopes = listOf("openid", "email", "profile", "offline_access", "User.Read")
|
|
597
|
-
val existing = if (currentScopes.isEmpty()) defaultMicrosoftScopes else currentScopes
|
|
598
|
-
val mergedScopes = (existing + scopes.toList()).distinct()
|
|
569
|
+
if (inMemoryMicrosoftRefreshToken != null) {
|
|
570
|
+
val mergedScopes = (inMemoryMicrosoftScopes + scopes.toList()).distinct()
|
|
599
571
|
val tenant = getMicrosoftTenantFromResources(ctx)
|
|
600
572
|
loginMicrosoft(ctx, mergedScopes.toTypedArray(), null, tenant, null)
|
|
601
573
|
return
|
|
@@ -624,20 +596,21 @@ object AuthAdapter {
|
|
|
624
596
|
googleSignInClient!!.silentSignIn().addOnCompleteListener { task ->
|
|
625
597
|
if (task.isSuccessful) {
|
|
626
598
|
val acc = task.result
|
|
627
|
-
nativeOnRefreshSuccess(
|
|
599
|
+
nativeOnRefreshSuccess(
|
|
600
|
+
acc?.idToken,
|
|
601
|
+
null,
|
|
602
|
+
getJwtExpirationTimeMs(acc?.idToken),
|
|
603
|
+
)
|
|
628
604
|
} else {
|
|
629
605
|
nativeOnRefreshError("network_error", task.exception?.message ?: "Silent sign-in failed")
|
|
630
606
|
}
|
|
631
607
|
}
|
|
632
608
|
return
|
|
633
609
|
}
|
|
634
|
-
val
|
|
635
|
-
if (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
refreshMicrosoftTokenForRefresh(ctx, refreshToken)
|
|
639
|
-
return
|
|
640
|
-
}
|
|
610
|
+
val refreshToken = getMicrosoftRefreshToken()
|
|
611
|
+
if (refreshToken != null) {
|
|
612
|
+
refreshMicrosoftTokenForRefresh(ctx, refreshToken)
|
|
613
|
+
return
|
|
641
614
|
}
|
|
642
615
|
nativeOnRefreshError("unknown", "No user logged in")
|
|
643
616
|
}
|
|
@@ -662,7 +635,8 @@ object AuthAdapter {
|
|
|
662
635
|
.build()
|
|
663
636
|
GoogleSignIn.getClient(ctx, gso).signOut()
|
|
664
637
|
}
|
|
665
|
-
|
|
638
|
+
inMemoryMicrosoftRefreshToken = null
|
|
639
|
+
inMemoryMicrosoftScopes = listOf("openid", "email", "profile", "offline_access", "User.Read")
|
|
666
640
|
}
|
|
667
641
|
|
|
668
642
|
@JvmStatic
|
|
@@ -677,7 +651,8 @@ object AuthAdapter {
|
|
|
677
651
|
.build()
|
|
678
652
|
GoogleSignIn.getClient(ctx, gso).revokeAccess()
|
|
679
653
|
}
|
|
680
|
-
|
|
654
|
+
inMemoryMicrosoftRefreshToken = null
|
|
655
|
+
inMemoryMicrosoftScopes = listOf("openid", "email", "profile", "offline_access", "User.Read")
|
|
681
656
|
}
|
|
682
657
|
|
|
683
658
|
private fun getClientIdFromResources(context: Context): String? {
|
|
@@ -685,66 +660,19 @@ object AuthAdapter {
|
|
|
685
660
|
return if (resId != 0) context.getString(resId) else null
|
|
686
661
|
}
|
|
687
662
|
|
|
688
|
-
@JvmStatic
|
|
689
|
-
fun getUserJson(context: Context): String? {
|
|
690
|
-
val pref = getPrefs(context)
|
|
691
|
-
return pref.getString("user_json", null)
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
@JvmStatic
|
|
695
|
-
fun setUserJson(context: Context, json: String) {
|
|
696
|
-
val pref = getPrefs(context)
|
|
697
|
-
pref.edit().putString("user_json", json).apply()
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
@JvmStatic
|
|
701
|
-
fun clearUser(context: Context) {
|
|
702
|
-
val pref = getPrefs(context)
|
|
703
|
-
pref.edit().clear().apply()
|
|
704
|
-
}
|
|
705
|
-
|
|
706
663
|
@JvmStatic
|
|
707
664
|
fun restoreSession(context: Context) {
|
|
708
665
|
val ctx = context.applicationContext ?: appContext ?: context
|
|
709
666
|
val account = GoogleSignIn.getLastSignedInAccount(ctx)
|
|
710
667
|
if (account != null) {
|
|
668
|
+
val expirationTime = getJwtExpirationTimeMs(account.idToken)
|
|
711
669
|
nativeOnLoginSuccess("google", account.email, account.displayName,
|
|
712
670
|
account.photoUrl?.toString(), account.idToken, null, account.serverAuthCode,
|
|
713
|
-
account.grantedScopes?.map { it.scopeUri }?.toTypedArray(),
|
|
671
|
+
account.grantedScopes?.map { it.scopeUri }?.toTypedArray(), expirationTime)
|
|
714
672
|
} else {
|
|
715
|
-
val
|
|
716
|
-
if (
|
|
717
|
-
|
|
718
|
-
val parsed = JSONObject(json)
|
|
719
|
-
parsed.optString("provider")
|
|
720
|
-
} catch (_: Exception) {
|
|
721
|
-
""
|
|
722
|
-
}
|
|
723
|
-
val effectiveProvider = when (provider) {
|
|
724
|
-
"google" -> "google"
|
|
725
|
-
"microsoft" -> "microsoft"
|
|
726
|
-
"apple" -> "apple"
|
|
727
|
-
else -> "apple"
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
if (effectiveProvider == "microsoft") {
|
|
731
|
-
val refreshToken = getMicrosoftRefreshToken(ctx)
|
|
732
|
-
if (refreshToken != null) {
|
|
733
|
-
refreshMicrosoftToken(ctx, refreshToken)
|
|
734
|
-
} else {
|
|
735
|
-
val email = extractJsonValue(json, "email")
|
|
736
|
-
val name = extractJsonValue(json, "name")
|
|
737
|
-
val idToken = extractJsonValue(json, "idToken")
|
|
738
|
-
nativeOnLoginSuccess(effectiveProvider, email, name, null, idToken, null, null, null, null)
|
|
739
|
-
}
|
|
740
|
-
} else {
|
|
741
|
-
val email = extractJsonValue(json, "email")
|
|
742
|
-
val name = extractJsonValue(json, "name")
|
|
743
|
-
val photo = extractJsonValue(json, "photo")
|
|
744
|
-
val idToken = extractJsonValue(json, "idToken")
|
|
745
|
-
val serverAuthCode = extractJsonValue(json, "serverAuthCode")
|
|
746
|
-
nativeOnLoginSuccess(effectiveProvider, email, name, photo, idToken, null, serverAuthCode, null, null)
|
|
747
|
-
}
|
|
673
|
+
val refreshToken = getMicrosoftRefreshToken()
|
|
674
|
+
if (refreshToken != null) {
|
|
675
|
+
refreshMicrosoftToken(ctx, refreshToken)
|
|
748
676
|
} else {
|
|
749
677
|
nativeOnLoginError("unknown", "No session")
|
|
750
678
|
}
|
|
@@ -801,9 +729,11 @@ object AuthAdapter {
|
|
|
801
729
|
val name = claims["name"]
|
|
802
730
|
|
|
803
731
|
if (newRefreshToken.isNotEmpty()) {
|
|
804
|
-
saveMicrosoftRefreshToken(
|
|
732
|
+
saveMicrosoftRefreshToken(newRefreshToken)
|
|
733
|
+
}
|
|
734
|
+
inMemoryMicrosoftScopes = pendingMicrosoftScopes.ifEmpty {
|
|
735
|
+
listOf("openid", "email", "profile", "offline_access", "User.Read")
|
|
805
736
|
}
|
|
806
|
-
saveUser(context, "microsoft", email, name, null, newIdToken, newRefreshToken, null)
|
|
807
737
|
|
|
808
738
|
nativeOnLoginSuccess("microsoft", email, name, null, newIdToken, newAccessToken, null, null, expirationTime)
|
|
809
739
|
} else {
|
|
@@ -859,9 +789,11 @@ object AuthAdapter {
|
|
|
859
789
|
val email = claims["preferred_username"] ?: claims["email"]
|
|
860
790
|
val name = claims["name"]
|
|
861
791
|
if (newRefreshToken.isNotEmpty()) {
|
|
862
|
-
saveMicrosoftRefreshToken(
|
|
792
|
+
saveMicrosoftRefreshToken(newRefreshToken)
|
|
793
|
+
}
|
|
794
|
+
inMemoryMicrosoftScopes = pendingMicrosoftScopes.ifEmpty {
|
|
795
|
+
listOf("openid", "email", "profile", "offline_access", "User.Read")
|
|
863
796
|
}
|
|
864
|
-
saveUser(context, "microsoft", email, name, null, newIdToken, newRefreshToken, null)
|
|
865
797
|
nativeOnRefreshSuccess(
|
|
866
798
|
newIdToken.ifEmpty { null },
|
|
867
799
|
newAccessToken.ifEmpty { null },
|
|
@@ -879,36 +811,4 @@ object AuthAdapter {
|
|
|
879
811
|
}
|
|
880
812
|
}
|
|
881
813
|
|
|
882
|
-
private fun extractJsonValue(json: String, key: String): String? {
|
|
883
|
-
return try {
|
|
884
|
-
val value = JSONObject(json).optString(key, "")
|
|
885
|
-
if (value.isEmpty()) null else value
|
|
886
|
-
} catch (_: Exception) {
|
|
887
|
-
null
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
private fun extractScopesFromUserJson(json: String): List<String> {
|
|
892
|
-
return try {
|
|
893
|
-
val jsonObj = JSONObject(json)
|
|
894
|
-
val arr = jsonObj.optJSONArray("scopes") ?: return emptyList()
|
|
895
|
-
(0 until arr.length()).mapNotNull { i -> arr.optString(i).takeIf { it.isNotEmpty() } }
|
|
896
|
-
} catch (_: Exception) {
|
|
897
|
-
emptyList()
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
private fun saveUser(context: Context, provider: String, email: String?, name: String?,
|
|
902
|
-
photo: String?, idToken: String?, serverAuthCode: String?, scopes: List<String>?) {
|
|
903
|
-
val pref = getPrefs(context)
|
|
904
|
-
val json = JSONObject()
|
|
905
|
-
json.put("provider", provider)
|
|
906
|
-
if (email != null) json.put("email", email)
|
|
907
|
-
if (name != null) json.put("name", name)
|
|
908
|
-
if (photo != null) json.put("photo", photo)
|
|
909
|
-
if (idToken != null) json.put("idToken", idToken)
|
|
910
|
-
if (serverAuthCode != null) json.put("serverAuthCode", serverAuthCode)
|
|
911
|
-
if (scopes != null) json.put("scopes", JSONArray(scopes))
|
|
912
|
-
pref.edit().putString("user_json", json.toString()).apply()
|
|
913
|
-
}
|
|
914
814
|
}
|
package/app.plugin.js
CHANGED
|
@@ -6,7 +6,6 @@ const {
|
|
|
6
6
|
AndroidConfig,
|
|
7
7
|
createRunOncePlugin,
|
|
8
8
|
} = require("@expo/config-plugins");
|
|
9
|
-
|
|
10
9
|
const pkg = require("./package.json");
|
|
11
10
|
|
|
12
11
|
const withNitroAuth = (config, props = {}) => {
|
|
@@ -157,9 +156,7 @@ const withNitroAuth = (config, props = {}) => {
|
|
|
157
156
|
],
|
|
158
157
|
};
|
|
159
158
|
const existingMsalActivity = application.activity.find(
|
|
160
|
-
(a) =>
|
|
161
|
-
a.$?.["android:name"] ===
|
|
162
|
-
"com.auth.MicrosoftAuthActivity",
|
|
159
|
+
(a) => a.$?.["android:name"] === "com.auth.MicrosoftAuthActivity",
|
|
163
160
|
);
|
|
164
161
|
if (!existingMsalActivity) {
|
|
165
162
|
application.activity.push(msalActivity);
|
|
@@ -172,8 +169,4 @@ const withNitroAuth = (config, props = {}) => {
|
|
|
172
169
|
return config;
|
|
173
170
|
};
|
|
174
171
|
|
|
175
|
-
module.exports = createRunOncePlugin(
|
|
176
|
-
withNitroAuth,
|
|
177
|
-
pkg.name,
|
|
178
|
-
pkg.version,
|
|
179
|
-
);
|
|
172
|
+
module.exports = createRunOncePlugin(withNitroAuth, pkg.name, pkg.version);
|