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