@netlify/identity 1.0.0 → 1.2.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.
@@ -27,9 +27,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
30
+ // src/main.ts
31
+ var main_exports = {};
32
+ __export(main_exports, {
33
33
  AUTH_EVENTS: () => AUTH_EVENTS,
34
34
  AuthError: () => AuthError,
35
35
  MissingIdentityError: () => MissingIdentityError,
@@ -51,9 +51,10 @@ __export(index_exports, {
51
51
  requestPasswordRecovery: () => requestPasswordRecovery,
52
52
  signup: () => signup,
53
53
  updateUser: () => updateUser,
54
- verifyEmailChange: () => verifyEmailChange
54
+ verifyEmailChange: () => verifyEmailChange,
55
+ verifyRequestOrigin: () => verifyRequestOrigin
55
56
  });
56
- module.exports = __toCommonJS(index_exports);
57
+ module.exports = __toCommonJS(main_exports);
57
58
 
58
59
  // src/types.ts
59
60
  var AUTH_PROVIDERS = ["google", "github", "gitlab", "bitbucket", "facebook", "email"];
@@ -149,7 +150,7 @@ var NF_JWT_COOKIE = "nf_jwt";
149
150
  var NF_REFRESH_COOKIE = "nf_refresh";
150
151
  var getCookie = (name) => {
151
152
  if (typeof document === "undefined") return null;
152
- const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
153
+ const match = new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`).exec(document.cookie);
153
154
  if (!match) return null;
154
155
  try {
155
156
  return decodeURIComponent(match[1]);
@@ -231,13 +232,15 @@ var triggerNextjsDynamic = () => {
231
232
  var DEFAULT_TIMEOUT_MS = 5e3;
232
233
  var fetchWithTimeout = async (url, options = {}, timeoutMs = DEFAULT_TIMEOUT_MS) => {
233
234
  const controller = new AbortController();
234
- const timer = setTimeout(() => controller.abort(), timeoutMs);
235
+ const timer = setTimeout(() => {
236
+ controller.abort();
237
+ }, timeoutMs);
235
238
  try {
236
239
  return await fetch(url, { ...options, signal: controller.signal });
237
240
  } catch (error) {
238
241
  if (error instanceof Error && error.name === "AbortError") {
239
242
  const pathname = new URL(url).pathname;
240
- throw new AuthError(`Identity request to ${pathname} timed out after ${timeoutMs}ms`);
243
+ throw new AuthError(`Identity request to ${pathname} timed out after ${String(timeoutMs)}ms`);
241
244
  }
242
245
  throw error;
243
246
  } finally {
@@ -304,16 +307,18 @@ var startTokenRefresh = () => {
304
307
  const nowS = Math.floor(Date.now() / 1e3);
305
308
  const expiresAtS = typeof token.expires_at === "number" && token.expires_at > 1e12 ? Math.floor(token.expires_at / 1e3) : token.expires_at;
306
309
  const delayMs = Math.max(0, expiresAtS - nowS - REFRESH_MARGIN_S) * 1e3;
307
- refreshTimer = setTimeout(async () => {
308
- try {
309
- const freshJwt = await user.jwt(true);
310
- const freshDetails = user.tokenDetails();
311
- setBrowserAuthCookies(freshJwt, freshDetails?.refresh_token);
312
- emitAuthEvent(AUTH_EVENTS.TOKEN_REFRESH, toUser(user));
313
- startTokenRefresh();
314
- } catch {
315
- stopTokenRefresh();
316
- }
310
+ refreshTimer = setTimeout(() => {
311
+ void (async () => {
312
+ try {
313
+ const freshJwt = await user.jwt(true);
314
+ const freshDetails = user.tokenDetails();
315
+ setBrowserAuthCookies(freshJwt, freshDetails?.refresh_token);
316
+ emitAuthEvent(AUTH_EVENTS.TOKEN_REFRESH, toUser(user));
317
+ startTokenRefresh();
318
+ } catch {
319
+ stopTokenRefresh();
320
+ }
321
+ })();
317
322
  }, delayMs);
318
323
  };
319
324
  var stopTokenRefresh = () => {
@@ -379,7 +384,7 @@ var refreshSession = async () => {
379
384
  }
380
385
  return null;
381
386
  }
382
- throw new AuthError(errorBody.msg || `Token refresh failed (${res.status})`, res.status);
387
+ throw new AuthError(errorBody.msg ?? `Token refresh failed (${String(res.status)})`, res.status);
383
388
  }
384
389
  const data = await res.json();
385
390
  const cookies = globalThis.Netlify?.context?.cookies;
@@ -426,7 +431,10 @@ var login = async (email, password) => {
426
431
  }
427
432
  if (!res.ok) {
428
433
  const errorBody = await res.json().catch(() => ({}));
429
- throw new AuthError(errorBody.msg || errorBody.error_description || `Login failed (${res.status})`, res.status);
434
+ throw new AuthError(
435
+ errorBody.msg ?? errorBody.error_description ?? `Login failed (${String(res.status)})`,
436
+ res.status
437
+ );
430
438
  }
431
439
  const data = await res.json();
432
440
  const accessToken = data.access_token;
@@ -440,7 +448,7 @@ var login = async (email, password) => {
440
448
  }
441
449
  if (!userRes.ok) {
442
450
  const errorBody = await userRes.json().catch(() => ({}));
443
- throw new AuthError(errorBody.msg || `Failed to fetch user data (${userRes.status})`, userRes.status);
451
+ throw new AuthError(errorBody.msg ?? `Failed to fetch user data (${String(userRes.status)})`, userRes.status);
444
452
  }
445
453
  const userData = await userRes.json();
446
454
  const user = toUser(userData);
@@ -476,7 +484,7 @@ var signup = async (email, password, data) => {
476
484
  }
477
485
  if (!res.ok) {
478
486
  const errorBody = await res.json().catch(() => ({}));
479
- throw new AuthError(errorBody.msg || `Signup failed (${res.status})`, res.status);
487
+ throw new AuthError(errorBody.msg ?? `Signup failed (${String(res.status)})`, res.status);
480
488
  }
481
489
  const responseData = await res.json();
482
490
  const user = toUser(responseData);
@@ -630,7 +638,7 @@ var handleEmailChangeCallback = async (client, emailChangeToken) => {
630
638
  if (!emailChangeRes.ok) {
631
639
  const errorBody = await emailChangeRes.json().catch(() => ({}));
632
640
  throw new AuthError(
633
- errorBody.msg || `Email change verification failed (${emailChangeRes.status})`,
641
+ errorBody.msg ?? `Email change verification failed (${String(emailChangeRes.status)})`,
634
642
  emailChangeRes.status
635
643
  );
636
644
  }
@@ -692,7 +700,7 @@ var toRoles = (appMeta) => {
692
700
  var toUser = (userData) => {
693
701
  const userMeta = userData.user_metadata ?? {};
694
702
  const appMeta = userData.app_metadata ?? {};
695
- const name = userMeta.full_name || userMeta.name;
703
+ const name = userMeta.full_name ?? userMeta.name;
696
704
  const pictureUrl = userMeta.avatar_url;
697
705
  return {
698
706
  id: userData.id,
@@ -718,7 +726,7 @@ var toUser = (userData) => {
718
726
  var claimsToUser = (claims) => {
719
727
  const appMeta = claims.app_metadata ?? {};
720
728
  const userMeta = claims.user_metadata ?? {};
721
- const name = userMeta.full_name || userMeta.name;
729
+ const name = userMeta.full_name ?? userMeta.name;
722
730
  const pictureUrl = userMeta.avatar_url;
723
731
  return {
724
732
  id: claims.sub ?? "",
@@ -790,7 +798,7 @@ var getUser = async () => {
790
798
  }
791
799
  triggerNextjsDynamic();
792
800
  const identityContext = globalThis.netlifyIdentityContext;
793
- const serverJwt = identityContext?.token || getServerCookie(NF_JWT_COOKIE);
801
+ const serverJwt = identityContext?.token ?? getServerCookie(NF_JWT_COOKIE);
794
802
  if (serverJwt) {
795
803
  const identityUrl = resolveIdentityUrl();
796
804
  if (identityUrl) {
@@ -832,6 +840,18 @@ var getSettings = async () => {
832
840
  }
833
841
  };
834
842
 
843
+ // src/csrf.ts
844
+ var verifyRequestOrigin = (request, options) => {
845
+ const origin = request.headers.get("origin");
846
+ if (!origin) {
847
+ throw new AuthError("Cross-origin request refused: missing Origin header.", 403);
848
+ }
849
+ const allowed = options?.allowedOrigins ?? [new URL(request.url).origin];
850
+ if (!allowed.includes(origin)) {
851
+ throw new AuthError(`Cross-origin request refused: Origin ${origin} did not match an allowed origin.`, 403);
852
+ }
853
+ };
854
+
835
855
  // src/account.ts
836
856
  var resolveCurrentUser = async () => {
837
857
  const client = getClient();
@@ -907,7 +927,7 @@ var verifyEmailChange = async (token) => {
907
927
  });
908
928
  if (!res.ok) {
909
929
  const errorBody = await res.json().catch(() => ({}));
910
- throw new AuthError(errorBody.msg || `Email change verification failed (${res.status})`, res.status);
930
+ throw new AuthError(errorBody.msg ?? `Email change verification failed (${String(res.status)})`, res.status);
911
931
  }
912
932
  const userData = await res.json();
913
933
  const user = toUser(userData);
@@ -971,7 +991,10 @@ var adminFetch = async (path, options = {}) => {
971
991
  }
972
992
  if (!res.ok) {
973
993
  const errorBody = await res.json().catch(() => ({}));
974
- throw new AuthError(errorBody.msg || `Admin request failed (${res.status})`, res.status);
994
+ throw new AuthError(
995
+ errorBody.msg ?? `Admin request failed (${String(res.status)})`,
996
+ res.status
997
+ );
975
998
  }
976
999
  return res;
977
1000
  };
@@ -1061,6 +1084,6 @@ var admin = { listUsers, getUser: getUser2, createUser, updateUser: updateUser2,
1061
1084
  requestPasswordRecovery,
1062
1085
  signup,
1063
1086
  updateUser,
1064
- verifyEmailChange
1087
+ verifyEmailChange,
1088
+ verifyRequestOrigin
1065
1089
  });
1066
- //# sourceMappingURL=index.cjs.map
@@ -278,6 +278,10 @@ declare const onAuthChange: (callback: AuthCallback) => (() => void);
278
278
  * try/catch. Next.js implements `redirect()` by throwing a special error; wrapping it in
279
279
  * try/catch will swallow the redirect.
280
280
  *
281
+ * **Server-side CSRF:** When called from a server endpoint, the endpoint must have CSRF
282
+ * protection. If your framework does not check the request's `Origin` by default, call
283
+ * {@link verifyRequestOrigin} at the start of the handler before invoking `login()`.
284
+ *
281
285
  * @example
282
286
  * ```ts
283
287
  * // Next.js server action
@@ -295,6 +299,11 @@ declare const login: (email: string, password: string) => Promise<User>;
295
299
  * In that case, no cookies are set and no auth event is emitted.
296
300
  *
297
301
  * @throws {AuthError} On duplicate email, validation failure, network error, or missing Netlify runtime.
302
+ *
303
+ * @remarks
304
+ * **Server-side CSRF:** When called from a server endpoint, the endpoint must have CSRF
305
+ * protection. If your framework does not check the request's `Origin` by default, call
306
+ * {@link verifyRequestOrigin} at the start of the handler before invoking `signup()`.
298
307
  */
299
308
  declare const signup: (email: string, password: string, data?: SignupData) => Promise<User>;
300
309
  /**
@@ -304,6 +313,11 @@ declare const signup: (email: string, password: string, data?: SignupData) => Pr
304
313
  * invalidation request fails. In the browser, emits a `'logout'` event via {@link onAuthChange}.
305
314
  *
306
315
  * @throws {AuthError} On missing Netlify runtime (server) or logout failure (browser).
316
+ *
317
+ * @remarks
318
+ * **Server-side CSRF:** When called from a server endpoint, the endpoint must have CSRF
319
+ * protection. If your framework does not check the request's `Origin` by default, call
320
+ * {@link verifyRequestOrigin} at the start of the handler before invoking `logout()`.
307
321
  */
308
322
  declare const logout: () => Promise<void>;
309
323
  /**
@@ -443,6 +457,63 @@ declare class MissingIdentityError extends Error {
443
457
  constructor(message?: string);
444
458
  }
445
459
 
460
+ /**
461
+ * Options for {@link verifyRequestOrigin}.
462
+ */
463
+ interface VerifyRequestOriginOptions {
464
+ /**
465
+ * Origins that are allowed to make state-changing requests to this endpoint.
466
+ *
467
+ * If omitted, the request is only accepted from its own origin (the origin of `request.url`),
468
+ * which is the right default for sites whose login form and login endpoint live on the same origin.
469
+ *
470
+ * Pass an explicit list when you trust additional origins (for example, a separate frontend domain
471
+ * posting to an API on another domain). The list replaces the default, so it must include every
472
+ * origin you want to allow, including the request's own origin if applicable.
473
+ *
474
+ * Each value should be a full origin string with scheme and host: `'https://example.com'`.
475
+ */
476
+ allowedOrigins?: string[];
477
+ }
478
+ /**
479
+ * Same-origin check for state-changing requests, can be used to defend against Cross-Site Request
480
+ * Forgery (CSRF) on server-side endpoints that call {@link login}, {@link signup}, or {@link logout}.
481
+ *
482
+ * Compares the incoming request's `Origin` header against the request's own origin (or an explicit
483
+ * allowlist via `options.allowedOrigins`) and throws if they don't match. Call this at the start of
484
+ * any server-side handler that performs an auth mutation, before invoking the auth function.
485
+ *
486
+ * The check runs unconditionally on every call: any HTTP method, with or without an `Origin` header.
487
+ * If you don't want the check to apply to a given method or path, simply don't call the helper there.
488
+ *
489
+ * @throws {AuthError} with status `403` when the request has no `Origin` header.
490
+ * @throws {AuthError} with status `403` when the request's `Origin` is not in the allowed origins.
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * // Netlify Function
495
+ * import { login, verifyRequestOrigin } from '@netlify/identity'
496
+ * import type { Context } from '@netlify/functions'
497
+ *
498
+ * export default async (req: Request, context: Context) => {
499
+ * verifyRequestOrigin(req)
500
+ * const { email, password } = await req.json()
501
+ * await login(email, password)
502
+ * return new Response(null, { status: 302, headers: { Location: '/dashboard' } })
503
+ * }
504
+ * ```
505
+ *
506
+ * @example
507
+ * ```ts
508
+ * // Allow a separate trusted origin (e.g. a marketing site posting to an app domain).
509
+ * // The list replaces the default, so include the request's own origin if you still want it allowed.
510
+ * verifyRequestOrigin(request, {
511
+ * allowedOrigins: ['https://app.example.com', 'https://www.example.com'],
512
+ * })
513
+ * ```
514
+ */
515
+ declare const verifyRequestOrigin: (request: Request, options?: VerifyRequestOriginOptions) => void;
516
+
446
517
  /**
447
518
  * The admin namespace for privileged user management operations.
448
519
  * All methods are server-only and require the operator token
@@ -546,4 +617,4 @@ declare const verifyEmailChange: (token: string) => Promise<User>;
546
617
  */
547
618
  declare const updateUser: (updates: UserUpdates) => Promise<User>;
548
619
 
549
- export { AUTH_EVENTS, type Admin, type AdminUserUpdates, type AppMetadata, type AuthCallback, AuthError, type AuthEvent, type AuthProvider, type CallbackResult, type CreateUserParams, type IdentityConfig, type ListUsersOptions, MissingIdentityError, type Settings, type SignupData, type User, type UserUpdates, acceptInvite, admin, confirmEmail, getIdentityConfig, getSettings, getUser, handleAuthCallback, hydrateSession, isAuthenticated, login, logout, oauthLogin, onAuthChange, recoverPassword, refreshSession, requestPasswordRecovery, signup, updateUser, verifyEmailChange };
620
+ export { AUTH_EVENTS, type Admin, type AdminUserUpdates, type AppMetadata, type AuthCallback, AuthError, type AuthEvent, type AuthProvider, type CallbackResult, type CreateUserParams, type IdentityConfig, type ListUsersOptions, MissingIdentityError, type Settings, type SignupData, type User, type UserUpdates, type VerifyRequestOriginOptions, acceptInvite, admin, confirmEmail, getIdentityConfig, getSettings, getUser, handleAuthCallback, hydrateSession, isAuthenticated, login, logout, oauthLogin, onAuthChange, recoverPassword, refreshSession, requestPasswordRecovery, signup, updateUser, verifyEmailChange, verifyRequestOrigin };
@@ -278,6 +278,10 @@ declare const onAuthChange: (callback: AuthCallback) => (() => void);
278
278
  * try/catch. Next.js implements `redirect()` by throwing a special error; wrapping it in
279
279
  * try/catch will swallow the redirect.
280
280
  *
281
+ * **Server-side CSRF:** When called from a server endpoint, the endpoint must have CSRF
282
+ * protection. If your framework does not check the request's `Origin` by default, call
283
+ * {@link verifyRequestOrigin} at the start of the handler before invoking `login()`.
284
+ *
281
285
  * @example
282
286
  * ```ts
283
287
  * // Next.js server action
@@ -295,6 +299,11 @@ declare const login: (email: string, password: string) => Promise<User>;
295
299
  * In that case, no cookies are set and no auth event is emitted.
296
300
  *
297
301
  * @throws {AuthError} On duplicate email, validation failure, network error, or missing Netlify runtime.
302
+ *
303
+ * @remarks
304
+ * **Server-side CSRF:** When called from a server endpoint, the endpoint must have CSRF
305
+ * protection. If your framework does not check the request's `Origin` by default, call
306
+ * {@link verifyRequestOrigin} at the start of the handler before invoking `signup()`.
298
307
  */
299
308
  declare const signup: (email: string, password: string, data?: SignupData) => Promise<User>;
300
309
  /**
@@ -304,6 +313,11 @@ declare const signup: (email: string, password: string, data?: SignupData) => Pr
304
313
  * invalidation request fails. In the browser, emits a `'logout'` event via {@link onAuthChange}.
305
314
  *
306
315
  * @throws {AuthError} On missing Netlify runtime (server) or logout failure (browser).
316
+ *
317
+ * @remarks
318
+ * **Server-side CSRF:** When called from a server endpoint, the endpoint must have CSRF
319
+ * protection. If your framework does not check the request's `Origin` by default, call
320
+ * {@link verifyRequestOrigin} at the start of the handler before invoking `logout()`.
307
321
  */
308
322
  declare const logout: () => Promise<void>;
309
323
  /**
@@ -443,6 +457,63 @@ declare class MissingIdentityError extends Error {
443
457
  constructor(message?: string);
444
458
  }
445
459
 
460
+ /**
461
+ * Options for {@link verifyRequestOrigin}.
462
+ */
463
+ interface VerifyRequestOriginOptions {
464
+ /**
465
+ * Origins that are allowed to make state-changing requests to this endpoint.
466
+ *
467
+ * If omitted, the request is only accepted from its own origin (the origin of `request.url`),
468
+ * which is the right default for sites whose login form and login endpoint live on the same origin.
469
+ *
470
+ * Pass an explicit list when you trust additional origins (for example, a separate frontend domain
471
+ * posting to an API on another domain). The list replaces the default, so it must include every
472
+ * origin you want to allow, including the request's own origin if applicable.
473
+ *
474
+ * Each value should be a full origin string with scheme and host: `'https://example.com'`.
475
+ */
476
+ allowedOrigins?: string[];
477
+ }
478
+ /**
479
+ * Same-origin check for state-changing requests, can be used to defend against Cross-Site Request
480
+ * Forgery (CSRF) on server-side endpoints that call {@link login}, {@link signup}, or {@link logout}.
481
+ *
482
+ * Compares the incoming request's `Origin` header against the request's own origin (or an explicit
483
+ * allowlist via `options.allowedOrigins`) and throws if they don't match. Call this at the start of
484
+ * any server-side handler that performs an auth mutation, before invoking the auth function.
485
+ *
486
+ * The check runs unconditionally on every call: any HTTP method, with or without an `Origin` header.
487
+ * If you don't want the check to apply to a given method or path, simply don't call the helper there.
488
+ *
489
+ * @throws {AuthError} with status `403` when the request has no `Origin` header.
490
+ * @throws {AuthError} with status `403` when the request's `Origin` is not in the allowed origins.
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * // Netlify Function
495
+ * import { login, verifyRequestOrigin } from '@netlify/identity'
496
+ * import type { Context } from '@netlify/functions'
497
+ *
498
+ * export default async (req: Request, context: Context) => {
499
+ * verifyRequestOrigin(req)
500
+ * const { email, password } = await req.json()
501
+ * await login(email, password)
502
+ * return new Response(null, { status: 302, headers: { Location: '/dashboard' } })
503
+ * }
504
+ * ```
505
+ *
506
+ * @example
507
+ * ```ts
508
+ * // Allow a separate trusted origin (e.g. a marketing site posting to an app domain).
509
+ * // The list replaces the default, so include the request's own origin if you still want it allowed.
510
+ * verifyRequestOrigin(request, {
511
+ * allowedOrigins: ['https://app.example.com', 'https://www.example.com'],
512
+ * })
513
+ * ```
514
+ */
515
+ declare const verifyRequestOrigin: (request: Request, options?: VerifyRequestOriginOptions) => void;
516
+
446
517
  /**
447
518
  * The admin namespace for privileged user management operations.
448
519
  * All methods are server-only and require the operator token
@@ -546,4 +617,4 @@ declare const verifyEmailChange: (token: string) => Promise<User>;
546
617
  */
547
618
  declare const updateUser: (updates: UserUpdates) => Promise<User>;
548
619
 
549
- export { AUTH_EVENTS, type Admin, type AdminUserUpdates, type AppMetadata, type AuthCallback, AuthError, type AuthEvent, type AuthProvider, type CallbackResult, type CreateUserParams, type IdentityConfig, type ListUsersOptions, MissingIdentityError, type Settings, type SignupData, type User, type UserUpdates, acceptInvite, admin, confirmEmail, getIdentityConfig, getSettings, getUser, handleAuthCallback, hydrateSession, isAuthenticated, login, logout, oauthLogin, onAuthChange, recoverPassword, refreshSession, requestPasswordRecovery, signup, updateUser, verifyEmailChange };
620
+ export { AUTH_EVENTS, type Admin, type AdminUserUpdates, type AppMetadata, type AuthCallback, AuthError, type AuthEvent, type AuthProvider, type CallbackResult, type CreateUserParams, type IdentityConfig, type ListUsersOptions, MissingIdentityError, type Settings, type SignupData, type User, type UserUpdates, type VerifyRequestOriginOptions, acceptInvite, admin, confirmEmail, getIdentityConfig, getSettings, getUser, handleAuthCallback, hydrateSession, isAuthenticated, login, logout, oauthLogin, onAuthChange, recoverPassword, refreshSession, requestPasswordRecovery, signup, updateUser, verifyEmailChange, verifyRequestOrigin };
@@ -99,7 +99,7 @@ var NF_JWT_COOKIE = "nf_jwt";
99
99
  var NF_REFRESH_COOKIE = "nf_refresh";
100
100
  var getCookie = (name) => {
101
101
  if (typeof document === "undefined") return null;
102
- const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
102
+ const match = new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`).exec(document.cookie);
103
103
  if (!match) return null;
104
104
  try {
105
105
  return decodeURIComponent(match[1]);
@@ -181,13 +181,15 @@ var triggerNextjsDynamic = () => {
181
181
  var DEFAULT_TIMEOUT_MS = 5e3;
182
182
  var fetchWithTimeout = async (url, options = {}, timeoutMs = DEFAULT_TIMEOUT_MS) => {
183
183
  const controller = new AbortController();
184
- const timer = setTimeout(() => controller.abort(), timeoutMs);
184
+ const timer = setTimeout(() => {
185
+ controller.abort();
186
+ }, timeoutMs);
185
187
  try {
186
188
  return await fetch(url, { ...options, signal: controller.signal });
187
189
  } catch (error) {
188
190
  if (error instanceof Error && error.name === "AbortError") {
189
191
  const pathname = new URL(url).pathname;
190
- throw new AuthError(`Identity request to ${pathname} timed out after ${timeoutMs}ms`);
192
+ throw new AuthError(`Identity request to ${pathname} timed out after ${String(timeoutMs)}ms`);
191
193
  }
192
194
  throw error;
193
195
  } finally {
@@ -254,16 +256,18 @@ var startTokenRefresh = () => {
254
256
  const nowS = Math.floor(Date.now() / 1e3);
255
257
  const expiresAtS = typeof token.expires_at === "number" && token.expires_at > 1e12 ? Math.floor(token.expires_at / 1e3) : token.expires_at;
256
258
  const delayMs = Math.max(0, expiresAtS - nowS - REFRESH_MARGIN_S) * 1e3;
257
- refreshTimer = setTimeout(async () => {
258
- try {
259
- const freshJwt = await user.jwt(true);
260
- const freshDetails = user.tokenDetails();
261
- setBrowserAuthCookies(freshJwt, freshDetails?.refresh_token);
262
- emitAuthEvent(AUTH_EVENTS.TOKEN_REFRESH, toUser(user));
263
- startTokenRefresh();
264
- } catch {
265
- stopTokenRefresh();
266
- }
259
+ refreshTimer = setTimeout(() => {
260
+ void (async () => {
261
+ try {
262
+ const freshJwt = await user.jwt(true);
263
+ const freshDetails = user.tokenDetails();
264
+ setBrowserAuthCookies(freshJwt, freshDetails?.refresh_token);
265
+ emitAuthEvent(AUTH_EVENTS.TOKEN_REFRESH, toUser(user));
266
+ startTokenRefresh();
267
+ } catch {
268
+ stopTokenRefresh();
269
+ }
270
+ })();
267
271
  }, delayMs);
268
272
  };
269
273
  var stopTokenRefresh = () => {
@@ -329,7 +333,7 @@ var refreshSession = async () => {
329
333
  }
330
334
  return null;
331
335
  }
332
- throw new AuthError(errorBody.msg || `Token refresh failed (${res.status})`, res.status);
336
+ throw new AuthError(errorBody.msg ?? `Token refresh failed (${String(res.status)})`, res.status);
333
337
  }
334
338
  const data = await res.json();
335
339
  const cookies = globalThis.Netlify?.context?.cookies;
@@ -376,7 +380,10 @@ var login = async (email, password) => {
376
380
  }
377
381
  if (!res.ok) {
378
382
  const errorBody = await res.json().catch(() => ({}));
379
- throw new AuthError(errorBody.msg || errorBody.error_description || `Login failed (${res.status})`, res.status);
383
+ throw new AuthError(
384
+ errorBody.msg ?? errorBody.error_description ?? `Login failed (${String(res.status)})`,
385
+ res.status
386
+ );
380
387
  }
381
388
  const data = await res.json();
382
389
  const accessToken = data.access_token;
@@ -390,7 +397,7 @@ var login = async (email, password) => {
390
397
  }
391
398
  if (!userRes.ok) {
392
399
  const errorBody = await userRes.json().catch(() => ({}));
393
- throw new AuthError(errorBody.msg || `Failed to fetch user data (${userRes.status})`, userRes.status);
400
+ throw new AuthError(errorBody.msg ?? `Failed to fetch user data (${String(userRes.status)})`, userRes.status);
394
401
  }
395
402
  const userData = await userRes.json();
396
403
  const user = toUser(userData);
@@ -426,7 +433,7 @@ var signup = async (email, password, data) => {
426
433
  }
427
434
  if (!res.ok) {
428
435
  const errorBody = await res.json().catch(() => ({}));
429
- throw new AuthError(errorBody.msg || `Signup failed (${res.status})`, res.status);
436
+ throw new AuthError(errorBody.msg ?? `Signup failed (${String(res.status)})`, res.status);
430
437
  }
431
438
  const responseData = await res.json();
432
439
  const user = toUser(responseData);
@@ -580,7 +587,7 @@ var handleEmailChangeCallback = async (client, emailChangeToken) => {
580
587
  if (!emailChangeRes.ok) {
581
588
  const errorBody = await emailChangeRes.json().catch(() => ({}));
582
589
  throw new AuthError(
583
- errorBody.msg || `Email change verification failed (${emailChangeRes.status})`,
590
+ errorBody.msg ?? `Email change verification failed (${String(emailChangeRes.status)})`,
584
591
  emailChangeRes.status
585
592
  );
586
593
  }
@@ -642,7 +649,7 @@ var toRoles = (appMeta) => {
642
649
  var toUser = (userData) => {
643
650
  const userMeta = userData.user_metadata ?? {};
644
651
  const appMeta = userData.app_metadata ?? {};
645
- const name = userMeta.full_name || userMeta.name;
652
+ const name = userMeta.full_name ?? userMeta.name;
646
653
  const pictureUrl = userMeta.avatar_url;
647
654
  return {
648
655
  id: userData.id,
@@ -668,7 +675,7 @@ var toUser = (userData) => {
668
675
  var claimsToUser = (claims) => {
669
676
  const appMeta = claims.app_metadata ?? {};
670
677
  const userMeta = claims.user_metadata ?? {};
671
- const name = userMeta.full_name || userMeta.name;
678
+ const name = userMeta.full_name ?? userMeta.name;
672
679
  const pictureUrl = userMeta.avatar_url;
673
680
  return {
674
681
  id: claims.sub ?? "",
@@ -740,7 +747,7 @@ var getUser = async () => {
740
747
  }
741
748
  triggerNextjsDynamic();
742
749
  const identityContext = globalThis.netlifyIdentityContext;
743
- const serverJwt = identityContext?.token || getServerCookie(NF_JWT_COOKIE);
750
+ const serverJwt = identityContext?.token ?? getServerCookie(NF_JWT_COOKIE);
744
751
  if (serverJwt) {
745
752
  const identityUrl = resolveIdentityUrl();
746
753
  if (identityUrl) {
@@ -782,6 +789,18 @@ var getSettings = async () => {
782
789
  }
783
790
  };
784
791
 
792
+ // src/csrf.ts
793
+ var verifyRequestOrigin = (request, options) => {
794
+ const origin = request.headers.get("origin");
795
+ if (!origin) {
796
+ throw new AuthError("Cross-origin request refused: missing Origin header.", 403);
797
+ }
798
+ const allowed = options?.allowedOrigins ?? [new URL(request.url).origin];
799
+ if (!allowed.includes(origin)) {
800
+ throw new AuthError(`Cross-origin request refused: Origin ${origin} did not match an allowed origin.`, 403);
801
+ }
802
+ };
803
+
785
804
  // src/account.ts
786
805
  var resolveCurrentUser = async () => {
787
806
  const client = getClient();
@@ -857,7 +876,7 @@ var verifyEmailChange = async (token) => {
857
876
  });
858
877
  if (!res.ok) {
859
878
  const errorBody = await res.json().catch(() => ({}));
860
- throw new AuthError(errorBody.msg || `Email change verification failed (${res.status})`, res.status);
879
+ throw new AuthError(errorBody.msg ?? `Email change verification failed (${String(res.status)})`, res.status);
861
880
  }
862
881
  const userData = await res.json();
863
882
  const user = toUser(userData);
@@ -921,7 +940,10 @@ var adminFetch = async (path, options = {}) => {
921
940
  }
922
941
  if (!res.ok) {
923
942
  const errorBody = await res.json().catch(() => ({}));
924
- throw new AuthError(errorBody.msg || `Admin request failed (${res.status})`, res.status);
943
+ throw new AuthError(
944
+ errorBody.msg ?? `Admin request failed (${String(res.status)})`,
945
+ res.status
946
+ );
925
947
  }
926
948
  return res;
927
949
  };
@@ -1010,6 +1032,6 @@ export {
1010
1032
  requestPasswordRecovery,
1011
1033
  signup,
1012
1034
  updateUser,
1013
- verifyEmailChange
1035
+ verifyEmailChange,
1036
+ verifyRequestOrigin
1014
1037
  };
1015
- //# sourceMappingURL=index.js.map