@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.js CHANGED
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/types.ts
2
9
  var AUTH_PROVIDERS = ["google", "github", "gitlab", "bitbucket", "facebook", "saml", "email"];
3
10
 
@@ -23,6 +30,7 @@ var MissingIdentityError = class extends Error {
23
30
  };
24
31
 
25
32
  // src/environment.ts
33
+ var IDENTITY_PATH = "/.netlify/identity";
26
34
  var goTrueClient = null;
27
35
  var cachedApiUrl;
28
36
  var warnedMissingUrl = false;
@@ -30,13 +38,15 @@ var isBrowser = () => typeof window !== "undefined" && typeof window.location !=
30
38
  var discoverApiUrl = () => {
31
39
  if (cachedApiUrl !== void 0) return cachedApiUrl;
32
40
  if (isBrowser()) {
33
- cachedApiUrl = `${window.location.origin}/.netlify/identity`;
41
+ cachedApiUrl = `${window.location.origin}${IDENTITY_PATH}`;
34
42
  } else {
35
43
  const identityContext = getIdentityContext();
36
44
  if (identityContext?.url) {
37
45
  cachedApiUrl = identityContext.url;
38
46
  } else if (globalThis.Netlify?.context?.url) {
39
- cachedApiUrl = new URL("/.netlify/identity", globalThis.Netlify.context.url).href;
47
+ cachedApiUrl = new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href;
48
+ } else if (process.env.URL) {
49
+ cachedApiUrl = new URL(IDENTITY_PATH, process.env.URL).href;
40
50
  }
41
51
  }
42
52
  return cachedApiUrl ?? null;
@@ -53,7 +63,7 @@ var getGoTrueClient = () => {
53
63
  }
54
64
  return null;
55
65
  }
56
- goTrueClient = new GoTrue({ APIUrl: apiUrl, setCookie: isBrowser() });
66
+ goTrueClient = new GoTrue({ APIUrl: apiUrl, setCookie: false });
57
67
  return goTrueClient;
58
68
  };
