@stackframe/stack 2.8.48 → 2.8.49
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 +10 -0
- 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 +10 -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 +207 -24
- 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 +1 -1
- package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- 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 +10 -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 +206 -23
- package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/stack-app/apps/implementations/common.js +1 -1
- package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
- package/dist/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
- package/package.json +4 -4
|
@@ -36,6 +36,7 @@ module.exports = __toCommonJS(client_app_impl_exports);
|
|
|
36
36
|
var import_browser = require("@simplewebauthn/browser");
|
|
37
37
|
var import_stack_shared = require("@stackframe/stack-shared");
|
|
38
38
|
var import_sessions = require("@stackframe/stack-shared/dist/sessions");
|
|
39
|
+
var import_bytes = require("@stackframe/stack-shared/dist/utils/bytes");
|
|
39
40
|
var import_compile_time = require("@stackframe/stack-shared/dist/utils/compile-time");
|
|
40
41
|
var import_env = require("@stackframe/stack-shared/dist/utils/env");
|
|
41
42
|
var import_errors = require("@stackframe/stack-shared/dist/utils/errors");
|
|
@@ -61,6 +62,7 @@ var import_projects = require("../../projects/index.js");
|
|
|
61
62
|
var import_teams = require("../../teams/index.js");
|
|
62
63
|
var import_users = require("../../users/index.js");
|
|
63
64
|
var import_common2 = require("./common.js");
|
|
65
|
+
var import_json = require("@stackframe/stack-shared/dist/utils/json");
|
|
64
66
|
var import_common3 = require("./common.js");
|
|
65
67
|
var sc = __toESM(require("@stackframe/stack-sc"));
|
|
66
68
|
var import_stack_sc = require("@stackframe/stack-sc");
|
|
@@ -214,11 +216,15 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
214
216
|
this._convexPartialUserCache = (0, import_common2.createCache)(
|
|
215
217
|
async ([ctx]) => await this._getPartialUserFromConvex(ctx)
|
|
216
218
|
);
|
|
219
|
+
this._trustedParentDomainCache = (0, import_common2.createCache)(
|
|
220
|
+
async ([domain]) => await this._getTrustedParentDomain(domain)
|
|
221
|
+
);
|
|
217
222
|
this._anonymousSignUpInProgress = null;
|
|
218
223
|
this._memoryTokenStore = (0, import_common2.createEmptyTokenStore)();
|
|
219
224
|
this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
|
|
220
225
|
this._requestTokenStores = /* @__PURE__ */ new WeakMap();
|
|
221
226
|
this._storedBrowserCookieTokenStore = null;
|
|
227
|
+
this._mostRecentQueuedCookieRefreshIndex = 0;
|
|
222
228
|
/**
|
|
223
229
|
* A map from token stores and session keys to sessions.
|
|
224
230
|
*
|
|
@@ -343,13 +349,90 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
343
349
|
(0, import_promises.runAsynchronously)(this._checkFeatureSupport(name, options));
|
|
344
350
|
throw new import_errors.StackAssertionError(`${name} is not currently supported. Please reach out to Stack support for more information.`);
|
|
345
351
|
}
|
|
352
|
+
get _legacyRefreshTokenCookieName() {
|
|
353
|
+
return `stack-refresh-${this.projectId}`;
|
|
354
|
+
}
|
|
346
355
|
get _refreshTokenCookieName() {
|
|
347
356
|
return `stack-refresh-${this.projectId}`;
|
|
348
357
|
}
|
|
358
|
+
_getRefreshTokenDefaultCookieNameForSecure(secure) {
|
|
359
|
+
return `${secure ? "__Host-" : ""}${this._refreshTokenCookieName}--default`;
|
|
360
|
+
}
|
|
361
|
+
_getCustomRefreshCookieName(domain) {
|
|
362
|
+
const encoded = (0, import_bytes.encodeBase32)(new TextEncoder().encode(domain.toLowerCase()));
|
|
363
|
+
return `${this._refreshTokenCookieName}--custom-${encoded}`;
|
|
364
|
+
}
|
|
365
|
+
_formatRefreshCookieValue(refreshToken, updatedAt) {
|
|
366
|
+
return JSON.stringify({
|
|
367
|
+
refresh_token: refreshToken,
|
|
368
|
+
updated_at_millis: updatedAt
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
_formatAccessCookieValue(refreshToken, accessToken) {
|
|
372
|
+
return refreshToken && accessToken ? JSON.stringify([refreshToken, accessToken]) : null;
|
|
373
|
+
}
|
|
374
|
+
_parseStructuredRefreshCookie(value) {
|
|
375
|
+
if (!value) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
const parsed = (0, import_json.parseJson)(value);
|
|
379
|
+
if (parsed.status !== "ok" || typeof parsed.data !== "object" || parsed.data === null) {
|
|
380
|
+
console.warn("Failed to parse structured refresh cookie");
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
const data = parsed.data;
|
|
384
|
+
const refreshToken = "refresh_token" in data && typeof data.refresh_token === "string" ? data.refresh_token : null;
|
|
385
|
+
const updatedAt = "updated_at_millis" in data && typeof data.updated_at_millis === "number" ? data.updated_at_millis : null;
|
|
386
|
+
if (!refreshToken) {
|
|
387
|
+
console.warn("Refresh token not found in structured refresh cookie");
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
refreshToken,
|
|
392
|
+
updatedAt
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
_extractRefreshTokenFromCookieMap(cookies2) {
|
|
396
|
+
const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
|
|
397
|
+
for (const name of legacyNames) {
|
|
398
|
+
const value = cookies2[name];
|
|
399
|
+
if (value) {
|
|
400
|
+
return { refreshToken: value, updatedAt: null };
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
let selected = null;
|
|
404
|
+
for (const [name, value] of Object.entries(cookies2)) {
|
|
405
|
+
if (!structuredPrefixes.some((prefix) => name.startsWith(prefix))) continue;
|
|
406
|
+
const parsed = this._parseStructuredRefreshCookie(value);
|
|
407
|
+
if (!parsed) continue;
|
|
408
|
+
const candidateUpdatedAt = parsed.updatedAt ?? Number.NEGATIVE_INFINITY;
|
|
409
|
+
const selectedUpdatedAt = selected?.updatedAt ?? Number.NEGATIVE_INFINITY;
|
|
410
|
+
if (!selected || candidateUpdatedAt > selectedUpdatedAt) {
|
|
411
|
+
selected = parsed;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!selected) {
|
|
415
|
+
return { refreshToken: null, updatedAt: null };
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
refreshToken: selected.refreshToken,
|
|
419
|
+
updatedAt: selected.updatedAt ?? null
|
|
420
|
+
};
|
|
421
|
+
}
|
|
349
422
|
_getTokensFromCookies(cookies2) {
|
|
350
|
-
const refreshToken = cookies2
|
|
351
|
-
const
|
|
352
|
-
|
|
423
|
+
const { refreshToken } = this._extractRefreshTokenFromCookieMap(cookies2);
|
|
424
|
+
const accessTokenCookie = cookies2[this._accessTokenCookieName] ?? null;
|
|
425
|
+
let accessToken = null;
|
|
426
|
+
if (accessTokenCookie && accessTokenCookie.startsWith('["')) {
|
|
427
|
+
const parsed = (0, import_json.parseJson)(accessTokenCookie);
|
|
428
|
+
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") {
|
|
429
|
+
if (parsed.data[0] === refreshToken) {
|
|
430
|
+
accessToken = parsed.data[1];
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
console.warn("Access token cookie has invalid format");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
353
436
|
return {
|
|
354
437
|
refreshToken,
|
|
355
438
|
accessToken
|
|
@@ -358,17 +441,98 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
358
441
|
get _accessTokenCookieName() {
|
|
359
442
|
return `stack-access`;
|
|
360
443
|
}
|
|
444
|
+
_getAllBrowserCookies() {
|
|
445
|
+
if (!(0, import_env.isBrowserLike)()) {
|
|
446
|
+
throw new import_errors.StackAssertionError("Cannot get browser cookies on the server!");
|
|
447
|
+
}
|
|
448
|
+
return cookie.parse(document.cookie || "");
|
|
449
|
+
}
|
|
450
|
+
_getRefreshTokenCookieNamePatterns() {
|
|
451
|
+
return {
|
|
452
|
+
legacyNames: [this._legacyRefreshTokenCookieName, "stack-refresh"],
|
|
453
|
+
structuredPrefixes: [
|
|
454
|
+
`${this._refreshTokenCookieName}--`,
|
|
455
|
+
`__Host-${this._refreshTokenCookieName}--`
|
|
456
|
+
]
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
_collectRefreshTokenCookieNames(cookies2) {
|
|
460
|
+
const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
|
|
461
|
+
const names = /* @__PURE__ */ new Set();
|
|
462
|
+
for (const name of legacyNames) {
|
|
463
|
+
if (cookies2[name]) {
|
|
464
|
+
names.add(name);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
for (const name of Object.keys(cookies2)) {
|
|
468
|
+
if (structuredPrefixes.some((prefix) => name.startsWith(prefix))) {
|
|
469
|
+
names.add(name);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return names;
|
|
473
|
+
}
|
|
474
|
+
_prepareRefreshCookieUpdate(existingCookies, refreshToken, accessToken, defaultCookieName) {
|
|
475
|
+
const cookieNames = this._collectRefreshTokenCookieNames(existingCookies);
|
|
476
|
+
cookieNames.delete(defaultCookieName);
|
|
477
|
+
const updatedAt = refreshToken ? Date.now() : null;
|
|
478
|
+
const refreshCookieValue = refreshToken && updatedAt !== null ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
|
|
479
|
+
const accessTokenPayload = this._formatAccessCookieValue(refreshToken, accessToken);
|
|
480
|
+
return {
|
|
481
|
+
updatedAt,
|
|
482
|
+
refreshCookieValue,
|
|
483
|
+
accessTokenPayload,
|
|
484
|
+
cookieNamesToDelete: [...cookieNames]
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
_queueCustomRefreshCookieUpdate(refreshToken, updatedAt, context) {
|
|
488
|
+
(0, import_promises.runAsynchronously)(async () => {
|
|
489
|
+
this._mostRecentQueuedCookieRefreshIndex++;
|
|
490
|
+
const updateIndex = this._mostRecentQueuedCookieRefreshIndex;
|
|
491
|
+
let hostname;
|
|
492
|
+
if ((0, import_env.isBrowserLike)()) {
|
|
493
|
+
hostname = window.location.hostname;
|
|
494
|
+
}
|
|
495
|
+
hostname = (await sc.headers?.())?.get("host");
|
|
496
|
+
if (!hostname) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const domain = await this._trustedParentDomainCache.getOrWait([hostname], "read-write");
|
|
500
|
+
const setCookie = async (targetDomain, value2) => {
|
|
501
|
+
const name = this._getCustomRefreshCookieName(targetDomain);
|
|
502
|
+
const options = { maxAge: 60 * 60 * 24 * 365, domain: targetDomain, noOpIfServerComponent: true };
|
|
503
|
+
if (context === "browser") {
|
|
504
|
+
(0, import_cookie.setOrDeleteCookieClient)(name, value2, options);
|
|
505
|
+
} else {
|
|
506
|
+
await (0, import_cookie.setOrDeleteCookie)(name, value2, options);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
if (domain.status === "error" || !domain.data || updateIndex !== this._mostRecentQueuedCookieRefreshIndex) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const value = refreshToken && updatedAt ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
|
|
513
|
+
await setCookie(domain.data, value);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
async _getTrustedParentDomain(currentDomain) {
|
|
517
|
+
const project = import_results.Result.orThrow(await this._interface.getClientProject());
|
|
518
|
+
const domains = project.config.domains.map((d) => d.domain.trim().replace(/^https?:\/\//, "").split("/")[0]?.toLowerCase());
|
|
519
|
+
const trustedWildcards = domains.filter((d) => d.startsWith("**."));
|
|
520
|
+
const parts = currentDomain.split(".");
|
|
521
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
522
|
+
const parentDomain = parts.slice(i).join(".");
|
|
523
|
+
if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) {
|
|
524
|
+
return parentDomain;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
361
529
|
_getBrowserCookieTokenStore() {
|
|
362
530
|
if (!(0, import_env.isBrowserLike)()) {
|
|
363
531
|
throw new Error("Cannot use cookie token store on the server!");
|
|
364
532
|
}
|
|
365
533
|
if (this._storedBrowserCookieTokenStore === null) {
|
|
366
534
|
const getCurrentValue = (old) => {
|
|
367
|
-
const tokens = this._getTokensFromCookies(
|
|
368
|
-
refreshTokenCookie: (0, import_cookie.getCookieClient)(this._refreshTokenCookieName) ?? (0, import_cookie.getCookieClient)("stack-refresh"),
|
|
369
|
-
// keep old cookie name for backwards-compatibility
|
|
370
|
-
accessTokenCookie: (0, import_cookie.getCookieClient)(this._accessTokenCookieName)
|
|
371
|
-
});
|
|
535
|
+
const tokens = this._getTokensFromCookies(this._getAllBrowserCookies());
|
|
372
536
|
return {
|
|
373
537
|
refreshToken: tokens.refreshToken,
|
|
374
538
|
accessToken: tokens.accessToken ?? (old?.refreshToken === tokens.refreshToken ? old.accessToken : null)
|
|
@@ -387,9 +551,19 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
387
551
|
}, 100);
|
|
388
552
|
this._storedBrowserCookieTokenStore.onChange((value) => {
|
|
389
553
|
try {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
554
|
+
const refreshToken = value.refreshToken;
|
|
555
|
+
const secure = window.location.protocol === "https:";
|
|
556
|
+
const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
|
|
557
|
+
const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
|
|
558
|
+
this._getAllBrowserCookies(),
|
|
559
|
+
refreshToken,
|
|
560
|
+
value.accessToken ?? null,
|
|
561
|
+
defaultName
|
|
562
|
+
);
|
|
563
|
+
(0, import_cookie.setOrDeleteCookieClient)(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, secure });
|
|
564
|
+
(0, import_cookie.setOrDeleteCookieClient)(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24 });
|
|
565
|
+
cookieNamesToDelete.forEach((name) => (0, import_cookie.deleteCookieClient)(name));
|
|
566
|
+
this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "browser");
|
|
393
567
|
hasSucceededInWriting = true;
|
|
394
568
|
} catch (e) {
|
|
395
569
|
if (!(0, import_env.isBrowserLike)()) {
|
|
@@ -412,18 +586,31 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
412
586
|
if ((0, import_env.isBrowserLike)()) {
|
|
413
587
|
return this._getBrowserCookieTokenStore();
|
|
414
588
|
} else {
|
|
415
|
-
const tokens = this._getTokensFromCookies(
|
|
416
|
-
refreshTokenCookie: cookieHelper.get(this._refreshTokenCookieName) ?? cookieHelper.get("stack-refresh"),
|
|
417
|
-
// keep old cookie name for backwards-compatibility
|
|
418
|
-
accessTokenCookie: cookieHelper.get(this._accessTokenCookieName)
|
|
419
|
-
});
|
|
589
|
+
const tokens = this._getTokensFromCookies(cookieHelper.getAll());
|
|
420
590
|
const store = new import_stores.Store(tokens);
|
|
421
591
|
store.onChange((value) => {
|
|
422
592
|
(0, import_promises.runAsynchronously)(async () => {
|
|
593
|
+
const refreshToken = value.refreshToken;
|
|
594
|
+
const secure = await (0, import_cookie.isSecure)();
|
|
595
|
+
const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
|
|
596
|
+
const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
|
|
597
|
+
cookieHelper.getAll(),
|
|
598
|
+
refreshToken,
|
|
599
|
+
value.accessToken ?? null,
|
|
600
|
+
defaultName
|
|
601
|
+
);
|
|
423
602
|
await Promise.all([
|
|
424
|
-
(0, import_cookie.setOrDeleteCookie)(
|
|
425
|
-
(0, import_cookie.setOrDeleteCookie)(this._accessTokenCookieName,
|
|
603
|
+
(0, import_cookie.setOrDeleteCookie)(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
|
|
604
|
+
(0, import_cookie.setOrDeleteCookie)(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
|
|
426
605
|
]);
|
|
606
|
+
if (cookieNamesToDelete.length > 0) {
|
|
607
|
+
await Promise.all(
|
|
608
|
+
cookieNamesToDelete.map(
|
|
609
|
+
(name) => (0, import_cookie.setOrDeleteCookie)(name, null, { noOpIfServerComponent: true })
|
|
610
|
+
)
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "server");
|
|
427
614
|
});
|
|
428
615
|
});
|
|
429
616
|
return store;
|
|
@@ -454,11 +641,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
|
|
|
454
641
|
}
|
|
455
642
|
const cookieHeader = tokenStoreInit.headers.get("cookie");
|
|
456
643
|
const parsed = cookie.parse(cookieHeader || "");
|
|
457
|
-
const res = new import_stores.Store(
|
|
458
|
-
refreshToken: parsed[this._refreshTokenCookieName] || parsed["stack-refresh"] || null,
|
|
459
|
-
// keep old cookie name for backwards-compatibility
|
|
460
|
-
accessToken: parsed[this._accessTokenCookieName] || null
|
|
461
|
-
});
|
|
644
|
+
const res = new import_stores.Store(this._getTokensFromCookies(parsed));
|
|
462
645
|
this._requestTokenStores.set(tokenStoreInit, res);
|
|
463
646
|
return res;
|
|
464
647
|
} else if ("accessToken" in tokenStoreInit || "refreshToken" in tokenStoreInit) {
|