@mastra/auth-workos 0.10.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/.turbo/turbo-build.log +30 -0
- package/CHANGELOG.md +1 -0
- package/LICENSE.md +7 -0
- package/README.md +78 -0
- package/dist/_tsup-dts-rollup.d.cts +20 -0
- package/dist/_tsup-dts-rollup.d.ts +20 -0
- package/dist/chunk-JLXWUSDO.js +972 -0
- package/dist/chunk-N62AETLJ.js +7 -0
- package/dist/getMachineId-bsd-IPBZSYCM.js +21 -0
- package/dist/getMachineId-darwin-UJH25LDA.js +22 -0
- package/dist/getMachineId-linux-YF3RLZNP.js +17 -0
- package/dist/getMachineId-unsupported-UOUTBEEW.js +9 -0
- package/dist/getMachineId-win-PKATJI4A.js +23 -0
- package/dist/index.cjs +1534 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +361 -0
- package/eslint.config.js +6 -0
- package/package.json +41 -0
- package/src/index.test.ts +185 -0
- package/src/index.ts +57 -0
- package/tsconfig.json +5 -0
- package/vitest.config.ts +8 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MastraAuthWorkos } from './_tsup-dts-rollup.cjs';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MastraAuthWorkos } from './_tsup-dts-rollup.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { SpanKind, trace, context, propagation, SpanStatusCode } from './chunk-JLXWUSDO.js';
|
|
2
|
+
import { verifyJwks } from '@mastra/auth';
|
|
3
|
+
import { WorkOS } from '@workos-inc/node';
|
|
4
|
+
|
|
5
|
+
// ../../packages/core/dist/chunk-EWDGXKOQ.js
|
|
6
|
+
function hasActiveTelemetry(tracerName = "default-tracer") {
|
|
7
|
+
try {
|
|
8
|
+
return !!trace.getTracer(tracerName);
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function getBaggageValues(ctx) {
|
|
14
|
+
const currentBaggage = propagation.getBaggage(ctx);
|
|
15
|
+
const requestId = currentBaggage?.getEntry("http.request_id")?.value;
|
|
16
|
+
const componentName = currentBaggage?.getEntry("componentName")?.value;
|
|
17
|
+
const runId = currentBaggage?.getEntry("runId")?.value;
|
|
18
|
+
return {
|
|
19
|
+
requestId,
|
|
20
|
+
componentName,
|
|
21
|
+
runId
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function withSpan(options) {
|
|
25
|
+
return function(_target, propertyKey, descriptor) {
|
|
26
|
+
if (!descriptor || typeof descriptor === "number") return;
|
|
27
|
+
const originalMethod = descriptor.value;
|
|
28
|
+
const methodName = String(propertyKey);
|
|
29
|
+
descriptor.value = function(...args) {
|
|
30
|
+
if (options?.skipIfNoTelemetry && !hasActiveTelemetry(options?.tracerName)) {
|
|
31
|
+
return originalMethod.apply(this, args);
|
|
32
|
+
}
|
|
33
|
+
const tracer = trace.getTracer(options?.tracerName ?? "default-tracer");
|
|
34
|
+
let spanName;
|
|
35
|
+
let spanKind;
|
|
36
|
+
if (typeof options === "string") {
|
|
37
|
+
spanName = options;
|
|
38
|
+
} else if (options) {
|
|
39
|
+
spanName = options.spanName || methodName;
|
|
40
|
+
spanKind = options.spanKind;
|
|
41
|
+
} else {
|
|
42
|
+
spanName = methodName;
|
|
43
|
+
}
|
|
44
|
+
const span = tracer.startSpan(spanName, { kind: spanKind });
|
|
45
|
+
let ctx = trace.setSpan(context.active(), span);
|
|
46
|
+
args.forEach((arg, index) => {
|
|
47
|
+
try {
|
|
48
|
+
span.setAttribute(`${spanName}.argument.${index}`, JSON.stringify(arg));
|
|
49
|
+
} catch {
|
|
50
|
+
span.setAttribute(`${spanName}.argument.${index}`, "[Not Serializable]");
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const { requestId, componentName, runId } = getBaggageValues(ctx);
|
|
54
|
+
if (requestId) {
|
|
55
|
+
span.setAttribute("http.request_id", requestId);
|
|
56
|
+
}
|
|
57
|
+
if (componentName) {
|
|
58
|
+
span.setAttribute("componentName", componentName);
|
|
59
|
+
span.setAttribute("runId", runId);
|
|
60
|
+
} else if (this && this.name) {
|
|
61
|
+
span.setAttribute("componentName", this.name);
|
|
62
|
+
span.setAttribute("runId", this.runId);
|
|
63
|
+
ctx = propagation.setBaggage(
|
|
64
|
+
ctx,
|
|
65
|
+
propagation.createBaggage({
|
|
66
|
+
// @ts-ignore
|
|
67
|
+
componentName: { value: this.name },
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
runId: { value: this.runId },
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
"http.request_id": { value: requestId }
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
let result;
|
|
76
|
+
try {
|
|
77
|
+
result = context.with(ctx, () => originalMethod.apply(this, args));
|
|
78
|
+
if (result instanceof Promise) {
|
|
79
|
+
return result.then((resolvedValue) => {
|
|
80
|
+
try {
|
|
81
|
+
span.setAttribute(`${spanName}.result`, JSON.stringify(resolvedValue));
|
|
82
|
+
} catch {
|
|
83
|
+
span.setAttribute(`${spanName}.result`, "[Not Serializable]");
|
|
84
|
+
}
|
|
85
|
+
return resolvedValue;
|
|
86
|
+
}).finally(() => span.end());
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
span.setAttribute(`${spanName}.result`, JSON.stringify(result));
|
|
90
|
+
} catch {
|
|
91
|
+
span.setAttribute(`${spanName}.result`, "[Not Serializable]");
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
span.setStatus({
|
|
96
|
+
code: SpanStatusCode.ERROR,
|
|
97
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
98
|
+
});
|
|
99
|
+
if (error instanceof Error) {
|
|
100
|
+
span.recordException(error);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
} finally {
|
|
104
|
+
if (!(result instanceof Promise)) {
|
|
105
|
+
span.end();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return descriptor;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function InstrumentClass(options) {
|
|
113
|
+
return function(target) {
|
|
114
|
+
const methods = Object.getOwnPropertyNames(target.prototype);
|
|
115
|
+
methods.forEach((method) => {
|
|
116
|
+
if (options?.excludeMethods?.includes(method) || method === "constructor") return;
|
|
117
|
+
if (options?.methodFilter && !options.methodFilter(method)) return;
|
|
118
|
+
const descriptor = Object.getOwnPropertyDescriptor(target.prototype, method);
|
|
119
|
+
if (descriptor && typeof descriptor.value === "function") {
|
|
120
|
+
Object.defineProperty(
|
|
121
|
+
target.prototype,
|
|
122
|
+
method,
|
|
123
|
+
withSpan({
|
|
124
|
+
spanName: options?.prefix ? `${options.prefix}.${method}` : method,
|
|
125
|
+
skipIfNoTelemetry: true,
|
|
126
|
+
spanKind: options?.spanKind || SpanKind.INTERNAL,
|
|
127
|
+
tracerName: options?.tracerName
|
|
128
|
+
})(target, method, descriptor)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return target;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ../../packages/core/dist/chunk-XXVGT7SJ.js
|
|
137
|
+
var RegisteredLogger = {
|
|
138
|
+
LLM: "LLM"};
|
|
139
|
+
var LogLevel = {
|
|
140
|
+
DEBUG: "debug",
|
|
141
|
+
INFO: "info",
|
|
142
|
+
WARN: "warn",
|
|
143
|
+
ERROR: "error"};
|
|
144
|
+
var MastraLogger = class {
|
|
145
|
+
name;
|
|
146
|
+
level;
|
|
147
|
+
transports;
|
|
148
|
+
constructor(options = {}) {
|
|
149
|
+
this.name = options.name || "Mastra";
|
|
150
|
+
this.level = options.level || LogLevel.ERROR;
|
|
151
|
+
this.transports = new Map(Object.entries(options.transports || {}));
|
|
152
|
+
}
|
|
153
|
+
getTransports() {
|
|
154
|
+
return this.transports;
|
|
155
|
+
}
|
|
156
|
+
async getLogs(transportId) {
|
|
157
|
+
if (!transportId || !this.transports.has(transportId)) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
return this.transports.get(transportId).getLogs() ?? [];
|
|
161
|
+
}
|
|
162
|
+
async getLogsByRunId({ transportId, runId }) {
|
|
163
|
+
if (!transportId || !this.transports.has(transportId) || !runId) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
return this.transports.get(transportId).getLogsByRunId({ runId }) ?? [];
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var ConsoleLogger = class extends MastraLogger {
|
|
170
|
+
constructor(options = {}) {
|
|
171
|
+
super(options);
|
|
172
|
+
}
|
|
173
|
+
debug(message, ...args) {
|
|
174
|
+
if (this.level === LogLevel.DEBUG) {
|
|
175
|
+
console.debug(message, ...args);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
info(message, ...args) {
|
|
179
|
+
if (this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
|
|
180
|
+
console.info(message, ...args);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
warn(message, ...args) {
|
|
184
|
+
if (this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
|
|
185
|
+
console.warn(message, ...args);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
error(message, ...args) {
|
|
189
|
+
if (this.level === LogLevel.ERROR || this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
|
|
190
|
+
console.error(message, ...args);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async getLogs(_transportId) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
async getLogsByRunId(_args) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// ../../packages/core/dist/chunk-JOCKZ2US.js
|
|
202
|
+
var MastraBase = class {
|
|
203
|
+
component = RegisteredLogger.LLM;
|
|
204
|
+
logger;
|
|
205
|
+
name;
|
|
206
|
+
telemetry;
|
|
207
|
+
constructor({ component, name }) {
|
|
208
|
+
this.component = component || RegisteredLogger.LLM;
|
|
209
|
+
this.name = name;
|
|
210
|
+
this.logger = new ConsoleLogger({ name: `${this.component} - ${this.name}` });
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Set the logger for the agent
|
|
214
|
+
* @param logger
|
|
215
|
+
*/
|
|
216
|
+
__setLogger(logger) {
|
|
217
|
+
this.logger = logger;
|
|
218
|
+
if (this.component !== RegisteredLogger.LLM) {
|
|
219
|
+
this.logger.debug(`Logger updated [component=${this.component}] [name=${this.name}]`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Set the telemetry for the
|
|
224
|
+
* @param telemetry
|
|
225
|
+
*/
|
|
226
|
+
__setTelemetry(telemetry) {
|
|
227
|
+
this.telemetry = telemetry;
|
|
228
|
+
if (this.component !== RegisteredLogger.LLM) {
|
|
229
|
+
this.logger.debug(`Telemetry updated [component=${this.component}] [name=${this.telemetry.name}]`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get the telemetry on the vector
|
|
234
|
+
* @returns telemetry
|
|
235
|
+
*/
|
|
236
|
+
__getTelemetry() {
|
|
237
|
+
return this.telemetry;
|
|
238
|
+
}
|
|
239
|
+
/*
|
|
240
|
+
get experimental_telemetry config
|
|
241
|
+
*/
|
|
242
|
+
get experimental_telemetry() {
|
|
243
|
+
return this.telemetry ? {
|
|
244
|
+
// tracer: this.telemetry.tracer,
|
|
245
|
+
tracer: this.telemetry.getBaggageTracer(),
|
|
246
|
+
isEnabled: !!this.telemetry.tracer
|
|
247
|
+
} : void 0;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// ../../packages/core/dist/chunk-C6A6W6XS.js
|
|
252
|
+
var __create = Object.create;
|
|
253
|
+
var __defProp = Object.defineProperty;
|
|
254
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
255
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
256
|
+
var __typeError = (msg) => {
|
|
257
|
+
throw TypeError(msg);
|
|
258
|
+
};
|
|
259
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
|
|
260
|
+
enumerable: true,
|
|
261
|
+
configurable: true,
|
|
262
|
+
writable: true,
|
|
263
|
+
value
|
|
264
|
+
}) : obj[key] = value;
|
|
265
|
+
var __name = (target, value) => __defProp(target, "name", {
|
|
266
|
+
value,
|
|
267
|
+
configurable: true
|
|
268
|
+
});
|
|
269
|
+
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
|
|
270
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
271
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
272
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({
|
|
273
|
+
kind: __decoratorStrings[kind],
|
|
274
|
+
name,
|
|
275
|
+
metadata,
|
|
276
|
+
addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
|
|
277
|
+
});
|
|
278
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
279
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
280
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ;
|
|
281
|
+
return value;
|
|
282
|
+
};
|
|
283
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
284
|
+
var it, done, ctx, k = flags & 7, p = false;
|
|
285
|
+
var j = 0;
|
|
286
|
+
var extraInitializers = array[j] || (array[j] = []);
|
|
287
|
+
var desc = k && ((target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(target , name));
|
|
288
|
+
__name(target, name);
|
|
289
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
290
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
291
|
+
it = (0, decorators[i])(target, ctx), done._ = 1;
|
|
292
|
+
__expectFn(it) && (target = it);
|
|
293
|
+
}
|
|
294
|
+
return __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// ../../packages/core/dist/server/index.js
|
|
298
|
+
var _MastraAuthProvider_decorators;
|
|
299
|
+
var _init;
|
|
300
|
+
var _a;
|
|
301
|
+
_MastraAuthProvider_decorators = [InstrumentClass({
|
|
302
|
+
prefix: "auth",
|
|
303
|
+
excludeMethods: ["__setTools", "__setLogger", "__setTelemetry", "#log"]
|
|
304
|
+
})];
|
|
305
|
+
var MastraAuthProvider = class extends (_a = MastraBase) {
|
|
306
|
+
constructor(options) {
|
|
307
|
+
super({
|
|
308
|
+
component: "AUTH",
|
|
309
|
+
name: options?.name
|
|
310
|
+
});
|
|
311
|
+
if (options?.authorizeUser) {
|
|
312
|
+
this.authorizeUser = options.authorizeUser.bind(this);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
registerOptions(opts) {
|
|
316
|
+
if (opts?.authorizeUser) {
|
|
317
|
+
this.authorizeUser = opts.authorizeUser.bind(this);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
MastraAuthProvider = /* @__PURE__ */ ((_) => {
|
|
322
|
+
_init = __decoratorStart(_a);
|
|
323
|
+
MastraAuthProvider = __decorateElement(_init, 0, "MastraAuthProvider", _MastraAuthProvider_decorators, MastraAuthProvider);
|
|
324
|
+
__runInitializers(_init, 1, MastraAuthProvider);
|
|
325
|
+
return MastraAuthProvider;
|
|
326
|
+
})();
|
|
327
|
+
var MastraAuthWorkos = class extends MastraAuthProvider {
|
|
328
|
+
workos;
|
|
329
|
+
constructor(options) {
|
|
330
|
+
super({ name: options?.name ?? "workos" });
|
|
331
|
+
const apiKey = options?.apiKey ?? process.env.WORKOS_API_KEY;
|
|
332
|
+
const clientId = options?.clientId ?? process.env.WORKOS_CLIENT_ID;
|
|
333
|
+
if (!apiKey || !clientId) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
"WorkOS API key and client ID are required, please provide them in the options or set the environment variables WORKOS_API_KEY and WORKOS_CLIENT_ID"
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
this.workos = new WorkOS(apiKey, {
|
|
339
|
+
clientId
|
|
340
|
+
});
|
|
341
|
+
this.registerOptions(options);
|
|
342
|
+
}
|
|
343
|
+
async authenticateToken(token) {
|
|
344
|
+
const jwksUri = this.workos.userManagement.getJwksUrl(process.env.WORKOS_CLIENT_ID);
|
|
345
|
+
const user = await verifyJwks(token, jwksUri);
|
|
346
|
+
return user;
|
|
347
|
+
}
|
|
348
|
+
async authorizeUser(user) {
|
|
349
|
+
if (!user) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const org = await this.workos.userManagement.listOrganizationMemberships({
|
|
353
|
+
userId: user.sub
|
|
354
|
+
});
|
|
355
|
+
const roles = org.data.map((org2) => org2.role);
|
|
356
|
+
const isAdmin = roles.some((role) => role.slug === "admin");
|
|
357
|
+
return isAdmin;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
export { MastraAuthWorkos };
|
package/eslint.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mastra/auth-workos",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Mastra WorkOS Auth integration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"license": "Elastic-2.0",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@workos-inc/node": "^7.50.1",
|
|
24
|
+
"@mastra/auth": "0.1.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.13.1",
|
|
28
|
+
"eslint": "^9.25.1",
|
|
29
|
+
"tsup": "^8.4.0",
|
|
30
|
+
"typescript": "^5.8.3",
|
|
31
|
+
"vitest": "^2.1.9",
|
|
32
|
+
"@mastra/core": "0.10.2",
|
|
33
|
+
"@internal/lint": "0.0.8"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake",
|
|
37
|
+
"build:watch": "pnpm build --watch",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"lint": "eslint ."
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { JwtPayload } from '@mastra/auth';
|
|
2
|
+
import { verifyJwks } from '@mastra/auth';
|
|
3
|
+
import { WorkOS } from '@workos-inc/node';
|
|
4
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
5
|
+
import { MastraAuthWorkos } from './index';
|
|
6
|
+
|
|
7
|
+
// Mock the WorkOS class
|
|
8
|
+
vi.mock('@workos-inc/node', () => ({
|
|
9
|
+
WorkOS: vi.fn().mockImplementation(() => ({
|
|
10
|
+
userManagement: {
|
|
11
|
+
getJwksUrl: vi.fn().mockReturnValue('https://mock-jwks-url'),
|
|
12
|
+
listOrganizationMemberships: vi.fn().mockResolvedValue({
|
|
13
|
+
data: [{ role: { slug: 'admin' } }, { role: { slug: 'member' } }],
|
|
14
|
+
}),
|
|
15
|
+
},
|
|
16
|
+
})),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// Mock the verifyJwks function
|
|
20
|
+
vi.mock('@mastra/auth', () => ({
|
|
21
|
+
verifyJwks: vi.fn().mockResolvedValue({
|
|
22
|
+
sub: 'user123',
|
|
23
|
+
email: 'test@example.com',
|
|
24
|
+
} as JwtPayload),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
describe('MastraAuthWorkos', () => {
|
|
28
|
+
const mockApiKey = 'test-api-key';
|
|
29
|
+
const mockClientId = 'test-client-id';
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
// Reset environment variables
|
|
34
|
+
delete process.env.WORKOS_API_KEY;
|
|
35
|
+
delete process.env.WORKOS_CLIENT_ID;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('constructor', () => {
|
|
39
|
+
it('should initialize with provided options', () => {
|
|
40
|
+
new MastraAuthWorkos({
|
|
41
|
+
apiKey: mockApiKey,
|
|
42
|
+
clientId: mockClientId,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(WorkOS).toHaveBeenCalledWith(mockApiKey, {
|
|
46
|
+
clientId: mockClientId,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should initialize with environment variables', () => {
|
|
51
|
+
process.env.WORKOS_API_KEY = mockApiKey;
|
|
52
|
+
process.env.WORKOS_CLIENT_ID = mockClientId;
|
|
53
|
+
|
|
54
|
+
new MastraAuthWorkos();
|
|
55
|
+
|
|
56
|
+
expect(WorkOS).toHaveBeenCalledWith(mockApiKey, {
|
|
57
|
+
clientId: mockClientId,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should throw error when neither options nor environment variables are provided', () => {
|
|
62
|
+
expect(() => new MastraAuthWorkos()).toThrow('WorkOS API key and client ID are required');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('authenticateToken', () => {
|
|
67
|
+
it('should authenticate a valid token', async () => {
|
|
68
|
+
const auth = new MastraAuthWorkos({
|
|
69
|
+
apiKey: mockApiKey,
|
|
70
|
+
clientId: mockClientId,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const mockToken = 'valid-token';
|
|
74
|
+
const result = await auth.authenticateToken(mockToken);
|
|
75
|
+
|
|
76
|
+
expect(verifyJwks).toHaveBeenCalledWith(mockToken, 'https://mock-jwks-url');
|
|
77
|
+
expect(result).toEqual({
|
|
78
|
+
sub: 'user123',
|
|
79
|
+
email: 'test@example.com',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return null for invalid token', async () => {
|
|
84
|
+
vi.mocked(verifyJwks).mockResolvedValueOnce(null as unknown as JwtPayload);
|
|
85
|
+
|
|
86
|
+
const auth = new MastraAuthWorkos({
|
|
87
|
+
apiKey: mockApiKey,
|
|
88
|
+
clientId: mockClientId,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const result = await auth.authenticateToken('invalid-token');
|
|
92
|
+
expect(result).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('authorizeUser', () => {
|
|
97
|
+
it('should return true for admin users', async () => {
|
|
98
|
+
const auth = new MastraAuthWorkos({
|
|
99
|
+
apiKey: mockApiKey,
|
|
100
|
+
clientId: mockClientId,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const result = await auth.authorizeUser({
|
|
104
|
+
sub: 'user123',
|
|
105
|
+
email: 'test@example.com',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(result).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should return false for non-admin users', async () => {
|
|
112
|
+
vi.mocked(WorkOS).mockImplementationOnce(
|
|
113
|
+
() =>
|
|
114
|
+
({
|
|
115
|
+
userManagement: {
|
|
116
|
+
getJwksUrl: vi.fn().mockReturnValue('https://mock-jwks-url'),
|
|
117
|
+
listOrganizationMemberships: vi.fn().mockResolvedValue({
|
|
118
|
+
data: [{ role: { slug: 'member' } }],
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
}) as unknown as WorkOS,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const auth = new MastraAuthWorkos({
|
|
125
|
+
apiKey: mockApiKey,
|
|
126
|
+
clientId: mockClientId,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const result = await auth.authorizeUser({
|
|
130
|
+
sub: 'user123',
|
|
131
|
+
email: 'test@example.com',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(result).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should return false for falsy user', async () => {
|
|
138
|
+
vi.mocked(WorkOS).mockImplementationOnce(
|
|
139
|
+
() =>
|
|
140
|
+
({
|
|
141
|
+
userManagement: {
|
|
142
|
+
getJwksUrl: vi.fn().mockReturnValue('https://mock-jwks-url'),
|
|
143
|
+
listOrganizationMemberships: vi.fn().mockResolvedValue({
|
|
144
|
+
data: [], // Empty data array means no roles
|
|
145
|
+
}),
|
|
146
|
+
},
|
|
147
|
+
}) as unknown as WorkOS,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const auth = new MastraAuthWorkos({
|
|
151
|
+
apiKey: mockApiKey,
|
|
152
|
+
clientId: mockClientId,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const result = await auth.authorizeUser({
|
|
156
|
+
sub: '',
|
|
157
|
+
email: '',
|
|
158
|
+
});
|
|
159
|
+
expect(result).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('can be overridden with custom authorization logic', async () => {
|
|
164
|
+
const workos = new MastraAuthWorkos({
|
|
165
|
+
apiKey: mockApiKey,
|
|
166
|
+
clientId: mockClientId,
|
|
167
|
+
async authorizeUser(user: any): Promise<boolean> {
|
|
168
|
+
// Custom authorization logic that checks for specific permissions
|
|
169
|
+
return user?.permissions?.includes('admin') ?? false;
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Test with admin user
|
|
174
|
+
const adminUser = { sub: 'user123', permissions: ['admin'] };
|
|
175
|
+
expect(await workos.authorizeUser(adminUser)).toBe(true);
|
|
176
|
+
|
|
177
|
+
// Test with non-admin user
|
|
178
|
+
const regularUser = { sub: 'user456', permissions: ['read'] };
|
|
179
|
+
expect(await workos.authorizeUser(regularUser)).toBe(false);
|
|
180
|
+
|
|
181
|
+
// Test with user without permissions
|
|
182
|
+
const noPermissionsUser = { sub: 'user789' };
|
|
183
|
+
expect(await workos.authorizeUser(noPermissionsUser)).toBe(false);
|
|
184
|
+
});
|
|
185
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { verifyJwks } from '@mastra/auth';
|
|
2
|
+
import type { JwtPayload } from '@mastra/auth';
|
|
3
|
+
import type { MastraAuthProviderOptions } from '@mastra/core/server';
|
|
4
|
+
import { MastraAuthProvider } from '@mastra/core/server';
|
|
5
|
+
import { WorkOS } from '@workos-inc/node';
|
|
6
|
+
|
|
7
|
+
type WorkosUser = JwtPayload;
|
|
8
|
+
|
|
9
|
+
interface MastraAuthWorkosOptions extends MastraAuthProviderOptions<WorkosUser> {
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
clientId?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class MastraAuthWorkos extends MastraAuthProvider<WorkosUser> {
|
|
15
|
+
protected workos: WorkOS;
|
|
16
|
+
|
|
17
|
+
constructor(options?: MastraAuthWorkosOptions) {
|
|
18
|
+
super({ name: options?.name ?? 'workos' });
|
|
19
|
+
|
|
20
|
+
const apiKey = options?.apiKey ?? process.env.WORKOS_API_KEY;
|
|
21
|
+
const clientId = options?.clientId ?? process.env.WORKOS_CLIENT_ID;
|
|
22
|
+
|
|
23
|
+
if (!apiKey || !clientId) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'WorkOS API key and client ID are required, please provide them in the options or set the environment variables WORKOS_API_KEY and WORKOS_CLIENT_ID',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.workos = new WorkOS(apiKey, {
|
|
30
|
+
clientId,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.registerOptions(options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async authenticateToken(token: string): Promise<WorkosUser | null> {
|
|
37
|
+
const jwksUri = this.workos.userManagement.getJwksUrl(process.env.WORKOS_CLIENT_ID!);
|
|
38
|
+
const user = await verifyJwks(token, jwksUri);
|
|
39
|
+
return user;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async authorizeUser(user: WorkosUser) {
|
|
43
|
+
if (!user) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const org = await this.workos.userManagement.listOrganizationMemberships({
|
|
48
|
+
userId: user.sub,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const roles = org.data.map(org => org.role);
|
|
52
|
+
|
|
53
|
+
const isAdmin = roles.some(role => role.slug === 'admin');
|
|
54
|
+
|
|
55
|
+
return isAdmin;
|
|
56
|
+
}
|
|
57
|
+
}
|
package/tsconfig.json
ADDED