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

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
 
@@ -16,13 +23,14 @@ var AuthError = class extends Error {
16
23
  }
17
24
  };
18
25
  var MissingIdentityError = class extends Error {
19
- constructor(message = "Identity is not available in this environment") {
26
+ constructor(message = "Netlify Identity is not available. Enable Identity in your site dashboard and use `netlify dev` for local development.") {
20
27
  super(message);
21
28
  this.name = "MissingIdentityError";
22
29
  }
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,92 @@ 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
+ const decoded = decodeJwtPayload(accessToken);
205
+ const expiresAt = decoded?.exp ?? Math.floor(Date.now() / 1e3) + 3600;
206
+ const expiresIn = Math.max(0, expiresAt - Math.floor(Date.now() / 1e3));
207
+ setTimeout(() => {
208
+ try {
209
+ const client = getClient();
210
+ client.createUser(
211
+ {
212
+ access_token: accessToken,
213
+ token_type: "bearer",
214
+ expires_in: expiresIn,
215
+ expires_at: expiresAt,
216
+ refresh_token: refreshToken
217
+ },
218
+ true
219
+ ).catch(() => {
220
+ }).finally(() => {
221
+ hydrating = false;
222
+ });
223
+ } catch {
224
+ hydrating = false;
225
+ }
226
+ }, 0);
227
+ };
228
+ var decodeJwtPayload = (token) => {
229
+ try {
230
+ const parts = token.split(".");
231
+ if (parts.length !== 3) return null;
232
+ const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
233
+ return JSON.parse(payload);
234
+ } catch {
235
+ return null;
236
+ }
237
+ };
110
238
  var getUser = () => {
111
239
  if (isBrowser()) {
112
240
  const client = getGoTrueClient();
113
241
  const currentUser = client?.currentUser() ?? null;
114
- if (!currentUser) return null;
115
- return toUser(currentUser);
242
+ if (currentUser) {
243
+ const jwt2 = getCookie(NF_JWT_COOKIE);
244
+ if (!jwt2) {
245
+ try {
246
+ currentUser.clearSession();
247
+ } catch {
248
+ }
249
+ return null;
250
+ }
251
+ return toUser(currentUser);
252
+ }
253
+ const jwt = getCookie(NF_JWT_COOKIE);
254
+ if (!jwt) return null;
255
+ const claims = decodeJwtPayload(jwt);
256
+ if (!claims) return null;
257
+ backgroundHydrate(jwt);
258
+ return claimsToUser(claims);
116
259
  }
260
+ triggerNextjsDynamic();
117
261
  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);
262
+ if (identityContext?.user) {
263
+ return claimsToUser(identityContext.user);
264
+ }
265
+ const serverJwt = getServerCookie(NF_JWT_COOKIE);
266
+ if (serverJwt) {
267
+ const claims = decodeJwtPayload(serverJwt);
268
+ if (claims) return claimsToUser(claims);
269
+ }
270
+ return null;
122
271
  };
123
272
  var isAuthenticated = () => getUser() !== null;
124
273
 
125
274
  // src/config.ts
126
275
  var getIdentityConfig = () => {
127
276
  if (isBrowser()) {
128
- return { url: `${window.location.origin}/.netlify/identity` };
277
+ return { url: `${window.location.origin}${IDENTITY_PATH}` };
129
278
  }
130
279
  return getIdentityContext();
131
280
  };
@@ -153,19 +302,37 @@ var getSettings = async () => {
153
302
  };
154
303
 
155
304
  // src/auth.ts
305
+ var getCookies = () => {
306
+ const cookies = globalThis.Netlify?.context?.cookies;
307
+ if (!cookies) {
308
+ throw new AuthError("Server-side auth requires Netlify Functions runtime");
309
+ }
310
+ return cookies;
311
+ };
312
+ var getServerIdentityUrl = () => {
313
+ const ctx = getIdentityContext();
314
+ if (!ctx?.url) {
315
+ throw new AuthError("Could not determine the Identity endpoint URL on the server");
316
+ }
317
+ return ctx.url;
318
+ };
156
319
  var persistSession = true;
157
320
  var listeners = /* @__PURE__ */ new Set();
158
321
  var emitAuthEvent = (event, user) => {
159
322
  for (const listener of listeners) {
160
- listener(event, user);
323
+ try {
324
+ listener(event, user);
325
+ } catch {
326
+ }
161
327
  }
162
328
  };
329
+ var GOTRUE_STORAGE_KEY = "gotrue.user";
163
330
  var storageListenerAttached = false;
