@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/README.md +31 -15
- package/dist/index.cjs +93 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -47
- package/dist/index.d.ts +36 -47
- package/dist/index.js +93 -97
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
*
|
|
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
|
|
111
|
-
* request body
|
|
112
|
-
* `user_metadata
|
|
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
|
-
/**
|
|
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
|
|
430
|
+
* All methods are server-only and require the operator token
|
|
431
|
+
* (automatically available in Netlify Functions and Edge Functions).
|
|
428
432
|
*
|
|
429
|
-
*
|
|
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.
|
|
437
|
+
* Lists all users. Server-only.
|
|
435
438
|
*
|
|
436
|
-
*
|
|
439
|
+
* Calls GoTrue `GET /admin/users` with the operator token. Pagination
|
|
437
440
|
* options (`page`, `perPage`) are forwarded as query parameters.
|
|
438
441
|
*
|
|
439
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
451
|
-
* have an admin role.
|
|
448
|
+
* Calls GoTrue `GET /admin/users/:id` with the operator token.
|
|
452
449
|
*
|
|
453
|
-
* @throws {AuthError} If
|
|
454
|
-
* or
|
|
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
|
-
*
|
|
456
|
+
* Server-only.
|
|
460
457
|
*
|
|
461
|
-
* The optional `data` fields are
|
|
462
|
-
* request body
|
|
463
|
-
*
|
|
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
|
-
*
|
|
463
|
+
* Calls GoTrue `POST /admin/users` with the operator token.
|
|
466
464
|
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
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.
|
|
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
|
-
*
|
|
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,
|
|
483
|
-
*
|
|
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.
|
|
488
|
-
*
|
|
489
|
-
* **Server:** calls GoTrue `DELETE /admin/users/:id` with the operator token.
|
|
479
|
+
* Deletes a user by ID. Server-only.
|
|
490
480
|
*
|
|
491
|
-
*
|
|
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,
|
|
495
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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 {
|