@mastra/auth-studio 1.3.2 → 1.3.3

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/index.cjs CHANGED
@@ -1,815 +1,880 @@
1
- 'use strict';
2
-
3
- // ../../packages/_internals/auth/dist/chunk-LZCWL5CT.js
4
- var RESOURCE_EXPANSIONS = {
5
- stored: [
6
- "stored-agents",
7
- "stored-mcp-clients",
8
- "stored-prompt-blocks",
9
- "stored-scorers",
10
- "stored-skills",
11
- "stored-workspaces"
12
- ]
13
- };
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let crypto$1 = require("crypto");
3
+ //#region ../../packages/_internals/auth/dist/ee-C8o0LZ3r.js
4
+ /**
5
+ * FGA enforcement utility for checking fine-grained authorization.
6
+ *
7
+ * @license Mastra Enterprise License - see ee/LICENSE
8
+ */
9
+ /**
10
+ * Compound resource keys that expand to a set of per-family resources.
11
+ * A granted `stored:<action>` is treated as matching any `stored-<family>:<action>`
12
+ * (and `stored:*` matches any `stored-<family>:*`).
13
+ */
14
+ const RESOURCE_EXPANSIONS = { stored: [
15
+ "stored-agents",
16
+ "stored-mcp-clients",
17
+ "stored-prompt-blocks",
18
+ "stored-scorers",
19
+ "stored-skills",
20
+ "stored-workspaces"
21
+ ] };
22
+ /**
23
+ * Check if a permission matches (including wildcard support).
24
+ *
25
+ * Permission format: `{resource}:{action}[:{resource-id}]`
26
+ *
27
+ * Examples:
28
+ * - `*` matches everything
29
+ * - `agents:*` matches `agents:read`, `agents:read:my-agent`
30
+ * - `*:read` matches `agents:read`, `workflows:read` (action across all resources)
31
+ * - `agents:read` matches `agents:read`, `agents:read:my-agent`
32
+ * - `agents:read:my-agent` matches only `agents:read:my-agent`
33
+ * - `agents:*:my-agent` matches `agents:read:my-agent`, `agents:write:my-agent`
34
+ *
35
+ * @param userPermission - Permission the user has
36
+ * @param requiredPermission - Permission being checked
37
+ * @returns True if permission matches
38
+ */
14
39
  function matchesPermission(userPermission, requiredPermission) {
15
- if (userPermission === "*") {
16
- return true;
17
- }
18
- const grantedParts = userPermission.split(":");
19
- const requiredParts = requiredPermission.split(":");
20
- const expandedFamilies = RESOURCE_EXPANSIONS[grantedParts[0] ?? ""];
21
- if (expandedFamilies && expandedFamilies.includes(requiredParts[0] ?? "")) {
22
- const aliased = [requiredParts[0], ...grantedParts.slice(1)].join(":");
23
- return matchesPermission(aliased, requiredPermission);
24
- }
25
- if (grantedParts.length < 2 || requiredParts.length < 2) {
26
- return userPermission === requiredPermission;
27
- }
28
- const [grantedResource, grantedAction, grantedId] = grantedParts;
29
- const [requiredResource, requiredAction, requiredId] = requiredParts;
30
- if (grantedResource === "*") {
31
- if (grantedAction === "*") {
32
- if (grantedId === void 0) {
33
- return true;
34
- }
35
- return grantedId === requiredId;
36
- }
37
- if (grantedAction !== requiredAction) {
38
- return false;
39
- }
40
- if (grantedId === void 0) {
41
- return true;
42
- }
43
- return grantedId === requiredId;
44
- }
45
- if (grantedResource !== requiredResource) {
46
- return false;
47
- }
48
- if (grantedAction === "*") {
49
- if (grantedId === void 0) {
50
- return true;
51
- }
52
- return grantedId === requiredId;
53
- }
54
- if (grantedAction !== requiredAction) {
55
- return false;
56
- }
57
- if (grantedId === void 0) {
58
- return true;
59
- }
60
- return grantedId === requiredId;
40
+ if (userPermission === "*") return true;
41
+ const grantedParts = userPermission.split(":");
42
+ const requiredParts = requiredPermission.split(":");
43
+ const expandedFamilies = RESOURCE_EXPANSIONS[grantedParts[0] ?? ""];
44
+ if (expandedFamilies && expandedFamilies.includes(requiredParts[0] ?? "")) return matchesPermission([requiredParts[0], ...grantedParts.slice(1)].join(":"), requiredPermission);
45
+ if (grantedParts.length < 2 || requiredParts.length < 2) return userPermission === requiredPermission;
46
+ const [grantedResource, grantedAction, grantedId] = grantedParts;
47
+ const [requiredResource, requiredAction, requiredId] = requiredParts;
48
+ if (grantedResource === "*") {
49
+ if (grantedAction === "*") {
50
+ if (grantedId === void 0) return true;
51
+ return grantedId === requiredId;
52
+ }
53
+ if (grantedAction !== requiredAction) return false;
54
+ if (grantedId === void 0) return true;
55
+ return grantedId === requiredId;
56
+ }
57
+ if (grantedResource !== requiredResource) return false;
58
+ if (grantedAction === "*") {
59
+ if (grantedId === void 0) return true;
60
+ return grantedId === requiredId;
61
+ }
62
+ if (grantedAction !== requiredAction) return false;
63
+ if (grantedId === void 0) return true;
64
+ return grantedId === requiredId;
61
65
  }
66
+ /**
67
+ * Resolve permissions from user roles using a role mapping.
68
+ *
69
+ * This function translates provider-defined roles (from WorkOS, Okta, etc.)
70
+ * to Mastra permissions using a configurable mapping.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const roleMapping = {
75
+ * "Engineering": ["agents:*", "workflows:*"],
76
+ * "Product": ["agents:read"],
77
+ * "_default": [],
78
+ * };
79
+ *
80
+ * // User has "Engineering" and "QA" roles
81
+ * const permissions = resolvePermissionsFromMapping(
82
+ * ["Engineering", "QA"],
83
+ * roleMapping
84
+ * );
85
+ * // Result: ["agents:*", "workflows:*"] (QA is unmapped, gets _default)
86
+ * ```
87
+ *
88
+ * @param roles - User's roles from the identity provider
89
+ * @param mapping - Role to permission mapping
90
+ * @returns Array of resolved permissions
91
+ */
62
92
  function resolvePermissionsFromMapping(roles, mapping) {
63
- const permissions = /* @__PURE__ */ new Set();
64
- const defaultPerms = mapping["_default"] ?? [];
65
- for (const role of roles) {
66
- const rolePerms = mapping[role];
67
- if (rolePerms) {
68
- for (const perm of rolePerms) {
69
- permissions.add(perm);
70
- }
71
- } else {
72
- for (const perm of defaultPerms) {
73
- permissions.add(perm);
74
- }
75
- }
76
- }
77
- return Array.from(permissions);
93
+ const permissions = /* @__PURE__ */ new Set();
94
+ const defaultPerms = mapping["_default"] ?? [];
95
+ for (const role of roles) {
96
+ const rolePerms = mapping[role];
97
+ if (rolePerms) for (const perm of rolePerms) permissions.add(perm);
98
+ else for (const perm of defaultPerms) permissions.add(perm);
99
+ }
100
+ return Array.from(permissions);
78
101
  }
