@stackframe/stack 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 (52) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/components-page/password-reset.js +6 -5
  3. package/dist/components-page/password-reset.js.map +1 -1
  4. package/dist/components-page/sign-out.js +2 -12
  5. package/dist/components-page/sign-out.js.map +1 -1
  6. package/dist/components-page/stack-handler-client.js +266 -0
  7. package/dist/components-page/stack-handler-client.js.map +1 -0
  8. package/dist/components-page/stack-handler.js +4 -243
  9. package/dist/components-page/stack-handler.js.map +1 -1
  10. package/dist/components-page/team-invitation.js +6 -5
  11. package/dist/components-page/team-invitation.js.map +1 -1
  12. package/dist/esm/components-page/password-reset.js +3 -2
  13. package/dist/esm/components-page/password-reset.js.map +1 -1
  14. package/dist/esm/components-page/sign-out.js +2 -2
  15. package/dist/esm/components-page/sign-out.js.map +1 -1
  16. package/dist/esm/components-page/stack-handler-client.js +242 -0
  17. package/dist/esm/components-page/stack-handler-client.js.map +1 -0
  18. package/dist/esm/components-page/stack-handler.js +5 -244
  19. package/dist/esm/components-page/stack-handler.js.map +1 -1
  20. package/dist/esm/components-page/team-invitation.js +3 -2
  21. package/dist/esm/components-page/team-invitation.js.map +1 -1
  22. package/dist/esm/lib/cookie.js +57 -14
  23. package/dist/esm/lib/cookie.js.map +1 -1
  24. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
  25. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  26. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +257 -56
  27. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  28. package/dist/esm/lib/stack-app/apps/implementations/common.js +3 -3
  29. package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
  30. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +39 -0
  31. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  32. package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
  33. package/dist/esm/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
  34. package/dist/esm/lib/stack-app/common.js.map +1 -1
  35. package/dist/esm/lib/stack-app/users/index.js.map +1 -1
  36. package/dist/index.d.mts +124 -98
  37. package/dist/index.d.ts +124 -98
  38. package/dist/lib/cookie.js +59 -14
  39. package/dist/lib/cookie.js.map +1 -1
  40. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
  41. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  42. package/dist/lib/stack-app/apps/implementations/client-app-impl.js +256 -55
  43. package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  44. package/dist/lib/stack-app/apps/implementations/common.js +2 -2
  45. package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
  46. package/dist/lib/stack-app/apps/implementations/server-app-impl.js +49 -0
  47. package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  48. package/dist/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
  49. package/dist/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
  50. package/dist/lib/stack-app/common.js.map +1 -1
  51. package/dist/lib/stack-app/users/index.js.map +1 -1
  52. package/package.json +8 -8
