@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.
- package/README.md +149 -0
- package/dist/casl-middleware.d.ts +158 -0
- package/dist/casl-middleware.js +40571 -0
- package/dist/casl-middleware.js.map +1 -0
- package/dist/casl_middleware.tsbuildinfo +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +3 -0
- package/dist/src/middleware/ability.d.ts +55 -0
- package/dist/src/middleware/ability.d.ts.map +1 -0
- package/dist/src/middleware/ability.js +139 -0
- package/dist/src/middleware/graphql.d.ts +11 -0
- package/dist/src/middleware/graphql.d.ts.map +1 -0
- package/dist/src/middleware/graphql.js +120 -0
- package/dist/src/middleware/introspection.d.ts +71 -0
- package/dist/src/middleware/introspection.d.ts.map +1 -0
- package/dist/src/middleware/introspection.js +169 -0
- package/dist/src/middleware/jwt.d.ts +114 -0
- package/dist/src/middleware/jwt.d.ts.map +1 -0
- package/dist/src/middleware/jwt.js +291 -0
- package/dist/src/middleware/postgraphile.d.ts +7 -0
- package/dist/src/middleware/postgraphile.d.ts.map +1 -0
- package/dist/src/middleware/postgraphile.js +80 -0
- package/dist/src/middleware/yoga.d.ts +15 -0
- package/dist/src/middleware/yoga.d.ts.map +1 -0
- package/dist/src/middleware/yoga.js +32 -0
- package/dist/src/tsdoc-metadata.json +11 -0
- package/dist/src/types/index.d.ts +114 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +0 -0
- package/dist/tests/ability.test.d.ts +2 -0
- package/dist/tests/ability.test.d.ts.map +1 -0
- package/dist/tests/ability.test.js +125 -0
- package/dist/tests/helpers/test-utils.d.ts +46 -0
- package/dist/tests/helpers/test-utils.d.ts.map +1 -0
- package/dist/tests/helpers/test-utils.js +92 -0
- package/dist/tests/introspection.test.d.ts +2 -0
- package/dist/tests/introspection.test.d.ts.map +1 -0
- package/dist/tests/introspection.test.js +368 -0
- package/dist/tests/jwt.test.d.ts +2 -0
- package/dist/tests/jwt.test.d.ts.map +1 -0
- package/dist/tests/jwt.test.js +371 -0
- package/dist/tests/middleware.test.d.ts +2 -0
- package/dist/tests/middleware.test.d.ts.map +1 -0
- package/dist/tests/middleware.test.js +184 -0
- package/dist/tests/postgraphile-plugin.test.d.ts +2 -0
- package/dist/tests/postgraphile-plugin.test.d.ts.map +1 -0
- package/dist/tests/postgraphile-plugin.test.js +56 -0
- package/dist/tests/setup.d.ts +2 -0
- package/dist/tests/setup.d.ts.map +1 -0
- package/dist/tests/setup.js +11 -0
- package/dist/tests/user-roles.test.d.ts +2 -0
- package/dist/tests/user-roles.test.d.ts.map +1 -0
- package/dist/tests/user-roles.test.js +157 -0
- package/dist/tests/yoga-plugin.test.d.ts +2 -0
- package/dist/tests/yoga-plugin.test.d.ts.map +1 -0
- package/dist/tests/yoga-plugin.test.js +47 -0
- package/package.json +91 -0
- package/src/index.ts +15 -0
- package/src/middleware/ability.ts +191 -0
- package/src/middleware/graphql.ts +157 -0
- package/src/middleware/introspection.ts +258 -0
- package/src/middleware/jwt.ts +394 -0
- package/src/middleware/postgraphile.ts +93 -0
- package/src/middleware/yoga.ts +39 -0
- package/src/types/index.ts +133 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { extendSchema, gql } from 'postgraphile/utils'
|
|
2
|
+
import type { ExecutableStep } from 'postgraphile/grafast'
|
|
3
|
+
import { GraphileConfig } from 'postgraphile/graphile-build'
|
|
4
|
+
|
|
5
|
+
import { createAbility } from './ability'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* PostGraphile plugin for CASL authorization
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export const pglCaslPlugin: GraphileConfig.Plugin = extendSchema(build => {
|
|
12
|
+
const {
|
|
13
|
+
grafast: { constant, object, sideEffect },
|
|
14
|
+
} = build
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
typeDefs: gql`
|
|
18
|
+
input CreateAbilityInput {
|
|
19
|
+
userId: String!
|
|
20
|
+
roles: [String!]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type AbilityResponse {
|
|
24
|
+
success: Boolean!
|
|
25
|
+
ability: JSON
|
|
26
|
+
message: String
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type SecretData {
|
|
30
|
+
id: String!
|
|
31
|
+
content: String!
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
extend type Query {
|
|
35
|
+
getSecretData: SecretData
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
extend type Mutation {
|
|
39
|
+
createAbility(input: CreateAbilityInput!): AbilityResponse!
|
|
40
|
+
}
|
|
41
|
+
`,
|
|
42
|
+
|
|
43
|
+
objects: {
|
|
44
|
+
Query: {
|
|
45
|
+
plans: {
|
|
46
|
+
async getSecretData() {
|
|
47
|
+
// TODO: This should be protected by CASL
|
|
48
|
+
// const $ability = context<Context>().get('ability')
|
|
49
|
+
// if (!$ability.can('read', 'SecretData')) {
|
|
50
|
+
// throw new Error('Access denied')
|
|
51
|
+
// }
|
|
52
|
+
|
|
53
|
+
return object({
|
|
54
|
+
id: constant('123'),
|
|
55
|
+
content: constant('This is protected content'),
|
|
56
|
+
})
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
Mutation: {
|
|
62
|
+
plans: {
|
|
63
|
+
async createAbility(_plan: any, fieldArgs: any) {
|
|
64
|
+
const $input = fieldArgs.getRaw().input
|
|
65
|
+
const $userId = $input.userId
|
|
66
|
+
const $roles = $input.roles
|
|
67
|
+
|
|
68
|
+
return sideEffect(
|
|
69
|
+
[$userId as ExecutableStep, $roles as ExecutableStep],
|
|
70
|
+
async ([userId, roles]: [string, string[]]) => {
|
|
71
|
+
// Make this async
|
|
72
|
+
try {
|
|
73
|
+
const ability = await createAbility({ id: userId, roles }) // Await here
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
ability: ability.rules,
|
|
77
|
+
message: 'Ability created successfully',
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
ability: null,
|
|
83
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Plugin } from 'graphql-yoga'
|
|
2
|
+
|
|
3
|
+
import { createAbility } from './ability'
|
|
4
|
+
import { createCaslMiddleware } from './graphql'
|
|
5
|
+
import type { Context, MiddlewareOptions } from '../types'
|
|
6
|
+
|
|
7
|
+
const getLoggedInUser = () => {
|
|
8
|
+
// Mock user for demonstration purposes
|
|
9
|
+
return { id: '1', roles: ['editor'] }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const yogaCaslPlugin: Plugin<Context> = {
|
|
13
|
+
onContextBuilding: async ({ context, extendContext }) => {
|
|
14
|
+
// Make this async
|
|
15
|
+
const user = getLoggedInUser()
|
|
16
|
+
const ability = await createAbility(user) // Await here
|
|
17
|
+
extendContext({ ability, user })
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a GraphQL Yoga plugin for CASL authorization
|
|
23
|
+
* Note: This is a placeholder for future implementation
|
|
24
|
+
*
|
|
25
|
+
* @param options - CASL middleware configuration options
|
|
26
|
+
* @returns Yoga plugin
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export const createYogaPlugin = (options: MiddlewareOptions = {}) => {
|
|
30
|
+
const middleware = createCaslMiddleware(options)
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
onExecute: async ({ args }: any) => {
|
|
34
|
+
// TODO: Implement Yoga plugin
|
|
35
|
+
// This would integrate with GraphQL Yoga's plugin system
|
|
36
|
+
console.log('Yoga plugin not yet implemented')
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { GraphQLResolveInfo } from 'graphql'
|
|
2
|
+
|
|
3
|
+
import type { AppAbility, AbilityBuilderFunction } from '../middleware/ability'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* User information for authorization
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
export interface User {
|
|
10
|
+
/** Unique identifier for the user */
|
|
11
|
+
id: string
|
|
12
|
+
/** Array of role names assigned to the user */
|
|
13
|
+
roles?: string[]
|
|
14
|
+
/** Additional user properties */
|
|
15
|
+
[key: string]: any
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* GraphQL context with CASL ability and user information
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export interface Context {
|
|
23
|
+
/** CASL ability instance for authorization checks */
|
|
24
|
+
ability?: AppAbility
|
|
25
|
+
/** Current authenticated user */
|
|
26
|
+
user?: User
|
|
27
|
+
/** Additional context properties */
|
|
28
|
+
[key: string]: any
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ... rest of the file stays the same
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Configuration options for CASL middleware
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export interface MiddlewareOptions {
|
|
38
|
+
/** Mapping of GraphQL types to authorization subjects */
|
|
39
|
+
subjectMap?: Record<string, string>
|
|
40
|
+
/** Mapping of GraphQL operations to CASL actions */
|
|
41
|
+
actionMap?: Record<string, string>
|
|
42
|
+
/** Field-level permission rules */
|
|
43
|
+
fieldPermissions?: Record<string, FieldPermission[]>
|
|
44
|
+
/** Custom function to build user abilities */
|
|
45
|
+
abilityBuilder?: AbilityBuilderFunction
|
|
46
|
+
/** Enable debug logging */
|
|
47
|
+
debug?: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Field-level permission definition for fine-grained access control
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export interface FieldPermission {
|
|
55
|
+
/** CASL action (e.g., 'read', 'write') */
|
|
56
|
+
action: string
|
|
57
|
+
/** Subject/resource type to apply permission to */
|
|
58
|
+
subject: string
|
|
59
|
+
/** Specific field name (optional) */
|
|
60
|
+
field?: string
|
|
61
|
+
/** Conditional rules for permission */
|
|
62
|
+
conditions?: any
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* GraphQL resolver function type
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export type ResolverFn = (root: any, args: any, context: Context, info: GraphQLResolveInfo) => Promise<any> | any
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Middleware function that wraps a GraphQL resolver with authorization logic
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
75
|
+
export type MiddlewareFn = (
|
|
76
|
+
resolve: ResolverFn,
|
|
77
|
+
root: any,
|
|
78
|
+
args: any,
|
|
79
|
+
context: Context,
|
|
80
|
+
info: GraphQLResolveInfo
|
|
81
|
+
) => Promise<any> | any
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Plugin configuration options for framework integrations
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
export interface PluginOptions extends MiddlewareOptions {
|
|
88
|
+
/** Custom function to build user abilities */
|
|
89
|
+
abilityBuilder?: AbilityBuilderFunction
|
|
90
|
+
/** Ability caching configuration */
|
|
91
|
+
cacheOptions?: {
|
|
92
|
+
/** Time-to-live in milliseconds */
|
|
93
|
+
ttl?: number
|
|
94
|
+
/** Function to generate cache key from user */
|
|
95
|
+
key?: (user?: User) => string
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Response type for ability creation mutations
|
|
101
|
+
* @public
|
|
102
|
+
*/
|
|
103
|
+
export interface AbilityResponse {
|
|
104
|
+
/** Whether the ability was created successfully */
|
|
105
|
+
success: boolean
|
|
106
|
+
/** The created ability rules */
|
|
107
|
+
ability: any
|
|
108
|
+
/** Success or error message */
|
|
109
|
+
message: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Input type for creating a new ability
|
|
114
|
+
* @public
|
|
115
|
+
*/
|
|
116
|
+
export interface CreateAbilityInput {
|
|
117
|
+
/** User ID to create ability for */
|
|
118
|
+
userId: string
|
|
119
|
+
/** Array of role names to assign */
|
|
120
|
+
roles: string[]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Rule definition for database storage
|
|
124
|
+
export interface AbilityRule {
|
|
125
|
+
id?: string
|
|
126
|
+
roleId?: string
|
|
127
|
+
userId?: string
|
|
128
|
+
action: string | string[]
|
|
129
|
+
subject: string
|
|
130
|
+
fields?: string[]
|
|
131
|
+
conditions?: any
|
|
132
|
+
inverted?: boolean
|
|
133
|
+
}
|