@netlify/identity 0.3.0-alpha.4 → 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 +19 -17
- package/dist/index.cjs +145 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -8
- package/dist/index.d.ts +14 -8
- package/dist/index.js +145 -137
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
}
|
|
@@ -611,8 +612,8 @@ export default function LoginPage() {
|
|
|
611
612
|
import { getUser } from '@netlify/identity'
|
|
612
613
|
import { redirect } from 'next/navigation'
|
|
613
614
|
|
|
614
|
-
export default function Dashboard() {
|
|
615
|
-
const user = getUser()
|
|
615
|
+
export default async function Dashboard() {
|
|
616
|
+
const user = await getUser()
|
|
616
617
|
if (!user) redirect('/login')
|
|
617
618
|
|
|
618
619
|
return <h1>Hello, {user.email}</h1>
|
|
@@ -651,7 +652,7 @@ import { getUser } from '@netlify/identity'
|
|
|
651
652
|
import { redirect } from '@remix-run/node'
|
|
652
653
|
|
|
653
654
|
export async function loader() {
|
|
654
|
-
const user = getUser()
|
|
655
|
+
const user = await getUser()
|
|
655
656
|
if (!user) return redirect('/login')
|
|
656
657
|
return { user }
|
|
657
658
|
}
|
|
@@ -669,7 +670,7 @@ import { createServerFn } from '@tanstack/react-start'
|
|
|
669
670
|
import { getUser } from '@netlify/identity'
|
|
670
671
|
|
|
671
672
|
export const getServerUser = createServerFn({ method: 'GET' }).handler(async () => {
|
|
672
|
-
const user = getUser()
|
|
673
|
+
const user = await getUser()
|
|
673
674
|
return user ?? null
|
|
674
675
|
})
|
|
675
676
|
```
|
|
@@ -755,7 +756,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|
|
755
756
|
// src/pages/dashboard.astro
|
|
756
757
|
import { getUser } from '@netlify/identity'
|
|
757
758
|
|
|
758
|
-
const user = getUser()
|
|
759
|
+
const user = await getUser()
|
|
759
760
|
if (!user) return Astro.redirect('/login')
|
|
760
761
|
---
|
|
761
762
|
<h1>Hello, {user.email}</h1>
|
|
@@ -797,8 +798,8 @@ if (!user) return Astro.redirect('/login')
|
|
|
797
798
|
import { getUser } from '@netlify/identity'
|
|
798
799
|
import { redirect } from '@sveltejs/kit'
|
|
799
800
|
|
|
800
|
-
export function load() {
|
|
801
|
-
const user = getUser()
|
|
801
|
+
export async function load() {
|
|
802
|
+
const user = await getUser()
|
|
802
803
|
if (!user) redirect(302, '/login')
|
|
803
804
|
return { user }
|
|
804
805
|
}
|
|
@@ -873,9 +874,10 @@ import { getUser, onAuthChange } from '@netlify/identity'
|
|
|
873
874
|
import type { User } from '@netlify/identity'
|
|
874
875
|
|
|
875
876
|
export function useAuth() {
|
|
876
|
-
const [user, setUser] = useState<User | null>(
|
|
877
|
+
const [user, setUser] = useState<User | null>(null)
|
|
877
878
|
|
|
878
879
|
useEffect(() => {
|
|
880
|
+
getUser().then(setUser)
|
|
879
881
|
return onAuthChange((_event, user) => setUser(user))
|
|
880
882
|
}, [])
|
|
881
883
|
|
package/dist/index.cjs
CHANGED
|
@@ -222,143 +222,6 @@ var triggerNextjsDynamic = () => {
|
|
|
222
222
|
}
|
|
223
223
|
};
|
|
224
224
|
|
|
225
|
-
// src/user.ts
|
|
226
|
-
var toAuthProvider = (value) => typeof value === "string" && AUTH_PROVIDERS.includes(value) ? value : void 0;
|
|
227
|
-
var toUser = (userData) => {
|
|
228
|
-
const userMeta = userData.user_metadata ?? {};
|
|
229
|
-
const appMeta = userData.app_metadata ?? {};
|
|
230
|
-
const name = userMeta.full_name || userMeta.name;
|
|
231
|
-
const pictureUrl = userMeta.avatar_url;
|
|
232
|
-
return {
|
|
233
|
-
id: userData.id,
|
|
234
|
-
email: userData.email,
|
|
235
|
-
emailVerified: !!userData.confirmed_at,
|
|
236
|
-
createdAt: userData.created_at,
|
|
237
|
-
updatedAt: userData.updated_at,
|
|
238
|
-
provider: toAuthProvider(appMeta.provider),
|
|
239
|
-
name: typeof name === "string" ? name : void 0,
|
|
240
|
-
pictureUrl: typeof pictureUrl === "string" ? pictureUrl : void 0,
|
|
241
|
-
metadata: userMeta,
|
|
242
|
-
rawGoTrueData: { ...userData }
|
|
243
|
-
};
|
|
244
|
-
};
|
|
245
|
-
var claimsToUser = (claims) => {
|
|
246
|
-
const appMeta = claims.app_metadata ?? {};
|
|
247
|
-
const userMeta = claims.user_metadata ?? {};
|
|
248
|
-
const name = userMeta.full_name || userMeta.name;
|
|
249
|
-
return {
|
|
250
|
-
id: claims.sub ?? "",
|
|
251
|
-
email: claims.email,
|
|
252
|
-
provider: toAuthProvider(appMeta.provider),
|
|
253
|
-
name: typeof name === "string" ? name : void 0,
|
|
254
|
-
metadata: userMeta
|
|
255
|
-
};
|
|
256
|
-
};
|
|
257
|
-
var hydrating = false;
|
|
258
|
-
var backgroundHydrate = (accessToken) => {
|
|
259
|
-
if (hydrating) return;
|
|
260
|
-
hydrating = true;
|
|
261
|
-
const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
|
|
262
|
-
const decoded = decodeJwtPayload(accessToken);
|
|
263
|
-
const expiresAt = decoded?.exp ?? Math.floor(Date.now() / 1e3) + 3600;
|
|
264
|
-
const expiresIn = Math.max(0, expiresAt - Math.floor(Date.now() / 1e3));
|
|
265
|
-
setTimeout(() => {
|
|
266
|
-
try {
|
|
267
|
-
const client = getClient();
|
|
268
|
-
client.createUser(
|
|
269
|
-
{
|
|
270
|
-
access_token: accessToken,
|
|
271
|
-
token_type: "bearer",
|
|
272
|
-
expires_in: expiresIn,
|
|
273
|
-
expires_at: expiresAt,
|
|
274
|
-
refresh_token: refreshToken
|
|
275
|
-
},
|
|
276
|
-
true
|
|
277
|
-
).catch(() => {
|
|
278
|
-
}).finally(() => {
|
|
279
|
-
hydrating = false;
|
|
280
|
-
});
|
|
281
|
-
} catch {
|
|
282
|
-
hydrating = false;
|
|
283
|
-
}
|
|
284
|
-
}, 0);
|
|
285
|
-
};
|
|
286
|
-
var decodeJwtPayload = (token) => {
|
|
287
|
-
try {
|
|
288
|
-
const parts = token.split(".");
|
|
289
|
-
if (parts.length !== 3) return null;
|
|
290
|
-
const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
|
|
291
|
-
return JSON.parse(payload);
|
|
292
|
-
} catch {
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
var getUser = () => {
|
|
297
|
-
if (isBrowser()) {
|
|
298
|
-
const client = getGoTrueClient();
|
|
299
|
-
const currentUser = client?.currentUser() ?? null;
|
|
300
|
-
if (currentUser) {
|
|
301
|
-
const jwt2 = getCookie(NF_JWT_COOKIE);
|
|
302
|
-
if (!jwt2) {
|
|
303
|
-
try {
|
|
304
|
-
currentUser.clearSession();
|
|
305
|
-
} catch {
|
|
306
|
-
}
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
return toUser(currentUser);
|
|
310
|
-
}
|
|
311
|
-
const jwt = getCookie(NF_JWT_COOKIE);
|
|
312
|
-
if (!jwt) return null;
|
|
313
|
-
const claims = decodeJwtPayload(jwt);
|
|
314
|
-
if (!claims) return null;
|
|
315
|
-
backgroundHydrate(jwt);
|
|
316
|
-
return claimsToUser(claims);
|
|
317
|
-
}
|
|
318
|
-
triggerNextjsDynamic();
|
|
319
|
-
const identityContext = globalThis.netlifyIdentityContext;
|
|
320
|
-
if (identityContext?.user) {
|
|
321
|
-
return claimsToUser(identityContext.user);
|
|
322
|
-
}
|
|
323
|
-
const serverJwt = getServerCookie(NF_JWT_COOKIE);
|
|
324
|
-
if (serverJwt) {
|
|
325
|
-
const claims = decodeJwtPayload(serverJwt);
|
|
326
|
-
if (claims) return claimsToUser(claims);
|
|
327
|
-
}
|
|
328
|
-
return null;
|
|
329
|
-
};
|
|
330
|
-
var isAuthenticated = () => getUser() !== null;
|
|
331
|
-
|
|
332
|
-
// src/config.ts
|
|
333
|
-
var getIdentityConfig = () => {
|
|
334
|
-
if (isBrowser()) {
|
|
335
|
-
return { url: `${window.location.origin}${IDENTITY_PATH}` };
|
|
336
|
-
}
|
|
337
|
-
return getIdentityContext();
|
|
338
|
-
};
|
|
339
|
-
var getSettings = async () => {
|
|
340
|
-
const client = getClient();
|
|
341
|
-
try {
|
|
342
|
-
const raw = await client.settings();
|
|
343
|
-
const external = raw.external ?? {};
|
|
344
|
-
return {
|
|
345
|
-
autoconfirm: raw.autoconfirm,
|
|
346
|
-
disableSignup: raw.disable_signup,
|
|
347
|
-
providers: {
|
|
348
|
-
google: external.google ?? false,
|
|
349
|
-
github: external.github ?? false,
|
|
350
|
-
gitlab: external.gitlab ?? false,
|
|
351
|
-
bitbucket: external.bitbucket ?? false,
|
|
352
|
-
facebook: external.facebook ?? false,
|
|
353
|
-
email: external.email ?? false,
|
|
354
|
-
saml: external.saml ?? false
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
} catch (err) {
|
|
358
|
-
throw new AuthError(err instanceof Error ? err.message : "Failed to fetch identity settings", 502, { cause: err });
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
225
|
// src/events.ts
|
|
363
226
|
var AUTH_EVENTS = {
|
|
364
227
|
LOGIN: "login",
|
|
@@ -681,6 +544,151 @@ var hydrateSession = async () => {
|
|
|
681
544
|
return user;
|
|
682
545
|
};
|
|
683
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
|
+
|
|
684
692
|
// src/account.ts
|
|
685
693
|
var resolveCurrentUser = async () => {
|
|
686
694
|
const client = getClient();
|