164
331
  var attachStorageListener = () => {
165
332
  if (storageListenerAttached) return;
166
333
  storageListenerAttached = true;
167
334
  window.addEventListener("storage", (event) => {
168
- if (event.key !== "gotrue.user") return;
335
+ if (event.key !== GOTRUE_STORAGE_KEY) return;
169
336
  if (event.newValue) {
170
337
  const client = getGoTrueClient();
171
338
  const currentUser = client?.currentUser();
@@ -187,9 +354,58 @@ var onAuthChange = (callback) => {
187
354
  };
188
355
  };
189
356
  var login = async (email, password) => {
357
+ if (!isBrowser()) {
358
+ const identityUrl = getServerIdentityUrl();
359
+ const cookies = getCookies();
360
+ const body = new URLSearchParams({
361
+ grant_type: "password",
362
+ username: email,
363
+ password
364
+ });
365
+ let res;
366
+ try {
367
+ res = await fetch(`${identityUrl}/token`, {
368
+ method: "POST",
369
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
370
+ body: body.toString()
371
+ });
372
+ } catch (error) {
373
+ throw new AuthError(error.message, void 0, { cause: error });
374
+ }
375
+ if (!res.ok) {
376
+ const errorBody = await res.json().catch(() => ({}));
377
+ throw new AuthError(
378
+ errorBody.msg || errorBody.error_description || `Login failed (${res.status})`,
379
+ res.status
380
+ );
381
+ }
382
+ const data = await res.json();
383
+ const accessToken = data.access_token;
384
+ let userRes;
385
+ try {
386
+ userRes = await fetch(`${identityUrl}/user`, {
387
+ headers: { Authorization: `Bearer ${accessToken}` }
388
+ });
389
+ } catch (error) {
390
+ throw new AuthError(error.message, void 0, { cause: error });
391
+ }
392
+ if (!userRes.ok) {
393
+ const errorBody = await userRes.json().catch(() => ({}));
394
+ throw new AuthError(
395
+ errorBody.msg || `Failed to fetch user data (${userRes.status})`,
396
+ userRes.status
397
+ );
398
+ }
399
+ const userData = await userRes.json();
400
+ const user = toUser(userData);
401
+ setAuthCookies(cookies, accessToken, data.refresh_token);
402
+ return user;
403
+ }
190
404
  const client = getClient();
191
405
  try {
192
406
  const gotrueUser = await client.login(email, password, persistSession);
407
+ const jwt = await gotrueUser.jwt();
408
+ setBrowserAuthCookies(jwt);
193
409
  const user = toUser(gotrueUser);
194
410
  emitAuthEvent("login", user);
195
411
  return user;
@@ -198,11 +414,43 @@ var login = async (email, password) => {
198
414
  }
199
415
  };
200
416
  var signup = async (email, password, data) => {
417
+ if (!isBrowser()) {
418
+ const identityUrl = getServerIdentityUrl();
419
+ const cookies = getCookies();
420
+ let res;
421
+ try {
422
+ res = await fetch(`${identityUrl}/signup`, {
423
+ method: "POST",
424
+ headers: { "Content-Type": "application/json" },
425
+ body: JSON.stringify({ email, password, data })
426
+ });
427
+ } catch (error) {
428
+ throw new AuthError(error.message, void 0, { cause: error });
429
+ }
430
+ if (!res.ok) {
431
+ const errorBody = await res.json().catch(() => ({}));
432
+ throw new AuthError(errorBody.msg || `Signup failed (${res.status})`, res.status);
433
+ }
434
+ const responseData = await res.json();
435
+ const user = toUser(responseData);
436
+ if (responseData.confirmed_at) {
437
+ const responseRecord = responseData;
438
+ const accessToken = responseRecord.access_token;
439
+ if (accessToken) {
440
+ setAuthCookies(cookies, accessToken, responseRecord.refresh_token);
441
+ }
442
+ }
443
+ return user;
444
+ }
201
445
  const client = getClient();
202
446
  try {
203
447
  const response = await client.signup(email, password, data);
204
448
  const user = toUser(response);
205
449
  if (response.confirmed_at) {
450
+ const jwt = await response.jwt?.();
451
+ if (jwt) {
452
+ setBrowserAuthCookies(jwt);
453
+ }
206
454
  emitAuthEvent("login", user);
207
455
  }
208
456
  return user;
@@ -211,12 +459,29 @@ var signup = async (email, password, data) => {
211
459
  }
212
460
  };
213
461
  var logout = async () => {
462
+ if (!isBrowser()) {
463
+ const identityUrl = getServerIdentityUrl();
464
+ const cookies = getCookies();
465
+ const jwt = cookies.get(NF_JWT_COOKIE);
466
+ if (jwt) {
467
+ try {
468
+ await fetch(`${identityUrl}/logout`, {
469
+ method: "POST",
470
+ headers: { Authorization: `Bearer ${jwt}` }
471
+ });
472
+ } catch {
473
+ }
474
+ }
475
+ deleteAuthCookies(cookies);
476
+ return;
477
+ }
214
478
  const client = getClient();
215
479
  try {
216
480
  const currentUser = client.currentUser();
217
481
  if (currentUser) {
218
482
  await currentUser.logout();
219
483
  }
484
+ deleteBrowserAuthCookies();
220
485
  emitAuthEvent("logout", null);
221
486
  } catch (error) {
222
487
  throw new AuthError(error.message, void 0, { cause: error });
@@ -224,11 +489,11 @@ var logout = async () => {
224
489
  };
225
490
  var oauthLogin = (provider) => {
226
491
  if (!isBrowser()) {
227
- throw new Error("oauthLogin() is only available in the browser");
492
+ throw new AuthError("oauthLogin() is only available in the browser");
228
493
  }
229
494
  const client = getClient();
230
495
  window.location.href = client.loginExternalUrl(provider);
231
- throw new Error("Redirecting to OAuth provider");
496
+ throw new AuthError("Redirecting to OAuth provider");
232
497
  };
233
498
  var handleAuthCallback = async () => {
234
499
  if (!isBrowser()) return null;
@@ -239,16 +504,18 @@ var handleAuthCallback = async () => {
239
504
  const params = new URLSearchParams(hash);
240
505
  const accessToken = params.get("access_token");
241
506
  if (accessToken) {
507
+ const refreshToken = params.get("refresh_token") ?? "";
242
508
  const gotrueUser = await client.createUser(
243
509
  {
244
510
  access_token: accessToken,
245
511
  token_type: params.get("token_type") ?? "bearer",
246
512
  expires_in: Number(params.get("expires_in")),
247
513
  expires_at: Number(params.get("expires_at")),
248
- refresh_token: params.get("refresh_token") ?? ""
514
+ refresh_token: refreshToken
249
515
  },
250
516
  persistSession
251
517
  );
518
+ setBrowserAuthCookies(accessToken, refreshToken || void 0);
252
519
  const user = toUser(gotrueUser);
253
520
  clearHash();
254
521
  emitAuthEvent("login", user);
@@ -257,6 +524,8 @@ var handleAuthCallback = async () => {
257
524
  const confirmationToken = params.get("confirmation_token");
258
525
  if (confirmationToken) {
259
526
  const gotrueUser = await client.confirm(confirmationToken, persistSession);
527
+ const jwt = await gotrueUser.jwt();
528
+ setBrowserAuthCookies(jwt);
260
529
  const user = toUser(gotrueUser);
261
530
  clearHash();
262
531
  emitAuthEvent("login", user);
@@ -265,6 +534,8 @@ var handleAuthCallback = async () => {
265
534
  const recoveryToken = params.get("recovery_token");
266
535
  if (recoveryToken) {
267
536
  const gotrueUser = await client.recover(recoveryToken, persistSession);
537
+ const jwt = await gotrueUser.jwt();
538
+ setBrowserAuthCookies(jwt);
268
539
  const user = toUser(gotrueUser);
269
540
  clearHash();
270
541
  emitAuthEvent("login", user);
@@ -279,25 +550,80 @@ var handleAuthCallback = async () => {
279
550
  if (emailChangeToken) {
280
551
  const currentUser = client.currentUser();
281
552
  if (!currentUser) {
282
- clearHash();
283
- return { type: "email_change", user: null, token: emailChangeToken };
553
+ throw new AuthError("Email change verification requires an active browser session");
284
554
  }
285
- const gotrueUser = await currentUser.update({ email_change_token: emailChangeToken });
286
- const user = toUser(gotrueUser);
555
+ const jwt = await currentUser.jwt();
556
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
557
+ const emailChangeRes = await fetch(`${identityUrl}/user`, {
558
+ method: "PUT",
559
+ headers: {
560
+ "Content-Type": "application/json",
561
+ Authorization: `Bearer ${jwt}`
562
+ },
563
+ body: JSON.stringify({ email_change_token: emailChangeToken })
564
+ });
565
+ if (!emailChangeRes.ok) {
566
+ const errorBody = await emailChangeRes.json().catch(() => ({}));
567
+ throw new AuthError(
568
+ errorBody.msg || `Email change verification failed (${emailChangeRes.status})`,
569
+ emailChangeRes.status
570
+ );
571
+ }
572
+ const emailChangeData = await emailChangeRes.json();
573
+ const user = toUser(emailChangeData);
287
574
  clearHash();
288
575
  emitAuthEvent("user_updated", user);
289
576
  return { type: "email_change", user };
290
577
  }
291
578
  return null;
292
579
  } catch (error) {
580
+ if (error instanceof AuthError) throw error;
293
581
  throw new AuthError(error.message, void 0, { cause: error });
294
582
  }
295
583
  };
296
584
  var clearHash = () => {
297
585
  history.replaceState(null, "", window.location.pathname + window.location.search);
298
586
  };
587
+ var hydrateSession = async () => {
588
+ if (!isBrowser()) return null;
589
+ const client = getClient();
590
+ const currentUser = client.currentUser();
591
+ if (currentUser) return toUser(currentUser);
592
+ const accessToken = getCookie(NF_JWT_COOKIE);
593
+ if (!accessToken) return null;
594
+ const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
595
+ const decoded = decodeJwtPayload(accessToken);
596
+ const expiresAt = decoded?.exp ?? Math.floor(Date.now() / 1e3) + 3600;
597
+ const expiresIn = Math.max(0, expiresAt - Math.floor(Date.now() / 1e3));
598
+ const gotrueUser = await client.createUser(
599
+ {
600
+ access_token: accessToken,
601
+ token_type: "bearer",
602
+ expires_in: expiresIn,
603
+ expires_at: expiresAt,
604
+ refresh_token: refreshToken
605
+ },
606
+ persistSession
607
+ );
608
+ const user = toUser(gotrueUser);
609
+ emitAuthEvent("login", user);
610
+ return user;
611
+ };
299
612
 
300
613
  // src/account.ts
614
+ var ensureCurrentUser = async () => {
615
+ const client = getClient();
616
+ let currentUser = client.currentUser();
617
+ if (!currentUser && isBrowser()) {
618
+ try {
619
+ await hydrateSession();
620
+ } catch {
621
+ }
622
+ currentUser = client.currentUser();
623
+ }
624
+ if (!currentUser) throw new AuthError("No user is currently logged in");
625
+ return currentUser;
626
+ };
301
627
  var requestPasswordRecovery = async (email) => {
302
628
  const client = getClient();
303
629
  try {
@@ -306,6 +632,18 @@ var requestPasswordRecovery = async (email) => {
306
632
  throw new AuthError(error.message, void 0, { cause: error });
307
633
  }
308
634
  };
635
+ var recoverPassword = async (token, newPassword) => {
636
+ const client = getClient();
637
+ try {
638
+ const gotrueUser = await client.recover(token, persistSession);
639
+ const updatedUser = await gotrueUser.update({ password: newPassword });
640
+ const user = toUser(updatedUser);
641
+ emitAuthEvent("login", user);
642
+ return user;
643
+ } catch (error) {
644
+ throw new AuthError(error.message, void 0, { cause: error });
645
+ }
646
+ };
309
647
  var confirmEmail = async (token) => {
310
648
  const client = getClient();
311
649
  try {
@@ -329,22 +667,37 @@ var acceptInvite = async (token, password) => {
329
667
  }
330
668
  };
331
669
  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");
670
+ if (!isBrowser()) throw new AuthError("verifyEmailChange() is only available in the browser");
671
+ const currentUser = await ensureCurrentUser();
672
+ const jwt = await currentUser.jwt();
673
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
335
674
  try {
336
- const gotrueUser = await currentUser.update({ email_change_token: token });
337
- const user = toUser(gotrueUser);
675
+ const res = await fetch(`${identityUrl}/user`, {
676
+ method: "PUT",
677
+ headers: {
678
+ "Content-Type": "application/json",
679
+ Authorization: `Bearer ${jwt}`
680
+ },
681
+ body: JSON.stringify({ email_change_token: token })
682
+ });
683
+ if (!res.ok) {
684
+ const errorBody = await res.json().catch(() => ({}));
685
+ throw new AuthError(
686
+ errorBody.msg || `Email change verification failed (${res.status})`,
687
+ res.status
688
+ );
689
+ }
690
+ const userData = await res.json();
691
+ const user = toUser(userData);
338
692
  emitAuthEvent("user_updated", user);
339
693
  return user;
340
694
  } catch (error) {
695
+ if (error instanceof AuthError) throw error;
341
696
  throw new AuthError(error.message, void 0, { cause: error });
342
697
  }
343
698
  };
344
699
  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");
700
+ const currentUser = await ensureCurrentUser();
348
701
  try {
349
702
  const updatedUser = await currentUser.update(updates);
350
703
  const user = toUser(updatedUser);
@@ -363,11 +716,13 @@ export {
363
716
  getSettings,
364
717
  getUser,
365
718
  handleAuthCallback,
719
+ hydrateSession,
366
720
  isAuthenticated,
367
721
  login,
368
722
  logout,
369
723
  oauthLogin,
370
724
  onAuthChange,
725
+ recoverPassword,
371
726
  requestPasswordRecovery,
372
727
  signup,
373
728
  updateUser,