{"id":21553144,"url":"https://github.com/char2sgu/nest-access-policy","last_synced_at":"2026-05-18T14:31:37.746Z","repository":{"id":57310003,"uuid":"384859169","full_name":"Char2sGu/nest-access-policy","owner":"Char2sGu","description":"Declarative and centralized access control.","archived":false,"fork":false,"pushed_at":"2021-09-24T15:50:20.000Z","size":128,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"develop","last_synced_at":"2025-10-18T16:17:36.978Z","etag":null,"topics":["access-control","api","crud","declarative","nestjs","permissions","rest","restful","typescript","web"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Char2sGu.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}},"created_at":"2021-07-11T04:26:45.000Z","updated_at":"2021-09-24T15:50:23.000Z","dependencies_parsed_at":"2022-09-01T13:22:22.281Z","dependency_job_id":null,"html_url":"https://github.com/Char2sGu/nest-access-policy","commit_stats":null,"previous_names":["char2sgu/nest-access-policy","thenightmarex/nest-access-policy"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Char2sGu/nest-access-policy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Char2sGu%2Fnest-access-policy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Char2sGu%2Fnest-access-policy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Char2sGu%2Fnest-access-policy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Char2sGu%2Fnest-access-policy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Char2sGu","download_url":"https://codeload.github.com/Char2sGu/nest-access-policy/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Char2sGu%2Fnest-access-policy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33180970,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T09:27:30.708Z","status":"ssl_error","status_checked_at":"2026-05-18T09:27:28.300Z","response_time":71,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["access-control","api","crud","declarative","nestjs","permissions","rest","restful","typescript","web"],"created_at":"2024-11-24T07:09:39.625Z","updated_at":"2026-05-18T14:31:37.727Z","avatar_url":"https://github.com/Char2sGu.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nest Access Policy\n\nDeclarative and centralized access control.\n\n\u003e Inspired by [drf-access-policy](https://github.com/rsinger86/drf-access-policy/).\n\n# Tutorial\n\n## Creating the Access Policy\n\nAn `AccessPolicy` is a special provider where we define our access statements.\n\n\u003e A common usage is to make access policies and controllers have one-to-one relationships.\n\n```ts\n// books.access-policy.ts\n\n@Injetable()\nexport class BooksAccessPolicy implements AccessPolicy {\n  statements: AccessPolicyStatement[] = [\n    {\n      actions: [\"list\", \"create\", \"retrieve\", \"update\", \"destroy\"],\n      effect: Effect.Allow,\n    },\n  ];\n}\n```\n\nEach method of the controller is considered as an _action_. The `actions` defines the names of the actions controlled by the statement.\n\nThere are two kinds of statements: **_allow_ statements** with `effect` set to `Effect.Allow` and **_forbid_ statements** with `effect` set to `Effect.Forbid`. An action is allowed only when all _allow_ statements are passed (**at least one**) and no _forbid_ statements are passed.\n\nTherefore, it is recommended to allow all the actions in the first statement as above and manage the permissions in next statements.\n\n\u003e The `AccessPolicy`, `AccessPolicyStatement`, `AccessPolicyCondition` types takes two optional type args, the first is the actions' type while the second is the request's type.\n\n---\n\nNow let's define the really important parts:\n\n```ts\n// books.access-policy.ts\n\n@Injetable()\nexport class BooksAccessPolicy implements AccessPolicy {\n  private isOwn: AccessPolicyCondition = async ({ req }) =\u003e\n    (await this.getBook(req)).owner == req.user;\n\n  private notOwn: AccessPolicyCondition = async (ctx) =\u003e\n    !(await this.isOwn(ctx));\n\n  private isPublic: AccessPolicyCondition = async ({ req }) =\u003e\n    (await this.getBook(req)).isPublic;\n\n  private notImmutable: AccessPolicyCondition = async ({ req }) =\u003e\n    !(await this.getBook(req)).isImmutable;\n\n  statements: AccessPolicyStatement[] = [\n    {\n      actions: [\"list\", \"create\", \"retrieve\", \"update\", \"destroy\"],\n      effect: Effect.Allow,\n    },\n    {\n      actions: [\"retrieve\"],\n      effect: Effect.Forbid,\n      conditions: [this.notOwn],\n    },\n    {\n      actions: [\"update\", \"destroy\"],\n      effect: Effect.Allow,\n      conditions: [this.notImmutable, [this.isOwn, this.isPublic]],\n    },\n  ];\n\n  @Inject()\n  private booksService: BooksService;\n\n  private async getBook(req: Request) {\n    if (!req.entity) {\n      const id = Number(req.params.id);\n      const book = await this.booksService.retrieve(id);\n      if (!book) throw new NotFoundException();\n      req.entity = book;\n    }\n    return req.entity;\n  }\n}\n```\n\nAll the elements in `conditions` are considered a _condition group_, which can be either a single condition or a list of conditions.  \nThe logical relationship between the condition groups is _and_, and the one between the member conditions of each condition groups is _or_.\n\n\u003e Since an `AccessPolicy` is a class, we can define the common conditions in a public policy such as `BaseAccessPolicy` and inherit the common conditions from it.\n\n\u003e If you prefer to define the conditions after the statements, you can make the statements a `getter`.\n\n---\n\nAlthough the statements above work well, they lack of error messages. Actually they are also organized improperly: The failure of a statement should be able to be described as a single reason.\n\nLet's fix this:\n\n```ts\n// books.access-policy.ts\n\n@Injetable()\nexport class BooksAccessPolicy implements AccessPolicy {\n  // ...\n  statements: AccessPolicyStatement[] = [\n    // ...\n    {\n      actions: [\"retrieve\", \"update\", \"destroy\"],\n      effect: Effect.Allow,\n      conditions: [[this.isOwn, this.isPublic]],\n      reason: \"Only your own books or public books can be managed\",\n    },\n    {\n      actions: [\"update\", \"destroy\"],\n      effect: Effect.Allow,\n      conditions: [this.notImmutable],\n      reason: \"The immutable books cannot be managed\",\n    },\n  ];\n  // ...\n}\n```\n\nWhen checking the statements, if a statement causes the request to be denied, such as a failed _allow_ statement or a passed _forbid_ statement, an `ForbiddenException` will be thrown with the reason of the statement as its message.\n\n## Applying the Access Policy\n\nNow we've done with the `AccessPolicy`, it only takes a few steps to apply it.\n\nSince the `AccessPolicy` is injectable, it should be added to the `providers` list of the module. Whatsmore, we will use a `AccessPolicyGuard` to protect our routes, who injects `AccessPolicyService` to check the statements, so we also need to import the `AccessPolicyModule`:\n\n```ts\n// books.module.ts\n\n@Module({\n  // ...\n  imports: [\n    // ...\n    AccessPolicyModule,\n    // ...\n  ],\n  providers: [\n    // ...\n    BooksAccessPolicy,\n    // ...\n  ],\n  // ...\n})\nexport class BooksModule {}\n```\n\nFinally, of course, use the guard and apply the policy:\n\n```ts\n// books.controller.ts\n\n@UseAccessPolicies(BooksAccessPolicy)\n@UseGuards(AccessPolicyGuard)\n@Controller()\nexport class BooksController {}\n```\n\n\u003e To access `req.user`, you should ensure the `AuthGuard` is before the `AccessPolicyGuard`. See the [request lifecycle](https://docs.nestjs.com/faq/request-lifecycle) for more help.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchar2sgu%2Fnest-access-policy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchar2sgu%2Fnest-access-policy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchar2sgu%2Fnest-access-policy/lists"}