@netlify/identity 0.3.0-alpha.3 → 0.3.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -18
- package/dist/index.cjs +189 -166
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -8
- package/dist/index.d.ts +15 -8
- package/dist/index.js +189 -166
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A lightweight, no-config headless authentication library for projects using Netl
|
|
|
9
9
|
- [Netlify Identity](https://docs.netlify.com/security/secure-access-to-sites/identity/) must be enabled on your Netlify project
|
|
10
10
|
- For local development, use [`netlify dev`](https://docs.netlify.com/cli/local-development/) so the Identity endpoint is available
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## How this library relates to other Netlify auth packages
|
|
13
13
|
|
|
14
14
|
| Package | What it is | When to use it |
|
|
15
15
|
| ------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
@@ -54,8 +54,8 @@ import { login, getUser } from '@netlify/identity'
|
|
|
54
54
|
const user = await login('jane@example.com', 'password123')
|
|
55
55
|
console.log(`Hello, ${user.name}`)
|
|
56
56
|
|
|
57
|
-
// Later, check auth state
|
|
58
|
-
const currentUser = getUser()
|
|
57
|
+
// Later, check auth state
|
|
58
|
+
const currentUser = await getUser()
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
### Protect a Netlify Function
|
|
@@ -65,7 +65,7 @@ import { getUser } from '@netlify/identity'
|
|
|
65
65
|
import type { Context } from '@netlify/functions'
|
|
66
66
|
|
|
67
67
|
export default async (req: Request, context: Context) => {
|
|
68
|
-
const user = getUser()
|
|
68
|
+
const user = await getUser()
|
|
69
69
|
if (!user) return new Response('Unauthorized', { status: 401 })
|
|
70
70
|
return Response.json({ id: user.id, email: user.email })
|
|
71
71
|
}
|
|
@@ -78,7 +78,7 @@ import { getUser } from '@netlify/identity'
|
|
|
78
78
|
import type { Context } from '@netlify/edge-functions'
|
|
79
79
|
|
|
80
80
|
export default async (req: Request, context: Context) => {
|
|
81
|
-
const user = getUser()
|
|
81
|
+
const user = await getUser()
|
|
82
82
|
if (!user) return new Response('Unauthorized', { status: 401 })
|
|
83
83
|
return Response.json({ id: user.id, email: user.email })
|
|
84
84
|
}
|
|
@@ -91,20 +91,20 @@ export default async (req: Request, context: Context) => {
|
|
|
91
91
|
#### `getUser`
|
|
92
92
|
|
|
93
93
|
```ts
|
|
94
|
-
getUser(): User | null
|
|
94
|
+
getUser(): Promise<User | null>
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
Returns the current authenticated user, or `null` if not logged in.
|
|
97
|
+
Returns the current authenticated user, or `null` if not logged in. Always returns a full `User` object with all available fields (email, roles, timestamps, metadata) regardless of whether the call happens in the browser or on the server. Never throws.
|
|
98
98
|
|
|
99
99
|
> **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
100
|
|
|
101
101
|
#### `isAuthenticated`
|
|
102
102
|
|
|
103
103
|
```ts
|
|
104
|
-
isAuthenticated(): boolean
|
|
104
|
+
isAuthenticated(): Promise<boolean>
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
Returns `true` if a user is currently authenticated. Equivalent to `getUser() !== null`. Never throws.
|
|
107
|
+
Returns `true` if a user is currently authenticated. Equivalent to `(await getUser()) !== null`. Never throws.
|
|
108
108
|
|
|
109
109
|
#### `getIdentityConfig`
|
|
110
110
|
|
|
@@ -200,7 +200,7 @@ hydrateSession(): Promise<User | null>
|
|
|
200
200
|
|
|
201
201
|
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.
|
|
202
202
|
|
|
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 gotrue-js has no browser session yet. `getUser()`
|
|
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 gotrue-js has no browser session yet. `getUser()` calls `hydrateSession()` automatically, but account operations like `updateUser()` or `verifyEmailChange()` require a live gotrue-js session. Call `hydrateSession()` explicitly if you need the session ready before calling those operations.
|
|
204
204
|
|
|
205
205
|
If a gotrue-js session already exists (e.g., from a browser-side login), this is a no-op and returns the existing user.
|
|
206
206
|
|
|
@@ -373,6 +373,7 @@ interface User {
|
|
|
373
373
|
provider?: AuthProvider
|
|
374
374
|
name?: string
|
|
375
375
|
pictureUrl?: string
|
|
376
|
+
roles?: string[]
|
|
376
377
|
metadata?: Record<string, unknown>
|
|
377
378
|
rawGoTrueData?: Record<string, unknown>
|
|
378
379
|
}
|
|
@@ -501,6 +502,14 @@ const AUTH_EVENTS: {
|
|
|
501
502
|
|
|
502
503
|
Constants for auth event names. Use these instead of string literals for type safety and autocomplete.
|
|
503
504
|
|
|
505
|
+
| Event | When it fires |
|
|
506
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
507
|
+
| `LOGIN` | `login()`, `signup()` (with autoconfirm), `recoverPassword()`, `handleAuthCallback()` (OAuth/confirmation), `hydrateSession()` |
|
|
508
|
+
| `LOGOUT` | `logout()` |
|
|
509
|
+
| `TOKEN_REFRESH` | gotrue-js refreshes an expiring access token in the background |
|
|
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). |
|
|
512
|
+
|
|
504
513
|
#### `AuthEvent`
|
|
505
514
|
|
|
506
515
|
```ts
|
|
@@ -603,8 +612,8 @@ export default function LoginPage() {
|
|
|
603
612
|
import { getUser } from '@netlify/identity'
|
|
604
613
|
import { redirect } from 'next/navigation'
|
|
605
614
|
|
|
606
|
-
export default function Dashboard() {
|
|
607
|
-
const user = getUser()
|
|
615
|
+
export default async function Dashboard() {
|
|
616
|
+
const user = await getUser()
|
|
608
617
|
if (!user) redirect('/login')
|
|
609
618
|
|
|
610
619
|
return <h1>Hello, {user.email}</h1>
|
|
@@ -643,7 +652,7 @@ import { getUser } from '@netlify/identity'
|
|
|
643
652
|
import { redirect } from '@remix-run/node'
|
|
644
653
|
|
|
645
654
|
export async function loader() {
|
|
646
|
-
const user = getUser()
|
|
655
|
+
const user = await getUser()
|
|
647
656
|
if (!user) return redirect('/login')
|
|
648
657
|
return { user }
|
|
649
658
|
}
|
|
@@ -661,7 +670,7 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
661
670
|
import { getUser } from '@netlify/identity'
|
|
662
671
|
|
|
663
672
|
export const getServerUser = createServerFn({ method: 'GET' }).handler(async () => {
|
|
664
|
-
const user = getUser()
|
|
673
|
+
const user = await getUser()
|
|
665
674
|
return user ?? null
|
|
666
675
|
})
|
|
667
676
|
```
|
|
@@ -747,7 +756,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|
|
747
756
|
// src/pages/dashboard.astro
|
|
748
757
|
import { getUser } from '@netlify/identity'
|
|
749
758
|
|
|
750
|
-
const user = getUser()
|
|
759
|
+
const user = await getUser()
|
|
751
760
|
if (!user) return Astro.redirect('/login')
|
|
752
761
|
---
|
|
753
762
|
<h1>Hello, {user.email}</h1>
|
|
@@ -789,8 +798,8 @@ if (!user) return Astro.redirect('/login')
|
|
|
789
798
|
import { getUser } from '@netlify/identity'
|
|
790
799
|
import { redirect } from '@sveltejs/kit'
|
|
791
800
|
|
|
792
|
-
export function load() {
|
|
793
|
-
const user = getUser()
|
|
801
|
+
export async function load() {
|
|
802
|
+
const user = await getUser()
|
|
794
803
|
if (!user) redirect(302, '/login')
|
|
795
804
|
return { user }
|
|
796
805
|
}
|
|
@@ -865,9 +874,10 @@ import { getUser, onAuthChange } from '@netlify/identity'
|
|
|
865
874
|
import type { User } from '@netlify/identity'
|
|
866
875
|
|
|
867
876
|
export function useAuth() {
|
|
868
|
-
const [user, setUser] = useState<User | null>(
|
|
877
|
+
const [user, setUser] = useState<User | null>(null)
|
|
869
878
|
|
|
870
879
|
useEffect(() => {
|
|
880
|
+
getUser().then(setUser)
|
|
871
881
|
return onAuthChange((_event, user) => setUser(user))
|
|
872
882
|
}, [])
|
|
873
883
|
|
package/dist/index.cjs
CHANGED
|
@@ -61,7 +61,7 @@ var AUTH_PROVIDERS = ["google", "github", "gitlab", "bitbucket", "facebook", "sa
|
|
|
61
61
|
var import_gotrue_js = __toESM(require("gotrue-js"), 1);
|
|
62
62
|
|
|
63
63
|
// src/errors.ts
|
|
64
|
-
var AuthError = class extends Error {
|
|
64
|
+
var AuthError = class _AuthError extends Error {
|
|
65
65
|
constructor(message, status, options) {
|
|
66
66
|
super(message);
|
|
67
67
|
this.name = "AuthError";
|
|
@@ -70,6 +70,10 @@ var AuthError = class extends Error {
|
|
|
70
70
|
this.cause = options.cause;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
static from(error) {
|
|
74
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
75
|
+
return new _AuthError(message, void 0, { cause: error });
|
|
76
|
+
}
|
|
73
77
|
};
|
|
74
78
|
var MissingIdentityError = class extends Error {
|
|
75
79
|
constructor(message = "Netlify Identity is not available.") {
|
|
@@ -94,7 +98,7 @@ var discoverApiUrl = () => {
|
|
|
94
98
|
cachedApiUrl = identityContext.url;
|
|
95
99
|
} else if (globalThis.Netlify?.context?.url) {
|
|
96
100
|
cachedApiUrl = new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href;
|
|
97
|
-
} else if (process.env
|
|
101
|
+
} else if (typeof process !== "undefined" && process.env?.URL) {
|
|
98
102
|
cachedApiUrl = new URL(IDENTITY_PATH, process.env.URL).href;
|
|
99
103
|
}
|
|
100
104
|
}
|
|
@@ -131,7 +135,7 @@ var getIdentityContext = () => {
|
|
|
131
135
|
if (globalThis.Netlify?.context?.url) {
|
|
132
136
|
return { url: new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href };
|
|
133
137
|
}
|
|
134
|
-
const siteUrl = process.env
|
|
138
|
+
const siteUrl = typeof process !== "undefined" ? process.env?.URL : void 0;
|
|
135
139
|
if (siteUrl) {
|
|
136
140
|
return { url: new URL(IDENTITY_PATH, siteUrl).href };
|
|
137
141
|
}
|
|
@@ -143,7 +147,12 @@ var NF_JWT_COOKIE = "nf_jwt";
|
|
|
143
147
|
var NF_REFRESH_COOKIE = "nf_refresh";
|
|
144
148
|
var getCookie = (name) => {
|
|
145
149
|
const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
|
|
146
|
-
|
|
150
|
+
if (!match) return null;
|
|
151
|
+
try {
|
|
152
|
+
return decodeURIComponent(match[1]);
|
|
153
|
+
} catch {
|
|
154
|
+
return match[1];
|
|
155
|
+
}
|
|
147
156
|
};
|
|
148
157
|
var setAuthCookies = (cookies, accessToken, refreshToken) => {
|
|
149
158
|
cookies.set({
|
|
@@ -213,143 +222,6 @@ var triggerNextjsDynamic = () => {
|
|
|
213
222
|
}
|
|
214
223
|
};
|
|
215
224
|
|
|
216
|
-
// src/user.ts
|
|
217
|
-
var toAuthProvider = (value) => typeof value === "string" && AUTH_PROVIDERS.includes(value) ? value : void 0;
|
|
218
|
-
var toUser = (userData) => {
|
|
219
|
-
const userMeta = userData.user_metadata ?? {};
|
|
220
|
-
const appMeta = userData.app_metadata ?? {};
|
|
221
|
-
const name = userMeta.full_name || userMeta.name;
|
|
222
|
-
const pictureUrl = userMeta.avatar_url;
|
|
223
|
-
return {
|
|
224
|
-
id: userData.id,
|
|
225
|
-
email: userData.email,
|
|
226
|
-
emailVerified: !!userData.confirmed_at,
|
|
227
|
-
createdAt: userData.created_at,
|
|
228
|
-
updatedAt: userData.updated_at,
|
|
229
|
-
provider: toAuthProvider(appMeta.provider),
|
|
230
|
-
name: typeof name === "string" ? name : void 0,
|
|
231
|
-
pictureUrl: typeof pictureUrl === "string" ? pictureUrl : void 0,
|
|
232
|
-
metadata: userMeta,
|
|
233
|
-
rawGoTrueData: { ...userData }
|
|
234
|
-
};
|
|
235
|
-
};
|
|
236
|
-
var claimsToUser = (claims) => {
|
|
237
|
-
const appMeta = claims.app_metadata ?? {};
|
|
238
|
-
const userMeta = claims.user_metadata ?? {};
|
|
239
|
-
const name = userMeta.full_name || userMeta.name;
|
|
240
|
-
return {
|
|
241
|
-
id: claims.sub ?? "",
|
|
242
|
-
email: claims.email,
|
|
243
|
-
provider: toAuthProvider(appMeta.provider),
|
|
244
|
-
name: typeof name === "string" ? name : void 0,
|
|
245
|
-
metadata: userMeta
|
|
246
|
-
};
|
|
247
|
-
};
|
|
248
|
-
var hydrating = false;
|
|
249
|
-
var backgroundHydrate = (accessToken) => {
|
|
250
|
-
if (hydrating) return;
|
|
251
|
-
hydrating = true;
|
|
252
|
-
const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
|
|
253
|
-
const decoded = decodeJwtPayload(accessToken);
|
|
254
|
-
const expiresAt = decoded?.exp ?? Math.floor(Date.now() / 1e3) + 3600;
|
|
255
|
-
const expiresIn = Math.max(0, expiresAt - Math.floor(Date.now() / 1e3));
|
|
256
|
-
setTimeout(() => {
|
|
257
|
-
try {
|
|
258
|
-
const client = getClient();
|
|
259
|
-
client.createUser(
|
|
260
|
-
{
|
|
261
|
-
access_token: accessToken,
|
|
262
|
-
token_type: "bearer",
|
|
263
|
-
expires_in: expiresIn,
|
|
264
|
-
expires_at: expiresAt,
|
|
265
|
-
refresh_token: refreshToken
|
|
266
|
-
},
|
|
267
|
-
true
|
|
268
|
-
).catch(() => {
|
|
269
|
-
}).finally(() => {
|
|
270
|
-
hydrating = false;
|
|
271
|
-
});
|
|
272
|
-
} catch {
|
|
273
|
-
hydrating = false;
|
|
274
|
-
}
|
|
275
|
-
}, 0);
|
|
276
|
-
};
|
|
277
|
-
var decodeJwtPayload = (token) => {
|
|
278
|
-
try {
|
|
279
|
-
const parts = token.split(".");
|
|
280
|
-
if (parts.length !== 3) return null;
|
|
281
|
-
const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
|
|
282
|
-
return JSON.parse(payload);
|
|
283
|
-
} catch {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
var getUser = () => {
|
|
288
|
-
if (isBrowser()) {
|
|
289
|
-
const client = getGoTrueClient();
|
|
290
|
-
const currentUser = client?.currentUser() ?? null;
|
|
291
|
-
if (currentUser) {
|
|
292
|
-
const jwt2 = getCookie(NF_JWT_COOKIE);
|
|
293
|
-
if (!jwt2) {
|
|
294
|
-
try {
|
|
295
|
-
currentUser.clearSession();
|
|
296
|
-
} catch {
|
|
297
|
-
}
|
|
298
|
-
return null;
|
|
299
|
-
}
|
|
300
|
-
return toUser(currentUser);
|
|
301
|
-
}
|
|
302
|
-
const jwt = getCookie(NF_JWT_COOKIE);
|
|
303
|
-
if (!jwt) return null;
|
|
304
|
-
const claims = decodeJwtPayload(jwt);
|
|
305
|
-
if (!claims) return null;
|
|
306
|
-
backgroundHydrate(jwt);
|
|
307
|
-
return claimsToUser(claims);
|
|
308
|
-
}
|
|
309
|
-
triggerNextjsDynamic();
|
|
310
|
-
const identityContext = globalThis.netlifyIdentityContext;
|
|
311
|
-
if (identityContext?.user) {
|
|
312
|
-
return claimsToUser(identityContext.user);
|
|
313
|
-
}
|
|
314
|
-
const serverJwt = getServerCookie(NF_JWT_COOKIE);
|
|
315
|
-
if (serverJwt) {
|
|
316
|
-
const claims = decodeJwtPayload(serverJwt);
|
|
317
|
-
if (claims) return claimsToUser(claims);
|
|
318
|
-
}
|
|
319
|
-
return null;
|
|
320
|
-
};
|
|
321
|
-
var isAuthenticated = () => getUser() !== null;
|
|
322
|
-
|
|
323
|
-
// src/config.ts
|
|
324
|
-
var getIdentityConfig = () => {
|
|
325
|
-
if (isBrowser()) {
|
|
326
|
-
return { url: `${window.location.origin}${IDENTITY_PATH}` };
|
|
327
|
-
}
|
|
328
|
-
return getIdentityContext();
|
|
329
|
-
};
|
|
330
|
-
var getSettings = async () => {
|
|
331
|
-
const client = getClient();
|
|
332
|
-
try {
|
|
333
|
-
const raw = await client.settings();
|
|
334
|
-
const external = raw.external ?? {};
|
|
335
|
-
return {
|
|
336
|
-
autoconfirm: raw.autoconfirm,
|
|
337
|
-
disableSignup: raw.disable_signup,
|
|
338
|
-
providers: {
|
|
339
|
-
google: external.google ?? false,
|
|
340
|
-
github: external.github ?? false,
|
|
341
|
-
gitlab: external.gitlab ?? false,
|
|
342
|
-
bitbucket: external.bitbucket ?? false,
|
|
343
|
-
facebook: external.facebook ?? false,
|
|
344
|
-
email: external.email ?? false,
|
|
345
|
-
saml: external.saml ?? false
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
} catch (err) {
|
|
349
|
-
throw new AuthError(err instanceof Error ? err.message : "Failed to fetch identity settings", 502, { cause: err });
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
|
|
353
225
|
// src/events.ts
|
|
354
226
|
var AUTH_EVENTS = {
|
|
355
227
|
LOGIN: "login",
|
|
@@ -428,7 +300,7 @@ var login = async (email, password) => {
|
|
|
428
300
|
body: body.toString()
|
|
429
301
|
});
|
|
430
302
|
} catch (error) {
|
|
431
|
-
throw
|
|
303
|
+
throw AuthError.from(error);
|
|
432
304
|
}
|
|
433
305
|
if (!res.ok) {
|
|
434
306
|
const errorBody = await res.json().catch(() => ({}));
|
|
@@ -442,7 +314,7 @@ var login = async (email, password) => {
|
|
|
442
314
|
headers: { Authorization: `Bearer ${accessToken}` }
|
|
443
315
|
});
|
|
444
316
|
} catch (error) {
|
|
445
|
-
throw
|
|
317
|
+
throw AuthError.from(error);
|
|
446
318
|
}
|
|
447
319
|
if (!userRes.ok) {
|
|
448
320
|
const errorBody = await userRes.json().catch(() => ({}));
|
|
@@ -462,7 +334,7 @@ var login = async (email, password) => {
|
|
|
462
334
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
463
335
|
return user;
|
|
464
336
|
} catch (error) {
|
|
465
|
-
throw
|
|
337
|
+
throw AuthError.from(error);
|
|
466
338
|
}
|
|
467
339
|
};
|
|
468
340
|
var signup = async (email, password, data) => {
|
|
@@ -477,7 +349,7 @@ var signup = async (email, password, data) => {
|
|
|
477
349
|
body: JSON.stringify({ email, password, data })
|
|
478
350
|
});
|
|
479
351
|
} catch (error) {
|
|
480
|
-
throw
|
|
352
|
+
throw AuthError.from(error);
|
|
481
353
|
}
|
|
482
354
|
if (!res.ok) {
|
|
483
355
|
const errorBody = await res.json().catch(() => ({}));
|
|
@@ -506,7 +378,7 @@ var signup = async (email, password, data) => {
|
|
|
506
378
|
}
|
|
507
379
|
return user;
|
|
508
380
|
} catch (error) {
|
|
509
|
-
throw
|
|
381
|
+
throw AuthError.from(error);
|
|
510
382
|
}
|
|
511
383
|
};
|
|
512
384
|
var logout = async () => {
|
|
@@ -535,7 +407,7 @@ var logout = async () => {
|
|
|
535
407
|
deleteBrowserAuthCookies();
|
|
536
408
|
emitAuthEvent(AUTH_EVENTS.LOGOUT, null);
|
|
537
409
|
} catch (error) {
|
|
538
|
-
throw
|
|
410
|
+
throw AuthError.from(error);
|
|
539
411
|
}
|
|
540
412
|
};
|
|
541
413
|
var oauthLogin = (provider) => {
|
|
@@ -566,7 +438,7 @@ var handleAuthCallback = async () => {
|
|
|
566
438
|
return null;
|
|
567
439
|
} catch (error) {
|
|
568
440
|
if (error instanceof AuthError) throw error;
|
|
569
|
-
throw
|
|
441
|
+
throw AuthError.from(error);
|
|
570
442
|
}
|
|
571
443
|
};
|
|
572
444
|
var handleOAuthCallback = async (client, params, accessToken) => {
|
|
@@ -651,21 +523,172 @@ var hydrateSession = async () => {
|
|
|
651
523
|
const decoded = decodeJwtPayload(accessToken);
|
|
652
524
|
const expiresAt = decoded?.exp ?? Math.floor(Date.now() / 1e3) + 3600;
|
|
653
525
|
const expiresIn = Math.max(0, expiresAt - Math.floor(Date.now() / 1e3));
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
526
|
+
let gotrueUser;
|
|
527
|
+
try {
|
|
528
|
+
gotrueUser = await client.createUser(
|
|
529
|
+
{
|
|
530
|
+
access_token: accessToken,
|
|
531
|
+
token_type: "bearer",
|
|
532
|
+
expires_in: expiresIn,
|
|
533
|
+
expires_at: expiresAt,
|
|
534
|
+
refresh_token: refreshToken
|
|
535
|
+
},
|
|
536
|
+
persistSession
|
|
537
|
+
);
|
|
538
|
+
} catch {
|
|
539
|
+
deleteBrowserAuthCookies();
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
664
542
|
const user = toUser(gotrueUser);
|
|
665
543
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
666
544
|
return user;
|
|
667
545
|
};
|
|
668
546
|
|
|
547
|
+
// src/user.ts
|
|
548
|
+
var toAuthProvider = (value) => typeof value === "string" && AUTH_PROVIDERS.includes(value) ? value : void 0;
|
|
549
|
+
var toRoles = (appMeta) => {
|
|
550
|
+
const roles = appMeta.roles;
|
|
551
|
+
if (Array.isArray(roles) && roles.every((r) => typeof r === "string")) {
|
|
552
|
+
return roles;
|
|
553
|
+
}
|
|
554
|
+
return void 0;
|
|
555
|
+
};
|
|
556
|
+
var toUser = (userData) => {
|
|
557
|
+
const userMeta = userData.user_metadata ?? {};
|
|
558
|
+
const appMeta = userData.app_metadata ?? {};
|
|
559
|
+
const name = userMeta.full_name || userMeta.name;
|
|
560
|
+
const pictureUrl = userMeta.avatar_url;
|
|
561
|
+
return {
|
|
562
|
+
id: userData.id,
|
|
563
|
+
email: userData.email,
|
|
564
|
+
emailVerified: !!userData.confirmed_at,
|
|
565
|
+
createdAt: userData.created_at,
|
|
566
|
+
updatedAt: userData.updated_at,
|
|
567
|
+
provider: toAuthProvider(appMeta.provider),
|
|
568
|
+
name: typeof name === "string" ? name : void 0,
|
|
569
|
+
pictureUrl: typeof pictureUrl === "string" ? pictureUrl : void 0,
|
|
570
|
+
roles: toRoles(appMeta),
|
|
571
|
+
metadata: userMeta,
|
|
572
|
+
rawGoTrueData: { ...userData }
|
|
573
|
+
};
|
|
574
|
+
};
|
|
575
|
+
var claimsToUser = (claims) => {
|
|
576
|
+
const appMeta = claims.app_metadata ?? {};
|
|
577
|
+
const userMeta = claims.user_metadata ?? {};
|
|
578
|
+
const name = userMeta.full_name || userMeta.name;
|
|
579
|
+
const pictureUrl = userMeta.avatar_url;
|
|
580
|
+
return {
|
|
581
|
+
id: claims.sub ?? "",
|
|
582
|
+
email: claims.email,
|
|
583
|
+
provider: toAuthProvider(appMeta.provider),
|
|
584
|
+
name: typeof name === "string" ? name : void 0,
|
|
585
|
+
pictureUrl: typeof pictureUrl === "string" ? pictureUrl : void 0,
|
|
586
|
+
roles: toRoles(appMeta),
|
|
587
|
+
metadata: userMeta
|
|
588
|
+
};
|
|
589
|
+
};
|
|
590
|
+
var decodeJwtPayload = (token) => {
|
|
591
|
+
try {
|
|
592
|
+
const parts = token.split(".");
|
|
593
|
+
if (parts.length !== 3) return null;
|
|
594
|
+
const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
|
|
595
|
+
return JSON.parse(payload);
|
|
596
|
+
} catch {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
var fetchFullUser = async (identityUrl, jwt) => {
|
|
601
|
+
try {
|
|
602
|
+
const res = await fetch(`${identityUrl}/user`, {
|
|
603
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
604
|
+
});
|
|
605
|
+
if (!res.ok) return null;
|
|
606
|
+
const userData = await res.json();
|
|
607
|
+
return toUser(userData);
|
|
608
|
+
} catch {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
var resolveIdentityUrl = () => {
|
|
613
|
+
const identityContext = getIdentityContext();
|
|
614
|
+
if (identityContext?.url) return identityContext.url;
|
|
615
|
+
if (globalThis.Netlify?.context?.url) {
|
|
616
|
+
return new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href;
|
|
617
|
+
}
|
|
618
|
+
const siteUrl = typeof process !== "undefined" ? process.env?.URL : void 0;
|
|
619
|
+
if (siteUrl) {
|
|
620
|
+
return new URL(IDENTITY_PATH, siteUrl).href;
|
|
621
|
+
}
|
|
622
|
+
return null;
|
|
623
|
+
};
|
|
624
|
+
var getUser = async () => {
|
|
625
|
+
if (isBrowser()) {
|
|
626
|
+
const client = getGoTrueClient();
|
|
627
|
+
const currentUser = client?.currentUser() ?? null;
|
|
628
|
+
if (currentUser) {
|
|
629
|
+
const jwt2 = getCookie(NF_JWT_COOKIE);
|
|
630
|
+
if (!jwt2) {
|
|
631
|
+
try {
|
|
632
|
+
currentUser.clearSession();
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
return toUser(currentUser);
|
|
638
|
+
}
|
|
639
|
+
const jwt = getCookie(NF_JWT_COOKIE);
|
|
640
|
+
if (!jwt) return null;
|
|
641
|
+
const claims2 = decodeJwtPayload(jwt);
|
|
642
|
+
if (!claims2) return null;
|
|
643
|
+
const hydrated = await hydrateSession();
|
|
644
|
+
if (hydrated) return hydrated;
|
|
645
|
+
return claimsToUser(claims2);
|
|
646
|
+
}
|
|
647
|
+
triggerNextjsDynamic();
|
|
648
|
+
const identityContext = globalThis.netlifyIdentityContext;
|
|
649
|
+
const serverJwt = identityContext?.token || getServerCookie(NF_JWT_COOKIE);
|
|
650
|
+
if (serverJwt) {
|
|
651
|
+
const identityUrl = resolveIdentityUrl();
|
|
652
|
+
if (identityUrl) {
|
|
653
|
+
const fullUser = await fetchFullUser(identityUrl, serverJwt);
|
|
654
|
+
if (fullUser) return fullUser;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const claims = identityContext?.user ?? (serverJwt ? decodeJwtPayload(serverJwt) : null);
|
|
658
|
+
return claims ? claimsToUser(claims) : null;
|
|
659
|
+
};
|
|
660
|
+
var isAuthenticated = async () => await getUser() !== null;
|
|
661
|
+
|
|
662
|
+
// src/config.ts
|
|
663
|
+
var getIdentityConfig = () => {
|
|
664
|
+
if (isBrowser()) {
|
|
665
|
+
return { url: `${window.location.origin}${IDENTITY_PATH}` };
|
|
666
|
+
}
|
|
667
|
+
return getIdentityContext();
|
|
668
|
+
};
|
|
669
|
+
var getSettings = async () => {
|
|
670
|
+
const client = getClient();
|
|
671
|
+
try {
|
|
672
|
+
const raw = await client.settings();
|
|
673
|
+
const external = raw.external ?? {};
|
|
674
|
+
return {
|
|
675
|
+
autoconfirm: raw.autoconfirm,
|
|
676
|
+
disableSignup: raw.disable_signup,
|
|
677
|
+
providers: {
|
|
678
|
+
google: external.google ?? false,
|
|
679
|
+
github: external.github ?? false,
|
|
680
|
+
gitlab: external.gitlab ?? false,
|
|
681
|
+
bitbucket: external.bitbucket ?? false,
|
|
682
|
+
facebook: external.facebook ?? false,
|
|
683
|
+
email: external.email ?? false,
|
|
684
|
+
saml: external.saml ?? false
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
} catch (err) {
|
|
688
|
+
throw new AuthError(err instanceof Error ? err.message : "Failed to fetch identity settings", 502, { cause: err });
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
|
|
669
692
|
// src/account.ts
|
|
670
693
|
var resolveCurrentUser = async () => {
|
|
671
694
|
const client = getClient();
|
|
@@ -685,7 +708,7 @@ var requestPasswordRecovery = async (email) => {
|
|
|
685
708
|
try {
|
|
686
709
|
await client.requestPasswordRecovery(email);
|
|
687
710
|
} catch (error) {
|
|
688
|
-
throw
|
|
711
|
+
throw AuthError.from(error);
|
|
689
712
|
}
|
|
690
713
|
};
|
|
691
714
|
var recoverPassword = async (token, newPassword) => {
|
|
@@ -697,7 +720,7 @@ var recoverPassword = async (token, newPassword) => {
|
|
|
697
720
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
698
721
|
return user;
|
|
699
722
|
} catch (error) {
|
|
700
|
-
throw
|
|
723
|
+
throw AuthError.from(error);
|
|
701
724
|
}
|
|
702
725
|
};
|
|
703
726
|
var confirmEmail = async (token) => {
|
|
@@ -708,7 +731,7 @@ var confirmEmail = async (token) => {
|
|
|
708
731
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
709
732
|
return user;
|
|
710
733
|
} catch (error) {
|
|
711
|
-
throw
|
|
734
|
+
throw AuthError.from(error);
|
|
712
735
|
}
|
|
713
736
|
};
|
|
714
737
|
var acceptInvite = async (token, password) => {
|
|
@@ -719,15 +742,15 @@ var acceptInvite = async (token, password) => {
|
|
|
719
742
|
emitAuthEvent(AUTH_EVENTS.LOGIN, user);
|
|
720
743
|
return user;
|
|
721
744
|
} catch (error) {
|
|
722
|
-
throw
|
|
745
|
+
throw AuthError.from(error);
|
|
723
746
|
}
|
|
724
747
|
};
|
|
725
748
|
var verifyEmailChange = async (token) => {
|
|
726
749
|
if (!isBrowser()) throw new AuthError("verifyEmailChange() is only available in the browser");
|
|
727
750
|
const currentUser = await resolveCurrentUser();
|
|
728
|
-
const jwt = await currentUser.jwt();
|
|
729
|
-
const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
|
|
730
751
|
try {
|
|
752
|
+
const jwt = await currentUser.jwt();
|
|
753
|
+
const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
|
|
731
754
|
const res = await fetch(`${identityUrl}/user`, {
|
|
732
755
|
method: "PUT",
|
|
733
756
|
headers: {
|
|
@@ -746,7 +769,7 @@ var verifyEmailChange = async (token) => {
|
|
|
746
769
|
return user;
|
|
747
770
|
} catch (error) {
|
|
748
771
|
if (error instanceof AuthError) throw error;
|
|
749
|
-
throw
|
|
772
|
+
throw AuthError.from(error);
|
|
750
773
|
}
|
|
751
774
|
};
|
|
752
775
|
var updateUser = async (updates) => {
|
|
@@ -757,7 +780,7 @@ var updateUser = async (updates) => {
|
|
|
757
780
|
emitAuthEvent(AUTH_EVENTS.USER_UPDATED, user);
|
|
758
781
|
return user;
|
|
759
782
|
} catch (error) {
|
|
760
|
-
throw
|
|
783
|
+
throw AuthError.from(error);
|
|
761
784
|
}
|
|
762
785
|
};
|
|
763
786
|
|