@stonecrop/casl-middleware 0.7.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.
Files changed (66) hide show
  1. package/README.md +149 -0
  2. package/dist/casl-middleware.d.ts +158 -0
  3. package/dist/casl-middleware.js +40571 -0
  4. package/dist/casl-middleware.js.map +1 -0
  5. package/dist/casl_middleware.tsbuildinfo +1 -0
  6. package/dist/src/index.d.ts +6 -0
  7. package/dist/src/index.d.ts.map +1 -0
  8. package/dist/src/index.js +3 -0
  9. package/dist/src/middleware/ability.d.ts +55 -0
  10. package/dist/src/middleware/ability.d.ts.map +1 -0
  11. package/dist/src/middleware/ability.js +139 -0
  12. package/dist/src/middleware/graphql.d.ts +11 -0
  13. package/dist/src/middleware/graphql.d.ts.map +1 -0
  14. package/dist/src/middleware/graphql.js +120 -0
  15. package/dist/src/middleware/introspection.d.ts +71 -0
  16. package/dist/src/middleware/introspection.d.ts.map +1 -0
  17. package/dist/src/middleware/introspection.js +169 -0
  18. package/dist/src/middleware/jwt.d.ts +114 -0
  19. package/dist/src/middleware/jwt.d.ts.map +1 -0
  20. package/dist/src/middleware/jwt.js +291 -0
  21. package/dist/src/middleware/postgraphile.d.ts +7 -0
  22. package/dist/src/middleware/postgraphile.d.ts.map +1 -0
  23. package/dist/src/middleware/postgraphile.js +80 -0
  24. package/dist/src/middleware/yoga.d.ts +15 -0
  25. package/dist/src/middleware/yoga.d.ts.map +1 -0
  26. package/dist/src/middleware/yoga.js +32 -0
  27. package/dist/src/tsdoc-metadata.json +11 -0
  28. package/dist/src/types/index.d.ts +114 -0
  29. package/dist/src/types/index.d.ts.map +1 -0
  30. package/dist/src/types/index.js +0 -0
  31. package/dist/tests/ability.test.d.ts +2 -0
  32. package/dist/tests/ability.test.d.ts.map +1 -0
  33. package/dist/tests/ability.test.js +125 -0
  34. package/dist/tests/helpers/test-utils.d.ts +46 -0
  35. package/dist/tests/helpers/test-utils.d.ts.map +1 -0
  36. package/dist/tests/helpers/test-utils.js +92 -0
  37. package/dist/tests/introspection.test.d.ts +2 -0
  38. package/dist/tests/introspection.test.d.ts.map +1 -0
  39. package/dist/tests/introspection.test.js +368 -0
  40. package/dist/tests/jwt.test.d.ts +2 -0
  41. package/dist/tests/jwt.test.d.ts.map +1 -0
  42. package/dist/tests/jwt.test.js +371 -0
  43. package/dist/tests/middleware.test.d.ts +2 -0
  44. package/dist/tests/middleware.test.d.ts.map +1 -0
  45. package/dist/tests/middleware.test.js +184 -0
  46. package/dist/tests/postgraphile-plugin.test.d.ts +2 -0
  47. package/dist/tests/postgraphile-plugin.test.d.ts.map +1 -0
  48. package/dist/tests/postgraphile-plugin.test.js +56 -0
  49. package/dist/tests/setup.d.ts +2 -0
  50. package/dist/tests/setup.d.ts.map +1 -0
  51. package/dist/tests/setup.js +11 -0
  52. package/dist/tests/user-roles.test.d.ts +2 -0
  53. package/dist/tests/user-roles.test.d.ts.map +1 -0
  54. package/dist/tests/user-roles.test.js +157 -0
  55. package/dist/tests/yoga-plugin.test.d.ts +2 -0
  56. package/dist/tests/yoga-plugin.test.d.ts.map +1 -0
  57. package/dist/tests/yoga-plugin.test.js +47 -0
  58. package/package.json +91 -0
  59. package/src/index.ts +15 -0
  60. package/src/middleware/ability.ts +191 -0
  61. package/src/middleware/graphql.ts +157 -0
  62. package/src/middleware/introspection.ts +258 -0
  63. package/src/middleware/jwt.ts +394 -0
  64. package/src/middleware/postgraphile.ts +93 -0
  65. package/src/middleware/yoga.ts +39 -0
  66. package/src/types/index.ts +133 -0
