@mastra/auth-workos 1.0.0 → 1.1.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,178 +1,883 @@
1
1
  'use strict';
2
2
 
3
3
  var auth = require('@mastra/auth');
4
+ var server = require('@mastra/core/server');
5
+ var authkitSession = require('@workos/authkit-session');
4
6
  var node = require('@workos-inc/node');
7
+ var ee = require('@mastra/core/auth/ee');
8
+ var lruCache = require('lru-cache');
5
9
 
6
- // src/index.ts
10
+ // src/auth-provider.ts
11
+ var WebSessionStorage = class extends authkitSession.CookieSessionStorage {
12
+ constructor(config) {
13
+ super(config);
14
+ }
15
+ /**
16
+ * Extract the encrypted session cookie from a Request.
17
+ *
18
+ * @param request - Standard Web Request object
19
+ * @returns The encrypted session string or null if not present
20
+ */
21
+ async getSession(request) {
22
+ const cookieHeader = request.headers.get("Cookie");
23
+ if (!cookieHeader) {
24
+ return null;
25
+ }
26
+ const cookies = cookieHeader.split(";").reduce(
27
+ (acc, cookie) => {
28
+ const [name, ...valueParts] = cookie.trim().split("=");
29
+ if (name) {
30
+ acc[name] = decodeURIComponent(valueParts.join("="));
31
+ }
32
+ return acc;
33
+ },
34
+ {}
35
+ );
36
+ return cookies[this.cookieName] || null;
37
+ }
38
+ };
7
39
 
