@stackframe/js 2.8.48 → 2.8.51

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/esm/lib/cookie.js +36 -7
  3. package/dist/esm/lib/cookie.js.map +1 -1
  4. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
  5. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  6. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +255 -56
  7. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  8. package/dist/esm/lib/stack-app/apps/implementations/common.js +1 -1
  9. package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
  10. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +40 -1
  11. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  12. package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
  13. package/dist/esm/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
  14. package/dist/esm/lib/stack-app/common.js.map +1 -1
  15. package/dist/esm/lib/stack-app/users/index.js.map +1 -1
  16. package/dist/index.d.mts +97 -79
  17. package/dist/index.d.ts +97 -79
  18. package/dist/lib/cookie.js +38 -7
  19. package/dist/lib/cookie.js.map +1 -1
  20. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
  21. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  22. package/dist/lib/stack-app/apps/implementations/client-app-impl.js +254 -55
  23. package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  24. package/dist/lib/stack-app/apps/implementations/common.js +1 -1
  25. package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
  26. package/dist/lib/stack-app/apps/implementations/server-app-impl.js +49 -0
  27. package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  28. package/dist/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
  29. package/dist/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
  30. package/dist/lib/stack-app/common.js.map +1 -1
  31. package/dist/lib/stack-app/users/index.js.map +1 -1
  32. package/package.json +2 -2
@@ -2,6 +2,7 @@
2
2
  import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
3
3
  import { KnownErrors, StackClientInterface } from "@stackframe/stack-shared";
4
4
  import { InternalSession } from "@stackframe/stack-shared/dist/sessions";