79
-
80
- // ../../packages/_internal-core/dist/chunk-3M4SEWMI.js
81
- var RegisteredLogger = {
82
- LLM: "LLM"};
83
- var LogLevel = {
84
- DEBUG: "debug",
85
- INFO: "info",
86
- WARN: "warn",
87
- ERROR: "error"};
102
+ /**
103
+ * @mastra/core/auth/ee
104
+ *
105
+ * Enterprise authentication capabilities for Mastra.
106
+ * This code is licensed under the Mastra Enterprise License - see ee/LICENSE.
107
+ *
108
+ * @license Mastra Enterprise License - see ee/LICENSE
109
+ * @packageDocumentation
110
+ */
111
+ //#endregion
112
+ //#region ../../packages/_internal-core/dist/logger/index.js
113
+ const RegisteredLogger = {
114
+ AGENT: "AGENT",
115
+ OBSERVABILITY: "OBSERVABILITY",
116
+ AUTH: "AUTH",
117
+ BROWSER: "BROWSER",
118
+ NETWORK: "NETWORK",
119
+ WORKFLOW: "WORKFLOW",
120
+ LLM: "LLM",
121
+ TTS: "TTS",
122
+ VOICE: "VOICE",
123
+ VECTOR: "VECTOR",
124
+ BUNDLER: "BUNDLER",
125
+ DEPLOYER: "DEPLOYER",
126
+ MEMORY: "MEMORY",
127
+ STORAGE: "STORAGE",
128
+ EMBEDDINGS: "EMBEDDINGS",
129
+ MCP_SERVER: "MCP_SERVER",
130
+ SERVER_CACHE: "SERVER_CACHE",
131
+ SERVER: "SERVER",
132
+ WORKSPACE: "WORKSPACE",
133
+ CHANNEL: "CHANNEL"
134
+ };
135
+ const LogLevel = {
136
+ DEBUG: "debug",
137
+ INFO: "info",
138
+ WARN: "warn",
139
+ ERROR: "error",
140
+ NONE: "silent"
141
+ };
88
142
  var MastraLogger = class {
89
- name;
90
- level;
91
- transports;
92
- constructor(options = {}) {
93
- this.name = options.name || "Mastra";
94
- this.level = options.level || LogLevel.ERROR;
95
- this.transports = new Map(Object.entries(options.transports || {}));
96
- }
97
- getTransports() {
98
- return this.transports;
99
- }
100
- trackException(_error, _metadata) {
101
- }
102
- async listLogs(transportId, params) {
103
- if (!transportId || !this.transports.has(transportId)) {
104
- return { logs: [], total: 0, page: params?.page ?? 1, perPage: params?.perPage ?? 100, hasMore: false };
105
- }
106
- return this.transports.get(transportId).listLogs?.(params) ?? {
107
- logs: [],
108
- total: 0,
109
- page: params?.page ?? 1,
110
- perPage: params?.perPage ?? 100,
111
- hasMore: false
112
- };
113
- }
114
- async listLogsByRunId({
115
- transportId,
116
- runId,
117
- fromDate,
118
- toDate,
119
- logLevel,
120
- filters,
121
- page,
122
- perPage
123
- }) {
124
- if (!transportId || !this.transports.has(transportId) || !runId) {
125
- return { logs: [], total: 0, page: page ?? 1, perPage: perPage ?? 100, hasMore: false };
126
- }
127
- return this.transports.get(transportId).listLogsByRunId?.({ runId, fromDate, toDate, logLevel, filters, page, perPage }) ?? {
128
- logs: [],
129
- total: 0,
130
- page: page ?? 1,
131
- perPage: perPage ?? 100,
132
- hasMore: false
133
- };
134
- }
143
+ name;
144
+ level;
145
+ transports;
146
+ constructor(options = {}) {
147
+ this.name = options.name || "Mastra";
148
+ this.level = options.level || LogLevel.ERROR;
149
+ this.transports = new Map(Object.entries(options.transports || {}));
150
+ }
151
+ getTransports() {
152
+ return this.transports;
153
+ }
154
+ trackException(_error, _metadata) {}
155
+ async listLogs(transportId, params) {
156
+ if (!transportId || !this.transports.has(transportId)) return {
157
+ logs: [],
158
+ total: 0,
159
+ page: params?.page ?? 1,
160
+ perPage: params?.perPage ?? 100,
161
+ hasMore: false
162
+ };
163
+ return this.transports.get(transportId).listLogs?.(params) ?? {
164
+ logs: [],
165
+ total: 0,
166
+ page: params?.page ?? 1,
167
+ perPage: params?.perPage ?? 100,
168
+ hasMore: false
169
+ };
170
+ }
171
+ async listLogsByRunId({ transportId, runId, fromDate, toDate, logLevel, filters, page, perPage }) {
172
+ if (!transportId || !this.transports.has(transportId) || !runId) return {
173
+ logs: [],
174
+ total: 0,
175
+ page: page ?? 1,
176
+ perPage: perPage ?? 100,
177
+ hasMore: false
178
+ };
179
+ return this.transports.get(transportId).listLogsByRunId?.({
180
+ runId,
181
+ fromDate,
182
+ toDate,
183
+ logLevel,
184
+ filters,
185
+ page,
186
+ perPage
187
+ }) ?? {
188
+ logs: [],
189
+ total: 0,
190
+ page: page ?? 1,
191
+ perPage: perPage ?? 100,
192
+ hasMore: false
193
+ };
194
+ }
135
195
  };
