@mastra/auth-workos 1.1.2 → 1.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4,8 +4,8 @@ var auth = require('@mastra/auth');
4
4
  var server = require('@mastra/core/server');
5
5
  var authkitSession = require('@workos/authkit-session');
6
6
  var node = require('@workos-inc/node');
7
- var ee = require('@mastra/core/auth/ee');
8
7
  var lruCache = require('lru-cache');
8
+ var ee = require('@mastra/core/auth/ee');
9
9
 
10
10
  // src/auth-provider.ts
11
11
  var WebSessionStorage = class extends authkitSession.CookieSessionStorage {
@@ -54,6 +54,8 @@ function mapWorkOSUserToEEUser(user) {
54
54
 
55
55
  // src/auth-provider.ts
56
56
  var DEV_COOKIE_PASSWORD = crypto.randomUUID() + crypto.randomUUID();
57
+ var MEMBERSHIP_CACHE_TTL_MS = 60 * 1e3;
58
+ var MEMBERSHIP_CACHE_MAX_SIZE = 1e3;
57
59
  var MastraAuthWorkos = class extends server.MastraAuthProvider {
58
60
  workos;
59
61
  clientId;
@@ -61,6 +63,11 @@ var MastraAuthWorkos = class extends server.MastraAuthProvider {
61
63
  ssoConfig;
62
64
  authService;
63
65
  config;
66
+ fetchMemberships;
67
+ trustJwtClaims;
68
+ jwtClaimOptions;
69
+ mapJwtPayloadToUser;
70
+ membershipCache;
64
71
  constructor(options) {
65
72
  super({ name: options?.name ?? "workos" });
66
73
  const apiKey = options?.apiKey ?? process.env.WORKOS_API_KEY;
@@ -85,6 +92,14 @@ var MastraAuthWorkos = class extends server.MastraAuthProvider {
85
92
  this.clientId = clientId;
86
93
  this.redirectUri = redirectUri;
87
94
  this.ssoConfig = options?.sso;
95
+ this.fetchMemberships = options?.fetchMemberships ?? false;
96
+ this.trustJwtClaims = options?.trustJwtClaims ?? false;
97
+ this.jwtClaimOptions = options?.jwtClaims;
98
+ this.mapJwtPayloadToUser = options?.mapJwtPayloadToUser;
99
+ this.membershipCache = new lruCache.LRUCache({
100
+ max: MEMBERSHIP_CACHE_MAX_SIZE,
101
+ ttl: MEMBERSHIP_CACHE_TTL_MS
102
+ });
88
103
  this.workos = new node.WorkOS(apiKey, { clientId });
89
104
  this.config = {
90
105
  clientId,
@@ -121,27 +136,57 @@ var MastraAuthWorkos = class extends server.MastraAuthProvider {
121
136
  const rawRequest = "raw" in request ? request.raw : request;
122
137
  const { auth: auth$1 } = await this.authService.withAuth(rawRequest);
123
138
  if (auth$1.user) {
139
+ let memberships;
140
+ if (this.fetchMemberships) {
141
+ try {
142
+ memberships = await this.getMemberships(auth$1.user.id);
143
+ } catch {
144
+ }
145
+ }
124
146
  return {
125
147
  ...mapWorkOSUserToEEUser(auth$1.user),
126
148
  workosId: auth$1.user.id,
127
- organizationId: auth$1.organizationId
128
- // Note: memberships not available from session, fetch if needed
149
+ organizationId: auth$1.organizationId,
150
+ memberships
129
151
  };
130
152
  }
131
153
  if (token) {
132
154
  const jwksUri = this.workos.userManagement.getJwksUrl(this.clientId);
133
155
  const payload = await auth.verifyJwks(token, jwksUri);
156
+ const jwtUser = this.resolveJwtPayloadUser(payload);
157
+ if (this.trustJwtClaims && jwtUser?.id && jwtUser?.workosId) {
158
+ return await this.attachMembershipsIfNeeded(jwtUser);
159
+ }
134
160
  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
- };
161
+ try {
162
+ const user = await this.workos.userManagement.getUser(payload.sub);
163
+ let memberships;
164
+ if (this.fetchMemberships) {
165
+ try {
166
+ memberships = await this.getMemberships(user.id);
167
+ } catch {
168
+ memberships = void 0;
169
+ }
170
+ }
171
+ return this.mergeJwtPayloadUser(
172
+ {
173
+ ...mapWorkOSUserToEEUser(user),
174
+ workosId: user.id,
175
+ organizationId: this.getSingleMembershipOrganizationId(memberships),
176
+ memberships
177
+ },
178
+ jwtUser,
179
+ { trustOrganizationClaims: this.trustJwtClaims }
180
+ );
181
+ } catch {
182
+ if (this.trustJwtClaims && jwtUser?.id && jwtUser?.workosId) {
183
+ return await this.attachMembershipsIfNeeded(jwtUser);
184
+ }
185
+ return null;
186
+ }
187
+ }
188
+ if (this.trustJwtClaims && jwtUser?.id && jwtUser?.workosId) {
189
+ return await this.attachMembershipsIfNeeded(jwtUser);
145
190
  }
146
191
  }
147
192
  return null;
@@ -168,19 +213,19 @@ var MastraAuthWorkos = class extends server.MastraAuthProvider {
168
213
  return null;
169
214
  }
170
215
  let organizationId = auth.organizationId;
171
- if (!organizationId) {
216
+ let memberships;
217
+ if (this.fetchMemberships) {
172
218
  try {
173
- const memberships = await this.workos.userManagement.listOrganizationMemberships({
174
- userId: auth.user.id
175
- });
176
- organizationId = memberships.data[0]?.organizationId;
219
+ memberships = await this.getMemberships(auth.user.id);
220
+ organizationId ??= this.getSingleMembershipOrganizationId(memberships);
177
221
  } catch {
178
222
  }
179
223
  }
180
224
  const user = {
181
225
  ...mapWorkOSUserToEEUser(auth.user),
182
226
  workosId: auth.user.id,
183
- organizationId
227
+ organizationId,
228
+ memberships
184
229
  };
185
230
  if (refreshedSessionData) {
186
231
  user._refreshedSessionData = refreshedSessionData;
@@ -210,6 +255,124 @@ var MastraAuthWorkos = class extends server.MastraAuthProvider {
210
255
  getUserProfileUrl(user) {
211
256
  return `/profile/${user.id}`;
212
257
  }
258
+ async getMemberships(userId) {
259
+ const cached = this.membershipCache.get(userId);
260
+ if (cached) {
261
+ return cached;
262
+ }
263
+ try {
264
+ const response = await this.workos.userManagement.listOrganizationMemberships({
265
+ userId
266
+ });
267
+ const memberships = await response.autoPagination();
268
+ this.membershipCache.set(userId, memberships);
269
+ return memberships;
270
+ } catch (error) {
271
+ this.membershipCache.delete(userId);
272
+ throw error;
273
+ }
274
+ }
275
+ async attachMembershipsIfNeeded(user) {
276
+ if (!this.fetchMemberships || user.organizationMembershipId) {
277
+ return user;
278
+ }
279
+ try {
280
+ const memberships = await this.getMemberships(user.workosId);
281
+ return {
282
+ ...user,
283
+ organizationId: user.organizationId ?? this.getSingleMembershipOrganizationId(memberships),
284
+ memberships
285
+ };
286
+ } catch {
287
+ return user;
288
+ }
289
+ }
290
+ getSingleMembershipOrganizationId(memberships) {
291
+ return memberships?.length === 1 ? memberships[0]?.organizationId : void 0;
292
+ }
293
+ resolveJwtPayloadUser(payload) {
294
+ if (!payload) {
295
+ return null;
296
+ }
297
+ const mappedClaims = this.buildUserFromJwtClaims(payload);
298
+ const customMappedClaims = this.mapJwtPayloadToUser?.(payload) ?? void 0;
299
+ const combined = {
300
+ ...payload,
301
+ ...mappedClaims ?? {},
302
+ ...customMappedClaims ?? {}
303
+ };
304
+ const id = typeof combined.id === "string" ? combined.id : void 0;
305
+ const workosId = typeof combined.workosId === "string" ? combined.workosId : id;
306
+ if (!id || !workosId) {
307
+ return null;
308
+ }
309
+ const metadata = combined.metadata && typeof combined.metadata === "object" && !Array.isArray(combined.metadata) ? combined.metadata : void 0;
310
+ return {
311
+ ...combined,
312
+ id,
313
+ workosId,
314
+ email: typeof combined.email === "string" ? combined.email : void 0,
315
+ name: typeof combined.name === "string" ? combined.name : typeof combined.email === "string" ? combined.email : id,
316
+ organizationId: typeof combined.organizationId === "string" ? combined.organizationId : void 0,
317
+ organizationMembershipId: typeof combined.organizationMembershipId === "string" ? combined.organizationMembershipId : void 0,
318
+ metadata: {
319
+ ...metadata ?? {},
320
+ workosId,
321
+ ...typeof combined.organizationId === "string" ? { organizationId: combined.organizationId } : {},
322
+ ...typeof combined.organizationMembershipId === "string" ? { organizationMembershipId: combined.organizationMembershipId } : {}
323
+ }
324
+ };
325
+ }
326
+ buildUserFromJwtClaims(payload) {
327
+ const userId = this.readJwtClaim(payload, this.jwtClaimOptions?.userId) ?? this.readJwtClaim(payload, "sub");
328
+ const workosId = this.readJwtClaim(payload, this.jwtClaimOptions?.workosId) ?? userId;
329
+ if (!userId || !workosId) {
330
+ return null;
331
+ }
332
+ return {
333
+ id: userId,
334
+ workosId,
335
+ email: this.readJwtClaim(payload, this.jwtClaimOptions?.email) ?? this.readJwtClaim(payload, "email"),
336
+ name: this.readJwtClaim(payload, this.jwtClaimOptions?.name) ?? this.readJwtClaim(payload, "name"),
337
+ organizationId: this.readJwtClaim(payload, this.jwtClaimOptions?.organizationId) ?? this.readJwtClaim(payload, "org_id"),
338
+ organizationMembershipId: this.readJwtClaim(payload, this.jwtClaimOptions?.organizationMembershipId)
339
+ };
340
+ }
341
+ mergeJwtPayloadUser(user, jwtUser, options) {
342
+ if (!jwtUser) {
343
+ return user;
344
+ }
345
+ const trustOrganizationClaims = options?.trustOrganizationClaims ?? true;
346
+ const jwtMetadata = { ...jwtUser.metadata ?? {} };
347
+ if (!trustOrganizationClaims) {
348
+ delete jwtMetadata.organizationId;
349
+ delete jwtMetadata.organizationMembershipId;
350
+ }
351
+ return {
352
+ ...jwtUser,
353
+ ...user,
354
+ organizationId: trustOrganizationClaims ? jwtUser.organizationId ?? user.organizationId : user.organizationId,
355
+ organizationMembershipId: trustOrganizationClaims ? jwtUser.organizationMembershipId ?? user.organizationMembershipId : user.organizationMembershipId,
356
+ memberships: trustOrganizationClaims ? user.memberships ?? jwtUser.memberships : user.memberships,
357
+ metadata: {
358
+ ...jwtMetadata,
359
+ ...user.metadata ?? {}
360
+ }
361
+ };
362
+ }
363
+ readJwtClaim(payload, claimPath) {
364
+ if (!claimPath) {
365
+ return void 0;
366
+ }
367
+ let current = payload;
368
+ for (const segment of claimPath.split(".")) {
369
+ if (!current || typeof current !== "object" || !(segment in current)) {
370
+ return void 0;
371
+ }
372
+ current = current[segment];
373
+ }
374
+ return typeof current === "string" ? current : void 0;
375
+ }
213
376
  // ============================================================================
214
377
  // ISSOProvider Implementation
215
378
  // ============================================================================
@@ -606,6 +769,408 @@ var MastraRBACWorkos = class {
606
769
  return relevantMemberships.map((m) => m.role.slug);
607
770
  }
608
771
  };
772
+ var FILTER_ACCESSIBLE_CHECK_CONCURRENCY = 5;
773
+ var WorkOSFGAResourceNotFoundError = class extends Error {
774
+ status = 404;
775
+ resourceType;
776
+ resourceId;
777
+ constructor(resourceType, resourceId) {
778
+ super(
779
+ `[MastraFGAWorkos] Resource '${resourceType}/${resourceId}' is not registered in WorkOS. Create the '${resourceType}' resource type in your WorkOS dashboard, then register '${resourceId}' using MastraFGAWorkos.createResource() or your seed script. See https://workos.com/docs/fga for setup instructions.`
780
+ );
781
+ this.name = "WorkOSFGAResourceNotFoundError";
782
+ this.resourceType = resourceType;
783
+ this.resourceId = resourceId;
784
+ }
785
+ };
786
+ var WorkOSFGAMembershipResolutionError = class extends Error {
787
+ status = 500;
788
+ userId;
789
+ constructor(user) {
790
+ super(
791
+ "[MastraFGAWorkos] Cannot resolve organization membership for user <redacted>. Ensure fetchMemberships is enabled on MastraAuthWorkos or provide organizationMembershipId on the user."
792
+ );
793
+ this.name = "WorkOSFGAMembershipResolutionError";
794
+ this.userId = user?.id ? "<redacted>" : void 0;
795
+ }
796
+ };
797
+ var MastraFGAWorkos = class {
798
+ workos;
799
+ organizationId;
800
+ resourceMapping;
801
+ permissionMapping;
802
+ constructor(options) {
803
+ const apiKey = options.apiKey ?? process.env.WORKOS_API_KEY;
804
+ const clientId = options.clientId ?? process.env.WORKOS_CLIENT_ID;
805
+ if (!apiKey || !clientId) {
806
+ throw new Error(
807
+ "WorkOS API key and client ID are required. Provide them in the options or set WORKOS_API_KEY and WORKOS_CLIENT_ID environment variables."
808
+ );
809
+ }
810
+ this.workos = new node.WorkOS(apiKey, { clientId });
811
+ this.organizationId = options.organizationId;
812
+ this.resourceMapping = options.resourceMapping ?? {};
813
+ this.permissionMapping = options.permissionMapping ?? {};
814
+ }
815
+ // ──────────────────────────────────────────────────────────────
816
+ // IFGAProvider — Read-only checks
817
+ // ──────────────────────────────────────────────────────────────
818
+ /**
819
+ * Check if a user has permission on a resource.
820
+ *
821
+ * Resolves the user's organization membership ID, maps the permission
822
+ * via `permissionMapping`, and delegates to `workos.authorization.check()`.
823
+ */
824
+ async check(user, params) {
825
+ const checkOptions = this.buildCheckOptions(user, params);
826
+ if (!checkOptions) return false;
827
+ try {
828
+ const result = await this.workos.authorization.check(checkOptions);
829
+ return result.authorized;
830
+ } catch (error) {
831
+ if (error?.status === 404 || error?.code === "entity_not_found") {
832
+ throw new WorkOSFGAResourceNotFoundError(params.resource.type, params.resource.id);
833
+ }
834
+ throw error;
835
+ }
836
+ }
837
+ /**
838
+ * Require that a user has permission, throwing FGADeniedError if not.
839
+ */
840
+ async require(user, params) {
841
+ const checkOptions = this.buildCheckOptions(user, params, { strictMembershipResolution: true });
842
+ if (!checkOptions) {
843
+ throw new ee.FGADeniedError(user, params.resource, params.permission);
844
+ }
845
+ try {
846
+ const result = await this.workos.authorization.check(checkOptions);
847
+ if (!result.authorized) {
848
+ throw new ee.FGADeniedError(user, params.resource, params.permission);
849
+ }
850
+ } catch (error) {
851
+ if (error instanceof ee.FGADeniedError) throw error;
852
+ if (error?.status === 404 || error?.code === "entity_not_found") {
853
+ throw new WorkOSFGAResourceNotFoundError(params.resource.type, params.resource.id);
854
+ }
855
+ throw error;
856
+ }
857
+ }
858
+ /**
859
+ * Filter resources to only those the user has permission to access.
860
+ *
861
+ * Uses WorkOS `listResourcesForMembership()` when the resource mapping can
862
+ * resolve a parent resource from user context. This avoids one check per
863
+ * resource for list endpoints like agents/workflows/tools.
864
+ *
865
+ * Falls back to per-resource `check()` calls when no parent resource can be
866
+ * resolved from the configured mapping.
867
+ */
868
+ async filterAccessible(user, resources, resourceType, permission) {
869
+ if (resources.length === 0) return [];
870
+ const membershipId = this.resolveOrganizationMembershipId(user);
871
+ if (!membershipId) return [];
872
+ const permissionSlug = this.resolvePermission(permission);
873
+ const parentResource = resourceType === "thread" ? void 0 : this.resolveParentResource(user, resourceType);
874
+ if (parentResource) {
875
+ const accessibleIds = await this.listAccessibleResourceExternalIds({
876
+ organizationMembershipId: membershipId,
877
+ permissionSlug,
878
+ parentResourceExternalId: parentResource.externalId,
879
+ parentResourceTypeSlug: parentResource.typeSlug
880
+ });
881
+ return resources.filter((resource) => {
882
+ const mappedId = this.resolveResourceId(
883
+ user,
884
+ resourceType,
885
+ resource.id,
886
+ "resourceId" in resource && typeof resource.resourceId === "string" ? { resourceId: resource.resourceId } : void 0
887
+ );
888
+ return !!mappedId && accessibleIds.has(mappedId);
889
+ });
890
+ }
891
+ const checks = [];
892
+ for (let start = 0; start < resources.length; start += FILTER_ACCESSIBLE_CHECK_CONCURRENCY) {
893
+ const batch = resources.slice(start, start + FILTER_ACCESSIBLE_CHECK_CONCURRENCY);
894
+ const batchChecks = await Promise.all(
895
+ batch.map(async (resource) => {
896
+ const authorized = await this.check(user, {
897
+ resource: { type: resourceType, id: resource.id },
898
+ permission,
899
+ context: "resourceId" in resource && typeof resource.resourceId === "string" ? { resourceId: resource.resourceId } : void 0
900
+ });
901
+ return { resource, authorized };
902
+ })
903
+ );
904
+ checks.push(...batchChecks);
905
+ }
906
+ return checks.filter((c) => c.authorized).map((c) => c.resource);
907
+ }
908
+ // ──────────────────────────────────────────────────────────────
909
+ // IFGAManager — Write operations
910
+ // ──────────────────────────────────────────────────────────────
911
+ /**
912
+ * Create an authorization resource in WorkOS.
913
+ */
914
+ async createResource(params) {
915
+ const options = {
916
+ externalId: params.externalId,
917
+ name: params.name,
918
+ resourceTypeSlug: params.resourceTypeSlug,
919
+ organizationId: params.organizationId
920
+ };
921
+ if (params.description !== void 0) options.description = params.description;
922
+ if (params.parentResourceId) options.parentResourceId = params.parentResourceId;
923
+ if (params.parentResourceExternalId) {
924
+ options.parentResourceExternalId = params.parentResourceExternalId;
925
+ options.parentResourceTypeSlug = params.parentResourceTypeSlug;
926
+ }
927
+ const result = await this.workos.authorization.createResource(options);
928
+ return this.mapAuthorizationResource(result);
929
+ }
930
+ /**
931
+ * Get an authorization resource by ID.
932
+ */
933
+ async getResource(resourceId) {
934
+ const result = await this.workos.authorization.getResource(resourceId);
935
+ return this.mapAuthorizationResource(result);
936
+ }
937
+ /**
938
+ * List authorization resources with optional filters.
939
+ */
940
+ async listResources(options) {
941
+ const listOptions = {};
942
+ if (options?.organizationId) listOptions.organizationId = options.organizationId;
943
+ if (options?.resourceTypeSlug) listOptions.resourceTypeSlug = options.resourceTypeSlug;
944
+ if (options?.parentResourceId) listOptions.parentResourceId = options.parentResourceId;
945
+ if (options?.search) listOptions.search = options.search;
946
+ if (options?.limit) listOptions.limit = options.limit;
947
+ if (options?.after) listOptions.after = options.after;
948
+ const result = await this.workos.authorization.listResources(listOptions);
949
+ return result.data.map((r) => this.mapAuthorizationResource(r));
950
+ }
951
+ /**
952
+ * Update an authorization resource.
953
+ */
954
+ async updateResource(params) {
955
+ const options = { resourceId: params.resourceId };
956
+ if (params.name !== void 0) options.name = params.name;
957
+ if (params.description !== void 0) options.description = params.description;
958
+ const result = await this.workos.authorization.updateResource(options);
959
+ return this.mapAuthorizationResource(result);
960
+ }
961
+ /**
962
+ * Delete an authorization resource.
963
+ */
964
+ async deleteResource(params) {
965
+ if ("resourceId" in params && params.resourceId) {
966
+ await this.workos.authorization.deleteResource({ resourceId: params.resourceId });
967
+ } else if ("externalId" in params && params.externalId && params.resourceTypeSlug) {
968
+ await this.workos.authorization.deleteResourceByExternalId({
969
+ externalId: params.externalId,
970
+ resourceTypeSlug: params.resourceTypeSlug,
971
+ organizationId: params.organizationId
972
+ });
973
+ }
974
+ }
975
+ /**
976
+ * Assign a role to an organization membership on a resource.
977
+ */
978
+ async assignRole(params) {
979
+ const options = {
980
+ organizationMembershipId: params.organizationMembershipId,
981
+ roleSlug: params.roleSlug
982
+ };
983
+ if (params.resourceId) options.resourceId = params.resourceId;
984
+ if (params.resourceExternalId) {
985
+ options.resourceExternalId = params.resourceExternalId;
986
+ options.resourceTypeSlug = params.resourceTypeSlug;
987
+ }
988
+ const result = await this.workos.authorization.assignRole(options);
989
+ return {
990
+ id: result.id,
991
+ role: result.role,
992
+ resource: {
993
+ id: result.resource.id,
994
+ externalId: result.resource.externalId,
995
+ resourceTypeSlug: result.resource.resourceTypeSlug
996
+ }
997
+ };
998
+ }
999
+ /**
1000
+ * Remove a role assignment.
1001
+ */
1002
+ async removeRole(params) {
1003
+ const options = {
1004
+ organizationMembershipId: params.organizationMembershipId,
1005
+ roleSlug: params.roleSlug
1006
+ };
1007
+ if (params.resourceId) options.resourceId = params.resourceId;
1008
+ if (params.resourceExternalId) {
1009
+ options.resourceExternalId = params.resourceExternalId;
1010
+ options.resourceTypeSlug = params.resourceTypeSlug;
1011
+ }
1012
+ await this.workos.authorization.removeRole(options);
1013
+ }
1014
+ /**
1015
+ * List role assignments for an organization membership.
1016
+ */
1017
+ async listRoleAssignments(options) {
1018
+ const result = await this.workos.authorization.listRoleAssignments({
1019
+ organizationMembershipId: options.organizationMembershipId,
1020
+ ...options.limit && { limit: options.limit },
1021
+ ...options.after && { after: options.after }
1022
+ });
1023
+ return result.data.map((ra) => ({
1024
+ id: ra.id,
1025
+ role: ra.role,
1026
+ resource: {
1027
+ id: ra.resource.id,
1028
+ externalId: ra.resource.externalId,
1029
+ resourceTypeSlug: ra.resource.resourceTypeSlug
1030
+ }
1031
+ }));
1032
+ }
1033
+ // ──────────────────────────────────────────────────────────────
1034
+ // Internal helpers
1035
+ // ──────────────────────────────────────────────────────────────
1036
+ /**
1037
+ * Resolve the organization membership ID from a user object.
1038
+ * Looks for organizationMembershipId, then finds membership matching
1039
+ * configured organizationId, then falls back to first membership.
1040
+ *
1041
+ * Returns undefined if no membership can be resolved, which causes
1042
+ * authorization checks to deny access. Enable `fetchMemberships: true`
1043
+ * on MastraAuthWorkos to populate the memberships field.
1044
+ */
1045
+ resolveOrganizationMembershipId(user, options) {
1046
+ if (user?.organizationMembershipId) return user.organizationMembershipId;
1047
+ if (!user?.memberships?.length) {
1048
+ console.warn(
1049
+ "[MastraFGAWorkos] Cannot resolve organization membership for user <redacted>. Ensure fetchMemberships is enabled on MastraAuthWorkos when using FGA."
1050
+ );
1051
+ if (options?.strictMembershipResolution) {
1052
+ throw new WorkOSFGAMembershipResolutionError(user);
1053
+ }
1054
+ return void 0;
1055
+ }
1056
+ if (this.organizationId) {
1057
+ const match = user.memberships.find((m) => m.organizationId === this.organizationId);
1058
+ if (match) return match.id;
1059
+ console.warn("[MastraFGAWorkos] User <redacted> does not belong to configured organization <redacted>.");
1060
+ if (options?.strictMembershipResolution) {
1061
+ throw new WorkOSFGAMembershipResolutionError(user);
1062
+ }
1063
+ return void 0;
1064
+ }
1065
+ return user.memberships[0].id;
1066
+ }
1067
+ /**
1068
+ * Map a Mastra permission string to a WorkOS permission slug via permissionMapping.
1069
+ * Falls back to the original permission if no mapping is found.
1070
+ */
1071
+ resolvePermission(permission) {
1072
+ return this.permissionMapping[permission] ?? permission;
1073
+ }
1074
+ /**
1075
+ * Resolve the parent resource context needed for WorkOS resource discovery.
1076
+ */
1077
+ resolveParentResource(user, resourceType) {
1078
+ const mapping = this.getResourceMapping(resourceType);
1079
+ const externalId = mapping?.deriveId?.({ user });
1080
+ const parentTypeSlug = mapping?.parentFgaResourceType ?? mapping?.parentResourceTypeSlug;
1081
+ if (!mapping?.fgaResourceType || !externalId || !parentTypeSlug || parentTypeSlug === mapping.fgaResourceType) {
1082
+ return void 0;
1083
+ }
1084
+ return {
1085
+ externalId,
1086
+ typeSlug: parentTypeSlug
1087
+ };
1088
+ }
1089
+ /**
1090
+ * Resolve the FGA resource ID using resourceMapping's deriveId function.
1091
+ * Falls back to the original resource ID if no mapping is found.
1092
+ */
1093
+ resolveResourceId(user, resourceType, resourceId, context) {
1094
+ const mapping = this.getResourceMapping(resourceType);
1095
+ const derivedId = mapping?.deriveId?.({
1096
+ user,
1097
+ resourceId: context?.resourceId ?? resourceId,
1098
+ requestContext: context?.requestContext
1099
+ });
1100
+ return derivedId ?? resourceId;
1101
+ }
1102
+ buildCheckOptions(user, params, options) {
1103
+ const membershipId = this.resolveOrganizationMembershipId(user, options);
1104
+ if (!membershipId) return null;
1105
+ const permissionSlug = this.resolvePermission(params.permission);
1106
+ const resourceId = this.resolveResourceId(user, params.resource.type, params.resource.id, params.context);
1107
+ const checkOptions = {
1108
+ organizationMembershipId: membershipId,
1109
+ permissionSlug
1110
+ };
1111
+ if (!resourceId) {
1112
+ return checkOptions;
1113
+ }
1114
+ const mapping = this.getResourceMapping(params.resource.type);
1115
+ if (mapping) {
1116
+ checkOptions.resourceExternalId = resourceId;
1117
+ checkOptions.resourceTypeSlug = mapping.fgaResourceType;
1118
+ } else {
1119
+ checkOptions.resourceExternalId = params.resource.id;
1120
+ checkOptions.resourceTypeSlug = params.resource.type;
1121
+ }
1122
+ return checkOptions;
1123
+ }
1124
+ getResourceMapping(resourceType) {
1125
+ const aliases = resourceType === "agent" ? ["agent", "agents"] : resourceType === "workflow" ? ["workflow", "workflows"] : resourceType === "tool" ? ["tool", "tools"] : resourceType === "thread" ? ["thread", "threads", "memory"] : [resourceType];
1126
+ for (const key of aliases) {
1127
+ const mapping = this.resourceMapping[key];
1128
+ if (mapping) {
1129
+ return mapping;
1130
+ }
1131
+ }
1132
+ return void 0;
1133
+ }
1134
+ /**
1135
+ * List accessible child resources for a membership, following pagination.
1136
+ */
1137
+ async listAccessibleResourceExternalIds(params) {
1138
+ const accessibleIds = /* @__PURE__ */ new Set();
1139
+ let after;
1140
+ do {
1141
+ const result = await this.workos.authorization.listResourcesForMembership({
1142
+ organizationMembershipId: params.organizationMembershipId,
1143
+ permissionSlug: params.permissionSlug,
1144
+ parentResourceExternalId: params.parentResourceExternalId,
1145
+ parentResourceTypeSlug: params.parentResourceTypeSlug,
1146
+ ...after ? { after } : {},
1147
+ limit: 100,
1148
+ order: "asc"
1149
+ });
1150
+ for (const resource of result.data ?? []) {
1151
+ if (typeof resource?.externalId === "string") {
1152
+ accessibleIds.add(resource.externalId);
1153
+ }
1154
+ }
1155
+ after = result.listMetadata?.after ?? void 0;
1156
+ } while (after);
1157
+ return accessibleIds;
1158
+ }
1159
+ /**
1160
+ * Map a WorkOS AuthorizationResource to Mastra's FGAResource type.
1161
+ */
1162
+ mapAuthorizationResource(resource) {
1163
+ return {
1164
+ id: resource.id,
1165
+ externalId: resource.externalId,
1166
+ name: resource.name,
1167
+ description: resource.description,
1168
+ resourceTypeSlug: resource.resourceTypeSlug,
1169
+ organizationId: resource.organizationId,
1170
+ parentResourceId: resource.parentResourceId
1171
+ };
1172
+ }
1173
+ };
609
1174
 
610
1175
  // src/directory-sync.ts
611
1176
  var WorkOSDirectorySync = class {
@@ -872,12 +1437,23 @@ var WorkOSAdminPortal = class {
872
1437
  return result.link;
873
1438
  }
874
1439
  };
1440
+ /**
1441
+ * WorkOS FGA provider for Mastra.
1442
+ *
1443
+ * Integrates WorkOS Authorization API with Mastra's FGA interface
1444
+ * for permission-based, resource-level authorization.
1445
+ *
1446
+ * @license Mastra Enterprise License - see ee/LICENSE
1447
+ */
875
1448
 
876
1449
  exports.MastraAuthWorkos = MastraAuthWorkos;
1450
+ exports.MastraFGAWorkos = MastraFGAWorkos;
877
1451
  exports.MastraRBACWorkos = MastraRBACWorkos;
878
1452
  exports.WebSessionStorage = WebSessionStorage;
879
1453
  exports.WorkOSAdminPortal = WorkOSAdminPortal;
880
1454
  exports.WorkOSDirectorySync = WorkOSDirectorySync;
1455
+ exports.WorkOSFGAMembershipResolutionError = WorkOSFGAMembershipResolutionError;
1456
+ exports.WorkOSFGAResourceNotFoundError = WorkOSFGAResourceNotFoundError;
881
1457
  exports.mapWorkOSUserToEEUser = mapWorkOSUserToEEUser;
882
1458
  //# sourceMappingURL=index.cjs.map
883
1459
  //# sourceMappingURL=index.cjs.map