{"id":23507375,"url":"https://github.com/simplyhexagonal/doorlock","last_synced_at":"2025-04-16T02:19:11.963Z","repository":{"id":71910320,"uuid":"321576534","full_name":"simplyhexagonal/doorlock","owner":"simplyhexagonal","description":"Allow or deny access based on a hierarchy of restrictions, permissions, roles, and users.","archived":false,"fork":false,"pushed_at":"2021-11-17T08:58:06.000Z","size":218,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-10T11:38:07.714Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/doorlock","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simplyhexagonal.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-12-15T06:40:01.000Z","updated_at":"2022-05-10T02:48:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"d0937061-2a7f-4cef-b836-7689e370f6ed","html_url":"https://github.com/simplyhexagonal/doorlock","commit_stats":{"total_commits":43,"total_committers":1,"mean_commits":43.0,"dds":0.0,"last_synced_commit":"9ca6623227befb2e35197deaa4e5ab47b00d2e0a"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplyhexagonal%2Fdoorlock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplyhexagonal%2Fdoorlock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplyhexagonal%2Fdoorlock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplyhexagonal%2Fdoorlock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simplyhexagonal","download_url":"https://codeload.github.com/simplyhexagonal/doorlock/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248953326,"owners_count":21188621,"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":[],"created_at":"2024-12-25T10:18:27.034Z","updated_at":"2025-04-16T02:19:11.944Z","avatar_url":"https://github.com/simplyhexagonal.png","language":"TypeScript","funding_links":["https://www.buymeacoffee.com/jeanlescure","https://opencollective.com/simplyhexagonal"],"categories":[],"sub_categories":[],"readme":"![DoorLock.js logo featuring a door lock with a fingerprint and abstract blueish shapes](https://assets.jeanlescure.io/doorlock-js-logo.svg)\n\n# DoorLock.js\n\n![tests](https://github.com/jeanlescure/doorlock/workflows/tests/badge.svg) [![try on runkit](https://badge.runkitcdn.com/doorlock.svg)](https://npm.runkit.com/doorlock)\n\n\u003c!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --\u003e\n[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:END --\u003e\n\nA zero-dependency package to do the heavy-lifting (in back-end APIs) of allowing or denying access\nbased on a hierarchy of restrictions, permissions, roles, and users.\n\nThe main goal is to make implementing role-based access (with permission overrides) in your code as\neasy as the following example:\n\n```js\n// The following promise will either return true or throw an 'Unauthorized' error\ndoorlock.evaluateAbilities(\n  // a user object (previously fetched by your code) to be evaluated against\n  // roles, permissions, and restrictions (a.k.a. \"abilities\")\n  user,\n  // options defining the \"abilities\" the user will be challenged against\n  {\n    // a group of users that share the 'author' role will be allowed access\n    roleHandles: ['author'],\n    // any single user with the 'doc-create' permission will be allowed access\n    // (regardless of whether they have the 'author' role or not)\n    permissionHandles: ['doc-create'],\n    // any single user with the 'deny-doc-create' restriction will be blocked and bounced-back\n    // (even if they have the 'author' role or the 'doc-create' permission)\n    restrictionHandles: ['deny-doc-create'],\n  },\n).then(\n  () =\u003e {\n    next();\n  }\n).catch(\n  (err) =\u003e {\n    res.status(401);\n    res.render('error', { error: err });\n  }\n);\n```\n\n([Click here to see an example on RunKit](https://npm.runkit.com/doorlock) that you can try out right now!)\n\n## Before you continue\n\n- If you are also looking to implement login using Google, Facebook, Github, and/or implement your own custom SSO, take a look at [Session SSO](https://github.com/simplyhexagonal/session-sso)\n\n## Open source notice\n\nThis project is open to updates by its users, [I](https://github.com/jeanlescure) ensure that PRs are relevant to the community.\nIn other words, if you find a bug or want a new feature, please help us by becoming one of the\n[contributors](#contributors-) ✌️ ! See the [contributing section](#contributing)\n\n## Like this module? ❤\n\nPlease consider:\n\n- [Buying me a coffee](https://www.buymeacoffee.com/jeanlescure) ☕\n- Supporting Simply Hexagonal on [Open Collective](https://opencollective.com/simplyhexagonal) 🏆\n- Starring this repo on [Github](https://github.com/simplyhexagonal/doorlock) 🌟\n\n## Basics\n\nIn order to better understand how to properly use DoorLock, it's good to understand\nthe basic entity definitions and assumptions driving the logic.\n\nFirstly, these are the basic principles that detail access hierarchy:\n\n- Permissions allow functionality to run\n- Restrictions block functionality from running\n- A Super Admin is a hard-coded user which has **all** permissions and **no** restrictions\n- A set of permissions and restrictions are called abilities\n- Roles have abilities\n- Users have roles\n- Users can also have abilities which override the user's role(s) abilities\n- Roles and abilities have handles which are used to let doorlock know when a user is allowed to trigger specific functionality\n\nFrom the previous, the following is assumed in regards to your user and access related data:\n\n```ts\n// Permissions will be stored with a structure containing at least the following properties:\n{\n  entityId: string,\n  name: string,\n  handle: string,\n  description: string\n}\n\n// Restrictions will be stored with a structure containing at least the following properties:\n{\n  entityId: string,\n  name: string,\n  handle: string,\n  description: string\n}\n\n// Roles will be stored with a structure containing at least the following properties:\n{\n  entityId: string,\n  name: string,\n  handle: string,\n  description: string,\n  abilities: {\n    permissions: PermissionId[],\n    restrictions: RestrictionId[]\n  }\n}\n\n// User will be stored with a structure containing at least the following properties:\n{\n  userId: string,\n  roles: RoleId[],\n  abilities: {\n    permissions: PermissionId[],\n    restrictions: RestrictionId[],\n  }\n}\n```\n\nFinally, the following rule definitions dictate precedence, from top to bottom, where top-most rules\ntrump any rules below them:\n\n- User restrictions\n- ⬇\n- User permissions\n- ⬇\n- User roles (when a user has two \"conflicting\" roles [i.e. one role restricts what the other permits], role restrictions will be respected above role permissions)\n- ⬇\n- Role restrictions\n- ⬇\n- Role permissions\n\n## What about Groups and Organizations?\n\nThis project is meant to be self-contained, small, and easy to maintain. When you take a look at the\nsource code you will see that much of the code is reutilized amongst DoorLock entities (restrictions,\npermissions, roles, and users).\n\nExperience dictates that the logic behind groups and organizations will vary widely between projects.\n\nFor example, just wonder if you will need to support the use-case for a group comprised of users from\ntwo or more organizations, a cross-organizational group if you may. That right there would be a\ntext-book example of the [law of diminishing returns](https://en.wikipedia.org/wiki/Diminishing_returns),\nwhere the effort to implement said use-case would be disproportionately higher to the benefit provided\nto a smaller percentage of the devs using DoorLock.\n\nIf your project is large enough to include groups and organizations as part of the access-management\nrequirements, more than likely your team will have sufficient members to create this higher order of\naccess hierarchy without much hassle. Specially taking into consideration that you'd already have\nDoorLock ready to throw as the cherry on top of your API cake 😉\n\n## How to get started using this package\n\nImport DoorLock as you would any other package:\n\n```ts\nimport DoorLock from 'doorlock';\n```\n\nDoorLock needs to be able to fetch roles, permissions, and restrictions (i.e. from your app's DB),\nthus anywhere on your code **before** your API's server start, define the following functions:\n\n```ts\nconst fetchRolesById = async (roleIds) =\u003e /* your custom logic */;\nconst fetchPermissionsById = async (permissionIds) =\u003e /* your custom logic */;\nconst fetchRestrictionsById = async (restrictionIds) =\u003e /* your custom logic */;\nconst fetchRolesByHandle = async (roleHandles) =\u003e /* your custom logic */;\nconst fetchPermissionsByHandle = async (permissionHandles) =\u003e /* your custom logic */;\nconst fetchRestrictionsByHandle = async (restrictionHandles) =\u003e /* your custom logic */;\n```\n\nAlso before starting the server, and more importantly, before defining any routes you wish to control\naccess to, instantiate DoorLock with the following options (which include the aforementione fetch functions):\n\n```ts\nconst doorlock = new DoorLock({\n  superAdminId: '...', // must be accessible from the user object as: user.id\n  fetchRolesById,\n  fetchPermissionsById,\n  fetchRestrictionsById,\n  fetchRolesByHandle,\n  fetchPermissionsByHandle,\n  fetchRestrictionsByHandle,\n  // The following are optional\n  verifyRoleExists: true, // defaults to: false (to save on performance)\n  verifyAbilitiesExist: true, // defaults to: false (to save on performance)\n  debug: true, // defaults to: false\n  logFn: (message: string) =\u003e console.log('MY CUSTOM LOG =\u003e', message), // only works if debug: true\n});\n```\n\n**NOTE:** _The `verifyRoleExists` and `verifyAbilitiesExist` options should be turned on solely to debug\nerrors or inconsistencies that you may suspect to be caused by \"ghost\" roles or abilities left behind\nafter deletion of the original entity (i.e. a user has a role id of a role that was deleted)._\n\nOnce instantiated you can implement DoorLock on each request, as in the following example:\n\n```ts\nserver.post('/doc', (req, res) =\u003e {\n  const user = /* i.e. the user object returned after validating the access token on req.headers */;\n\n  doorlock.evaluateAbilities(\n    user,\n    {\n      roleHandles: ['author'], // \u003c== Only allows access to users with the author role\n      permissionHandles: [],\n      restrictionHandles: [],\n    },\n  ).then(() =\u003e {\n    res.status(200);\n    res.send(`User ${userId} is allowed to create documents`);\n  }).catch(() =\u003e {\n    res.status(401);\n    res.render('error', {error: `User ${userId} is NOT allowed to create documents`});\n  });\n});\n```\n\nOr better yet, create access specific middlewares:\n\n```ts\n// Middleware that only allows users/roles with the 'doc-manipulation' permission\nconst docManipulationAccessControl = (req, res, next) =\u003e {\n  const user = /* i.e. the user object returned after validating the access token on req.headers */;\n\n  return doorlock.evaluateAbilities(\n    user,\n    {\n      roleHandles: [],\n      permissionHandles: ['doc-manipulation'],\n      restrictionHandles: [],\n    },\n  ).then(() =\u003e {\n    next();\n  }).catch(() =\u003e {\n    res.status(401);\n    res.send('Unauthorized');\n  });\n}\n\n// Then the access logic becomes trivial to re-use and maintain\nserver.post('/doc', docManipulationAccessControl, (req, res) =\u003e { /* ... */ });\nserver.get('/doc', docManipulationAccessControl, (req, res) =\u003e { /* ... */ });\nserver.put('/doc', docManipulationAccessControl, (req, res) =\u003e { /* ... */ });\nserver.delete('/doc', docManipulationAccessControl, (req, res) =\u003e { /* ... */ });\n```\n\nIf you would like a functioning example, you're welcome to try [this dummy server with DoorLock example on Runkit](https://npm.runkit.com/doorlock).\n\nAnd if you'd like a more thorough example take a look at the mock and test files under the `specs`\ndirectory on this repository.\n\n## Troubleshooting\n\n- If a user that's supposed to be given access keeps being blocked, there may be an error produced by\nmissing data or properties that is being suppressed by the `catch` logic. In these cases simply try and\nrefactor the catch logic to log the error (i.e. `.catch((e) =\u003e console.log(e))`) to get more details\nto debug with. Although it would be advisable that you refactor the catch clause to verify the error\nand alert you of any error **other** than `Unauthorized`.\n\n## Development and build scripts\n\nRollup was chosen to handle the transpiling, compression, and any other transformations needed to get\nTypescript code running as quickly and performant as possible.\n\n## Contributing\n\nYes, thank you! Projects like this thrive when they are community-driven.\n\nPlease update the docs and tests and add your name to the package.json file on any PR you submit.\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://jeanlescure.cr\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/3330339?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJean Lescure\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#maintenance-jeanlescure\" title=\"Maintenance\"\u003e🚧\u003c/a\u003e \u003ca href=\"https://github.com/jeanlescure/doorlock/commits?author=jeanlescure\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"#userTesting-jeanlescure\" title=\"User Testing\"\u003e📓\u003c/a\u003e \u003ca href=\"https://github.com/jeanlescure/doorlock/commits?author=jeanlescure\" title=\"Tests\"\u003e⚠️\u003c/a\u003e \u003ca href=\"#example-jeanlescure\" title=\"Examples\"\u003e💡\u003c/a\u003e \u003ca href=\"https://github.com/jeanlescure/doorlock/commits?author=jeanlescure\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://dianalu.design\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/1036995?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eDiana Lescure\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/jeanlescure/doorlock/commits?author=DiLescure\" title=\"Documentation\"\u003e📖\u003c/a\u003e \u003ca href=\"https://github.com/jeanlescure/doorlock/pulls?q=is%3Apr+reviewed-by%3ADiLescure\" title=\"Reviewed Pull Requests\"\u003e👀\u003c/a\u003e \u003ca href=\"#design-DiLescure\" title=\"Design\"\u003e🎨\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-enable --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\n## License\n\nCopyright (c) 2020-2021 [DoorLock Contributors](https://github.com/jeanlescure/doorlock/#contributors-).\u003cbr/\u003e\nLicensed under the Apache License 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplyhexagonal%2Fdoorlock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimplyhexagonal%2Fdoorlock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplyhexagonal%2Fdoorlock/lists"}