@auth-gate/react-native 0.7.1
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/dist/index.cjs +403 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.mjs +372 -0
- package/package.json +42 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AuthGateError: () => import_core2.AuthGateError,
|
|
34
|
+
AuthGateNativeClient: () => AuthGateNativeClient,
|
|
35
|
+
AuthGuard: () => AuthGuard,
|
|
36
|
+
AuthProvider: () => AuthProvider,
|
|
37
|
+
ConfigurationError: () => import_core2.ConfigurationError,
|
|
38
|
+
STORAGE_KEYS: () => STORAGE_KEYS,
|
|
39
|
+
SUPPORTED_PROVIDERS: () => import_core2.SUPPORTED_PROVIDERS,
|
|
40
|
+
TokenVerificationError: () => import_core2.TokenVerificationError,
|
|
41
|
+
useAuth: () => useAuth,
|
|
42
|
+
useSession: () => useSession
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
46
|
+
// src/provider.tsx
|
|
47
|
+
var import_react = require("react");
|
|
48
|
+
|
|
49
|
+
// src/client.ts
|
|
50
|
+
var import_core = require("@auth-gate/core");
|
|
51
|
+
|
|
52
|
+
// src/storage.ts
|
|
53
|
+
var SecureStore = __toESM(require("expo-secure-store"), 1);
|
|
54
|
+
|
|
55
|
+
// src/types.ts
|
|
56
|
+
var STORAGE_KEYS = {
|
|
57
|
+
ACCESS_TOKEN: "authgate_access_token",
|
|
58
|
+
REFRESH_TOKEN: "authgate_refresh_token",
|
|
59
|
+
USER: "authgate_user"
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/storage.ts
|
|
63
|
+
async function saveAccessToken(token) {
|
|
64
|
+
await SecureStore.setItemAsync(STORAGE_KEYS.ACCESS_TOKEN, token);
|
|
65
|
+
}
|
|
66
|
+
async function getAccessToken() {
|
|
67
|
+
return SecureStore.getItemAsync(STORAGE_KEYS.ACCESS_TOKEN);
|
|
68
|
+
}
|
|
69
|
+
async function saveRefreshToken(token) {
|
|
70
|
+
await SecureStore.setItemAsync(STORAGE_KEYS.REFRESH_TOKEN, token);
|
|
71
|
+
}
|
|
72
|
+
async function getRefreshToken() {
|
|
73
|
+
return SecureStore.getItemAsync(STORAGE_KEYS.REFRESH_TOKEN);
|
|
74
|
+
}
|
|
75
|
+
async function saveUser(user) {
|
|
76
|
+
await SecureStore.setItemAsync(STORAGE_KEYS.USER, JSON.stringify(user));
|
|
77
|
+
}
|
|
78
|
+
async function clearStorage() {
|
|
79
|
+
await Promise.all([
|
|
80
|
+
SecureStore.deleteItemAsync(STORAGE_KEYS.ACCESS_TOKEN),
|
|
81
|
+
SecureStore.deleteItemAsync(STORAGE_KEYS.REFRESH_TOKEN),
|
|
82
|
+
SecureStore.deleteItemAsync(STORAGE_KEYS.USER)
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/oauth.ts
|
|
87
|
+
var WebBrowser = __toESM(require("expo-web-browser"), 1);
|
|
88
|
+
async function openOAuthFlow(client, provider, scheme, callbackPath) {
|
|
89
|
+
const callbackUrl = `${scheme}://${callbackPath}`;
|
|
90
|
+
const authUrl = await client.getOAuthUrl(provider, callbackUrl);
|
|
91
|
+
const result = await WebBrowser.openAuthSessionAsync(authUrl, callbackUrl);
|
|
92
|
+
if (result.type !== "success" || !result.url) {
|
|
93
|
+
throw new Error(`OAuth flow cancelled or failed: ${result.type}`);
|
|
94
|
+
}
|
|
95
|
+
const url = new URL(result.url);
|
|
96
|
+
const token = url.searchParams.get("token");
|
|
97
|
+
const refreshToken = url.searchParams.get("refresh_token");
|
|
98
|
+
if (!token) {
|
|
99
|
+
const error = url.searchParams.get("error");
|
|
100
|
+
throw new Error(error || "No token received from OAuth callback");
|
|
101
|
+
}
|
|
102
|
+
return { token, refreshToken: refreshToken != null ? refreshToken : void 0 };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/client.ts
|
|
106
|
+
var AuthGateNativeClient = class {
|
|
107
|
+
constructor(config) {
|
|
108
|
+
this.refreshTimer = null;
|
|
109
|
+
this.onSessionChange = null;
|
|
110
|
+
var _a;
|
|
111
|
+
this.core = (0, import_core.createAuthGateClient)({
|
|
112
|
+
apiKey: config.apiKey,
|
|
113
|
+
baseUrl: config.baseUrl,
|
|
114
|
+
sessionMaxAge: config.sessionMaxAge
|
|
115
|
+
});
|
|
116
|
+
this.scheme = config.scheme;
|
|
117
|
+
this.callbackPath = (_a = config.callbackPath) != null ? _a : "auth/callback";
|
|
118
|
+
}
|
|
119
|
+
/** Register a callback to be notified when the session changes. */
|
|
120
|
+
setSessionChangeListener(listener) {
|
|
121
|
+
this.onSessionChange = listener;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Restore session from secure storage on app launch.
|
|
125
|
+
* Verifies the stored token is still valid.
|
|
126
|
+
*/
|
|
127
|
+
async restoreSession() {
|
|
128
|
+
const token = await getAccessToken();
|
|
129
|
+
if (!token) return null;
|
|
130
|
+
const result = await this.core.verifyToken(token);
|
|
131
|
+
if (result.valid && result.user) {
|
|
132
|
+
await saveUser(result.user);
|
|
133
|
+
this.scheduleRefresh(result.expiresAt);
|
|
134
|
+
return result.user;
|
|
135
|
+
}
|
|
136
|
+
return this.tryRefresh();
|
|
137
|
+
}
|
|
138
|
+
/** Start an OAuth flow in the system browser. */
|
|
139
|
+
async loginWithOAuth(provider) {
|
|
140
|
+
const result = await openOAuthFlow(
|
|
141
|
+
this.core,
|
|
142
|
+
provider,
|
|
143
|
+
this.scheme,
|
|
144
|
+
this.callbackPath
|
|
145
|
+
);
|
|
146
|
+
await this.handleAuthResult(result);
|
|
147
|
+
}
|
|
148
|
+
/** Sign in with email and password. May return MFA challenge. */
|
|
149
|
+
async loginWithEmail(email, password) {
|
|
150
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
151
|
+
const response = await this.core.emailSignin({
|
|
152
|
+
email,
|
|
153
|
+
password,
|
|
154
|
+
callbackUrl
|
|
155
|
+
});
|
|
156
|
+
const data = await response.json();
|
|
157
|
+
if (data.mfa_required) {
|
|
158
|
+
return {
|
|
159
|
+
mfaRequired: true,
|
|
160
|
+
challenge: data.mfa_challenge,
|
|
161
|
+
methods: data.mfa_methods
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (data.token) {
|
|
165
|
+
await this.handleAuthResult({
|
|
166
|
+
token: data.token,
|
|
167
|
+
refreshToken: data.refresh_token
|
|
168
|
+
});
|
|
169
|
+
} else if (!response.ok) {
|
|
170
|
+
throw new Error(data.error || `Sign in failed: ${response.status}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Sign up with email and password. */
|
|
174
|
+
async signUp(email, password, name) {
|
|
175
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
176
|
+
const response = await this.core.emailSignup({
|
|
177
|
+
email,
|
|
178
|
+
password,
|
|
179
|
+
name,
|
|
180
|
+
callbackUrl
|
|
181
|
+
});
|
|
182
|
+
const data = await response.json();
|
|
183
|
+
if (data.token) {
|
|
184
|
+
await this.handleAuthResult({
|
|
185
|
+
token: data.token,
|
|
186
|
+
refreshToken: data.refresh_token
|
|
187
|
+
});
|
|
188
|
+
} else if (!response.ok) {
|
|
189
|
+
throw new Error(data.error || `Sign up failed: ${response.status}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Send a magic link email. */
|
|
193
|
+
async sendMagicLink(email) {
|
|
194
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
195
|
+
const response = await this.core.magicLinkSend({ email, callbackUrl });
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
const data = await response.json().catch(() => ({}));
|
|
198
|
+
throw new Error(data.error || `Magic link failed: ${response.status}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Send an SMS verification code. */
|
|
202
|
+
async sendSmsCode(phone) {
|
|
203
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
204
|
+
const response = await this.core.smsSendCode({ phone, callbackUrl });
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
const data = await response.json().catch(() => ({}));
|
|
207
|
+
throw new Error(data.error || `SMS send failed: ${response.status}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Verify an SMS code and complete authentication. */
|
|
211
|
+
async verifySmsCode(phone, code) {
|
|
212
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
213
|
+
const response = await this.core.smsVerifyCode({
|
|
214
|
+
phone,
|
|
215
|
+
code,
|
|
216
|
+
callbackUrl
|
|
217
|
+
});
|
|
218
|
+
const data = await response.json();
|
|
219
|
+
if (data.token) {
|
|
220
|
+
await this.handleAuthResult({
|
|
221
|
+
token: data.token,
|
|
222
|
+
refreshToken: data.refresh_token
|
|
223
|
+
});
|
|
224
|
+
} else if (!response.ok) {
|
|
225
|
+
throw new Error(data.error || `SMS verify failed: ${response.status}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** Verify MFA code to complete authentication after email sign-in. */
|
|
229
|
+
async verifyMfa(challenge, code, method) {
|
|
230
|
+
const response = await this.core.mfaVerify({ challenge, code, method });
|
|
231
|
+
const data = await response.json();
|
|
232
|
+
if (data.token) {
|
|
233
|
+
await this.handleAuthResult({
|
|
234
|
+
token: data.token,
|
|
235
|
+
refreshToken: data.refresh_token
|
|
236
|
+
});
|
|
237
|
+
} else if (!response.ok) {
|
|
238
|
+
throw new Error(data.error || `MFA verify failed: ${response.status}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** Sign out: revoke session and clear storage. */
|
|
242
|
+
async logout() {
|
|
243
|
+
var _a;
|
|
244
|
+
this.cancelRefresh();
|
|
245
|
+
const refreshToken = await getRefreshToken();
|
|
246
|
+
if (refreshToken) {
|
|
247
|
+
await this.core.revokeSession(refreshToken).catch(() => {
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
await clearStorage();
|
|
251
|
+
(_a = this.onSessionChange) == null ? void 0 : _a.call(this, null);
|
|
252
|
+
}
|
|
253
|
+
/** Manually refresh the session using the stored refresh token. */
|
|
254
|
+
async refresh() {
|
|
255
|
+
return this.tryRefresh();
|
|
256
|
+
}
|
|
257
|
+
// ── Internal ─────────────────────────────────────────────────────
|
|
258
|
+
async handleAuthResult(result) {
|
|
259
|
+
var _a;
|
|
260
|
+
const verified = await this.core.verifyToken(result.token);
|
|
261
|
+
if (!verified.valid || !verified.user) {
|
|
262
|
+
throw new Error(verified.error || "Token verification failed");
|
|
263
|
+
}
|
|
264
|
+
await saveAccessToken(result.token);
|
|
265
|
+
if (result.refreshToken) {
|
|
266
|
+
await saveRefreshToken(result.refreshToken);
|
|
267
|
+
}
|
|
268
|
+
await saveUser(verified.user);
|
|
269
|
+
this.scheduleRefresh(verified.expiresAt);
|
|
270
|
+
(_a = this.onSessionChange) == null ? void 0 : _a.call(this, verified.user);
|
|
271
|
+
}
|
|
272
|
+
async tryRefresh() {
|
|
273
|
+
var _a, _b, _c;
|
|
274
|
+
const refreshToken = await getRefreshToken();
|
|
275
|
+
if (!refreshToken) {
|
|
276
|
+
await clearStorage();
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
const result = await this.core.refreshToken(refreshToken);
|
|
280
|
+
if (!result) {
|
|
281
|
+
await clearStorage();
|
|
282
|
+
(_a = this.onSessionChange) == null ? void 0 : _a.call(this, null);
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
await saveAccessToken(result.token);
|
|
286
|
+
const verified = await this.core.verifyToken(result.token);
|
|
287
|
+
if (verified.valid && verified.user) {
|
|
288
|
+
await saveUser(verified.user);
|
|
289
|
+
this.scheduleRefresh(verified.expiresAt);
|
|
290
|
+
(_b = this.onSessionChange) == null ? void 0 : _b.call(this, verified.user);
|
|
291
|
+
return verified.user;
|
|
292
|
+
}
|
|
293
|
+
await clearStorage();
|
|
294
|
+
(_c = this.onSessionChange) == null ? void 0 : _c.call(this, null);
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
scheduleRefresh(expiresAt) {
|
|
298
|
+
this.cancelRefresh();
|
|
299
|
+
if (!expiresAt) return;
|
|
300
|
+
const expiresMs = new Date(expiresAt).getTime();
|
|
301
|
+
const refreshIn = Math.max(expiresMs - Date.now() - 6e4, 1e4);
|
|
302
|
+
this.refreshTimer = setTimeout(() => {
|
|
303
|
+
this.tryRefresh();
|
|
304
|
+
}, refreshIn);
|
|
305
|
+
}
|
|
306
|
+
cancelRefresh() {
|
|
307
|
+
if (this.refreshTimer) {
|
|
308
|
+
clearTimeout(this.refreshTimer);
|
|
309
|
+
this.refreshTimer = null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/provider.tsx
|
|
315
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
316
|
+
var AuthContext = (0, import_react.createContext)(null);
|
|
317
|
+
function AuthProvider({ children, config }) {
|
|
318
|
+
const [user, setUser] = (0, import_react.useState)(null);
|
|
319
|
+
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
320
|
+
const clientRef = (0, import_react.useRef)(null);
|
|
321
|
+
if (!clientRef.current) {
|
|
322
|
+
clientRef.current = new AuthGateNativeClient(config);
|
|
323
|
+
}
|
|
324
|
+
const client = clientRef.current;
|
|
325
|
+
(0, import_react.useEffect)(() => {
|
|
326
|
+
client.setSessionChangeListener(setUser);
|
|
327
|
+
return () => client.setSessionChangeListener(() => {
|
|
328
|
+
});
|
|
329
|
+
}, [client]);
|
|
330
|
+
(0, import_react.useEffect)(() => {
|
|
331
|
+
client.restoreSession().then((restored) => setUser(restored)).finally(() => setLoading(false));
|
|
332
|
+
}, [client]);
|
|
333
|
+
const refresh = (0, import_react.useCallback)(async () => {
|
|
334
|
+
const refreshed = await client.refresh();
|
|
335
|
+
setUser(refreshed);
|
|
336
|
+
}, [client]);
|
|
337
|
+
const logout = (0, import_react.useCallback)(async () => {
|
|
338
|
+
await client.logout();
|
|
339
|
+
setUser(null);
|
|
340
|
+
}, [client]);
|
|
341
|
+
const login = (0, import_react.useMemo)(
|
|
342
|
+
() => ({
|
|
343
|
+
withOAuth: (provider) => client.loginWithOAuth(provider),
|
|
344
|
+
withEmail: (email, password) => client.loginWithEmail(email, password),
|
|
345
|
+
withMagicLink: (email) => client.sendMagicLink(email),
|
|
346
|
+
signUp: (email, password, name) => client.signUp(email, password, name),
|
|
347
|
+
withSms: (phone) => client.sendSmsCode(phone),
|
|
348
|
+
verifySmsCode: (phone, code) => client.verifySmsCode(phone, code),
|
|
349
|
+
verifyMfa: (challenge, code, method) => client.verifyMfa(challenge, code, method)
|
|
350
|
+
}),
|
|
351
|
+
[client]
|
|
352
|
+
);
|
|
353
|
+
const value = (0, import_react.useMemo)(
|
|
354
|
+
() => ({ user, loading, login, logout, refresh }),
|
|
355
|
+
[user, loading, login, logout, refresh]
|
|
356
|
+
);
|
|
357
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext, { value, children });
|
|
358
|
+
}
|
|
359
|
+
function useAuthContext() {
|
|
360
|
+
const context = (0, import_react.useContext)(AuthContext);
|
|
361
|
+
if (!context) {
|
|
362
|
+
throw new Error("useAuth must be used within an AuthProvider");
|
|
363
|
+
}
|
|
364
|
+
return context;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/hooks.ts
|
|
368
|
+
function useAuth() {
|
|
369
|
+
return useAuthContext();
|
|
370
|
+
}
|
|
371
|
+
function useSession() {
|
|
372
|
+
const { user } = useAuthContext();
|
|
373
|
+
return user;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/guard.tsx
|
|
377
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
378
|
+
function AuthGuard({ children, fallback, loading }) {
|
|
379
|
+
const { user, loading: isLoading } = useAuthContext();
|
|
380
|
+
if (isLoading) {
|
|
381
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: loading != null ? loading : null });
|
|
382
|
+
}
|
|
383
|
+
if (!user) {
|
|
384
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
|
|
385
|
+
}
|
|
386
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/index.ts
|
|
390
|
+
var import_core2 = require("@auth-gate/core");
|
|
391
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
392
|
+
0 && (module.exports = {
|
|
393
|
+
AuthGateError,
|
|
394
|
+
AuthGateNativeClient,
|
|
395
|
+
AuthGuard,
|
|
396
|
+
AuthProvider,
|
|
397
|
+
ConfigurationError,
|
|
398
|
+
STORAGE_KEYS,
|
|
399
|
+
SUPPORTED_PROVIDERS,
|
|
400
|
+
TokenVerificationError,
|
|
401
|
+
useAuth,
|
|
402
|
+
useSession
|
|
403
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { AuthGateUser, OAuthProvider, AuthGateClient } from '@auth-gate/core';
|
|
4
|
+
export { AuthGateError, AuthGateUser, BackupCodesResponse, ConfigurationError, MfaChallengeResponse, MfaStatus, OAuthProvider, SUPPORTED_PROVIDERS, TokenVerificationError, TotpSetupResponse } from '@auth-gate/core';
|
|
5
|
+
|
|
6
|
+
/** Configuration for the React Native AuthGate SDK. */
|
|
7
|
+
interface AuthGateNativeConfig {
|
|
8
|
+
/** Server-side API key for your AuthGate project. */
|
|
9
|
+
apiKey: string;
|
|
10
|
+
/** Base URL of the AuthGate service. */
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
/** Deep link scheme for OAuth callbacks (e.g. "myapp"). */
|
|
13
|
+
scheme: string;
|
|
14
|
+
/**
|
|
15
|
+
* Session lifetime in seconds.
|
|
16
|
+
* @defaultValue 604800 (7 days)
|
|
17
|
+
*/
|
|
18
|
+
sessionMaxAge?: number;
|
|
19
|
+
/** Custom callback path fragment appended to the deep link scheme. @defaultValue "auth/callback" */
|
|
20
|
+
callbackPath?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Login methods exposed via the useAuth hook. */
|
|
23
|
+
interface LoginMethods {
|
|
24
|
+
/** Start an OAuth flow in the system browser. */
|
|
25
|
+
withOAuth: (provider: OAuthProvider) => Promise<void>;
|
|
26
|
+
/** Sign in with email and password. Returns MFA challenge if required. */
|
|
27
|
+
withEmail: (email: string, password: string) => Promise<MfaChallengeResult | void>;
|
|
28
|
+
/** Send a magic link to the given email. */
|
|
29
|
+
withMagicLink: (email: string) => Promise<void>;
|
|
30
|
+
/** Sign up with email, password, and optional name. */
|
|
31
|
+
signUp: (email: string, password: string, name?: string) => Promise<void>;
|
|
32
|
+
/** Send an SMS verification code. */
|
|
33
|
+
withSms: (phone: string) => Promise<void>;
|
|
34
|
+
/** Verify an SMS code. */
|
|
35
|
+
verifySmsCode: (phone: string, code: string) => Promise<void>;
|
|
36
|
+
/** Complete MFA verification. */
|
|
37
|
+
verifyMfa: (challenge: string, code: string, method: "totp" | "sms" | "backup_code") => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/** Value returned by useAuth(). */
|
|
40
|
+
interface AuthContextValue {
|
|
41
|
+
user: AuthGateUser | null;
|
|
42
|
+
loading: boolean;
|
|
43
|
+
login: LoginMethods;
|
|
44
|
+
logout: () => Promise<void>;
|
|
45
|
+
refresh: () => Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/** Returned when MFA is required after email sign-in. */
|
|
48
|
+
interface MfaChallengeResult {
|
|
49
|
+
mfaRequired: true;
|
|
50
|
+
challenge: string;
|
|
51
|
+
methods: string[];
|
|
52
|
+
}
|
|
53
|
+
/** Keys used for secure storage. */
|
|
54
|
+
declare const STORAGE_KEYS: {
|
|
55
|
+
readonly ACCESS_TOKEN: "authgate_access_token";
|
|
56
|
+
readonly REFRESH_TOKEN: "authgate_refresh_token";
|
|
57
|
+
readonly USER: "authgate_user";
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
interface AuthProviderProps {
|
|
61
|
+
children: ReactNode;
|
|
62
|
+
/** AuthGate configuration including API key, base URL, and deep link scheme. */
|
|
63
|
+
config: AuthGateNativeConfig;
|
|
64
|
+
}
|
|
65
|
+
declare function AuthProvider({ children, config }: AuthProviderProps): react_jsx_runtime.JSX.Element;
|
|
66
|
+
|
|
67
|
+
/** Access the full auth context: user, loading, login methods, logout, refresh. */
|
|
68
|
+
declare function useAuth(): AuthContextValue;
|
|
69
|
+
/** Shorthand hook that returns just the current user (or null). */
|
|
70
|
+
declare function useSession(): AuthGateUser | null;
|
|
71
|
+
|
|
72
|
+
interface AuthGuardProps {
|
|
73
|
+
children: ReactNode;
|
|
74
|
+
/** Content to render when the user is not authenticated. */
|
|
75
|
+
fallback: ReactNode;
|
|
76
|
+
/** Content to render while the session is loading. */
|
|
77
|
+
loading?: ReactNode;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Renders children when authenticated, fallback when not.
|
|
81
|
+
* Unlike the web AuthGuard, this doesn't redirect — it renders the fallback component
|
|
82
|
+
* (typically a login screen) since mobile navigation works differently.
|
|
83
|
+
*/
|
|
84
|
+
declare function AuthGuard({ children, fallback, loading }: AuthGuardProps): react_jsx_runtime.JSX.Element;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Native client that wraps core AuthGateClient with mobile-specific
|
|
88
|
+
* storage and OAuth flows. Manages tokens in secure storage instead of cookies.
|
|
89
|
+
*/
|
|
90
|
+
declare class AuthGateNativeClient {
|
|
91
|
+
readonly core: AuthGateClient;
|
|
92
|
+
private readonly scheme;
|
|
93
|
+
private readonly callbackPath;
|
|
94
|
+
private refreshTimer;
|
|
95
|
+
private onSessionChange;
|
|
96
|
+
constructor(config: AuthGateNativeConfig);
|
|
97
|
+
/** Register a callback to be notified when the session changes. */
|
|
98
|
+
setSessionChangeListener(listener: (user: AuthGateUser | null) => void): void;
|
|
99
|
+
/**
|
|
100
|
+
* Restore session from secure storage on app launch.
|
|
101
|
+
* Verifies the stored token is still valid.
|
|
102
|
+
*/
|
|
103
|
+
restoreSession(): Promise<AuthGateUser | null>;
|
|
104
|
+
/** Start an OAuth flow in the system browser. */
|
|
105
|
+
loginWithOAuth(provider: OAuthProvider): Promise<void>;
|
|
106
|
+
/** Sign in with email and password. May return MFA challenge. */
|
|
107
|
+
loginWithEmail(email: string, password: string): Promise<MfaChallengeResult | void>;
|
|
108
|
+
/** Sign up with email and password. */
|
|
109
|
+
signUp(email: string, password: string, name?: string): Promise<void>;
|
|
110
|
+
/** Send a magic link email. */
|
|
111
|
+
sendMagicLink(email: string): Promise<void>;
|
|
112
|
+
/** Send an SMS verification code. */
|
|
113
|
+
sendSmsCode(phone: string): Promise<void>;
|
|
114
|
+
/** Verify an SMS code and complete authentication. */
|
|
115
|
+
verifySmsCode(phone: string, code: string): Promise<void>;
|
|
116
|
+
/** Verify MFA code to complete authentication after email sign-in. */
|
|
117
|
+
verifyMfa(challenge: string, code: string, method: "totp" | "sms" | "backup_code"): Promise<void>;
|
|
118
|
+
/** Sign out: revoke session and clear storage. */
|
|
119
|
+
logout(): Promise<void>;
|
|
120
|
+
/** Manually refresh the session using the stored refresh token. */
|
|
121
|
+
refresh(): Promise<AuthGateUser | null>;
|
|
122
|
+
private handleAuthResult;
|
|
123
|
+
private tryRefresh;
|
|
124
|
+
private scheduleRefresh;
|
|
125
|
+
private cancelRefresh;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { type AuthContextValue, AuthGateNativeClient, type AuthGateNativeConfig, AuthGuard, type AuthGuardProps, AuthProvider, type AuthProviderProps, type LoginMethods, type MfaChallengeResult, STORAGE_KEYS, useAuth, useSession };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { AuthGateUser, OAuthProvider, AuthGateClient } from '@auth-gate/core';
|
|
4
|
+
export { AuthGateError, AuthGateUser, BackupCodesResponse, ConfigurationError, MfaChallengeResponse, MfaStatus, OAuthProvider, SUPPORTED_PROVIDERS, TokenVerificationError, TotpSetupResponse } from '@auth-gate/core';
|
|
5
|
+
|
|
6
|
+
/** Configuration for the React Native AuthGate SDK. */
|
|
7
|
+
interface AuthGateNativeConfig {
|
|
8
|
+
/** Server-side API key for your AuthGate project. */
|
|
9
|
+
apiKey: string;
|
|
10
|
+
/** Base URL of the AuthGate service. */
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
/** Deep link scheme for OAuth callbacks (e.g. "myapp"). */
|
|
13
|
+
scheme: string;
|
|
14
|
+
/**
|
|
15
|
+
* Session lifetime in seconds.
|
|
16
|
+
* @defaultValue 604800 (7 days)
|
|
17
|
+
*/
|
|
18
|
+
sessionMaxAge?: number;
|
|
19
|
+
/** Custom callback path fragment appended to the deep link scheme. @defaultValue "auth/callback" */
|
|
20
|
+
callbackPath?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Login methods exposed via the useAuth hook. */
|
|
23
|
+
interface LoginMethods {
|
|
24
|
+
/** Start an OAuth flow in the system browser. */
|
|
25
|
+
withOAuth: (provider: OAuthProvider) => Promise<void>;
|
|
26
|
+
/** Sign in with email and password. Returns MFA challenge if required. */
|
|
27
|
+
withEmail: (email: string, password: string) => Promise<MfaChallengeResult | void>;
|
|
28
|
+
/** Send a magic link to the given email. */
|
|
29
|
+
withMagicLink: (email: string) => Promise<void>;
|
|
30
|
+
/** Sign up with email, password, and optional name. */
|
|
31
|
+
signUp: (email: string, password: string, name?: string) => Promise<void>;
|
|
32
|
+
/** Send an SMS verification code. */
|
|
33
|
+
withSms: (phone: string) => Promise<void>;
|
|
34
|
+
/** Verify an SMS code. */
|
|
35
|
+
verifySmsCode: (phone: string, code: string) => Promise<void>;
|
|
36
|
+
/** Complete MFA verification. */
|
|
37
|
+
verifyMfa: (challenge: string, code: string, method: "totp" | "sms" | "backup_code") => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/** Value returned by useAuth(). */
|
|
40
|
+
interface AuthContextValue {
|
|
41
|
+
user: AuthGateUser | null;
|
|
42
|
+
loading: boolean;
|
|
43
|
+
login: LoginMethods;
|
|
44
|
+
logout: () => Promise<void>;
|
|
45
|
+
refresh: () => Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/** Returned when MFA is required after email sign-in. */
|
|
48
|
+
interface MfaChallengeResult {
|
|
49
|
+
mfaRequired: true;
|
|
50
|
+
challenge: string;
|
|
51
|
+
methods: string[];
|
|
52
|
+
}
|
|
53
|
+
/** Keys used for secure storage. */
|
|
54
|
+
declare const STORAGE_KEYS: {
|
|
55
|
+
readonly ACCESS_TOKEN: "authgate_access_token";
|
|
56
|
+
readonly REFRESH_TOKEN: "authgate_refresh_token";
|
|
57
|
+
readonly USER: "authgate_user";
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
interface AuthProviderProps {
|
|
61
|
+
children: ReactNode;
|
|
62
|
+
/** AuthGate configuration including API key, base URL, and deep link scheme. */
|
|
63
|
+
config: AuthGateNativeConfig;
|
|
64
|
+
}
|
|
65
|
+
declare function AuthProvider({ children, config }: AuthProviderProps): react_jsx_runtime.JSX.Element;
|
|
66
|
+
|
|
67
|
+
/** Access the full auth context: user, loading, login methods, logout, refresh. */
|
|
68
|
+
declare function useAuth(): AuthContextValue;
|
|
69
|
+
/** Shorthand hook that returns just the current user (or null). */
|
|
70
|
+
declare function useSession(): AuthGateUser | null;
|
|
71
|
+
|
|
72
|
+
interface AuthGuardProps {
|
|
73
|
+
children: ReactNode;
|
|
74
|
+
/** Content to render when the user is not authenticated. */
|
|
75
|
+
fallback: ReactNode;
|
|
76
|
+
/** Content to render while the session is loading. */
|
|
77
|
+
loading?: ReactNode;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Renders children when authenticated, fallback when not.
|
|
81
|
+
* Unlike the web AuthGuard, this doesn't redirect — it renders the fallback component
|
|
82
|
+
* (typically a login screen) since mobile navigation works differently.
|
|
83
|
+
*/
|
|
84
|
+
declare function AuthGuard({ children, fallback, loading }: AuthGuardProps): react_jsx_runtime.JSX.Element;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Native client that wraps core AuthGateClient with mobile-specific
|
|
88
|
+
* storage and OAuth flows. Manages tokens in secure storage instead of cookies.
|
|
89
|
+
*/
|
|
90
|
+
declare class AuthGateNativeClient {
|
|
91
|
+
readonly core: AuthGateClient;
|
|
92
|
+
private readonly scheme;
|
|
93
|
+
private readonly callbackPath;
|
|
94
|
+
private refreshTimer;
|
|
95
|
+
private onSessionChange;
|
|
96
|
+
constructor(config: AuthGateNativeConfig);
|
|
97
|
+
/** Register a callback to be notified when the session changes. */
|
|
98
|
+
setSessionChangeListener(listener: (user: AuthGateUser | null) => void): void;
|
|
99
|
+
/**
|
|
100
|
+
* Restore session from secure storage on app launch.
|
|
101
|
+
* Verifies the stored token is still valid.
|
|
102
|
+
*/
|
|
103
|
+
restoreSession(): Promise<AuthGateUser | null>;
|
|
104
|
+
/** Start an OAuth flow in the system browser. */
|
|
105
|
+
loginWithOAuth(provider: OAuthProvider): Promise<void>;
|
|
106
|
+
/** Sign in with email and password. May return MFA challenge. */
|
|
107
|
+
loginWithEmail(email: string, password: string): Promise<MfaChallengeResult | void>;
|
|
108
|
+
/** Sign up with email and password. */
|
|
109
|
+
signUp(email: string, password: string, name?: string): Promise<void>;
|
|
110
|
+
/** Send a magic link email. */
|
|
111
|
+
sendMagicLink(email: string): Promise<void>;
|
|
112
|
+
/** Send an SMS verification code. */
|
|
113
|
+
sendSmsCode(phone: string): Promise<void>;
|
|
114
|
+
/** Verify an SMS code and complete authentication. */
|
|
115
|
+
verifySmsCode(phone: string, code: string): Promise<void>;
|
|
116
|
+
/** Verify MFA code to complete authentication after email sign-in. */
|
|
117
|
+
verifyMfa(challenge: string, code: string, method: "totp" | "sms" | "backup_code"): Promise<void>;
|
|
118
|
+
/** Sign out: revoke session and clear storage. */
|
|
119
|
+
logout(): Promise<void>;
|
|
120
|
+
/** Manually refresh the session using the stored refresh token. */
|
|
121
|
+
refresh(): Promise<AuthGateUser | null>;
|
|
122
|
+
private handleAuthResult;
|
|
123
|
+
private tryRefresh;
|
|
124
|
+
private scheduleRefresh;
|
|
125
|
+
private cancelRefresh;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { type AuthContextValue, AuthGateNativeClient, type AuthGateNativeConfig, AuthGuard, type AuthGuardProps, AuthProvider, type AuthProviderProps, type LoginMethods, type MfaChallengeResult, STORAGE_KEYS, useAuth, useSession };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
// src/provider.tsx
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
useContext,
|
|
5
|
+
useState,
|
|
6
|
+
useEffect,
|
|
7
|
+
useCallback,
|
|
8
|
+
useRef,
|
|
9
|
+
useMemo
|
|
10
|
+
} from "react";
|
|
11
|
+
|
|
12
|
+
// src/client.ts
|
|
13
|
+
import {
|
|
14
|
+
createAuthGateClient
|
|
15
|
+
} from "@auth-gate/core";
|
|
16
|
+
|
|
17
|
+
// src/storage.ts
|
|
18
|
+
import * as SecureStore from "expo-secure-store";
|
|
19
|
+
|
|
20
|
+
// src/types.ts
|
|
21
|
+
var STORAGE_KEYS = {
|
|
22
|
+
ACCESS_TOKEN: "authgate_access_token",
|
|
23
|
+
REFRESH_TOKEN: "authgate_refresh_token",
|
|
24
|
+
USER: "authgate_user"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/storage.ts
|
|
28
|
+
async function saveAccessToken(token) {
|
|
29
|
+
await SecureStore.setItemAsync(STORAGE_KEYS.ACCESS_TOKEN, token);
|
|
30
|
+
}
|
|
31
|
+
async function getAccessToken() {
|
|
32
|
+
return SecureStore.getItemAsync(STORAGE_KEYS.ACCESS_TOKEN);
|
|
33
|
+
}
|
|
34
|
+
async function saveRefreshToken(token) {
|
|
35
|
+
await SecureStore.setItemAsync(STORAGE_KEYS.REFRESH_TOKEN, token);
|
|
36
|
+
}
|
|
37
|
+
async function getRefreshToken() {
|
|
38
|
+
return SecureStore.getItemAsync(STORAGE_KEYS.REFRESH_TOKEN);
|
|
39
|
+
}
|
|
40
|
+
async function saveUser(user) {
|
|
41
|
+
await SecureStore.setItemAsync(STORAGE_KEYS.USER, JSON.stringify(user));
|
|
42
|
+
}
|
|
43
|
+
async function clearStorage() {
|
|
44
|
+
await Promise.all([
|
|
45
|
+
SecureStore.deleteItemAsync(STORAGE_KEYS.ACCESS_TOKEN),
|
|
46
|
+
SecureStore.deleteItemAsync(STORAGE_KEYS.REFRESH_TOKEN),
|
|
47
|
+
SecureStore.deleteItemAsync(STORAGE_KEYS.USER)
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/oauth.ts
|
|
52
|
+
import * as WebBrowser from "expo-web-browser";
|
|
53
|
+
async function openOAuthFlow(client, provider, scheme, callbackPath) {
|
|
54
|
+
const callbackUrl = `${scheme}://${callbackPath}`;
|
|
55
|
+
const authUrl = await client.getOAuthUrl(provider, callbackUrl);
|
|
56
|
+
const result = await WebBrowser.openAuthSessionAsync(authUrl, callbackUrl);
|
|
57
|
+
if (result.type !== "success" || !result.url) {
|
|
58
|
+
throw new Error(`OAuth flow cancelled or failed: ${result.type}`);
|
|
59
|
+
}
|
|
60
|
+
const url = new URL(result.url);
|
|
61
|
+
const token = url.searchParams.get("token");
|
|
62
|
+
const refreshToken = url.searchParams.get("refresh_token");
|
|
63
|
+
if (!token) {
|
|
64
|
+
const error = url.searchParams.get("error");
|
|
65
|
+
throw new Error(error || "No token received from OAuth callback");
|
|
66
|
+
}
|
|
67
|
+
return { token, refreshToken: refreshToken != null ? refreshToken : void 0 };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/client.ts
|
|
71
|
+
var AuthGateNativeClient = class {
|
|
72
|
+
constructor(config) {
|
|
73
|
+
this.refreshTimer = null;
|
|
74
|
+
this.onSessionChange = null;
|
|
75
|
+
var _a;
|
|
76
|
+
this.core = createAuthGateClient({
|
|
77
|
+
apiKey: config.apiKey,
|
|
78
|
+
baseUrl: config.baseUrl,
|
|
79
|
+
sessionMaxAge: config.sessionMaxAge
|
|
80
|
+
});
|
|
81
|
+
this.scheme = config.scheme;
|
|
82
|
+
this.callbackPath = (_a = config.callbackPath) != null ? _a : "auth/callback";
|
|
83
|
+
}
|
|
84
|
+
/** Register a callback to be notified when the session changes. */
|
|
85
|
+
setSessionChangeListener(listener) {
|
|
86
|
+
this.onSessionChange = listener;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Restore session from secure storage on app launch.
|
|
90
|
+
* Verifies the stored token is still valid.
|
|
91
|
+
*/
|
|
92
|
+
async restoreSession() {
|
|
93
|
+
const token = await getAccessToken();
|
|
94
|
+
if (!token) return null;
|
|
95
|
+
const result = await this.core.verifyToken(token);
|
|
96
|
+
if (result.valid && result.user) {
|
|
97
|
+
await saveUser(result.user);
|
|
98
|
+
this.scheduleRefresh(result.expiresAt);
|
|
99
|
+
return result.user;
|
|
100
|
+
}
|
|
101
|
+
return this.tryRefresh();
|
|
102
|
+
}
|
|
103
|
+
/** Start an OAuth flow in the system browser. */
|
|
104
|
+
async loginWithOAuth(provider) {
|
|
105
|
+
const result = await openOAuthFlow(
|
|
106
|
+
this.core,
|
|
107
|
+
provider,
|
|
108
|
+
this.scheme,
|
|
109
|
+
this.callbackPath
|
|
110
|
+
);
|
|
111
|
+
await this.handleAuthResult(result);
|
|
112
|
+
}
|
|
113
|
+
/** Sign in with email and password. May return MFA challenge. */
|
|
114
|
+
async loginWithEmail(email, password) {
|
|
115
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
116
|
+
const response = await this.core.emailSignin({
|
|
117
|
+
email,
|
|
118
|
+
password,
|
|
119
|
+
callbackUrl
|
|
120
|
+
});
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
if (data.mfa_required) {
|
|
123
|
+
return {
|
|
124
|
+
mfaRequired: true,
|
|
125
|
+
challenge: data.mfa_challenge,
|
|
126
|
+
methods: data.mfa_methods
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (data.token) {
|
|
130
|
+
await this.handleAuthResult({
|
|
131
|
+
token: data.token,
|
|
132
|
+
refreshToken: data.refresh_token
|
|
133
|
+
});
|
|
134
|
+
} else if (!response.ok) {
|
|
135
|
+
throw new Error(data.error || `Sign in failed: ${response.status}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Sign up with email and password. */
|
|
139
|
+
async signUp(email, password, name) {
|
|
140
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
141
|
+
const response = await this.core.emailSignup({
|
|
142
|
+
email,
|
|
143
|
+
password,
|
|
144
|
+
name,
|
|
145
|
+
callbackUrl
|
|
146
|
+
});
|
|
147
|
+
const data = await response.json();
|
|
148
|
+
if (data.token) {
|
|
149
|
+
await this.handleAuthResult({
|
|
150
|
+
token: data.token,
|
|
151
|
+
refreshToken: data.refresh_token
|
|
152
|
+
});
|
|
153
|
+
} else if (!response.ok) {
|
|
154
|
+
throw new Error(data.error || `Sign up failed: ${response.status}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Send a magic link email. */
|
|
158
|
+
async sendMagicLink(email) {
|
|
159
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
160
|
+
const response = await this.core.magicLinkSend({ email, callbackUrl });
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
const data = await response.json().catch(() => ({}));
|
|
163
|
+
throw new Error(data.error || `Magic link failed: ${response.status}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Send an SMS verification code. */
|
|
167
|
+
async sendSmsCode(phone) {
|
|
168
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
169
|
+
const response = await this.core.smsSendCode({ phone, callbackUrl });
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
const data = await response.json().catch(() => ({}));
|
|
172
|
+
throw new Error(data.error || `SMS send failed: ${response.status}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Verify an SMS code and complete authentication. */
|
|
176
|
+
async verifySmsCode(phone, code) {
|
|
177
|
+
const callbackUrl = `${this.scheme}://${this.callbackPath}`;
|
|
178
|
+
const response = await this.core.smsVerifyCode({
|
|
179
|
+
phone,
|
|
180
|
+
code,
|
|
181
|
+
callbackUrl
|
|
182
|
+
});
|
|
183
|
+
const data = await response.json();
|
|
184
|
+
if (data.token) {
|
|
185
|
+
await this.handleAuthResult({
|
|
186
|
+
token: data.token,
|
|
187
|
+
refreshToken: data.refresh_token
|
|
188
|
+
});
|
|
189
|
+
} else if (!response.ok) {
|
|
190
|
+
throw new Error(data.error || `SMS verify failed: ${response.status}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** Verify MFA code to complete authentication after email sign-in. */
|
|
194
|
+
async verifyMfa(challenge, code, method) {
|
|
195
|
+
const response = await this.core.mfaVerify({ challenge, code, method });
|
|
196
|
+
const data = await response.json();
|
|
197
|
+
if (data.token) {
|
|
198
|
+
await this.handleAuthResult({
|
|
199
|
+
token: data.token,
|
|
200
|
+
refreshToken: data.refresh_token
|
|
201
|
+
});
|
|
202
|
+
} else if (!response.ok) {
|
|
203
|
+
throw new Error(data.error || `MFA verify failed: ${response.status}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Sign out: revoke session and clear storage. */
|
|
207
|
+
async logout() {
|
|
208
|
+
var _a;
|
|
209
|
+
this.cancelRefresh();
|
|
210
|
+
const refreshToken = await getRefreshToken();
|
|
211
|
+
if (refreshToken) {
|
|
212
|
+
await this.core.revokeSession(refreshToken).catch(() => {
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
await clearStorage();
|
|
216
|
+
(_a = this.onSessionChange) == null ? void 0 : _a.call(this, null);
|
|
217
|
+
}
|
|
218
|
+
/** Manually refresh the session using the stored refresh token. */
|
|
219
|
+
async refresh() {
|
|
220
|
+
return this.tryRefresh();
|
|
221
|
+
}
|
|
222
|
+
// ── Internal ─────────────────────────────────────────────────────
|
|
223
|
+
async handleAuthResult(result) {
|
|
224
|
+
var _a;
|
|
225
|
+
const verified = await this.core.verifyToken(result.token);
|
|
226
|
+
if (!verified.valid || !verified.user) {
|
|
227
|
+
throw new Error(verified.error || "Token verification failed");
|
|
228
|
+
}
|
|
229
|
+
await saveAccessToken(result.token);
|
|
230
|
+
if (result.refreshToken) {
|
|
231
|
+
await saveRefreshToken(result.refreshToken);
|
|
232
|
+
}
|
|
233
|
+
await saveUser(verified.user);
|
|
234
|
+
this.scheduleRefresh(verified.expiresAt);
|
|
235
|
+
(_a = this.onSessionChange) == null ? void 0 : _a.call(this, verified.user);
|
|
236
|
+
}
|
|
237
|
+
async tryRefresh() {
|
|
238
|
+
var _a, _b, _c;
|
|
239
|
+
const refreshToken = await getRefreshToken();
|
|
240
|
+
if (!refreshToken) {
|
|
241
|
+
await clearStorage();
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const result = await this.core.refreshToken(refreshToken);
|
|
245
|
+
if (!result) {
|
|
246
|
+
await clearStorage();
|
|
247
|
+
(_a = this.onSessionChange) == null ? void 0 : _a.call(this, null);
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
await saveAccessToken(result.token);
|
|
251
|
+
const verified = await this.core.verifyToken(result.token);
|
|
252
|
+
if (verified.valid && verified.user) {
|
|
253
|
+
await saveUser(verified.user);
|
|
254
|
+
this.scheduleRefresh(verified.expiresAt);
|
|
255
|
+
(_b = this.onSessionChange) == null ? void 0 : _b.call(this, verified.user);
|
|
256
|
+
return verified.user;
|
|
257
|
+
}
|
|
258
|
+
await clearStorage();
|
|
259
|
+
(_c = this.onSessionChange) == null ? void 0 : _c.call(this, null);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
scheduleRefresh(expiresAt) {
|
|
263
|
+
this.cancelRefresh();
|
|
264
|
+
if (!expiresAt) return;
|
|
265
|
+
const expiresMs = new Date(expiresAt).getTime();
|
|
266
|
+
const refreshIn = Math.max(expiresMs - Date.now() - 6e4, 1e4);
|
|
267
|
+
this.refreshTimer = setTimeout(() => {
|
|
268
|
+
this.tryRefresh();
|
|
269
|
+
}, refreshIn);
|
|
270
|
+
}
|
|
271
|
+
cancelRefresh() {
|
|
272
|
+
if (this.refreshTimer) {
|
|
273
|
+
clearTimeout(this.refreshTimer);
|
|
274
|
+
this.refreshTimer = null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// src/provider.tsx
|
|
280
|
+
import { jsx } from "react/jsx-runtime";
|
|
281
|
+
var AuthContext = createContext(null);
|
|
282
|
+
function AuthProvider({ children, config }) {
|
|
283
|
+
const [user, setUser] = useState(null);
|
|
284
|
+
const [loading, setLoading] = useState(true);
|
|
285
|
+
const clientRef = useRef(null);
|
|
286
|
+
if (!clientRef.current) {
|
|
287
|
+
clientRef.current = new AuthGateNativeClient(config);
|
|
288
|
+
}
|
|
289
|
+
const client = clientRef.current;
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
client.setSessionChangeListener(setUser);
|
|
292
|
+
return () => client.setSessionChangeListener(() => {
|
|
293
|
+
});
|
|
294
|
+
}, [client]);
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
client.restoreSession().then((restored) => setUser(restored)).finally(() => setLoading(false));
|
|
297
|
+
}, [client]);
|
|
298
|
+
const refresh = useCallback(async () => {
|
|
299
|
+
const refreshed = await client.refresh();
|
|
300
|
+
setUser(refreshed);
|
|
301
|
+
}, [client]);
|
|
302
|
+
const logout = useCallback(async () => {
|
|
303
|
+
await client.logout();
|
|
304
|
+
setUser(null);
|
|
305
|
+
}, [client]);
|
|
306
|
+
const login = useMemo(
|
|
307
|
+
() => ({
|
|
308
|
+
withOAuth: (provider) => client.loginWithOAuth(provider),
|
|
309
|
+
withEmail: (email, password) => client.loginWithEmail(email, password),
|
|
310
|
+
withMagicLink: (email) => client.sendMagicLink(email),
|
|
311
|
+
signUp: (email, password, name) => client.signUp(email, password, name),
|
|
312
|
+
withSms: (phone) => client.sendSmsCode(phone),
|
|
313
|
+
verifySmsCode: (phone, code) => client.verifySmsCode(phone, code),
|
|
314
|
+
verifyMfa: (challenge, code, method) => client.verifyMfa(challenge, code, method)
|
|
315
|
+
}),
|
|
316
|
+
[client]
|
|
317
|
+
);
|
|
318
|
+
const value = useMemo(
|
|
319
|
+
() => ({ user, loading, login, logout, refresh }),
|
|
320
|
+
[user, loading, login, logout, refresh]
|
|
321
|
+
);
|
|
322
|
+
return /* @__PURE__ */ jsx(AuthContext, { value, children });
|
|
323
|
+
}
|
|
324
|
+
function useAuthContext() {
|
|
325
|
+
const context = useContext(AuthContext);
|
|
326
|
+
if (!context) {
|
|
327
|
+
throw new Error("useAuth must be used within an AuthProvider");
|
|
328
|
+
}
|
|
329
|
+
return context;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/hooks.ts
|
|
333
|
+
function useAuth() {
|
|
334
|
+
return useAuthContext();
|
|
335
|
+
}
|
|
336
|
+
function useSession() {
|
|
337
|
+
const { user } = useAuthContext();
|
|
338
|
+
return user;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/guard.tsx
|
|
342
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
343
|
+
function AuthGuard({ children, fallback, loading }) {
|
|
344
|
+
const { user, loading: isLoading } = useAuthContext();
|
|
345
|
+
if (isLoading) {
|
|
346
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: loading != null ? loading : null });
|
|
347
|
+
}
|
|
348
|
+
if (!user) {
|
|
349
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
|
|
350
|
+
}
|
|
351
|
+
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/index.ts
|
|
355
|
+
import {
|
|
356
|
+
AuthGateError,
|
|
357
|
+
TokenVerificationError,
|
|
358
|
+
ConfigurationError,
|
|
359
|
+
SUPPORTED_PROVIDERS
|
|
360
|
+
} from "@auth-gate/core";
|
|
361
|
+
export {
|
|
362
|
+
AuthGateError,
|
|
363
|
+
AuthGateNativeClient,
|
|
364
|
+
AuthGuard,
|
|
365
|
+
AuthProvider,
|
|
366
|
+
ConfigurationError,
|
|
367
|
+
STORAGE_KEYS,
|
|
368
|
+
SUPPORTED_PROVIDERS,
|
|
369
|
+
TokenVerificationError,
|
|
370
|
+
useAuth,
|
|
371
|
+
useSession
|
|
372
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auth-gate/react-native",
|
|
3
|
+
"version": "0.7.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.mjs",
|
|
9
|
+
"require": "./dist/index.cjs"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.mjs",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@auth-gate/core": "0.7.1"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": ">=18",
|
|
23
|
+
"react-native": ">=0.72",
|
|
24
|
+
"expo-secure-store": ">=13",
|
|
25
|
+
"expo-web-browser": ">=13",
|
|
26
|
+
"expo-linking": ">=6"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"react": "^19.0.0",
|
|
30
|
+
"react-native": "^0.76.0",
|
|
31
|
+
"@types/react": "^19",
|
|
32
|
+
"expo-secure-store": "^14.0.0",
|
|
33
|
+
"expo-web-browser": "^14.0.0",
|
|
34
|
+
"expo-linking": "^7.0.0",
|
|
35
|
+
"tsup": "^8.0.0",
|
|
36
|
+
"typescript": "^5"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"typecheck": "tsc --noEmit"
|
|
41
|
+
}
|
|
42
|
+
}
|