@@ -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 { scrambleDuringCompileTime } from "@stackframe/stack-shared/dist/utils/compile-time";
6
7
  import { isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
7
8
  import { StackAssertionError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
@@ -19,7 +20,7 @@ import * as NextNavigationUnscrambled from "next/navigation";
19
20
  import React, { useCallback, useMemo } from "react";
20
21
  import { constructRedirectUrl } from "../../../../utils/url.js";
21
22
  import { addNewOAuthProviderOrScope, callOAuthCallback, signInWithOAuth } from "../../../auth.js";
22
- import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient, getCookieClient, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
23
+ import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient, isSecure as isSecureCookieContext, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
23
24
  import { apiKeyCreationOptionsToCrud } from "../../api-keys/index.js";
24
25
  import { stackAppInternalsSymbol } from "../../common.js";
25
26
  import { contactChannelCreateOptionsToCrud, contactChannelUpdateOptionsToCrud } from "../../contact-channels/index.js";
@@ -27,6 +28,7 @@ import { adminProjectCreateOptionsToCrud } from "../../projects/index.js";
27
28
  import { teamCreateOptionsToCrud, teamUpdateOptionsToCrud } from "../../teams/index.js";
28
29
  import { attachUserDestructureGuard, userUpdateOptionsToCrud } from "../../users/index.js";
29
30
  import { clientVersion, createCache, createCacheBySession, createEmptyTokenStore, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getUrls, resolveConstructorOptions } from "./common.js";
31
+ import { parseJson } from "@stackframe/stack-shared/dist/utils/json";
30
32
  import { useAsyncCache } from "./common.js";
31
33
  import * as sc from "@stackframe/stack-sc";
32
34
  import { cookies } from "@stackframe/stack-sc";
@@ -180,11 +182,15 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
180
182
  this._convexPartialUserCache = createCache(
181
183
  async ([ctx]) => await this._getPartialUserFromConvex(ctx)
182
184
  );
185
+ this._trustedParentDomainCache = createCache(
186
+ async ([domain]) => await this._getTrustedParentDomain(domain)
187
+ );
183
188
  this._anonymousSignUpInProgress = null;
184
189
  this._memoryTokenStore = createEmptyTokenStore();
185
190
  this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
186
191
  this._requestTokenStores = /* @__PURE__ */ new WeakMap();
187
192
  this._storedBrowserCookieTokenStore = null;
193
+ this._mostRecentQueuedCookieRefreshIndex = 0;
188
194
  /**
189
195
  * A map from token stores and session keys to sessions.
190
196
  *
@@ -200,13 +206,17 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
200
206
  }
201
207
  this._options = resolvedOptions;
202
208
  this._extraOptions = extraOptions;
209
+ const projectId = resolvedOptions.projectId ?? getDefaultProjectId();
210
+ 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)) {
211
+ throw new Error(`Invalid project ID: ${projectId}. Project IDs must be UUIDs. Please check your environment variables and/or your StackApp.`);
212
+ }
203
213
  if (extraOptions && extraOptions.interface) {
204
214
  this._interface = extraOptions.interface;
205
215
  } else {
206
216
  this._interface = new StackClientInterface({
207
217
  getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl),
208
218
  extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? getDefaultExtraRequestHeaders(),
209
- projectId: resolvedOptions.projectId ?? getDefaultProjectId(),
219
+ projectId,
210
220
  clientVersion,
211
221
  publishableClientKey: resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey(),
212
222
  prepareRequest: async () => {
@@ -309,13 +319,90 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
309
319
  runAsynchronously(this._checkFeatureSupport(name, options));
310
320
  throw new StackAssertionError(`${name} is not currently supported. Please reach out to Stack support for more information.`);
311
321
  }
322
+ get _legacyRefreshTokenCookieName() {
323
+ return `stack-refresh-${this.projectId}`;
324
+ }
312
325
  get _refreshTokenCookieName() {
313
326
  return `stack-refresh-${this.projectId}`;
314
327
  }
328
+ _getRefreshTokenDefaultCookieNameForSecure(secure) {
329
+ return `${secure ? "__Host-" : ""}${this._refreshTokenCookieName}--default`;
330
+ }
331
+ _getCustomRefreshCookieName(domain) {
332
+ const encoded = encodeBase32(new TextEncoder().encode(domain.toLowerCase()));
333
+ return `${this._refreshTokenCookieName}--custom-${encoded}`;
334
+ }
335
+ _formatRefreshCookieValue(refreshToken, updatedAt) {
336
+ return JSON.stringify({
337
+ refresh_token: refreshToken,
338
+ updated_at_millis: updatedAt
339
+ });
340
+ }
341
+ _formatAccessCookieValue(refreshToken, accessToken) {
342
+ return refreshToken && accessToken ? JSON.stringify([refreshToken, accessToken]) : null;
343
+ }
344
+ _parseStructuredRefreshCookie(value) {
345
+ if (!value) {
346
+ return null;
347
+ }
348
+ const parsed = parseJson(value);
349
+ if (parsed.status !== "ok" || typeof parsed.data !== "object" || parsed.data === null) {
350
+ console.warn("Failed to parse structured refresh cookie");
351
+ return null;
352
+ }
353
+ const data = parsed.data;
354
+ const refreshToken = "refresh_token" in data && typeof data.refresh_token === "string" ? data.refresh_token : null;
355
+ const updatedAt = "updated_at_millis" in data && typeof data.updated_at_millis === "number" ? data.updated_at_millis : null;
356
+ if (!refreshToken) {
357
+ console.warn("Refresh token not found in structured refresh cookie");
358
+ return null;
359
+ }
360
+ return {
361
+ refreshToken,
362
+ updatedAt
363
+ };
364
+ }
365
+ _extractRefreshTokenFromCookieMap(cookies2) {
366
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
367
+ for (const name of legacyNames) {
368
+ const value = cookies2[name];
369
+ if (value) {
370
+ return { refreshToken: value, updatedAt: null };
371
+ }
372
+ }
373
+ let selected = null;
374
+ for (const [name, value] of Object.entries(cookies2)) {
375
+ if (!structuredPrefixes.some((prefix) => name.startsWith(prefix))) continue;
376
+ const parsed = this._parseStructuredRefreshCookie(value);
377
+ if (!parsed) continue;
378
+ const candidateUpdatedAt = parsed.updatedAt ?? Number.NEGATIVE_INFINITY;
379
+ const selectedUpdatedAt = selected?.updatedAt ?? Number.NEGATIVE_INFINITY;
380
+ if (!selected || candidateUpdatedAt > selectedUpdatedAt) {
381
+ selected = parsed;
382
+ }
383
+ }
384
+ if (!selected) {
385
+ return { refreshToken: null, updatedAt: null };
386
+ }
387
+ return {
388
+ refreshToken: selected.refreshToken,
389
+ updatedAt: selected.updatedAt ?? null
390
+ };
391
+ }
315
392
  _getTokensFromCookies(cookies2) {
316
- const refreshToken = cookies2.refreshTokenCookie;
317
- const accessTokenObject = cookies2.accessTokenCookie?.startsWith('["') ? JSON.parse(cookies2.accessTokenCookie) : null;
318
- const accessToken = accessTokenObject && refreshToken === accessTokenObject[0] ? accessTokenObject[1] : null;
393
+ const { refreshToken } = this._extractRefreshTokenFromCookieMap(cookies2);
394
+ const accessTokenCookie = cookies2[this._accessTokenCookieName] ?? null;
395
+ let accessToken = null;
396
+ if (accessTokenCookie && accessTokenCookie.startsWith('["')) {
397
+ const parsed = parseJson(accessTokenCookie);
398
+ 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") {
399
+ if (parsed.data[0] === refreshToken) {
400
+ accessToken = parsed.data[1];
401
+ }
402
+ } else {
403
+ console.warn("Access token cookie has invalid format");
404
+ }
405
+ }
319
406
  return {
320
407
  refreshToken,
321
408
  accessToken
@@ -324,17 +411,100 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
324
411
  get _accessTokenCookieName() {
325
412
  return `stack-access`;
326
413
  }
414
+ _getAllBrowserCookies() {
415
+ if (!isBrowserLike()) {
416
+ throw new StackAssertionError("Cannot get browser cookies on the server!");
417
+ }
418
+ return cookie.parse(document.cookie || "");
419
+ }
420
+ _getRefreshTokenCookieNamePatterns() {
421
+ return {
422
+ legacyNames: [this._legacyRefreshTokenCookieName, "stack-refresh"],
423
+ structuredPrefixes: [
424
+ `${this._refreshTokenCookieName}--`,
425
+ `__Host-${this._refreshTokenCookieName}--`
426
+ ]
427
+ };
428
+ }
429
+ _collectRefreshTokenCookieNames(cookies2) {
430
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
431
+ const names = /* @__PURE__ */ new Set();
432
+ for (const name of legacyNames) {
433
+ if (cookies2[name]) {
434
+ names.add(name);
435
+ }
436
+ }
437
+ for (const name of Object.keys(cookies2)) {
438
+ if (structuredPrefixes.some((prefix) => name.startsWith(prefix))) {
439
+ names.add(name);
440
+ }
441
+ }
442
+ return names;
443
+ }
444
+ _prepareRefreshCookieUpdate(existingCookies, refreshToken, accessToken, defaultCookieName) {
445
+ const cookieNames = this._collectRefreshTokenCookieNames(existingCookies);
446
+ cookieNames.delete(defaultCookieName);
447
+ const updatedAt = refreshToken ? Date.now() : null;
448
+ const refreshCookieValue = refreshToken && updatedAt !== null ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
449
+ const accessTokenPayload = this._formatAccessCookieValue(refreshToken, accessToken);
450
+ return {
451
+ updatedAt,
452
+ refreshCookieValue,
453
+ accessTokenPayload,
454
+ cookieNamesToDelete: [...cookieNames]
455
+ };
456
+ }
457
+ _queueCustomRefreshCookieUpdate(refreshToken, updatedAt, context) {
458
+ runAsynchronously(async () => {
459
+ this._mostRecentQueuedCookieRefreshIndex++;
460
+ const updateIndex = this._mostRecentQueuedCookieRefreshIndex;
461
+ let hostname;
462
+ if (isBrowserLike()) {
463
+ hostname = window.location.hostname;
464
+ } else {
465
+ hostname = (await sc.headers?.())?.get("host");
466
+ }
467
+ if (!hostname) {
468
+ console.warn("No hostname found when queueing custom refresh cookie update");
469
+ return;
470
+ }
471
+ const domain = await this._trustedParentDomainCache.getOrWait([hostname], "read-write");
472
+ const setCookie = async (targetDomain, value2) => {
473
+ const name = this._getCustomRefreshCookieName(targetDomain);
474
+ const options = { maxAge: 60 * 60 * 24 * 365, domain: targetDomain, noOpIfServerComponent: true };
475
+ if (context === "browser") {
476
+ setOrDeleteCookieClient(name, value2, options);
477
+ } else {
478
+ await setOrDeleteCookie(name, value2, options);
479
+ }
480
+ };
481
+ if (domain.status === "error" || !domain.data || updateIndex !== this._mostRecentQueuedCookieRefreshIndex) {
482
+ return;
483
+ }
484
+ const value = refreshToken && updatedAt ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
485
+ await setCookie(domain.data, value);
486
+ });
487
+ }
488
+ async _getTrustedParentDomain(currentDomain) {
489
+ const project = Result.orThrow(await this._interface.getClientProject());
490
+ const domains = project.config.domains.map((d) => d.domain.trim().replace(/^https?:\/\//, "").split("/")[0]?.toLowerCase());
491
+ const trustedWildcards = domains.filter((d) => d.startsWith("**."));
492
+ const parts = currentDomain.split(".");
493
+ for (let i = parts.length - 2; i >= 0; i--) {
494
+ const parentDomain = parts.slice(i).join(".");
495
+ if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) {
496
+ return parentDomain;
497
+ }
498
+ }
499
+ return null;
500
+ }
327
501
  _getBrowserCookieTokenStore() {
328
502
  if (!isBrowserLike()) {
329
503
  throw new Error("Cannot use cookie token store on the server!");
330
504
  }
331
505
  if (this._storedBrowserCookieTokenStore === null) {
332
506
  const getCurrentValue = (old) => {
333
- const tokens = this._getTokensFromCookies({
334
- refreshTokenCookie: getCookieClient(this._refreshTokenCookieName) ?? getCookieClient("stack-refresh"),
335
- // keep old cookie name for backwards-compatibility
336
- accessTokenCookie: getCookieClient(this._accessTokenCookieName)
337
- });
507
+ const tokens = this._getTokensFromCookies(this._getAllBrowserCookies());
338
508
  return {
339
509
  refreshToken: tokens.refreshToken,
340
510
  accessToken: tokens.accessToken ?? (old?.refreshToken === tokens.refreshToken ? old.accessToken : null)
@@ -353,9 +523,19 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
353
523
  }, 100);
354
524
  this._storedBrowserCookieTokenStore.onChange((value) => {
355
525
  try {
356
- setOrDeleteCookieClient(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
357
- setOrDeleteCookieClient(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24 });
358
- deleteCookieClient("stack-refresh");
526
+ const refreshToken = value.refreshToken;
527
+ const secure = window.location.protocol === "https:";
528
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
529
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
530
+ this._getAllBrowserCookies(),
531
+ refreshToken,
532
+ value.accessToken ?? null,
533
+ defaultName
534
+ );
535
+ setOrDeleteCookieClient(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, secure });
536
+ setOrDeleteCookieClient(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24 });
537
+ cookieNamesToDelete.forEach((name) => deleteCookieClient(name));
538
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "browser");
359
539
  hasSucceededInWriting = true;
360
540
  } catch (e) {
361
541
  if (!isBrowserLike()) {
@@ -378,18 +558,31 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
378
558
  if (isBrowserLike()) {
379
559
  return this._getBrowserCookieTokenStore();
380
560
  } else {
381
- const tokens = this._getTokensFromCookies({
382
- refreshTokenCookie: cookieHelper.get(this._refreshTokenCookieName) ?? cookieHelper.get("stack-refresh"),
383
- // keep old cookie name for backwards-compatibility
384
- accessTokenCookie: cookieHelper.get(this._accessTokenCookieName)
385
- });
561
+ const tokens = this._getTokensFromCookies(cookieHelper.getAll());
386
562
  const store = new Store(tokens);
387
563
  store.onChange((value) => {
388
564
  runAsynchronously(async () => {
565
+ const refreshToken = value.refreshToken;
566
+ const secure = await isSecureCookieContext();
567
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
568
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
569
+ cookieHelper.getAll(),
570
+ refreshToken,
571
+ value.accessToken ?? null,
572
+ defaultName
573
+ );
389
574
  await Promise.all([
390
- setOrDeleteCookie(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
391
- setOrDeleteCookie(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
575
+ setOrDeleteCookie(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
576
+ setOrDeleteCookie(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
392
577
  ]);
578
+ if (cookieNamesToDelete.length > 0) {
579
+ await Promise.all(
580
+ cookieNamesToDelete.map(
581
+ (name) => setOrDeleteCookie(name, null, { noOpIfServerComponent: true })
582
+ )
583
+ );
584
+ }
585
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "server");
393
586
  });
394
587
  });
395
588
  return store;
@@ -420,11 +613,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
420
613
  }
421
614
  const cookieHeader = tokenStoreInit.headers.get("cookie");
422
615
  const parsed = cookie.parse(cookieHeader || "");
423
- const res = new Store({
424
- refreshToken: parsed[this._refreshTokenCookieName] || parsed["stack-refresh"] || null,
425
- // keep old cookie name for backwards-compatibility
426
- accessToken: parsed[this._accessTokenCookieName] || null
427
- });
616
+ const res = new Store(this._getTokensFromCookies(parsed));
428
617
  this._requestTokenStores.set(tokenStoreInit, res);
429
618
  return res;
430
619
  } else if ("accessToken" in tokenStoreInit || "refreshToken" in tokenStoreInit) {
@@ -770,35 +959,6 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
770
959
  const tokens = await this.currentSession.getTokens();
771
960
  return tokens;
772
961
  },
773
- async registerPasskey(options) {
774
- const hostname = (await app._getCurrentUrl())?.hostname;
775
- if (!hostname) {
776
- throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
777
- }
778
- const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
779
- if (initiationResult.status !== "ok") {
780
- return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
781
- }
782
- const { options_json, code } = initiationResult.data;
783
- if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
784
- throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
785
- }
786
- options_json.rp.id = hostname;
787
- let attResp;
788
- try {
789
- attResp = await startRegistration({ optionsJSON: options_json });
790
- } catch (error) {
791
- if (error instanceof WebAuthnError) {
792
- return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
793
- } else {
794
- captureError("passkey-registration-failed", error);
795
- return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
796
- }
797
- }
798
- const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
799
- await app._refreshUser(session);
800
- return registrationResult;
801
- },
802
962
  signOut(options) {
803
963
  return app._signOut(session, options);
804
964
  }
@@ -1047,6 +1207,35 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
1047
1207
  async getOAuthProvider(id) {
1048
1208
  const providers = await this.listOAuthProviders();
1049
1209
  return providers.find((p) => p.id === id) ?? null;
1210
+ },
1211
+ async registerPasskey(options) {
1212
+ const hostname = (await app._getCurrentUrl())?.hostname;
1213
+ if (!hostname) {
1214
+ throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
1215
+ }
1216
+ const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
1217
+ if (initiationResult.status !== "ok") {
1218
+ return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
1219
+ }
1220
+ const { options_json, code } = initiationResult.data;
1221
+ if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
1222
+ throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
1223
+ }
1224
+ options_json.rp.id = hostname;
1225
+ let attResp;
1226
+ try {
1227
+ attResp = await startRegistration({ optionsJSON: options_json });
1228
+ } catch (error) {
1229
+ if (error instanceof WebAuthnError) {
1230
+ return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
1231
+ } else {
1232
+ captureError("passkey-registration-failed", error);
1233
+ return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
1234
+ }
1235
+ }
1236
+ const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
1237
+ await app._refreshUser(session);
1238
+ return registrationResult;
1050
1239
  }
1051
1240
  };
1052
1241
  }
@@ -1820,10 +2009,22 @@ ${url}`);
1820
2009
  });
1821
2010
  }
1822
2011
  async signOut(options) {
1823
- const user = await this.getUser();
2012
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
2013
+ if (user) {
2014
+ await user.signOut({ redirectUrl: options?.redirectUrl });
2015
+ }
2016
+ }
2017
+ async getAuthHeaders(options) {
2018
+ return {
2019
+ "x-stack-auth": JSON.stringify(await this.getAuthJson(options))
2020
+ };
2021
+ }
2022
+ async getAuthJson(options) {
2023
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
1824
2024
  if (user) {
1825
- await user.signOut(options);
2025
+ return await user.getAuthJson();
1826
2026
  }
2027
+ return { accessToken: null, refreshToken: null };
1827
2028
  }
1828
2029
  async getProject() {
1829
2030
  const crud = Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));