@stonecrop/casl-middleware 0.13.3 → 0.13.5
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/casl-middleware.d.ts +2 -2
- package/dist/casl-middleware.js +46 -49
- package/dist/casl-middleware.js.map +1 -1
- package/dist/src/middleware/graphql.js +1 -1
- package/dist/src/middleware/jwt.d.ts +4 -4
- package/dist/src/middleware/jwt.d.ts.map +1 -1
- package/dist/src/middleware/jwt.js +17 -6
- package/dist/src/middleware/postgraphile.d.ts.map +1 -1
- package/dist/src/middleware/postgraphile.js +2 -3
- package/dist/src/middleware/yoga.d.ts +1 -1
- package/dist/src/middleware/yoga.d.ts.map +1 -1
- package/dist/src/middleware/yoga.js +4 -6
- package/dist/src/types/index.d.ts +2 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/package.json +5 -7
- package/src/middleware/graphql.ts +1 -1
- package/src/middleware/jwt.ts +23 -12
- package/src/middleware/postgraphile.ts +17 -21
- package/src/middleware/yoga.ts +4 -7
- package/src/types/index.ts +2 -2
|
@@ -75,7 +75,7 @@ export const createCaslMiddleware = (options = {}) => {
|
|
|
75
75
|
const subjectType = info.parentType.name;
|
|
76
76
|
const subjectName = subjectMap[subjectType] || subjectType;
|
|
77
77
|
if (debug) {
|
|
78
|
-
console.
|
|
78
|
+
console.debug(`Checking permission: ${action} on ${subjectName} at ${fieldPath}`);
|
|
79
79
|
}
|
|
80
80
|
// Check field-level permissions if defined
|
|
81
81
|
if (fieldPermissions[fieldPath]) {
|
|
@@ -57,7 +57,7 @@ export declare const createJWTMiddleware: (config?: JWTConfig) => (context: Cont
|
|
|
57
57
|
*/
|
|
58
58
|
export declare const createJWT: (user: User, config: {
|
|
59
59
|
secret: string;
|
|
60
|
-
expiresIn?:
|
|
60
|
+
expiresIn?: jwt.SignOptions["expiresIn"];
|
|
61
61
|
issuer?: string;
|
|
62
62
|
audience?: string;
|
|
63
63
|
additionalClaims?: Record<string, any>;
|
|
@@ -92,8 +92,8 @@ export declare const refreshTokenUtils: {
|
|
|
92
92
|
createTokenPair: (user: User, config: {
|
|
93
93
|
accessSecret: string;
|
|
94
94
|
refreshSecret: string;
|
|
95
|
-
accessExpiresIn?:
|
|
96
|
-
refreshExpiresIn?:
|
|
95
|
+
accessExpiresIn?: jwt.SignOptions["expiresIn"];
|
|
96
|
+
refreshExpiresIn?: jwt.SignOptions["expiresIn"];
|
|
97
97
|
}) => {
|
|
98
98
|
accessToken: string;
|
|
99
99
|
refreshToken: string;
|
|
@@ -105,7 +105,7 @@ export declare const refreshTokenUtils: {
|
|
|
105
105
|
accessSecret: string;
|
|
106
106
|
refreshSecret: string;
|
|
107
107
|
getUserById: (id: string) => Promise<User | null>;
|
|
108
|
-
accessExpiresIn?:
|
|
108
|
+
accessExpiresIn?: jwt.SignOptions["expiresIn"];
|
|
109
109
|
}) => Promise<{
|
|
110
110
|
accessToken: string;
|
|
111
111
|
user: User;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../../src/middleware/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAA;AAE9B,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAG7C,MAAM,WAAW,SAAS;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,GAAG,CAAC,SAAS,EAAE,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,SAAS,CAAA;IAChD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,UAAW,SAAQ,GAAG,CAAC,UAAU;IACjD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,CAAC,EAAE,KAAK,CAAC;QACnB,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;QACf,UAAU,CAAC,EAAE,GAAG,CAAA;KAChB,CAAC,CAAA;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAClB;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAQ,SAAc,MAoB3C,SAAS,OAAO,EAAE,MAAM,MAAM,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../../src/middleware/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAA;AAE9B,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAG7C,MAAM,WAAW,SAAS;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,GAAG,CAAC,SAAS,EAAE,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,SAAS,CAAA;IAChD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,UAAW,SAAQ,GAAG,CAAC,UAAU;IACjD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,CAAC,EAAE,KAAK,CAAC;QACnB,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;QACf,UAAU,CAAC,EAAE,GAAG,CAAA;KAChB,CAAC,CAAA;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAClB;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAQ,SAAc,MAoB3C,SAAS,OAAO,EAAE,MAAM,MAAM,OAAO,CAAC,GAAG,CAAC,iBAwExD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,GACrB,MAAM,IAAI,EACV,QAAQ;IACP,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;IACxC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACtC,KACC,MAiBF,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAAI,UAAS,SAAc,MAChD,OAAO,IAAI,4CAsBzB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,QAAQ,SAAS;;;;;yBAQtC,GAAG,UAAU,GAAG;;;CAwBtC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,SAAS,MAI1C,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,GAAG,kBA4B3C,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB;IAC7B;;OAEG;4BAEI,IAAI,UACF;QACP,YAAY,EAAE,MAAM,CAAA;QACpB,aAAa,EAAE,MAAM,CAAA;QACrB,eAAe,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;QAC9C,gBAAgB,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;KAC/C;;;;IAkCF;;OAEG;uCAEY,MAAM,UACZ;QACP,YAAY,EAAE,MAAM,CAAA;QACpB,aAAa,EAAE,MAAM,CAAA;QACrB,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;QACjD,eAAe,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;KAC9C;;;;CAgDF,CAAA"}
|
|
@@ -74,7 +74,11 @@ export const createJWTMiddleware = (config = {}) => {
|
|
|
74
74
|
};
|
|
75
75
|
// Verify and decode token
|
|
76
76
|
const secretOrPublicKey = publicKey || secret;
|
|
77
|
-
const
|
|
77
|
+
const decoded = jwt.verify(token, secretOrPublicKey, verifyOptions);
|
|
78
|
+
if (typeof decoded === 'string' || decoded == null) {
|
|
79
|
+
throw new Error('Invalid JWT payload: expected object');
|
|
80
|
+
}
|
|
81
|
+
const payload = decoded;
|
|
78
82
|
// Extract user from payload
|
|
79
83
|
const user = extractUser(payload);
|
|
80
84
|
if (user) {
|
|
@@ -125,7 +129,7 @@ export const createJWT = (user, config) => {
|
|
|
125
129
|
if (audience !== undefined)
|
|
126
130
|
signOptions.audience = audience;
|
|
127
131
|
if (expiresIn !== undefined)
|
|
128
|
-
signOptions.expiresIn = expiresIn;
|
|
132
|
+
signOptions.expiresIn = expiresIn;
|
|
129
133
|
return jwt.sign(payload, secret, signOptions);
|
|
130
134
|
};
|
|
131
135
|
/**
|
|
@@ -240,13 +244,13 @@ export const refreshTokenUtils = {
|
|
|
240
244
|
// Create access token with proper options
|
|
241
245
|
const accessOptions = {};
|
|
242
246
|
if (accessExpiresIn) {
|
|
243
|
-
accessOptions.expiresIn = accessExpiresIn;
|
|
247
|
+
accessOptions.expiresIn = accessExpiresIn;
|
|
244
248
|
}
|
|
245
249
|
const accessToken = jwt.sign(accessPayload, accessSecret, accessOptions);
|
|
246
250
|
// Create refresh token with proper options
|
|
247
251
|
const refreshOptions = {};
|
|
248
252
|
if (refreshExpiresIn) {
|
|
249
|
-
refreshOptions.expiresIn = refreshExpiresIn;
|
|
253
|
+
refreshOptions.expiresIn = refreshExpiresIn;
|
|
250
254
|
}
|
|
251
255
|
const refreshToken = jwt.sign(refreshPayload, refreshSecret, refreshOptions);
|
|
252
256
|
return { accessToken, refreshToken };
|
|
@@ -258,11 +262,18 @@ export const refreshTokenUtils = {
|
|
|
258
262
|
const { accessSecret, refreshSecret, getUserById, accessExpiresIn = '15m' } = config;
|
|
259
263
|
try {
|
|
260
264
|
// Verify refresh token
|
|
261
|
-
const
|
|
265
|
+
const decoded = jwt.verify(refreshToken, refreshSecret);
|
|
266
|
+
if (typeof decoded === 'string' || decoded == null) {
|
|
267
|
+
throw new Error('Invalid refresh token payload: expected object');
|
|
268
|
+
}
|
|
269
|
+
const payload = decoded;
|
|
262
270
|
if (payload.type !== 'refresh') {
|
|
263
271
|
throw new Error('Invalid token type');
|
|
264
272
|
}
|
|
265
273
|
// Get fresh user data
|
|
274
|
+
if (!payload.sub) {
|
|
275
|
+
throw new Error('Invalid refresh token payload: missing subject');
|
|
276
|
+
}
|
|
266
277
|
const user = await getUserById(payload.sub);
|
|
267
278
|
if (!user) {
|
|
268
279
|
throw new Error('User not found');
|
|
@@ -276,7 +287,7 @@ export const refreshTokenUtils = {
|
|
|
276
287
|
// Create access token with proper options
|
|
277
288
|
const accessOptions = {};
|
|
278
289
|
if (accessExpiresIn) {
|
|
279
|
-
accessOptions.expiresIn = accessExpiresIn;
|
|
290
|
+
accessOptions.expiresIn = accessExpiresIn;
|
|
280
291
|
}
|
|
281
292
|
const accessToken = jwt.sign(accessPayload, accessSecret, accessOptions);
|
|
282
293
|
return { accessToken, user };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgraphile.d.ts","sourceRoot":"","sources":["../../../src/middleware/postgraphile.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAI5D;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"postgraphile.d.ts","sourceRoot":"","sources":["../../../src/middleware/postgraphile.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAI5D;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,cAAc,CAAC,MA8EzC,CAAA"}
|
|
@@ -51,9 +51,8 @@ export const pglCaslPlugin = extendSchema(build => {
|
|
|
51
51
|
Mutation: {
|
|
52
52
|
plans: {
|
|
53
53
|
async createAbility(_plan, fieldArgs) {
|
|
54
|
-
const $
|
|
55
|
-
const $
|
|
56
|
-
const $roles = $input.roles;
|
|
54
|
+
const $userId = fieldArgs.getRaw().input.userId;
|
|
55
|
+
const $roles = fieldArgs.getRaw().input.roles;
|
|
57
56
|
return sideEffect([$userId, $roles], async ([userId, roles]) => {
|
|
58
57
|
// Make this async
|
|
59
58
|
try {
|
|
@@ -9,7 +9,7 @@ export declare const yogaCaslPlugin: Plugin<Context>;
|
|
|
9
9
|
* @returns Yoga plugin
|
|
10
10
|
* @public
|
|
11
11
|
*/
|
|
12
|
-
export declare const createYogaPlugin: (
|
|
12
|
+
export declare const createYogaPlugin: (_options?: MiddlewareOptions) => {
|
|
13
13
|
onExecute: (_: any) => Promise<void>;
|
|
14
14
|
};
|
|
15
15
|
//# sourceMappingURL=yoga.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yoga.d.ts","sourceRoot":"","sources":["../../../src/middleware/yoga.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"yoga.d.ts","sourceRoot":"","sources":["../../../src/middleware/yoga.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAG1C,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAO1D,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,OAAO,CAO1C,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAU,iBAAsB;mBAE1C,GAAG;CAMzB,CAAA"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createAbility } from './ability';
|
|
2
|
-
import { createCaslMiddleware } from './graphql';
|
|
3
2
|
const getLoggedInUser = () => {
|
|
4
3
|
// Mock user for demonstration purposes
|
|
5
4
|
return { id: '1', roles: ['editor'] };
|
|
@@ -20,13 +19,12 @@ export const yogaCaslPlugin = {
|
|
|
20
19
|
* @returns Yoga plugin
|
|
21
20
|
* @public
|
|
22
21
|
*/
|
|
23
|
-
export const createYogaPlugin = (
|
|
24
|
-
const _middleware = createCaslMiddleware(options);
|
|
22
|
+
export const createYogaPlugin = (_options = {}) => {
|
|
25
23
|
return {
|
|
26
24
|
onExecute: async (_) => {
|
|
27
|
-
// TODO: Implement Yoga plugin
|
|
28
|
-
// This would integrate with GraphQL Yoga's plugin system
|
|
29
|
-
console.
|
|
25
|
+
// TODO: Implement Yoga plugin via createCaslMiddleware(_options).
|
|
26
|
+
// This would integrate with GraphQL Yoga's plugin system.
|
|
27
|
+
console.warn('Yoga plugin not yet implemented');
|
|
30
28
|
},
|
|
31
29
|
};
|
|
32
30
|
};
|
|
@@ -58,12 +58,12 @@ export interface FieldPermission {
|
|
|
58
58
|
* GraphQL resolver function type
|
|
59
59
|
* @public
|
|
60
60
|
*/
|
|
61
|
-
export type ResolverFn = (root: any, args: any, context: Context, info: GraphQLResolveInfo) =>
|
|
61
|
+
export type ResolverFn = (root: any, args: any, context: Context, info: GraphQLResolveInfo) => any;
|
|
62
62
|
/**
|
|
63
63
|
* Middleware function that wraps a GraphQL resolver with authorization logic
|
|
64
64
|
* @public
|
|
65
65
|
*/
|
|
66
|
-
export type MiddlewareFn = (resolve: ResolverFn, root: any, args: any, context: Context, info: GraphQLResolveInfo) =>
|
|
66
|
+
export type MiddlewareFn = (resolve: ResolverFn, root: any, args: any, context: Context, info: GraphQLResolveInfo) => any;
|
|
67
67
|
/**
|
|
68
68
|
* Plugin configuration options for framework integrations
|
|
69
69
|
* @public
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAE5C,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAE/E;;;GAGG;AACH,MAAM,WAAW,IAAI;IACpB,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,iCAAiC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACvB,qDAAqD;IACrD,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB,iCAAiC;IACjC,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,oCAAoC;IACpC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAClB;AAID;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IACjC,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAA;IACpD,8CAA8C;IAC9C,cAAc,CAAC,EAAE,sBAAsB,CAAA;IACvC,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAA;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAA;IACf,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,uCAAuC;IACvC,UAAU,CAAC,EAAE,GAAG,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,kBAAkB,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonecrop/casl-middleware",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": {
|
|
@@ -42,14 +42,11 @@
|
|
|
42
42
|
"@rushstack/heft": "^1.2.17",
|
|
43
43
|
"@types/node": "^24.12.4",
|
|
44
44
|
"@vitest/coverage-istanbul": "^4.1.5",
|
|
45
|
-
"@eslint/js": "^10.0.1",
|
|
46
|
-
"eslint": "^10.3.0",
|
|
47
|
-
"eslint-config-prettier": "^10.1.8",
|
|
48
|
-
"globals": "^17.6.0",
|
|
49
|
-
"typescript-eslint": "^8.59.1",
|
|
50
45
|
"graphql-middleware": "^6.1.35",
|
|
51
46
|
"graphql-yoga": "^5.18.0",
|
|
52
47
|
"jsdom": "^29.1.1",
|
|
48
|
+
"oxlint": "1.67.0",
|
|
49
|
+
"oxlint-tsgolint": "0.23.0",
|
|
53
50
|
"postgraphile": "^5.0.3",
|
|
54
51
|
"typescript": "^6.0.3",
|
|
55
52
|
"vite": "^7.3.2",
|
|
@@ -80,7 +77,8 @@
|
|
|
80
77
|
"build": "heft build && vite build && rushx docs",
|
|
81
78
|
"dev": "vite",
|
|
82
79
|
"docs": "bash ../common/scripts/run-docs.sh casl_middleware",
|
|
83
|
-
"lint": "
|
|
80
|
+
"lint": "oxlint",
|
|
81
|
+
"lint:fix": "oxlint --fix",
|
|
84
82
|
"preview": "vite preview",
|
|
85
83
|
"test": "vitest run --coverage.enabled false",
|
|
86
84
|
"test:watch": "vitest watch",
|
|
@@ -98,7 +98,7 @@ export const createCaslMiddleware = (options: MiddlewareOptions = {}) => {
|
|
|
98
98
|
const subjectName = subjectMap[subjectType] || subjectType
|
|
99
99
|
|
|
100
100
|
if (debug) {
|
|
101
|
-
console.
|
|
101
|
+
console.debug(`Checking permission: ${action} on ${subjectName} at ${fieldPath}`)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// Check field-level permissions if defined
|
package/src/middleware/jwt.ts
CHANGED
|
@@ -122,7 +122,11 @@ export const createJWTMiddleware = (config: JWTConfig = {}) => {
|
|
|
122
122
|
|
|
123
123
|
// Verify and decode token
|
|
124
124
|
const secretOrPublicKey = publicKey || secret!
|
|
125
|
-
const
|
|
125
|
+
const decoded = jwt.verify(token, secretOrPublicKey, verifyOptions)
|
|
126
|
+
if (typeof decoded === 'string' || decoded == null) {
|
|
127
|
+
throw new Error('Invalid JWT payload: expected object')
|
|
128
|
+
}
|
|
129
|
+
const payload: JWTPayload = decoded
|
|
126
130
|
|
|
127
131
|
// Extract user from payload
|
|
128
132
|
const user = extractUser(payload)
|
|
@@ -166,7 +170,7 @@ export const createJWT = (
|
|
|
166
170
|
user: User,
|
|
167
171
|
config: {
|
|
168
172
|
secret: string
|
|
169
|
-
expiresIn?:
|
|
173
|
+
expiresIn?: jwt.SignOptions['expiresIn']
|
|
170
174
|
issuer?: string
|
|
171
175
|
audience?: string
|
|
172
176
|
additionalClaims?: Record<string, any>
|
|
@@ -185,7 +189,7 @@ export const createJWT = (
|
|
|
185
189
|
// Add optional fields only if they exist
|
|
186
190
|
if (issuer !== undefined) signOptions.issuer = issuer
|
|
187
191
|
if (audience !== undefined) signOptions.audience = audience
|
|
188
|
-
if (expiresIn !== undefined) signOptions.expiresIn = expiresIn
|
|
192
|
+
if (expiresIn !== undefined) signOptions.expiresIn = expiresIn
|
|
189
193
|
|
|
190
194
|
return jwt.sign(payload, secret, signOptions)
|
|
191
195
|
}
|
|
@@ -196,11 +200,11 @@ export const createJWT = (
|
|
|
196
200
|
export const createJWTAbilityBuilder = (_config: JWTConfig = {}) => {
|
|
197
201
|
return async (user?: User) => {
|
|
198
202
|
// If user has direct permissions in JWT, use those
|
|
199
|
-
const jwtPermissions =
|
|
203
|
+
const jwtPermissions: unknown = user?.permissions
|
|
200
204
|
|
|
201
205
|
if (jwtPermissions && Array.isArray(jwtPermissions)) {
|
|
202
206
|
// Build ability from JWT permissions
|
|
203
|
-
const { can, cannot, build } = new AbilityBuilder<PureAbility>(PureAbility
|
|
207
|
+
const { can, cannot, build } = new AbilityBuilder<PureAbility>(PureAbility)
|
|
204
208
|
|
|
205
209
|
jwtPermissions.forEach((permission: any) => {
|
|
206
210
|
if (permission.inverted) {
|
|
@@ -304,8 +308,8 @@ export const refreshTokenUtils = {
|
|
|
304
308
|
config: {
|
|
305
309
|
accessSecret: string
|
|
306
310
|
refreshSecret: string
|
|
307
|
-
accessExpiresIn?:
|
|
308
|
-
refreshExpiresIn?:
|
|
311
|
+
accessExpiresIn?: jwt.SignOptions['expiresIn']
|
|
312
|
+
refreshExpiresIn?: jwt.SignOptions['expiresIn']
|
|
309
313
|
}
|
|
310
314
|
) => {
|
|
311
315
|
const { accessSecret, refreshSecret, accessExpiresIn = '15m', refreshExpiresIn = '7d' } = config
|
|
@@ -324,7 +328,7 @@ export const refreshTokenUtils = {
|
|
|
324
328
|
// Create access token with proper options
|
|
325
329
|
const accessOptions: jwt.SignOptions = {}
|
|
326
330
|
if (accessExpiresIn) {
|
|
327
|
-
accessOptions.expiresIn = accessExpiresIn
|
|
331
|
+
accessOptions.expiresIn = accessExpiresIn
|
|
328
332
|
}
|
|
329
333
|
|
|
330
334
|
const accessToken = jwt.sign(accessPayload, accessSecret, accessOptions)
|
|
@@ -332,7 +336,7 @@ export const refreshTokenUtils = {
|
|
|
332
336
|
// Create refresh token with proper options
|
|
333
337
|
const refreshOptions: jwt.SignOptions = {}
|
|
334
338
|
if (refreshExpiresIn) {
|
|
335
|
-
refreshOptions.expiresIn = refreshExpiresIn
|
|
339
|
+
refreshOptions.expiresIn = refreshExpiresIn
|
|
336
340
|
}
|
|
337
341
|
|
|
338
342
|
const refreshToken = jwt.sign(refreshPayload, refreshSecret, refreshOptions)
|
|
@@ -349,20 +353,27 @@ export const refreshTokenUtils = {
|
|
|
349
353
|
accessSecret: string
|
|
350
354
|
refreshSecret: string
|
|
351
355
|
getUserById: (id: string) => Promise<User | null>
|
|
352
|
-
accessExpiresIn?:
|
|
356
|
+
accessExpiresIn?: jwt.SignOptions['expiresIn']
|
|
353
357
|
}
|
|
354
358
|
) => {
|
|
355
359
|
const { accessSecret, refreshSecret, getUserById, accessExpiresIn = '15m' } = config
|
|
356
360
|
|
|
357
361
|
try {
|
|
358
362
|
// Verify refresh token
|
|
359
|
-
const
|
|
363
|
+
const decoded = jwt.verify(refreshToken, refreshSecret)
|
|
364
|
+
if (typeof decoded === 'string' || decoded == null) {
|
|
365
|
+
throw new Error('Invalid refresh token payload: expected object')
|
|
366
|
+
}
|
|
367
|
+
const payload = decoded as jwt.JwtPayload & { type?: string; sub?: string }
|
|
360
368
|
|
|
361
369
|
if (payload.type !== 'refresh') {
|
|
362
370
|
throw new Error('Invalid token type')
|
|
363
371
|
}
|
|
364
372
|
|
|
365
373
|
// Get fresh user data
|
|
374
|
+
if (!payload.sub) {
|
|
375
|
+
throw new Error('Invalid refresh token payload: missing subject')
|
|
376
|
+
}
|
|
366
377
|
const user = await getUserById(payload.sub)
|
|
367
378
|
if (!user) {
|
|
368
379
|
throw new Error('User not found')
|
|
@@ -378,7 +389,7 @@ export const refreshTokenUtils = {
|
|
|
378
389
|
// Create access token with proper options
|
|
379
390
|
const accessOptions: jwt.SignOptions = {}
|
|
380
391
|
if (accessExpiresIn) {
|
|
381
|
-
accessOptions.expiresIn = accessExpiresIn
|
|
392
|
+
accessOptions.expiresIn = accessExpiresIn
|
|
382
393
|
}
|
|
383
394
|
|
|
384
395
|
const accessToken = jwt.sign(accessPayload, accessSecret, accessOptions)
|
|
@@ -61,30 +61,26 @@ export const pglCaslPlugin: GraphileConfig.Plugin = extendSchema(build => {
|
|
|
61
61
|
Mutation: {
|
|
62
62
|
plans: {
|
|
63
63
|
async createAbility(_plan: any, fieldArgs: any) {
|
|
64
|
-
const $
|
|
65
|
-
const $
|
|
66
|
-
const $roles = $input.roles
|
|
64
|
+
const $userId: ExecutableStep = fieldArgs.getRaw().input.userId
|
|
65
|
+
const $roles: ExecutableStep = fieldArgs.getRaw().input.roles
|
|
67
66
|
|
|
68
|
-
return sideEffect(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
ability: null,
|
|
83
|
-
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
84
|
-
}
|
|
67
|
+
return sideEffect([$userId, $roles], async ([userId, roles]: readonly [string, string[]]) => {
|
|
68
|
+
// Make this async
|
|
69
|
+
try {
|
|
70
|
+
const ability = await createAbility({ id: userId, roles }) // Await here
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
ability: ability.rules,
|
|
74
|
+
message: 'Ability created successfully',
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
ability: null,
|
|
80
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
|
-
)
|
|
83
|
+
})
|
|
88
84
|
},
|
|
89
85
|
},
|
|
90
86
|
},
|
package/src/middleware/yoga.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from 'graphql-yoga'
|
|
2
2
|
|
|
3
3
|
import { createAbility } from './ability'
|
|
4
|
-
import { createCaslMiddleware } from './graphql'
|
|
5
4
|
import type { Context, MiddlewareOptions } from '../types'
|
|
6
5
|
|
|
7
6
|
const getLoggedInUser = () => {
|
|
@@ -26,14 +25,12 @@ export const yogaCaslPlugin: Plugin<Context> = {
|
|
|
26
25
|
* @returns Yoga plugin
|
|
27
26
|
* @public
|
|
28
27
|
*/
|
|
29
|
-
export const createYogaPlugin = (
|
|
30
|
-
const _middleware = createCaslMiddleware(options)
|
|
31
|
-
|
|
28
|
+
export const createYogaPlugin = (_options: MiddlewareOptions = {}) => {
|
|
32
29
|
return {
|
|
33
30
|
onExecute: async (_: any) => {
|
|
34
|
-
// TODO: Implement Yoga plugin
|
|
35
|
-
// This would integrate with GraphQL Yoga's plugin system
|
|
36
|
-
console.
|
|
31
|
+
// TODO: Implement Yoga plugin via createCaslMiddleware(_options).
|
|
32
|
+
// This would integrate with GraphQL Yoga's plugin system.
|
|
33
|
+
console.warn('Yoga plugin not yet implemented')
|
|
37
34
|
},
|
|
38
35
|
}
|
|
39
36
|
}
|
package/src/types/index.ts
CHANGED
|
@@ -66,7 +66,7 @@ export interface FieldPermission {
|
|
|
66
66
|
* GraphQL resolver function type
|
|
67
67
|
* @public
|
|
68
68
|
*/
|
|
69
|
-
export type ResolverFn = (root: any, args: any, context: Context, info: GraphQLResolveInfo) =>
|
|
69
|
+
export type ResolverFn = (root: any, args: any, context: Context, info: GraphQLResolveInfo) => any
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Middleware function that wraps a GraphQL resolver with authorization logic
|
|
@@ -78,7 +78,7 @@ export type MiddlewareFn = (
|
|
|
78
78
|
args: any,
|
|
79
79
|
context: Context,
|
|
80
80
|
info: GraphQLResolveInfo
|
|
81
|
-
) =>
|
|
81
|
+
) => any
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* Plugin configuration options for framework integrations
|