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