@netlify/identity 0.3.0-alpha.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -26
- package/dist/index.cjs +122 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -2
- package/dist/index.d.ts +36 -2
- package/dist/index.js +121 -4
- package/dist/index.js.map +1 -1
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @netlify/identity
|
|
2
2
|
|
|
3
|
-
A lightweight, no-config headless authentication library for projects using Netlify Identity
|
|
3
|
+
A lightweight, no-config headless authentication library for projects using Netlify Identity. Works in both browser and server contexts.
|
|
4
|
+
This is NOT the Netlify Identity Widget. This library exports standalone async functions (e.g., import { login, getUser } from '@netlify/identity'). There is no class to instantiate and no .init() call. Just import the functions you need and call them.
|
|
4
5
|
|
|
5
6
|
> **Status:** Beta. The API may change before 1.0.
|
|
6
7
|
|
|
@@ -17,14 +18,14 @@ A lightweight, no-config headless authentication library for projects using Netl
|
|
|
17
18
|
| [`netlify-identity-widget`](https://github.com/netlify/netlify-identity-widget) | Pre-built login/signup modal (HTML + CSS) | You want a drop-in UI component with no custom design |
|
|
18
19
|
| [`gotrue-js`](https://github.com/netlify/gotrue-js) | Low-level GoTrue HTTP client (browser only) | You're building your own auth wrapper and need direct API access |
|
|
19
20
|
|
|
20
|
-
This library
|
|
21
|
+
This library provides a unified API that works in both browser and server contexts, handles cookie management, and normalizes the user object. You do not need to install `gotrue-js` or the widget separately.
|
|
21
22
|
|
|
22
23
|
## Table of contents
|
|
23
24
|
|
|
24
25
|
- [Installation](#installation)
|
|
25
26
|
- [Quick start](#quick-start)
|
|
26
27
|
- [API](#api)
|
|
27
|
-
- [Functions](#functions) -- `getUser`, `login`, `signup`, `logout`, `oauthLogin`, `handleAuthCallback`, `onAuthChange`, `hydrateSession`, and more
|
|
28
|
+
- [Functions](#functions) -- `getUser`, `login`, `signup`, `logout`, `oauthLogin`, `handleAuthCallback`, `onAuthChange`, `hydrateSession`, `refreshSession`, and more
|
|
28
29
|
- [Admin Operations](#admin-operations) -- `admin.listUsers`, `admin.getUser`, `admin.createUser`, `admin.updateUser`, `admin.deleteUser`
|
|
29
30
|
- [Types](#types) -- `User`, `AuthEvent`, `CallbackResult`, `Settings`, `Admin`, `ListUsersOptions`, `CreateUserParams`, etc.
|
|
30
31
|
- [Errors](#errors) -- `AuthError`, `MissingIdentityError`
|
|
@@ -94,7 +95,7 @@ export default async (req: Request, context: Context) => {
|
|
|
94
95
|
getUser(): Promise<User | null>
|
|
95
96
|
```
|
|
96
97
|
|
|
97
|
-
Returns the current authenticated user, or `null` if not logged in.
|
|
98
|
+
Returns the current authenticated user, or `null` if not logged in. Returns the best available normalized `User` from the current context. In the browser or when the server can reach the Identity API, all fields are populated. When falling back to JWT claims (e.g., Identity API unreachable), fields like `createdAt`, `updatedAt`, `emailVerified`, and `rawGoTrueData` may be missing. Never throws.
|
|
98
99
|
|
|
99
100
|
> **Next.js note:** Calling `getUser()` in a Server Component opts the page into [dynamic rendering](https://nextjs.org/docs/app/building-your-application/rendering/server-components#dynamic-rendering) because it reads cookies. This is expected and correct for authenticated pages. Next.js handles the internal dynamic rendering signal automatically.
|
|
100
101
|
|
|
@@ -132,7 +133,7 @@ login(email: string, password: string): Promise<User>
|
|
|
132
133
|
|
|
133
134
|
Logs in with email and password. Works in both browser and server contexts.
|
|
134
135
|
|
|
135
|
-
In the browser,
|
|
136
|
+
In the browser, emits a `'login'` event. On the server (Netlify Functions, Edge Functions), calls the Identity API directly and sets the `nf_jwt` cookie via the Netlify runtime.
|
|
136
137
|
|
|
137
138
|
**Throws:** `AuthError` on invalid credentials or network failure. In the browser, `MissingIdentityError` if Identity is not configured. On the server, `AuthError` if the Netlify Functions runtime is not available.
|
|
138
139
|
|
|
@@ -158,7 +159,7 @@ logout(): Promise<void>
|
|
|
158
159
|
|
|
159
160
|
Logs out the current user and clears the session. Works in both browser and server contexts.
|
|
160
161
|
|
|
161
|
-
In the browser,
|
|
162
|
+
In the browser, emits a `'logout'` event. On the server, calls the Identity `/logout` endpoint with the JWT from the `nf_jwt` cookie, then deletes the cookie. Auth cookies are always cleared, even if the server call fails.
|
|
162
163
|
|
|
163
164
|
**Throws:** In the browser, `MissingIdentityError` if Identity is not configured. On the server, `AuthError` if the Netlify Functions runtime is not available.
|
|
164
165
|
|
|
@@ -198,11 +199,11 @@ Subscribes to auth state changes (login, logout, token refresh, user updates, an
|
|
|
198
199
|
hydrateSession(): Promise<User | null>
|
|
199
200
|
```
|
|
200
201
|
|
|
201
|
-
Bootstraps the browser-side
|
|
202
|
+
Bootstraps the browser-side session from server-set auth cookies (`nf_jwt`, `nf_refresh`). Returns the hydrated `User`, or `null` if no auth cookies are present. No-op on the server.
|
|
202
203
|
|
|
203
|
-
**When to use:** After a server-side login (e.g., via a Netlify Function or Server Action), the `nf_jwt` cookie is set but
|
|
204
|
+
**When to use:** After a server-side login (e.g., via a Netlify Function or Server Action), the `nf_jwt` cookie is set but no browser session exists yet. `getUser()` calls `hydrateSession()` automatically, but account operations like `updateUser()` or `verifyEmailChange()` require a live browser session. Call `hydrateSession()` explicitly if you need the session ready before calling those operations.
|
|
204
205
|
|
|
205
|
-
If a
|
|
206
|
+
If a browser session already exists (e.g., from a browser-side login), this is a no-op and returns the existing user.
|
|
206
207
|
|
|
207
208
|
```ts
|
|
208
209
|
import { hydrateSession, updateUser } from '@netlify/identity'
|
|
@@ -214,6 +215,30 @@ await hydrateSession()
|
|
|
214
215
|
await updateUser({ data: { full_name: 'Jane Doe' } })
|
|
215
216
|
```
|
|
216
217
|
|
|
218
|
+
#### `refreshSession`
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
refreshSession(): Promise<string | null>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Refreshes an expired or near-expired session. Returns the new access token on success, or `null` if no refresh is needed or the refresh token is invalid/missing.
|
|
225
|
+
|
|
226
|
+
**Browser:** Checks if the current access token is near expiry and refreshes it if needed, syncing the new token to the `nf_jwt` cookie. Note: the library automatically refreshes tokens in the background after any browser flow that establishes a session (`login()`, `signup()`, `hydrateSession()`, `handleAuthCallback()`, `confirmEmail()`, `recoverPassword()`, `acceptInvite()`), so you typically don't need to call this manually. `getUser()` also restarts the refresh timer when it finds an existing session. Browser-side errors return `null`, not an `AuthError`.
|
|
227
|
+
|
|
228
|
+
**Server:** Reads the `nf_jwt` and `nf_refresh` cookies. If the access token is expired or within 60 seconds of expiry, exchanges the refresh token for a new access token via the Identity `/token` endpoint and updates both cookies on the response. Call this in framework middleware or at the start of server-side request handlers to ensure the JWT is valid for downstream processing.
|
|
229
|
+
|
|
230
|
+
**Throws:** `AuthError` on network failure or if the Identity endpoint URL cannot be determined. Does **not** throw for invalid/expired refresh tokens (returns `null` instead).
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// Example: Astro middleware
|
|
234
|
+
import { refreshSession } from '@netlify/identity'
|
|
235
|
+
|
|
236
|
+
export async function onRequest(context, next) {
|
|
237
|
+
await refreshSession()
|
|
238
|
+
return next()
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
217
242
|
#### `requestPasswordRecovery`
|
|
218
243
|
|
|
219
244
|
```ts
|
|
@@ -279,7 +304,7 @@ Updates the current user's metadata or credentials. Requires an active session.
|
|
|
279
304
|
The `admin` namespace provides user management functions for administrators. These work in two contexts:
|
|
280
305
|
|
|
281
306
|
- **Server:** Uses the operator token from the Netlify runtime for full admin access. No logged-in user required.
|
|
282
|
-
- **Browser:** Uses the logged-in user's JWT
|
|
307
|
+
- **Browser:** Uses the logged-in user's JWT. The user must have an admin role.
|
|
283
308
|
|
|
284
309
|
```ts
|
|
285
310
|
import { admin } from '@netlify/identity'
|
|
@@ -315,7 +340,7 @@ export default async (req: Request, context: Context) => {
|
|
|
315
340
|
admin.listUsers(options?: ListUsersOptions): Promise<User[]>
|
|
316
341
|
```
|
|
317
342
|
|
|
318
|
-
Lists all users. Pagination options are supported on the server; they are ignored in the browser
|
|
343
|
+
Lists all users. Pagination options are supported on the server; they are ignored in the browser.
|
|
319
344
|
|
|
320
345
|
**Throws:** `AuthError` if the operator token is missing (server) or no user is logged in (browser).
|
|
321
346
|
|
|
@@ -345,7 +370,7 @@ Creates a new user. The user is auto-confirmed. Optional `data` is spread into t
|
|
|
345
370
|
admin.updateUser(userId: string, attributes: AdminUserUpdates): Promise<User>
|
|
346
371
|
```
|
|
347
372
|
|
|
348
|
-
Updates an existing user by ID. Pass any attributes to change (e.g., `{ email: 'new@example.com' }`). See
|
|
373
|
+
Updates an existing user by ID. Pass any attributes to change (e.g., `{ email: 'new@example.com' }`). See `AdminUserUpdates` for typed fields.
|
|
349
374
|
|
|
350
375
|
**Throws:** `AuthError` if the user is not found or the update fails.
|
|
351
376
|
|
|
@@ -460,7 +485,7 @@ interface ListUsersOptions {
|
|
|
460
485
|
}
|
|
461
486
|
```
|
|
462
487
|
|
|
463
|
-
Pagination options for `admin.listUsers()`. Only used on the server; pagination is ignored in the browser
|
|
488
|
+
Pagination options for `admin.listUsers()`. Only used on the server; pagination is ignored in the browser.
|
|
464
489
|
|
|
465
490
|
#### `CreateUserParams`
|
|
466
491
|
|
|
@@ -472,7 +497,7 @@ interface CreateUserParams {
|
|
|
472
497
|
}
|
|
473
498
|
```
|
|
474
499
|
|
|
475
|
-
Parameters for `admin.createUser()`. Optional `data` is spread into the
|
|
500
|
+
Parameters for `admin.createUser()`. Optional `data` is spread into the request body as top-level attributes (use it to set `app_metadata`, `user_metadata`, `role`, etc.).
|
|
476
501
|
|
|
477
502
|
#### `Admin`
|
|
478
503
|
|
|
@@ -502,13 +527,13 @@ const AUTH_EVENTS: {
|
|
|
502
527
|
|
|
503
528
|
Constants for auth event names. Use these instead of string literals for type safety and autocomplete.
|
|
504
529
|
|
|
505
|
-
| Event | When it fires
|
|
506
|
-
| --------------- |
|
|
507
|
-
| `LOGIN` | `login()`, `signup()` (with autoconfirm), `recoverPassword()`, `handleAuthCallback()` (OAuth/confirmation), `hydrateSession()`
|
|
508
|
-
| `LOGOUT` | `logout()`
|
|
509
|
-
| `TOKEN_REFRESH` |
|
|
510
|
-
| `USER_UPDATED` | `updateUser()`, `verifyEmailChange()`, `handleAuthCallback()` (email change)
|
|
511
|
-
| `RECOVERY` | `handleAuthCallback()` (recovery token only). The user is authenticated but has **not** set a new password yet. Listen for this to redirect to a password reset form. `recoverPassword()` emits `LOGIN` instead because it completes both steps (token redemption + password change).
|
|
530
|
+
| Event | When it fires |
|
|
531
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
532
|
+
| `LOGIN` | `login()`, `signup()` (with autoconfirm), `recoverPassword()`, `confirmEmail()`, `acceptInvite()`, `handleAuthCallback()` (OAuth/confirmation), `hydrateSession()` |
|
|
533
|
+
| `LOGOUT` | `logout()` |
|
|
534
|
+
| `TOKEN_REFRESH` | The library's auto-refresh timer refreshes an expiring access token and syncs the new token to the `nf_jwt` cookie. Fires automatically after any session-establishing flow: `login()`, `signup()`, `hydrateSession()`, `handleAuthCallback()`, `confirmEmail()`, `recoverPassword()`, `acceptInvite()`. `getUser()` also restarts the timer when it finds an existing session. |
|
|
535
|
+
| `USER_UPDATED` | `updateUser()`, `verifyEmailChange()`, `handleAuthCallback()` (email change) |
|
|
536
|
+
| `RECOVERY` | `handleAuthCallback()` (recovery token only). The user is authenticated but has **not** set a new password yet. Listen for this to redirect to a password reset form. `recoverPassword()` emits `LOGIN` instead because it completes both steps (token redemption + password change). |
|
|
512
537
|
|
|
513
538
|
#### `AuthEvent`
|
|
514
539
|
|
|
@@ -564,7 +589,7 @@ For SSR frameworks (Next.js, Remix, Astro, TanStack Start), the recommended patt
|
|
|
564
589
|
- **Browser-side** for auth mutations: `login()`, `signup()`, `logout()`, `oauthLogin()`
|
|
565
590
|
- **Server-side** for reading auth state: `getUser()`, `getSettings()`, `getIdentityConfig()`
|
|
566
591
|
|
|
567
|
-
Browser-side auth mutations call the
|
|
592
|
+
Browser-side auth mutations call the Identity API directly from the browser, set the `nf_jwt` cookie, and emit `onAuthChange` events. This keeps the client UI in sync immediately. Server-side reads work because the cookie is sent with every request.
|
|
568
593
|
|
|
569
594
|
The library also supports server-side mutations (`login()`, `signup()`, `logout()` inside Netlify Functions), but these require the Netlify Functions runtime to set cookies. After a server-side mutation, you need a full page navigation so the browser sends the new cookie.
|
|
570
595
|
|
|
@@ -1010,14 +1035,16 @@ if (result?.type === 'invite' && result.token) {
|
|
|
1010
1035
|
|
|
1011
1036
|
### Session lifetime
|
|
1012
1037
|
|
|
1013
|
-
Sessions are managed by Netlify Identity
|
|
1038
|
+
Sessions are managed by Netlify Identity on the server side. The library stores two cookies:
|
|
1014
1039
|
|
|
1015
|
-
- **`nf_jwt`**: A short-lived JWT access token (default: 1 hour).
|
|
1040
|
+
- **`nf_jwt`**: A short-lived JWT access token (default: 1 hour).
|
|
1016
1041
|
- **`nf_refresh`**: A long-lived refresh token used to obtain new access tokens without re-authenticating.
|
|
1017
1042
|
|
|
1018
|
-
|
|
1043
|
+
**Browser auto-refresh:** After any session-establishing flow (`login()`, `signup()`, `hydrateSession()`, `handleAuthCallback()`, `confirmEmail()`, `recoverPassword()`, `acceptInvite()`), the library automatically schedules a background refresh 60 seconds before the access token expires. `getUser()` also restarts the refresh timer when it finds an existing session (e.g., after a page reload). When the refresh fires, it obtains a new access token, syncs it to the `nf_jwt` cookie, and emits a `TOKEN_REFRESH` event. This keeps the cookie fresh as long as the user has the tab open. If the refresh fails (e.g., the refresh token was revoked), the timer stops and the user will need to log in again.
|
|
1044
|
+
|
|
1045
|
+
**Server-side refresh:** On the server, the access token in the `nf_jwt` cookie is validated as-is. If it has expired and no refresh happens, `getUser()` returns `null`. To handle this, call `refreshSession()` in your framework middleware or request handler. This checks if the token is near expiry, exchanges the refresh token for a new one, and updates the cookies on the response.
|
|
1019
1046
|
|
|
1020
|
-
Session lifetime is configured in your
|
|
1047
|
+
Session lifetime is configured in your Netlify Identity settings, not in this library.
|
|
1021
1048
|
|
|
1022
1049
|
## License
|
|
1023
1050
|
|
package/dist/index.cjs
CHANGED
|
@@ -47,6 +47,7 @@ __export(index_exports, {
|
|
|
47
47
|
oauthLogin: () => oauthLogin,
|
|
48
48
|
onAuthChange: () => onAuthChange,
|
|
49
49
|
recoverPassword: () => recoverPassword,
|
|
50
|
+
refreshSession: () => refreshSession,
|
|
50
51
|
requestPasswordRecovery: () => requestPasswordRecovery,
|
|
51
52
|
signup: () => signup,
|
|
52
53
|
updateUser: () => updateUser,
|
|
@@ -71,6 +72,7 @@ var AuthError = class _AuthError extends Error {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
static from(error) {
|
|
75
|
+
if (error instanceof _AuthError) return error;
|
|
74
76
|
const message = error instanceof Error ? error.message : String(error);
|
|
75
77
|
return new _AuthError(message, void 0, { cause: error });
|
|
76
78
|
}
|
|
@@ -146,6 +148,7 @@ var getIdentityContext = () => {
|
|
|
146
148
|
var NF_JWT_COOKIE = "nf_jwt";
|
|
147
149
|
var NF_REFRESH_COOKIE = "nf_refresh";
|
|
148
150
|
var getCookie = (name) => {
|
|
151
|
+
if (typeof document === "undefined") return null;
|
|
149
152
|
const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
|
|
150
153
|
if (!match) return null;
|
|
151
154
|
try {
|
|
@@ -179,12 +182,14 @@ var deleteAuthCookies = (cookies) => {
|
|
|
179
182
|
cookies.delete(NF_REFRESH_COOKIE);
|
|
180
183
|
};
|
|
181
184
|
var setBrowserAuthCookies = (accessToken, refreshToken) => {
|
|
185
|
+
if (typeof document === "undefined") return;
|
|
182
186
|
document.cookie = `${NF_JWT_COOKIE}=${encodeURIComponent(accessToken)}; path=/; secure; samesite=lax`;
|
|
183
187
|
if (refreshToken) {
|
|
184
188
|
document.cookie = `${NF_REFRESH_COOKIE}=${encodeURIComponent(refreshToken)}; path=/; secure; samesite=lax`;
|
|
185
189
|
}
|
|
186
190
|
};
|
|
187
191
|
var deleteBrowserAuthCookies = () => {
|
|
192
|
+
if (typeof document === "undefined") return;
|
|
188
193
|
document.cookie = `${NF_JWT_COOKIE}=; path=/; secure; samesite=lax; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
189
194
|
document.cookie = `${NF_REFRESH_COOKIE}=; path=/; secure; samesite=lax; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
|
190
195
|
};
|
|
@@ -267,6 +272,105 @@ var onAuthChange = (callback) => {
|
|
|
267
272
|
};
|
|
268
273
|
};
|
|
269
274
|
|
|
275
|
+
// src/refresh.ts
|
|
276
|
+
var REFRESH_MARGIN_S = 60;
|
|
277
|
+
var refreshTimer = null;
|
|
278
|
+
var startTokenRefresh = () => {
|
|
279
|
+
if (!isBrowser()) return;
|
|
280
|
+
stopTokenRefresh();
|
|
281
|
+
const client = getGoTrueClient();
|
|
282
|
+
const user = client?.currentUser();
|
|
283
|
+
if (!user) return;
|
|
284
|
+
const token = user.tokenDetails();
|
|
285
|
+
if (!token?.expires_at) return;
|
|
286
|
+
const nowS = Math.floor(Date.now() / 1e3);
|
|
287
|
+
const expiresAtS = typeof token.expires_at === "number" && token.expires_at > 1e12 ? Math.floor(token.expires_at / 1e3) : token.expires_at;
|
|
288
|
+
const delayMs = Math.max(0, expiresAtS - nowS - REFRESH_MARGIN_S) * 1e3;
|
|
289
|
+
refreshTimer = setTimeout(async () => {
|
|
290
|
+
try {
|
|
291
|
+
const freshJwt = await user.jwt(true);
|
|
292
|
+
const freshDetails = user.tokenDetails();
|
|
293
|
+
setBrowserAuthCookies(freshJwt, freshDetails?.refresh_token);
|
|
294
|
+
emitAuthEvent(AUTH_EVENTS.TOKEN_REFRESH, toUser(user));
|
|
295
|
+
startTokenRefresh();
|
|
296
|
+
} catch {
|
|
297
|
+
stopTokenRefresh();
|
|
298
|
+
}
|
|
299
|
+
}, delayMs);
|
|
300
|
+
};
|
|
301
|
+
var stopTokenRefresh = () => {
|
|
302
|
+
if (refreshTimer !== null) {
|
|
303
|
+
clearTimeout(refreshTimer);
|
|
304
|
+
refreshTimer = null;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
var refreshSession = async () => {
|
|
308
|
+
if (isBrowser()) {
|
|
309
|
+
const client = getGoTrueClient();
|
|
310
|
+
const user = client?.currentUser();
|
|
311
|
+
if (!user) return null;
|
|
312
|
+
const details = user.tokenDetails();
|
|
313
|
+
if (details?.expires_at) {
|
|
314
|
+
const nowS2 = Math.floor(Date.now() / 1e3);
|
|
315
|
+
const expiresAtS = typeof details.expires_at === "number" && details.expires_at > 1e12 ? Math.floor(details.expires_at / 1e3) : details.expires_at;
|
|
316
|
+
if (expiresAtS - nowS2 > REFRESH_MARGIN_S) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const jwt = await user.jwt(true);
|
|
322
|
+
setBrowserAuthCookies(jwt, user.tokenDetails()?.refresh_token);
|
|
323
|
+
emitAuthEvent(AUTH_EVENTS.TOKEN_REFRESH, toUser(user));
|
|
324
|
+
startTokenRefresh();
|
|
325
|
+
return jwt;
|
|
326
|
+
} catch {
|
|
327
|
+
stopTokenRefresh();
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const accessToken = getServerCookie(NF_JWT_COOKIE);
|
|
332
|
+
const refreshToken = getServerCookie(NF_REFRESH_COOKIE);
|
|
333
|
+
if (!accessToken || !refreshToken) return null;
|
|
334
|
+
const decoded = decodeJwtPayload(accessToken);
|
|
335
|
+
if (!decoded?.exp) return null;
|
|
336
|
+
const nowS = Math.floor(Date.now() / 1e3);
|
|
337
|
+
if (decoded.exp - nowS > REFRESH_MARGIN_S) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const ctx = getIdentityContext();
|
|
341
|
+
const identityUrl = ctx?.url ?? (globalThis.Netlify?.context?.url ? new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href : null);
|
|
342
|
+
if (!identityUrl) {
|
|
343
|
+
throw new AuthError("Could not determine the Identity endpoint URL for token refresh");
|
|
344
|
+
}
|
|
345
|
+
let res;
|
|
346
|
+
try {
|
|
347
|
+
res = await fetch(`${identityUrl}/token`, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
350
|
+
body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken }).toString()
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
throw AuthError.from(error);
|
|
354
|
+
}
|
|
355
|
+
if (!res.ok) {
|
|
356
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
357
|
+
if (res.status === 401 || res.status === 400) {
|
|
358
|
+
const cookies2 = globalThis.Netlify?.context?.cookies;
|
|
359
|
+
if (cookies2) {
|
|
360
|
+
deleteAuthCookies(cookies2);
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
throw new AuthError(errorBody.msg || `Token refresh failed (${res.status})`, res.status);
|
|
365
|
+
}
|
|
366
|
+
const data = await res.json();
|
|
367
|
+
const cookies = globalThis.Netlify?.context?.cookies;
|
|
368
|
+
if (cookies) {
|
|
369
|
+
setAuthCookies(cookies, data.access_token, data.refresh_token);
|
|
370
|
+
}
|
|
371
|
+
return data.access_token;
|
|
372
|
+
};
|
|
373
|
+
|
|
270
374
|
// src/auth.ts
|
|
271
375
|
var getCookies = () => {
|
|
272
376
|
const cookies = globalThis.Netlify?.context?.cookies;
|
|
@@ -331,6 +435,7 @@ var login = async (email, password) => {
|
|
|
331
435
|
const jwt = await gotrueUser.jwt();
|
|
332
436
|
setBrowserAuthCookies(jwt, gotrueUser.tokenDetails()?.refresh_token);
|
|
333
437
|
const user = toUser(gotrueUser);
|
|
438
|
+
startTokenRefresh();
|
|
334
439
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
335
440
|
return user;
|
|
336
441
|
} catch (error) {
|
|
@@ -375,6 +480,7 @@ var signup = async (email, password, data) => {
|
|
|
375
480
|
const refreshToken = response.tokenDetails?.()?.refresh_token;
|
|
376
481
|
setBrowserAuthCookies(jwt, refreshToken);
|
|
377
482
|
}
|
|
483
|
+
startTokenRefresh();
|
|
378
484
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
379
485
|
}
|
|
380
486
|
return user;
|
|
@@ -406,6 +512,7 @@ var logout = async () => {
|
|
|
406
512
|
await currentUser.logout();
|
|
407
513
|
}
|
|
408
514
|
deleteBrowserAuthCookies();
|
|
515
|
+
stopTokenRefresh();
|
|
409
516
|
emitAuthEvent(AUTH_EVENTS.LOGOUT, null);
|
|
410
517
|
} catch (error) {
|
|
411
518
|
throw AuthError.from(error);
|
|
@@ -458,6 +565,7 @@ var handleOAuthCallback = async (client, params, accessToken) => {
|
|
|
458
565
|
);
|
|
459
566
|
setBrowserAuthCookies(accessToken, refreshToken || void 0);
|
|
460
567
|
const user = toUser(gotrueUser);
|
|
568
|
+
startTokenRefresh();
|
|
461
569
|
clearHash();
|
|
462
570
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
463
571
|
return { type: "oauth", user };
|
|
@@ -467,6 +575,7 @@ var handleConfirmationCallback = async (client, token) => {
|
|
|
467
575
|
const jwt = await gotrueUser.jwt();
|
|
468
576
|
setBrowserAuthCookies(jwt, gotrueUser.tokenDetails()?.refresh_token);
|
|
469
577
|
const user = toUser(gotrueUser);
|
|
578
|
+
startTokenRefresh();
|
|
470
579
|
clearHash();
|
|
471
580
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
472
581
|
return { type: "confirmation", user };
|
|
@@ -476,6 +585,7 @@ var handleRecoveryCallback = async (client, token) => {
|
|
|
476
585
|
const jwt = await gotrueUser.jwt();
|
|
477
586
|
setBrowserAuthCookies(jwt, gotrueUser.tokenDetails()?.refresh_token);
|
|
478
587
|
const user = toUser(gotrueUser);
|
|
588
|
+
startTokenRefresh();
|
|
479
589
|
clearHash();
|
|
480
590
|
emitAuthEvent(AUTH_EVENTS.RECOVERY, user);
|
|
481
591
|
return { type: "recovery", user };
|
|
@@ -519,7 +629,10 @@ var hydrateSession = async () => {
|
|
|
519
629
|
if (!isBrowser()) return null;
|
|
520
630
|
const client = getClient();
|
|
521
631
|
const currentUser = client.currentUser();
|
|
522
|
-
if (currentUser)
|
|
632
|
+
if (currentUser) {
|
|
633
|
+
startTokenRefresh();
|
|
634
|
+
return toUser(currentUser);
|
|
635
|
+
}
|
|
523
636
|
const accessToken = getCookie(NF_JWT_COOKIE);
|
|
524
637
|
if (!accessToken) return null;
|
|
525
638
|
const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
|
|
@@ -543,6 +656,7 @@ var hydrateSession = async () => {
|
|
|
543
656
|
return null;
|
|
544
657
|
}
|
|
545
658
|
const user = toUser(gotrueUser);
|
|
659
|
+
startTokenRefresh();
|
|
546
660
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
547
661
|
return user;
|
|
548
662
|
};
|
|
@@ -637,6 +751,7 @@ var getUser = async () => {
|
|
|
637
751
|
}
|
|
638
752
|
return null;
|
|
639
753
|
}
|
|
754
|
+
startTokenRefresh();
|
|
640
755
|
return toUser(currentUser);
|
|
641
756
|
}
|
|
642
757
|
const jwt = getCookie(NF_JWT_COOKIE);
|
|
@@ -644,8 +759,7 @@ var getUser = async () => {
|
|
|
644
759
|
const claims2 = decodeJwtPayload(jwt);
|
|
645
760
|
if (!claims2) return null;
|
|
646
761
|
const hydrated = await hydrateSession();
|
|
647
|
-
|
|
648
|
-
return claimsToUser(claims2);
|
|
762
|
+
return hydrated ?? null;
|
|
649
763
|
}
|
|
650
764
|
triggerNextjsDynamic();
|
|
651
765
|
const identityContext = globalThis.netlifyIdentityContext;
|
|
@@ -657,7 +771,7 @@ var getUser = async () => {
|
|
|
657
771
|
if (fullUser) return fullUser;
|
|
658
772
|
}
|
|
659
773
|
}
|
|
660
|
-
const claims = identityContext?.user ??
|
|
774
|
+
const claims = identityContext?.user ?? null;
|
|
661
775
|
return claims ? claimsToUser(claims) : null;
|
|
662
776
|
};
|
|
663
777
|
var isAuthenticated = async () => await getUser() !== null;
|
|
@@ -720,6 +834,7 @@ var recoverPassword = async (token, newPassword) => {
|
|
|
720
834
|
const gotrueUser = await client.recover(token, persistSession);
|
|
721
835
|
const updatedUser = await gotrueUser.update({ password: newPassword });
|
|
722
836
|
const user = toUser(updatedUser);
|
|
837
|
+
startTokenRefresh();
|
|
723
838
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
724
839
|
return user;
|
|
725
840
|
} catch (error) {
|
|
@@ -731,6 +846,7 @@ var confirmEmail = async (token) => {
|
|
|
731
846
|
try {
|
|
732
847
|
const gotrueUser = await client.confirm(token, persistSession);
|
|
733
848
|
const user = toUser(gotrueUser);
|
|
849
|
+
startTokenRefresh();
|
|
734
850
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
735
851
|
return user;
|
|
736
852
|
} catch (error) {
|
|
@@ -742,6 +858,7 @@ var acceptInvite = async (token, password) => {
|
|
|
742
858
|
try {
|
|
743
859
|
const gotrueUser = await client.acceptInvite(token, password, persistSession);
|
|
744
860
|
const user = toUser(gotrueUser);
|
|
861
|
+
startTokenRefresh();
|
|
745
862
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
746
863
|
return user;
|
|
747
864
|
} catch (error) {
|
|
@@ -939,6 +1056,7 @@ var admin = { listUsers, getUser: getUser2, createUser, updateUser: updateUser2,
|
|
|
939
1056
|
oauthLogin,
|
|
940
1057
|
onAuthChange,
|
|
941
1058
|
recoverPassword,
|
|
1059
|
+
refreshSession,
|
|
942
1060
|
requestPasswordRecovery,
|
|
943
1061
|
signup,
|
|
944
1062
|
updateUser,
|