8
- // ../../packages/core/dist/chunk-NRUZYMHE.js
9
- var RegisteredLogger = {
10
- LLM: "LLM"};
11
- var LogLevel = {
12
- DEBUG: "debug",
13
- INFO: "info",
14
- WARN: "warn",
15
- ERROR: "error"};
16
- var MastraLogger = class {
17
- name;
18
- level;
19
- transports;
20
- constructor(options = {}) {
21
- this.name = options.name || "Mastra";
22
- this.level = options.level || LogLevel.ERROR;
23
- this.transports = new Map(Object.entries(options.transports || {}));
24
- }
25
- getTransports() {
26
- return this.transports;
27
- }
28
- trackException(_error) {
29
- }
30
- async listLogs(transportId, params) {
31
- if (!transportId || !this.transports.has(transportId)) {
32
- return { logs: [], total: 0, page: params?.page ?? 1, perPage: params?.perPage ?? 100, hasMore: false };
33
- }
34
- return this.transports.get(transportId).listLogs(params) ?? {
35
- logs: [],
36
- total: 0,
37
- page: params?.page ?? 1,
38
- perPage: params?.perPage ?? 100,
39
- hasMore: false
40
+ // src/types.ts
41
+ function mapWorkOSUserToEEUser(user) {
42
+ return {
43
+ id: user.id,
44
+ email: user.email,
45
+ name: user.firstName && user.lastName ? `${user.firstName} ${user.lastName}` : user.firstName || user.email,
46
+ avatarUrl: user.profilePictureUrl ?? void 0,
47
+ metadata: {
48
+ workosId: user.id,
49
+ emailVerified: user.emailVerified,
50
+ createdAt: user.createdAt
51
+ }
52
+ };
53
+ }
54
+
55
+ // src/auth-provider.ts
56
+ var DEV_COOKIE_PASSWORD = crypto.randomUUID() + crypto.randomUUID();
57
+ var MastraAuthWorkos = class extends server.MastraAuthProvider {
58
+ workos;
59
+ clientId;
60
+ redirectUri;
61
+ ssoConfig;
62
+ authService;
63
+ config;
64
+ constructor(options) {
65
+ super({ name: options?.name ?? "workos" });
66
+ const apiKey = options?.apiKey ?? process.env.WORKOS_API_KEY;
67
+ const clientId = options?.clientId ?? process.env.WORKOS_CLIENT_ID;
68
+ const redirectUri = options?.redirectUri ?? process.env.WORKOS_REDIRECT_URI;
69
+ const cookiePassword = options?.session?.cookiePassword ?? process.env.WORKOS_COOKIE_PASSWORD ?? DEV_COOKIE_PASSWORD;
70
+ if (!apiKey || !clientId) {
71
+ throw new Error(
72
+ "WorkOS API key and client ID are required. Provide them in the options or set WORKOS_API_KEY and WORKOS_CLIENT_ID environment variables."
73
+ );
74
+ }
75
+ if (!redirectUri) {
76
+ throw new Error(
77
+ "WorkOS redirect URI is required. Provide it in the options or set WORKOS_REDIRECT_URI environment variable."
78
+ );
79
+ }
80
+ if (cookiePassword.length < 32) {
81
+ throw new Error(
82
+ "Cookie password must be at least 32 characters. Set WORKOS_COOKIE_PASSWORD environment variable or provide session.cookiePassword option."
83
+ );
84
+ }
85
+ this.clientId = clientId;
86
+ this.redirectUri = redirectUri;
87
+ this.ssoConfig = options?.sso;
88
+ this.workos = new node.WorkOS(apiKey, { clientId });
89
+ this.config = {
90
+ clientId,
91
+ apiKey,
92
+ redirectUri,
93
+ cookiePassword,
94
+ cookieName: options?.session?.cookieName ?? "wos_session",
95
+ cookieMaxAge: options?.session?.maxAge ?? 60 * 60 * 24 * 400,
96
+ // 400 days
97
+ cookieSameSite: options?.session?.sameSite?.toLowerCase(),
98
+ cookieDomain: void 0,
99
+ apiHttps: true
40
100
  };
101
+ const storage = new WebSessionStorage(this.config);
102
+ this.authService = new authkitSession.AuthService(this.config, storage, this.workos, authkitSession.sessionEncryption);
103
+ this.registerOptions(options);
104
+ if (cookiePassword === DEV_COOKIE_PASSWORD) {
105
+ console.warn(
106
+ "[WorkOS] Using auto-generated cookie password for development. Sessions will not persist across server restarts. Set WORKOS_COOKIE_PASSWORD for persistent sessions."
107
+ );
108
+ }
41
109
  }
42
- async listLogsByRunId({
43
- transportId,
44
- runId,
45
- fromDate,
46
- toDate,
47
- logLevel,
48
- filters,
49
- page,
50
- perPage
51
- }) {
52
- if (!transportId || !this.transports.has(transportId) || !runId) {
53
- return { logs: [], total: 0, page: page ?? 1, perPage: perPage ?? 100, hasMore: false };
54
- }
55
- return this.transports.get(transportId).listLogsByRunId({ runId, fromDate, toDate, logLevel, filters, page, perPage }) ?? {
56
- logs: [],
57
- total: 0,
58
- page: page ?? 1,
59
- perPage: perPage ?? 100,
60
- hasMore: false
61
- };
110
+ // ============================================================================
111
+ // MastraAuthProvider Implementation
112
+ // ============================================================================
113
+ /**
114
+ * Authenticate a bearer token or session cookie.
115
+ *
116
+ * Uses AuthKit's withAuth() for cookie-based sessions, falls back to
117
+ * JWT verification for bearer tokens.
118
+ */
119
+ async authenticateToken(token, request) {
120
+ try {
121
+ const rawRequest = "raw" in request ? request.raw : request;
122
+ const { auth: auth$1 } = await this.authService.withAuth(rawRequest);
123
+ if (auth$1.user) {
124
+ return {
125
+ ...mapWorkOSUserToEEUser(auth$1.user),
126
+ workosId: auth$1.user.id,
127
+ organizationId: auth$1.organizationId
128
+ // Note: memberships not available from session, fetch if needed
129
+ };
130
+ }
131
+ if (token) {
132
+ const jwksUri = this.workos.userManagement.getJwksUrl(this.clientId);
133
+ const payload = await auth.verifyJwks(token, jwksUri);
134
+ if (payload?.sub) {
135
+ const user = await this.workos.userManagement.getUser(payload.sub);
136
+ const memberships = await this.workos.userManagement.listOrganizationMemberships({
137
+ userId: user.id
138
+ });
139
+ return {
140
+ ...mapWorkOSUserToEEUser(user),
141
+ workosId: user.id,
142
+ organizationId: memberships.data[0]?.organizationId,
143
+ memberships: memberships.data
144
+ };
145
+ }
146
+ }
147
+ return null;
148
+ } catch {
149
+ return null;
150
+ }
62
151
  }
63
- };
64
- var ConsoleLogger = class extends MastraLogger {
65
- constructor(options = {}) {
66
- super(options);
152
+ /**
153
+ * Authorize a user for access.
154
+ */
155
+ async authorizeUser(user) {
156
+ return !!user?.id && !!user?.workosId;
67
157
  }
68
- debug(message, ...args) {
69
- if (this.level === LogLevel.DEBUG) {
70
- console.info(message, ...args);
158
+ // ============================================================================
159
+ // IUserProvider Implementation
160
+ // ============================================================================
161
+ /**
162
+ * Get the current user from the request using AuthKit session.
163
+ */
164
+ async getCurrentUser(request) {
165
+ try {
166
+ const { auth, refreshedSessionData } = await this.authService.withAuth(request);
167
+ if (!auth.user) {
168
+ return null;
169
+ }
170
+ let organizationId = auth.organizationId;
171
+ if (!organizationId) {
172
+ try {
173
+ const memberships = await this.workos.userManagement.listOrganizationMemberships({
174
+ userId: auth.user.id
175
+ });
176
+ organizationId = memberships.data[0]?.organizationId;
177
+ } catch {
178
+ }
179
+ }
180
+ const user = {
181
+ ...mapWorkOSUserToEEUser(auth.user),
182
+ workosId: auth.user.id,
183
+ organizationId
184
+ };
185
+ if (refreshedSessionData) {
186
+ user._refreshedSessionData = refreshedSessionData;
187
+ }
188
+ return user;
189
+ } catch {
190
+ return null;
71
191
  }
72
192
  }
73
- info(message, ...args) {
74
- if (this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
75
- console.info(message, ...args);
193
+ /**
194
+ * Get a user by their ID.
195
+ */
196
+ async getUser(userId) {
197
+ try {
198
+ const user = await this.workos.userManagement.getUser(userId);
199
+ return {
200
+ ...mapWorkOSUserToEEUser(user),
201
+ workosId: user.id
202
+ };
203
+ } catch {
204
+ return null;
76
205
  }
77
206
  }
78
- warn(message, ...args) {
79
- if (this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
80
- console.info(message, ...args);
207
+ /**
208
+ * Get the URL to the user's profile page.
209
+ */
210
+ getUserProfileUrl(user) {
211
+ return `/profile/${user.id}`;
212
+ }
213
+ // ============================================================================
214
+ // ISSOProvider Implementation
215
+ // ============================================================================
216
+ /**
217
+ * Get the URL to redirect users to for SSO login.
218
+ */
219
+ getLoginUrl(redirectUri, state) {
220
+ const baseOptions = {
221
+ clientId: this.clientId,
222
+ redirectUri: redirectUri || this.redirectUri,
223
+ state
224
+ };
225
+ if (this.ssoConfig?.connection) {
226
+ return this.workos.userManagement.getAuthorizationUrl({
227
+ ...baseOptions,
228
+ connectionId: this.ssoConfig.connection
229
+ });
230
+ } else if (this.ssoConfig?.provider) {
231
+ return this.workos.userManagement.getAuthorizationUrl({
232
+ ...baseOptions,
233
+ provider: this.ssoConfig.provider
234
+ });
235
+ } else if (this.ssoConfig?.defaultOrganization) {
236
+ return this.workos.userManagement.getAuthorizationUrl({
237
+ ...baseOptions,
238
+ organizationId: this.ssoConfig.defaultOrganization
239
+ });
81
240
  }
241
+ return this.workos.userManagement.getAuthorizationUrl({
242
+ ...baseOptions,
243
+ provider: "authkit"
244
+ });
82
245
  }
83
- error(message, ...args) {
84
- if (this.level === LogLevel.ERROR || this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
85
- console.error(message, ...args);
246
+ /**
247
+ * Handle the OAuth callback from WorkOS.
248
+ *
249
+ * Uses AuthKit's handleCallback for proper session creation.
250
+ */
251
+ async handleCallback(code, _state) {
252
+ const result = await this.authService.handleCallback(
253
+ new Request("http://localhost"),
254
+ // Dummy request, not used
255
+ new Response(),
256
+ // Dummy response to get headers
257
+ { code, state: _state }
258
+ );
259
+ const user = {
260
+ ...mapWorkOSUserToEEUser(result.authResponse.user),
261
+ workosId: result.authResponse.user.id,
262
+ organizationId: result.authResponse.organizationId
263
+ };
264
+ const sessionCookie = result.headers?.["Set-Cookie"];
265
+ const cookies = sessionCookie ? Array.isArray(sessionCookie) ? sessionCookie : [sessionCookie] : void 0;
266
+ return {
267
+ user,
268
+ tokens: {
269
+ accessToken: result.authResponse.accessToken,
270
+ refreshToken: result.authResponse.refreshToken
271
+ },
272
+ cookies
273
+ };
274
+ }
275
+ /**
276
+ * Get the URL to redirect users to for logout.
277
+ * Extracts session ID from the request's JWT to build a valid WorkOS logout URL.
278
+ *
279
+ * @param redirectUri - URL to redirect to after logout
280
+ * @param request - Request containing session cookie (needed to extract sid)
281
+ * @returns Logout URL or null if no active session
282
+ */
283
+ async getLogoutUrl(redirectUri, request) {
284
+ if (!request) {
285
+ return null;
286
+ }
287
+ try {
288
+ const { auth } = await this.authService.withAuth(request);
289
+ if (!auth.user) {
290
+ return null;
291
+ }
292
+ const [, payloadBase64] = auth.accessToken.split(".");
293
+ if (!payloadBase64) {
294
+ return null;
295
+ }
296
+ const payload = JSON.parse(atob(payloadBase64));
297
+ const sessionId = payload.sid;
298
+ if (!sessionId) {
299
+ return null;
300
+ }
301
+ return this.workos.userManagement.getLogoutUrl({ sessionId, returnTo: redirectUri });
302
+ } catch {
303
+ return null;
86
304
  }
87
305
  }
88
- async listLogs(_transportId, _params) {
89
- return { logs: [], total: 0, page: _params?.page ?? 1, perPage: _params?.perPage ?? 100, hasMore: false };
306
+ /**
307
+ * Get the configuration for rendering the login button.
308
+ */
309
+ getLoginButtonConfig() {
310
+ let text = "Sign in";
311
+ if (this.ssoConfig?.provider) {
312
+ const providerNames = {
313
+ GoogleOAuth: "Google",
314
+ MicrosoftOAuth: "Microsoft",
315
+ GitHubOAuth: "GitHub",
316
+ AppleOAuth: "Apple"
317
+ };
318
+ const providerName = providerNames[this.ssoConfig.provider];
319
+ if (providerName) {
320
+ text = `Sign in with ${providerName}`;
321
+ }
322
+ }
323
+ return {
324
+ provider: "workos",
325
+ text
326
+ };
90
327
  }
91
- async listLogsByRunId(_args) {
92
- return { logs: [], total: 0, page: _args.page ?? 1, perPage: _args.perPage ?? 100, hasMore: false };
328
+ // ============================================================================
329
+ // ISessionProvider Implementation
330
+ // ============================================================================
331
+ /**
332
+ * Create a new session for a user.
333
+ *
334
+ * Note: With AuthKit, sessions are created via handleCallback.
335
+ * This method is kept for interface compatibility.
336
+ */
337
+ async createSession(userId, metadata) {
338
+ const sessionId = crypto.randomUUID();
339
+ const now = /* @__PURE__ */ new Date();
340
+ const expiresAt = new Date(now.getTime() + this.config.cookieMaxAge * 1e3);
341
+ return {
342
+ id: sessionId,
343
+ userId,
344
+ createdAt: now,
345
+ expiresAt,
346
+ metadata
347
+ };
93
348
  }
94
- };
95
-
96
- // ../../packages/core/dist/chunk-LSHPJWM5.js
97
- var MastraBase = class {
98
- component = RegisteredLogger.LLM;
99
- logger;
100
- name;
101
- constructor({ component, name }) {
102
- this.component = component || RegisteredLogger.LLM;
103
- this.name = name;
104
- this.logger = new ConsoleLogger({ name: `${this.component} - ${this.name}` });
349
+ /**
350
+ * Validate a session.
351
+ *
352
+ * With AuthKit, sessions are validated via withAuth().
353
+ */
354
+ async validateSession(_sessionId) {
355
+ return null;
105
356
  }
106
357
  /**
107
- * Set the logger for the agent
108
- * @param logger
358
+ * Destroy a session.
109
359
  */
110
- __setLogger(logger) {
111
- this.logger = logger;
112
- if (this.component !== RegisteredLogger.LLM) {
113
- this.logger.debug(`Logger updated [component=${this.component}] [name=${this.name}]`);
360
+ async destroySession(_sessionId) {
361
+ }
362
+ /**
363
+ * Refresh a session.
364
+ */
365
+ async refreshSession(_sessionId) {
366
+ return null;
367
+ }
368
+ /**
369
+ * Extract session ID from a request.
370
+ */
371
+ getSessionIdFromRequest(_request) {
372
+ return null;
373
+ }
374
+ /**
375
+ * Get response headers to set the session cookie.
376
+ */
377
+ getSessionHeaders(session) {
378
+ const sessionCookie = session._sessionCookie;
379
+ if (sessionCookie) {
380
+ return { "Set-Cookie": Array.isArray(sessionCookie) ? sessionCookie[0] : sessionCookie };
114
381
  }
382
+ return {};
383
+ }
384
+ /**
385
+ * Get response headers to clear the session cookie.
386
+ */
387
+ getClearSessionHeaders() {
388
+ const cookieParts = [`${this.config.cookieName}=`, "Path=/", "Max-Age=0", "HttpOnly"];
389
+ return { "Set-Cookie": cookieParts.join("; ") };
390
+ }
391
+ // ============================================================================
392
+ // Helper Methods
393
+ // ============================================================================
394
+ /**
395
+ * Get the underlying WorkOS client.
396
+ */
397
+ getWorkOS() {
398
+ return this.workos;
399
+ }
400
+ /**
401
+ * Get the AuthKit AuthService.
402
+ */
403
+ getAuthService() {
404
+ return this.authService;
405
+ }
406
+ /**
407
+ * Get the configured client ID.
408
+ */
409
+ getClientId() {
410
+ return this.clientId;
411
+ }
412
+ /**
413
+ * Get the configured redirect URI.
414
+ */
415
+ getRedirectUri() {
416
+ return this.redirectUri;
115
417
  }
116
418
  };
117
-
118
- // ../../packages/core/dist/server/index.js
119
- var MastraAuthProvider = class extends MastraBase {
120
- protected;
121
- public;
419
+ var DEFAULT_CACHE_TTL_MS = 60 * 1e3;
420
+ var DEFAULT_CACHE_MAX_SIZE = 1e3;
421
+ var MastraRBACWorkos = class {
422
+ workos;
423
+ options;
424
+ /**
425
+ * Single cache for roles (the expensive WorkOS API call).
426
+ * Permissions are derived from roles on-the-fly (cheap, synchronous).
427
+ * Storing promises handles concurrent request deduplication.
428
+ */
429
+ rolesCache;
430
+ /**
431
+ * Expose roleMapping for middleware access.
432
+ * This allows the authorization middleware to resolve permissions
433
+ * without needing to call the async methods.
434
+ */
435
+ get roleMapping() {
436
+ return this.options.roleMapping;
437
+ }
438
+ /**
439
+ * Create a new WorkOS RBAC provider.
440
+ *
441
+ * @param options - RBAC configuration options
442
+ */
122
443
  constructor(options) {
123
- super({ component: "AUTH", name: options?.name });
124
- if (options?.authorizeUser) {
125
- this.authorizeUser = options.authorizeUser.bind(this);
444
+ const apiKey = options.apiKey ?? process.env.WORKOS_API_KEY;
445
+ const clientId = options.clientId ?? process.env.WORKOS_CLIENT_ID;
446
+ if (!apiKey || !clientId) {
447
+ throw new Error(
448
+ "WorkOS API key and client ID are required. Provide them in the options or set WORKOS_API_KEY and WORKOS_CLIENT_ID environment variables."
449
+ );
126
450
  }
127
- this.protected = options?.protected;
128
- this.public = options?.public;
451
+ this.workos = new node.WorkOS(apiKey, { clientId });
452
+ this.options = options;
453
+ this.rolesCache = new lruCache.LRUCache({
454
+ max: options.cache?.maxSize ?? DEFAULT_CACHE_MAX_SIZE,
455
+ ttl: options.cache?.ttlMs ?? DEFAULT_CACHE_TTL_MS
456
+ });
129
457
  }
130
- registerOptions(opts) {
131
- if (opts?.authorizeUser) {
132
- this.authorizeUser = opts.authorizeUser.bind(this);
458
+ /**
459
+ * Get all roles for a user from their WorkOS organization memberships.
460
+ *
461
+ * Fetches organization memberships from WorkOS and extracts role slugs.
462
+ * If an organizationId is configured, only returns roles from that organization.
463
+ * Otherwise, returns roles from all organizations the user belongs to.
464
+ *
465
+ * Results are cached and concurrent requests are deduplicated.
466
+ *
467
+ * @param user - WorkOS user to get roles for
468
+ * @returns Array of role slugs
469
+ */
470
+ async getRoles(user) {
471
+ if (user.memberships && user.memberships.length > 0) {
472
+ return this.extractRolesFromMemberships(user);
133
473
  }
134
- if (opts?.protected) {
135
- this.protected = opts.protected;
474
+ const cacheKey = user.workosId ?? user.id;
475
+ const cached = this.rolesCache.get(cacheKey);
476
+ if (cached) {
477
+ return cached;
478
+ }
479
+ const rolesPromise = this.fetchRolesFromWorkOS(user);
480
+ this.rolesCache.set(cacheKey, rolesPromise);
481
+ return rolesPromise;
482
+ }
483
+ /**
484
+ * Fetch roles from WorkOS API.
485
+ */
486
+ async fetchRolesFromWorkOS(user) {
487
+ try {
488
+ const memberships = await this.workos.userManagement.listOrganizationMemberships({
489
+ userId: user.workosId
490
+ });
491
+ const relevantMemberships = this.options.organizationId ? memberships.data.filter((m) => m.organizationId === this.options.organizationId) : memberships.data;
492
+ return relevantMemberships.map((m) => m.role.slug);
493
+ } catch {
494
+ return [];
495
+ }
496
+ }
497
+ /**
498
+ * Check if a user has a specific role.
499
+ *
500
+ * @param user - WorkOS user to check
501
+ * @param role - Role slug to check for
502
+ * @returns True if user has the role
503
+ */
504
+ async hasRole(user, role) {
505
+ const roles = await this.getRoles(user);
506
+ return roles.includes(role);
507
+ }
508
+ /**
509
+ * Get all permissions for a user by mapping their WorkOS roles.
510
+ *
511
+ * Uses the configured roleMapping to translate WorkOS role slugs
512
+ * into Mastra permission strings. Roles are cached; permissions
513
+ * are derived on-the-fly (cheap, synchronous operation).
514
+ *
515
+ * If the user has no roles (no organization memberships), the
516
+ * _default permissions from the role mapping are applied.
517
+ *
518
+ * @param user - WorkOS user to get permissions for
519
+ * @returns Array of permission strings
520
+ */
521
+ async getPermissions(user) {
522
+ const roles = await this.getRoles(user);
523
+ if (roles.length === 0) {
524
+ return this.options.roleMapping["_default"] ?? [];
136
525
  }
137
- if (opts?.public) {
138
- this.public = opts.public;
526
+ return ee.resolvePermissionsFromMapping(roles, this.options.roleMapping);
527
+ }
528
+ /**
529
+ * Check if a user has a specific permission.
530
+ *
531
+ * Uses wildcard matching to check if any of the user's permissions
532
+ * grant access to the required permission.
533
+ *
534
+ * @param user - WorkOS user to check
535
+ * @param permission - Permission to check for (e.g., 'agents:read')
536
+ * @returns True if user has the permission
537
+ */
538
+ async hasPermission(user, permission) {
539
+ const permissions = await this.getPermissions(user);
540
+ return permissions.some((p) => ee.matchesPermission(p, permission));
541
+ }
542
+ /**
543
+ * Check if a user has ALL of the specified permissions.
544
+ *
545
+ * @param user - WorkOS user to check
546
+ * @param permissions - Array of permissions to check for
547
+ * @returns True if user has all permissions
548
+ */
549
+ async hasAllPermissions(user, permissions) {
550
+ const userPermissions = await this.getPermissions(user);
551
+ return permissions.every((required) => userPermissions.some((p) => ee.matchesPermission(p, required)));
552
+ }
553
+ /**
554
+ * Check if a user has ANY of the specified permissions.
555
+ *
556
+ * @param user - WorkOS user to check
557
+ * @param permissions - Array of permissions to check for
558
+ * @returns True if user has at least one permission
559
+ */
560
+ async hasAnyPermission(user, permissions) {
561
+ const userPermissions = await this.getPermissions(user);
562
+ return permissions.some((required) => userPermissions.some((p) => ee.matchesPermission(p, required)));
563
+ }
564
+ /**
565
+ * Clear the roles cache.
566
+ *
567
+ * Call this when system-wide role changes occur.
568
+ * For individual user changes, prefer clearUserCache() instead.
569
+ */
570
+ clearCache() {
571
+ this.rolesCache.clear();
572
+ }
573
+ /**
574
+ * Clear cached roles for a specific user.
575
+ *
576
+ * Call this when a user's roles change to ensure fresh permission resolution
577
+ * on their next request. This is more efficient than clearing the entire cache.
578
+ *
579
+ * @param userId - The user ID to clear from cache
580
+ */
581
+ clearUserCache(userId) {
582
+ this.rolesCache.delete(userId);
583
+ }
584
+ /**
585
+ * Get cache statistics for monitoring.
586
+ *
587
+ * @returns Object with cache size and max size
588
+ */
589
+ getCacheStats() {
590
+ return {
591
+ size: this.rolesCache.size,
592
+ maxSize: this.rolesCache.max
593
+ };
594
+ }
595
+ /**
596
+ * Extract role slugs from memberships attached to the user object.
597
+ *
598
+ * @param user - WorkOS user with memberships
599
+ * @returns Array of role slugs
600
+ */
601
+ extractRolesFromMemberships(user) {
602
+ if (!user.memberships) {
603
+ return [];
139
604
  }
605
+ const relevantMemberships = this.options.organizationId ? user.memberships.filter((m) => m.organizationId === this.options.organizationId) : user.memberships;
606
+ return relevantMemberships.map((m) => m.role.slug);
140
607
  }
141
608
  };
142
- var MastraAuthWorkos = class extends MastraAuthProvider {
609
+
610
+ // src/directory-sync.ts
611
+ var WorkOSDirectorySync = class {
143
612
  workos;
144
- constructor(options) {
145
- super({ name: options?.name ?? "workos" });
146
- const apiKey = options?.apiKey ?? process.env.WORKOS_API_KEY;
147
- const clientId = options?.clientId ?? process.env.WORKOS_CLIENT_ID;
148
- if (!apiKey || !clientId) {
613
+ webhookSecret;
614
+ handlers;
615
+ /**
616
+ * Creates a new WorkOSDirectorySync instance.
617
+ *
618
+ * @param workos - WorkOS client instance
619
+ * @param options - Configuration options including webhook secret and event handlers
620
+ * @throws Error if webhook secret is not provided
621
+ */
622
+ constructor(workos, options) {
623
+ this.workos = workos;
624
+ const webhookSecret = options.webhookSecret ?? process.env.WORKOS_WEBHOOK_SECRET;
625
+ if (!webhookSecret) {
149
626
  throw new Error(
150
- "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"
627
+ "WorkOS webhook secret is required. Provide it in options or set WORKOS_WEBHOOK_SECRET environment variable."
151
628
  );
152
629
  }
153
- this.workos = new node.WorkOS(apiKey, {
154
- clientId
155
- });
156
- this.registerOptions(options);
630
+ this.webhookSecret = webhookSecret;
631
+ this.handlers = options.handlers;
157
632
  }
158
- async authenticateToken(token) {
159
- const jwksUri = this.workos.userManagement.getJwksUrl(process.env.WORKOS_CLIENT_ID);
160
- const user = await auth.verifyJwks(token, jwksUri);
161
- return user;
633
+ /**
634
+ * Handles incoming webhook events from WorkOS Directory Sync.
635
+ *
636
+ * This method verifies the webhook signature for security, parses the event,
637
+ * and routes it to the appropriate handler based on the event type.
638
+ *
639
+ * @param payload - Raw webhook payload (string or object)
640
+ * @param signature - WorkOS signature header for verification
641
+ * @throws Error if signature verification fails
642
+ */
643
+ async handleWebhook(payload, signature) {
644
+ const parsedPayload = typeof payload === "string" ? JSON.parse(payload) : payload;
645
+ const event = await this.workos.webhooks.constructEvent({
646
+ payload: parsedPayload,
647
+ sigHeader: signature,
648
+ secret: this.webhookSecret
649
+ });
650
+ try {
651
+ await this.routeEvent(event);
652
+ } catch (error) {
653
+ console.error(`[WorkOSDirectorySync] Error handling event ${event.event}:`, error);
654
+ }
162
655
  }
163
- async authorizeUser(user) {
164
- if (!user) {
165
- return false;
656
+ /**
657
+ * Routes a directory sync event to the appropriate handler.
658
+ *
659
+ * @param event - The verified webhook event
660
+ */
661
+ async routeEvent(event) {
662
+ const { event: eventType, data } = event;
663
+ switch (eventType) {
664
+ case "dsync.user.created":
665
+ if (this.handlers.onUserCreated) {
666
+ await this.handlers.onUserCreated(this.mapUserData(data));
667
+ }
668
+ break;
669
+ case "dsync.user.updated":
670
+ if (this.handlers.onUserUpdated) {
671
+ await this.handlers.onUserUpdated(this.mapUserData(data));
672
+ }
673
+ break;
674
+ case "dsync.user.deleted":
675
+ if (this.handlers.onUserDeleted) {
676
+ await this.handlers.onUserDeleted(this.mapUserData(data));
677
+ }
678
+ break;
679
+ case "dsync.group.created":
680
+ if (this.handlers.onGroupCreated) {
681
+ await this.handlers.onGroupCreated(this.mapGroupData(data));
682
+ }
683
+ break;
684
+ case "dsync.group.updated":
685
+ if (this.handlers.onGroupUpdated) {
686
+ await this.handlers.onGroupUpdated(this.mapGroupData(data));
687
+ }
688
+ break;
689
+ case "dsync.group.deleted":
690
+ if (this.handlers.onGroupDeleted) {
691
+ await this.handlers.onGroupDeleted(this.mapGroupData(data));
692
+ }
693
+ break;
694
+ case "dsync.group.user_added":
695
+ if (this.handlers.onGroupUserAdded) {
696
+ await this.handlers.onGroupUserAdded({
697
+ group: this.mapGroupData(data.group),
698
+ user: this.mapUserData(data.user)
699
+ });
700
+ }
701
+ break;
702
+ case "dsync.group.user_removed":
703
+ if (this.handlers.onGroupUserRemoved) {
704
+ await this.handlers.onGroupUserRemoved({
705
+ group: this.mapGroupData(data.group),
706
+ user: this.mapUserData(data.user)
707
+ });
708
+ }
709
+ break;
710
+ default:
711
+ console.warn(`[WorkOSDirectorySync] Unknown event type: ${eventType}`);
166
712
  }
167
- const org = await this.workos.userManagement.listOrganizationMemberships({
168
- userId: user.sub
713
+ }
714
+ /**
715
+ * Maps raw webhook user data to the DirectorySyncUserData type.
716
+ *
717
+ * @param data - Raw user data from webhook
718
+ * @returns Typed user data
719
+ */
720
+ mapUserData(data) {
721
+ return {
722
+ id: data.id,
723
+ directoryId: data.directory_id,
724
+ organizationId: data.organization_id,
725
+ idpId: data.idp_id,
726
+ firstName: data.first_name,
727
+ lastName: data.last_name,
728
+ jobTitle: data.job_title,
729
+ emails: data.emails ?? [],
730
+ username: data.username,
731
+ groups: data.groups ?? [],
732
+ state: data.state,
733
+ rawAttributes: data.raw_attributes ?? {},
734
+ customAttributes: data.custom_attributes ?? {},
735
+ createdAt: data.created_at,
736
+ updatedAt: data.updated_at
737
+ };
738
+ }
739
+ /**
740
+ * Maps raw webhook group data to the DirectorySyncGroupData type.
741
+ *
742
+ * @param data - Raw group data from webhook
743
+ * @returns Typed group data
744
+ */
745
+ mapGroupData(data) {
746
+ return {
747
+ id: data.id,
748
+ directoryId: data.directory_id,
749
+ organizationId: data.organization_id,
750
+ idpId: data.idp_id,
751
+ name: data.name,
752
+ createdAt: data.created_at,
753
+ updatedAt: data.updated_at,
754
+ rawAttributes: data.raw_attributes ?? {}
755
+ };
756
+ }
757
+ // ===========================================================================
758
+ // Helper Methods for Directory Sync Operations
759
+ // ===========================================================================
760
+ /**
761
+ * Lists all directories for an organization.
762
+ *
763
+ * @param organizationId - The WorkOS organization ID
764
+ * @returns Array of directories
765
+ *
766
+ * @example
767
+ * ```typescript
768
+ * const directories = await directorySync.listDirectories('org_123');
769
+ * for (const dir of directories) {
770
+ * console.log(`Directory: ${dir.name} (${dir.type})`);
771
+ * }
772
+ * ```
773
+ */
774
+ async listDirectories(organizationId) {
775
+ const response = await this.workos.directorySync.listDirectories({
776
+ organizationId
777
+ });
778
+ return response.data;
779
+ }
780
+ /**
781
+ * Lists all users in a directory.
782
+ *
783
+ * @param directoryId - The directory ID
784
+ * @returns Array of directory users
785
+ *
786
+ * @example
787
+ * ```typescript
788
+ * const users = await directorySync.listDirectoryUsers('directory_123');
789
+ * for (const user of users) {
790
+ * console.log(`User: ${user.firstName} ${user.lastName}`);
791
+ * }
792
+ * ```
793
+ */
794
+ async listDirectoryUsers(directoryId) {
795
+ const response = await this.workos.directorySync.listUsers({
796
+ directory: directoryId
797
+ });
798
+ return response.data;
799
+ }
800
+ /**
801
+ * Lists all groups in a directory.
802
+ *
803
+ * @param directoryId - The directory ID
804
+ * @returns Array of directory groups
805
+ *
806
+ * @example
807
+ * ```typescript
808
+ * const groups = await directorySync.listDirectoryGroups('directory_123');
809
+ * for (const group of groups) {
810
+ * console.log(`Group: ${group.name}`);
811
+ * }
812
+ * ```
813
+ */
814
+ async listDirectoryGroups(directoryId) {
815
+ const response = await this.workos.directorySync.listGroups({
816
+ directory: directoryId
817
+ });
818
+ return response.data;
819
+ }
820
+ };
821
+ var INTENT_MAP = {
822
+ sso: node.GeneratePortalLinkIntent.SSO,
823
+ dsync: node.GeneratePortalLinkIntent.DSync,
824
+ audit_logs: node.GeneratePortalLinkIntent.AuditLogs,
825
+ log_streams: node.GeneratePortalLinkIntent.LogStreams
826
+ };
827
+ var WorkOSAdminPortal = class {
828
+ workos;
829
+ returnUrl;
830
+ /**
831
+ * Creates a new WorkOSAdminPortal instance.
832
+ *
833
+ * @param workos - The WorkOS client instance
834
+ * @param options - Configuration options for the Admin Portal
835
+ */
836
+ constructor(workos, options) {
837
+ this.workos = workos;
838
+ this.returnUrl = options?.returnUrl ?? "/";
839
+ }
840
+ /**
841
+ * Generates a link to the WorkOS Admin Portal for a specific organization.
842
+ *
843
+ * The generated link is a one-time use URL that expires after a short period.
844
+ * Users should be redirected to this link immediately after generation.
845
+ *
846
+ * @param organizationId - The WorkOS organization ID (e.g., 'org_01H...')
847
+ * @param intent - The portal section to open. Determines what the user can configure:
848
+ * - `'sso'`: Configure SSO connections (SAML, OIDC providers)
849
+ * - `'dsync'`: Configure Directory Sync (SCIM provisioning)
850
+ * - `'audit_logs'`: View and export audit logs
851
+ * - `'log_streams'`: Configure log streaming to external SIEM systems
852
+ * @returns A promise that resolves to the Admin Portal URL
853
+ *
854
+ * @example
855
+ * ```typescript
856
+ * // SSO configuration (default)
857
+ * const link = await adminPortal.getPortalLink('org_01H...');
858
+ *
859
+ * // Directory Sync configuration
860
+ * const link = await adminPortal.getPortalLink('org_01H...', 'dsync');
861
+ *
862
+ * // Audit logs viewing
863
+ * const link = await adminPortal.getPortalLink('org_01H...', 'audit_logs');
864
+ * ```
865
+ */
866
+ async getPortalLink(organizationId, intent) {
867
+ const result = await this.workos.portal.generateLink({
868
+ organization: organizationId,
869
+ intent: INTENT_MAP[intent ?? "sso"],
870
+ returnUrl: this.returnUrl
169
871
  });
170
- const roles = org.data.map((org2) => org2.role);
171
- const isAdmin = roles.some((role) => role.slug === "admin");
172
- return isAdmin;
872
+ return result.link;
173
873
  }
174
874
  };
175
875
 
176
876
  exports.MastraAuthWorkos = MastraAuthWorkos;
877
+ exports.MastraRBACWorkos = MastraRBACWorkos;
878
+ exports.WebSessionStorage = WebSessionStorage;
879
+ exports.WorkOSAdminPortal = WorkOSAdminPortal;
880
+ exports.WorkOSDirectorySync = WorkOSDirectorySync;
881
+ exports.mapWorkOSUserToEEUser = mapWorkOSUserToEEUser;
177
882
  //# sourceMappingURL=index.cjs.map
178
883
  //# sourceMappingURL=index.cjs.map