react-native-nitro-auth 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -13
- package/android/build.gradle +8 -3
- package/android/src/main/cpp/PlatformAuth+Android.cpp +14 -3
- package/android/src/main/java/com/auth/AuthAdapter.kt +100 -3
- package/cpp/HybridAuth.cpp +64 -13
- package/cpp/HybridAuth.hpp +10 -0
- package/cpp/PlatformAuth.hpp +2 -1
- package/ios/PlatformAuth+iOS.mm +22 -4
- package/lib/commonjs/Auth.web.js +91 -23
- package/lib/commonjs/Auth.web.js.map +1 -1
- package/lib/commonjs/AuthStorage.nitro.js +6 -0
- package/lib/commonjs/AuthStorage.nitro.js.map +1 -0
- 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/ui/social-button.js +2 -2
- package/lib/commonjs/ui/social-button.js.map +1 -1
- package/lib/commonjs/ui/social-button.web.js +2 -2
- package/lib/commonjs/ui/social-button.web.js.map +1 -1
- package/lib/commonjs/utils/logger.js +17 -0
- package/lib/commonjs/utils/logger.js.map +1 -0
- package/lib/module/Auth.web.js +91 -23
- package/lib/module/Auth.web.js.map +1 -1
- package/lib/module/AuthStorage.nitro.js +4 -0
- package/lib/module/AuthStorage.nitro.js.map +1 -0
- 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/ui/social-button.js +2 -2
- package/lib/module/ui/social-button.js.map +1 -1
- package/lib/module/ui/social-button.web.js +2 -2
- package/lib/module/ui/social-button.web.js.map +1 -1
- package/lib/module/utils/logger.js +13 -0
- package/lib/module/utils/logger.js.map +1 -0
- package/lib/typescript/Auth.nitro.d.ts +5 -0
- package/lib/typescript/Auth.nitro.d.ts.map +1 -1
- package/lib/typescript/Auth.web.d.ts +9 -1
- package/lib/typescript/Auth.web.d.ts.map +1 -1
- package/lib/typescript/AuthStorage.nitro.d.ts +19 -0
- package/lib/typescript/AuthStorage.nitro.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +1 -0
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/ui/social-button.d.ts.map +1 -1
- package/lib/typescript/ui/social-button.web.d.ts.map +1 -1
- package/lib/typescript/utils/logger.d.ts +8 -0
- package/lib/typescript/utils/logger.d.ts.map +1 -0
- package/nitrogen/generated/android/NitroAuth+autolinking.cmake +1 -0
- package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +3 -0
- package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +7 -0
- package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/LoginOptions.hpp +6 -2
- package/package.json +3 -4
- package/react-native-nitro-auth.podspec +1 -1
- package/src/Auth.nitro.ts +6 -0
- package/src/Auth.web.ts +114 -24
- package/src/AuthStorage.nitro.ts +17 -0
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
- package/src/ui/social-button.tsx +3 -3
- package/src/ui/social-button.web.tsx +3 -3
- package/src/utils/logger.ts +11 -0
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# react-native-nitro-auth
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/react-native-nitro-auth)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nitro.margelo.com)
|
|
6
|
+
|
|
3
7
|
🚀 **High-performance, JSI-powered Authentication for React Native.**
|
|
4
8
|
|
|
5
9
|
Nitro Auth is a modern authentication library for React Native built on top of [Nitro Modules](https://github.com/mrousavy/nitro). It provides a unified, type-safe API for Google and Apple Sign-In with zero-bridge overhead.
|
|
@@ -8,21 +12,24 @@ Nitro Auth is a modern authentication library for React Native built on top of [
|
|
|
8
12
|
|
|
9
13
|
Nitro Auth is designed to replace legacy modules like `@react-native-google-signin/google-signin` with a modern, high-performance architecture.
|
|
10
14
|
|
|
11
|
-
| Feature
|
|
12
|
-
|
|
|
15
|
+
| Feature | Legacy Modules | Nitro Auth |
|
|
16
|
+
| :-------------- | :--------------------------- | :----------------------------- |
|
|
13
17
|
| **Performance** | Async bridge overhead (JSON) | **Direct JSI C++ (Zero-copy)** |
|
|
14
|
-
| **Persistence** | Varies / Manual
|
|
15
|
-
| **Setup**
|
|
16
|
-
| **Types**
|
|
18
|
+
| **Persistence** | Varies / Manual | **Built-in & Automatic** |
|
|
19
|
+
| **Setup** | Manual async initialization | **Sync & declarative plugins** |
|
|
20
|
+
| **Types** | Manual / Brittle | **Fully Generated (Nitrogen)** |
|
|
17
21
|
|
|
18
22
|
## Features
|
|
19
23
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
24
|
+
- **Ultra-fast**: Direct C++ calls using JSI (no JSON serialization).
|
|
25
|
+
- **Fully Type-Safe**: Shared types between TypeScript, C++, Swift, and Kotlin.
|
|
26
|
+
- **Incremental Auth**: Request additional OAuth scopes on the fly.
|
|
27
|
+
- **Expo Ready**: Comes with a powerful Config Plugin for zero-config setup.
|
|
28
|
+
- **Cross-Platform**: Unified API for iOS, Android, and Web.
|
|
29
|
+
- **Auto-Refresh**: Synchronous access to tokens with automatic silent refresh.
|
|
30
|
+
- **Google One-Tap**: Modern login experience on Android using Credential Manager.
|
|
31
|
+
- **Custom Storage**: Pluggable storage adapters for secure persistence (e.g., Keychain).
|
|
32
|
+
- **Refresh Interceptors**: Listen to token updates globally.
|
|
26
33
|
|
|
27
34
|
## Installation
|
|
28
35
|
|
|
@@ -57,8 +64,7 @@ Add the plugin to `app.json`:
|
|
|
57
64
|
}
|
|
58
65
|
```
|
|
59
66
|
|
|
60
|
-
> [!NOTE]
|
|
61
|
-
> `googleServerClientId` is only required if you need a `serverAuthCode` for backend integration.
|
|
67
|
+
> [!NOTE] > `googleServerClientId` is only required if you need a `serverAuthCode` for backend integration.
|
|
62
68
|
|
|
63
69
|
### Bare React Native
|
|
64
70
|
|
|
@@ -183,6 +189,49 @@ if (user?.serverAuthCode) {
|
|
|
183
189
|
}
|
|
184
190
|
```
|
|
185
191
|
|
|
192
|
+
### Custom Storage Adapter
|
|
193
|
+
|
|
194
|
+
By default, Nitro Auth uses standard local storage. You can provide a custom adapter for better security (e.g., using `react-native-keychain`):
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { AuthService, AuthStorageAdapter } from "react-native-nitro-auth";
|
|
198
|
+
|
|
199
|
+
const myStorage: AuthStorageAdapter = {
|
|
200
|
+
save: (key, value) => {
|
|
201
|
+
/* Save to Keychain */
|
|
202
|
+
},
|
|
203
|
+
load: (key) => {
|
|
204
|
+
/* Load from Keychain */
|
|
205
|
+
},
|
|
206
|
+
remove: (key) => {
|
|
207
|
+
/* Clear from Keychain */
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
AuthService.setStorageAdapter(myStorage);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Token Refresh Listeners
|
|
215
|
+
|
|
216
|
+
Perfect for updating your API client (e.g., Axios/Fetch) whenever tokens are refreshed in the background:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
AuthService.onTokensRefreshed((tokens) => {
|
|
220
|
+
console.log("Tokens were updated!", tokens.accessToken);
|
|
221
|
+
apiClient.defaults.headers.common[
|
|
222
|
+
"Authorization"
|
|
223
|
+
] = `Bearer ${tokens.accessToken}`;
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Google One-Tap (Android)
|
|
228
|
+
|
|
229
|
+
Explicitly enable the modern One-Tap flow on Android:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
await login("google", { useOneTap: true });
|
|
233
|
+
```
|
|
234
|
+
|
|
186
235
|
## API Reference
|
|
187
236
|
|
|
188
237
|
### useAuth Hook
|
package/android/build.gradle
CHANGED
|
@@ -45,6 +45,7 @@ android {
|
|
|
45
45
|
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
46
46
|
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
47
47
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
48
|
+
consumerProguardFiles "proguard-rules.pro"
|
|
48
49
|
|
|
49
50
|
externalNativeBuild {
|
|
50
51
|
cmake {
|
|
@@ -63,7 +64,6 @@ android {
|
|
|
63
64
|
|
|
64
65
|
buildFeatures {
|
|
65
66
|
buildConfig true
|
|
66
|
-
consumerProguardFiles "proguard-rules.pro"
|
|
67
67
|
prefab true
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -89,12 +89,17 @@ repositories {
|
|
|
89
89
|
|
|
90
90
|
dependencies {
|
|
91
91
|
implementation "com.facebook.react:react-native:+"
|
|
92
|
-
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.
|
|
92
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.24"
|
|
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.2.0"
|
|
97
97
|
|
|
98
98
|
// Activity result APIs
|
|
99
99
|
implementation "androidx.activity:activity-ktx:1.9.3"
|
|
100
|
+
|
|
101
|
+
// Google Credential Manager (One-Tap / Passkeys)
|
|
102
|
+
implementation "androidx.credentials:credentials:1.5.0"
|
|
103
|
+
implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
|
|
104
|
+
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
|
|
100
105
|
}
|
|
@@ -24,7 +24,7 @@ static std::shared_ptr<Promise<AuthTokens>> gRefreshPromise;
|
|
|
24
24
|
static std::shared_ptr<Promise<std::optional<AuthUser>>> gSilentPromise;
|
|
25
25
|
static std::mutex gMutex;
|
|
26
26
|
|
|
27
|
-
std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::
|
|
27
|
+
std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
|
|
28
28
|
auto promise = Promise<AuthUser>::create();
|
|
29
29
|
auto contextPtr = static_cast<jobject>(AuthCache::getAndroidContext());
|
|
30
30
|
if (!contextPtr) {
|
|
@@ -38,6 +38,16 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
std::string providerStr = (provider == AuthProvider::GOOGLE) ? "google" : "apple";
|
|
41
|
+
std::vector<std::string> scopes = {"email", "profile"};
|
|
42
|
+
std::optional<std::string> loginHint;
|
|
43
|
+
bool useOneTap = false;
|
|
44
|
+
|
|
45
|
+
if (options) {
|
|
46
|
+
if (options->scopes) scopes = *options->scopes;
|
|
47
|
+
loginHint = options->loginHint;
|
|
48
|
+
useOneTap = options->useOneTap.value_or(false);
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
JNIEnv* env = Environment::current();
|
|
42
52
|
jclass stringClass = env->FindClass("java/lang/String");
|
|
43
53
|
jobjectArray jScopes = env->NewObjectArray(scopes.size(), stringClass, nullptr);
|
|
@@ -48,13 +58,14 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
|
|
|
48
58
|
jstring jLoginHint = loginHint.has_value() ? make_jstring(loginHint.value()).get() : nullptr;
|
|
49
59
|
jclass adapterClass = env->FindClass("com/auth/AuthAdapter");
|
|
50
60
|
jmethodID loginMethod = env->GetStaticMethodID(adapterClass, "loginSync",
|
|
51
|
-
"(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V");
|
|
61
|
+
"(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Z)V");
|
|
52
62
|
env->CallStaticVoidMethod(adapterClass, loginMethod,
|
|
53
63
|
contextPtr,
|
|
54
64
|
make_jstring(providerStr).get(),
|
|
55
65
|
nullptr,
|
|
56
66
|
jScopes,
|
|
57
|
-
jLoginHint
|
|
67
|
+
jLoginHint,
|
|
68
|
+
(jboolean)useOneTap);
|
|
58
69
|
|
|
59
70
|
return promise;
|
|
60
71
|
}
|
|
@@ -10,12 +10,23 @@ import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
|
|
10
10
|
import com.google.android.gms.common.GoogleApiAvailability
|
|
11
11
|
import com.google.android.gms.common.ConnectionResult
|
|
12
12
|
import com.google.android.gms.common.api.Scope
|
|
13
|
+
import androidx.credentials.CredentialManager
|
|
14
|
+
import androidx.credentials.GetCredentialRequest
|
|
15
|
+
import androidx.credentials.GetCredentialResponse
|
|
16
|
+
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
|
|
17
|
+
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
|
|
18
|
+
import kotlinx.coroutines.CoroutineScope
|
|
19
|
+
import kotlinx.coroutines.Dispatchers
|
|
20
|
+
import kotlinx.coroutines.launch
|
|
21
|
+
import android.app.Activity
|
|
22
|
+
import android.app.Application
|
|
13
23
|
|
|
14
24
|
object AuthAdapter {
|
|
15
25
|
private const val TAG = "AuthAdapter"
|
|
16
26
|
private const val PREF_NAME = "nitro_auth"
|
|
17
27
|
|
|
18
28
|
private var appContext: Context? = null
|
|
29
|
+
private var currentActivity: Activity? = null
|
|
19
30
|
private var googleSignInClient: GoogleSignInClient? = null
|
|
20
31
|
private var pendingScopes: List<String> = emptyList()
|
|
21
32
|
|
|
@@ -45,7 +56,19 @@ object AuthAdapter {
|
|
|
45
56
|
private external fun nativeOnRefreshError(error: String)
|
|
46
57
|
|
|
47
58
|
fun initialize(context: Context) {
|
|
48
|
-
|
|
59
|
+
val app = context.applicationContext as? Application
|
|
60
|
+
appContext = app
|
|
61
|
+
|
|
62
|
+
app?.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
|
|
63
|
+
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { currentActivity = activity }
|
|
64
|
+
override fun onActivityStarted(activity: Activity) { currentActivity = activity }
|
|
65
|
+
override fun onActivityResumed(activity: Activity) { currentActivity = activity }
|
|
66
|
+
override fun onActivityPaused(activity: Activity) { if (currentActivity == activity) currentActivity = null }
|
|
67
|
+
override fun onActivityStopped(activity: Activity) { if (currentActivity == activity) currentActivity = null }
|
|
68
|
+
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
|
69
|
+
override fun onActivityDestroyed(activity: Activity) { if (currentActivity == activity) currentActivity = null }
|
|
70
|
+
})
|
|
71
|
+
|
|
49
72
|
try {
|
|
50
73
|
System.loadLibrary("NitroAuth")
|
|
51
74
|
nativeInitialize(appContext!!)
|
|
@@ -73,7 +96,7 @@ object AuthAdapter {
|
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
@JvmStatic
|
|
76
|
-
fun loginSync(context: Context, provider: String, googleClientId: String?, scopes: Array<String>?, loginHint: String
|
|
99
|
+
fun loginSync(context: Context, provider: String, googleClientId: String?, scopes: Array<String>?, loginHint: String?, useOneTap: Boolean) {
|
|
77
100
|
if (provider == "apple") {
|
|
78
101
|
nativeOnLoginError("Apple Sign-In is not supported on Android.")
|
|
79
102
|
return
|
|
@@ -94,10 +117,84 @@ object AuthAdapter {
|
|
|
94
117
|
val requestedScopes = scopes?.toList() ?: listOf("email", "profile")
|
|
95
118
|
pendingScopes = requestedScopes
|
|
96
119
|
|
|
97
|
-
|
|
120
|
+
if (useOneTap) {
|
|
121
|
+
loginOneTap(context, clientId, requestedScopes)
|
|
122
|
+
} else {
|
|
123
|
+
val intent = GoogleSignInActivity.createIntent(ctx, clientId, requestedScopes.toTypedArray(), loginHint)
|
|
124
|
+
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
125
|
+
ctx.startActivity(intent)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private fun loginOneTap(context: Context, clientId: String, scopes: List<String>) {
|
|
130
|
+
val activity = currentActivity ?: context as? Activity
|
|
131
|
+
if (activity == null) {
|
|
132
|
+
Log.w(TAG, "No Activity context available for One-Tap, falling back to legacy")
|
|
133
|
+
return loginLegacy(context, clientId, scopes)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
val credentialManager = CredentialManager.create(activity)
|
|
137
|
+
val googleIdOption = GetGoogleIdOption.Builder()
|
|
138
|
+
.setFilterByAuthorizedAccounts(false)
|
|
139
|
+
.setServerClientId(clientId)
|
|
140
|
+
.setAutoSelectEnabled(false) // Disable auto-select for testing so the sheet always shows
|
|
141
|
+
.build()
|
|
142
|
+
|
|
143
|
+
val request = GetCredentialRequest.Builder()
|
|
144
|
+
.addCredentialOption(googleIdOption)
|
|
145
|
+
.build()
|
|
146
|
+
|
|
147
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
148
|
+
try {
|
|
149
|
+
val result = credentialManager.getCredential(context = activity, request = request)
|
|
150
|
+
handleCredentialResponse(result, scopes)
|
|
151
|
+
} catch (e: Exception) {
|
|
152
|
+
Log.w(TAG, "One-Tap failed, falling back to legacy: ${e.message}")
|
|
153
|
+
loginLegacy(context, clientId, scopes)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private fun loginLegacy(context: Context, clientId: String, scopes: List<String>) {
|
|
159
|
+
val ctx = appContext ?: context.applicationContext
|
|
160
|
+
val intent = GoogleSignInActivity.createIntent(ctx, clientId, scopes.toTypedArray(), null)
|
|
161
|
+
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
98
162
|
ctx.startActivity(intent)
|
|
99
163
|
}
|
|
100
164
|
|
|
165
|
+
private fun handleCredentialResponse(response: GetCredentialResponse, scopes: List<String>) {
|
|
166
|
+
val credential = response.credential
|
|
167
|
+
val googleIdTokenCredential = try {
|
|
168
|
+
if (credential is GoogleIdTokenCredential) {
|
|
169
|
+
credential
|
|
170
|
+
} else if (credential.type == "com.google.android.libraries.identity.googleid.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL") {
|
|
171
|
+
GoogleIdTokenCredential.createFrom(credential.data)
|
|
172
|
+
} else {
|
|
173
|
+
null
|
|
174
|
+
}
|
|
175
|
+
} catch (e: Exception) {
|
|
176
|
+
Log.e(TAG, "Failed to parse Google ID token credential: ${e.message}")
|
|
177
|
+
null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (googleIdTokenCredential != null) {
|
|
181
|
+
nativeOnLoginSuccess(
|
|
182
|
+
"google",
|
|
183
|
+
googleIdTokenCredential.id,
|
|
184
|
+
googleIdTokenCredential.displayName,
|
|
185
|
+
googleIdTokenCredential.profilePictureUri?.toString(),
|
|
186
|
+
googleIdTokenCredential.idToken,
|
|
187
|
+
null,
|
|
188
|
+
null,
|
|
189
|
+
scopes.toTypedArray(),
|
|
190
|
+
null
|
|
191
|
+
)
|
|
192
|
+
} else {
|
|
193
|
+
Log.w(TAG, "Unsupported credential type: ${credential.type}")
|
|
194
|
+
nativeOnLoginError("Unsupported credential type: ${credential.type}")
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
101
198
|
@JvmStatic
|
|
102
199
|
fun requestScopesSync(context: Context, scopes: Array<String>) {
|
|
103
200
|
val ctx = appContext ?: context.applicationContext
|
package/cpp/HybridAuth.cpp
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
#include <chrono>
|
|
7
7
|
|
|
8
8
|
namespace margelo::nitro::NitroAuth {
|
|
9
|
+
|
|
10
|
+
bool HybridAuth::sLoggingEnabled = false;
|
|
9
11
|
|
|
10
12
|
HybridAuth::HybridAuth() : HybridObject(TAG) {
|
|
11
13
|
loadFromCache();
|
|
@@ -27,7 +29,14 @@ bool HybridAuth::getHasPlayServices() {
|
|
|
27
29
|
|
|
28
30
|
void HybridAuth::loadFromCache() {
|
|
29
31
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
30
|
-
|
|
32
|
+
std::optional<std::string> json;
|
|
33
|
+
|
|
34
|
+
if (_storageAdapter) {
|
|
35
|
+
json = _storageAdapter->load("nitro_auth_user");
|
|
36
|
+
} else {
|
|
37
|
+
json = AuthCache::getUserJson();
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
if (json) {
|
|
32
41
|
_currentUser = JSONSerializer::deserialize(*json);
|
|
33
42
|
if (_currentUser && _currentUser->scopes) {
|
|
@@ -39,9 +48,17 @@ void HybridAuth::loadFromCache() {
|
|
|
39
48
|
void HybridAuth::saveToCache(const std::optional<AuthUser>& user) {
|
|
40
49
|
if (user) {
|
|
41
50
|
auto json = JSONSerializer::serialize(*user);
|
|
42
|
-
|
|
51
|
+
if (_storageAdapter) {
|
|
52
|
+
_storageAdapter->save("nitro_auth_user", json);
|
|
53
|
+
} else {
|
|
54
|
+
AuthCache::setUserJson(json);
|
|
55
|
+
}
|
|
43
56
|
} else {
|
|
44
|
-
|
|
57
|
+
if (_storageAdapter) {
|
|
58
|
+
_storageAdapter->remove("nitro_auth_user");
|
|
59
|
+
} else {
|
|
60
|
+
AuthCache::clear();
|
|
61
|
+
}
|
|
45
62
|
}
|
|
46
63
|
}
|
|
47
64
|
|
|
@@ -71,6 +88,17 @@ std::function<void()> HybridAuth::onAuthStateChanged(const std::function<void(co
|
|
|
71
88
|
};
|
|
72
89
|
}
|
|
73
90
|
|
|
91
|
+
std::function<void()> HybridAuth::onTokensRefreshed(const std::function<void(const AuthTokens&)>& callback) {
|
|
92
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
93
|
+
int id = _nextTokenListenerId++;
|
|
94
|
+
_tokenListeners[id] = callback;
|
|
95
|
+
|
|
96
|
+
return [this, id]() {
|
|
97
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
98
|
+
_tokenListeners.erase(id);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
74
102
|
void HybridAuth::logout() {
|
|
75
103
|
{
|
|
76
104
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
@@ -84,20 +112,16 @@ void HybridAuth::logout() {
|
|
|
84
112
|
|
|
85
113
|
std::shared_ptr<Promise<void>> HybridAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
|
|
86
114
|
auto promise = Promise<void>::create();
|
|
87
|
-
std::vector<std::string> scopes;
|
|
88
|
-
std::optional<std::string> loginHint;
|
|
89
|
-
if (options) {
|
|
90
|
-
if (options->scopes) scopes = *options->scopes;
|
|
91
|
-
loginHint = options->loginHint;
|
|
92
|
-
}
|
|
93
115
|
|
|
94
|
-
auto loginPromise = PlatformAuth::login(provider,
|
|
95
|
-
loginPromise->addOnResolvedListener([this, promise,
|
|
116
|
+
auto loginPromise = PlatformAuth::login(provider, options);
|
|
117
|
+
loginPromise->addOnResolvedListener([this, promise, options](const AuthUser& user) {
|
|
96
118
|
{
|
|
97
119
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
98
120
|
_currentUser = user;
|
|
99
|
-
|
|
100
|
-
|
|
121
|
+
if (options && options->scopes) {
|
|
122
|
+
_grantedScopes = *options->scopes;
|
|
123
|
+
}
|
|
124
|
+
if (_currentUser) _currentUser->scopes = _grantedScopes;
|
|
101
125
|
saveToCache(_currentUser);
|
|
102
126
|
}
|
|
103
127
|
notifyAuthStateChanged();
|
|
@@ -200,6 +224,7 @@ std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
|
|
|
200
224
|
saveToCache(_currentUser);
|
|
201
225
|
}
|
|
202
226
|
}
|
|
227
|
+
notifyTokensRefreshed(tokens);
|
|
203
228
|
notifyAuthStateChanged();
|
|
204
229
|
promise->resolve(tokens);
|
|
205
230
|
});
|
|
@@ -209,5 +234,31 @@ std::shared_ptr<Promise<AuthTokens>> HybridAuth::refreshToken() {
|
|
|
209
234
|
});
|
|
210
235
|
return promise;
|
|
211
236
|
}
|
|
237
|
+
|
|
238
|
+
void HybridAuth::setLoggingEnabled(bool enabled) {
|
|
239
|
+
sLoggingEnabled = enabled;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
void HybridAuth::setStorageAdapter(const std::optional<std::shared_ptr<HybridAuthStorageAdapterSpec>>& adapter) {
|
|
243
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
244
|
+
_storageAdapter = adapter.value_or(nullptr);
|
|
245
|
+
if (_storageAdapter) {
|
|
246
|
+
loadFromCache();
|
|
247
|
+
notifyAuthStateChanged();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
void HybridAuth::notifyTokensRefreshed(const AuthTokens& tokens) {
|
|
252
|
+
std::vector<std::function<void(const AuthTokens&)>> listeners;
|
|
253
|
+
{
|
|
254
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
255
|
+
for (auto const& [id, listener] : _tokenListeners) {
|
|
256
|
+
listeners.push_back(listener);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const auto& listener : listeners) {
|
|
260
|
+
listener(tokens);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
212
263
|
|
|
213
264
|
} // namespace margelo::nitro::NitroAuth
|
package/cpp/HybridAuth.hpp
CHANGED
|
@@ -28,20 +28,30 @@ public:
|
|
|
28
28
|
|
|
29
29
|
void logout() override;
|
|
30
30
|
std::function<void()> onAuthStateChanged(const std::function<void(const std::optional<AuthUser>&)>& callback) override;
|
|
31
|
+
std::function<void()> onTokensRefreshed(const std::function<void(const AuthTokens&)>& callback) override;
|
|
32
|
+
void setLoggingEnabled(bool enabled) override;
|
|
33
|
+
void setStorageAdapter(const std::optional<std::shared_ptr<HybridAuthStorageAdapterSpec>>& adapter) override;
|
|
31
34
|
|
|
32
35
|
private:
|
|
33
36
|
void loadFromCache();
|
|
34
37
|
void saveToCache(const std::optional<AuthUser>& user);
|
|
35
38
|
void notifyAuthStateChanged();
|
|
39
|
+
void notifyTokensRefreshed(const AuthTokens& tokens);
|
|
36
40
|
|
|
37
41
|
private:
|
|
38
42
|
std::optional<AuthUser> _currentUser;
|
|
39
43
|
std::vector<std::string> _grantedScopes;
|
|
40
44
|
std::map<int, std::function<void(const std::optional<AuthUser>&)>> _listeners;
|
|
41
45
|
int _nextListenerId = 0;
|
|
46
|
+
|
|
47
|
+
std::shared_ptr<HybridAuthStorageAdapterSpec> _storageAdapter;
|
|
48
|
+
std::map<int, std::function<void(const AuthTokens&)>> _tokenListeners;
|
|
49
|
+
int _nextTokenListenerId = 0;
|
|
50
|
+
|
|
42
51
|
std::mutex _mutex;
|
|
43
52
|
|
|
44
53
|
static constexpr auto TAG = "Auth";
|
|
54
|
+
static bool sLoggingEnabled;
|
|
45
55
|
};
|
|
46
56
|
|
|
47
57
|
} // namespace margelo::nitro::NitroAuth
|
package/cpp/PlatformAuth.hpp
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "AuthProvider.hpp"
|
|
4
4
|
#include "AuthUser.hpp"
|
|
5
5
|
#include "AuthTokens.hpp"
|
|
6
|
+
#include "LoginOptions.hpp"
|
|
6
7
|
#include <NitroModules/Promise.hpp>
|
|
7
8
|
#include <memory>
|
|
8
9
|
#include <vector>
|
|
@@ -14,7 +15,7 @@ using namespace margelo::nitro;
|
|
|
14
15
|
|
|
15
16
|
class PlatformAuth {
|
|
16
17
|
public:
|
|
17
|
-
static std::shared_ptr<Promise<AuthUser>> login(AuthProvider provider, const std::
|
|
18
|
+
static std::shared_ptr<Promise<AuthUser>> login(AuthProvider provider, const std::optional<LoginOptions>& options = std::nullopt);
|
|
18
19
|
static std::shared_ptr<Promise<AuthUser>> requestScopes(const std::vector<std::string>& scopes);
|
|
19
20
|
static std::shared_ptr<Promise<AuthTokens>> refreshToken();
|
|
20
21
|
static std::shared_ptr<Promise<std::optional<AuthUser>>> silentRestore();
|
package/ios/PlatformAuth+iOS.mm
CHANGED
|
@@ -11,14 +11,32 @@
|
|
|
11
11
|
#import "react_native_nitro_auth-Swift.h"
|
|
12
12
|
#endif
|
|
13
13
|
|
|
14
|
+
#include "LoginOptions.hpp"
|
|
15
|
+
|
|
14
16
|
namespace margelo::nitro::NitroAuth {
|
|
15
17
|
|
|
16
|
-
std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::
|
|
18
|
+
std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, const std::optional<LoginOptions>& options) {
|
|
17
19
|
auto promise = Promise<AuthUser>::create();
|
|
18
20
|
NSString* providerStr = provider == AuthProvider::GOOGLE ? @"google" : @"apple";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
NSString* hintStr =
|
|
21
|
+
|
|
22
|
+
NSMutableArray* scopesArray = [NSMutableArray array];
|
|
23
|
+
NSString* hintStr = nil;
|
|
24
|
+
|
|
25
|
+
if (options.has_value()) {
|
|
26
|
+
if (options->scopes.has_value()) {
|
|
27
|
+
for (const auto& scope : *options->scopes) {
|
|
28
|
+
[scopesArray addObject:[NSString stringWithUTF8String:scope.c_str()]];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (options->loginHint.has_value()) {
|
|
32
|
+
hintStr = [NSString stringWithUTF8String:options->loginHint->c_str()];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Default scopes if none provided
|
|
37
|
+
if (scopesArray.count == 0) {
|
|
38
|
+
[scopesArray addObjectsFromArray:@[@"openid", @"email", @"profile"]];
|
|
39
|
+
}
|
|
22
40
|
|
|
23
41
|
[AuthAdapter loginWithProvider:providerStr scopes:scopesArray loginHint:hintStr completion:^(NSDictionary* _Nullable data, NSString* _Nullable error) {
|
|
24
42
|
if (error != nil) {
|