136
- var ConsoleLogger = class _ConsoleLogger extends MastraLogger {
137
- component;
138
- filter;
139
- constructor(options = {}) {
140
- super(options);
141
- this.component = options.component;
142
- this.filter = options.filter;
143
- }
144
- child(componentOrBindings) {
145
- const component = typeof componentOrBindings === "string" ? componentOrBindings : componentOrBindings?.component ?? this.component;
146
- return new _ConsoleLogger({
147
- name: this.name,
148
- level: this.level,
149
- component,
150
- filter: this.filter
151
- });
152
- }
153
- shouldLog(level, message, args) {
154
- if (!this.filter) return true;
155
- try {
156
- return this.filter({ component: this.component, level, message, args });
157
- } catch (e) {
158
- console.error(`[Logger] Filter error for component=${this.component} level=${level}:`, e);
159
- return true;
160
- }
161
- }
162
- prefix() {
163
- return this.component ? `[${this.component}] ` : "";
164
- }
165
- debug(message, ...args) {
166
- if (this.level === LogLevel.DEBUG && this.shouldLog(LogLevel.DEBUG, message, args)) {
167
- console.info(`${this.prefix()}${message}`, ...args);
168
- }
169
- }
170
- info(message, ...args) {
171
- if ((this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.INFO, message, args)) {
172
- console.info(`${this.prefix()}${message}`, ...args);
173
- }
174
- }
175
- warn(message, ...args) {
176
- if ((this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.WARN, message, args)) {
177
- console.warn(`${this.prefix()}${message}`, ...args);
178
- }
179
- }
180
- error(message, ...args) {
181
- if ((this.level === LogLevel.ERROR || this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.ERROR, message, args)) {
182
- console.error(`${this.prefix()}${message}`, ...args);
183
- }
184
- }
185
- async listLogs(_transportId, _params) {
186
- return { logs: [], total: 0, page: _params?.page ?? 1, perPage: _params?.perPage ?? 100, hasMore: false };
187
- }
188
- async listLogsByRunId(_args) {
189
- return { logs: [], total: 0, page: _args.page ?? 1, perPage: _args.perPage ?? 100, hasMore: false };
190
- }
196
+ var ConsoleLogger = class ConsoleLogger extends MastraLogger {
197
+ component;
198
+ filter;
199
+ constructor(options = {}) {
200
+ super(options);
201
+ this.component = options.component;
202
+ this.filter = options.filter;
203
+ }
204
+ child(componentOrBindings) {
205
+ const component = typeof componentOrBindings === "string" ? componentOrBindings : componentOrBindings?.component ?? this.component;
206
+ return new ConsoleLogger({
207
+ name: this.name,
208
+ level: this.level,
209
+ component,
210
+ filter: this.filter
211
+ });
212
+ }
213
+ shouldLog(level, message, args) {
214
+ if (!this.filter) return true;
215
+ try {
216
+ return this.filter({
217
+ component: this.component,
218
+ level,
219
+ message,
220
+ args
221
+ });
222
+ } catch (e) {
223
+ console.error(`[Logger] Filter error for component=${this.component} level=${level}:`, e);
224
+ return true;
225
+ }
226
+ }
227
+ prefix() {
228
+ return this.component ? `[${this.component}] ` : "";
229
+ }
230
+ debug(message, ...args) {
231
+ if (this.level === LogLevel.DEBUG && this.shouldLog(LogLevel.DEBUG, message, args)) console.info(`${this.prefix()}${message}`, ...args);
232
+ }
233
+ info(message, ...args) {
234
+ if ((this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.INFO, message, args)) console.info(`${this.prefix()}${message}`, ...args);
235
+ }
236
+ warn(message, ...args) {
237
+ if ((this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.WARN, message, args)) console.warn(`${this.prefix()}${message}`, ...args);
238
+ }
239
+ error(message, ...args) {
240
+ if ((this.level === LogLevel.ERROR || this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.ERROR, message, args)) console.error(`${this.prefix()}${message}`, ...args);
241
+ }
242
+ async listLogs(_transportId, _params) {
243
+ return {
244
+ logs: [],
245
+ total: 0,
246
+ page: _params?.page ?? 1,
247
+ perPage: _params?.perPage ?? 100,
248
+ hasMore: false
249
+ };
250
+ }
251
+ async listLogsByRunId(_args) {
252
+ return {
253
+ logs: [],
254
+ total: 0,
255
+ page: _args.page ?? 1,
256
+ perPage: _args.perPage ?? 100,
257
+ hasMore: false
258
+ };
259
+ }
191
260
  };
192
-
193
- // ../../packages/_internal-core/dist/base/index.js
261
+ //#endregion
262
+ //#region ../../packages/_internal-core/dist/base/index.js
194
263
  var MastraBase = class {
195
- component = RegisteredLogger.LLM;
196
- logger;
197
- name;
198
- #rawConfig;
199
- constructor({
200
- component,
201
- name,
202
- rawConfig
203
- }) {
204
- this.component = component || RegisteredLogger.LLM;
205
- this.name = name;
206
- this.#rawConfig = rawConfig;
207
- this.logger = new ConsoleLogger({ name: `${this.component} - ${this.name}` });
208
- }
209
- /**
210
- * Returns the raw storage configuration this primitive was created from,
211
- * or undefined if it was created from code.
212
- */
213
- toRawConfig() {
214
- return this.#rawConfig;
215
- }
216
- /**
217
- * Sets the raw storage configuration for this primitive.
218
- * @internal
219
- */
220
- __setRawConfig(rawConfig) {
221
- this.#rawConfig = rawConfig;
222
- }
223
- /**
224
- * Set the logger for the agent
225
- * @param logger
226
- */
227
- __setLogger(logger) {
228
- this.logger = "child" in logger && typeof logger.child === "function" ? logger.child({ component: this.component }) : logger;
229
- }
264
+ component = RegisteredLogger.LLM;
265
+ logger;
266
+ name;
267
+ #rawConfig;
268
+ constructor({ component, name, rawConfig }) {
269
+ this.component = component || RegisteredLogger.LLM;
270
+ this.name = name;
271
+ this.#rawConfig = rawConfig;
272
+ this.logger = new ConsoleLogger({ name: `${this.component} - ${this.name}` });
273
+ }
274
+ /**
275
+ * Returns the raw storage configuration this primitive was created from,
276
+ * or undefined if it was created from code.
277
+ */
278
+ toRawConfig() {
279
+ return this.#rawConfig;
280
+ }
281
+ /**
282
+ * Sets the raw storage configuration for this primitive.
283
+ * @internal
284
+ */
285
+ __setRawConfig(rawConfig) {
286
+ this.#rawConfig = rawConfig;
287
+ }
288
+ /**
289
+ * Set the logger for the agent
290
+ * @param logger
291
+ */
292
+ __setLogger(logger) {
293
+ this.logger = "child" in logger && typeof logger.child === "function" ? logger.child({ component: this.component }) : logger;
294
+ }
230
295
  };
231
-
232
- // ../../packages/_internals/auth/dist/chunk-NJOKT6V5.js
296
+ //#endregion
297
+ //#region ../../packages/_internals/auth/dist/provider/index.js
233
298
  var MastraAuthProvider = class extends MastraBase {
234
- protected;
235
- public;
236
- constructor(options) {
237
- super({ component: "AUTH", name: options?.name });
238
- if (options?.authorizeUser) {
239
- this.authorizeUser = options.authorizeUser.bind(this);
240
- }
241
- this.protected = options?.protected;
242
- this.public = options?.public;
243
- this.mapUserToResourceId = options?.mapUserToResourceId;
244
- }
245
- registerOptions(opts) {
246
- if (opts?.authorizeUser) {
247
- this.authorizeUser = opts.authorizeUser.bind(this);
248
- }
249
- if (opts?.mapUserToResourceId) {
250
- this.mapUserToResourceId = opts.mapUserToResourceId;
251
- }
252
- if (opts?.protected) {
253
- this.protected = opts.protected;
254
- }
255
- if (opts?.public) {
256
- this.public = opts.public;
257
- }
258
- }
299
+ protected;
300
+ public;
301
+ constructor(options) {
302
+ super({
303
+ component: "AUTH",
304
+ name: options?.name
305
+ });
306
+ if (options?.authorizeUser) this.authorizeUser = options.authorizeUser.bind(this);
307
+ this.protected = options?.protected;
308
+ this.public = options?.public;
309
+ this.mapUserToResourceId = options?.mapUserToResourceId;
310
+ }
311
+ registerOptions(opts) {
312
+ if (opts?.authorizeUser) this.authorizeUser = opts.authorizeUser.bind(this);
313
+ if (opts?.mapUserToResourceId) this.mapUserToResourceId = opts.mapUserToResourceId;
314
+ if (opts?.protected) this.protected = opts.protected;
315
+ if (opts?.public) this.public = opts.public;
316
+ }
259
317
  };
260
-
261
- // src/index.ts
262
- function getRequestHeader2(request, name) {
263
- if (request instanceof Request) {
264
- return request.headers.get(name);
265
- }
266
- return request.raw?.headers.get(name) ?? request.headers?.get(name) ?? request.header(name) ?? null;
318
+ //#endregion
319
+ //#region src/index.ts
320
+ function getRequestHeader(request, name) {
321
+ if (request instanceof Request) return request.headers.get(name);
322
+ return request.raw?.headers.get(name) ?? request.headers?.get(name) ?? request.header(name) ?? null;
267
323
  }
268
- var COOKIE_NAME = "wos-session";
324
+ const COOKIE_NAME = "wos-session";
325
+ /**
326
+ * Upper bound for shared-API verification fetches. Matches the platform API
327
+ * client's request budget: a slow shared API must fail a single request in
328
+ * bounded time instead of hanging every authenticated endpoint behind it.
329
+ */
330
+ const VERIFY_FETCH_TIMEOUT_MS = 15e3;
331
+ /**
332
+ * How long a successful credential verification may be reused without
333
+ * re-contacting the shared API. Bounds revocation lag: a signed-out or
334
+ * revoked session can be honored for at most this long.
335
+ */
336
+ const VERIFY_CACHE_TTL_MS = 3e4;
337
+ /**
338
+ * Auth provider for Mastra Studio deployed instances.
339
+ *
340
+ * Proxies all authentication through the shared API, keeping the
341
+ * WorkOS API key safely in the shared API. Deployed instances only
342
+ * need the shared API URL — no secrets required.
343
+ *
344
+ * The shared API's sealed session cookie (`wos-session`) is set with
345
+ * `Domain=.mastra.ai` in production, so it's included in requests
346
+ * to deployed instances and can be forwarded for validation.
347
+ */
269
348
  var MastraAuthStudio = class extends MastraAuthProvider {
270
- isMastraCloudAuth = true;
271
- sharedApiUrl;
272
- organizationId;
273
- useProductionCookies;
274
- cookieDomain;
275
- /**
276
- * `userId → sealed session cookie` cache. The `IOrganizationsProvider`
277
- * interface only hands us a `userId`, but the shared API's org endpoints are
278
- * cookie-authenticated — so we remember the cookie last seen for a user
279
- * inside `verifySessionCookie` and reuse it here. Kept small: bounded to
280
- * the last 1000 users, LRU-evicted on insert.
281
- */
282
- userSessionCookies = /* @__PURE__ */ new Map();
283
- maxCachedSessions = 1e3;
284
- /**
285
- * In-flight `ensureOrganization` promises keyed by userId. Concurrent calls
286
- * for the same brand-new user (multiple tabs, parallel requests) would
287
- * otherwise all see "no org" from `GET /auth/me` and each fire
288
- * `POST /auth/orgs`, creating duplicate personal organizations. The first
289
- * caller's promise is reused by every follower until it settles.
290
- */
291
- organizationBootstrapInFlight = /* @__PURE__ */ new Map();
292
- constructor(options) {
293
- super({ name: "mastra-studio", ...options });
294
- const explicitSharedApiUrl = options?.sharedApiUrl || process.env.MASTRA_SHARED_API_URL;
295
- this.sharedApiUrl = explicitSharedApiUrl || "https://platform.mastra.ai/v1";
296
- this.organizationId = options?.organizationId || process.env.MASTRA_ORGANIZATION_ID;
297
- if (this.sharedApiUrl.endsWith("/")) {
298
- this.sharedApiUrl = this.sharedApiUrl.slice(0, -1);
299
- }
300
- this.cookieDomain = options?.cookieDomain || process.env.MASTRA_COOKIE_DOMAIN;
301
- let autoDetectMastraAi = false;
302
- if (explicitSharedApiUrl) {
303
- try {
304
- const hostname = new URL(this.sharedApiUrl).hostname.toLowerCase();
305
- autoDetectMastraAi = hostname === "mastra.ai" || hostname.endsWith(".mastra.ai");
306
- } catch {
307
- autoDetectMastraAi = false;
308
- }
309
- }
310
- this.useProductionCookies = !!this.cookieDomain || autoDetectMastraAi;
311
- if (!this.cookieDomain && autoDetectMastraAi) {
312
- this.cookieDomain = ".mastra.ai";
313
- }
314
- if (options) {
315
- this.registerOptions(options);
316
- }
317
- }
318
- // ---------------------------------------------------------------------------
319
- // MastraAuthProvider abstract methods
320
- // ---------------------------------------------------------------------------
321
- /**
322
- * Authenticate an incoming request by forwarding the sealed session cookie
323
- * to the shared API's /auth/me endpoint, or a Bearer token to /auth/verify.
324
- */
325
- async authenticateToken(token, request) {
326
- let user = null;
327
- const cookieHeader = getRequestHeader2(request, "Cookie");
328
- const sessionCookie = parseCookie(cookieHeader, COOKIE_NAME);
329
- if (sessionCookie) {
330
- user = await this.verifySessionCookie(sessionCookie);
331
- }
332
- if (!user && token) {
333
- user = await this.verifyBearerToken(token);
334
- }
335
- if (!user) return null;
336
- if (this.organizationId && !user.memberOrgIds?.includes(this.organizationId)) {
337
- return null;
338
- }
339
- return user;
340
- }
341
- authorizeUser(user) {
342
- return !!user?.id;
343
- }
344
- // ---------------------------------------------------------------------------
345
- // ISSOProvider
346
- // ---------------------------------------------------------------------------
347
- getLoginUrl(redirectUri, state) {
348
- let postLoginRedirect = "/";
349
- if (state) {
350
- const pipeIndex = state.indexOf("|");
351
- if (pipeIndex !== -1) {
352
- try {
353
- postLoginRedirect = decodeURIComponent(state.slice(pipeIndex + 1));
354
- } catch {
355
- }
356
- }
357
- }
358
- const params = new URLSearchParams({
359
- product: "deploy",
360
- redirect_uri: redirectUri,
361
- post_login_redirect: postLoginRedirect,
362
- // Force re-authentication so AuthKit always shows the account picker
363
- prompt: "login",
364
- ...this.organizationId ? { organization_id: this.organizationId } : {}
365
- });
366
- return `${this.sharedApiUrl}/auth/login?${params.toString()}`;
367
- }
368
- async handleCallback(code, _state) {
369
- this.logger.debug("SSO callback: validating sealed session via shared API", {
370
- sharedApiUrl: this.sharedApiUrl,
371
- codeLength: code?.length
372
- });
373
- const user = await this.verifySessionCookie(code);
374
- if (!user) {
375
- this.logger.error("SSO callback: session validation failed \u2014 verifySessionCookie returned null", {
376
- sharedApiUrl: this.sharedApiUrl,
377
- codeLength: code?.length
378
- });
379
- throw new Error("Session validation failed");
380
- }
381
- return {
382
- user,
383
- tokens: {
384
- accessToken: code
385
- }
386
- };
387
- }
388
- setCallbackCookieHeader(_cookieHeader) {
389
- }
390
- getLoginCookies() {
391
- return void 0;
392
- }
393
- getLoginButtonConfig() {
394
- return {
395
- provider: "mastra-studio",
396
- text: "Sign in with Mastra",
397
- description: "Your deployed Studio is secured by your Mastra account. Sign in with the same email you used to sign up on mastra.ai."
398
- };
399
- }
400
- async getLogoutUrl(_redirectUri, request) {
401
- const cookieHeader = request?.headers.get("Cookie");
402
- const sessionCookie = parseCookie(cookieHeader, COOKIE_NAME);
403
- if (!sessionCookie) return null;
404
- try {
405
- const res = await fetch(`${this.sharedApiUrl}/auth/logout`, {
406
- method: "POST",
407
- headers: {
408
- "Content-Type": "application/json",
409
- Cookie: `${COOKIE_NAME}=${sessionCookie}`
410
- }
411
- });
412
- if (res.ok) {
413
- const data = await res.json();
414
- return data.logoutUrl ?? null;
415
- }
416
- } catch {
417
- }
418
- return null;
419
- }
420
- // ---------------------------------------------------------------------------
421
- // ISessionProvider
422
- // ---------------------------------------------------------------------------
423
- async createSession(userId, metadata) {
424
- const now = /* @__PURE__ */ new Date();
425
- return {
426
- id: metadata?.accessToken || crypto.randomUUID(),
427
- userId,
428
- expiresAt: new Date(now.getTime() + 24 * 60 * 60 * 1e3),
429
- // 24 hours
430
- createdAt: now,
431
- metadata
432
- };
433
- }
434
- async validateSession(sessionId) {
435
- const user = await this.verifySessionCookie(sessionId);
436
- if (!user) return null;
437
- const now = /* @__PURE__ */ new Date();
438
- return {
439
- id: sessionId,
440
- userId: user.id,
441
- expiresAt: new Date(now.getTime() + 24 * 60 * 60 * 1e3),
442
- createdAt: now
443
- };
444
- }
445
- async destroySession(sessionId) {
446
- try {
447
- await fetch(`${this.sharedApiUrl}/auth/logout`, {
448
- method: "POST",
449
- headers: {
450
- Cookie: `${COOKIE_NAME}=${sessionId}`
451
- }
452
- });
453
- } catch {
454
- }
455
- }
456
- async refreshSession(sessionId) {
457
- try {
458
- const res = await fetch(`${this.sharedApiUrl}/auth/refresh`, {
459
- method: "GET",
460
- headers: {
461
- Cookie: `${COOKIE_NAME}=${sessionId}`
462
- }
463
- });
464
- if (!res.ok) {
465
- this.logger.warn("refreshSession: shared API refresh returned non-OK status", {
466
- status: res.status,
467
- url: `${this.sharedApiUrl}/auth/refresh`
468
- });
469
- return this.validateSession(sessionId);
470
- }
471
- const setCookie = res.headers.get("Set-Cookie");
472
- const newSessionId = setCookie ? parseCookieFromHeader(setCookie, COOKIE_NAME) : null;
473
- if (!newSessionId) {
474
- this.logger.warn("refreshSession: no Set-Cookie header in refresh response");
475
- return this.validateSession(sessionId);
476
- }
477
- const user = await this.verifySessionCookie(newSessionId);
478
- if (!user) return null;
479
- const now = /* @__PURE__ */ new Date();
480
- return {
481
- id: newSessionId,
482
- userId: user.id,
483
- expiresAt: new Date(now.getTime() + 24 * 60 * 60 * 1e3),
484
- createdAt: now
485
- };
486
- } catch (error) {
487
- this.logger.error("refreshSession: fetch to shared API failed", {
488
- url: `${this.sharedApiUrl}/auth/refresh`,
489
- error: error instanceof Error ? { message: error.message, stack: error.stack } : String(error)
490
- });
491
- return this.validateSession(sessionId);
492
- }
493
- }
494
- getSessionIdFromRequest(request) {
495
- const cookieHeader = request.headers.get("Cookie");
496
- return parseCookie(cookieHeader, COOKIE_NAME);
497
- }
498
- getSessionHeaders(session) {
499
- const parts = [`${COOKIE_NAME}=${session.id}`, "HttpOnly", "SameSite=Lax", "Path=/", "Max-Age=86400"];
500
- if (this.useProductionCookies && this.cookieDomain) {
501
- parts.push("Secure");
502
- parts.push(`Domain=${this.cookieDomain}`);
503
- }
504
- return { "Set-Cookie": parts.join("; ") };
505
- }
506
- getClearSessionHeaders() {
507
- const parts = [`${COOKIE_NAME}=`, "HttpOnly", "SameSite=Lax", "Path=/", "Max-Age=0"];
508
- if (this.useProductionCookies && this.cookieDomain) {
509
- parts.push("Secure");
510
- parts.push(`Domain=${this.cookieDomain}`);
511
- }
512
- return { "Set-Cookie": parts.join("; ") };
513
- }
514
- // ---------------------------------------------------------------------------
515
- // IUserProvider
516
- // ---------------------------------------------------------------------------
517
- async getCurrentUser(request) {
518
- const cookieHeader = request.headers.get("Cookie");
519
- const sessionCookie = parseCookie(cookieHeader, COOKIE_NAME);
520
- if (sessionCookie) {
521
- return this.verifySessionCookie(sessionCookie);
522
- }
523
- const authHeader = request.headers.get("Authorization");
524
- if (authHeader?.startsWith("Bearer ")) {
525
- return this.verifyBearerToken(authHeader.slice(7));
526
- }
527
- return null;
528
- }
529
- async getUser(_userId) {
530
- return null;
531
- }
532
- // ---------------------------------------------------------------------------
533
- // IOrganizationsProvider
534
- // ---------------------------------------------------------------------------
535
- /**
536
- * Ensure the user belongs to an organization, bootstrapping a personal org
537
- * on first use when they have none.
538
- *
539
- * Because the shared API's org endpoints are cookie-authenticated but this
540
- * method only receives a userId, we look up the user's sealed session cookie
541
- * from the {@link userSessionCookies} cache populated by
542
- * {@link verifySessionCookie}. If we have never seen a cookie for this user
543
- * (e.g. bearer-token flow, or the cache was evicted), we skip bootstrap and
544
- * return `undefined` — the caller keeps the user in their current no-org
545
- * state and the next authenticated request retries.
546
- *
547
- * Best-effort: any shared-API failure returns `undefined` rather than
548
- * throwing, mirroring `MastraAuthWorkos.ensureOrganization`.
549
- */
550
- async ensureOrganization(userId) {
551
- const inFlight = this.organizationBootstrapInFlight.get(userId);
552
- if (inFlight) return inFlight;
553
- const bootstrap = this.doEnsureOrganization(userId).finally(() => {
554
- this.organizationBootstrapInFlight.delete(userId);
555
- });
556
- this.organizationBootstrapInFlight.set(userId, bootstrap);
557
- return bootstrap;
558
- }
559
- async doEnsureOrganization(userId) {
560
- const sessionCookie = this.userSessionCookies.get(userId);
561
- if (!sessionCookie) {
562
- this.logger.debug("ensureOrganization: no cached session cookie for user; skipping bootstrap", { userId });
563
- return void 0;
564
- }
565
- try {
566
- const me = await this.fetchMe(sessionCookie);
567
- if (me?.organizationId) return me.organizationId;
568
- if (me?.memberOrgIds && me.memberOrgIds.length > 0) return me.memberOrgIds[0];
569
- const orgName = me?.user?.email ? `${me.user.email}'s org` : `Personal (${userId})`;
570
- const res = await fetch(`${this.sharedApiUrl}/auth/orgs`, {
571
- method: "POST",
572
- headers: {
573
- "Content-Type": "application/json",
574
- Cookie: `${COOKIE_NAME}=${sessionCookie}`
575
- },
576
- body: JSON.stringify({ name: orgName })
577
- });
578
- if (!res.ok) {
579
- this.logger.warn("ensureOrganization: shared API POST /auth/orgs returned non-OK", {
580
- status: res.status,
581
- userId
582
- });
583
- return void 0;
584
- }
585
- const data = await res.json();
586
- return data.organization?.id;
587
- } catch (error) {
588
- this.logger.error("ensureOrganization: fetch to shared API failed", {
589
- userId,
590
- error: error instanceof Error ? { message: error.message, stack: error.stack } : String(error)
591
- });
592
- return void 0;
593
- }
594
- }
595
- /**
596
- * Whether the user holds an admin-equivalent role in the organization.
597
- *
598
- * Fast path: if the org matches the user's currently-active session org, we
599
- * read the role directly from `/auth/me`. Cross-org path: we call
600
- * `/auth/orgs` (which returns per-membership roles) and look up the target
601
- * org. Any shared-API failure resolves to `false`.
602
- */
603
- async isOrganizationAdmin(organizationId, userId) {
604
- const sessionCookie = this.userSessionCookies.get(userId);
605
- if (!sessionCookie) return false;
606
- try {
607
- const me = await this.fetchMe(sessionCookie);
608
- if (me?.organizationId === organizationId) {
609
- return isAdminRole(me.role);
610
- }
611
- const res = await fetch(`${this.sharedApiUrl}/auth/orgs`, {
612
- headers: { Cookie: `${COOKIE_NAME}=${sessionCookie}` }
613
- });
614
- if (!res.ok) return false;
615
- const data = await res.json();
616
- const membership = data.organizations?.find((o) => o.id === organizationId);
617
- return isAdminRole(membership?.role ?? void 0);
618
- } catch {
619
- return false;
620
- }
621
- }
622
- // ---------------------------------------------------------------------------
623
- // Internal helpers
624
- // ---------------------------------------------------------------------------
625
- /**
626
- * Record the sealed session cookie last seen for a user so
627
- * {@link ensureOrganization} / {@link isOrganizationAdmin} can act on their
628
- * behalf. LRU-evicted at {@link maxCachedSessions} entries.
629
- */
630
- rememberUserSession(userId, sessionCookie) {
631
- this.userSessionCookies.delete(userId);
632
- this.userSessionCookies.set(userId, sessionCookie);
633
- if (this.userSessionCookies.size > this.maxCachedSessions) {
634
- const oldest = this.userSessionCookies.keys().next().value;
635
- if (oldest !== void 0) this.userSessionCookies.delete(oldest);
636
- }
637
- }
638
- /**
639
- * Fetch the shared API's `/auth/me` and return the raw response body, or
640
- * `null` on any non-OK / network error. Split out so `ensureOrganization`
641
- * and `isOrganizationAdmin` can reuse it without duplicating the shape.
642
- */
643
- async fetchMe(sessionCookie) {
644
- try {
645
- const res = await fetch(`${this.sharedApiUrl}/auth/me`, {
646
- headers: { Cookie: `${COOKIE_NAME}=${sessionCookie}` }
647
- });
648
- if (!res.ok) return null;
649
- return await res.json();
650
- } catch {
651
- return null;
652
- }
653
- }
654
- /**
655
- * Forward a sealed session cookie to the shared API's /auth/me endpoint
656
- * to validate it and get user info.
657
- */
658
- async verifySessionCookie(sessionCookie) {
659
- try {
660
- const res = await fetch(`${this.sharedApiUrl}/auth/me`, {
661
- headers: {
662
- Cookie: `${COOKIE_NAME}=${sessionCookie}`
663
- }
664
- });
665
- if (!res.ok) {
666
- this.logger.warn("verifySessionCookie: shared API returned non-OK status", {
667
- status: res.status,
668
- statusText: res.statusText,
669
- url: `${this.sharedApiUrl}/auth/me`
670
- });
671
- return null;
672
- }
673
- const data = await res.json();
674
- this.rememberUserSession(data.user.id, sessionCookie);
675
- return {
676
- id: data.user.id,
677
- email: data.user.email,
678
- name: [data.user.firstName, data.user.lastName].filter(Boolean).join(" ") || void 0,
679
- avatarUrl: data.user.profilePictureUrl,
680
- organizationId: data.organizationId,
681
- role: data.role,
682
- permissions: data.permissions,
683
- memberOrgIds: data.memberOrgIds
684
- };
685
- } catch (error) {
686
- this.logger.error("verifySessionCookie: fetch to shared API failed", {
687
- url: `${this.sharedApiUrl}/auth/me`,
688
- error: error instanceof Error ? { message: error.message, stack: error.stack } : String(error)
689
- });
690
- return null;
691
- }
692
- }
693
- /**
694
- * Forward a Bearer token to the shared API's /auth/verify endpoint
695
- * to validate it and get user info (used for CLI tokens).
696
- */
697
- async verifyBearerToken(token) {
698
- try {
699
- const res = await fetch(`${this.sharedApiUrl}/auth/verify`, {
700
- headers: {
701
- Authorization: `Bearer ${token}`
702
- }
703
- });
704
- if (!res.ok) {
705
- this.logger.warn("verifyBearerToken: shared API returned non-OK status", {
706
- status: res.status,
707
- url: `${this.sharedApiUrl}/auth/verify`
708
- });
709
- return null;
710
- }
711
- const data = await res.json();
712
- return {
713
- id: data.user.id,
714
- email: data.user.email,
715
- name: [data.user.firstName, data.user.lastName].filter(Boolean).join(" ") || void 0,
716
- organizationId: data.organizationId,
717
- role: data.role,
718
- memberOrgIds: data.memberOrgIds
719
- };
720
- } catch (error) {
721
- this.logger.error("verifyBearerToken: fetch to shared API failed", {
722
- url: `${this.sharedApiUrl}/auth/verify`,
723
- error: error instanceof Error ? { message: error.message, stack: error.stack } : String(error)
724
- });
725
- return null;
726
- }
727
- }
349
+ isMastraCloudAuth = true;
350
+ sharedApiUrl;
351
+ organizationId;
352
+ useProductionCookies;
353
+ cookieDomain;
354
+ /**
355
+ * `userId → sealed session cookie` cache. The `IOrganizationsProvider`
356
+ * interface only hands us a `userId`, but the shared API's org endpoints are
357
+ * cookie-authenticated — so we remember the cookie last seen for a user
358
+ * inside `verifySessionCookie` and reuse it here. Kept small: bounded to
359
+ * the last 1000 users, LRU-evicted on insert.
360
+ */
361
+ userSessionCookies = /* @__PURE__ */ new Map();
362
+ maxCachedSessions = 1e3;
363
+ /**
364
+ * Short-TTL cache of SUCCESSFUL credential verifications, keyed by a
365
+ * sha256 of the credential (never the raw cookie/token). Every protected
366
+ * request re-verifies against the shared API otherwise one network
367
+ * round trip per request. Failures are never cached, so a rejected
368
+ * credential is always re-checked. Bounded + insert-order evicted.
369
+ */
370
+ verifiedCredentials = /* @__PURE__ */ new Map();
371
+ maxCachedVerifications = 1e3;
372
+ /**
373
+ * In-flight `ensureOrganization` promises keyed by userId. Concurrent calls
374
+ * for the same brand-new user (multiple tabs, parallel requests) would
375
+ * otherwise all see "no org" from `GET /auth/me` and each fire
376
+ * `POST /auth/orgs`, creating duplicate personal organizations. The first
377
+ * caller's promise is reused by every follower until it settles.
378
+ */
379
+ organizationBootstrapInFlight = /* @__PURE__ */ new Map();
380
+ constructor(options) {
381
+ super({
382
+ name: "mastra-studio",
383
+ ...options
384
+ });
385
+ const explicitSharedApiUrl = options?.sharedApiUrl || process.env.MASTRA_SHARED_API_URL;
386
+ this.sharedApiUrl = explicitSharedApiUrl || "https://platform.mastra.ai/v1";
387
+ this.organizationId = options?.organizationId || process.env.MASTRA_ORGANIZATION_ID;
388
+ if (this.sharedApiUrl.endsWith("/")) this.sharedApiUrl = this.sharedApiUrl.slice(0, -1);
389
+ this.cookieDomain = options?.cookieDomain || process.env.MASTRA_COOKIE_DOMAIN;
390
+ let autoDetectMastraAi = false;
391
+ if (explicitSharedApiUrl) try {
392
+ const hostname = new URL(this.sharedApiUrl).hostname.toLowerCase();
393
+ autoDetectMastraAi = hostname === "mastra.ai" || hostname.endsWith(".mastra.ai");
394
+ } catch {
395
+ autoDetectMastraAi = false;
396
+ }
397
+ this.useProductionCookies = !!this.cookieDomain || autoDetectMastraAi;
398
+ if (!this.cookieDomain && autoDetectMastraAi) this.cookieDomain = ".mastra.ai";
399
+ if (options) this.registerOptions(options);
400
+ }
401
+ /**
402
+ * Authenticate an incoming request by forwarding the sealed session cookie
403
+ * to the shared API's /auth/me endpoint, or a Bearer token to /auth/verify.
404
+ */
405
+ async authenticateToken(token, request) {
406
+ let user = null;
407
+ const sessionCookie = parseCookie(getRequestHeader(request, "Cookie"), COOKIE_NAME);
408
+ if (sessionCookie) user = await this.verifySessionCookie(sessionCookie);
409
+ if (!user && token) user = await this.verifyBearerToken(token);
410
+ if (!user) return null;
411
+ if (this.organizationId && !user.memberOrgIds?.includes(this.organizationId)) return null;
412
+ return user;
413
+ }
414
+ authorizeUser(user) {
415
+ return !!user?.id;
416
+ }
417
+ getLoginUrl(redirectUri, state) {
418
+ let postLoginRedirect = "/";
419
+ if (state) {
420
+ const pipeIndex = state.indexOf("|");
421
+ if (pipeIndex !== -1) try {
422
+ postLoginRedirect = decodeURIComponent(state.slice(pipeIndex + 1));
423
+ } catch {}
424
+ }
425
+ const params = new URLSearchParams({
426
+ product: "deploy",
427
+ redirect_uri: redirectUri,
428
+ post_login_redirect: postLoginRedirect,
429
+ prompt: "login",
430
+ ...this.organizationId ? { organization_id: this.organizationId } : {}
431
+ });
432
+ return `${this.sharedApiUrl}/auth/login?${params.toString()}`;
433
+ }
434
+ async handleCallback(code, _state) {
435
+ this.logger.debug("SSO callback: validating sealed session via shared API", {
436
+ sharedApiUrl: this.sharedApiUrl,
437
+ codeLength: code?.length
438
+ });
439
+ const user = await this.verifySessionCookie(code);
440
+ if (!user) {
441
+ this.logger.error("SSO callback: session validation failed verifySessionCookie returned null", {
442
+ sharedApiUrl: this.sharedApiUrl,
443
+ codeLength: code?.length
444
+ });
445
+ throw new Error("Session validation failed");
446
+ }
447
+ return {
448
+ user,
449
+ tokens: { accessToken: code }
450
+ };
451
+ }
452
+ setCallbackCookieHeader(_cookieHeader) {}
453
+ getLoginCookies() {}
454
+ getLoginButtonConfig() {
455
+ return {
456
+ provider: "mastra-studio",
457
+ text: "Sign in with Mastra",
458
+ description: "Your deployed Studio is secured by your Mastra account. Sign in with the same email you used to sign up on mastra.ai."
459
+ };
460
+ }
461
+ async getLogoutUrl(_redirectUri, request) {
462
+ const cookieHeader = request?.headers.get("Cookie");
463
+ const sessionCookie = parseCookie(cookieHeader, COOKIE_NAME);
464
+ if (!sessionCookie) return null;
465
+ try {
466
+ const res = await fetch(`${this.sharedApiUrl}/auth/logout`, {
467
+ method: "POST",
468
+ headers: {
469
+ "Content-Type": "application/json",
470
+ Cookie: `${COOKIE_NAME}=${sessionCookie}`
471
+ }
472
+ });
473
+ if (res.ok) return (await res.json()).logoutUrl ?? null;
474
+ } catch {}
475
+ return null;
476
+ }
477
+ async createSession(userId, metadata) {
478
+ const now = /* @__PURE__ */ new Date();
479
+ return {
480
+ id: metadata?.accessToken || crypto.randomUUID(),
481
+ userId,
482
+ expiresAt: new Date(now.getTime() + 1440 * 60 * 1e3),
483
+ createdAt: now,
484
+ metadata
485
+ };
486
+ }
487
+ async validateSession(sessionId) {
488
+ const user = await this.verifySessionCookie(sessionId);
489
+ if (!user) return null;
490
+ const now = /* @__PURE__ */ new Date();
491
+ return {
492
+ id: sessionId,
493
+ userId: user.id,
494
+ expiresAt: new Date(now.getTime() + 1440 * 60 * 1e3),
495
+ createdAt: now
496
+ };
497
+ }
498
+ async destroySession(sessionId) {
499
+ try {
500
+ await fetch(`${this.sharedApiUrl}/auth/logout`, {
501
+ method: "POST",
502
+ headers: { Cookie: `${COOKIE_NAME}=${sessionId}` }
503
+ });
504
+ } catch {}
505
+ }
506
+ async refreshSession(sessionId) {
507
+ try {
508
+ const res = await fetch(`${this.sharedApiUrl}/auth/refresh`, {
509
+ method: "GET",
510
+ headers: { Cookie: `${COOKIE_NAME}=${sessionId}` }
511
+ });
512
+ if (!res.ok) {
513
+ this.logger.warn("refreshSession: shared API refresh returned non-OK status", {
514
+ status: res.status,
515
+ url: `${this.sharedApiUrl}/auth/refresh`
516
+ });
517
+ return this.validateSession(sessionId);
518
+ }
519
+ const setCookie = res.headers.get("Set-Cookie");
520
+ const newSessionId = setCookie ? parseCookieFromHeader(setCookie, COOKIE_NAME) : null;
521
+ if (!newSessionId) {
522
+ this.logger.warn("refreshSession: no Set-Cookie header in refresh response");
523
+ return this.validateSession(sessionId);
524
+ }
525
+ const user = await this.verifySessionCookie(newSessionId);
526
+ if (!user) return null;
527
+ const now = /* @__PURE__ */ new Date();
528
+ return {
529
+ id: newSessionId,
530
+ userId: user.id,
531
+ expiresAt: new Date(now.getTime() + 1440 * 60 * 1e3),
532
+ createdAt: now
533
+ };
534
+ } catch (error) {
535
+ this.logger.error("refreshSession: fetch to shared API failed", {
536
+ url: `${this.sharedApiUrl}/auth/refresh`,
537
+ error: error instanceof Error ? {
538
+ message: error.message,
539
+ stack: error.stack
540
+ } : String(error)
541
+ });
542
+ return this.validateSession(sessionId);
543
+ }
544
+ }
545
+ getSessionIdFromRequest(request) {
546
+ return parseCookie(request.headers.get("Cookie"), COOKIE_NAME);
547
+ }
548
+ getSessionHeaders(session) {
549
+ const parts = [
550
+ `${COOKIE_NAME}=${session.id}`,
551
+ "HttpOnly",
552
+ "SameSite=Lax",
553
+ "Path=/",
554
+ "Max-Age=86400"
555
+ ];
556
+ if (this.useProductionCookies && this.cookieDomain) {
557
+ parts.push("Secure");
558
+ parts.push(`Domain=${this.cookieDomain}`);
559
+ }
560
+ return { "Set-Cookie": parts.join("; ") };
561
+ }
562
+ getClearSessionHeaders() {
563
+ const parts = [
564
+ `${COOKIE_NAME}=`,
565
+ "HttpOnly",
566
+ "SameSite=Lax",
567
+ "Path=/",
568
+ "Max-Age=0"
569
+ ];
570
+ if (this.useProductionCookies && this.cookieDomain) {
571
+ parts.push("Secure");
572
+ parts.push(`Domain=${this.cookieDomain}`);
573
+ }
574
+ return { "Set-Cookie": parts.join("; ") };
575
+ }
576
+ async getCurrentUser(request) {
577
+ const sessionCookie = parseCookie(request.headers.get("Cookie"), COOKIE_NAME);
578
+ if (sessionCookie) return this.verifySessionCookie(sessionCookie);
579
+ const authHeader = request.headers.get("Authorization");
580
+ if (authHeader?.startsWith("Bearer ")) return this.verifyBearerToken(authHeader.slice(7));
581
+ return null;
582
+ }
583
+ async getUser(_userId) {
584
+ return null;
585
+ }
586
+ /**
587
+ * Ensure the user belongs to an organization, bootstrapping a personal org
588
+ * on first use when they have none.
589
+ *
590
+ * Because the shared API's org endpoints are cookie-authenticated but this
591
+ * method only receives a userId, we look up the user's sealed session cookie
592
+ * from the {@link userSessionCookies} cache populated by
593
+ * {@link verifySessionCookie}. If we have never seen a cookie for this user
594
+ * (e.g. bearer-token flow, or the cache was evicted), we skip bootstrap and
595
+ * return `undefined` — the caller keeps the user in their current no-org
596
+ * state and the next authenticated request retries.
597
+ *
598
+ * Best-effort: any shared-API failure returns `undefined` rather than
599
+ * throwing, mirroring `MastraAuthWorkos.ensureOrganization`.
600
+ */
601
+ async ensureOrganization(userId) {
602
+ const inFlight = this.organizationBootstrapInFlight.get(userId);
603
+ if (inFlight) return inFlight;
604
+ const bootstrap = this.doEnsureOrganization(userId).finally(() => {
605
+ this.organizationBootstrapInFlight.delete(userId);
606
+ });
607
+ this.organizationBootstrapInFlight.set(userId, bootstrap);
608
+ return bootstrap;
609
+ }
610
+ async doEnsureOrganization(userId) {
611
+ const sessionCookie = this.userSessionCookies.get(userId);
612
+ if (!sessionCookie) {
613
+ this.logger.debug("ensureOrganization: no cached session cookie for user; skipping bootstrap", { userId });
614
+ return;
615
+ }
616
+ try {
617
+ const me = await this.fetchMe(sessionCookie);
618
+ if (me?.organizationId) return me.organizationId;
619
+ if (me?.memberOrgIds && me.memberOrgIds.length > 0) return me.memberOrgIds[0];
620
+ const orgName = me?.user?.email ? `${me.user.email}'s org` : `Personal (${userId})`;
621
+ const res = await fetch(`${this.sharedApiUrl}/auth/orgs`, {
622
+ method: "POST",
623
+ headers: {
624
+ "Content-Type": "application/json",
625
+ Cookie: `${COOKIE_NAME}=${sessionCookie}`
626
+ },
627
+ body: JSON.stringify({ name: orgName })
628
+ });
629
+ if (!res.ok) {
630
+ this.logger.warn("ensureOrganization: shared API POST /auth/orgs returned non-OK", {
631
+ status: res.status,
632
+ userId
633
+ });
634
+ return;
635
+ }
636
+ return (await res.json()).organization?.id;
637
+ } catch (error) {
638
+ this.logger.error("ensureOrganization: fetch to shared API failed", {
639
+ userId,
640
+ error: error instanceof Error ? {
641
+ message: error.message,
642
+ stack: error.stack
643
+ } : String(error)
644
+ });
645
+ return;
646
+ }
647
+ }
648
+ /**
649
+ * Whether the user holds an admin-equivalent role in the organization.
650
+ *
651
+ * Fast path: if the org matches the user's currently-active session org, we
652
+ * read the role directly from `/auth/me`. Cross-org path: we call
653
+ * `/auth/orgs` (which returns per-membership roles) and look up the target
654
+ * org. Any shared-API failure resolves to `false`.
655
+ */
656
+ async isOrganizationAdmin(organizationId, userId) {
657
+ const sessionCookie = this.userSessionCookies.get(userId);
658
+ if (!sessionCookie) return false;
659
+ try {
660
+ const me = await this.fetchMe(sessionCookie);
661
+ if (me?.organizationId === organizationId) return isAdminRole(me.role);
662
+ const res = await fetch(`${this.sharedApiUrl}/auth/orgs`, { headers: { Cookie: `${COOKIE_NAME}=${sessionCookie}` } });
663
+ if (!res.ok) return false;
664
+ const membership = (await res.json()).organizations?.find((o) => o.id === organizationId);
665
+ return isAdminRole(membership?.role ?? void 0);
666
+ } catch {
667
+ return false;
668
+ }
669
+ }
670
+ /**
671
+ * Record the sealed session cookie last seen for a user so
672
+ * {@link ensureOrganization} / {@link isOrganizationAdmin} can act on their
673
+ * behalf. LRU-evicted at {@link maxCachedSessions} entries.
674
+ */
675
+ rememberUserSession(userId, sessionCookie) {
676
+ this.userSessionCookies.delete(userId);
677
+ this.userSessionCookies.set(userId, sessionCookie);
678
+ if (this.userSessionCookies.size > this.maxCachedSessions) {
679
+ const oldest = this.userSessionCookies.keys().next().value;
680
+ if (oldest !== void 0) this.userSessionCookies.delete(oldest);
681
+ }
682
+ }
683
+ /** Cache key for a verified credential — hash, never the raw secret. */
684
+ verificationKey(kind, credential) {
685
+ return (0, crypto$1.createHash)("sha256").update(`${kind}:${credential}`).digest("hex");
686
+ }
687
+ getCachedVerification(key) {
688
+ const entry = this.verifiedCredentials.get(key);
689
+ if (!entry) return null;
690
+ if (entry.expiresAt <= Date.now()) {
691
+ this.verifiedCredentials.delete(key);
692
+ return null;
693
+ }
694
+ return entry.user;
695
+ }
696
+ cacheVerification(key, user) {
697
+ this.verifiedCredentials.delete(key);
698
+ this.verifiedCredentials.set(key, {
699
+ user,
700
+ expiresAt: Date.now() + VERIFY_CACHE_TTL_MS
701
+ });
702
+ if (this.verifiedCredentials.size > this.maxCachedVerifications) {
703
+ const oldest = this.verifiedCredentials.keys().next().value;
704
+ if (oldest !== void 0) this.verifiedCredentials.delete(oldest);
705
+ }
706
+ }
707
+ /**
708
+ * Fetch the shared API's `/auth/me` and return the raw response body, or
709
+ * `null` on any non-OK / network error. Split out so `ensureOrganization`
710
+ * and `isOrganizationAdmin` can reuse it without duplicating the shape.
711
+ */
712
+ async fetchMe(sessionCookie) {
713
+ try {
714
+ const res = await fetch(`${this.sharedApiUrl}/auth/me`, {
715
+ headers: { Cookie: `${COOKIE_NAME}=${sessionCookie}` },
716
+ signal: AbortSignal.timeout(VERIFY_FETCH_TIMEOUT_MS)
717
+ });
718
+ if (!res.ok) return null;
719
+ return await res.json();
720
+ } catch {
721
+ return null;
722
+ }
723
+ }
724
+ /**
725
+ * Forward a sealed session cookie to the shared API's /auth/me endpoint
726
+ * to validate it and get user info.
727
+ */
728
+ async verifySessionCookie(sessionCookie) {
729
+ const cacheKey = this.verificationKey("cookie", sessionCookie);
730
+ const cached = this.getCachedVerification(cacheKey);
731
+ if (cached) {
732
+ this.rememberUserSession(cached.id, sessionCookie);
733
+ return cached;
734
+ }
735
+ try {
736
+ const res = await fetch(`${this.sharedApiUrl}/auth/me`, {
737
+ headers: { Cookie: `${COOKIE_NAME}=${sessionCookie}` },
738
+ signal: AbortSignal.timeout(VERIFY_FETCH_TIMEOUT_MS)
739
+ });
740
+ if (!res.ok) {
741
+ this.logger.warn("verifySessionCookie: shared API returned non-OK status", {
742
+ status: res.status,
743
+ statusText: res.statusText,
744
+ url: `${this.sharedApiUrl}/auth/me`
745
+ });
746
+ return null;
747
+ }
748
+ const data = await res.json();
749
+ this.rememberUserSession(data.user.id, sessionCookie);
750
+ const user = {
751
+ id: data.user.id,
752
+ email: data.user.email,
753
+ name: [data.user.firstName, data.user.lastName].filter(Boolean).join(" ") || void 0,
754
+ avatarUrl: data.user.profilePictureUrl,
755
+ organizationId: data.organizationId,
756
+ role: data.role,
757
+ permissions: data.permissions,
758
+ memberOrgIds: data.memberOrgIds
759
+ };
760
+ if (user.organizationId) this.cacheVerification(cacheKey, user);
761
+ return user;
762
+ } catch (error) {
763
+ this.logger.error("verifySessionCookie: fetch to shared API failed", {
764
+ url: `${this.sharedApiUrl}/auth/me`,
765
+ error: error instanceof Error ? {
766
+ message: error.message,
767
+ stack: error.stack
768
+ } : String(error)
769
+ });
770
+ return null;
771
+ }
772
+ }
773
+ /**
774
+ * Forward a Bearer token to the shared API's /auth/verify endpoint
775
+ * to validate it and get user info (used for CLI tokens).
776
+ */
777
+ async verifyBearerToken(token) {
778
+ const cacheKey = this.verificationKey("bearer", token);
779
+ const cached = this.getCachedVerification(cacheKey);
780
+ if (cached) return cached;
781
+ try {
782
+ const res = await fetch(`${this.sharedApiUrl}/auth/verify`, {
783
+ headers: { Authorization: `Bearer ${token}` },
784
+ signal: AbortSignal.timeout(VERIFY_FETCH_TIMEOUT_MS)
785
+ });
786
+ if (!res.ok) {
787
+ this.logger.warn("verifyBearerToken: shared API returned non-OK status", {
788
+ status: res.status,
789
+ url: `${this.sharedApiUrl}/auth/verify`
790
+ });
791
+ return null;
792
+ }
793
+ const data = await res.json();
794
+ const user = {
795
+ id: data.user.id,
796
+ email: data.user.email,
797
+ name: [data.user.firstName, data.user.lastName].filter(Boolean).join(" ") || void 0,
798
+ organizationId: data.organizationId,
799
+ role: data.role,
800
+ memberOrgIds: data.memberOrgIds
801
+ };
802
+ if (user.organizationId) this.cacheVerification(cacheKey, user);
803
+ return user;
804
+ } catch (error) {
805
+ this.logger.error("verifyBearerToken: fetch to shared API failed", {
806
+ url: `${this.sharedApiUrl}/auth/verify`,
807
+ error: error instanceof Error ? {
808
+ message: error.message,
809
+ stack: error.stack
810
+ } : String(error)
811
+ });
812
+ return null;
813
+ }
814
+ }
728
815
  };
729
816
  function parseCookie(cookieHeader, name) {
730
- if (!cookieHeader) return null;
731
- const match = cookieHeader.match(new RegExp(`${name}=([^;]+)`));
732
- return match?.[1] ?? null;
817
+ if (!cookieHeader) return null;
818
+ return cookieHeader.match(new RegExp(`${name}=([^;]+)`))?.[1] ?? null;
733
819
  }
820
+ /**
821
+ * WorkOS AuthKit conventionally uses `admin` / `owner` for admin-equivalent
822
+ * roles; the shared API surfaces the WorkOS role slug directly on
823
+ * `/auth/me` and `/auth/orgs`.
824
+ */
734
825
  function isAdminRole(role) {
735
- return role === "admin" || role === "owner";
826
+ return role === "admin" || role === "owner";
736
827
  }
828
+ /**
829
+ * Parse a cookie value from a Set-Cookie header.
830
+ * Set-Cookie format: "name=value; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400"
831
+ */
737
832
  function parseCookieFromHeader(setCookieHeader, name) {
738
- const parts = setCookieHeader.split(";");
739
- if (parts.length === 0) return null;
740
- const [cookieName, ...valueParts] = parts[0].split("=");
741
- if (cookieName?.trim() !== name) return null;
742
- return valueParts.join("=") || null;
833
+ const parts = setCookieHeader.split(";");
834
+ if (parts.length === 0) return null;
835
+ const [cookieName, ...valueParts] = parts[0].split("=");
836
+ if (cookieName?.trim() !== name) return null;
837
+ return valueParts.join("=") || null;
743
838
  }
839
+ /**
840
+ * RBAC provider for Mastra Studio authentication.
841
+ *
842
+ * Maps user roles (from the shared API's /auth/me endpoint) to Mastra permissions
843
+ * using a configurable role mapping.
844
+ */
744
845
  var MastraRBACStudio = class {
745
- options;
746
- get roleMapping() {
747
- return this.options.roleMapping;
748
- }
749
- constructor(options) {
750
- this.options = options;
751
- }
752
- async getRoles(user) {
753
- return user.role ? [user.role] : [];
754
- }
755
- async hasRole(user, role) {
756
- const roles = await this.getRoles(user);
757
- return roles.includes(role);
758
- }
759
- async getPermissions(user) {
760
- const roles = await this.getRoles(user);
761
- if (roles.length === 0) {
762
- return this.options.roleMapping["_default"] ?? [];
763
- }
764
- return resolvePermissionsFromMapping(roles, this.options.roleMapping);
765
- }
766
- async hasPermission(user, permission) {
767
- const permissions = await this.getPermissions(user);
768
- return permissions.some((p) => matchesPermission(p, permission));
769
- }
770
- async hasAllPermissions(user, permissions) {
771
- const userPermissions = await this.getPermissions(user);
772
- return permissions.every((required) => userPermissions.some((p) => matchesPermission(p, required)));
773
- }
774
- async hasAnyPermission(user, permissions) {
775
- const userPermissions = await this.getPermissions(user);
776
- return permissions.some((required) => userPermissions.some((p) => matchesPermission(p, required)));
777
- }
846
+ options;
847
+ get roleMapping() {
848
+ return this.options.roleMapping;
849
+ }
850
+ constructor(options) {
851
+ this.options = options;
852
+ }
853
+ async getRoles(user) {
854
+ return user.role ? [user.role] : [];
855
+ }
856
+ async hasRole(user, role) {
857
+ return (await this.getRoles(user)).includes(role);
858
+ }
859
+ async getPermissions(user) {
860
+ const roles = await this.getRoles(user);
861
+ if (roles.length === 0) return this.options.roleMapping["_default"] ?? [];
862
+ return resolvePermissionsFromMapping(roles, this.options.roleMapping);
863
+ }
864
+ async hasPermission(user, permission) {
865
+ return (await this.getPermissions(user)).some((p) => matchesPermission(p, permission));
866
+ }
867
+ async hasAllPermissions(user, permissions) {
868
+ const userPermissions = await this.getPermissions(user);
869
+ return permissions.every((required) => userPermissions.some((p) => matchesPermission(p, required)));
870
+ }
871
+ async hasAnyPermission(user, permissions) {
872
+ const userPermissions = await this.getPermissions(user);
873
+ return permissions.some((required) => userPermissions.some((p) => matchesPermission(p, required)));
874
+ }
778
875
  };
779
- /**
780
- * EE Authentication Interfaces
781
- *
782
- * Enterprise interfaces for RBAC, ACL, and advanced authorization.
783
- *
784
- * @license Mastra Enterprise License - see ee/LICENSE
785
- * @packageDocumentation
786
- */
787
- /**
788
- * FGA enforcement utility for checking fine-grained authorization.
789
- *
790
- * @license Mastra Enterprise License - see ee/LICENSE
791
- */
792
- /**
793
- * RBAC provider implementations.
794
- *
795
- * @license Mastra Enterprise License - see ee/LICENSE
796
- */
797
- /**
798
- * Default implementations for EE authentication.
799
- *
800
- * @license Mastra Enterprise License - see ee/LICENSE
801
- */
802
- /**
803
- * @mastra/core/auth/ee
804
- *
805
- * Enterprise authentication capabilities for Mastra.
806
- * This code is licensed under the Mastra Enterprise License - see ee/LICENSE.
807
- *
808
- * @license Mastra Enterprise License - see ee/LICENSE
809
- * @packageDocumentation
810
- */
811
-
876
+ //#endregion
812
877
  exports.MastraAuthStudio = MastraAuthStudio;
813
878
  exports.MastraRBACStudio = MastraRBACStudio;
814
- //# sourceMappingURL=index.cjs.map
879
+
815
880
  //# sourceMappingURL=index.cjs.map