59
69
  var getClient = () => {
@@ -63,18 +73,97 @@ var getClient = () => {
63
73
  };
64
74
  var getIdentityContext = () => {
65
75
  const identityContext = globalThis.netlifyIdentityContext;
66
- if (identityContext?.url && typeof identityContext.url === "string") {
76
+ if (identityContext?.url) {
67
77
  return {
68
78
  url: identityContext.url,
69
- token: typeof identityContext.token === "string" ? identityContext.token : void 0
79
+ token: identityContext.token
70
80
  };
71
81
  }
72
82
  if (globalThis.Netlify?.context?.url) {
73
- return { url: new URL("/.netlify/identity", globalThis.Netlify.context.url).href };
83
+ return { url: new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href };
84
+ }
85
+ const siteUrl = process.env.URL;
86
+ if (siteUrl) {
87
+ return { url: new URL(IDENTITY_PATH, siteUrl).href };
74
88
  }
75
89
  return null;
76
90
  };
77
91
 
92
+ // src/cookies.ts
93
+ var NF_JWT_COOKIE = "nf_jwt";
94
+ var NF_REFRESH_COOKIE = "nf_refresh";
95
+ var getCookie = (name) => {
96
+ const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
97
+ return match ? decodeURIComponent(match[1]) : null;
98
+ };
99
+ var setAuthCookies = (cookies, accessToken, refreshToken) => {
100
+ cookies.set({
101
+ name: NF_JWT_COOKIE,
102
+ value: accessToken,
103
+ httpOnly: false,
104
+ secure: true,
105
+ path: "/",
106
+ sameSite: "Lax"
107
+ });
108
+ if (refreshToken) {
109
+ cookies.set({
110
+ name: NF_REFRESH_COOKIE,
111
+ value: refreshToken,
112
+ httpOnly: false,
113
+ secure: true,
114
+ path: "/",
115
+ sameSite: "Lax"
116
+ });
117
+ }
118
+ };
119
+ var deleteAuthCookies = (cookies) => {
120
+ cookies.delete(NF_JWT_COOKIE);
121
+ cookies.delete(NF_REFRESH_COOKIE);
122
+ };
123
+ var setBrowserAuthCookies = (accessToken, refreshToken) => {
124
+ document.cookie = `${NF_JWT_COOKIE}=${encodeURIComponent(accessToken)}; path=/; secure; samesite=lax`;
125
+ if (refreshToken) {
126
+ document.cookie = `${NF_REFRESH_COOKIE}=${encodeURIComponent(refreshToken)}; path=/; secure; samesite=lax`;
127
+ }
128
+ };
129
+ var deleteBrowserAuthCookies = () => {
130
+ document.cookie = `${NF_JWT_COOKIE}=; path=/; secure; samesite=lax; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
131
+ document.cookie = `${NF_REFRESH_COOKIE}=; path=/; secure; samesite=lax; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
132
+ };
133
+ var getServerCookie = (name) => {
134
+ const cookies = globalThis.Netlify?.context?.cookies;
135
+ if (!cookies || typeof cookies.get !== "function") return null;
136
+ return cookies.get(name) ?? null;
137
+ };
138
+
139
+ // src/nextjs.ts
140
+ var nextHeadersFn;
141
+ var triggerNextjsDynamic = () => {
142
+ if (nextHeadersFn === null) return;
143
+ if (nextHeadersFn === void 0) {
144
+ try {
145
+ if (typeof __require === "undefined") {
146
+ nextHeadersFn = null;
147
+ return;
148
+ }
149
+ const mod = __require("next/headers");
150
+ nextHeadersFn = mod.headers;
151
+ } catch {
152
+ nextHeadersFn = null;
153
+ return;
154
+ }
155
+ }
156
+ const fn = nextHeadersFn;
157
+ if (!fn) return;
158
+ try {
159
+ fn();
160
+ } catch (e) {
161
+ if (e instanceof Error && ("digest" in e || /bail\s*out.*prerende/i.test(e.message))) {
162
+ throw e;
163
+ }
164
+ }
165
+ };
166
+
78
167
  // src/user.ts
79
168
  var toAuthProvider = (value) => typeof value === "string" && AUTH_PROVIDERS.includes(value) ? value : void 0;
80
169
  var toUser = (userData) => {
@@ -100,32 +189,89 @@ var claimsToUser = (claims) => {
100
189
  const userMeta = claims.user_metadata ?? {};
101
190
  const name = userMeta.full_name || userMeta.name;
102
191
  return {
103
- id: typeof claims.sub === "string" ? claims.sub : "",
104
- email: typeof claims.email === "string" ? claims.email : void 0,
192
+ id: claims.sub ?? "",
193
+ email: claims.email,
105
194
  provider: toAuthProvider(appMeta.provider),
106
195
  name: typeof name === "string" ? name : void 0,
107
196
  metadata: userMeta
108
197
  };
109
198
  };
199
+ var hydrating = false;
200
+ var backgroundHydrate = (accessToken) => {
201
+ if (hydrating) return;
202
+ hydrating = true;
203
+ const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
204
+ setTimeout(() => {
205
+ try {
206
+ const client = getClient();
207
+ client.createUser(
208
+ {
209
+ access_token: accessToken,
210
+ token_type: "bearer",
211
+ expires_in: 3600,
212
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
213
+ refresh_token: refreshToken
214
+ },
215
+ true
216
+ ).catch(() => {
217
+ }).finally(() => {
218
+ hydrating = false;
219
+ });
220
+ } catch {
221
+ hydrating = false;
222
+ }
223
+ }, 0);
224
+ };
225
+ var decodeJwtPayload = (token) => {
226
+ try {
227
+ const parts = token.split(".");
228
+ if (parts.length !== 3) return null;
229
+ const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
230
+ return JSON.parse(payload);
231
+ } catch {
232
+ return null;
233
+ }
234
+ };
110
235
  var getUser = () => {
111
236
  if (isBrowser()) {
112
237
  const client = getGoTrueClient();
113
238
  const currentUser = client?.currentUser() ?? null;
114
- if (!currentUser) return null;
115
- return toUser(currentUser);
239
+ if (currentUser) {
240
+ const jwt2 = getCookie(NF_JWT_COOKIE);
241
+ if (!jwt2) {
242
+ try {
243
+ currentUser.clearSession();
244
+ } catch {
245
+ }
246
+ return null;
247
+ }
248
+ return toUser(currentUser);
249
+ }
250
+ const jwt = getCookie(NF_JWT_COOKIE);
251
+ if (!jwt) return null;
252
+ const claims = decodeJwtPayload(jwt);
253
+ if (!claims) return null;
254
+ backgroundHydrate(jwt);
255
+ return claimsToUser(claims);
116
256
  }
257
+ triggerNextjsDynamic();
117
258
  const identityContext = globalThis.netlifyIdentityContext;
118
- if (!identityContext) return null;
119
- const claims = identityContext.user ?? (identityContext.sub ? identityContext : null);
120
- if (!claims) return null;
121
- return claimsToUser(claims);
259
+ if (identityContext?.user) {
260
+ return claimsToUser(identityContext.user);
261
+ }
262
+ const serverJwt = getServerCookie(NF_JWT_COOKIE);
263
+ if (serverJwt) {
264
+ const claims = decodeJwtPayload(serverJwt);
265
+ if (claims) return claimsToUser(claims);
266
+ }
267
+ return null;
122
268
  };
123
269
  var isAuthenticated = () => getUser() !== null;
124
270
 
125
271
  // src/config.ts
126
272
  var getIdentityConfig = () => {
127
273
  if (isBrowser()) {
128
- return { url: `${window.location.origin}/.netlify/identity` };
274
+ return { url: `${window.location.origin}${IDENTITY_PATH}` };
129
275
  }
130
276
  return getIdentityContext();
131
277
  };
@@ -153,6 +299,20 @@ var getSettings = async () => {
153
299
  };
154
300
 
155
301
  // src/auth.ts
302
+ var getCookies = () => {
303
+ const cookies = globalThis.Netlify?.context?.cookies;
304
+ if (!cookies) {
305
+ throw new AuthError("Server-side auth requires Netlify Functions runtime");
306
+ }
307
+ return cookies;
308
+ };
309
+ var getServerIdentityUrl = () => {
310
+ const ctx = getIdentityContext();
311
+ if (!ctx?.url) {
312
+ throw new AuthError("Could not determine the Identity endpoint URL on the server");
313
+ }
314
+ return ctx.url;
315
+ };
156
316
  var persistSession = true;
157
317
  var listeners = /* @__PURE__ */ new Set();
158
318
  var emitAuthEvent = (event, user) => {
@@ -187,9 +347,58 @@ var onAuthChange = (callback) => {
187
347
  };
188
348
  };
189
349
  var login = async (email, password) => {
350
+ if (!isBrowser()) {
351
+ const identityUrl = getServerIdentityUrl();
352
+ const cookies = getCookies();
353
+ const body = new URLSearchParams({
354
+ grant_type: "password",
355
+ username: email,
356
+ password
357
+ });
358
+ let res;
359
+ try {
360
+ res = await fetch(`${identityUrl}/token`, {
361
+ method: "POST",
362
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
363
+ body: body.toString()
364
+ });
365
+ } catch (error) {
366
+ throw new AuthError(error.message, void 0, { cause: error });
367
+ }
368
+ if (!res.ok) {
369
+ const errorBody = await res.json().catch(() => ({}));
370
+ throw new AuthError(
371
+ errorBody.msg || errorBody.error_description || `Login failed (${res.status})`,
372
+ res.status
373
+ );
374
+ }
375
+ const data = await res.json();
376
+ const accessToken = data.access_token;
377
+ let userRes;
378
+ try {
379
+ userRes = await fetch(`${identityUrl}/user`, {
380
+ headers: { Authorization: `Bearer ${accessToken}` }
381
+ });
382
+ } catch (error) {
383
+ throw new AuthError(error.message, void 0, { cause: error });
384
+ }
385
+ if (!userRes.ok) {
386
+ const errorBody = await userRes.json().catch(() => ({}));
387
+ throw new AuthError(
388
+ errorBody.msg || `Failed to fetch user data (${userRes.status})`,
389
+ userRes.status
390
+ );
391
+ }
392
+ const userData = await userRes.json();
393
+ const user = toUser(userData);
394
+ setAuthCookies(cookies, accessToken, data.refresh_token);
395
+ return user;
396
+ }
190
397
  const client = getClient();
191
398
  try {
192
399
  const gotrueUser = await client.login(email, password, persistSession);
400
+ const jwt = await gotrueUser.jwt();
401
+ setBrowserAuthCookies(jwt);
193
402
  const user = toUser(gotrueUser);
194
403
  emitAuthEvent("login", user);
195
404
  return user;
@@ -198,6 +407,34 @@ var login = async (email, password) => {
198
407
  }
199
408
  };
200
409
  var signup = async (email, password, data) => {
410
+ if (!isBrowser()) {
411
+ const identityUrl = getServerIdentityUrl();
412
+ const cookies = getCookies();
413
+ let res;
414
+ try {
415
+ res = await fetch(`${identityUrl}/signup`, {
416
+ method: "POST",
417
+ headers: { "Content-Type": "application/json" },
418
+ body: JSON.stringify({ email, password, data })
419
+ });
420
+ } catch (error) {
421
+ throw new AuthError(error.message, void 0, { cause: error });
422
+ }
423
+ if (!res.ok) {
424
+ const errorBody = await res.json().catch(() => ({}));
425
+ throw new AuthError(errorBody.msg || `Signup failed (${res.status})`, res.status);
426
+ }
427
+ const responseData = await res.json();
428
+ const user = toUser(responseData);
429
+ if (responseData.confirmed_at) {
430
+ const responseRecord = responseData;
431
+ const accessToken = responseRecord.access_token;
432
+ if (accessToken) {
433
+ setAuthCookies(cookies, accessToken, responseRecord.refresh_token);
434
+ }
435
+ }
436
+ return user;
437
+ }
201
438
  const client = getClient();
202
439
  try {
203
440
  const response = await client.signup(email, password, data);
@@ -211,12 +448,30 @@ var signup = async (email, password, data) => {
211
448
  }
212
449
  };
213
450
  var logout = async () => {
451
+ if (!isBrowser()) {
452
+ const identityUrl = getServerIdentityUrl();
453
+ const cookies = getCookies();
454
+ const jwt = cookies.get(NF_JWT_COOKIE);
455
+ if (jwt) {
456
+ try {
457
+ await fetch(`${identityUrl}/logout`, {
458
+ method: "POST",
459
+ headers: { Authorization: `Bearer ${jwt}` }
460
+ });
461
+ } catch (error) {
462
+ throw new AuthError(error.message, void 0, { cause: error });
463
+ }
464
+ }
465
+ deleteAuthCookies(cookies);
466
+ return;
467
+ }
214
468
  const client = getClient();
215
469
  try {
216
470
  const currentUser = client.currentUser();
217
471
  if (currentUser) {
218
472
  await currentUser.logout();
219
473
  }
474
+ deleteBrowserAuthCookies();
220
475
  emitAuthEvent("logout", null);
221
476
  } catch (error) {
222
477
  throw new AuthError(error.message, void 0, { cause: error });
@@ -239,16 +494,18 @@ var handleAuthCallback = async () => {
239
494
  const params = new URLSearchParams(hash);
240
495
  const accessToken = params.get("access_token");
241
496
  if (accessToken) {
497
+ const refreshToken = params.get("refresh_token") ?? "";
242
498
  const gotrueUser = await client.createUser(
243
499
  {
244
500
  access_token: accessToken,
245
501
  token_type: params.get("token_type") ?? "bearer",
246
502
  expires_in: Number(params.get("expires_in")),
247
503
  expires_at: Number(params.get("expires_at")),
248
- refresh_token: params.get("refresh_token") ?? ""
504
+ refresh_token: refreshToken
249
505
  },
250
506
  persistSession
251
507
  );
508
+ setBrowserAuthCookies(accessToken, refreshToken || void 0);
252
509
  const user = toUser(gotrueUser);
253
510
  clearHash();
254
511
  emitAuthEvent("login", user);
@@ -257,6 +514,8 @@ var handleAuthCallback = async () => {
257
514
  const confirmationToken = params.get("confirmation_token");
258
515
  if (confirmationToken) {
259
516
  const gotrueUser = await client.confirm(confirmationToken, persistSession);
517
+ const jwt = await gotrueUser.jwt();
518
+ setBrowserAuthCookies(jwt);
260
519
  const user = toUser(gotrueUser);
261
520
  clearHash();
262
521
  emitAuthEvent("login", user);
@@ -265,6 +524,8 @@ var handleAuthCallback = async () => {
265
524
  const recoveryToken = params.get("recovery_token");
266
525
  if (recoveryToken) {
267
526
  const gotrueUser = await client.recover(recoveryToken, persistSession);
527
+ const jwt = await gotrueUser.jwt();
528
+ setBrowserAuthCookies(jwt);
268
529
  const user = toUser(gotrueUser);
269
530
  clearHash();
270
531
  emitAuthEvent("login", user);
@@ -279,11 +540,27 @@ var handleAuthCallback = async () => {
279
540
  if (emailChangeToken) {
280
541
  const currentUser = client.currentUser();
281
542
  if (!currentUser) {
282
- clearHash();
283
- return { type: "email_change", user: null, token: emailChangeToken };
543
+ throw new AuthError("Email change verification requires an active browser session");
284
544
  }
285
- const gotrueUser = await currentUser.update({ email_change_token: emailChangeToken });
286
- const user = toUser(gotrueUser);
545
+ const jwt = await currentUser.jwt();
546
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
547
+ const emailChangeRes = await fetch(`${identityUrl}/user`, {
548
+ method: "PUT",
549
+ headers: {
550
+ "Content-Type": "application/json",
551
+ Authorization: `Bearer ${jwt}`
552
+ },
553
+ body: JSON.stringify({ email_change_token: emailChangeToken })
554
+ });
555
+ if (!emailChangeRes.ok) {
556
+ const errorBody = await emailChangeRes.json().catch(() => ({}));
557
+ throw new AuthError(
558
+ errorBody.msg || `Email change verification failed (${emailChangeRes.status})`,
559
+ emailChangeRes.status
560
+ );
561
+ }
562
+ const emailChangeData = await emailChangeRes.json();
563
+ const user = toUser(emailChangeData);
287
564
  clearHash();
288
565
  emitAuthEvent("user_updated", user);
289
566
  return { type: "email_change", user };
@@ -296,8 +573,43 @@ var handleAuthCallback = async () => {
296
573
  var clearHash = () => {
297
574
  history.replaceState(null, "", window.location.pathname + window.location.search);
298
575
  };
576
+ var hydrateSession = async () => {
577
+ if (!isBrowser()) return null;
578
+ const client = getClient();
579
+ const currentUser = client.currentUser();
580
+ if (currentUser) return toUser(currentUser);
581
+ const accessToken = getCookie(NF_JWT_COOKIE);
582
+ if (!accessToken) return null;
583
+ const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
584
+ const gotrueUser = await client.createUser(
585
+ {
586
+ access_token: accessToken,
587
+ token_type: "bearer",
588
+ expires_in: 3600,
589
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
590
+ refresh_token: refreshToken
591
+ },
592
+ persistSession
593
+ );
594
+ const user = toUser(gotrueUser);
595
+ emitAuthEvent("login", user);
596
+ return user;
597
+ };
299
598
 
300
599
  // src/account.ts
600
+ var ensureCurrentUser = async () => {
601
+ const client = getClient();
602
+ let currentUser = client.currentUser();
603
+ if (!currentUser && isBrowser()) {
604
+ try {
605
+ await hydrateSession();
606
+ } catch {
607
+ }
608
+ currentUser = client.currentUser();
609
+ }
610
+ if (!currentUser) throw new AuthError("No user is currently logged in");
611
+ return currentUser;
612
+ };
301
613
  var requestPasswordRecovery = async (email) => {
302
614
  const client = getClient();
303
615
  try {
@@ -306,6 +618,18 @@ var requestPasswordRecovery = async (email) => {
306
618
  throw new AuthError(error.message, void 0, { cause: error });
307
619
  }
308
620
  };
621
+ var recoverPassword = async (token, newPassword) => {
622
+ const client = getClient();
623
+ try {
624
+ const gotrueUser = await client.recover(token, persistSession);
625
+ const updatedUser = await gotrueUser.update({ password: newPassword });
626
+ const user = toUser(updatedUser);
627
+ emitAuthEvent("login", user);
628
+ return user;
629
+ } catch (error) {
630
+ throw new AuthError(error.message, void 0, { cause: error });
631
+ }
632
+ };
309
633
  var confirmEmail = async (token) => {
310
634
  const client = getClient();
311
635
  try {
@@ -329,22 +653,37 @@ var acceptInvite = async (token, password) => {
329
653
  }
330
654
  };
331
655
  var verifyEmailChange = async (token) => {
332
- const client = getClient();
333
- const currentUser = client.currentUser();
334
- if (!currentUser) throw new AuthError("No user is currently logged in");
656
+ if (!isBrowser()) throw new AuthError("verifyEmailChange() is only available in the browser");
657
+ const currentUser = await ensureCurrentUser();
658
+ const jwt = await currentUser.jwt();
659
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
335
660
  try {
336
- const gotrueUser = await currentUser.update({ email_change_token: token });
337
- const user = toUser(gotrueUser);
661
+ const res = await fetch(`${identityUrl}/user`, {
662
+ method: "PUT",
663
+ headers: {
664
+ "Content-Type": "application/json",
665
+ Authorization: `Bearer ${jwt}`
666
+ },
667
+ body: JSON.stringify({ email_change_token: token })
668
+ });
669
+ if (!res.ok) {
670
+ const errorBody = await res.json().catch(() => ({}));
671
+ throw new AuthError(
672
+ errorBody.msg || `Email change verification failed (${res.status})`,
673
+ res.status
674
+ );
675
+ }
676
+ const userData = await res.json();
677
+ const user = toUser(userData);
338
678
  emitAuthEvent("user_updated", user);
339
679
  return user;
340
680
  } catch (error) {
681
+ if (error instanceof AuthError) throw error;
341
682
  throw new AuthError(error.message, void 0, { cause: error });
342
683
  }
343
684
  };
344
685
  var updateUser = async (updates) => {
345
- const client = getClient();
346
- const currentUser = client.currentUser();
347
- if (!currentUser) throw new AuthError("No user is currently logged in");
686
+ const currentUser = await ensureCurrentUser();
348
687
  try {
349
688
  const updatedUser = await currentUser.update(updates);
350
689
  const user = toUser(updatedUser);
@@ -368,6 +707,7 @@ export {
368
707
  logout,
369
708
  oauthLogin,
370
709
  onAuthChange,
710
+ recoverPassword,
371
711
  requestPasswordRecovery,
372
712
  signup,
373
713
  updateUser,