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