@netlify/identity 0.1.1-alpha.2 → 0.1.1-alpha.20

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.cjs CHANGED
@@ -43,6 +43,7 @@ __export(index_exports, {
43
43
  logout: () => logout,
44
44
  oauthLogin: () => oauthLogin,
45
45
  onAuthChange: () => onAuthChange,
46
+ recoverPassword: () => recoverPassword,
46
47
  requestPasswordRecovery: () => requestPasswordRecovery,
47
48
  signup: () => signup,
48
49
  updateUser: () => updateUser,
@@ -75,6 +76,7 @@ var MissingIdentityError = class extends Error {
75
76
  };
76
77
 
77
78
  // src/environment.ts
79
+ var IDENTITY_PATH = "/.netlify/identity";
78
80
  var goTrueClient = null;
79
81
  var cachedApiUrl;
80
82
  var warnedMissingUrl = false;
@@ -82,13 +84,15 @@ var isBrowser = () => typeof window !== "undefined" && typeof window.location !=
82
84
  var discoverApiUrl = () => {
83
85
  if (cachedApiUrl !== void 0) return cachedApiUrl;
84
86
  if (isBrowser()) {
85
- cachedApiUrl = `${window.location.origin}/.netlify/identity`;
87
+ cachedApiUrl = `${window.location.origin}${IDENTITY_PATH}`;
86
88
  } else {
87
89
  const identityContext = getIdentityContext();
88
90
  if (identityContext?.url) {
89
91
  cachedApiUrl = identityContext.url;
90
92
  } else if (globalThis.Netlify?.context?.url) {
91
- cachedApiUrl = new URL("/.netlify/identity", globalThis.Netlify.context.url).href;
93
+ cachedApiUrl = new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href;
94
+ } else if (process.env.URL) {
95
+ cachedApiUrl = new URL(IDENTITY_PATH, process.env.URL).href;
92
96
  }
93
97
  }
94
98
  return cachedApiUrl ?? null;
@@ -105,7 +109,7 @@ var getGoTrueClient = () => {
105
109
  }
106
110
  return null;
107
111
  }
108
- goTrueClient = new import_gotrue_js.default({ APIUrl: apiUrl, setCookie: isBrowser() });
112
+ goTrueClient = new import_gotrue_js.default({ APIUrl: apiUrl, setCookie: false });
109
113
  return goTrueClient;
110
114
  };
111
115
  var getClient = () => {
@@ -115,18 +119,97 @@ var getClient = () => {
115
119
  };
116
120
  var getIdentityContext = () => {
117
121
  const identityContext = globalThis.netlifyIdentityContext;
118
- if (identityContext?.url && typeof identityContext.url === "string") {
122
+ if (identityContext?.url) {
119
123
  return {
120
124
  url: identityContext.url,
121
- token: typeof identityContext.token === "string" ? identityContext.token : void 0
125
+ token: identityContext.token
122
126
  };
123
127
  }
124
128
  if (globalThis.Netlify?.context?.url) {
125
- return { url: new URL("/.netlify/identity", globalThis.Netlify.context.url).href };
129
+ return { url: new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href };
130
+ }
131
+ const siteUrl = process.env.URL;
132
+ if (siteUrl) {
133
+ return { url: new URL(IDENTITY_PATH, siteUrl).href };
126
134
  }
127
135
  return null;
128
136
  };
129
137
 
138
+ // src/cookies.ts
139
+ var NF_JWT_COOKIE = "nf_jwt";
140
+ var NF_REFRESH_COOKIE = "nf_refresh";
141
+ var getCookie = (name) => {
142
+ const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
143
+ return match ? decodeURIComponent(match[1]) : null;
144
+ };
145
+ var setAuthCookies = (cookies, accessToken, refreshToken) => {
146
+ cookies.set({
147
+ name: NF_JWT_COOKIE,
148
+ value: accessToken,
149
+ httpOnly: false,
150
+ secure: true,
151
+ path: "/",
152
+ sameSite: "Lax"
153
+ });
154
+ if (refreshToken) {
155
+ cookies.set({
156
+ name: NF_REFRESH_COOKIE,
157
+ value: refreshToken,
158
+ httpOnly: false,
159
+ secure: true,
160
+ path: "/",
161
+ sameSite: "Lax"
162
+ });
163
+ }
164
+ };
165
+ var deleteAuthCookies = (cookies) => {
166
+ cookies.delete(NF_JWT_COOKIE);
167
+ cookies.delete(NF_REFRESH_COOKIE);
168
+ };
169
+ var setBrowserAuthCookies = (accessToken, refreshToken) => {
170
+ document.cookie = `${NF_JWT_COOKIE}=${encodeURIComponent(accessToken)}; path=/; secure; samesite=lax`;
171
+ if (refreshToken) {
172
+ document.cookie = `${NF_REFRESH_COOKIE}=${encodeURIComponent(refreshToken)}; path=/; secure; samesite=lax`;
173
+ }
174
+ };
175
+ var deleteBrowserAuthCookies = () => {
176
+ document.cookie = `${NF_JWT_COOKIE}=; path=/; secure; samesite=lax; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
177
+ document.cookie = `${NF_REFRESH_COOKIE}=; path=/; secure; samesite=lax; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
178
+ };
179
+ var getServerCookie = (name) => {
180
+ const cookies = globalThis.Netlify?.context?.cookies;
181
+ if (!cookies || typeof cookies.get !== "function") return null;
182
+ return cookies.get(name) ?? null;
183
+ };
184
+
185
+ // src/nextjs.ts
186
+ var nextHeadersFn;
187
+ var triggerNextjsDynamic = () => {
188
+ if (nextHeadersFn === null) return;
189
+ if (nextHeadersFn === void 0) {
190
+ try {
191
+ if (typeof require === "undefined") {
192
+ nextHeadersFn = null;
193
+ return;
194
+ }
195
+ const mod = require("next/headers");
196
+ nextHeadersFn = mod.headers;
197
+ } catch {
198
+ nextHeadersFn = null;
199
+ return;
200
+ }
201
+ }
202
+ const fn = nextHeadersFn;
203
+ if (!fn) return;
204
+ try {
205
+ fn();
206
+ } catch (e) {
207
+ if (e instanceof Error && ("digest" in e || /bail\s*out.*prerende/i.test(e.message))) {
208
+ throw e;
209
+ }
210
+ }
211
+ };
212
+
130
213
  // src/user.ts
131
214
  var toAuthProvider = (value) => typeof value === "string" && AUTH_PROVIDERS.includes(value) ? value : void 0;
132
215
  var toUser = (userData) => {
@@ -152,32 +235,89 @@ var claimsToUser = (claims) => {
152
235
  const userMeta = claims.user_metadata ?? {};
153
236
  const name = userMeta.full_name || userMeta.name;
154
237
  return {
155
- id: typeof claims.sub === "string" ? claims.sub : "",
156
- email: typeof claims.email === "string" ? claims.email : void 0,
238
+ id: claims.sub ?? "",
239
+ email: claims.email,
157
240
  provider: toAuthProvider(appMeta.provider),
158
241
  name: typeof name === "string" ? name : void 0,
159
242
  metadata: userMeta
160
243
  };
161
244
  };
245
+ var hydrating = false;
246
+ var backgroundHydrate = (accessToken) => {
247
+ if (hydrating) return;
248
+ hydrating = true;
249
+ const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
250
+ setTimeout(() => {
251
+ try {
252
+ const client = getClient();
253
+ client.createUser(
254
+ {
255
+ access_token: accessToken,
256
+ token_type: "bearer",
257
+ expires_in: 3600,
258
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
259
+ refresh_token: refreshToken
260
+ },
261
+ true
262
+ ).catch(() => {
263
+ }).finally(() => {
264
+ hydrating = false;
265
+ });
266
+ } catch {
267
+ hydrating = false;
268
+ }
269
+ }, 0);
270
+ };
271
+ var decodeJwtPayload = (token) => {
272
+ try {
273
+ const parts = token.split(".");
274
+ if (parts.length !== 3) return null;
275
+ const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
276
+ return JSON.parse(payload);
277
+ } catch {
278
+ return null;
279
+ }
280
+ };
162
281
  var getUser = () => {
163
282
  if (isBrowser()) {
164
283
  const client = getGoTrueClient();
165
284
  const currentUser = client?.currentUser() ?? null;
166
- if (!currentUser) return null;
167
- return toUser(currentUser);
285
+ if (currentUser) {
286
+ const jwt2 = getCookie(NF_JWT_COOKIE);
287
+ if (!jwt2) {
288
+ try {
289
+ currentUser.clearSession();
290
+ } catch {
291
+ }
292
+ return null;
293
+ }
294
+ return toUser(currentUser);
295
+ }
296
+ const jwt = getCookie(NF_JWT_COOKIE);
297
+ if (!jwt) return null;
298
+ const claims = decodeJwtPayload(jwt);
299
+ if (!claims) return null;
300
+ backgroundHydrate(jwt);
301
+ return claimsToUser(claims);
168
302
  }
303
+ triggerNextjsDynamic();
169
304
  const identityContext = globalThis.netlifyIdentityContext;
170
- if (!identityContext) return null;
171
- const claims = identityContext.user ?? (identityContext.sub ? identityContext : null);
172
- if (!claims) return null;
173
- return claimsToUser(claims);
305
+ if (identityContext?.user) {
306
+ return claimsToUser(identityContext.user);
307
+ }
308
+ const serverJwt = getServerCookie(NF_JWT_COOKIE);
309
+ if (serverJwt) {
310
+ const claims = decodeJwtPayload(serverJwt);
311
+ if (claims) return claimsToUser(claims);
312
+ }
313
+ return null;
174
314
  };
175
315
  var isAuthenticated = () => getUser() !== null;
176
316
 
177
317
  // src/config.ts
178
318
  var getIdentityConfig = () => {
179
319
  if (isBrowser()) {
180
- return { url: `${window.location.origin}/.netlify/identity` };
320
+ return { url: `${window.location.origin}${IDENTITY_PATH}` };
181
321
  }
182
322
  return getIdentityContext();
183
323
  };
@@ -205,6 +345,20 @@ var getSettings = async () => {
205
345
  };
206
346
 
207
347
  // src/auth.ts
348
+ var getCookies = () => {
349
+ const cookies = globalThis.Netlify?.context?.cookies;
350
+ if (!cookies) {
351
+ throw new AuthError("Server-side auth requires Netlify Functions runtime");
352
+ }
353
+ return cookies;
354
+ };
355
+ var getServerIdentityUrl = () => {
356
+ const ctx = getIdentityContext();
357
+ if (!ctx?.url) {
358
+ throw new AuthError("Could not determine the Identity endpoint URL on the server");
359
+ }
360
+ return ctx.url;
361
+ };
208
362
  var persistSession = true;
209
363
  var listeners = /* @__PURE__ */ new Set();
210
364
  var emitAuthEvent = (event, user) => {
@@ -239,9 +393,58 @@ var onAuthChange = (callback) => {
239
393
  };
240
394
  };
241
395
  var login = async (email, password) => {
396
+ if (!isBrowser()) {
397
+ const identityUrl = getServerIdentityUrl();
398
+ const cookies = getCookies();
399
+ const body = new URLSearchParams({
400
+ grant_type: "password",
401
+ username: email,
402
+ password
403
+ });
404
+ let res;
405
+ try {
406
+ res = await fetch(`${identityUrl}/token`, {
407
+ method: "POST",
408
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
409
+ body: body.toString()
410
+ });
411
+ } catch (error) {
412
+ throw new AuthError(error.message, void 0, { cause: error });
413
+ }
414
+ if (!res.ok) {
415
+ const errorBody = await res.json().catch(() => ({}));
416
+ throw new AuthError(
417
+ errorBody.msg || errorBody.error_description || `Login failed (${res.status})`,
418
+ res.status
419
+ );
420
+ }
421
+ const data = await res.json();
422
+ const accessToken = data.access_token;
423
+ let userRes;
424
+ try {
425
+ userRes = await fetch(`${identityUrl}/user`, {
426
+ headers: { Authorization: `Bearer ${accessToken}` }
427
+ });
428
+ } catch (error) {
429
+ throw new AuthError(error.message, void 0, { cause: error });
430
+ }
431
+ if (!userRes.ok) {
432
+ const errorBody = await userRes.json().catch(() => ({}));
433
+ throw new AuthError(
434
+ errorBody.msg || `Failed to fetch user data (${userRes.status})`,
435
+ userRes.status
436
+ );
437
+ }
438
+ const userData = await userRes.json();
439
+ const user = toUser(userData);
440
+ setAuthCookies(cookies, accessToken, data.refresh_token);
441
+ return user;
442
+ }
242
443
  const client = getClient();
243
444
  try {
244
445
  const gotrueUser = await client.login(email, password, persistSession);
446
+ const jwt = await gotrueUser.jwt();
447
+ setBrowserAuthCookies(jwt);
245
448
  const user = toUser(gotrueUser);
246
449
  emitAuthEvent("login", user);
247
450
  return user;
@@ -250,6 +453,34 @@ var login = async (email, password) => {
250
453
  }
251
454
  };
252
455
  var signup = async (email, password, data) => {
456
+ if (!isBrowser()) {
457
+ const identityUrl = getServerIdentityUrl();
458
+ const cookies = getCookies();
459
+ let res;
460
+ try {
461
+ res = await fetch(`${identityUrl}/signup`, {
462
+ method: "POST",
463
+ headers: { "Content-Type": "application/json" },
464
+ body: JSON.stringify({ email, password, data })
465
+ });
466
+ } catch (error) {
467
+ throw new AuthError(error.message, void 0, { cause: error });
468
+ }
469
+ if (!res.ok) {
470
+ const errorBody = await res.json().catch(() => ({}));
471
+ throw new AuthError(errorBody.msg || `Signup failed (${res.status})`, res.status);
472
+ }
473
+ const responseData = await res.json();
474
+ const user = toUser(responseData);
475
+ if (responseData.confirmed_at) {
476
+ const responseRecord = responseData;
477
+ const accessToken = responseRecord.access_token;
478
+ if (accessToken) {
479
+ setAuthCookies(cookies, accessToken, responseRecord.refresh_token);
480
+ }
481
+ }
482
+ return user;
483
+ }
253
484
  const client = getClient();
254
485
  try {
255
486
  const response = await client.signup(email, password, data);
@@ -263,12 +494,30 @@ var signup = async (email, password, data) => {
263
494
  }
264
495
  };
265
496
  var logout = async () => {
497
+ if (!isBrowser()) {
498
+ const identityUrl = getServerIdentityUrl();
499
+ const cookies = getCookies();
500
+ const jwt = cookies.get(NF_JWT_COOKIE);
501
+ if (jwt) {
502
+ try {
503
+ await fetch(`${identityUrl}/logout`, {
504
+ method: "POST",
505
+ headers: { Authorization: `Bearer ${jwt}` }
506
+ });
507
+ } catch (error) {
508
+ throw new AuthError(error.message, void 0, { cause: error });
509
+ }
510
+ }
511
+ deleteAuthCookies(cookies);
512
+ return;
513
+ }
266
514
  const client = getClient();
267
515
  try {
268
516
  const currentUser = client.currentUser();
269
517
  if (currentUser) {
270
518
  await currentUser.logout();
271
519
  }
520
+ deleteBrowserAuthCookies();
272
521
  emitAuthEvent("logout", null);
273
522
  } catch (error) {
274
523
  throw new AuthError(error.message, void 0, { cause: error });
@@ -291,16 +540,18 @@ var handleAuthCallback = async () => {
291
540
  const params = new URLSearchParams(hash);
292
541
  const accessToken = params.get("access_token");
293
542
  if (accessToken) {
543
+ const refreshToken = params.get("refresh_token") ?? "";
294
544
  const gotrueUser = await client.createUser(
295
545
  {
296
546
  access_token: accessToken,
297
547
  token_type: params.get("token_type") ?? "bearer",
298
548
  expires_in: Number(params.get("expires_in")),
299
549
  expires_at: Number(params.get("expires_at")),
300
- refresh_token: params.get("refresh_token") ?? ""
550
+ refresh_token: refreshToken
301
551
  },
302
552
  persistSession
303
553
  );
554
+ setBrowserAuthCookies(accessToken, refreshToken || void 0);
304
555
  const user = toUser(gotrueUser);
305
556
  clearHash();
306
557
  emitAuthEvent("login", user);
@@ -309,6 +560,8 @@ var handleAuthCallback = async () => {
309
560
  const confirmationToken = params.get("confirmation_token");
310
561
  if (confirmationToken) {
311
562
  const gotrueUser = await client.confirm(confirmationToken, persistSession);
563
+ const jwt = await gotrueUser.jwt();
564
+ setBrowserAuthCookies(jwt);
312
565
  const user = toUser(gotrueUser);
313
566
  clearHash();
314
567
  emitAuthEvent("login", user);
@@ -317,6 +570,8 @@ var handleAuthCallback = async () => {
317
570
  const recoveryToken = params.get("recovery_token");
318
571
  if (recoveryToken) {
319
572
  const gotrueUser = await client.recover(recoveryToken, persistSession);
573
+ const jwt = await gotrueUser.jwt();
574
+ setBrowserAuthCookies(jwt);
320
575
  const user = toUser(gotrueUser);
321
576
  clearHash();
322
577
  emitAuthEvent("login", user);
@@ -331,11 +586,27 @@ var handleAuthCallback = async () => {
331
586
  if (emailChangeToken) {
332
587
  const currentUser = client.currentUser();
333
588
  if (!currentUser) {
334
- clearHash();
335
- return { type: "email_change", user: null, token: emailChangeToken };
589
+ throw new AuthError("Email change verification requires an active browser session");
336
590
  }
337
- const gotrueUser = await currentUser.update({ email_change_token: emailChangeToken });
338
- const user = toUser(gotrueUser);
591
+ const jwt = await currentUser.jwt();
592
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
593
+ const emailChangeRes = await fetch(`${identityUrl}/user`, {
594
+ method: "PUT",
595
+ headers: {
596
+ "Content-Type": "application/json",
597
+ Authorization: `Bearer ${jwt}`
598
+ },
599
+ body: JSON.stringify({ email_change_token: emailChangeToken })
600
+ });
601
+ if (!emailChangeRes.ok) {
602
+ const errorBody = await emailChangeRes.json().catch(() => ({}));
603
+ throw new AuthError(
604
+ errorBody.msg || `Email change verification failed (${emailChangeRes.status})`,
605
+ emailChangeRes.status
606
+ );
607
+ }
608
+ const emailChangeData = await emailChangeRes.json();
609
+ const user = toUser(emailChangeData);
339
610
  clearHash();
340
611
  emitAuthEvent("user_updated", user);
341
612
  return { type: "email_change", user };
@@ -348,8 +619,43 @@ var handleAuthCallback = async () => {
348
619
  var clearHash = () => {
349
620
  history.replaceState(null, "", window.location.pathname + window.location.search);
350
621
  };
622
+ var hydrateSession = async () => {
623
+ if (!isBrowser()) return null;
624
+ const client = getClient();
625
+ const currentUser = client.currentUser();
626
+ if (currentUser) return toUser(currentUser);
627
+ const accessToken = getCookie(NF_JWT_COOKIE);
628
+ if (!accessToken) return null;
629
+ const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
630
+ const gotrueUser = await client.createUser(
631
+ {
632
+ access_token: accessToken,
633
+ token_type: "bearer",
634
+ expires_in: 3600,
635
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
636
+ refresh_token: refreshToken
637
+ },
638
+ persistSession
639
+ );
640
+ const user = toUser(gotrueUser);
641
+ emitAuthEvent("login", user);
642
+ return user;
643
+ };
351
644
 
352
645
  // src/account.ts
646
+ var ensureCurrentUser = async () => {
647
+ const client = getClient();
648
+ let currentUser = client.currentUser();
649
+ if (!currentUser && isBrowser()) {
650
+ try {
651
+ await hydrateSession();
652
+ } catch {
653
+ }
654
+ currentUser = client.currentUser();
655
+ }
656
+ if (!currentUser) throw new AuthError("No user is currently logged in");
657
+ return currentUser;
658
+ };
353
659
  var requestPasswordRecovery = async (email) => {
354
660
  const client = getClient();
355
661
  try {
@@ -358,6 +664,18 @@ var requestPasswordRecovery = async (email) => {
358
664
  throw new AuthError(error.message, void 0, { cause: error });
359
665
  }
360
666
  };
667
+ var recoverPassword = async (token, newPassword) => {
668
+ const client = getClient();
669
+ try {
670
+ const gotrueUser = await client.recover(token, persistSession);
671
+ const updatedUser = await gotrueUser.update({ password: newPassword });
672
+ const user = toUser(updatedUser);
673
+ emitAuthEvent("login", user);
674
+ return user;
675
+ } catch (error) {
676
+ throw new AuthError(error.message, void 0, { cause: error });
677
+ }
678
+ };
361
679
  var confirmEmail = async (token) => {
362
680
  const client = getClient();
363
681
  try {
@@ -381,22 +699,37 @@ var acceptInvite = async (token, password) => {
381
699
  }
382
700
  };
383
701
  var verifyEmailChange = async (token) => {
384
- const client = getClient();
385
- const currentUser = client.currentUser();
386
- if (!currentUser) throw new AuthError("No user is currently logged in");
702
+ if (!isBrowser()) throw new AuthError("verifyEmailChange() is only available in the browser");
703
+ const currentUser = await ensureCurrentUser();
704
+ const jwt = await currentUser.jwt();
705
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
387
706
  try {
388
- const gotrueUser = await currentUser.update({ email_change_token: token });
389
- const user = toUser(gotrueUser);
707
+ const res = await fetch(`${identityUrl}/user`, {
708
+ method: "PUT",
709
+ headers: {
710
+ "Content-Type": "application/json",
711
+ Authorization: `Bearer ${jwt}`
712
+ },
713
+ body: JSON.stringify({ email_change_token: token })
714
+ });
715
+ if (!res.ok) {
716
+ const errorBody = await res.json().catch(() => ({}));
717
+ throw new AuthError(
718
+ errorBody.msg || `Email change verification failed (${res.status})`,
719
+ res.status
720
+ );
721
+ }
722
+ const userData = await res.json();
723
+ const user = toUser(userData);
390
724
  emitAuthEvent("user_updated", user);
391
725
  return user;
392
726
  } catch (error) {
727
+ if (error instanceof AuthError) throw error;
393
728
  throw new AuthError(error.message, void 0, { cause: error });
394
729
  }
395
730
  };
396
731
  var updateUser = async (updates) => {
397
- const client = getClient();
398
- const currentUser = client.currentUser();
399
- if (!currentUser) throw new AuthError("No user is currently logged in");
732
+ const currentUser = await ensureCurrentUser();
400
733
  try {
401
734
  const updatedUser = await currentUser.update(updates);
402
735
  const user = toUser(updatedUser);
@@ -421,6 +754,7 @@ var updateUser = async (updates) => {
421
754
  logout,
422
755
  oauthLogin,
423
756
  onAuthChange,
757
+ recoverPassword,
424
758
  requestPasswordRecovery,
425
759
  signup,
426
760
  updateUser,