@netlify/identity 0.4.1 → 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
 
@@ -426,75 +427,61 @@ declare class MissingIdentityError extends Error {
426
427
 
427
428
  /**
428
429
  * The admin namespace for privileged user management operations.
429
- * 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).
430
432
  *
431
- * **Server:** uses the operator token (automatically available in Netlify Functions).
432
- * **Browser:** requires a logged-in user with an admin role.
433
+ * Calling any admin method from a browser environment throws an `AuthError`.
433
434
  */
434
435
  interface Admin {
435
436
  /**
436
- * Lists all users. Works in both server and browser contexts.
437
+ * Lists all users. Server-only.
437
438
  *
438
- * **Server:** calls GoTrue `GET /admin/users` with the operator token. Pagination
439
+ * Calls GoTrue `GET /admin/users` with the operator token. Pagination
439
440
  * options (`page`, `perPage`) are forwarded as query parameters.
440
441
  *
441
- * **Browser:** calls gotrue-js `user.admin.listUsers()`. The logged-in user must
442
- * have an admin role. Pagination options are ignored (gotrue-js does not support them).
443
- *
444
- * @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.
445
443
  */
446
444
  listUsers: (options?: ListUsersOptions) => Promise<User[]>;
447
445
  /**
448
- * Gets a single user by ID. Works in both server and browser contexts.
449
- *
450
- * **Server:** calls GoTrue `GET /admin/users/:id` with the operator token.
446
+ * Gets a single user by ID. Server-only.
451
447
  *
452
- * **Browser:** calls gotrue-js `user.admin.getUser()`. The logged-in user must
453
- * have an admin role.
448
+ * Calls GoTrue `GET /admin/users/:id` with the operator token.
454
449
  *
455
- * @throws {AuthError} If the user is not found, the operator token is missing (server),
456
- * 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.
457
452
  */
458
453
  getUser: (userId: string) => Promise<User>;
459
454
  /**
460
455
  * Creates a new user. The user is auto-confirmed (no confirmation email is sent).
461
- * Works in both server and browser contexts.
456
+ * Server-only.
462
457
  *
463
- * The optional `data` fields are spread as **top-level attributes** in the GoTrue
464
- * request body (not nested under `user_metadata`). Use this to set `app_metadata`,
465
- * `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`.
466
462
  *
467
- * **Server:** calls GoTrue `POST /admin/users` with the operator token.
463
+ * Calls GoTrue `POST /admin/users` with the operator token.
468
464
  *
469
- * **Browser:** calls gotrue-js `user.admin.createUser()`. The logged-in user must
470
- * have an admin role.
471
- *
472
- * @throws {AuthError} If the email already exists, the operator token is missing (server),
473
- * 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.
474
467
  */
475
468
  createUser: (params: CreateUserParams) => Promise<User>;
476
469
  /**
477
- * Updates an existing user by ID. Works in both server and browser contexts.
478
- *
479
- * **Server:** calls GoTrue `PUT /admin/users/:id` with the operator token.
470
+ * Updates an existing user by ID. Server-only.
480
471
  *
481
- * **Browser:** calls gotrue-js `user.admin.updateUser()`. The logged-in user must
482
- * have an admin role.
472
+ * Calls GoTrue `PUT /admin/users/:id` with the operator token.
483
473
  *
484
- * @throws {AuthError} If the user is not found, the update fails, the operator token
485
- * 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.
486
476
  */
487
477
  updateUser: (userId: string, attributes: AdminUserUpdates) => Promise<User>;
488
478
  /**
489
- * Deletes a user by ID. Works in both server and browser contexts.
490
- *
491
- * **Server:** calls GoTrue `DELETE /admin/users/:id` with the operator token.
479
+ * Deletes a user by ID. Server-only.
492
480
  *
493
- * **Browser:** calls gotrue-js `user.admin.deleteUser()`. The logged-in user must
494
- * have an admin role.
481
+ * Calls GoTrue `DELETE /admin/users/:id` with the operator token.
495
482
  *
496
- * @throws {AuthError} If the user is not found, the deletion fails, the operator token
497
- * 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.
498
485
  */
499
486
  deleteUser: (userId: string) => Promise<void>;
500
487
  }
package/dist/index.js CHANGED
@@ -876,6 +876,19 @@ var updateUser = async (updates) => {
876
876
  };
877
877
 
878
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
+ };
879
892
  var getAdminAuth = () => {
880
893
  const ctx = getIdentityContext();
881
894
  if (!ctx?.url) {
@@ -907,105 +920,67 @@ var adminFetch = async (path, options = {}) => {
907
920
  }
908
921
  return res;
909
922
  };
910
- var getAdminUser = () => {
911
- const client = getClient();
912
- const user = client.currentUser();
913
- if (!user) {
914
- throw new AuthError("Admin operations require a logged-in user with admin role");
915
- }
916
- return user;
917
- };
918
923
  var listUsers = async (options) => {
919
- if (!isBrowser()) {
920
- const params = new URLSearchParams();
921
- if (options?.page != null) params.set("page", String(options.page));
922
- if (options?.perPage != null) params.set("per_page", String(options.perPage));
923
- const query = params.toString();
924
- const path = `/admin/users${query ? `?${query}` : ""}`;
925
- const res = await adminFetch(path);
926
- const body = await res.json();
927
- return body.users.map(toUser);
928
- }
929
- try {
930
- const user = getAdminUser();
931
- const users = await user.admin.listUsers("");
932
- return users.map(toUser);
933
- } catch (error) {
934
- if (error instanceof AuthError) throw error;
935
- throw new AuthError(error.message, void 0, { cause: error });
936
- }
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);
937
933
  };
