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