@makerx/graphql-core 3.0.0-beta.1 → 3.0.0-beta.3

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 CHANGED
@@ -91,21 +91,21 @@ const graphqlServer = createServer({
91
91
 
92
92
  `context.requestInfo` is built for every request — both HTTP and websocket subscription connects — so downstream code can distinguish sources, rebuild URLs, pass through correlation headers, etc.
93
93
 
94
- | Field | Description |
95
- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
- | `requestId` | `x-request-id` header if present, otherwise a freshly generated UUID. |
97
- | `source` | `'http'` for regular requests, `'subscription'` for websocket connects. |
98
- | `protocol` | `'http'` / `'https'` for HTTP, `'ws'` / `'wss'` for subscriptions (resolved via `x-forwarded-proto` or TLS socket encryption). |
99
- | `host` | Hostname only (no port). Prefers `x-forwarded-host`, falls back to the `Host` header, then `req.hostname` (Express only). |
100
- | `port` | Port parsed from `x-forwarded-host` / `Host` header when present; `undefined` otherwise. |
94
+ | Field | Description |
95
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
96
+ | `requestId` | `x-request-id` header if present, otherwise a freshly generated UUID. |
97
+ | `source` | `'http'` for regular requests, `'subscription'` for websocket connects. |
98
+ | `protocol` | `'http'` / `'https'` for HTTP, `'ws'` / `'wss'` for subscriptions (resolved via `x-forwarded-proto` or TLS socket encryption). |
99
+ | `host` | Hostname only (no port). Prefers `x-forwarded-host`, falls back to the `Host` header, then `req.hostname` (Express only). |
100
+ | `port` | Port parsed from `x-forwarded-host` / `Host` header when present; `undefined` otherwise. |
101
101
  | `baseUrl` | Fully-qualified origin (`scheme://host[:port]`) with default ports stripped. For subscriptions the scheme is normalised to `http(s)` so the value composes with relative URLs. |
102
- | `url` | `req.originalUrl` for HTTP, `req.url` for subscription connects. |
103
- | `origin` | `Origin` header. |
104
- | `referer` | `Referer` header. |
105
- | `correlationId` | `x-correlation-id` header. |
106
- | `arrLogId` | `x-arr-log-id` header (Azure Front Door / ARR). |
107
- | `clientIp` | First value from `x-forwarded-for`, falling back to `socket.remoteAddress`. |
108
- | `userAgent` | `User-Agent` header. |
102
+ | `url` | `req.originalUrl` for HTTP, `req.url` for subscription connects. |
103
+ | `origin` | `Origin` header. |
104
+ | `referer` | `Referer` header. |
105
+ | `correlationId` | `x-correlation-id` header. |
106
+ | `arrLogId` | `x-arr-log-id` header (Azure Front Door / ARR). |
107
+ | `clientIp` | First value from `x-forwarded-for`, falling back to `socket.remoteAddress`. |
108
+ | `userAgent` | `User-Agent` header. |
109
109
 
110
110
  You can add more via `augmentRequestInfo(input)`. Lambda deployments also get `functionName` and `awsRequestId` when a `LambdaContext` is supplied.
111
111
 
@@ -163,6 +163,112 @@ const graphqlServer = createServer({
163
163
  })
164
164
  ```
165
165
 
166
+ ## Shield (authorization)
167
+
168
+ The `shield` module provides typed helpers around [graphql-shield](https://the-guild.dev/graphql/shield) for defining authorization middleware against your generated `Resolvers` type.
169
+
170
+ `graphql-shield` is an optional peer dependency — install it only if you use this module:
171
+
172
+ ```sh
173
+ npm i graphql-shield
174
+ ```
175
+
176
+ ### createShieldSchema
177
+
178
+ Builds a shield middleware with a schema typed against your resolver map, so field-level rule wiring is checked by the TypeScript compiler.
179
+
180
+ ```ts
181
+ import { Resolvers } from '../generated/types'
182
+ import { or } from 'graphql-shield'
183
+ import { applyMiddleware } from 'graphql-middleware'
184
+ import { createShieldSchema, hasRoleRule, unauthorisedError } from '@makerx/graphql-core'
185
+
186
+ const isCoordinator = hasRoleRule('coordinator')
187
+ const isSystemAdmin = hasRoleRule('system-admin')
188
+ const isAuthorisedUser = or(isCoordinator, isSystemAdmin)
189
+
190
+ const shieldSchema = createShieldSchema<Resolvers>(
191
+ {
192
+ Query: {
193
+ '*': isAuthorisedUser,
194
+ user: isCoordinator,
195
+ },
196
+ Mutation: {
197
+ '*': isCoordinator,
198
+ createUser: isSystemAdmin,
199
+ },
200
+ },
201
+ { fallbackRule: isAuthorisedUser, fallbackError: unauthorisedError() },
202
+ )
203
+
204
+ const schema = applyMiddleware(makeExecutableSchema({ ... }), shieldSchema)
205
+ ```
206
+
207
+ The `'*'` key at each object level is a fallback rule applied to any field at that level without an explicit rule.
208
+
209
+ The wrapper defaults `allowExternalErrors: true`; the full shield options object (second argument) is forwarded to `shield(...)` and can override it.
210
+
211
+ #### v3 breaking change: no default fallback rule
212
+
213
+ Prior to v3 this wrapper defaulted `fallbackRule` to `allow` — fields not covered by the schema were open by default. v3 removes that default and the wrapper no longer makes that choice for you. Without an explicit `fallbackRule`, graphql-shield's own default (`allow`) still applies, so behaviour is unchanged when no options are passed, but if you want a deny-by-default posture you should now pass an explicit `fallbackRule` (commonly your auth-check rule) along with a `fallbackError`.
214
+
215
+ The second argument also changed from a bare `ShieldRule` to the full shield options object:
216
+
217
+ ```ts
218
+ // v2
219
+ createShieldSchema<Resolvers>(schema, isAuthorisedUser)
220
+
221
+ // v3
222
+ createShieldSchema<Resolvers>(schema, { fallbackRule: isAuthorisedUser, fallbackError: unauthorisedError() })
223
+ ```
224
+
225
+ ### Role and scope rules
226
+
227
+ Convenience builders that read `ctx.user.roles` / `ctx.user.scopes`. All use graphql-shield's `'contextual'` cache so the result is reused across fields within a request.
228
+
229
+ | Helper | Description |
230
+ | ---------------------------------------- | ----------------------------------------------------------------------------- |
231
+ | `hasRoleRule(role, ruleName?)` | Passes when `user.roles` includes `role`. |
232
+ | `hasAnyRoleRule(...roles)` | Passes when `user.roles` includes at least one of `roles`. |
233
+ | `hasAnyRoleRuleWithName(name, ...roles)` | Same, with an explicit rule name / cache key for large or dynamic role lists. |
234
+ | `hasScopeRule(scope)` | Passes when `user.scopes` includes `scope`. |
235
+ | `hasAnyScopeRule(...scopes)` | Passes when `user.scopes` includes at least one of `scopes`. |
236
+
237
+ ### createRule
238
+
239
+ Typed wrapper around graphql-shield's `rule(...)` for ad-hoc rules. Unlike the underlying `rule`, `createRule` types `parent`, `args` and `ctx` to your generics.
240
+
241
+ ```ts
242
+ const isOwner = createRule<GraphQLContext, Record, { ownerId: string }>((parent, _, ctx) => parent.ownerId === ctx.user?.id)
243
+ ```
244
+
245
+ Defaults to `'strict'` cache; pass `'contextual'` as the second argument for rules whose result depends only on `ctx`.
246
+
247
+ ### combineRuleWithAll
248
+
249
+ Composes a rule with every rule in an existing shield schema via a combinator (`and` | `or` | `chain` | `race`). Useful for post-hoc gating — e.g. bolting a "not blocked" check onto an existing schema without touching each field:
250
+
251
+ ```ts
252
+ import { chain } from 'graphql-shield'
253
+
254
+ const gated = combineRuleWithAll(shieldSchema, accountNotBlocked, chain)
255
+ ```
256
+
257
+ ### Authorization errors
258
+
259
+ The `unauthorised` module provides shared helpers for producing forbidden errors used by the shield wrapper and by resolvers.
260
+
261
+ | Helper | Description |
262
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
263
+ | `unauthorisedError(message?)` | Builds a `GraphQLError` with Apollo's `FORBIDDEN` extension code and HTTP 403 status. |
264
+ | `throwUnauthorised(message?)` | Throws `unauthorisedError`; return type is `never` so TypeScript narrows control flow past the call. |
265
+ | `permissionInvariant(condition, message?)` | Assertion helper: throws `unauthorisedError` when `condition` is false, otherwise narrows it to `true`. |
266
+
267
+ ```ts
268
+ permissionInvariant(ctx.user?.id === record.ownerId, 'Only the owner can edit this record')
269
+ // past this line, TypeScript knows the condition held
270
+ ```
271
+
166
272
  ## Logging
167
273
 
168
274
  ### logGraphQLOperation
@@ -223,7 +329,6 @@ This library includes a `subscriptions` module to provide simple setup using the
223
329
  1. Create a subscriptions server, using the ws-server cleanup function in your server lifecycle.
224
330
 
225
331
  The `useSubscriptionsServer` function sets up:
226
-
227
332
  - Auth token validation as part of establishing (or rejecting) the connection (behaviour defined by `verifyToken` and `requireAuth` args)
228
333
  - GraphQL context creation
229
334
  - Logging from the server `onConnect`, `onDisconnect`, `onOperation`, `onNext` and `onError` callbacks
package/index.d.ts CHANGED
@@ -3,5 +3,6 @@ export * from './logging';
3
3
  export * from './request-info';
4
4
  export * from './schema-util';
5
5
  export * from './type-utils';
6
+ export * from './unauthorised';
6
7
  export * from './User';
7
8
  export * from './utils';
package/index.js CHANGED
@@ -4,6 +4,7 @@ var context = require('./context.js');
4
4
  var logging = require('./logging.js');
5
5
  var requestInfo = require('./request-info.js');
6
6
  var schemaUtil = require('./schema-util.js');
7
+ var unauthorised = require('./unauthorised.js');
7
8
  var User = require('./User.js');
8
9
  var utils = require('./utils.js');
9
10
 
@@ -18,6 +19,9 @@ exports.buildConnectRequestInfo = requestInfo.buildConnectRequestInfo;
18
19
  exports.connectRequestBaseUrl = requestInfo.connectRequestBaseUrl;
19
20
  exports.requestBaseUrl = requestInfo.requestBaseUrl;
20
21
  exports.requireExplicitResolversForScalars = schemaUtil.requireExplicitResolversForScalars;
22
+ exports.permissionInvariant = unauthorised.permissionInvariant;
23
+ exports.throwUnauthorised = unauthorised.throwUnauthorised;
24
+ exports.unauthorisedError = unauthorised.unauthorisedError;
21
25
  exports.User = User.User;
22
26
  exports.isIntrospectionQuery = utils.isIntrospectionQuery;
23
27
  exports.isNil = utils.isNil;
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/index.mjs CHANGED
@@ -2,6 +2,7 @@ export { createContextFactory, defaultCreateUser } from './context.mjs';
2
2
  export { logGraphQLOperation, logSubscriptionOperation } from './logging.mjs';
3
3
  export { buildBaseRequestInfo, buildConnectRequestInfo, connectRequestBaseUrl, requestBaseUrl } from './request-info.mjs';
4
4
  export { requireExplicitResolversForScalars } from './schema-util.mjs';
5
+ export { permissionInvariant, throwUnauthorised, unauthorisedError } from './unauthorised.mjs';
5
6
  export { User } from './User.mjs';
6
7
  export { isIntrospectionQuery, isNil, isNotNil } from './utils.mjs';
7
8
  //# sourceMappingURL=index.mjs.map
package/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "**"
5
5
  ],
6
6
  "name": "@makerx/graphql-core",
7
- "version": "3.0.0-beta.1",
7
+ "version": "3.0.0-beta.3",
8
8
  "private": false,
9
9
  "description": "A set of core GraphQL utilities that MakerX uses to build GraphQL APIs",
10
10
  "author": "MakerX",
@@ -20,18 +20,19 @@
20
20
  "type": "git",
21
21
  "url": "git+https://github.com/MakerXStudio/graphql-core.git"
22
22
  },
23
- "dependencies": {
24
- "@makerx/node-common": "^1.5.0"
25
- },
26
23
  "peerDependencies": {
24
+ "@makerx/node-common": ">=1",
27
25
  "es-toolkit": ">=1",
28
- "express": "*",
29
- "graphql": "*",
30
- "graphql-shield": "*",
31
- "graphql-ws": "*",
32
- "ws": "*"
26
+ "express": ">=4",
27
+ "graphql": ">=16",
28
+ "graphql-shield": ">=7",
29
+ "graphql-ws": ">=5",
30
+ "ws": ">=8"
33
31
  },
34
32
  "peerDependenciesMeta": {
33
+ "graphql-shield": {
34
+ "optional": true
35
+ },
35
36
  "graphql-ws": {
36
37
  "optional": true
37
38
  },
package/shield.js CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  var graphqlShield = require('graphql-shield');
4
4
 
5
+ /**
6
+ * Thin typed wrapper around graphql-shield's `rule(...)` for building ad-hoc shield rules.
7
+ *
8
+ * Lets you pass a plain predicate (sync or async, or returning an `Error`) and get back a
9
+ * {@link ShieldRule} with the `parent`, `args`, and `ctx` arguments typed to your generics —
10
+ * graphql-shield's native `rule` types these as `any`.
11
+ *
12
+ * Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on
13
+ * `ctx` (e.g. the current user), so it can be reused across fields in the same request.
14
+ *
15
+ * @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.
16
+ * @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.
17
+ *
18
+ * Usage:
19
+ *
20
+ * const isOwner = createRule<GraphQLContext, { ownerId: string }>(
21
+ * (parent, _, ctx) => parent.ownerId === ctx.user?.id,
22
+ * )
23
+ */
5
24
  const createRule = (logic, cache = 'strict') => graphqlShield.rule({ cache })(async (parent, args, ctx) => logic(parent, args, ctx));
6
25
  /**
7
26
  * Combines a given rule with all existing defined rules using the provided combinator
@@ -24,30 +43,88 @@ function combineRuleWithAll(schema, rule, combinator) {
24
43
  /**
25
44
  * Creates graphql shield middleware making use of generics to type the definition of the rules.
26
45
  * @param schema A mapping of schema objects to the shield rules that define the access to that object
27
- * @param fallbackRule A fallback rule to apply to any object or property which doesn't have a defined rule.
46
+ * @param options Shield options forwarded to `shield`. Defaults `allowExternalErrors` to `true`.
28
47
  *
29
48
  * Usage:
30
49
  * import { Resolvers } from '../generated/types'
31
50
  *
51
+ * const isCoordinator = hasRoleRule(UserRoles.Coordinator)
52
+ * const isSystemAdmin = hasRoleRule(UserRoles.SystemAdmin)
53
+ * const isQualityAssurance = hasRoleRule(UserRoles.QualityAssurance)
54
+ *
55
+ * const isAuthorisedUser = or(isCoordinator, isSystemAdmin, isQualityAssurance)
56
+ * const isDataAuthor = or(isCoordinator, isSystemAdmin)
57
+ *
32
58
  * const shieldSchema = createShieldSchema<Resolvers>({
33
59
  * Query: {
34
- * getThings: allow,
35
- * dontGetThings: deny,
36
- * }
37
- * })
38
- *
39
- * let schema = makeExecutableSchema({...})
60
+ * '*': isAuthorisedUser,
61
+ * user: isCoordinator,
62
+ * findUsers: isCoordinator,
63
+ * },
64
+ * Mutation: {
65
+ * '*': isDataAuthor,
66
+ * createUser: isCoordinator,
67
+ * },
68
+ * // specific rules for mutations, types, fields...
69
+ * }, { fallbackRule: isAuthorisedUser, fallbackError: unauthorisedError()})
40
70
  *
41
- * schema = applyMiddleware(schema, shieldSchema)
71
+ * const unprotected = makeExecutableSchema({...})
72
+ * return applyMiddleware(unprotected, shieldSchema)
42
73
  */
43
- function createShieldSchema(schema, fallbackRule = graphqlShield.allow) {
74
+ function createShieldSchema(schema, options) {
44
75
  return graphqlShield.shield(schema, {
45
76
  allowExternalErrors: true,
46
- fallbackRule: fallbackRule,
77
+ ...options,
47
78
  });
48
79
  }
80
+ /**
81
+ * Shield rule that passes when the current user has the given role.
82
+ * @param role The role the user must have on `ctx.user.roles`.
83
+ * @param ruleName Optional override for the rule's cache key. Defaults to `hasRole-${role}`.
84
+ */
85
+ function hasRoleRule(role, ruleName) {
86
+ return graphqlShield.rule(ruleName ?? `hasRole-${role}`, { cache: 'contextual' })((_, __, { user }) => user?.roles.includes(role) === true);
87
+ }
88
+ /**
89
+ * Shield rule that passes when the current user has at least one of the given roles.
90
+ * Rule name is derived from the roles; use {@link hasAnyRoleRuleWithName} to supply your own.
91
+ * @param roles The set of roles, any of which satisfies the rule.
92
+ */
93
+ function hasAnyRoleRule(...roles) {
94
+ return hasAnyRoleRuleWithName(`hasAnyRole-${roles.join(',')}`, ...roles);
95
+ }
96
+ /**
97
+ * Shield rule that passes when the current user has at least one of the given roles, with an explicit rule name.
98
+ * Prefer this over {@link hasAnyRoleRule} when the role list is large or dynamic and you want a stable cache key.
99
+ * @param ruleName The name (and cache key) for the rule.
100
+ * @param roles The set of roles, any of which satisfies the rule.
101
+ */
102
+ function hasAnyRoleRuleWithName(ruleName, ...roles) {
103
+ return graphqlShield.rule(ruleName, { cache: 'contextual' })((_, __, { user }) => {
104
+ return user?.roles.some((role) => roles.includes(role)) === true;
105
+ });
106
+ }
107
+ /**
108
+ * Shield rule that passes when the current user has the given scope.
109
+ * @param scope The scope the user must have on `ctx.user.scopes`.
110
+ */
111
+ function hasScopeRule(scope) {
112
+ return graphqlShield.rule(`hasScope-${scope}`, { cache: 'contextual' })((_, __, { user }) => user?.scopes.includes(scope) === true);
113
+ }
114
+ /**
115
+ * Shield rule that passes when the current user has at least one of the given scopes.
116
+ * @param scopes The set of scopes, any of which satisfies the rule.
117
+ */
118
+ function hasAnyScopeRule(...scopes) {
119
+ return graphqlShield.rule(`hasAnyScope-${scopes.join(',')}`, { cache: 'contextual' })((_, __, { user }) => user?.scopes.some((scope) => scopes.includes(scope)) === true);
120
+ }
49
121
 
50
122
  exports.combineRuleWithAll = combineRuleWithAll;
51
123
  exports.createRule = createRule;
52
124
  exports.createShieldSchema = createShieldSchema;
125
+ exports.hasAnyRoleRule = hasAnyRoleRule;
126
+ exports.hasAnyRoleRuleWithName = hasAnyRoleRuleWithName;
127
+ exports.hasAnyScopeRule = hasAnyScopeRule;
128
+ exports.hasRoleRule = hasRoleRule;
129
+ exports.hasScopeRule = hasScopeRule;
53
130
  //# sourceMappingURL=shield.js.map
package/shield.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"shield.js","sources":["../src/shield.ts"],"sourcesContent":["import type { and, chain, IRules, or, race } from 'graphql-shield'\nimport { allow, rule, shield } from 'graphql-shield'\nimport type { Primitive } from './type-utils'\n\ntype RuleCombinator = typeof chain | typeof race | typeof or | typeof and\n// For whatever reason, graphql-shield doesn't export this type, but we can extract if from\n// what it does export.\nexport type ShieldRule = ReturnType<(typeof allow)['getRules']>[number]\nexport const createRule = <TContext, TParent = unknown, TArgs = unknown>(\n logic: (parent: TParent, args: TArgs, ctx: TContext) => boolean | Promise<boolean> | Error,\n cache: 'contextual' | 'strict' = 'strict',\n): ShieldRule => rule({ cache })(async (parent, args, ctx) => logic(parent, args, ctx))\n\n/**\n * Combines a given rule with all existing defined rules using the provided combinator\n * @param schema The existing shield schema with rules\n * @param rule The rule to be combined with existing rules\n * @param combinator The combinator to use. Eg. and | or | chain | race\n *\n * Usage:\n *\n * const updatedSchema = combineRuleWithAll(shieldSchema, rules.accountNotBlocked, chain)\n */\nexport function combineRuleWithAll<T>(schema: ShieldSchema<T>, rule: ShieldRule, combinator: RuleCombinator): ShieldSchema<T> {\n return Object.fromEntries(\n Object.entries(schema).map(([key, value]) => {\n if (value.constructor.name !== 'Object') {\n return [key, combinator(rule, value)]\n }\n return [key, combineRuleWithAll(value, rule, combinator)]\n }),\n ) as ShieldSchema<T>\n}\n\n/**\n * Creates graphql shield middleware making use of generics to type the definition of the rules.\n * @param schema A mapping of schema objects to the shield rules that define the access to that object\n * @param fallbackRule A fallback rule to apply to any object or property which doesn't have a defined rule.\n *\n * Usage:\n * import { Resolvers } from '../generated/types'\n *\n * const shieldSchema = createShieldSchema<Resolvers>({\n * Query: {\n * getThings: allow,\n * dontGetThings: deny,\n * }\n * })\n *\n * let schema = makeExecutableSchema({...})\n *\n * schema = applyMiddleware(schema, shieldSchema)\n */\nexport function createShieldSchema<TRootResolvers>(schema: ShieldSchema<TRootResolvers>, fallbackRule: ShieldRule = allow) {\n return shield(schema as IRules, {\n allowExternalErrors: true,\n fallbackRule: fallbackRule,\n })\n}\n\nexport type ShieldSchema<TResolver> = TResolver extends Primitive\n ? ShieldRule\n : {\n [key in keyof TResolver]?: ShieldSchema<TResolver[key]> | ShieldRule\n } & {\n '*'?: ShieldRule\n }\n"],"names":["rule","allow","shield"],"mappings":";;;;AAQO,MAAM,UAAU,GAAG,CACxB,KAA0F,EAC1F,KAAA,GAAiC,QAAQ,KAC1BA,kBAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC;AAEtF;;;;;;;;;AASG;SACa,kBAAkB,CAAI,MAAuB,EAAE,IAAgB,EAAE,UAA0B,EAAA;IACzG,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAI;QAC1C,IAAI,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ,EAAE;YACvC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC;AACA,QAAA,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAC3D,CAAC,CAAC,CACgB;AACtB;AAEA;;;;;;;;;;;;;;;;;;AAkBG;SACa,kBAAkB,CAAiB,MAAoC,EAAE,eAA2BC,mBAAK,EAAA;IACvH,OAAOC,oBAAM,CAAC,MAAgB,EAAE;AAC9B,QAAA,mBAAmB,EAAE,IAAI;AACzB,QAAA,YAAY,EAAE,YAAY;AAC3B,KAAA,CAAC;AACJ;;;;;;"}
1
+ {"version":3,"file":"shield.js","sources":["../src/shield.ts"],"sourcesContent":["import type { allow, and, chain, IRules, or, race } from 'graphql-shield'\nimport { rule, shield } from 'graphql-shield'\nimport type { GraphQLContext } from './context'\nimport type { Primitive } from './type-utils'\n\nexport type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>\n\ntype RuleCombinator = typeof chain | typeof race | typeof or | typeof and\n// For whatever reason, graphql-shield doesn't export this type, but we can extract if from\n// what it does export.\nexport type ShieldRule = ReturnType<(typeof allow)['getRules']>[number]\n\n/**\n * Thin typed wrapper around graphql-shield's `rule(...)` for building ad-hoc shield rules.\n *\n * Lets you pass a plain predicate (sync or async, or returning an `Error`) and get back a\n * {@link ShieldRule} with the `parent`, `args`, and `ctx` arguments typed to your generics —\n * graphql-shield's native `rule` types these as `any`.\n *\n * Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on\n * `ctx` (e.g. the current user), so it can be reused across fields in the same request.\n *\n * @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.\n * @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.\n *\n * Usage:\n *\n * const isOwner = createRule<GraphQLContext, { ownerId: string }>(\n * (parent, _, ctx) => parent.ownerId === ctx.user?.id,\n * )\n */\nexport const createRule = <TContext, TParent = unknown, TArgs = unknown>(\n logic: (parent: TParent, args: TArgs, ctx: TContext) => boolean | Promise<boolean> | Error,\n cache: 'contextual' | 'strict' = 'strict',\n): ShieldRule => rule({ cache })(async (parent, args, ctx) => logic(parent, args, ctx))\n\n/**\n * Combines a given rule with all existing defined rules using the provided combinator\n * @param schema The existing shield schema with rules\n * @param rule The rule to be combined with existing rules\n * @param combinator The combinator to use. Eg. and | or | chain | race\n *\n * Usage:\n *\n * const updatedSchema = combineRuleWithAll(shieldSchema, rules.accountNotBlocked, chain)\n */\nexport function combineRuleWithAll<T>(schema: ShieldSchema<T>, rule: ShieldRule, combinator: RuleCombinator): ShieldSchema<T> {\n return Object.fromEntries(\n Object.entries(schema).map(([key, value]) => {\n if (value.constructor.name !== 'Object') {\n return [key, combinator(rule, value)]\n }\n return [key, combineRuleWithAll(value, rule, combinator)]\n }),\n ) as ShieldSchema<T>\n}\n\n/**\n * Creates graphql shield middleware making use of generics to type the definition of the rules.\n * @param schema A mapping of schema objects to the shield rules that define the access to that object\n * @param options Shield options forwarded to `shield`. Defaults `allowExternalErrors` to `true`.\n *\n * Usage:\n * import { Resolvers } from '../generated/types'\n *\n * const isCoordinator = hasRoleRule(UserRoles.Coordinator)\n * const isSystemAdmin = hasRoleRule(UserRoles.SystemAdmin)\n * const isQualityAssurance = hasRoleRule(UserRoles.QualityAssurance)\n *\n * const isAuthorisedUser = or(isCoordinator, isSystemAdmin, isQualityAssurance)\n * const isDataAuthor = or(isCoordinator, isSystemAdmin)\n *\n * const shieldSchema = createShieldSchema<Resolvers>({\n * Query: {\n * '*': isAuthorisedUser,\n * user: isCoordinator,\n * findUsers: isCoordinator,\n * },\n * Mutation: {\n * '*': isDataAuthor,\n * createUser: isCoordinator,\n * },\n * // specific rules for mutations, types, fields...\n * }, { fallbackRule: isAuthorisedUser, fallbackError: unauthorisedError()})\n *\n * const unprotected = makeExecutableSchema({...})\n * return applyMiddleware(unprotected, shieldSchema)\n */\nexport function createShieldSchema<TRootResolvers>(schema: ShieldSchema<TRootResolvers>, options?: ShieldOptions) {\n return shield(schema as IRules, {\n allowExternalErrors: true,\n ...options,\n })\n}\n\n/**\n * A shield rule tree typed against a resolver map (typically your generated `Resolvers` type).\n *\n * Mirrors the shape of `TResolver` so each type/field can be assigned a {@link ShieldRule}.\n * Object levels may also include a `'*'` key — a fallback rule applied to any field at that\n * level that doesn't have an explicit rule. Leaf (primitive) positions collapse to a single\n * {@link ShieldRule}.\n *\n * Usage:\n *\n * const schema: ShieldSchema<Resolvers> = {\n * Query: {\n * '*': isAuthorisedUser,\n * publicThing: allow,\n * },\n * Mutation: { createUser: isCoordinator },\n * }\n */\nexport type ShieldSchema<TResolver> = TResolver extends Primitive\n ? ShieldRule\n : {\n [key in keyof TResolver]?: ShieldSchema<TResolver[key]> | ShieldRule\n } & {\n '*'?: ShieldRule\n }\n\n/**\n * Shield rule that passes when the current user has the given role.\n * @param role The role the user must have on `ctx.user.roles`.\n * @param ruleName Optional override for the rule's cache key. Defaults to `hasRole-${role}`.\n */\nexport function hasRoleRule(role: string, ruleName?: string) {\n return rule(ruleName ?? `hasRole-${role}`, { cache: 'contextual' })(\n (_, __, { user }: GraphQLContext) => user?.roles.includes(role) === true,\n )\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given roles.\n * Rule name is derived from the roles; use {@link hasAnyRoleRuleWithName} to supply your own.\n * @param roles The set of roles, any of which satisfies the rule.\n */\nexport function hasAnyRoleRule(...roles: string[]) {\n return hasAnyRoleRuleWithName(`hasAnyRole-${roles.join(',')}`, ...roles)\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given roles, with an explicit rule name.\n * Prefer this over {@link hasAnyRoleRule} when the role list is large or dynamic and you want a stable cache key.\n * @param ruleName The name (and cache key) for the rule.\n * @param roles The set of roles, any of which satisfies the rule.\n */\nexport function hasAnyRoleRuleWithName(ruleName: string, ...roles: string[]) {\n return rule(ruleName, { cache: 'contextual' })((_, __, { user }: GraphQLContext) => {\n return user?.roles.some((role: string) => roles.includes(role)) === true\n })\n}\n\n/**\n * Shield rule that passes when the current user has the given scope.\n * @param scope The scope the user must have on `ctx.user.scopes`.\n */\nexport function hasScopeRule(scope: string) {\n return rule(`hasScope-${scope}`, { cache: 'contextual' })((_, __, { user }: GraphQLContext) => user?.scopes.includes(scope) === true)\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given scopes.\n * @param scopes The set of scopes, any of which satisfies the rule.\n */\nexport function hasAnyScopeRule(...scopes: string[]) {\n return rule(`hasAnyScope-${scopes.join(',')}`, { cache: 'contextual' })(\n (_, __, { user }: GraphQLContext) => user?.scopes.some((scope: string) => scopes.includes(scope)) === true,\n )\n}\n"],"names":["rule","shield"],"mappings":"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"}
package/shield.mjs CHANGED
@@ -1,5 +1,24 @@
1
- import { allow, rule, shield } from 'graphql-shield';
1
+ import { rule, shield } from 'graphql-shield';
2
2
 
3
+ /**
4
+ * Thin typed wrapper around graphql-shield's `rule(...)` for building ad-hoc shield rules.
5
+ *
6
+ * Lets you pass a plain predicate (sync or async, or returning an `Error`) and get back a
7
+ * {@link ShieldRule} with the `parent`, `args`, and `ctx` arguments typed to your generics —
8
+ * graphql-shield's native `rule` types these as `any`.
9
+ *
10
+ * Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on
11
+ * `ctx` (e.g. the current user), so it can be reused across fields in the same request.
12
+ *
13
+ * @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.
14
+ * @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.
15
+ *
16
+ * Usage:
17
+ *
18
+ * const isOwner = createRule<GraphQLContext, { ownerId: string }>(
19
+ * (parent, _, ctx) => parent.ownerId === ctx.user?.id,
20
+ * )
21
+ */
3
22
  const createRule = (logic, cache = 'strict') => rule({ cache })(async (parent, args, ctx) => logic(parent, args, ctx));
4
23
  /**
5
24
  * Combines a given rule with all existing defined rules using the provided combinator
@@ -22,28 +41,81 @@ function combineRuleWithAll(schema, rule, combinator) {
22
41
  /**
23
42
  * Creates graphql shield middleware making use of generics to type the definition of the rules.
24
43
  * @param schema A mapping of schema objects to the shield rules that define the access to that object
25
- * @param fallbackRule A fallback rule to apply to any object or property which doesn't have a defined rule.
44
+ * @param options Shield options forwarded to `shield`. Defaults `allowExternalErrors` to `true`.
26
45
  *
27
46
  * Usage:
28
47
  * import { Resolvers } from '../generated/types'
29
48
  *
49
+ * const isCoordinator = hasRoleRule(UserRoles.Coordinator)
50
+ * const isSystemAdmin = hasRoleRule(UserRoles.SystemAdmin)
51
+ * const isQualityAssurance = hasRoleRule(UserRoles.QualityAssurance)
52
+ *
53
+ * const isAuthorisedUser = or(isCoordinator, isSystemAdmin, isQualityAssurance)
54
+ * const isDataAuthor = or(isCoordinator, isSystemAdmin)
55
+ *
30
56
  * const shieldSchema = createShieldSchema<Resolvers>({
31
57
  * Query: {
32
- * getThings: allow,
33
- * dontGetThings: deny,
34
- * }
35
- * })
36
- *
37
- * let schema = makeExecutableSchema({...})
58
+ * '*': isAuthorisedUser,
59
+ * user: isCoordinator,
60
+ * findUsers: isCoordinator,
61
+ * },
62
+ * Mutation: {
63
+ * '*': isDataAuthor,
64
+ * createUser: isCoordinator,
65
+ * },
66
+ * // specific rules for mutations, types, fields...
67
+ * }, { fallbackRule: isAuthorisedUser, fallbackError: unauthorisedError()})
38
68
  *
39
- * schema = applyMiddleware(schema, shieldSchema)
69
+ * const unprotected = makeExecutableSchema({...})
70
+ * return applyMiddleware(unprotected, shieldSchema)
40
71
  */
41
- function createShieldSchema(schema, fallbackRule = allow) {
72
+ function createShieldSchema(schema, options) {
42
73
  return shield(schema, {
43
74
  allowExternalErrors: true,
44
- fallbackRule: fallbackRule,
75
+ ...options,
45
76
  });
46
77
  }
78
+ /**
79
+ * Shield rule that passes when the current user has the given role.
80
+ * @param role The role the user must have on `ctx.user.roles`.
81
+ * @param ruleName Optional override for the rule's cache key. Defaults to `hasRole-${role}`.
82
+ */
83
+ function hasRoleRule(role, ruleName) {
84
+ return rule(ruleName ?? `hasRole-${role}`, { cache: 'contextual' })((_, __, { user }) => user?.roles.includes(role) === true);
85
+ }
86
+ /**
87
+ * Shield rule that passes when the current user has at least one of the given roles.
88
+ * Rule name is derived from the roles; use {@link hasAnyRoleRuleWithName} to supply your own.
89
+ * @param roles The set of roles, any of which satisfies the rule.
90
+ */
91
+ function hasAnyRoleRule(...roles) {
92
+ return hasAnyRoleRuleWithName(`hasAnyRole-${roles.join(',')}`, ...roles);
93
+ }
94
+ /**
95
+ * Shield rule that passes when the current user has at least one of the given roles, with an explicit rule name.
96
+ * Prefer this over {@link hasAnyRoleRule} when the role list is large or dynamic and you want a stable cache key.
97
+ * @param ruleName The name (and cache key) for the rule.
98
+ * @param roles The set of roles, any of which satisfies the rule.
99
+ */
100
+ function hasAnyRoleRuleWithName(ruleName, ...roles) {
101
+ return rule(ruleName, { cache: 'contextual' })((_, __, { user }) => {
102
+ return user?.roles.some((role) => roles.includes(role)) === true;
103
+ });
104
+ }
105
+ /**
106
+ * Shield rule that passes when the current user has the given scope.
107
+ * @param scope The scope the user must have on `ctx.user.scopes`.
108
+ */
109
+ function hasScopeRule(scope) {
110
+ return rule(`hasScope-${scope}`, { cache: 'contextual' })((_, __, { user }) => user?.scopes.includes(scope) === true);
111
+ }
112
+ /**
113
+ * Shield rule that passes when the current user has at least one of the given scopes.
114
+ * @param scopes The set of scopes, any of which satisfies the rule.
115
+ */
116
+ function hasAnyScopeRule(...scopes) {
117
+ return rule(`hasAnyScope-${scopes.join(',')}`, { cache: 'contextual' })((_, __, { user }) => user?.scopes.some((scope) => scopes.includes(scope)) === true);
118
+ }
47
119
 
48
- export { combineRuleWithAll, createRule, createShieldSchema };
120
+ export { combineRuleWithAll, createRule, createShieldSchema, hasAnyRoleRule, hasAnyRoleRuleWithName, hasAnyScopeRule, hasRoleRule, hasScopeRule };
49
121
  //# sourceMappingURL=shield.mjs.map
package/shield.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"shield.mjs","sources":["../src/shield.ts"],"sourcesContent":["import type { and, chain, IRules, or, race } from 'graphql-shield'\nimport { allow, rule, shield } from 'graphql-shield'\nimport type { Primitive } from './type-utils'\n\ntype RuleCombinator = typeof chain | typeof race | typeof or | typeof and\n// For whatever reason, graphql-shield doesn't export this type, but we can extract if from\n// what it does export.\nexport type ShieldRule = ReturnType<(typeof allow)['getRules']>[number]\nexport const createRule = <TContext, TParent = unknown, TArgs = unknown>(\n logic: (parent: TParent, args: TArgs, ctx: TContext) => boolean | Promise<boolean> | Error,\n cache: 'contextual' | 'strict' = 'strict',\n): ShieldRule => rule({ cache })(async (parent, args, ctx) => logic(parent, args, ctx))\n\n/**\n * Combines a given rule with all existing defined rules using the provided combinator\n * @param schema The existing shield schema with rules\n * @param rule The rule to be combined with existing rules\n * @param combinator The combinator to use. Eg. and | or | chain | race\n *\n * Usage:\n *\n * const updatedSchema = combineRuleWithAll(shieldSchema, rules.accountNotBlocked, chain)\n */\nexport function combineRuleWithAll<T>(schema: ShieldSchema<T>, rule: ShieldRule, combinator: RuleCombinator): ShieldSchema<T> {\n return Object.fromEntries(\n Object.entries(schema).map(([key, value]) => {\n if (value.constructor.name !== 'Object') {\n return [key, combinator(rule, value)]\n }\n return [key, combineRuleWithAll(value, rule, combinator)]\n }),\n ) as ShieldSchema<T>\n}\n\n/**\n * Creates graphql shield middleware making use of generics to type the definition of the rules.\n * @param schema A mapping of schema objects to the shield rules that define the access to that object\n * @param fallbackRule A fallback rule to apply to any object or property which doesn't have a defined rule.\n *\n * Usage:\n * import { Resolvers } from '../generated/types'\n *\n * const shieldSchema = createShieldSchema<Resolvers>({\n * Query: {\n * getThings: allow,\n * dontGetThings: deny,\n * }\n * })\n *\n * let schema = makeExecutableSchema({...})\n *\n * schema = applyMiddleware(schema, shieldSchema)\n */\nexport function createShieldSchema<TRootResolvers>(schema: ShieldSchema<TRootResolvers>, fallbackRule: ShieldRule = allow) {\n return shield(schema as IRules, {\n allowExternalErrors: true,\n fallbackRule: fallbackRule,\n })\n}\n\nexport type ShieldSchema<TResolver> = TResolver extends Primitive\n ? ShieldRule\n : {\n [key in keyof TResolver]?: ShieldSchema<TResolver[key]> | ShieldRule\n } & {\n '*'?: ShieldRule\n }\n"],"names":[],"mappings":";;AAQO,MAAM,UAAU,GAAG,CACxB,KAA0F,EAC1F,KAAA,GAAiC,QAAQ,KAC1B,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC;AAEtF;;;;;;;;;AASG;SACa,kBAAkB,CAAI,MAAuB,EAAE,IAAgB,EAAE,UAA0B,EAAA;IACzG,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAI;QAC1C,IAAI,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ,EAAE;YACvC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC;AACA,QAAA,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAC3D,CAAC,CAAC,CACgB;AACtB;AAEA;;;;;;;;;;;;;;;;;;AAkBG;SACa,kBAAkB,CAAiB,MAAoC,EAAE,eAA2B,KAAK,EAAA;IACvH,OAAO,MAAM,CAAC,MAAgB,EAAE;AAC9B,QAAA,mBAAmB,EAAE,IAAI;AACzB,QAAA,YAAY,EAAE,YAAY;AAC3B,KAAA,CAAC;AACJ;;;;"}
1
+ {"version":3,"file":"shield.mjs","sources":["../src/shield.ts"],"sourcesContent":["import type { allow, and, chain, IRules, or, race } from 'graphql-shield'\nimport { rule, shield } from 'graphql-shield'\nimport type { GraphQLContext } from './context'\nimport type { Primitive } from './type-utils'\n\nexport type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>\n\ntype RuleCombinator = typeof chain | typeof race | typeof or | typeof and\n// For whatever reason, graphql-shield doesn't export this type, but we can extract if from\n// what it does export.\nexport type ShieldRule = ReturnType<(typeof allow)['getRules']>[number]\n\n/**\n * Thin typed wrapper around graphql-shield's `rule(...)` for building ad-hoc shield rules.\n *\n * Lets you pass a plain predicate (sync or async, or returning an `Error`) and get back a\n * {@link ShieldRule} with the `parent`, `args`, and `ctx` arguments typed to your generics —\n * graphql-shield's native `rule` types these as `any`.\n *\n * Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on\n * `ctx` (e.g. the current user), so it can be reused across fields in the same request.\n *\n * @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.\n * @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.\n *\n * Usage:\n *\n * const isOwner = createRule<GraphQLContext, { ownerId: string }>(\n * (parent, _, ctx) => parent.ownerId === ctx.user?.id,\n * )\n */\nexport const createRule = <TContext, TParent = unknown, TArgs = unknown>(\n logic: (parent: TParent, args: TArgs, ctx: TContext) => boolean | Promise<boolean> | Error,\n cache: 'contextual' | 'strict' = 'strict',\n): ShieldRule => rule({ cache })(async (parent, args, ctx) => logic(parent, args, ctx))\n\n/**\n * Combines a given rule with all existing defined rules using the provided combinator\n * @param schema The existing shield schema with rules\n * @param rule The rule to be combined with existing rules\n * @param combinator The combinator to use. Eg. and | or | chain | race\n *\n * Usage:\n *\n * const updatedSchema = combineRuleWithAll(shieldSchema, rules.accountNotBlocked, chain)\n */\nexport function combineRuleWithAll<T>(schema: ShieldSchema<T>, rule: ShieldRule, combinator: RuleCombinator): ShieldSchema<T> {\n return Object.fromEntries(\n Object.entries(schema).map(([key, value]) => {\n if (value.constructor.name !== 'Object') {\n return [key, combinator(rule, value)]\n }\n return [key, combineRuleWithAll(value, rule, combinator)]\n }),\n ) as ShieldSchema<T>\n}\n\n/**\n * Creates graphql shield middleware making use of generics to type the definition of the rules.\n * @param schema A mapping of schema objects to the shield rules that define the access to that object\n * @param options Shield options forwarded to `shield`. Defaults `allowExternalErrors` to `true`.\n *\n * Usage:\n * import { Resolvers } from '../generated/types'\n *\n * const isCoordinator = hasRoleRule(UserRoles.Coordinator)\n * const isSystemAdmin = hasRoleRule(UserRoles.SystemAdmin)\n * const isQualityAssurance = hasRoleRule(UserRoles.QualityAssurance)\n *\n * const isAuthorisedUser = or(isCoordinator, isSystemAdmin, isQualityAssurance)\n * const isDataAuthor = or(isCoordinator, isSystemAdmin)\n *\n * const shieldSchema = createShieldSchema<Resolvers>({\n * Query: {\n * '*': isAuthorisedUser,\n * user: isCoordinator,\n * findUsers: isCoordinator,\n * },\n * Mutation: {\n * '*': isDataAuthor,\n * createUser: isCoordinator,\n * },\n * // specific rules for mutations, types, fields...\n * }, { fallbackRule: isAuthorisedUser, fallbackError: unauthorisedError()})\n *\n * const unprotected = makeExecutableSchema({...})\n * return applyMiddleware(unprotected, shieldSchema)\n */\nexport function createShieldSchema<TRootResolvers>(schema: ShieldSchema<TRootResolvers>, options?: ShieldOptions) {\n return shield(schema as IRules, {\n allowExternalErrors: true,\n ...options,\n })\n}\n\n/**\n * A shield rule tree typed against a resolver map (typically your generated `Resolvers` type).\n *\n * Mirrors the shape of `TResolver` so each type/field can be assigned a {@link ShieldRule}.\n * Object levels may also include a `'*'` key — a fallback rule applied to any field at that\n * level that doesn't have an explicit rule. Leaf (primitive) positions collapse to a single\n * {@link ShieldRule}.\n *\n * Usage:\n *\n * const schema: ShieldSchema<Resolvers> = {\n * Query: {\n * '*': isAuthorisedUser,\n * publicThing: allow,\n * },\n * Mutation: { createUser: isCoordinator },\n * }\n */\nexport type ShieldSchema<TResolver> = TResolver extends Primitive\n ? ShieldRule\n : {\n [key in keyof TResolver]?: ShieldSchema<TResolver[key]> | ShieldRule\n } & {\n '*'?: ShieldRule\n }\n\n/**\n * Shield rule that passes when the current user has the given role.\n * @param role The role the user must have on `ctx.user.roles`.\n * @param ruleName Optional override for the rule's cache key. Defaults to `hasRole-${role}`.\n */\nexport function hasRoleRule(role: string, ruleName?: string) {\n return rule(ruleName ?? `hasRole-${role}`, { cache: 'contextual' })(\n (_, __, { user }: GraphQLContext) => user?.roles.includes(role) === true,\n )\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given roles.\n * Rule name is derived from the roles; use {@link hasAnyRoleRuleWithName} to supply your own.\n * @param roles The set of roles, any of which satisfies the rule.\n */\nexport function hasAnyRoleRule(...roles: string[]) {\n return hasAnyRoleRuleWithName(`hasAnyRole-${roles.join(',')}`, ...roles)\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given roles, with an explicit rule name.\n * Prefer this over {@link hasAnyRoleRule} when the role list is large or dynamic and you want a stable cache key.\n * @param ruleName The name (and cache key) for the rule.\n * @param roles The set of roles, any of which satisfies the rule.\n */\nexport function hasAnyRoleRuleWithName(ruleName: string, ...roles: string[]) {\n return rule(ruleName, { cache: 'contextual' })((_, __, { user }: GraphQLContext) => {\n return user?.roles.some((role: string) => roles.includes(role)) === true\n })\n}\n\n/**\n * Shield rule that passes when the current user has the given scope.\n * @param scope The scope the user must have on `ctx.user.scopes`.\n */\nexport function hasScopeRule(scope: string) {\n return rule(`hasScope-${scope}`, { cache: 'contextual' })((_, __, { user }: GraphQLContext) => user?.scopes.includes(scope) === true)\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given scopes.\n * @param scopes The set of scopes, any of which satisfies the rule.\n */\nexport function hasAnyScopeRule(...scopes: string[]) {\n return rule(`hasAnyScope-${scopes.join(',')}`, { cache: 'contextual' })(\n (_, __, { user }: GraphQLContext) => user?.scopes.some((scope: string) => scopes.includes(scope)) === true,\n )\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { GraphQLError } from 'graphql';
2
+ /**
3
+ * Builds a `GraphQLError` representing an authorization failure.
4
+ *
5
+ * Sets the Apollo-standard `FORBIDDEN` extension code and an HTTP 403 status, so clients and
6
+ * gateways that inspect `extensions.code` / `extensions.http.status` see a consistent shape.
7
+ *
8
+ * @param message Error message shown to the client. Defaults to `'Not Authorized!'`.
9
+ */
10
+ export declare const unauthorisedError: (message?: string) => GraphQLError;
11
+ /**
12
+ * Throws an {@link unauthorisedError}. Return type is `never` so TypeScript narrows control flow past the call.
13
+ * @param message Optional override for the error message.
14
+ */
15
+ export declare function throwUnauthorised(message?: string): never;
16
+ /**
17
+ * Assertion helper: throws an {@link unauthorisedError} when `hasPermission` is false, otherwise narrows it to `true`.
18
+ *
19
+ * Use at the top of resolvers or services to fail fast on authorization checks while keeping
20
+ * the happy path unindented.
21
+ *
22
+ * @param hasPermission Boolean guard; the function returns normally only when this is `true`.
23
+ * @param message Optional override for the error message thrown on failure.
24
+ *
25
+ * Usage:
26
+ *
27
+ * permissionInvariant(ctx.user?.id === record.ownerId, 'Only the owner can edit this record')
28
+ * // past this line, TypeScript knows the condition held
29
+ */
30
+ export declare function permissionInvariant(hasPermission: boolean, message?: string): asserts hasPermission;
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ var graphql = require('graphql');
4
+
5
+ /**
6
+ * Builds a `GraphQLError` representing an authorization failure.
7
+ *
8
+ * Sets the Apollo-standard `FORBIDDEN` extension code and an HTTP 403 status, so clients and
9
+ * gateways that inspect `extensions.code` / `extensions.http.status` see a consistent shape.
10
+ *
11
+ * @param message Error message shown to the client. Defaults to `'Not Authorized!'`.
12
+ */
13
+ const unauthorisedError = (message = 'Not Authorized!') => new graphql.GraphQLError(message, {
14
+ extensions: { code: 'FORBIDDEN', http: { status: 403 } },
15
+ });
16
+ /**
17
+ * Throws an {@link unauthorisedError}. Return type is `never` so TypeScript narrows control flow past the call.
18
+ * @param message Optional override for the error message.
19
+ */
20
+ function throwUnauthorised(message) {
21
+ throw unauthorisedError(message);
22
+ }
23
+ /**
24
+ * Assertion helper: throws an {@link unauthorisedError} when `hasPermission` is false, otherwise narrows it to `true`.
25
+ *
26
+ * Use at the top of resolvers or services to fail fast on authorization checks while keeping
27
+ * the happy path unindented.
28
+ *
29
+ * @param hasPermission Boolean guard; the function returns normally only when this is `true`.
30
+ * @param message Optional override for the error message thrown on failure.
31
+ *
32
+ * Usage:
33
+ *
34
+ * permissionInvariant(ctx.user?.id === record.ownerId, 'Only the owner can edit this record')
35
+ * // past this line, TypeScript knows the condition held
36
+ */
37
+ function permissionInvariant(hasPermission, message) {
38
+ if (!hasPermission)
39
+ throwUnauthorised(message);
40
+ }
41
+
42
+ exports.permissionInvariant = permissionInvariant;
43
+ exports.throwUnauthorised = throwUnauthorised;
44
+ exports.unauthorisedError = unauthorisedError;
45
+ //# sourceMappingURL=unauthorised.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unauthorised.js","sources":["../src/unauthorised.ts"],"sourcesContent":["import { GraphQLError } from 'graphql'\n\n/**\n * Builds a `GraphQLError` representing an authorization failure.\n *\n * Sets the Apollo-standard `FORBIDDEN` extension code and an HTTP 403 status, so clients and\n * gateways that inspect `extensions.code` / `extensions.http.status` see a consistent shape.\n *\n * @param message Error message shown to the client. Defaults to `'Not Authorized!'`.\n */\nexport const unauthorisedError = (message: string = 'Not Authorized!') =>\n new GraphQLError(message, {\n extensions: { code: 'FORBIDDEN', http: { status: 403 } },\n })\n\n/**\n * Throws an {@link unauthorisedError}. Return type is `never` so TypeScript narrows control flow past the call.\n * @param message Optional override for the error message.\n */\nexport function throwUnauthorised(message?: string): never {\n throw unauthorisedError(message)\n}\n\n/**\n * Assertion helper: throws an {@link unauthorisedError} when `hasPermission` is false, otherwise narrows it to `true`.\n *\n * Use at the top of resolvers or services to fail fast on authorization checks while keeping\n * the happy path unindented.\n *\n * @param hasPermission Boolean guard; the function returns normally only when this is `true`.\n * @param message Optional override for the error message thrown on failure.\n *\n * Usage:\n *\n * permissionInvariant(ctx.user?.id === record.ownerId, 'Only the owner can edit this record')\n * // past this line, TypeScript knows the condition held\n */\nexport function permissionInvariant(hasPermission: boolean, message?: string): asserts hasPermission {\n if (!hasPermission) throwUnauthorised(message)\n}\n"],"names":["GraphQLError"],"mappings":";;;;AAEA;;;;;;;AAOG;AACI,MAAM,iBAAiB,GAAG,CAAC,OAAA,GAAkB,iBAAiB,KACnE,IAAIA,oBAAY,CAAC,OAAO,EAAE;AACxB,IAAA,UAAU,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;AACzD,CAAA;AAEH;;;AAGG;AACG,SAAU,iBAAiB,CAAC,OAAgB,EAAA;AAChD,IAAA,MAAM,iBAAiB,CAAC,OAAO,CAAC;AAClC;AAEA;;;;;;;;;;;;;AAaG;AACG,SAAU,mBAAmB,CAAC,aAAsB,EAAE,OAAgB,EAAA;AAC1E,IAAA,IAAI,CAAC,aAAa;QAAE,iBAAiB,CAAC,OAAO,CAAC;AAChD;;;;;;"}
@@ -0,0 +1,41 @@
1
+ import { GraphQLError } from 'graphql';
2
+
3
+ /**
4
+ * Builds a `GraphQLError` representing an authorization failure.
5
+ *
6
+ * Sets the Apollo-standard `FORBIDDEN` extension code and an HTTP 403 status, so clients and
7
+ * gateways that inspect `extensions.code` / `extensions.http.status` see a consistent shape.
8
+ *
9
+ * @param message Error message shown to the client. Defaults to `'Not Authorized!'`.
10
+ */
11
+ const unauthorisedError = (message = 'Not Authorized!') => new GraphQLError(message, {
12
+ extensions: { code: 'FORBIDDEN', http: { status: 403 } },
13
+ });
14
+ /**
15
+ * Throws an {@link unauthorisedError}. Return type is `never` so TypeScript narrows control flow past the call.
16
+ * @param message Optional override for the error message.
17
+ */
18
+ function throwUnauthorised(message) {
19
+ throw unauthorisedError(message);
20
+ }
21
+ /**
22
+ * Assertion helper: throws an {@link unauthorisedError} when `hasPermission` is false, otherwise narrows it to `true`.
23
+ *
24
+ * Use at the top of resolvers or services to fail fast on authorization checks while keeping
25
+ * the happy path unindented.
26
+ *
27
+ * @param hasPermission Boolean guard; the function returns normally only when this is `true`.
28
+ * @param message Optional override for the error message thrown on failure.
29
+ *
30
+ * Usage:
31
+ *
32
+ * permissionInvariant(ctx.user?.id === record.ownerId, 'Only the owner can edit this record')
33
+ * // past this line, TypeScript knows the condition held
34
+ */
35
+ function permissionInvariant(hasPermission, message) {
36
+ if (!hasPermission)
37
+ throwUnauthorised(message);
38
+ }
39
+
40
+ export { permissionInvariant, throwUnauthorised, unauthorisedError };
41
+ //# sourceMappingURL=unauthorised.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unauthorised.mjs","sources":["../src/unauthorised.ts"],"sourcesContent":["import { GraphQLError } from 'graphql'\n\n/**\n * Builds a `GraphQLError` representing an authorization failure.\n *\n * Sets the Apollo-standard `FORBIDDEN` extension code and an HTTP 403 status, so clients and\n * gateways that inspect `extensions.code` / `extensions.http.status` see a consistent shape.\n *\n * @param message Error message shown to the client. Defaults to `'Not Authorized!'`.\n */\nexport const unauthorisedError = (message: string = 'Not Authorized!') =>\n new GraphQLError(message, {\n extensions: { code: 'FORBIDDEN', http: { status: 403 } },\n })\n\n/**\n * Throws an {@link unauthorisedError}. Return type is `never` so TypeScript narrows control flow past the call.\n * @param message Optional override for the error message.\n */\nexport function throwUnauthorised(message?: string): never {\n throw unauthorisedError(message)\n}\n\n/**\n * Assertion helper: throws an {@link unauthorisedError} when `hasPermission` is false, otherwise narrows it to `true`.\n *\n * Use at the top of resolvers or services to fail fast on authorization checks while keeping\n * the happy path unindented.\n *\n * @param hasPermission Boolean guard; the function returns normally only when this is `true`.\n * @param message Optional override for the error message thrown on failure.\n *\n * Usage:\n *\n * permissionInvariant(ctx.user?.id === record.ownerId, 'Only the owner can edit this record')\n * // past this line, TypeScript knows the condition held\n */\nexport function permissionInvariant(hasPermission: boolean, message?: string): asserts hasPermission {\n if (!hasPermission) throwUnauthorised(message)\n}\n"],"names":[],"mappings":";;AAEA;;;;;;;AAOG;AACI,MAAM,iBAAiB,GAAG,CAAC,OAAA,GAAkB,iBAAiB,KACnE,IAAI,YAAY,CAAC,OAAO,EAAE;AACxB,IAAA,UAAU,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;AACzD,CAAA;AAEH;;;AAGG;AACG,SAAU,iBAAiB,CAAC,OAAgB,EAAA;AAChD,IAAA,MAAM,iBAAiB,CAAC,OAAO,CAAC;AAClC;AAEA;;;;;;;;;;;;;AAaG;AACG,SAAU,mBAAmB,CAAC,aAAsB,EAAE,OAAgB,EAAA;AAC1E,IAAA,IAAI,CAAC,aAAa;QAAE,iBAAiB,CAAC,OAAO,CAAC;AAChD;;;;"}
package/shield.d.ts DELETED
@@ -1,43 +0,0 @@
1
- import type { and, chain, or, race } from 'graphql-shield';
2
- import { allow } from 'graphql-shield';
3
- import type { Primitive } from './type-utils';
4
- type RuleCombinator = typeof chain | typeof race | typeof or | typeof and;
5
- export type ShieldRule = ReturnType<(typeof allow)['getRules']>[number];
6
- export declare const createRule: <TContext, TParent = unknown, TArgs = unknown>(logic: (parent: TParent, args: TArgs, ctx: TContext) => boolean | Promise<boolean> | Error, cache?: "contextual" | "strict") => ShieldRule;
7
- /**
8
- * Combines a given rule with all existing defined rules using the provided combinator
9
- * @param schema The existing shield schema with rules
10
- * @param rule The rule to be combined with existing rules
11
- * @param combinator The combinator to use. Eg. and | or | chain | race
12
- *
13
- * Usage:
14
- *
15
- * const updatedSchema = combineRuleWithAll(shieldSchema, rules.accountNotBlocked, chain)
16
- */
17
- export declare function combineRuleWithAll<T>(schema: ShieldSchema<T>, rule: ShieldRule, combinator: RuleCombinator): ShieldSchema<T>;
18
- /**
19
- * Creates graphql shield middleware making use of generics to type the definition of the rules.
20
- * @param schema A mapping of schema objects to the shield rules that define the access to that object
21
- * @param fallbackRule A fallback rule to apply to any object or property which doesn't have a defined rule.
22
- *
23
- * Usage:
24
- * import { Resolvers } from '../generated/types'
25
- *
26
- * const shieldSchema = createShieldSchema<Resolvers>({
27
- * Query: {
28
- * getThings: allow,
29
- * dontGetThings: deny,
30
- * }
31
- * })
32
- *
33
- * let schema = makeExecutableSchema({...})
34
- *
35
- * schema = applyMiddleware(schema, shieldSchema)
36
- */
37
- export declare function createShieldSchema<TRootResolvers>(schema: ShieldSchema<TRootResolvers>, fallbackRule?: ShieldRule): import("graphql-middleware").IMiddlewareGenerator<any, any, any>;
38
- export type ShieldSchema<TResolver> = TResolver extends Primitive ? ShieldRule : {
39
- [key in keyof TResolver]?: ShieldSchema<TResolver[key]> | ShieldRule;
40
- } & {
41
- '*'?: ShieldRule;
42
- };
43
- export {};