938
934
  var getUser2 = async (userId) => {
939
- if (!isBrowser()) {
940
- const res = await adminFetch(`/admin/users/${userId}`);
941
- const userData = await res.json();
942
- return toUser(userData);
943
- }
944
- try {
945
- const user = getAdminUser();
946
- const userData = await user.admin.getUser({ id: userId });
947
- return toUser(userData);
948
- } catch (error) {
949
- if (error instanceof AuthError) throw error;
950
- throw new AuthError(error.message, void 0, { cause: error });
951
- }
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);
952
940
  };
953
941
  var createUser = async (params) => {
954
- if (!isBrowser()) {
955
- const res = await adminFetch("/admin/users", {
956
- method: "POST",
957
- body: JSON.stringify({
958
- email: params.email,
959
- password: params.password,
960
- ...params.data,
961
- confirm: true
962
- })
963
- });
964
- const userData = await res.json();
965
- return toUser(userData);
966
- }
967
- try {
968
- const user = getAdminUser();
969
- const userData = await user.admin.createUser(params.email, params.password, {
970
- ...params.data,
971
- confirm: true
972
- });
973
- return toUser(userData);
974
- } catch (error) {
975
- if (error instanceof AuthError) throw error;
976
- 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
+ }
977
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);
978
962
  };
979
963
  var updateUser2 = async (userId, attributes) => {
980
- if (!isBrowser()) {
981
- const res = await adminFetch(`/admin/users/${userId}`, {
982
- method: "PUT",
983
- body: JSON.stringify(attributes)
984
- });
985
- const userData = await res.json();
986
- return toUser(userData);
987
- }
988
- try {
989
- const user = getAdminUser();
990
- const userData = await user.admin.updateUser({ id: userId }, attributes);
991
- return toUser(userData);
992
- } catch (error) {
993
- if (error instanceof AuthError) throw error;
994
- 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
+ }
995
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);
996
979
  };
997
980
  var deleteUser = async (userId) => {
998
- if (!isBrowser()) {
999
- await adminFetch(`/admin/users/${userId}`, { method: "DELETE" });
1000
- return;
1001
- }
1002
- try {
1003
- const user = getAdminUser();
1004
- await user.admin.deleteUser({ id: userId });
1005
- } catch (error) {
1006
- if (error instanceof AuthError) throw error;
1007
- throw new AuthError(error.message, void 0, { cause: error });
1008
- }
981
+ assertServer();
982
+ const sanitizedUserId = sanitizeUserId(userId);
983
+ await adminFetch(`/admin/users/${sanitizedUserId}`, { method: "DELETE" });
1009
984
  };
1010
985
  var admin = { listUsers, getUser: getUser2, createUser, updateUser: updateUser2, deleteUser };
1011
986
  export {