@netlify/identity 0.1.1-alpha.1 → 0.1.1-alpha.11

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;
@@ -115,18 +119,92 @@ 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=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
177
+ document.cookie = `${NF_REFRESH_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
178
+ };
179
+
180
+ // src/nextjs.ts
181
+ var nextHeadersFn;
182
+ var triggerNextjsDynamic = () => {
183
+ if (nextHeadersFn === null) return;
184
+ if (nextHeadersFn === void 0) {
185
+ try {
186
+ if (typeof require === "undefined") {
187
+ nextHeadersFn = null;
188
+ return;
189
+ }
190
+ const mod = require("next/headers");
191
+ nextHeadersFn = mod.headers;
192
+ } catch {
193
+ nextHeadersFn = null;
194
+ return;
195
+ }
196
+ }
197
+ const fn = nextHeadersFn;
198
+ if (!fn) return;
199
+ try {
200
+ fn();
201
+ } catch (e) {
202
+ if (e instanceof Error && ("digest" in e || /bail\s*out.*prerende/i.test(e.message))) {
203
+ throw e;
204
+ }
205
+ }
206
+ };
207
+
130
208
  // src/user.ts
131
209
  var toAuthProvider = (value) => typeof value === "string" && AUTH_PROVIDERS.includes(value) ? value : void 0;
132
210
  var toUser = (userData) => {
@@ -152,32 +230,57 @@ var claimsToUser = (claims) => {
152
230
  const userMeta = claims.user_metadata ?? {};
153
231
  const name = userMeta.full_name || userMeta.name;
154
232
  return {
155
- id: typeof claims.sub === "string" ? claims.sub : "",
156
- email: typeof claims.email === "string" ? claims.email : void 0,
233
+ id: claims.sub ?? "",
234
+ email: claims.email,
157
235
  provider: toAuthProvider(appMeta.provider),
158
236
  name: typeof name === "string" ? name : void 0,
159
237
  metadata: userMeta
160
238
  };
161
239
  };
240
+ var decodeJwtPayload = (token) => {
241
+ try {
242
+ const parts = token.split(".");
243
+ if (parts.length !== 3) return null;
244
+ const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
245
+ return JSON.parse(payload);
246
+ } catch {
247
+ return null;
248
+ }
249
+ };
162
250
  var getUser = () => {
163
251
  if (isBrowser()) {
164
252
  const client = getGoTrueClient();
165
253
  const currentUser = client?.currentUser() ?? null;
166
- if (!currentUser) return null;
167
- return toUser(currentUser);
254
+ if (currentUser) {
255
+ const jwt2 = getCookie(NF_JWT_COOKIE);
256
+ if (!jwt2) {
257
+ try {
258
+ currentUser.logout();
259
+ } catch {
260
+ }
261
+ return null;
262
+ }
263
+ return toUser(currentUser);
264
+ }
265
+ const jwt = getCookie(NF_JWT_COOKIE);
266
+ if (!jwt) return null;
267
+ const claims = decodeJwtPayload(jwt);
268
+ if (!claims) return null;
269
+ return claimsToUser(claims);
168
270
  }
271
+ triggerNextjsDynamic();
169
272
  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);
273
+ if (identityContext?.user) {
274
+ return claimsToUser(identityContext.user);
275
+ }
276
+ return null;
174
277
  };
175
278
  var isAuthenticated = () => getUser() !== null;
176
279
 
177
280
  // src/config.ts
178
281
  var getIdentityConfig = () => {
179
282
  if (isBrowser()) {
180
- return { url: `${window.location.origin}/.netlify/identity` };
283
+ return { url: `${window.location.origin}${IDENTITY_PATH}` };
181
284
  }
182
285
  return getIdentityContext();
183
286
  };
@@ -205,6 +308,20 @@ var getSettings = async () => {
205
308
  };
206
309
 
207
310
  // src/auth.ts
311
+ var getCookies = () => {
312
+ const cookies = globalThis.Netlify?.context?.cookies;
313
+ if (!cookies) {
314
+ throw new AuthError("Server-side auth requires Netlify Functions runtime");
315
+ }
316
+ return cookies;
317
+ };
318
+ var getServerIdentityUrl = () => {
319
+ const ctx = getIdentityContext();
320
+ if (!ctx?.url) {
321
+ throw new AuthError("Could not determine the Identity endpoint URL on the server");
322
+ }
323
+ return ctx.url;
324
+ };
208
325
  var persistSession = true;
209
326
  var listeners = /* @__PURE__ */ new Set();
210
327
  var emitAuthEvent = (event, user) => {
@@ -239,9 +356,58 @@ var onAuthChange = (callback) => {
239
356
  };
240
357
  };
241
358
  var login = async (email, password) => {
359
+ if (!isBrowser()) {
360
+ const identityUrl = getServerIdentityUrl();
361
+ const cookies = getCookies();
362
+ const body = new URLSearchParams({
363
+ grant_type: "password",
364
+ username: email,
365
+ password
366
+ });
367
+ let res;
368
+ try {
369
+ res = await fetch(`${identityUrl}/token`, {
370
+ method: "POST",
371
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
372
+ body: body.toString()
373
+ });
374
+ } catch (error) {
375
+ throw new AuthError(error.message, void 0, { cause: error });
376
+ }
377
+ if (!res.ok) {
378
+ const errorBody = await res.json().catch(() => ({}));
379
+ throw new AuthError(
380
+ errorBody.msg || errorBody.error_description || `Login failed (${res.status})`,
381
+ res.status
382
+ );
383
+ }
384
+ const data = await res.json();
385
+ const accessToken = data.access_token;
386
+ let userRes;
387
+ try {
388
+ userRes = await fetch(`${identityUrl}/user`, {
389
+ headers: { Authorization: `Bearer ${accessToken}` }
390
+ });
391
+ } catch (error) {
392
+ throw new AuthError(error.message, void 0, { cause: error });
393
+ }
394
+ if (!userRes.ok) {
395
+ const errorBody = await userRes.json().catch(() => ({}));
396
+ throw new AuthError(
397
+ errorBody.msg || `Failed to fetch user data (${userRes.status})`,
398
+ userRes.status
399
+ );
400
+ }
401
+ const userData = await userRes.json();
402
+ const user = toUser(userData);
403
+ setAuthCookies(cookies, accessToken, data.refresh_token);
404
+ return user;
405
+ }
242
406
  const client = getClient();
243
407
  try {
244
408
  const gotrueUser = await client.login(email, password, persistSession);
409
+ const jwt = await gotrueUser.jwt();
410
+ setBrowserAuthCookies(jwt);
245
411
  const user = toUser(gotrueUser);
246
412
  emitAuthEvent("login", user);
247
413
  return user;
@@ -250,6 +416,34 @@ var login = async (email, password) => {
250
416
  }
251
417
  };
252
418
  var signup = async (email, password, data) => {
419
+ if (!isBrowser()) {
420
+ const identityUrl = getServerIdentityUrl();
421
+ const cookies = getCookies();
422
+ let res;
423
+ try {
424
+ res = await fetch(`${identityUrl}/signup`, {
425
+ method: "POST",
426
+ headers: { "Content-Type": "application/json" },
427
+ body: JSON.stringify({ email, password, data })
428
+ });
429
+ } catch (error) {
430
+ throw new AuthError(error.message, void 0, { cause: error });
431
+ }
432
+ if (!res.ok) {
433
+ const errorBody = await res.json().catch(() => ({}));
434
+ throw new AuthError(errorBody.msg || `Signup failed (${res.status})`, res.status);
435
+ }
436
+ const responseData = await res.json();
437
+ const user = toUser(responseData);
438
+ if (responseData.confirmed_at) {
439
+ const responseRecord = responseData;
440
+ const accessToken = responseRecord.access_token;
441
+ if (accessToken) {
442
+ setAuthCookies(cookies, accessToken, responseRecord.refresh_token);
443
+ }
444
+ }
445
+ return user;
446
+ }
253
447
  const client = getClient();
254
448
  try {
255
449
  const response = await client.signup(email, password, data);
@@ -263,12 +457,30 @@ var signup = async (email, password, data) => {
263
457
  }
264
458
  };
265
459
  var logout = async () => {
460
+ if (!isBrowser()) {
461
+ const identityUrl = getServerIdentityUrl();
462
+ const cookies = getCookies();
463
+ const jwt = cookies.get(NF_JWT_COOKIE);
464
+ if (jwt) {
465
+ try {
466
+ await fetch(`${identityUrl}/logout`, {
467
+ method: "POST",
468
+ headers: { Authorization: `Bearer ${jwt}` }
469
+ });
470
+ } catch (error) {
471
+ throw new AuthError(error.message, void 0, { cause: error });
472
+ }
473
+ }
474
+ deleteAuthCookies(cookies);
475
+ return;
476
+ }
266
477
  const client = getClient();
267
478
  try {
268
479
  const currentUser = client.currentUser();
269
480
  if (currentUser) {
270
481
  await currentUser.logout();
271
482
  }
483
+ deleteBrowserAuthCookies();
272
484
  emitAuthEvent("logout", null);
273
485
  } catch (error) {
274
486
  throw new AuthError(error.message, void 0, { cause: error });
@@ -291,16 +503,18 @@ var handleAuthCallback = async () => {
291
503
  const params = new URLSearchParams(hash);
292
504
  const accessToken = params.get("access_token");
293
505
  if (accessToken) {
506
+ const refreshToken = params.get("refresh_token") ?? "";
294
507
  const gotrueUser = await client.createUser(
295
508
  {
296
509
  access_token: accessToken,
297
510
  token_type: params.get("token_type") ?? "bearer",
298
511
  expires_in: Number(params.get("expires_in")),
299
512
  expires_at: Number(params.get("expires_at")),
300
- refresh_token: params.get("refresh_token") ?? ""
513
+ refresh_token: refreshToken
301
514
  },
302
515
  persistSession
303
516
  );
517
+ setBrowserAuthCookies(accessToken, refreshToken || void 0);
304
518
  const user = toUser(gotrueUser);
305
519
  clearHash();
306
520
  emitAuthEvent("login", user);
@@ -309,6 +523,8 @@ var handleAuthCallback = async () => {
309
523
  const confirmationToken = params.get("confirmation_token");
310
524
  if (confirmationToken) {
311
525
  const gotrueUser = await client.confirm(confirmationToken, persistSession);
526
+ const jwt = await gotrueUser.jwt();
527
+ setBrowserAuthCookies(jwt);
312
528
  const user = toUser(gotrueUser);
313
529
  clearHash();
314
530
  emitAuthEvent("login", user);
@@ -317,6 +533,8 @@ var handleAuthCallback = async () => {
317
533
  const recoveryToken = params.get("recovery_token");
318
534
  if (recoveryToken) {
319
535
  const gotrueUser = await client.recover(recoveryToken, persistSession);
536
+ const jwt = await gotrueUser.jwt();
537
+ setBrowserAuthCookies(jwt);
320
538
  const user = toUser(gotrueUser);
321
539
  clearHash();
322
540
  emitAuthEvent("login", user);
@@ -329,8 +547,29 @@ var handleAuthCallback = async () => {
329
547
  }
330
548
  const emailChangeToken = params.get("email_change_token");
331
549
  if (emailChangeToken) {
332
- const gotrueUser = await client.verify("email_change", emailChangeToken, persistSession);
333
- const user = toUser(gotrueUser);
550
+ const currentUser = client.currentUser();
551
+ if (!currentUser) {
552
+ throw new AuthError("Email change verification requires an active browser session");
553
+ }
554
+ const jwt = await currentUser.jwt();
555
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
556
+ const emailChangeRes = await fetch(`${identityUrl}/user`, {
557
+ method: "PUT",
558
+ headers: {
559
+ "Content-Type": "application/json",
560
+ Authorization: `Bearer ${jwt}`
561
+ },
562
+ body: JSON.stringify({ email_change_token: emailChangeToken })
563
+ });
564
+ if (!emailChangeRes.ok) {
565
+ const errorBody = await emailChangeRes.json().catch(() => ({}));
566
+ throw new AuthError(
567
+ errorBody.msg || `Email change verification failed (${emailChangeRes.status})`,
568
+ emailChangeRes.status
569
+ );
570
+ }
571
+ const emailChangeData = await emailChangeRes.json();
572
+ const user = toUser(emailChangeData);
334
573
  clearHash();
335
574
  emitAuthEvent("user_updated", user);
336
575
  return { type: "email_change", user };
@@ -343,8 +582,43 @@ var handleAuthCallback = async () => {
343
582
  var clearHash = () => {
344
583
  history.replaceState(null, "", window.location.pathname + window.location.search);
345
584
  };
585
+ var hydrateSession = async () => {
586
+ if (!isBrowser()) return null;
587
+ const client = getClient();
588
+ const currentUser = client.currentUser();
589
+ if (currentUser) return toUser(currentUser);
590
+ const accessToken = getCookie(NF_JWT_COOKIE);
591
+ if (!accessToken) return null;
592
+ const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
593
+ const gotrueUser = await client.createUser(
594
+ {
595
+ access_token: accessToken,
596
+ token_type: "bearer",
597
+ expires_in: 3600,
598
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
599
+ refresh_token: refreshToken
600
+ },
601
+ persistSession
602
+ );
603
+ const user = toUser(gotrueUser);
604
+ emitAuthEvent("login", user);
605
+ return user;
606
+ };
346
607
 
347
608
  // src/account.ts
609
+ var ensureCurrentUser = async () => {
610
+ const client = getClient();
611
+ let currentUser = client.currentUser();
612
+ if (!currentUser && isBrowser()) {
613
+ try {
614
+ await hydrateSession();
615
+ } catch {
616
+ }
617
+ currentUser = client.currentUser();
618
+ }
619
+ if (!currentUser) throw new AuthError("No user is currently logged in");
620
+ return currentUser;
621
+ };
348
622
  var requestPasswordRecovery = async (email) => {
349
623
  const client = getClient();
350
624
  try {
@@ -353,6 +627,18 @@ var requestPasswordRecovery = async (email) => {
353
627
  throw new AuthError(error.message, void 0, { cause: error });
354
628
  }
355
629
  };
630
+ var recoverPassword = async (token, newPassword) => {
631
+ const client = getClient();
632
+ try {
633
+ const gotrueUser = await client.recover(token, persistSession);
634
+ const updatedUser = await gotrueUser.update({ password: newPassword });
635
+ const user = toUser(updatedUser);
636
+ emitAuthEvent("login", user);
637
+ return user;
638
+ } catch (error) {
639
+ throw new AuthError(error.message, void 0, { cause: error });
640
+ }
641
+ };
356
642
  var confirmEmail = async (token) => {
357
643
  const client = getClient();
358
644
  try {
@@ -376,20 +662,37 @@ var acceptInvite = async (token, password) => {
376
662
  }
377
663
  };
378
664
  var verifyEmailChange = async (token) => {
379
- const client = getClient();
665
+ if (!isBrowser()) throw new AuthError("verifyEmailChange() is only available in the browser");
666
+ const currentUser = await ensureCurrentUser();
667
+ const jwt = await currentUser.jwt();
668
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
380
669
  try {
381
- const gotrueUser = await client.verify("email_change", token, persistSession);
382
- const user = toUser(gotrueUser);
670
+ const res = await fetch(`${identityUrl}/user`, {
671
+ method: "PUT",
672
+ headers: {
673
+ "Content-Type": "application/json",
674
+ Authorization: `Bearer ${jwt}`
675
+ },
676
+ body: JSON.stringify({ email_change_token: token })
677
+ });
678
+ if (!res.ok) {
679
+ const errorBody = await res.json().catch(() => ({}));
680
+ throw new AuthError(
681
+ errorBody.msg || `Email change verification failed (${res.status})`,
682
+ res.status
683
+ );
684
+ }
685
+ const userData = await res.json();
686
+ const user = toUser(userData);
383
687
  emitAuthEvent("user_updated", user);
384
688
  return user;
385
689
  } catch (error) {
690
+ if (error instanceof AuthError) throw error;
386
691
  throw new AuthError(error.message, void 0, { cause: error });
387
692
  }
388
693
  };
389
694
  var updateUser = async (updates) => {
390
- const client = getClient();
391
- const currentUser = client.currentUser();
392
- if (!currentUser) throw new AuthError("No user is currently logged in");
695
+ const currentUser = await ensureCurrentUser();
393
696
  try {
394
697
  const updatedUser = await currentUser.update(updates);
395
698
  const user = toUser(updatedUser);
@@ -414,6 +717,7 @@ var updateUser = async (updates) => {
414
717
  logout,
415
718
  oauthLogin,
416
719
  onAuthChange,
720
+ recoverPassword,
417
721
  requestPasswordRecovery,
418
722
  signup,
419
723
  updateUser,