{"id":13485193,"url":"https://github.com/Sytten/nexus-shield","last_synced_at":"2025-03-27T17:30:51.615Z","repository":{"id":38109754,"uuid":"271561632","full_name":"Sytten/nexus-shield","owner":"Sytten","description":"🛡 Nexus plugin to ease the creation of the authorization layer","archived":false,"fork":false,"pushed_at":"2024-08-06T10:43:34.000Z","size":1415,"stargazers_count":93,"open_issues_count":14,"forks_count":6,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-25T02:40:42.214Z","etag":null,"topics":["authorization","graphql","nexus-schema","plugin"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sytten.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-11T14:03:32.000Z","updated_at":"2025-02-18T07:57:01.000Z","dependencies_parsed_at":"2023-11-14T03:25:26.923Z","dependency_job_id":"e013f4f5-b5d8-48c5-9459-50232ea7142e","html_url":"https://github.com/Sytten/nexus-shield","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sytten%2Fnexus-shield","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sytten%2Fnexus-shield/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sytten%2Fnexus-shield/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sytten%2Fnexus-shield/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sytten","download_url":"https://codeload.github.com/Sytten/nexus-shield/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245678901,"owners_count":20654738,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","graphql","nexus-schema","plugin"],"created_at":"2024-07-31T17:01:50.138Z","updated_at":"2025-03-27T17:30:51.591Z","avatar_url":"https://github.com/Sytten.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","🏗️ Plugins"],"sub_categories":["Authorization"],"readme":"# nexus-shield\n\n[![Github Actions](https://github.com/Sytten/nexus-shield/workflows/Release/badge.svg)](https://circleci.com/gh/maticzav/graphql-shield/tree/master)\n[![codecov](https://codecov.io/gh/Sytten/nexus-shield/branch/master/graph/badge.svg)](https://codecov.io/gh/Sytten/nexus-shield)\n[![npm version](https://badge.fury.io/js/nexus-shield.svg)](https://badge.fury.io/js/nexus-shield)\n\n## Help Wanted ⚠️\n\nIf you are a Typescript expert, I could use a hand on a lingering typing issue when a shield parameter is added to an objectType.\nPlease see the related [issue](https://github.com/Sytten/nexus-shield/issues/50) for details. Thanks!\n\n## Overview\n\nNexus Shield is a [nexus](https://github.com/graphql-nexus/nexus) plugin that helps you create an authorization layer for your application. It is a replacement for the provided authorization plugin. It is heavily inspired by [Graphql Shield](https://github.com/maticzav/graphql-shield) and reuses most of its familiar ruling system. It takes full advantage of the type safety provided by nexus.\n\n## Install\n\n```bash\nnpm install --save nexus-shield\n\nOR\n\nyarn add nexus-shield\n```\n\n## Usage\n\n### Nexus configuration\n\nThe plugin first needs to be installed in nexus. This will add the new `shield` parameter. The plugin will work without any provided configuration, but it is recommended to provide one that is relevant to your application. The available parameters are:\n\n- `defaultError`: The error that is thrown if the access is denied. See the [errors section](#Error).\n- `defaultRule`: Rule that is used if none is specified for a field.\n- `hashFunction`: Function used to hash the input to provide [caching keys](#Caching).\n\nFor example, using an [Apollo server](https://www.apollographql.com/server/):\n\n```typescript\nimport { nexusShield, allow } from 'nexus-shield';\nimport { ForbiddenError } from 'apollo-server';\n\nconst schema = makeSchema({\n  // ... Rest of the configuration\n  plugins: [\n    nexusShield({\n      defaultError: new ForbiddenError('Not allowed'),\n      defaultRule: allow,\n    }),\n  ],\n});\n```\n\n#### Subscriptions configuration\n\n- When using subscriptions with a server that is not integrated directly into your \"main\" GraphQL server, you **must** make sure that you pass in a valid context.\n- This context should contain all the information needed to evaluate the rules. Ideally, it is the same as the context for your \"main\" server otherwise the typing won't reflect the data available to the rules.\n\nFor example, using [GraphQL-WS](https://github.com/enisdenjo/graphql-ws):\n\n```typescript\nuseServer(\n  {\n    schema,\n    context: (ctx, msg, args) =\u003e {\n      // That will return the same context that was passed when the\n      // server received the subscription request\n      return ctx;\n    },\n  },\n  wsServer\n);\n```\n\n### Styles\n\nTwo interface styles are provided for convenience: `Graphql-Shield` and `Nexus`.\n\n#### Graphql-Shield\n\n```typescript\nrule()((root, args, ctx) =\u003e {\n  return !!ctx.user;\n});\n```\n\n#### Nexus\n\n```typescript\nruleType({\n  resolve: (root, args, ctx) =\u003e {\n    return !!ctx.user;\n  },\n});\n```\n\n### Error\n\n- A rule needs to return a `boolean`, a `Promise\u003cboolean\u003e` or throw an `Error`.\n- Contrary to Graphql-shield, this plugin will **NOT** catch the errors you throw and will just pass them down to the next plugins and eventually to the server\n- If `false` is returned, the configured `defaultError` will be thrown by the plugin.\n\n```typescript\nimport { AuthenticationError } from 'apollo-server';\n\nconst isAuthenticated = ruleType({\n  resolve: (root, args, ctx) =\u003e {\n    const allowed = !!ctx.user;\n    if (!allowed) throw new AuthenticationError('Bearer token required');\n    return allowed;\n  },\n});\n```\n\n### Operators\n\nRules can be combined in a very flexible manner. The plugin provides the following operators:\n\n- `and`: Returns `true` if **all** rules return `true`\n- `or`: Returns `true` if **one** rule returns `true`\n- `not`: Inverts the result of a rule\n- `chain`: Same as `and`, but rules are executed in order\n- `race`: Same as `or`, but rules are executed in order\n- `deny`: Returns `false`\n- `allow`: Returns `true`\n\nSimple example:\n\n```typescript\nimport { chain, not, ruleType } from 'nexus-shield';\n\nconst hasScope = (scope: string) =\u003e {\n  return ruleType({\n    resolve: (root, args, ctx) =\u003e {\n      return ctx.user.permissions.includes(scope);\n    },\n  });\n};\n\nconst backlist = ruleType({\n  resolve: (root, args, ctx) =\u003e {\n    return ctx.user.token === 'some-token';\n  },\n});\n\nconst viewerIsAuthorized = chain(\n  isAuthenticated,\n  not(backlist),\n  hasScope('products:read')\n);\n```\n\n### Shield Parameter\n\nTo use a rule, it must be assigned to the `shield` parameter of a field:\n\n```typescript\nexport const Product = objectType({\n  name: 'Product',\n  definition(t) {\n    t.id('id');\n    t.string('prop', {\n      shield: ruleType({\n        resolve: (root, args, ctx) =\u003e {\n          return !!ctx.user;\n        },\n      }),\n    });\n  },\n});\n```\n\n### Type safety\n\nThis plugin will try its best to provide typing to the rules.\n\n- It is **preferable** to define rules directly in the `definition` to have access to the full typing of `root` and `args`.\n- The `ctx` is always typed if it was properly configured in nexus `makeSchema`.\n- If creating generic or partial rules, use the appropriate helpers (see below).\n\n```typescript\nexport type Context = {\n  user?: { id: string };\n};\n\nexport const Product = objectType({\n  name: 'Product',\n  definition(t) {\n    t.id('id');\n    t.string('ownerId');\n    t.string('prop', {\n      args: {\n        filter: stringArg({ nullable: false }),\n      },\n      shield: ruleType({\n        resolve: (root, args, ctx) =\u003e {\n          // root =\u003e { id: string }, args =\u003e { filter: string }, ctx =\u003e Context\n          return true;\n        },\n      }),\n    });\n  },\n});\n```\n\n#### Generic rules\n\n- Generic rules are rules that do not depend on the type of the `root` or `args`.\n- The wrapper `generic` is provided for this purpose. It will wrap your rule in a generic function.\n\n```typescript\nconst isAuthenticated = generic(\n  ruleType({\n    resolve: (root, args, ctx) =\u003e {\n      // Only ctx is typed\n      return !!ctx.user;\n    },\n  })\n);\n\n// Usage\nt.string('prop', {\n  shield: isAuthenticated(),\n});\n```\n\n#### Partial rules\n\n- Partial rules are rules that depend only on the type of the `root`.\n- The wrapper `partial` is provided for this purpose. It will wrap your rule in a generic function.\n\n```typescript\nconst viewerIsOwner = partial(\n  ruleType({\n    type: 'Product' // It is also possible to use the generic parameter of `partial`\n    resolve: (root, args, ctx) =\u003e {\n      // Both root and ctx are typed\n      return root.ownerId === ctx.user.id;\n    },\n  })\n);\n\n// Usage\nt.string('prop', {\n  shield: viewerIsOwner(),\n});\n```\n\n#### Combining rules\n\nIf you mix and match generic rules with partial rules, you will need to specify the type in the parent helper.\n\n```typescript\nconst viewerIsAuthorized = partial\u003c'Product'\u003e(\n  chain(isAuthenticated(), viewerIsOwner())\n);\n```\n\nHowever, if you specify it directly in the `shield` field, there is no need for a helper thus no need for a parameter.\n\n```typescript\nt.string('prop', {\n  shield: chain(isAuthenticated(), viewerIsOwner()),\n});\n```\n\n### Caching\n\n- The result of a rule can be cached to maximize performance. This is important when using generic or partial rules that require access to external data.\n- The caching is **always** scoped to the request\n\nThe plugin offers 3 levels of caching:\n\n- `NO_CACHE`: No caching is done (default)\n- `CONTEXTUAL`: Use when the rule only depends on the `ctx`\n- `STRICT`: Use when the rule depends on the `root` or `args`\n\nUsage:\n\n```typescript\nrule({ cache: ShieldCache.STRICT })((root, args, ctx) =\u003e {\n  return true;\n});\n\nruleType({\n  cache: ShieldCache.STRICT,\n  resolve: (root, args, ctx) =\u003e {\n    return !!ctx.user;\n  },\n});\n```\n\n### Known issues / limitations\n\n- Currently, the typing of the `shield` parameter on `objectType` doesn't work. Tracked by issue: https://github.com/Sytten/nexus-shield/issues/50\n\n- It is not possible to pass directly an `objectType` to the parameter `type` of a `ruleType`. Tracked by issue: https://github.com/graphql-nexus/schema/issues/451\n\n- The helpers are necessary to provide strong typing and avoid the propagation of `any`. See [this StackOverflow issue](https://stackoverflow.com/questions/62363077/combining-typescript-generics-with-any-without-losing-type/62435780#62435780) for more on the subject.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSytten%2Fnexus-shield","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSytten%2Fnexus-shield","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSytten%2Fnexus-shield/lists"}