@mastra/auth-studio 1.3.10 → 1.3.11-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_types/@internal_auth/dist/_types/@internal_core/dist/base/index.d.ts +1 -2
- package/dist/_types/@internal_auth/dist/session/index.d.ts +9 -0
- package/dist/index.cjs +126 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +56 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -31
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as IMastraLogger, u as RegisteredLogger } from "../index-Biaf3BmX.js";
|
|
2
2
|
//#region src/base/MastraBase.d.ts
|
|
3
|
-
declare class MastraBase {
|
|
3
|
+
export declare class MastraBase {
|
|
4
4
|
#private;
|
|
5
5
|
component: RegisteredLogger;
|
|
6
6
|
protected logger: IMastraLogger;
|
|
@@ -27,5 +27,4 @@ declare class MastraBase {
|
|
|
27
27
|
__setLogger(logger: IMastraLogger): void;
|
|
28
28
|
}
|
|
29
29
|
//#endregion
|
|
30
|
-
export { MastraBase };
|
|
31
30
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -24,6 +24,15 @@ export interface ISessionProvider<TSession extends Session = Session> {
|
|
|
24
24
|
getSessionIdFromRequest(request: Request): string | null;
|
|
25
25
|
getSessionHeaders(session: TSession): Record<string, string>;
|
|
26
26
|
getClearSessionHeaders(): Record<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Optional: `Set-Cookie` values expiring legacy copies of the session cookie
|
|
29
|
+
* (for example one an earlier release scoped to a shared parent `Domain`).
|
|
30
|
+
* A browser only deletes a cookie whose exact `(name, Domain, Path)` match,
|
|
31
|
+
* so servers append these to every response that sets or clears the session
|
|
32
|
+
* cookie; otherwise a stale shared-domain copy keeps being sent to every
|
|
33
|
+
* sibling host until it expires.
|
|
34
|
+
*/
|
|
35
|
+
getLegacySessionClearCookies?(): string[];
|
|
27
36
|
}
|
|
28
37
|
export { MemorySessionProvider, type MemorySessionProviderOptions } from './memory.js';
|
|
29
38
|
export { CookieSessionProvider, type CookieSessionProviderOptions } from './cookie.js';
|
package/dist/index.cjs
CHANGED
|
@@ -429,7 +429,27 @@ function resolvePermissionsFromMapping(roles, mapping) {
|
|
|
429
429
|
//#endregion
|
|
430
430
|
//#region src/index.ts
|
|
431
431
|
const DEFAULT_SESSION_MAX_AGE_SECONDS = 1209600;
|
|
432
|
+
/**
|
|
433
|
+
* Name of the sealed session cookie on the shared API. Used for server-to-server
|
|
434
|
+
* calls to the shared API, and as the unprefixed (legacy / insecure-dev /
|
|
435
|
+
* explicit-domain) browser cookie name.
|
|
436
|
+
*/
|
|
432
437
|
const COOKIE_NAME = "wos-session";
|
|
438
|
+
/** Host-only browser session cookie used on secure deployments. */
|
|
439
|
+
const HOST_SESSION_COOKIE_NAME = "__Host-wos-session";
|
|
440
|
+
/** Unprefixed session cookie name earlier releases issued (often with a shared parent `Domain`). */
|
|
441
|
+
const LEGACY_SESSION_COOKIE_NAME = COOKIE_NAME;
|
|
442
|
+
/** A `Domain` attribute value we are willing to emit in a clearing cookie. */
|
|
443
|
+
function normalizeCookieDomain(value) {
|
|
444
|
+
const domain = value?.trim().toLowerCase();
|
|
445
|
+
if (!domain) return void 0;
|
|
446
|
+
if (!/^\.?[a-z0-9-]+(\.[a-z0-9-]+)*$/.test(domain)) return void 0;
|
|
447
|
+
return domain;
|
|
448
|
+
}
|
|
449
|
+
function isLocalhostDomain(domain) {
|
|
450
|
+
const bare = domain.replace(/^\./, "");
|
|
451
|
+
return bare === "localhost" || bare.endsWith(".localhost") || /^[\d.]+$/.test(bare);
|
|
452
|
+
}
|
|
433
453
|
const PROJECT_CONFIG_FILE = ".mastra-project.json";
|
|
434
454
|
/**
|
|
435
455
|
* Read `organizationId` from a project's `.mastra-project.json` in the current
|
|
@@ -479,6 +499,10 @@ var MastraAuthStudio = class extends MastraAuthProvider {
|
|
|
479
499
|
organizationId;
|
|
480
500
|
useProductionCookies;
|
|
481
501
|
cookieDomain;
|
|
502
|
+
/** Browser cookie name this provider issues. */
|
|
503
|
+
sessionCookieName;
|
|
504
|
+
/** `Set-Cookie` values expiring legacy copies of the session cookie. */
|
|
505
|
+
legacyClearCookies;
|
|
482
506
|
sessionMaxAgeSeconds;
|
|
483
507
|
/**
|
|
484
508
|
* `userId → sealed session cookie` cache. The `IOrganizationsProvider`
|
|
@@ -528,7 +552,8 @@ var MastraAuthStudio = class extends MastraAuthProvider {
|
|
|
528
552
|
this.sharedApiUrl = explicitSharedApiUrl || "https://platform.mastra.ai/v1";
|
|
529
553
|
this.organizationId = options?.organizationId || process.env.MASTRA_ORGANIZATION_ID || readOrganizationIdFromProjectConfig();
|
|
530
554
|
if (this.sharedApiUrl.endsWith("/")) this.sharedApiUrl = this.sharedApiUrl.slice(0, -1);
|
|
531
|
-
this.cookieDomain = options?.cookieDomain
|
|
555
|
+
this.cookieDomain = normalizeCookieDomain(options?.cookieDomain);
|
|
556
|
+
const envCookieDomain = normalizeCookieDomain(process.env.MASTRA_COOKIE_DOMAIN);
|
|
532
557
|
const envMaxAge = Number(process.env.MASTRA_SESSION_MAX_AGE);
|
|
533
558
|
const maxAge = options?.sessionMaxAgeSeconds ?? (envMaxAge > 0 ? envMaxAge : void 0);
|
|
534
559
|
this.sessionMaxAgeSeconds = maxAge && Number.isFinite(maxAge) && maxAge > 0 ? Math.floor(maxAge) : DEFAULT_SESSION_MAX_AGE_SECONDS;
|
|
@@ -539,8 +564,31 @@ var MastraAuthStudio = class extends MastraAuthProvider {
|
|
|
539
564
|
} catch {
|
|
540
565
|
autoDetectMastraAi = false;
|
|
541
566
|
}
|
|
542
|
-
this.useProductionCookies = !!this.cookieDomain || autoDetectMastraAi;
|
|
543
|
-
|
|
567
|
+
this.useProductionCookies = options?.secureCookies ?? (!!(this.cookieDomain && !isLocalhostDomain(this.cookieDomain)) || !!(envCookieDomain && !isLocalhostDomain(envCookieDomain)) || autoDetectMastraAi);
|
|
568
|
+
this.sessionCookieName = this.useProductionCookies && !this.cookieDomain ? HOST_SESSION_COOKIE_NAME : LEGACY_SESSION_COOKIE_NAME;
|
|
569
|
+
const legacyDomains = /* @__PURE__ */ new Set();
|
|
570
|
+
const addLegacyDomain = (value) => {
|
|
571
|
+
const domain = normalizeCookieDomain(value);
|
|
572
|
+
if (domain && domain !== this.cookieDomain) legacyDomains.add(domain);
|
|
573
|
+
};
|
|
574
|
+
addLegacyDomain(envCookieDomain);
|
|
575
|
+
if (autoDetectMastraAi) addLegacyDomain(".mastra.ai");
|
|
576
|
+
for (const value of (process.env.MASTRA_LEGACY_COOKIE_DOMAINS ?? "").split(",")) addLegacyDomain(value);
|
|
577
|
+
for (const value of options?.legacyCookieDomains ?? []) addLegacyDomain(value);
|
|
578
|
+
const clearAttributes = [
|
|
579
|
+
"HttpOnly",
|
|
580
|
+
"SameSite=Lax",
|
|
581
|
+
"Path=/",
|
|
582
|
+
"Max-Age=0"
|
|
583
|
+
];
|
|
584
|
+
if (this.useProductionCookies) clearAttributes.push("Secure");
|
|
585
|
+
this.legacyClearCookies = [];
|
|
586
|
+
if (this.sessionCookieName !== "wos-session" || this.cookieDomain) this.legacyClearCookies.push([`${LEGACY_SESSION_COOKIE_NAME}=`, ...clearAttributes].join("; "));
|
|
587
|
+
for (const domain of legacyDomains) this.legacyClearCookies.push([
|
|
588
|
+
`${LEGACY_SESSION_COOKIE_NAME}=`,
|
|
589
|
+
...clearAttributes,
|
|
590
|
+
`Domain=${domain}`
|
|
591
|
+
].join("; "));
|
|
544
592
|
if (options) this.registerOptions(options);
|
|
545
593
|
}
|
|
546
594
|
/**
|
|
@@ -549,8 +597,12 @@ var MastraAuthStudio = class extends MastraAuthProvider {
|
|
|
549
597
|
*/
|
|
550
598
|
async authenticateToken(token, request) {
|
|
551
599
|
let user = null;
|
|
552
|
-
const
|
|
553
|
-
|
|
600
|
+
const cookieHeader = getRequestHeader(request, "Cookie");
|
|
601
|
+
const sessionCookie = this.readSessionCookie(cookieHeader);
|
|
602
|
+
if (sessionCookie) {
|
|
603
|
+
user = await this.verifySessionCookie(sessionCookie.value, request);
|
|
604
|
+
if (user && sessionCookie.legacy) this.queueSessionCookieMigration(request, sessionCookie.value);
|
|
605
|
+
}
|
|
554
606
|
if (!user && token) user = await this.verifyBearerToken(token);
|
|
555
607
|
if (!user) return null;
|
|
556
608
|
if (this.organizationId && !user.memberOrgIds?.includes(this.organizationId)) return null;
|
|
@@ -604,8 +656,7 @@ var MastraAuthStudio = class extends MastraAuthProvider {
|
|
|
604
656
|
};
|
|
605
657
|
}
|
|
606
658
|
async getLogoutUrl(_redirectUri, request) {
|
|
607
|
-
const
|
|
608
|
-
const sessionCookie = parseCookie(cookieHeader, COOKIE_NAME);
|
|
659
|
+
const sessionCookie = this.readSessionCookie(request?.headers.get("Cookie"))?.value;
|
|
609
660
|
if (!sessionCookie) return null;
|
|
610
661
|
try {
|
|
611
662
|
const res = await fetch(`${this.sharedApiUrl}/auth/logout`, {
|
|
@@ -688,39 +739,76 @@ var MastraAuthStudio = class extends MastraAuthProvider {
|
|
|
688
739
|
}
|
|
689
740
|
}
|
|
690
741
|
getSessionIdFromRequest(request) {
|
|
691
|
-
return
|
|
742
|
+
return this.readSessionCookie(request.headers.get("Cookie"))?.value ?? null;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* The browser session cookie on a request: this provider's cookie
|
|
746
|
+
* (`__Host-wos-session` on secure deployments) first, then — only to migrate
|
|
747
|
+
* sessions issued by earlier releases — the unprefixed `wos-session`.
|
|
748
|
+
*/
|
|
749
|
+
readSessionCookie(cookieHeader) {
|
|
750
|
+
const current = parseCookie(cookieHeader, this.sessionCookieName);
|
|
751
|
+
if (current) return {
|
|
752
|
+
value: current,
|
|
753
|
+
legacy: false
|
|
754
|
+
};
|
|
755
|
+
if (this.sessionCookieName === "wos-session") return null;
|
|
756
|
+
const legacy = parseCookie(cookieHeader, LEGACY_SESSION_COOKIE_NAME);
|
|
757
|
+
return legacy ? {
|
|
758
|
+
value: legacy,
|
|
759
|
+
legacy: true
|
|
760
|
+
} : null;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* A request authenticated with a legacy `wos-session` cookie: re-issue the
|
|
764
|
+
* session under this provider's cookie so the browser stops depending on the
|
|
765
|
+
* legacy (possibly shared-domain) copy. The legacy copy itself is expired on
|
|
766
|
+
* the next sign-in, refresh or sign-out (see `getLegacySessionClearCookies`).
|
|
767
|
+
*/
|
|
768
|
+
queueSessionCookieMigration(request, sessionId) {
|
|
769
|
+
const rawRequest = getWebRequest(request);
|
|
770
|
+
if (!rawRequest || this.pendingResponseHeaders.has(rawRequest)) return;
|
|
771
|
+
this.pendingResponseHeaders.set(rawRequest, this.getSessionHeaders({
|
|
772
|
+
id: sessionId,
|
|
773
|
+
userId: "",
|
|
774
|
+
expiresAt: /* @__PURE__ */ new Date(),
|
|
775
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
776
|
+
}));
|
|
692
777
|
}
|
|
693
778
|
getSessionHeaders(session) {
|
|
694
|
-
|
|
695
|
-
`${COOKIE_NAME}=${session.id}`,
|
|
696
|
-
"HttpOnly",
|
|
697
|
-
"SameSite=Lax",
|
|
698
|
-
"Path=/",
|
|
699
|
-
`Max-Age=${this.sessionMaxAgeSeconds}`
|
|
700
|
-
];
|
|
701
|
-
if (this.useProductionCookies && this.cookieDomain) {
|
|
702
|
-
parts.push("Secure");
|
|
703
|
-
parts.push(`Domain=${this.cookieDomain}`);
|
|
704
|
-
}
|
|
705
|
-
return { "Set-Cookie": parts.join("; ") };
|
|
779
|
+
return { "Set-Cookie": this.buildSessionCookie(`${this.sessionCookieName}=${session.id}`, [`Max-Age=${this.sessionMaxAgeSeconds}`]) };
|
|
706
780
|
}
|
|
707
781
|
getClearSessionHeaders() {
|
|
782
|
+
return { "Set-Cookie": this.buildSessionCookie(`${this.sessionCookieName}=`, ["Max-Age=0"]) };
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* `Set-Cookie` values expiring legacy copies of the session cookie: the
|
|
786
|
+
* unprefixed `wos-session` on this host and on every domain an earlier
|
|
787
|
+
* release (or the platform) scoped it to. Servers send these with every
|
|
788
|
+
* response that sets or clears the session cookie.
|
|
789
|
+
*/
|
|
790
|
+
getLegacySessionClearCookies() {
|
|
791
|
+
return [...this.legacyClearCookies];
|
|
792
|
+
}
|
|
793
|
+
buildSessionCookie(nameValue, extra) {
|
|
708
794
|
const parts = [
|
|
709
|
-
|
|
795
|
+
nameValue,
|
|
710
796
|
"HttpOnly",
|
|
711
797
|
"SameSite=Lax",
|
|
712
798
|
"Path=/",
|
|
713
|
-
|
|
799
|
+
...extra
|
|
714
800
|
];
|
|
715
|
-
if (this.useProductionCookies
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
719
|
-
return { "Set-Cookie": parts.join("; ") };
|
|
801
|
+
if (this.useProductionCookies) parts.push("Secure");
|
|
802
|
+
if (this.cookieDomain && this.sessionCookieName !== "__Host-wos-session") parts.push(`Domain=${this.cookieDomain}`);
|
|
803
|
+
return parts.join("; ");
|
|
720
804
|
}
|
|
721
805
|
async getCurrentUser(request) {
|
|
722
|
-
const sessionCookie =
|
|
723
|
-
if (sessionCookie)
|
|
806
|
+
const sessionCookie = this.readSessionCookie(request.headers.get("Cookie"));
|
|
807
|
+
if (sessionCookie) {
|
|
808
|
+
const user = await this.verifySessionCookie(sessionCookie.value, request);
|
|
809
|
+
if (user && sessionCookie.legacy) this.queueSessionCookieMigration(request, sessionCookie.value);
|
|
810
|
+
return user;
|
|
811
|
+
}
|
|
724
812
|
const authHeader = request.headers.get("Authorization");
|
|
725
813
|
if (authHeader?.startsWith("Bearer ")) return this.verifyBearerToken(authHeader.slice(7));
|
|
726
814
|
return null;
|
|
@@ -1038,7 +1126,13 @@ var MastraAuthStudio = class extends MastraAuthProvider {
|
|
|
1038
1126
|
};
|
|
1039
1127
|
function parseCookie(cookieHeader, name) {
|
|
1040
1128
|
if (!cookieHeader) return null;
|
|
1041
|
-
|
|
1129
|
+
for (const pair of cookieHeader.split(";")) {
|
|
1130
|
+
const eq = pair.indexOf("=");
|
|
1131
|
+
if (eq === -1 || pair.slice(0, eq).trim() !== name) continue;
|
|
1132
|
+
const value = pair.slice(eq + 1).trim();
|
|
1133
|
+
if (value) return value;
|
|
1134
|
+
}
|
|
1135
|
+
return null;
|
|
1042
1136
|
}
|
|
1043
1137
|
/**
|
|
1044
1138
|
* WorkOS AuthKit conventionally uses `admin` / `owner` for admin-equivalent
|
|
@@ -1097,6 +1191,8 @@ var MastraRBACStudio = class {
|
|
|
1097
1191
|
}
|
|
1098
1192
|
};
|
|
1099
1193
|
//#endregion
|
|
1194
|
+
exports.HOST_SESSION_COOKIE_NAME = HOST_SESSION_COOKIE_NAME;
|
|
1195
|
+
exports.LEGACY_SESSION_COOKIE_NAME = LEGACY_SESSION_COOKIE_NAME;
|
|
1100
1196
|
exports.MastraAuthStudio = MastraAuthStudio;
|
|
1101
1197
|
exports.MastraRBACStudio = MastraRBACStudio;
|
|
1102
1198
|
|