{"id":15137722,"url":"https://github.com/alexanderc/nestjs-iacry","last_synced_at":"2025-10-23T13:30:49.971Z","repository":{"id":39590333,"uuid":"283980568","full_name":"AlexanderC/nestjs-iacry","owner":"AlexanderC","description":"An Identity and Access Control (Management) module for Nest framework (node.js) highly inspired by the AWS IAM","archived":false,"fork":false,"pushed_at":"2023-07-10T13:09:36.000Z","size":824,"stargazers_count":23,"open_issues_count":3,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-08T14:33:49.268Z","etag":null,"topics":["control","firewall","iac","iam","identity","nest","nestjs","policy"],"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/AlexanderC.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-07-31T08:12:39.000Z","updated_at":"2024-12-23T15:45:07.000Z","dependencies_parsed_at":"2024-10-24T09:43:16.782Z","dependency_job_id":null,"html_url":"https://github.com/AlexanderC/nestjs-iacry","commit_stats":{"total_commits":44,"total_committers":7,"mean_commits":6.285714285714286,"dds":0.25,"last_synced_commit":"41fc9224e690905a12e205e9a3acf6ea4b933644"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexanderC%2Fnestjs-iacry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexanderC%2Fnestjs-iacry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexanderC%2Fnestjs-iacry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlexanderC%2Fnestjs-iacry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AlexanderC","download_url":"https://codeload.github.com/AlexanderC/nestjs-iacry/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237834599,"owners_count":19373756,"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":["control","firewall","iac","iam","identity","nest","nestjs","policy"],"created_at":"2024-09-26T07:01:44.336Z","updated_at":"2025-10-23T13:30:49.610Z","avatar_url":"https://github.com/AlexanderC.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"http://nestjs.com/\" target=\"blank\"\u003e\n    \u003cimg src=\"https://nestjs.com/img/logo_text.svg\" width=\"320\" alt=\"Nest Logo\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  An Identity and Access Control (Management) module for Nest framework (node.js) highly inspired by the \u003ca href=\"https://aws.amazon.com/iam/\"\u003eAWS IAM\u003c/a\u003e.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://npmjs.com/package/nestjs-iacry\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/nestjs-iacry.svg\" alt=\"NPM Version\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://npmjs.com/package/nestjs-iacry\"\u003e\u003cimg src=\"https://img.shields.io/npm/l/nestjs-iacry.svg\" alt=\"Package License\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://npmjs.com/package/nestjs-iacry\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/nestjs-iacry.svg\" alt=\"NPM Downloads\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n### Installation\n\n```sh\nnpm install --save nestjs-iacry sequelize-typescript\n#or\nyarn add nestjs-iacry sequelize-typescript\n```\n\n\u003e Important: `sequelize-typescript` is required if you are using `sequelize` policy storage model.\n\n### Configuration\n\nConfigure policy storage using sequelize adapter:\n```typescript\n// models/policies-storage.model.ts\nimport { PoliciesStorageSequelizeModel } from 'nestjs-iacry';\n\nexport default class PoliciesStorage extends PoliciesStorageSequelizeModel\u003cPoliciesStorage\u003e {  }\n```\n\nConfigure your models:\n```typescript\n// models/user.model.ts\nimport { IACryEntity } from 'nestjs-iacry';\n\n// You might optionally use dynamic name fields to allow matching like \"Principal: 'admin:*'\"\n// @IACryEntity({ nameField: 'role' })\n@IACryEntity() \n@Table({})\nexport default class User extends Model\u003cUser\u003e {\n  id: string;\n  role?: string; // nameField\n}\n\n// models/book.model.ts\nimport { IACryEntity } from 'nestjs-iacry';\n\n@IACryEntity()\n@Table({})\nexport default class Book extends Model\u003cBook\u003e {\n  id: string;\n}\n```\n\nAnd finaly include the module and the service *(assume using [Nestjs Configuration](https://docs.nestjs.com/techniques/configuration))*:\n```typescript\n// src/app.module.ts\nimport { IACryModule, Effect, PolicyInterface, SEQUELIZE_STORAGE, IOREDIS_CACHE } from 'nestjs-iacry';\nimport PolicyStorage from '../models/policy-storage.model';\n\n@Module({\n  imports: [\n    IACryModule.forRootAsync({\n      imports: [ConfigModule, RedisModule],\n      inject: [ConfigService, RedisService],\n      useFactory: async (configService: ConfigService, redisService: RedisService) =\u003e {\n        return {\n          storage: SEQUELIZE_STORAGE, // dynamic policy storage (e.g. sequelize)\n          storageRepository: PolicyStorage, // if database storage specified\n          cache: IOREDIS_CACHE, // dynamic policy storage cache (e.g. ioredis)\n          cacheClient: \u003cIORedis.Redis\u003eawait redisService.getClient(), // if cache adapter was specified\n          cacheOptions: { expire: 600 }, // policy cache expires in 10 minutes (default 1 hour)\n          policies: [ // some hardcoded policies...\n            ...configService.get\u003cArray\u003cstring | PolicyInterface\u003e\u003e('policies'),\n            {\n              // allow any action to be performed by the user\n              Effect: Effect.ALLOW,\n              Action: '*',\n              // If used \"@IACryEntity({ nameField: 'role' })\" you might specify \"admin\"\n              Principal: 'user',\n            },\n          ],\n        };\n      },\n    }),\n  ],\n},\n```\n\n### Usage\n\nUsing firewall guard in controllers:\n```typescript\n// src/some-fancy.controller.ts\nimport { Controller, Post, UseGuards } from '@nestjs/common';\nimport { IACryAction, IACryResource, IACryPrincipal, IACryFirewall, IACryFirewallGuard } from 'nestjs-iacry';\n\n@Controller()\nexport class BookController {\n  @IACryAction('book:update')\n  @IACryResource('book:{params.id}') // {params.id} is replaced with req.params.id [OPTIONAL]\n  @IACryPrincipal() // taken from req.user by default\n  @UseGuards(JwtAuthGuard, IACryFirewallGuard)\n  @Post('book/:id')\n  async update(@Request() req) { }\n\n  // ...or the definition above might be replaced with a shorthand...\n  @IACryFirewall({ resource: 'book:{params.id}' })\n  @UseGuards(JwtAuthGuard, IACryFirewallGuard)\n  @Post('book/:id')\n  async update(@Request() req) { }\n\n  // ...you might also combine them...\n  @IACryFirewall()\n  @IACryResource('book:{params.id}')\n  @UseGuards(JwtAuthGuard, IACryFirewallGuard)\n  @Post('book/:id')\n  async update(@Request() req) { }\n}\n```\n\n\u003e Important: check out the `FirewallOptions` definition below:\n\u003e ```typescript\n\u003e export interface FirewallOptions {\n\u003e   action?: Action, // default \"book:update\" for BookController.update()\n\u003e   resource?: Resource, // default \"*\"\n\u003e   principal?: Principal, // default REQUEST_USER\n\u003e }\n\u003e ```\n\nUsing the service:\n```typescript\n// src/some-fancy.controller.ts\nimport { Controller, Post, UseGuards, UnauthorizedException } from '@nestjs/common';\nimport { IACryService } from 'nestjs-iacry';\n\n@Controller()\nexport class BookController {\n  constructor(private readonly firewall: IACryService) { }\n\n  @UseGuards(JwtAuthGuard)\n  @Post('book/:id')\n  async update(@User user: User, @Book() book: Book) {\n    if (!this.firewall.isGranted('book:update', user, book)) {\n      throw new UnauthorizedException(`You are not allowed to update book:${book.id}`);\n    }\n  }\n}\n```\n\nManage user policies:\n```typescript\nimport { IACryService, Effect } from 'nestjs-iacry';\n\nlet firewall: IACryService;\nlet user: User;\nlet book: Book;\n\nconst attachedPoliciesCount = await firewall.attach(user, [\n  // Allow any action on the book service\n  { Effect: Effect.ALLOW, Action: 'book:*'},\n  // allow updating any books to any user except the user with ID=7\n  { Effect: Effect.DENY, Action: ['book:update', 'book:patch'], Principal: 'user:!7' },\n  // deny deleting books to all users\n  { Effect: Effect.DENY, Action: 'book:delete', Principal: ['user:*'] },\n]);\nconst attachedPoliciesCount = await firewall.upsertBySid(\n  'Some policy Sid (mainly a name)',\n  user,\n  [{ Sid: 'Some policy Sid (mainly a name)', Effect: Effect.ALLOW, Action: 'book:*'}],\n);\n// oneliner to allow user patching and updating but deleting the book\nconst attachedPoliciesCount = await firewall.grant('book:patch|update|!delete', user, book);\nconst policies = await firewall.retrieve(user);\nconst policies = await firewall.retrieveBySid('Some policy Sid (mainly a name)', user);\nconst deletedPoliciesCount = await firewall.reset(user);\n```\n\nManaging a policy by it's Sid might be useful when automating policy assignments.\nE.g. granting `book:update|patch|delete` on books created by the user is possible by upserting\na system managed policy w/ the sid `system:user:book` as follows:\n\n```typescript\nimport { IACryService, Effect } from 'nestjs-iacry';\n\nlet firewall: IACryService;\nlet user: User;\nlet newBook: Book;\n\nconst BOOK_SID = 'system:user:book';\nlet policies = await firewall.retrieveBySid(BOOK_SID, user);\n\nif (policies.length \u003e 0) {\n  policies[0] = policies[0].toJSON();\n  policies[0].Resource.push(newBook.toDynamicIdentifier());\n} else {\n  policies = [{\n    Sid: BOOK_SID, // frankly speaking this is optional o_O...\n    Effect: Effect.ALLOW,\n    Action: 'book:update|patch|delete',\n    Resource: [newBook.toDynamicIdentifier()],\n  }];\n}\n\nawait firewall.upsertBySid(BOOK_SID, user, policies);\n```\n\n### Documentation\n\n#### Policy Definition\n\nStructure:\n```typescript\ninterface PolicyInterface {\n  Sid?: string, // policy identifier\n  Effect: 'Allow' | 'Deny', // Allow | Deny\n  Action: string | { service: string, action: string } | Array\u003cstring | { service: string, action: string }\u003e, // Which action: e.g. \"book:update\"\n  Resource?: string | { entity: string, id: number | string } | Array\u003cstring | { entity: string, id: number | string }\u003e, // Action object: e.g. \"book:33\"\n  Principal?: string | { entity: string, id: number | string } | Array\u003cstring | { entity: string, id: number | string }\u003e, // Whom: e.g. \"user:1\"\n}\n```\n\nSyntax Sugar:\n\n- **DIs might be negated** which would mean that a `book:!33` would match any book but the one with ID=33.\n- **DIs might be piped/or-ed** which would mean that a `book:!(update|delete)` would allow any action BUT updating or deleting a book.\n- **DIs might contain complex match patterns** which would mean that a principal `*/admin:!33` would match an admin user from any namespace but the one with ID=33.\n\n\u003e More information about how `micromatch` matches strings [can be found here](https://github.com/micromatch/micromatch#matching-features).\n\n\u003e Important: Within the matcher `*` is replaced with `**` automatically, thus a single `*` won't work as of original micromatch docs. \n\n**Dynamic Identifiers** or **DIs** are considered `Action`, `Resource` and `Principal` properties.\n\n### Development\n\nRunning tests:\n```bash\nnpm test\n```\n\nReleasing:\n```bash\nnpm run format\nnpm run release # npm run patch|minor|major\nnpm run deploy\n```\n\n### TODO\n\n- [ ] Implement an abstraction over the system managed policies\n- [ ] Implement policy conditional statements (e.g. update books that the user created himself)\n- [ ] Add more built in conditional matchers to cover basic use-cases\n- [ ] Cover most of codebase w/ tests\n- [ ] Add comprehensive Documentation\n\n### Contributing\n\n* [Alex Cucer](https://github.com/AlexanderC)\n\n### License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexanderc%2Fnestjs-iacry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexanderc%2Fnestjs-iacry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexanderc%2Fnestjs-iacry/lists"}