@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.
- package/CHANGELOG.md +30 -0
- package/dist/components-page/password-reset.js +6 -5
- package/dist/components-page/password-reset.js.map +1 -1
- package/dist/components-page/sign-out.js +2 -12
- package/dist/components-page/sign-out.js.map +1 -1
- package/dist/components-page/stack-handler-client.js +266 -0
- package/dist/components-page/stack-handler-client.js.map +1 -0
- package/dist/components-page/stack-handler.js +4 -243
- package/dist/components-page/stack-handler.js.map +1 -1
- package/dist/components-page/team-invitation.js +6 -5
- package/dist/components-page/team-invitation.js.map +1 -1
- package/dist/esm/components-page/password-reset.js +3 -2
- package/dist/esm/components-page/password-reset.js.map +1 -1
- package/dist/esm/components-page/sign-out.js +2 -2
- package/dist/esm/components-page/sign-out.js.map +1 -1
- package/dist/esm/components-page/stack-handler-client.js +242 -0
- package/dist/esm/components-page/stack-handler-client.js.map +1 -0
- package/dist/esm/components-page/stack-handler.js +5 -244
- package/dist/esm/components-page/stack-handler.js.map +1 -1
- package/dist/esm/components-page/team-invitation.js +3 -2
- package/dist/esm/components-page/team-invitation.js.map +1 -1
- package/dist/esm/lib/cookie.js +57 -14
- package/dist/esm/lib/cookie.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
- package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +257 -56
- package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/common.js +3 -3
- package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +39 -0
- package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/esm/lib/stack-app/common.js.map +1 -1
- package/dist/esm/lib/stack-app/users/index.js.map +1 -1
- package/dist/index.d.mts +124 -98
- package/dist/index.d.ts +124 -98
- package/dist/lib/cookie.js +59 -14
- package/dist/lib/cookie.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
- package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js +256 -55
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/common.js +2 -2
- package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/server-app-impl.js +49 -0
- package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
- package/dist/lib/stack-app/common.js.map +1 -1
- package/dist/lib/stack-app/users/index.js.map +1 -1
- 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,
|
|
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
|
|
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
|
|
317
|
-
const
|
|
318
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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(
|
|
391
|
-
setOrDeleteCookie(this._accessTokenCookieName,
|
|
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.
|
|
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"));
|