@mastra/auth-workos 1.1.2-alpha.0 → 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/CHANGELOG.md +50 -0
- package/README.md +70 -6
- package/dist/auth-provider.d.ts +14 -0
- package/dist/auth-provider.d.ts.map +1 -1
- package/dist/fga-provider.d.ts +158 -0
- package/dist/fga-provider.d.ts.map +1 -0
- package/dist/index.cjs +595 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +593 -20
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +135 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -11
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
216
|
+
let memberships;
|
|
217
|
+
if (this.fetchMemberships) {
|
|
172
218
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
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
|