5
+ import { encodeBase32 } from "@stackframe/stack-shared/dist/utils/bytes";
5
6
  import { isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
6
7
  import { StackAssertionError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
7
8
  import { DependenciesMap } from "@stackframe/stack-shared/dist/utils/maps";
@@ -15,7 +16,7 @@ import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
15
16
  import * as cookie from "cookie";
16
17
  import { constructRedirectUrl } from "../../../../utils/url.js";
17
18
  import { addNewOAuthProviderOrScope, callOAuthCallback, signInWithOAuth } from "../../../auth.js";
18
- import { createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient, getCookieClient, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
19
+ import { createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient, isSecure as isSecureCookieContext, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
19
20
  import { apiKeyCreationOptionsToCrud } from "../../api-keys/index.js";
20
21
  import { stackAppInternalsSymbol } from "../../common.js";
21
22
  import { contactChannelCreateOptionsToCrud, contactChannelUpdateOptionsToCrud } from "../../contact-channels/index.js";
@@ -23,6 +24,7 @@ import { adminProjectCreateOptionsToCrud } from "../../projects/index.js";
23
24
  import { teamCreateOptionsToCrud, teamUpdateOptionsToCrud } from "../../teams/index.js";
24
25
  import { attachUserDestructureGuard, userUpdateOptionsToCrud } from "../../users/index.js";
25
26
  import { clientVersion, createCache, createCacheBySession, createEmptyTokenStore, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getUrls, resolveConstructorOptions } from "./common.js";
27
+ import { parseJson } from "@stackframe/stack-shared/dist/utils/json";
26
28
  var isReactServer = false;
27
29
  var process = globalThis.process ?? { env: {} };
28
30
  var allClientApps = /* @__PURE__ */ new Map();
@@ -171,11 +173,15 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
171
173
  this._convexPartialUserCache = createCache(
172
174
  async ([ctx]) => await this._getPartialUserFromConvex(ctx)
173
175
  );
176
+ this._trustedParentDomainCache = createCache(
177
+ async ([domain]) => await this._getTrustedParentDomain(domain)
178
+ );
174
179
  this._anonymousSignUpInProgress = null;
175
180
  this._memoryTokenStore = createEmptyTokenStore();
176
181
  this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
177
182
  this._requestTokenStores = /* @__PURE__ */ new WeakMap();
178
183
  this._storedBrowserCookieTokenStore = null;
184
+ this._mostRecentQueuedCookieRefreshIndex = 0;
179
185
  /**
180
186
  * A map from token stores and session keys to sessions.
181
187
  *
@@ -191,13 +197,17 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
191
197
  }
192
198
  this._options = resolvedOptions;
193
199
  this._extraOptions = extraOptions;
200
+ const projectId = resolvedOptions.projectId ?? getDefaultProjectId();
201
+ if (projectId !== "internal" && !projectId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i)) {
202
+ throw new Error(`Invalid project ID: ${projectId}. Project IDs must be UUIDs. Please check your environment variables and/or your StackApp.`);
203
+ }
194
204
  if (extraOptions && extraOptions.interface) {
195
205
  this._interface = extraOptions.interface;
196
206
  } else {
197
207
  this._interface = new StackClientInterface({
198
208
  getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl),
199
209
  extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? getDefaultExtraRequestHeaders(),
200
- projectId: resolvedOptions.projectId ?? getDefaultProjectId(),
210
+ projectId,
201
211
  clientVersion,
202
212
  publishableClientKey: resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey(),
203
213
  prepareRequest: async () => {
@@ -291,13 +301,90 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
291
301
  runAsynchronously(this._checkFeatureSupport(name, options));
292
302
  throw new StackAssertionError(`${name} is not currently supported. Please reach out to Stack support for more information.`);
293
303
  }
304
+ get _legacyRefreshTokenCookieName() {
305
+ return `stack-refresh-${this.projectId}`;
306
+ }
294
307
  get _refreshTokenCookieName() {
295
308
  return `stack-refresh-${this.projectId}`;
296
309
  }
310
+ _getRefreshTokenDefaultCookieNameForSecure(secure) {
311
+ return `${secure ? "__Host-" : ""}${this._refreshTokenCookieName}--default`;
312
+ }
313
+ _getCustomRefreshCookieName(domain) {
314
+ const encoded = encodeBase32(new TextEncoder().encode(domain.toLowerCase()));
315
+ return `${this._refreshTokenCookieName}--custom-${encoded}`;
316
+ }
317
+ _formatRefreshCookieValue(refreshToken, updatedAt) {
318
+ return JSON.stringify({
319
+ refresh_token: refreshToken,
320
+ updated_at_millis: updatedAt
321
+ });
322
+ }
323
+ _formatAccessCookieValue(refreshToken, accessToken) {
324
+ return refreshToken && accessToken ? JSON.stringify([refreshToken, accessToken]) : null;
325
+ }
326
+ _parseStructuredRefreshCookie(value) {
327
+ if (!value) {
328
+ return null;
329
+ }
330
+ const parsed = parseJson(value);
331
+ if (parsed.status !== "ok" || typeof parsed.data !== "object" || parsed.data === null) {
332
+ console.warn("Failed to parse structured refresh cookie");
333
+ return null;
334
+ }
335
+ const data = parsed.data;
336
+ const refreshToken = "refresh_token" in data && typeof data.refresh_token === "string" ? data.refresh_token : null;
337
+ const updatedAt = "updated_at_millis" in data && typeof data.updated_at_millis === "number" ? data.updated_at_millis : null;
338
+ if (!refreshToken) {
339
+ console.warn("Refresh token not found in structured refresh cookie");
340
+ return null;
341
+ }
342
+ return {
343
+ refreshToken,
344
+ updatedAt
345
+ };
346
+ }
347
+ _extractRefreshTokenFromCookieMap(cookies) {
348
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
349
+ for (const name of legacyNames) {
350
+ const value = cookies[name];
351
+ if (value) {
352
+ return { refreshToken: value, updatedAt: null };
353
+ }
354
+ }
355
+ let selected = null;
356
+ for (const [name, value] of Object.entries(cookies)) {
357
+ if (!structuredPrefixes.some((prefix) => name.startsWith(prefix))) continue;
358
+ const parsed = this._parseStructuredRefreshCookie(value);
359
+ if (!parsed) continue;
360
+ const candidateUpdatedAt = parsed.updatedAt ?? Number.NEGATIVE_INFINITY;
361
+ const selectedUpdatedAt = selected?.updatedAt ?? Number.NEGATIVE_INFINITY;
362
+ if (!selected || candidateUpdatedAt > selectedUpdatedAt) {
363
+ selected = parsed;
364
+ }
365
+ }
366
+ if (!selected) {
367
+ return { refreshToken: null, updatedAt: null };
368
+ }
369
+ return {
370
+ refreshToken: selected.refreshToken,
371
+ updatedAt: selected.updatedAt ?? null
372
+ };
373
+ }
297
374
  _getTokensFromCookies(cookies) {
298
- const refreshToken = cookies.refreshTokenCookie;
299
- const accessTokenObject = cookies.accessTokenCookie?.startsWith('["') ? JSON.parse(cookies.accessTokenCookie) : null;
300
- const accessToken = accessTokenObject && refreshToken === accessTokenObject[0] ? accessTokenObject[1] : null;
375
+ const { refreshToken } = this._extractRefreshTokenFromCookieMap(cookies);
376
+ const accessTokenCookie = cookies[this._accessTokenCookieName] ?? null;
377
+ let accessToken = null;
378
+ if (accessTokenCookie && accessTokenCookie.startsWith('["')) {
379
+ const parsed = parseJson(accessTokenCookie);
380
+ if (parsed.status === "ok" && typeof parsed.data === "object" && parsed.data !== null && Array.isArray(parsed.data) && parsed.data.length === 2 && typeof parsed.data[0] === "string" && typeof parsed.data[1] === "string") {
381
+ if (parsed.data[0] === refreshToken) {
382
+ accessToken = parsed.data[1];
383
+ }
384
+ } else {
385
+ console.warn("Access token cookie has invalid format");
386
+ }
387
+ }
301
388
  return {
302
389
  refreshToken,
303
390
  accessToken
@@ -306,17 +393,98 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
306
393
  get _accessTokenCookieName() {
307
394
  return `stack-access`;
308
395
  }
396
+ _getAllBrowserCookies() {
397
+ if (!isBrowserLike()) {
398
+ throw new StackAssertionError("Cannot get browser cookies on the server!");
399
+ }
400
+ return cookie.parse(document.cookie || "");
401
+ }
402
+ _getRefreshTokenCookieNamePatterns() {
403
+ return {
404
+ legacyNames: [this._legacyRefreshTokenCookieName, "stack-refresh"],
405
+ structuredPrefixes: [
406
+ `${this._refreshTokenCookieName}--`,
407
+ `__Host-${this._refreshTokenCookieName}--`
408
+ ]
409
+ };
410
+ }
411
+ _collectRefreshTokenCookieNames(cookies) {
412
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
413
+ const names = /* @__PURE__ */ new Set();
414
+ for (const name of legacyNames) {
415
+ if (cookies[name]) {
416
+ names.add(name);
417
+ }
418
+ }
419
+ for (const name of Object.keys(cookies)) {
420
+ if (structuredPrefixes.some((prefix) => name.startsWith(prefix))) {
421
+ names.add(name);
422
+ }
423
+ }
424
+ return names;
425
+ }
426
+ _prepareRefreshCookieUpdate(existingCookies, refreshToken, accessToken, defaultCookieName) {
427
+ const cookieNames = this._collectRefreshTokenCookieNames(existingCookies);
428
+ cookieNames.delete(defaultCookieName);
429
+ const updatedAt = refreshToken ? Date.now() : null;
430
+ const refreshCookieValue = refreshToken && updatedAt !== null ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
431
+ const accessTokenPayload = this._formatAccessCookieValue(refreshToken, accessToken);
432
+ return {
433
+ updatedAt,
434
+ refreshCookieValue,
435
+ accessTokenPayload,
436
+ cookieNamesToDelete: [...cookieNames]
437
+ };
438
+ }
439
+ _queueCustomRefreshCookieUpdate(refreshToken, updatedAt, context) {
440
+ runAsynchronously(async () => {
441
+ this._mostRecentQueuedCookieRefreshIndex++;
442
+ const updateIndex = this._mostRecentQueuedCookieRefreshIndex;
443
+ let hostname;
444
+ if (isBrowserLike()) {
445
+ hostname = window.location.hostname;
446
+ }
447
+ if (!hostname) {
448
+ console.warn("No hostname found when queueing custom refresh cookie update");
449
+ return;
450
+ }
451
+ const domain = await this._trustedParentDomainCache.getOrWait([hostname], "read-write");
452
+ const setCookie = async (targetDomain, value2) => {
453
+ const name = this._getCustomRefreshCookieName(targetDomain);
454
+ const options = { maxAge: 60 * 60 * 24 * 365, domain: targetDomain, noOpIfServerComponent: true };
455
+ if (context === "browser") {
456
+ setOrDeleteCookieClient(name, value2, options);
457
+ } else {
458
+ await setOrDeleteCookie(name, value2, options);
459
+ }
460
+ };
461
+ if (domain.status === "error" || !domain.data || updateIndex !== this._mostRecentQueuedCookieRefreshIndex) {
462
+ return;
463
+ }
464
+ const value = refreshToken && updatedAt ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
465
+ await setCookie(domain.data, value);
466
+ });
467
+ }
468
+ async _getTrustedParentDomain(currentDomain) {
469
+ const project = Result.orThrow(await this._interface.getClientProject());
470
+ const domains = project.config.domains.map((d) => d.domain.trim().replace(/^https?:\/\//, "").split("/")[0]?.toLowerCase());
471
+ const trustedWildcards = domains.filter((d) => d.startsWith("**."));
472
+ const parts = currentDomain.split(".");
473
+ for (let i = parts.length - 2; i >= 0; i--) {
474
+ const parentDomain = parts.slice(i).join(".");
475
+ if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) {
476
+ return parentDomain;
477
+ }
478
+ }
479
+ return null;
480
+ }
309
481
  _getBrowserCookieTokenStore() {
310
482
  if (!isBrowserLike()) {
311
483
  throw new Error("Cannot use cookie token store on the server!");
312
484
  }
313
485
  if (this._storedBrowserCookieTokenStore === null) {
314
486
  const getCurrentValue = (old) => {
315
- const tokens = this._getTokensFromCookies({
316
- refreshTokenCookie: getCookieClient(this._refreshTokenCookieName) ?? getCookieClient("stack-refresh"),
317
- // keep old cookie name for backwards-compatibility
318
- accessTokenCookie: getCookieClient(this._accessTokenCookieName)
319
- });
487
+ const tokens = this._getTokensFromCookies(this._getAllBrowserCookies());
320
488
  return {
321
489
  refreshToken: tokens.refreshToken,
322
490
  accessToken: tokens.accessToken ?? (old?.refreshToken === tokens.refreshToken ? old.accessToken : null)
@@ -335,9 +503,19 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
335
503
  }, 100);
336
504
  this._storedBrowserCookieTokenStore.onChange((value) => {
337
505
  try {
338
- setOrDeleteCookieClient(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
339
- setOrDeleteCookieClient(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24 });
340
- deleteCookieClient("stack-refresh");
506
+ const refreshToken = value.refreshToken;
507
+ const secure = window.location.protocol === "https:";
508
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
509
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
510
+ this._getAllBrowserCookies(),
511
+ refreshToken,
512
+ value.accessToken ?? null,
513
+ defaultName
514
+ );
515
+ setOrDeleteCookieClient(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, secure });
516
+ setOrDeleteCookieClient(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24 });
517
+ cookieNamesToDelete.forEach((name) => deleteCookieClient(name));
518
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "browser");
341
519
  hasSucceededInWriting = true;
342
520
  } catch (e) {
343
521
  if (!isBrowserLike()) {
@@ -360,18 +538,31 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
360
538
  if (isBrowserLike()) {
361
539
  return this._getBrowserCookieTokenStore();
362
540
  } else {
363
- const tokens = this._getTokensFromCookies({
364
- refreshTokenCookie: cookieHelper.get(this._refreshTokenCookieName) ?? cookieHelper.get("stack-refresh"),
365
- // keep old cookie name for backwards-compatibility
366
- accessTokenCookie: cookieHelper.get(this._accessTokenCookieName)
367
- });
541
+ const tokens = this._getTokensFromCookies(cookieHelper.getAll());
368
542
  const store = new Store(tokens);
369
543
  store.onChange((value) => {
370
544
  runAsynchronously(async () => {
545
+ const refreshToken = value.refreshToken;
546
+ const secure = await isSecureCookieContext();
547
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
548
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
549
+ cookieHelper.getAll(),
550
+ refreshToken,
551
+ value.accessToken ?? null,
552
+ defaultName
553
+ );
371
554
  await Promise.all([
372
- setOrDeleteCookie(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
373
- setOrDeleteCookie(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
555
+ setOrDeleteCookie(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
556
+ setOrDeleteCookie(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
374
557
  ]);
558
+ if (cookieNamesToDelete.length > 0) {
559
+ await Promise.all(
560
+ cookieNamesToDelete.map(
561
+ (name) => setOrDeleteCookie(name, null, { noOpIfServerComponent: true })
562
+ )
563
+ );
564
+ }
565
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "server");
375
566
  });
376
567
  });
377
568
  return store;
@@ -402,11 +593,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
402
593
  }
403
594
  const cookieHeader = tokenStoreInit.headers.get("cookie");
404
595
  const parsed = cookie.parse(cookieHeader || "");
405
- const res = new Store({
406
- refreshToken: parsed[this._refreshTokenCookieName] || parsed["stack-refresh"] || null,
407
- // keep old cookie name for backwards-compatibility
408
- accessToken: parsed[this._accessTokenCookieName] || null
409
- });
596
+ const res = new Store(this._getTokensFromCookies(parsed));
410
597
  this._requestTokenStores.set(tokenStoreInit, res);
411
598
  return res;
412
599
  } else if ("accessToken" in tokenStoreInit || "refreshToken" in tokenStoreInit) {
@@ -723,35 +910,6 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
723
910
  const tokens = await this.currentSession.getTokens();
724
911
  return tokens;
725
912
  },
726
- async registerPasskey(options) {
727
- const hostname = (await app._getCurrentUrl())?.hostname;
728
- if (!hostname) {
729
- throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
730
- }
731
- const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
732
- if (initiationResult.status !== "ok") {
733
- return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
734
- }
735
- const { options_json, code } = initiationResult.data;
736
- if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
737
- throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
738
- }
739
- options_json.rp.id = hostname;
740
- let attResp;
741
- try {
742
- attResp = await startRegistration({ optionsJSON: options_json });
743
- } catch (error) {
744
- if (error instanceof WebAuthnError) {
745
- return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
746
- } else {
747
- captureError("passkey-registration-failed", error);
748
- return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
749
- }
750
- }
751
- const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
752
- await app._refreshUser(session);
753
- return registrationResult;
754
- },
755
913
  signOut(options) {
756
914
  return app._signOut(session, options);
757
915
  }
@@ -936,6 +1094,35 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
936
1094
  async getOAuthProvider(id) {
937
1095
  const providers = await this.listOAuthProviders();
938
1096
  return providers.find((p) => p.id === id) ?? null;
1097
+ },
1098
+ async registerPasskey(options) {
1099
+ const hostname = (await app._getCurrentUrl())?.hostname;
1100
+ if (!hostname) {
1101
+ throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
1102
+ }
1103
+ const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
1104
+ if (initiationResult.status !== "ok") {
1105
+ return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
1106
+ }
1107
+ const { options_json, code } = initiationResult.data;
1108
+ if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
1109
+ throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
1110
+ }
1111
+ options_json.rp.id = hostname;
1112
+ let attResp;
1113
+ try {
1114
+ attResp = await startRegistration({ optionsJSON: options_json });
1115
+ } catch (error) {
1116
+ if (error instanceof WebAuthnError) {
1117
+ return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
1118
+ } else {
1119
+ captureError("passkey-registration-failed", error);
1120
+ return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
1121
+ }
1122
+ }
1123
+ const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
1124
+ await app._refreshUser(session);
1125
+ return registrationResult;
939
1126
  }
940
1127
  };
941
1128
  }
@@ -1618,10 +1805,22 @@ ${url}`);
1618
1805
  });
1619
1806
  }
1620
1807
  async signOut(options) {
1621
- const user = await this.getUser();
1808
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
1809
+ if (user) {
1810
+ await user.signOut({ redirectUrl: options?.redirectUrl });
1811
+ }
1812
+ }
1813
+ async getAuthHeaders(options) {
1814
+ return {
1815
+ "x-stack-auth": JSON.stringify(await this.getAuthJson(options))
1816
+ };
1817
+ }
1818
+ async getAuthJson(options) {
1819
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
1622
1820
  if (user) {
1623
- await user.signOut(options);
1821
+ return await user.getAuthJson();
1624
1822
  }
1823
+ return { accessToken: null, refreshToken: null };
1625
1824
  }
1626
1825
  async getProject() {
1627
1826
  const crud = Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));