@@ -0,0 +1,114 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import type { Context, User } from '../types';
3
+ export interface JWTConfig {
4
+ enabled?: boolean;
5
+ secret?: string;
6
+ publicKey?: string;
7
+ algorithms?: jwt.Algorithm[];
8
+ issuer?: string;
9
+ audience?: string;
10
+ extractUser?: (payload: any) => User | undefined;
11
+ headerName?: string;
12
+ tokenPrefix?: string;
13
+ optional?: boolean;
14
+ maxAge?: string;
15
+ }
16
+ export interface JWTPayload extends jwt.JwtPayload {
17
+ sub?: string;
18
+ roles?: string[];
19
+ permissions?: Array<{
20
+ action: string;
21
+ subject: string;
22
+ conditions?: any;
23
+ }>;
24
+ [key: string]: any;
25
+ }
26
+ /**
27
+ * JWT middleware factory for GraphQL servers
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // In Nuxt Yoga
32
+ * export default defineNuxtConfig({
33
+ * yoga: {
34
+ * middleware: [
35
+ * createJWTMiddleware({
36
+ * enabled: true,
37
+ * secret: process.env.JWT_SECRET,
38
+ * optional: true // Don't fail if no token
39
+ * })
40
+ * ]
41
+ * }
42
+ * })
43
+ *
44
+ * // In Postgraphile
45
+ * const jwtPlugin = createPostgraphileJWTPlugin({
46
+ * secret: process.env.JWT_SECRET,
47
+ * extractUser: (payload) => ({
48
+ * id: payload.user_id,
49
+ * roles: payload.user_roles
50
+ * })
51
+ * })
52
+ * ```
53
+ */
54
+ export declare const createJWTMiddleware: (config?: JWTConfig) => (context: Context, next: () => Promise<any>) => Promise<any>;
55
+ /**
56
+ * Create a JWT token with user data
57
+ */
58
+ export declare const createJWT: (user: User, config: {
59
+ secret: string;
60
+ expiresIn?: string | number;
61
+ issuer?: string;
62
+ audience?: string;
63
+ additionalClaims?: Record<string, any>;
64
+ }) => string;
65
+ /**
66
+ * Integration with CASL ability builder
67
+ */
68
+ export declare const createJWTAbilityBuilder: (config?: JWTConfig) => (user?: User) => Promise<import("./ability").AppAbility>;
69
+ /**
70
+ * Postgraphile-specific JWT plugin
71
+ */
72
+ export declare const createPostgraphileJWTPlugin: (config: JWTConfig) => {
73
+ name: string;
74
+ version: string;
75
+ grafast: {
76
+ hooks: {
77
+ context(ctx: any, build: any): Promise<any>;
78
+ };
79
+ };
80
+ };
81
+ /**
82
+ * Express/Koa middleware for REST endpoints
83
+ */
84
+ export declare const createHTTPJWTMiddleware: (config: JWTConfig) => (req: any, res: any, next: any) => Promise<void>;
85
+ /**
86
+ * Refresh token utilities
87
+ */
88
+ export declare const refreshTokenUtils: {
89
+ /**
90
+ * Create access and refresh tokens
91
+ */
92
+ createTokenPair: (user: User, config: {
93
+ accessSecret: string;
94
+ refreshSecret: string;
95
+ accessExpiresIn?: string;
96
+ refreshExpiresIn?: string;
97
+ }) => {
98
+ accessToken: string;
99
+ refreshToken: string;
100
+ };
101
+ /**
102
+ * Verify refresh token and create new access token
103
+ */
104
+ refreshAccessToken: (refreshToken: string, config: {
105
+ accessSecret: string;
106
+ refreshSecret: string;
107
+ getUserById: (id: string) => Promise<User | null>;
108
+ accessExpiresIn?: string;
109
+ }) => Promise<{
110
+ accessToken: string;
111
+ user: User;
112
+ }>;
113
+ };
114
+ //# sourceMappingURL=jwt.d.ts.map
@@ -0,0 +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,iBAoExD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,GACrB,MAAM,IAAI,EACV,QAAQ;IACP,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B,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,SAAQ,SAAc,MAC/C,OAAO,IAAI,4CAsBzB,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,QAAQ,SAAS;;;;;yBAQtC,GAAG,SAAS,GAAG;;;CAwBrC,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,MAAM,CAAA;QACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;KACzB;;;;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,MAAM,CAAA;KACxB;;;;CAyCF,CAAA"}
@@ -0,0 +1,291 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import { AbilityBuilder, PureAbility } from '@casl/ability';
3
+ import { defaultAbilityBuilder } from './ability';
4
+ /**
5
+ * Default user extractor from JWT payload
6
+ */
7
+ const defaultUserExtractor = (payload) => {
8
+ if (!payload.sub)
9
+ return undefined;
10
+ return {
11
+ id: payload.sub,
12
+ roles: payload.roles || [],
13
+ ...payload, // Include any additional claims
14
+ };
15
+ };
16
+ /**
17
+ * JWT middleware factory for GraphQL servers
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // In Nuxt Yoga
22
+ * export default defineNuxtConfig({
23
+ * yoga: {
24
+ * middleware: [
25
+ * createJWTMiddleware({
26
+ * enabled: true,
27
+ * secret: process.env.JWT_SECRET,
28
+ * optional: true // Don't fail if no token
29
+ * })
30
+ * ]
31
+ * }
32
+ * })
33
+ *
34
+ * // In Postgraphile
35
+ * const jwtPlugin = createPostgraphileJWTPlugin({
36
+ * secret: process.env.JWT_SECRET,
37
+ * extractUser: (payload) => ({
38
+ * id: payload.user_id,
39
+ * roles: payload.user_roles
40
+ * })
41
+ * })
42
+ * ```
43
+ */
44
+ export const createJWTMiddleware = (config = {}) => {
45
+ const { enabled = true, secret, publicKey, algorithms = ['HS256'], issuer, audience, headerName = 'authorization', tokenPrefix = 'Bearer ', optional = false, extractUser = defaultUserExtractor, maxAge, } = config;
46
+ // Validate configuration
47
+ if (enabled && !secret && !publicKey) {
48
+ throw new Error('JWT middleware requires either secret or publicKey');
49
+ }
50
+ return async (context, next) => {
51
+ // Skip if JWT is disabled
52
+ if (!enabled) {
53
+ return next();
54
+ }
55
+ try {
56
+ // Extract token from request headers
57
+ const authHeader = context.req?.headers?.get?.(headerName) ||
58
+ context.request?.headers?.get?.(headerName) ||
59
+ context.headers?.[headerName];
60
+ if (!authHeader) {
61
+ if (optional) {
62
+ return next();
63
+ }
64
+ throw new Error('No authorization header found');
65
+ }
66
+ // Remove token prefix
67
+ const token = authHeader.startsWith(tokenPrefix) ? authHeader.slice(tokenPrefix.length) : authHeader;
68
+ // Prepare verification options
69
+ const verifyOptions = {
70
+ algorithms,
71
+ ...(issuer && { issuer }),
72
+ ...(audience && { audience }),
73
+ ...(maxAge && { maxAge }),
74
+ };
75
+ // Verify and decode token
76
+ const secretOrPublicKey = publicKey || secret;
77
+ const payload = jwt.verify(token, secretOrPublicKey, verifyOptions);
78
+ // Extract user from payload
79
+ const user = extractUser(payload);
80
+ if (user) {
81
+ context.user = user;
82
+ // Store the raw payload for potential use
83
+ context.jwtPayload = payload;
84
+ }
85
+ // Continue to next middleware
86
+ return next();
87
+ }
88
+ catch (error) {
89
+ if (optional) {
90
+ // Log error in development
91
+ if (process.env.NODE_ENV === 'development') {
92
+ console.warn('JWT verification failed (optional):', error.message);
93
+ }
94
+ // Continue without user if optional
95
+ return next();
96
+ }
97
+ // Re-throw with more specific error messages
98
+ if (error.name === 'TokenExpiredError') {
99
+ throw new Error('Token has expired');
100
+ }
101
+ else if (error.name === 'JsonWebTokenError') {
102
+ throw new Error('Invalid token');
103
+ }
104
+ else if (error.name === 'NotBeforeError') {
105
+ throw new Error('Token not active yet');
106
+ }
107
+ throw error;
108
+ }
109
+ };
110
+ };
111
+ /**
112
+ * Create a JWT token with user data
113
+ */
114
+ export const createJWT = (user, config) => {
115
+ const { secret, expiresIn = '1h', issuer, audience, additionalClaims = {} } = config;
116
+ const payload = {
117
+ sub: user.id,
118
+ roles: user.roles || [],
119
+ ...additionalClaims,
120
+ };
121
+ const signOptions = {};
122
+ // Add optional fields only if they exist
123
+ if (issuer !== undefined)
124
+ signOptions.issuer = issuer;
125
+ if (audience !== undefined)
126
+ signOptions.audience = audience;
127
+ if (expiresIn !== undefined)
128
+ signOptions.expiresIn = expiresIn; // Type cast to avoid TS issues
129
+ return jwt.sign(payload, secret, signOptions);
130
+ };
131
+ /**
132
+ * Integration with CASL ability builder
133
+ */
134
+ export const createJWTAbilityBuilder = (config = {}) => {
135
+ return async (user) => {
136
+ // If user has direct permissions in JWT, use those
137
+ const jwtPermissions = user?.permissions;
138
+ if (jwtPermissions && Array.isArray(jwtPermissions)) {
139
+ // Build ability from JWT permissions
140
+ const { can, cannot, build } = new AbilityBuilder(PureAbility);
141
+ jwtPermissions.forEach((permission) => {
142
+ if (permission.inverted) {
143
+ cannot(permission.action, permission.subject, permission.conditions);
144
+ }
145
+ else {
146
+ can(permission.action, permission.subject, permission.conditions);
147
+ }
148
+ });
149
+ return build();
150
+ }
151
+ // Fall back to role-based abilities
152
+ return defaultAbilityBuilder(user);
153
+ };
154
+ };
155
+ /**
156
+ * Postgraphile-specific JWT plugin
157
+ */
158
+ export const createPostgraphileJWTPlugin = (config) => {
159
+ return {
160
+ name: 'JWTAuthPlugin',
161
+ version: '1.0.0',
162
+ // Hook into Postgraphile's context building
163
+ grafast: {
164
+ hooks: {
165
+ async context(ctx, build) {
166
+ const middleware = createJWTMiddleware(config);
167
+ // Create a simple context object that the middleware can work with
168
+ const context = {
169
+ req: ctx.req,
170
+ headers: ctx.req?.headers,
171
+ user: undefined,
172
+ jwtPayload: undefined,
173
+ };
174
+ // Run the JWT middleware
175
+ await middleware(context, async () => { });
176
+ // Add user to Postgraphile context
177
+ if (context.user) {
178
+ return { ...ctx, user: context.user, jwtPayload: context.jwtPayload };
179
+ }
180
+ return ctx;
181
+ },
182
+ },
183
+ },
184
+ };
185
+ };
186
+ /**
187
+ * Express/Koa middleware for REST endpoints
188
+ */
189
+ export const createHTTPJWTMiddleware = (config) => {
190
+ const jwtMiddleware = createJWTMiddleware(config);
191
+ // Express middleware
192
+ return async (req, res, next) => {
193
+ const context = {
194
+ req: {
195
+ headers: {
196
+ get: (name) => req.headers[name],
197
+ },
198
+ },
199
+ headers: req.headers,
200
+ user: undefined,
201
+ jwtPayload: undefined,
202
+ };
203
+ try {
204
+ await jwtMiddleware(context, async () => { });
205
+ req.user = context.user;
206
+ req.jwtPayload = context.jwtPayload;
207
+ next();
208
+ }
209
+ catch (error) {
210
+ if (config.optional) {
211
+ next();
212
+ }
213
+ else {
214
+ res.status(401).json({
215
+ error: error.message,
216
+ code: 'UNAUTHORIZED',
217
+ });
218
+ }
219
+ }
220
+ };
221
+ };
222
+ /**
223
+ * Refresh token utilities
224
+ */
225
+ export const refreshTokenUtils = {
226
+ /**
227
+ * Create access and refresh tokens
228
+ */
229
+ createTokenPair: (user, config) => {
230
+ const { accessSecret, refreshSecret, accessExpiresIn = '15m', refreshExpiresIn = '7d' } = config;
231
+ const accessPayload = {
232
+ sub: user.id,
233
+ roles: user.roles,
234
+ type: 'access',
235
+ };
236
+ const refreshPayload = {
237
+ sub: user.id,
238
+ type: 'refresh',
239
+ };
240
+ // Create access token with proper options
241
+ const accessOptions = {};
242
+ if (accessExpiresIn) {
243
+ accessOptions.expiresIn = accessExpiresIn; // Type cast to avoid TS issues
244
+ }
245
+ const accessToken = jwt.sign(accessPayload, accessSecret, accessOptions);
246
+ // Create refresh token with proper options
247
+ const refreshOptions = {};
248
+ if (refreshExpiresIn) {
249
+ refreshOptions.expiresIn = refreshExpiresIn; // Type cast to avoid TS issues
250
+ }
251
+ const refreshToken = jwt.sign(refreshPayload, refreshSecret, refreshOptions);
252
+ return { accessToken, refreshToken };
253
+ },
254
+ /**
255
+ * Verify refresh token and create new access token
256
+ */
257
+ refreshAccessToken: async (refreshToken, config) => {
258
+ const { accessSecret, refreshSecret, getUserById, accessExpiresIn = '15m' } = config;
259
+ try {
260
+ // Verify refresh token
261
+ const payload = jwt.verify(refreshToken, refreshSecret);
262
+ if (payload.type !== 'refresh') {
263
+ throw new Error('Invalid token type');
264
+ }
265
+ // Get fresh user data
266
+ const user = await getUserById(payload.sub);
267
+ if (!user) {
268
+ throw new Error('User not found');
269
+ }
270
+ // Create new access token
271
+ const accessPayload = {
272
+ sub: user.id,
273
+ roles: user.roles,
274
+ type: 'access',
275
+ };
276
+ // Create access token with proper options
277
+ const accessOptions = {};
278
+ if (accessExpiresIn) {
279
+ accessOptions.expiresIn = accessExpiresIn; // Type cast to avoid TS issues
280
+ }
281
+ const accessToken = jwt.sign(accessPayload, accessSecret, accessOptions);
282
+ return { accessToken, user };
283
+ }
284
+ catch (error) {
285
+ if (error.name === 'TokenExpiredError') {
286
+ throw new Error('Refresh token expired');
287
+ }
288
+ throw error;
289
+ }
290
+ },
291
+ };
@@ -0,0 +1,7 @@
1
+ import { GraphileConfig } from 'postgraphile/graphile-build';
2
+ /**
3
+ * PostGraphile plugin for CASL authorization
4
+ * @public
5
+ */
6
+ export declare const pglCaslPlugin: GraphileConfig.Plugin;
7
+ //# sourceMappingURL=postgraphile.d.ts.map
@@ -0,0 +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,MAkFzC,CAAA"}
@@ -0,0 +1,80 @@
1
+ import { extendSchema, gql } from 'postgraphile/utils';
2
+ import { createAbility } from './ability';
3
+ /**
4
+ * PostGraphile plugin for CASL authorization
5
+ * @public
6
+ */
7
+ export const pglCaslPlugin = extendSchema(build => {
8
+ const { grafast: { constant, object, sideEffect }, } = build;
9
+ return {
10
+ typeDefs: gql `
11
+ input CreateAbilityInput {
12
+ userId: String!
13
+ roles: [String!]
14
+ }
15
+
16
+ type AbilityResponse {
17
+ success: Boolean!
18
+ ability: JSON
19
+ message: String
20
+ }
21
+
22
+ type SecretData {
23
+ id: String!
24
+ content: String!
25
+ }
26
+
27
+ extend type Query {
28
+ getSecretData: SecretData
29
+ }
30
+
31
+ extend type Mutation {
32
+ createAbility(input: CreateAbilityInput!): AbilityResponse!
33
+ }
34
+ `,
35
+ objects: {
36
+ Query: {
37
+ plans: {
38
+ async getSecretData() {
39
+ // TODO: This should be protected by CASL
40
+ // const $ability = context<Context>().get('ability')
41
+ // if (!$ability.can('read', 'SecretData')) {
42
+ // throw new Error('Access denied')
43
+ // }
44
+ return object({
45
+ id: constant('123'),
46
+ content: constant('This is protected content'),
47
+ });
48
+ },
49
+ },
50
+ },
51
+ Mutation: {
52
+ plans: {
53
+ async createAbility(_plan, fieldArgs) {
54
+ const $input = fieldArgs.getRaw().input;
55
+ const $userId = $input.userId;
56
+ const $roles = $input.roles;
57
+ return sideEffect([$userId, $roles], async ([userId, roles]) => {
58
+ // Make this async
59
+ try {
60
+ const ability = await createAbility({ id: userId, roles }); // Await here
61
+ return {
62
+ success: true,
63
+ ability: ability.rules,
64
+ message: 'Ability created successfully',
65
+ };
66
+ }
67
+ catch (error) {
68
+ return {
69
+ success: false,
70
+ ability: null,
71
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
72
+ };
73
+ }
74
+ });
75
+ },
76
+ },
77
+ },
78
+ },
79
+ };
80
+ });
@@ -0,0 +1,15 @@
1
+ import type { Plugin } from 'graphql-yoga';
2
+ import type { Context, MiddlewareOptions } from '../types';
3
+ export declare const yogaCaslPlugin: Plugin<Context>;
4
+ /**
5
+ * Create a GraphQL Yoga plugin for CASL authorization
6
+ * Note: This is a placeholder for future implementation
7
+ *
8
+ * @param options - CASL middleware configuration options
9
+ * @returns Yoga plugin
10
+ * @public
11
+ */
12
+ export declare const createYogaPlugin: (options?: MiddlewareOptions) => {
13
+ onExecute: ({ args }: any) => Promise<void>;
14
+ };
15
+ //# sourceMappingURL=yoga.d.ts.map
@@ -0,0 +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;AAI1C,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,UAAS,iBAAsB;0BAIlC,GAAG;CAMhC,CAAA"}
@@ -0,0 +1,32 @@
1
+ import { createAbility } from './ability';
2
+ import { createCaslMiddleware } from './graphql';
3
+ const getLoggedInUser = () => {
4
+ // Mock user for demonstration purposes
5
+ return { id: '1', roles: ['editor'] };
6
+ };
7
+ export const yogaCaslPlugin = {
8
+ onContextBuilding: async ({ context, extendContext }) => {
9
+ // Make this async
10
+ const user = getLoggedInUser();
11
+ const ability = await createAbility(user); // Await here
12
+ extendContext({ ability, user });
13
+ },
14
+ };
15
+ /**
16
+ * Create a GraphQL Yoga plugin for CASL authorization
17
+ * Note: This is a placeholder for future implementation
18
+ *
19
+ * @param options - CASL middleware configuration options
20
+ * @returns Yoga plugin
21
+ * @public
22
+ */
23
+ export const createYogaPlugin = (options = {}) => {
24
+ const middleware = createCaslMiddleware(options);
25
+ return {
26
+ onExecute: async ({ args }) => {
27
+ // TODO: Implement Yoga plugin
28
+ // This would integrate with GraphQL Yoga's plugin system
29
+ console.log('Yoga plugin not yet implemented');
30
+ },
31
+ };
32
+ };
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.55.2"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,114 @@
1
+ import { GraphQLResolveInfo } from 'graphql';
2
+ import type { AppAbility, AbilityBuilderFunction } from '../middleware/ability';
3
+ /**
4
+ * User information for authorization
5
+ * @public
6
+ */
7
+ export interface User {
8
+ /** Unique identifier for the user */
9
+ id: string;
10
+ /** Array of role names assigned to the user */
11
+ roles?: string[];
12
+ /** Additional user properties */
13
+ [key: string]: any;
14
+ }
15
+ /**
16
+ * GraphQL context with CASL ability and user information
17
+ * @public
18
+ */
19
+ export interface Context {
20
+ /** CASL ability instance for authorization checks */
21
+ ability?: AppAbility;
22
+ /** Current authenticated user */
23
+ user?: User;
24
+ /** Additional context properties */
25
+ [key: string]: any;
26
+ }
27
+ /**
28
+ * Configuration options for CASL middleware
29
+ * @public
30
+ */
31
+ export interface MiddlewareOptions {
32
+ /** Mapping of GraphQL types to authorization subjects */
33
+ subjectMap?: Record<string, string>;
34
+ /** Mapping of GraphQL operations to CASL actions */
35
+ actionMap?: Record<string, string>;
36
+ /** Field-level permission rules */
37
+ fieldPermissions?: Record<string, FieldPermission[]>;
38
+ /** Custom function to build user abilities */
39
+ abilityBuilder?: AbilityBuilderFunction;
40
+ /** Enable debug logging */
41
+ debug?: boolean;
42
+ }
43
+ /**
44
+ * Field-level permission definition for fine-grained access control
45
+ * @public
46
+ */
47
+ export interface FieldPermission {
48
+ /** CASL action (e.g., 'read', 'write') */
49
+ action: string;
50
+ /** Subject/resource type to apply permission to */
51
+ subject: string;
52
+ /** Specific field name (optional) */
53
+ field?: string;
54
+ /** Conditional rules for permission */
55
+ conditions?: any;
56
+ }
57
+ /**
58
+ * GraphQL resolver function type
59
+ * @public
60
+ */
61
+ export type ResolverFn = (root: any, args: any, context: Context, info: GraphQLResolveInfo) => Promise<any> | any;
62
+ /**
63
+ * Middleware function that wraps a GraphQL resolver with authorization logic
64
+ * @public
65
+ */
66
+ export type MiddlewareFn = (resolve: ResolverFn, root: any, args: any, context: Context, info: GraphQLResolveInfo) => Promise<any> | any;
67
+ /**
68
+ * Plugin configuration options for framework integrations
69
+ * @public
70
+ */
71
+ export interface PluginOptions extends MiddlewareOptions {
72
+ /** Custom function to build user abilities */
73
+ abilityBuilder?: AbilityBuilderFunction;
74
+ /** Ability caching configuration */
75
+ cacheOptions?: {
76
+ /** Time-to-live in milliseconds */
77
+ ttl?: number;
78
+ /** Function to generate cache key from user */
79
+ key?: (user?: User) => string;
80
+ };
81
+ }
82
+ /**
83
+ * Response type for ability creation mutations
84
+ * @public
85
+ */
86
+ export interface AbilityResponse {
87
+ /** Whether the ability was created successfully */
88
+ success: boolean;
89
+ /** The created ability rules */
90
+ ability: any;
91
+ /** Success or error message */
92
+ message: string;
93
+ }
94
+ /**
95
+ * Input type for creating a new ability
96
+ * @public
97
+ */
98
+ export interface CreateAbilityInput {
99
+ /** User ID to create ability for */
100
+ userId: string;
101
+ /** Array of role names to assign */
102
+ roles: string[];
103
+ }
104
+ export interface AbilityRule {
105
+ id?: string;
106
+ roleId?: string;
107
+ userId?: string;
108
+ action: string | string[];
109
+ subject: string;
110
+ fields?: string[];
111
+ conditions?: any;
112
+ inverted?: boolean;
113
+ }
114
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"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"}
File without changes