@netlify/identity 0.1.1-alpha.20 → 0.1.1-alpha.22
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 +188 -32
- package/dist/index.cjs +27 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -16
- package/dist/index.d.ts +111 -16
- package/dist/index.js +26 -11
- package/dist/index.js.map +1 -1
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -19,18 +19,20 @@ npm install @netlify/identity
|
|
|
19
19
|
|
|
20
20
|
## Quick start
|
|
21
21
|
|
|
22
|
-
###
|
|
22
|
+
### Log in (browser)
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
|
-
import { getUser } from '@netlify/identity'
|
|
25
|
+
import { login, getUser } from '@netlify/identity'
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
// Log in
|
|
28
|
+
const user = await login('jane@example.com', 'password123')
|
|
29
|
+
console.log(`Hello, ${user.name}`)
|
|
30
|
+
|
|
31
|
+
// Later, check auth state synchronously
|
|
32
|
+
const currentUser = getUser()
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
### Netlify Function
|
|
35
|
+
### Protect a Netlify Function
|
|
34
36
|
|
|
35
37
|
```ts
|
|
36
38
|
import { getUser } from '@netlify/identity'
|
|
@@ -43,7 +45,7 @@ export default async (req: Request, context: Context) => {
|
|
|
43
45
|
}
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
### Edge Function
|
|
48
|
+
### Protect an Edge Function
|
|
47
49
|
|
|
48
50
|
```ts
|
|
49
51
|
import { getUser } from '@netlify/identity'
|
|
@@ -66,7 +68,9 @@ export default async (req: Request, context: Context) => {
|
|
|
66
68
|
getUser(): User | null
|
|
67
69
|
```
|
|
68
70
|
|
|
69
|
-
Returns the current authenticated user, or `null` if not logged in. Synchronous
|
|
71
|
+
Returns the current authenticated user, or `null` if not logged in. Synchronous. Never throws.
|
|
72
|
+
|
|
73
|
+
> **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.
|
|
70
74
|
|
|
71
75
|
#### `isAuthenticated`
|
|
72
76
|
|
|
@@ -109,12 +113,14 @@ In the browser, uses gotrue-js and emits a `'login'` event. On the server (Netli
|
|
|
109
113
|
#### `signup`
|
|
110
114
|
|
|
111
115
|
```ts
|
|
112
|
-
signup(email: string, password: string, data?:
|
|
116
|
+
signup(email: string, password: string, data?: SignupData): Promise<User>
|
|
113
117
|
```
|
|
114
118
|
|
|
115
119
|
Creates a new account. Works in both browser and server contexts.
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
If autoconfirm is enabled in your Identity settings, the user is logged in immediately: cookies are set and a `'login'` event is emitted. If autoconfirm is **disabled** (the default), the user receives a confirmation email and must click the link before they can log in. In that case, no cookies are set and no auth event is emitted.
|
|
122
|
+
|
|
123
|
+
The optional `data` parameter sets user metadata (e.g., `{ full_name: 'Jane Doe' }`), stored in the user's `user_metadata` field.
|
|
118
124
|
|
|
119
125
|
**Throws:** `AuthError` on failure (e.g., email already registered, signup disabled). In the browser, `MissingIdentityError` if Identity is not configured. On the server, `AuthError` if the Netlify Functions runtime is not available.
|
|
120
126
|
|
|
@@ -126,9 +132,9 @@ logout(): Promise<void>
|
|
|
126
132
|
|
|
127
133
|
Logs out the current user and clears the session. Works in both browser and server contexts.
|
|
128
134
|
|
|
129
|
-
In the browser, uses gotrue-js and emits a `'logout'` event. On the server, calls GoTrue's `/logout` endpoint with the JWT from the `nf_jwt` cookie, then deletes the cookie.
|
|
135
|
+
In the browser, uses gotrue-js and emits a `'logout'` event. On the server, calls GoTrue's `/logout` endpoint with the JWT from the `nf_jwt` cookie, then deletes the cookie. Auth cookies are always cleared, even if the GoTrue call fails.
|
|
130
136
|
|
|
131
|
-
**Throws:**
|
|
137
|
+
**Throws:** In the browser, `MissingIdentityError` if Identity is not configured. On the server, `AuthError` if the Netlify Functions runtime is not available.
|
|
132
138
|
|
|
133
139
|
#### `oauthLogin`
|
|
134
140
|
|
|
@@ -140,7 +146,7 @@ Redirects to an OAuth provider. The page navigates away, so this function never
|
|
|
140
146
|
|
|
141
147
|
The `provider` argument should be one of the `AuthProvider` values: `'google'`, `'github'`, `'gitlab'`, `'bitbucket'`, `'facebook'`, or `'saml'`.
|
|
142
148
|
|
|
143
|
-
**Throws:** `MissingIdentityError` if Identity is not configured. `
|
|
149
|
+
**Throws:** `MissingIdentityError` if Identity is not configured. `AuthError` if called on the server.
|
|
144
150
|
|
|
145
151
|
#### `handleAuthCallback`
|
|
146
152
|
|
|
@@ -160,6 +166,28 @@ onAuthChange(callback: AuthCallback): () => void
|
|
|
160
166
|
|
|
161
167
|
Subscribes to auth state changes (login, logout, token refresh, user updates). Returns an unsubscribe function. Also fires on cross-tab session changes. No-op on the server.
|
|
162
168
|
|
|
169
|
+
#### `hydrateSession`
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
hydrateSession(): Promise<User | null>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Bootstraps the browser-side gotrue-js 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.
|
|
176
|
+
|
|
177
|
+
**When to use:** After a server-side login (e.g., via a Netlify Function or Server Action), the `nf_jwt` cookie is set but gotrue-js has no browser session yet. `getUser()` works immediately (it decodes the cookie), but account operations like `updateUser()` or `verifyEmailChange()` require a live gotrue-js session. Call `hydrateSession()` once on page load to bridge this gap.
|
|
178
|
+
|
|
179
|
+
If a gotrue-js session already exists (e.g., from a browser-side login), this is a no-op and returns the existing user.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { hydrateSession, updateUser } from '@netlify/identity'
|
|
183
|
+
|
|
184
|
+
// On page load, hydrate the session from server-set cookies
|
|
185
|
+
await hydrateSession()
|
|
186
|
+
|
|
187
|
+
// Now browser account operations work
|
|
188
|
+
await updateUser({ data: { full_name: 'Jane Doe' } })
|
|
189
|
+
```
|
|
190
|
+
|
|
163
191
|
#### `requestPasswordRecovery`
|
|
164
192
|
|
|
165
193
|
```ts
|
|
@@ -213,10 +241,10 @@ Redeems a recovery token and sets a new password. Logs the user in on success.
|
|
|
213
241
|
#### `updateUser`
|
|
214
242
|
|
|
215
243
|
```ts
|
|
216
|
-
updateUser(updates:
|
|
244
|
+
updateUser(updates: UserUpdates): Promise<User>
|
|
217
245
|
```
|
|
218
246
|
|
|
219
|
-
Updates the current user's metadata or credentials. Requires an active session.
|
|
247
|
+
Updates the current user's metadata or credentials. Requires an active session. Pass `email` or `password` to change credentials, or `data` to update user metadata (e.g., `{ data: { full_name: 'New Name' } }`).
|
|
220
248
|
|
|
221
249
|
**Throws:** `MissingIdentityError` if Identity is not configured. `AuthError` if no user is logged in, or the update fails.
|
|
222
250
|
|
|
@@ -264,6 +292,27 @@ interface IdentityConfig {
|
|
|
264
292
|
type AuthProvider = 'google' | 'github' | 'gitlab' | 'bitbucket' | 'facebook' | 'saml' | 'email'
|
|
265
293
|
```
|
|
266
294
|
|
|
295
|
+
#### `UserUpdates`
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
interface UserUpdates {
|
|
299
|
+
email?: string
|
|
300
|
+
password?: string
|
|
301
|
+
data?: Record<string, unknown>
|
|
302
|
+
[key: string]: unknown
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Fields accepted by `updateUser()`. All fields are optional.
|
|
307
|
+
|
|
308
|
+
#### `SignupData`
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
type SignupData = Record<string, unknown>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
User metadata passed as the third argument to `signup()`. Stored in the user's `user_metadata` field.
|
|
315
|
+
|
|
267
316
|
#### `AppMetadata`
|
|
268
317
|
|
|
269
318
|
```ts
|
|
@@ -422,7 +471,7 @@ export async function loader() {
|
|
|
422
471
|
}
|
|
423
472
|
```
|
|
424
473
|
|
|
425
|
-
Remix actions return HTTP responses, so `redirect()`
|
|
474
|
+
Remix `redirect()` works after server-side `login()` because Remix actions return real HTTP responses. The browser receives a 302 with the `Set-Cookie` header already applied, so the next request includes the auth cookie. This is different from Next.js, where `redirect()` in a Server Action triggers a client-side (soft) navigation that may not include newly-set cookies.
|
|
426
475
|
|
|
427
476
|
### TanStack Start
|
|
428
477
|
|
|
@@ -526,37 +575,133 @@ if (!user) return Astro.redirect('/login')
|
|
|
526
575
|
<h1>Hello, {user.email}</h1>
|
|
527
576
|
```
|
|
528
577
|
|
|
578
|
+
### SvelteKit
|
|
579
|
+
|
|
580
|
+
**Login from the browser (recommended):**
|
|
581
|
+
|
|
582
|
+
```svelte
|
|
583
|
+
<!-- src/routes/login/+page.svelte -->
|
|
584
|
+
<script lang="ts">
|
|
585
|
+
import { login } from '@netlify/identity'
|
|
586
|
+
|
|
587
|
+
let email = ''
|
|
588
|
+
let password = ''
|
|
589
|
+
let error = ''
|
|
590
|
+
|
|
591
|
+
async function handleLogin() {
|
|
592
|
+
try {
|
|
593
|
+
await login(email, password)
|
|
594
|
+
window.location.href = '/dashboard'
|
|
595
|
+
} catch (e) {
|
|
596
|
+
error = (e as Error).message
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
</script>
|
|
600
|
+
|
|
601
|
+
<form on:submit|preventDefault={handleLogin}>
|
|
602
|
+
<input bind:value={email} type="email" />
|
|
603
|
+
<input bind:value={password} type="password" />
|
|
604
|
+
<button type="submit">Log in</button>
|
|
605
|
+
{#if error}<p>{error}</p>{/if}
|
|
606
|
+
</form>
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
```ts
|
|
610
|
+
// src/routes/dashboard/+page.server.ts
|
|
611
|
+
import { getUser } from '@netlify/identity'
|
|
612
|
+
import { redirect } from '@sveltejs/kit'
|
|
613
|
+
|
|
614
|
+
export function load() {
|
|
615
|
+
const user = getUser()
|
|
616
|
+
if (!user) redirect(302, '/login')
|
|
617
|
+
return { user }
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
529
621
|
### Handling OAuth callbacks in SPAs
|
|
530
622
|
|
|
531
|
-
All SPA frameworks need a callback handler that runs on page load to process OAuth redirects, email confirmations, and password recovery tokens.
|
|
623
|
+
All SPA frameworks need a callback handler that runs on page load to process OAuth redirects, email confirmations, and password recovery tokens. Use a **wrapper component** that blocks page content while processing tokens. This prevents a flash of unauthenticated content that occurs when the page renders before the callback completes.
|
|
532
624
|
|
|
533
625
|
```tsx
|
|
534
626
|
// React component (works with Next.js, Remix, TanStack Start)
|
|
535
|
-
import { useEffect } from 'react'
|
|
627
|
+
import { useEffect, useState } from 'react'
|
|
536
628
|
import { handleAuthCallback } from '@netlify/identity'
|
|
537
629
|
|
|
538
|
-
|
|
630
|
+
const AUTH_HASH_PATTERN = /^#(confirmation_token|recovery_token|invite_token|email_change_token|access_token)=/
|
|
631
|
+
|
|
632
|
+
export function CallbackHandler({ children }: { children: React.ReactNode }) {
|
|
633
|
+
const [processing, setProcessing] = useState(
|
|
634
|
+
() => typeof window !== 'undefined' && AUTH_HASH_PATTERN.test(window.location.hash),
|
|
635
|
+
)
|
|
636
|
+
const [error, setError] = useState<string | null>(null)
|
|
637
|
+
|
|
539
638
|
useEffect(() => {
|
|
540
|
-
if (!window.location.hash) return
|
|
541
|
-
|
|
542
|
-
handleAuthCallback()
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
639
|
+
if (!window.location.hash || !AUTH_HASH_PATTERN.test(window.location.hash)) return
|
|
640
|
+
|
|
641
|
+
handleAuthCallback()
|
|
642
|
+
.then((result) => {
|
|
643
|
+
if (!result) {
|
|
644
|
+
setProcessing(false)
|
|
645
|
+
return
|
|
646
|
+
}
|
|
647
|
+
if (result.type === 'invite') {
|
|
648
|
+
window.location.href = `/accept-invite?token=${result.token}`
|
|
649
|
+
} else {
|
|
650
|
+
window.location.href = '/dashboard'
|
|
651
|
+
}
|
|
652
|
+
})
|
|
653
|
+
.catch((err) => {
|
|
654
|
+
setError(err instanceof Error ? err.message : 'Callback failed')
|
|
655
|
+
setProcessing(false)
|
|
656
|
+
})
|
|
550
657
|
}, [])
|
|
551
658
|
|
|
552
|
-
return
|
|
659
|
+
if (error) return <div>Auth error: {error}</div>
|
|
660
|
+
if (processing) return <div>Confirming your account...</div>
|
|
661
|
+
return <>{children}</>
|
|
553
662
|
}
|
|
554
663
|
```
|
|
555
664
|
|
|
556
|
-
|
|
665
|
+
Wrap your page content with this component in your **root layout** so it runs on every page:
|
|
666
|
+
|
|
667
|
+
```tsx
|
|
668
|
+
// Root layout
|
|
669
|
+
<CallbackHandler>
|
|
670
|
+
<Outlet /> {/* or {children} in Next.js */}
|
|
671
|
+
</CallbackHandler>
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
If you only mount it on a `/callback` route, OAuth redirects and email confirmation links that land on other pages will not be processed.
|
|
557
675
|
|
|
558
676
|
## Guides
|
|
559
677
|
|
|
678
|
+
### React `useAuth` hook
|
|
679
|
+
|
|
680
|
+
The library is framework-agnostic, but here's a simple React hook for keeping components in sync with auth state:
|
|
681
|
+
|
|
682
|
+
```tsx
|
|
683
|
+
import { useState, useEffect } from 'react'
|
|
684
|
+
import { getUser, onAuthChange } from '@netlify/identity'
|
|
685
|
+
import type { User } from '@netlify/identity'
|
|
686
|
+
|
|
687
|
+
export function useAuth() {
|
|
688
|
+
const [user, setUser] = useState<User | null>(getUser())
|
|
689
|
+
|
|
690
|
+
useEffect(() => {
|
|
691
|
+
return onAuthChange((_event, user) => setUser(user))
|
|
692
|
+
}, [])
|
|
693
|
+
|
|
694
|
+
return user
|
|
695
|
+
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
```tsx
|
|
699
|
+
function NavBar() {
|
|
700
|
+
const user = useAuth()
|
|
701
|
+
return user ? <p>Hello, {user.name}</p> : <a href="/login">Log in</a>
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
560
705
|
### Listening for auth changes
|
|
561
706
|
|
|
562
707
|
Use `onAuthChange` to keep your UI in sync with auth state. It fires on login, logout, token refresh, and user updates. It also detects session changes in other browser tabs (via `localStorage`).
|
|
@@ -656,6 +801,17 @@ if (result?.type === 'invite' && result.token) {
|
|
|
656
801
|
}
|
|
657
802
|
```
|
|
658
803
|
|
|
804
|
+
### Session lifetime
|
|
805
|
+
|
|
806
|
+
Sessions are managed by Netlify Identity (GoTrue) on the server side. The library stores two cookies:
|
|
807
|
+
|
|
808
|
+
- **`nf_jwt`**: A short-lived JWT access token (default: 1 hour). Automatically refreshed by gotrue-js in the browser using the refresh token.
|
|
809
|
+
- **`nf_refresh`**: A long-lived refresh token used to obtain new access tokens without re-authenticating.
|
|
810
|
+
|
|
811
|
+
In the browser, gotrue-js handles token refresh automatically in the background. On the server, the access token in the `nf_jwt` cookie is validated as-is; if it has expired, `getUser()` returns `null`. The user will need to refresh the page (which triggers a browser-side token refresh) or log in again.
|
|
812
|
+
|
|
813
|
+
Session lifetime is configured in your GoTrue/Identity server settings, not in this library.
|
|
814
|
+
|
|
659
815
|
## License
|
|
660
816
|
|
|
661
817
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -38,6 +38,7 @@ __export(index_exports, {
|
|
|
38
38
|
getSettings: () => getSettings,
|
|
39
39
|
getUser: () => getUser,
|
|
40
40
|
handleAuthCallback: () => handleAuthCallback,
|
|
41
|
+
hydrateSession: () => hydrateSession,
|
|
41
42
|
isAuthenticated: () => isAuthenticated,
|
|
42
43
|
login: () => login,
|
|
43
44
|
logout: () => logout,
|
|
@@ -69,7 +70,7 @@ var AuthError = class extends Error {
|
|
|
69
70
|
}
|
|
70
71
|
};
|
|
71
72
|
var MissingIdentityError = class extends Error {
|
|
72
|
-
constructor(message = "Identity is not available in
|
|
73
|
+
constructor(message = "Netlify Identity is not available. Enable Identity in your site dashboard and use `netlify dev` for local development.") {
|
|
73
74
|
super(message);
|
|
74
75
|
this.name = "MissingIdentityError";
|
|
75
76
|
}
|
|
@@ -247,6 +248,9 @@ var backgroundHydrate = (accessToken) => {
|
|
|
247
248
|
if (hydrating) return;
|
|
248
249
|
hydrating = true;
|
|
249
250
|
const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
|
|
251
|
+
const decoded = decodeJwtPayload(accessToken);
|
|
252
|
+
const expiresAt = decoded?.exp ?? Math.floor(Date.now() / 1e3) + 3600;
|
|
253
|
+
const expiresIn = Math.max(0, expiresAt - Math.floor(Date.now() / 1e3));
|
|
250
254
|
setTimeout(() => {
|
|
251
255
|
try {
|
|
252
256
|
const client = getClient();
|
|
@@ -254,8 +258,8 @@ var backgroundHydrate = (accessToken) => {
|
|
|
254
258
|
{
|
|
255
259
|
access_token: accessToken,
|
|
256
260
|
token_type: "bearer",
|
|
257
|
-
expires_in:
|
|
258
|
-
expires_at:
|
|
261
|
+
expires_in: expiresIn,
|
|
262
|
+
expires_at: expiresAt,
|
|
259
263
|
refresh_token: refreshToken
|
|
260
264
|
},
|
|
261
265
|
true
|
|
@@ -363,15 +367,19 @@ var persistSession = true;
|
|
|
363
367
|
var listeners = /* @__PURE__ */ new Set();
|
|
364
368
|
var emitAuthEvent = (event, user) => {
|
|
365
369
|
for (const listener of listeners) {
|
|
366
|
-
|
|
370
|
+
try {
|
|
371
|
+
listener(event, user);
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
367
374
|
}
|
|
368
375
|
};
|
|
376
|
+
var GOTRUE_STORAGE_KEY = "gotrue.user";
|
|
369
377
|
var storageListenerAttached = false;
|
|
370
378
|
var attachStorageListener = () => {
|
|
371
379
|
if (storageListenerAttached) return;
|
|
372
380
|
storageListenerAttached = true;
|
|
373
381
|
window.addEventListener("storage", (event) => {
|
|
374
|
-
if (event.key !==
|
|
382
|
+
if (event.key !== GOTRUE_STORAGE_KEY) return;
|
|
375
383
|
if (event.newValue) {
|
|
376
384
|
const client = getGoTrueClient();
|
|
377
385
|
const currentUser = client?.currentUser();
|
|
@@ -486,6 +494,10 @@ var signup = async (email, password, data) => {
|
|
|
486
494
|
const response = await client.signup(email, password, data);
|
|
487
495
|
const user = toUser(response);
|
|
488
496
|
if (response.confirmed_at) {
|
|
497
|
+
const jwt = await response.jwt?.();
|
|
498
|
+
if (jwt) {
|
|
499
|
+
setBrowserAuthCookies(jwt);
|
|
500
|
+
}
|
|
489
501
|
emitAuthEvent("login", user);
|
|
490
502
|
}
|
|
491
503
|
return user;
|
|
@@ -504,8 +516,7 @@ var logout = async () => {
|
|
|
504
516
|
method: "POST",
|
|
505
517
|
headers: { Authorization: `Bearer ${jwt}` }
|
|
506
518
|
});
|
|
507
|
-
} catch
|
|
508
|
-
throw new AuthError(error.message, void 0, { cause: error });
|
|
519
|
+
} catch {
|
|
509
520
|
}
|
|
510
521
|
}
|
|
511
522
|
deleteAuthCookies(cookies);
|
|
@@ -525,11 +536,11 @@ var logout = async () => {
|
|
|
525
536
|
};
|
|
526
537
|
var oauthLogin = (provider) => {
|
|
527
538
|
if (!isBrowser()) {
|
|
528
|
-
throw new
|
|
539
|
+
throw new AuthError("oauthLogin() is only available in the browser");
|
|
529
540
|
}
|
|
530
541
|
const client = getClient();
|
|
531
542
|
window.location.href = client.loginExternalUrl(provider);
|
|
532
|
-
throw new
|
|
543
|
+
throw new AuthError("Redirecting to OAuth provider");
|
|
533
544
|
};
|
|
534
545
|
var handleAuthCallback = async () => {
|
|
535
546
|
if (!isBrowser()) return null;
|
|
@@ -613,6 +624,7 @@ var handleAuthCallback = async () => {
|
|
|
613
624
|
}
|
|
614
625
|
return null;
|
|
615
626
|
} catch (error) {
|
|
627
|
+
if (error instanceof AuthError) throw error;
|
|
616
628
|
throw new AuthError(error.message, void 0, { cause: error });
|
|
617
629
|
}
|
|
618
630
|
};
|
|
@@ -627,12 +639,15 @@ var hydrateSession = async () => {
|
|
|
627
639
|
const accessToken = getCookie(NF_JWT_COOKIE);
|
|
628
640
|
if (!accessToken) return null;
|
|
629
641
|
const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
|
|
642
|
+
const decoded = decodeJwtPayload(accessToken);
|
|
643
|
+
const expiresAt = decoded?.exp ?? Math.floor(Date.now() / 1e3) + 3600;
|
|
644
|
+
const expiresIn = Math.max(0, expiresAt - Math.floor(Date.now() / 1e3));
|
|
630
645
|
const gotrueUser = await client.createUser(
|
|
631
646
|
{
|
|
632
647
|
access_token: accessToken,
|
|
633
648
|
token_type: "bearer",
|
|
634
|
-
expires_in:
|
|
635
|
-
expires_at:
|
|
649
|
+
expires_in: expiresIn,
|
|
650
|
+
expires_at: expiresAt,
|
|
636
651
|
refresh_token: refreshToken
|
|
637
652
|
},
|
|
638
653
|
persistSession
|
|
@@ -749,6 +764,7 @@ var updateUser = async (updates) => {
|
|
|
749
764
|
getSettings,
|
|
750
765
|
getUser,
|
|
751
766
|
handleAuthCallback,
|
|
767
|
+
hydrateSession,
|
|
752
768
|
isAuthenticated,
|
|
753
769
|
login,
|
|
754
770
|
logout,
|