@netlify/identity 0.3.1-alpha.6 → 0.4.2

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/dist/index.d.ts CHANGED
@@ -86,17 +86,17 @@ interface AdminUserUpdates {
86
86
  password?: string;
87
87
  /** The user's role (e.g., `'admin'`, `'editor'`). */
88
88
  role?: string;
89
+ /** The user's audience (rarely needed; defaults to the site's audience). */
90
+ aud?: string;
89
91
  /** Set to `true` to force-confirm the user's email without sending a confirmation email. */
90
92
  confirm?: boolean;
91
93
  /** Server-managed metadata. Only writable via admin operations. */
92
94
  app_metadata?: Record<string, unknown>;
93
95
  /** User-managed metadata (display name, avatar, preferences, etc.). */
94
96
  user_metadata?: Record<string, unknown>;
95
- [key: string]: unknown;
96
97
  }
97
98
  /**
98
- * Options for {@link admin.listUsers}. Only used on the server;
99
- * pagination is ignored in the browser (gotrue-js limitation).
99
+ * Pagination options for {@link admin.listUsers}.
100
100
  */
101
101
  interface ListUsersOptions {
102
102
  /** 1-based page number. */
@@ -107,9 +107,10 @@ interface ListUsersOptions {
107
107
  /**
108
108
  * Parameters for {@link admin.createUser}.
109
109
  *
110
- * The optional `data` fields are spread as top-level attributes in the GoTrue
111
- * request body (not nested under `user_metadata`). Use this to set `app_metadata`,
112
- * `user_metadata`, `role`, or other GoTrue user fields at creation time.
110
+ * The optional `data` fields are forwarded as top-level attributes in the GoTrue
111
+ * request body. Only these keys are accepted: `role`, `aud`, `app_metadata`,
112
+ * `user_metadata`. Any other keys are silently ignored. `data` cannot override
113
+ * `email`, `password`, or `confirm`.
113
114
  *
114
115
  * @example
115
116
  * ```ts
@@ -123,7 +124,7 @@ interface ListUsersOptions {
123
124
  interface CreateUserParams {
124
125
  email: string;
125
126
  password: string;
126
- /** Additional GoTrue user fields spread into the request body. */
127
+ /** GoTrue user fields: `role`, `aud`, `app_metadata`, `user_metadata`. Other keys are ignored. */
127
128
  data?: Record<string, unknown>;
128
129
  }
129
130
 
@@ -161,6 +162,8 @@ interface User {
161
162
  roles?: string[];
162
163
  /** The full `user_metadata` object. */
163
164
  metadata?: Record<string, unknown>;
165
+ /** The full `app_metadata` object. */
166
+ appMetadata?: Record<string, unknown>;
164
167
  /** The raw GoTrue user data, for accessing fields not mapped to this interface. */
165
168
  rawGoTrueData?: Record<string, unknown>;
166
169
  }
@@ -424,75 +427,61 @@ declare class MissingIdentityError extends Error {
424
427
 
425
428
  /**
426
429
  * The admin namespace for privileged user management operations.
427
- * All methods work in both server and browser contexts.
430
+ * All methods are server-only and require the operator token
431
+ * (automatically available in Netlify Functions and Edge Functions).
428
432
  *
429
- * **Server:** uses the operator token (automatically available in Netlify Functions).
430
- * **Browser:** requires a logged-in user with an admin role.
433
+ * Calling any admin method from a browser environment throws an `AuthError`.
431
434
  */
432
435
  interface Admin {
433
436
  /**
434
- * Lists all users. Works in both server and browser contexts.
437
+ * Lists all users. Server-only.
435
438
  *
436
- * **Server:** calls GoTrue `GET /admin/users` with the operator token. Pagination
439
+ * Calls GoTrue `GET /admin/users` with the operator token. Pagination
437
440
  * options (`page`, `perPage`) are forwarded as query parameters.
438
441
  *
439
- * **Browser:** calls gotrue-js `user.admin.listUsers()`. The logged-in user must
440
- * have an admin role. Pagination options are ignored (gotrue-js does not support them).
441
- *
442
- * @throws {AuthError} If the operator token is missing (server) or no admin user is logged in (browser).
442
+ * @throws {AuthError} If called from a browser, or if the operator token is missing.
443
443
  */
444
444
  listUsers: (options?: ListUsersOptions) => Promise<User[]>;
445
445
  /**
446
- * Gets a single user by ID. Works in both server and browser contexts.
447
- *
448
- * **Server:** calls GoTrue `GET /admin/users/:id` with the operator token.
446
+ * Gets a single user by ID. Server-only.
449
447
  *
450
- * **Browser:** calls gotrue-js `user.admin.getUser()`. The logged-in user must
451
- * have an admin role.
448
+ * Calls GoTrue `GET /admin/users/:id` with the operator token.
452
449
  *
453
- * @throws {AuthError} If the user is not found, the operator token is missing (server),
454
- * or no admin user is logged in (browser).
450
+ * @throws {AuthError} If called from a browser, the user is not found,
451
+ * or the operator token is missing.
455
452
  */
456
453
  getUser: (userId: string) => Promise<User>;
457
454
  /**
458
455
  * Creates a new user. The user is auto-confirmed (no confirmation email is sent).
459
- * Works in both server and browser contexts.
456
+ * Server-only.
460
457
  *
461
- * The optional `data` fields are spread as **top-level attributes** in the GoTrue
462
- * request body (not nested under `user_metadata`). Use this to set `app_metadata`,
463
- * `user_metadata`, `role`, or any other GoTrue user field at creation time.
458
+ * The optional `data` fields are forwarded as top-level attributes in the GoTrue
459
+ * request body. Accepted fields: `role`, `aud`, `app_metadata`, `user_metadata`.
460
+ * Any other keys in `data` are silently ignored. `data` cannot override `email`,
461
+ * `password`, or `confirm`.
464
462
  *
465
- * **Server:** calls GoTrue `POST /admin/users` with the operator token.
463
+ * Calls GoTrue `POST /admin/users` with the operator token.
466
464
  *
467
- * **Browser:** calls gotrue-js `user.admin.createUser()`. The logged-in user must
468
- * have an admin role.
469
- *
470
- * @throws {AuthError} If the email already exists, the operator token is missing (server),
471
- * or no admin user is logged in (browser).
465
+ * @throws {AuthError} If called from a browser, the email already exists,
466
+ * or the operator token is missing.
472
467
  */
473
468
  createUser: (params: CreateUserParams) => Promise<User>;
474
469
  /**
475
- * Updates an existing user by ID. Works in both server and browser contexts.
476
- *
477
- * **Server:** calls GoTrue `PUT /admin/users/:id` with the operator token.
470
+ * Updates an existing user by ID. Server-only.
478
471
  *
479
- * **Browser:** calls gotrue-js `user.admin.updateUser()`. The logged-in user must
480
- * have an admin role.
472
+ * Calls GoTrue `PUT /admin/users/:id` with the operator token.
481
473
  *
482
- * @throws {AuthError} If the user is not found, the update fails, the operator token
483
- * is missing (server), or no admin user is logged in (browser).
474
+ * @throws {AuthError} If called from a browser, the user is not found,
475
+ * the update fails, or the operator token is missing.
484
476
  */
485
477
  updateUser: (userId: string, attributes: AdminUserUpdates) => Promise<User>;
486
478
  /**
487
- * Deletes a user by ID. Works in both server and browser contexts.
488
- *
489
- * **Server:** calls GoTrue `DELETE /admin/users/:id` with the operator token.
479
+ * Deletes a user by ID. Server-only.
490
480
  *
491
- * **Browser:** calls gotrue-js `user.admin.deleteUser()`. The logged-in user must
492
- * have an admin role.
481
+ * Calls GoTrue `DELETE /admin/users/:id` with the operator token.
493
482
  *
494
- * @throws {AuthError} If the user is not found, the deletion fails, the operator token
495
- * is missing (server), or no admin user is logged in (browser).
483
+ * @throws {AuthError} If called from a browser, the user is not found,
484
+ * the deletion fails, or the operator token is missing.
496
485
  */
497
486
  deleteUser: (userId: string) => Promise<void>;
498
487
  }
package/dist/index.js CHANGED
@@ -177,6 +177,24 @@ var triggerNextjsDynamic = () => {
177
177
  }
178
178
  };
179
179
 
180
+ // src/fetch.ts
181
+ var DEFAULT_TIMEOUT_MS = 5e3;
182
+ var fetchWithTimeout = async (url, options = {}, timeoutMs = DEFAULT_TIMEOUT_MS) => {
183
+ const controller = new AbortController();
184
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
185
+ try {
186
+ return await fetch(url, { ...options, signal: controller.signal });
187
+ } catch (error) {
188
+ if (error instanceof Error && error.name === "AbortError") {
189
+ const pathname = new URL(url).pathname;
190
+ throw new AuthError(`Identity request to ${pathname} timed out after ${timeoutMs}ms`);
191
+ }
192
+ throw error;
193
+ } finally {
194
+ clearTimeout(timer);
195
+ }
196
+ };
197
+
180
198
  // src/events.ts
181
199
  var AUTH_EVENTS = {
182
200
  LOGIN: "login",
@@ -294,7 +312,7 @@ var refreshSession = async () => {
294
312
  }
295
313
  let res;
296
314
  try {
297
- res = await fetch(`${identityUrl}/token`, {
315
+ res = await fetchWithTimeout(`${identityUrl}/token`, {
298
316
  method: "POST",
299
317
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
300
318
  body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken }).toString()
@@ -348,7 +366,7 @@ var login = async (email, password) => {
348
366
  });
349
367
  let res;
350
368
  try {
351
- res = await fetch(`${identityUrl}/token`, {
369
+ res = await fetchWithTimeout(`${identityUrl}/token`, {
352
370
  method: "POST",
353
371
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
354
372
  body: body.toString()
@@ -364,7 +382,7 @@ var login = async (email, password) => {
364
382
  const accessToken = data.access_token;
365
383
  let userRes;
366
384
  try {
367
- userRes = await fetch(`${identityUrl}/user`, {
385
+ userRes = await fetchWithTimeout(`${identityUrl}/user`, {
368
386
  headers: { Authorization: `Bearer ${accessToken}` }
369
387
  });
370
388
  } catch (error) {
@@ -398,7 +416,7 @@ var signup = async (email, password, data) => {
398
416
  const cookies = getCookies();
399
417
  let res;
400
418
  try {
401
- res = await fetch(`${identityUrl}/signup`, {
419
+ res = await fetchWithTimeout(`${identityUrl}/signup`, {
402
420
  method: "POST",
403
421
  headers: { "Content-Type": "application/json" },
404
422
  body: JSON.stringify({ email, password, data })
@@ -445,7 +463,7 @@ var logout = async () => {
445
463
  const jwt = cookies.get(NF_JWT_COOKIE);
446
464
  if (jwt) {
447
465
  try {
448
- await fetch(`${identityUrl}/logout`, {
466
+ await fetchWithTimeout(`${identityUrl}/logout`, {
449
467
  method: "POST",
450
468
  headers: { Authorization: `Bearer ${jwt}` }
451
469
  });
@@ -625,6 +643,7 @@ var toUser = (userData) => {
625
643
  const appMeta = userData.app_metadata ?? {};
626
644
  const name = userMeta.full_name || userMeta.name;
627
645
  const pictureUrl = userMeta.avatar_url;
646
+ const { token: _token, ...safeUserData } = userData;
628
647
  return {
629
648
  id: userData.id,
630
649
  email: userData.email,
@@ -636,7 +655,8 @@ var toUser = (userData) => {
636
655
  pictureUrl: typeof pictureUrl === "string" ? pictureUrl : void 0,
637
656
  roles: toRoles(appMeta),
638
657
  metadata: userMeta,
639
- rawGoTrueData: { ...userData }
658
+ appMetadata: appMeta,
659
+ rawGoTrueData: { ...safeUserData }
640
660
  };
641
661
  };
642
662
  var claimsToUser = (claims) => {
@@ -651,7 +671,8 @@ var claimsToUser = (claims) => {
651
671
  name: typeof name === "string" ? name : void 0,
652
672
  pictureUrl: typeof pictureUrl === "string" ? pictureUrl : void 0,
653
673
  roles: toRoles(appMeta),
654
- metadata: userMeta
674
+ metadata: userMeta,
675
+ appMetadata: appMeta
655
676
  };
656
677
  };
657
678
  var decodeJwtPayload = (token) => {
@@ -666,7 +687,7 @@ var decodeJwtPayload = (token) => {
666
687
  };
667
688
  var fetchFullUser = async (identityUrl, jwt) => {
668
689
  try {
669
- const res = await fetch(`${identityUrl}/user`, {
690
+ const res = await fetchWithTimeout(`${identityUrl}/user`, {
670
691
  headers: { Authorization: `Bearer ${jwt}` }
671
692
  });
672
693
  if (!res.ok) return null;
@@ -855,6 +876,19 @@ var updateUser = async (updates) => {
855
876
  };
856
877
 
857
878
  // src/admin.ts
879
+ var SERVER_ONLY_MESSAGE = "Admin operations are server-only. Call admin methods from a Netlify Function or Edge Function, not from browser code.";
880
+ var sanitizeUserId = (userId) => {
881
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
882
+ if (!uuidRegex.test(userId)) {
883
+ throw new AuthError("User ID is not a valid UUID");
884
+ }
885
+ return encodeURIComponent(userId);
886
+ };
887
+ var assertServer = () => {
888
+ if (isBrowser()) {
889
+ throw new AuthError(SERVER_ONLY_MESSAGE);
890
+ }
891
+ };
858
892
  var getAdminAuth = () => {
859
893
  const ctx = getIdentityContext();
860
894
  if (!ctx?.url) {
@@ -869,7 +903,7 @@ var adminFetch = async (path, options = {}) => {
869
903
  const { url, token } = getAdminAuth();
870
904
  let res;
871
905
  try {
872
- res = await fetch(`${url}${path}`, {
906
+ res = await fetchWithTimeout(`${url}${path}`, {
873
907
  ...options,
874
908
  headers: {
875
909
  ...options.headers,
@@ -886,105 +920,67 @@ var adminFetch = async (path, options = {}) => {
886
920
  }
887
921
  return res;
888
922
  };
889
- var getAdminUser = () => {
890
- const client = getClient();
891
- const user = client.currentUser();
892
- if (!user) {
893
- throw new AuthError("Admin operations require a logged-in user with admin role");
894
- }
895
- return user;
896
- };
897
923
  var listUsers = async (options) => {
898
- if (!isBrowser()) {
899
- const params = new URLSearchParams();
900
- if (options?.page != null) params.set("page", String(options.page));
901
- if (options?.perPage != null) params.set("per_page", String(options.perPage));
902
- const query = params.toString();
903
- const path = `/admin/users${query ? `?${query}` : ""}`;
904
- const res = await adminFetch(path);
905
- const body = await res.json();
906
- return body.users.map(toUser);
907
- }
908
- try {
909
- const user = getAdminUser();
910
- const users = await user.admin.listUsers("");
911
- return users.map(toUser);
912
- } catch (error) {
913
- if (error instanceof AuthError) throw error;
914
- throw new AuthError(error.message, void 0, { cause: error });
915
- }
924
+ assertServer();
925
+ const params = new URLSearchParams();
926
+ if (options?.page != null) params.set("page", String(options.page));
927
+ if (options?.perPage != null) params.set("per_page", String(options.perPage));
928
+ const query = params.toString();
929
+ const path = `/admin/users${query ? `?${query}` : ""}`;
930
+ const res = await adminFetch(path);
931
+ const body = await res.json();
932
+ return body.users.map(toUser);
916
933
  };
917
934
  var getUser2 = async (userId) => {
918
- if (!isBrowser()) {
919
- const res = await adminFetch(`/admin/users/${userId}`);
920
- const userData = await res.json();
921
- return toUser(userData);
922
- }
923
- try {
924
- const user = getAdminUser();
925
- const userData = await user.admin.getUser({ id: userId });
926
- return toUser(userData);
927
- } catch (error) {
928
- if (error instanceof AuthError) throw error;
929
- throw new AuthError(error.message, void 0, { cause: error });
930
- }
935
+ assertServer();
936
+ const sanitizedUserId = sanitizeUserId(userId);
937
+ const res = await adminFetch(`/admin/users/${sanitizedUserId}`);
938
+ const userData = await res.json();
939
+ return toUser(userData);
931
940
  };
932
941
  var createUser = async (params) => {
933
- if (!isBrowser()) {
934
- const res = await adminFetch("/admin/users", {
935
- method: "POST",
936
- body: JSON.stringify({
937
- email: params.email,
938
- password: params.password,
939
- ...params.data,
940
- confirm: true
941
- })
942
- });
943
- const userData = await res.json();
944
- return toUser(userData);
945
- }
946
- try {
947
- const user = getAdminUser();
948
- const userData = await user.admin.createUser(params.email, params.password, {
949
- ...params.data,
950
- confirm: true
951
- });
952
- return toUser(userData);
953
- } catch (error) {
954
- if (error instanceof AuthError) throw error;
955
- throw new AuthError(error.message, void 0, { cause: error });
942
+ assertServer();
943
+ const body = {
944
+ email: params.email,
945
+ password: params.password,
946
+ confirm: true
947
+ };
948
+ if (params.data) {
949
+ const allowedKeys = ["role", "aud", "app_metadata", "user_metadata"];
950
+ for (const key of allowedKeys) {
951
+ if (key in params.data) {
952
+ body[key] = params.data[key];
953
+ }
954
+ }
956
955
  }
956
+ const res = await adminFetch("/admin/users", {
957
+ method: "POST",
958
+ body: JSON.stringify(body)
959
+ });
960
+ const userData = await res.json();
961
+ return toUser(userData);
957
962
  };
958
963
  var updateUser2 = async (userId, attributes) => {
959
- if (!isBrowser()) {
960
- const res = await adminFetch(`/admin/users/${userId}`, {
961
- method: "PUT",
962
- body: JSON.stringify(attributes)
963
- });
964
- const userData = await res.json();
965
- return toUser(userData);
966
- }
967
- try {
968
- const user = getAdminUser();
969
- const userData = await user.admin.updateUser({ id: userId }, attributes);
970
- return toUser(userData);
971
- } catch (error) {
972
- if (error instanceof AuthError) throw error;
973
- throw new AuthError(error.message, void 0, { cause: error });
964
+ assertServer();
965
+ const sanitizedUserId = sanitizeUserId(userId);
966
+ const body = {};
967
+ const allowedKeys = ["email", "password", "role", "aud", "confirm", "app_metadata", "user_metadata"];
968
+ for (const key of allowedKeys) {
969
+ if (key in attributes) {
970
+ body[key] = attributes[key];
971
+ }
974
972
  }
973
+ const res = await adminFetch(`/admin/users/${sanitizedUserId}`, {
974
+ method: "PUT",
975
+ body: JSON.stringify(body)
976
+ });
977
+ const userData = await res.json();
978
+ return toUser(userData);
975
979
  };
976
980
  var deleteUser = async (userId) => {
977
- if (!isBrowser()) {
978
- await adminFetch(`/admin/users/${userId}`, { method: "DELETE" });
979
- return;
980
- }
981
- try {
982
- const user = getAdminUser();
983
- await user.admin.deleteUser({ id: userId });
984
- } catch (error) {
985
- if (error instanceof AuthError) throw error;
986
- throw new AuthError(error.message, void 0, { cause: error });
987
- }
981
+ assertServer();
982
+ const sanitizedUserId = sanitizeUserId(userId);
983
+ await adminFetch(`/admin/users/${sanitizedUserId}`, { method: "DELETE" });
988
984
  };
989
985
  var admin = { listUsers, getUser: getUser2, createUser, updateUser: updateUser2, deleteUser };
990
986
  export {