{"_id":"@damzoindistress/permissions-builder","name":"@damzoindistress/permissions-builder","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@damzoindistress/permissions-builder","version":"0.1.0","scripts":{"clean":"rimraf dist","prebuild":"npm run clean","dev":"blitz dev","build":"npx tsc -p tsconfig.library.json","prepublishOnly":"npm run build","postversion":"git push --follow-tags","lint":"eslint --ignore-path .gitignore --ext .js,.ts,.tsx .","test":"vitest run --passWithNoTests","test:watch":"vitest","prepare":"husky install"},"main":"dist/src/index.js","types":"dist/src/index.d.ts","prettier":{"semi":true,"printWidth":100},"lint-staged":{"*.{js,ts,tsx}":["eslint --fix"]},"dependencies":{"@casl/ability":"^6.5.0"},"peerDependencies":{"zod":"^3.17.3"},"devDependencies":{"@blitzjs/next":"^2.0.0-beta.32","@next/bundle-analyzer":"12.0.8","@testing-library/jest-dom":"5.16.3","@types/del":"4.0.0","@types/fs-extra":"11.0.1","@types/node":"18.11.9","@types/react":"18.0.25","@types/tmp":"0.2.3","@typescript-eslint/eslint-plugin":"5.30.5","@vitejs/plugin-react":"2.2.0","blitz":"2.0.0-beta.32","eslint":"8.27.0","eslint-config-next":"12.3.1","eslint-config-prettier":"8.5.0","fs-extra":"11.1.1","husky":"8.0.2","jsdom":"20.0.3","lint-staged":"13.0.3","minimist":"^1.2.5","next":"13.4.5","prettier":"^2.7.1","pretty-quick":"3.1.3","react":"18.2.0","react-dom":"18.2.0","rimraf":"5.0.1","ts-node":"10.9.1","typescript":"^4.8.4","vite-tsconfig-paths":"3.6.0","vitest":"0.25.3","zod":"3.22.2"},"private":false,"_id":"@damzoindistress/permissions-builder@0.1.0","gitHead":"4f274b0c28144bbef97d775fd5104b31fdef793b","description":"`@damzoindistress/permissions-builder` offers a centralized and flexible approach to managing permissions across your application's resources. Built on top of CASL and zod, it extends the expressive power of permissions management with MongoDB's query lan","_nodeVersion":"20.5.1","_npmVersion":"9.8.0","dist":{"integrity":"sha512-6Gc94JgIKMK2UXw9K9QYc5/lK5iFm7dz8L4eW98E8twdJmCb/XvwmBedQzDSIhk4SN4jIDMXL0Ph4D7JCC3NpQ==","shasum":"b6ff314c537b22905c0d199bad7cc1f9d9c1a1a1","tarball":"https://registry.npmjs.org/@damzoindistress/permissions-builder/-/permissions-builder-0.1.0.tgz","fileCount":9,"unpackedSize":22820,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIF/UYi/+pQsKrswg/vUGrpODMDTGa4yhL6yb4/umG7OHAiEA9aVShC5T+LTbP8menkjlVvceV0APpSoa0AGk95MygNw="}]},"_npmUser":{"name":"damzoindistress","email":"heeled.07-snouts@icloud.com"},"directories":{},"maintainers":[{"name":"damzoindistress","email":"heeled.07-snouts@icloud.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/permissions-builder_0.1.0_1694751928183_0.08118032201700331"},"_hasShrinkwrap":false}},"time":{"created":"2023-09-15T04:25:28.182Z","0.1.0":"2023-09-15T04:25:28.343Z","modified":"2023-09-15T04:25:28.675Z"},"maintainers":[{"name":"damzoindistress","email":"heeled.07-snouts@icloud.com"}],"description":"`@damzoindistress/permissions-builder` offers a centralized and flexible approach to managing permissions across your application's resources. Built on top of CASL and zod, it extends the expressive power of permissions management with MongoDB's query lan","readme":"# @damzoindistress/permissions-builder\n\n`@damzoindistress/permissions-builder` offers a centralized and flexible approach to managing permissions across your application's resources. Built on top of CASL and zod, it extends the expressive power of permissions management with MongoDB's query language.\n\n## Key Features\n\n- **Built on CASL**: Utilizes CASL's established abilities for permissions management.\n- **Centralized Configuration**: Manage permissions related to a specific resource in a single place.\n- **Flexibility**: Take advantage of MongoDB's query language to create intricate permission rules based on object properties.\n- **Reusability**: Consistently apply the same permission rules throughout different areas of your application.\n\n## Getting Started\n\n\n## Installation\n\n```bash\nnpm install @damzoindistress/permissions-builder zod\n```\n\nYou'll also need to install zod as it's a peer dependency.\n\n### 1. Setting Up Permissions Context\n\nTo initialize the permissions, utilize the `setupPermissionsContext` function:\n\n```typescript\nimport { setupPermissionsContext } from \"@damzoindistress/permissions-builder\";\n\nimport { z } from \"zod\";\n\nconst ContextSchema = z.object({\n  userId: z.string(),\n});\n\nconst { defineResource, createPermissionsBuilder } = setupPermissionsContext({\n  contextSchema: ContextSchema, // Your schema goes here\n});\n```\n\nThis method sets up the context required for defining resources and creating permissions. This context will be available for every resource to access when defining permissions. You can define the context as whatever object you want, for instance, it might be the schema of a user in your application. Do note that when you've set up your permissions and ready to check whether they're valid, you will have to provide data that matches whatever context schema.\n\n### 2. Defining Resources and Permissions\n\nNext, use the `defineResource` method to define a resource and its associated permissions. Like the context, the resource schema must be a zod object schema:\n\n```typescript\n\nconst WorkspaceSchema = z.object({\n  createdAt: z.date(),\n  name: z.string(),\n  id: z.string(),\n  createdBy: z.string(),\n});\n\n\nexport const buildPermissions = createPermissionsBuilder({\n  workspace: defineResource({\n    actions: z.enum([\"read\", \"update\", \"delete\"]),\n    schema: WorkspaceSchema, // Your schema goes here\n    defineAbility: function ({ can, cannot, context }) {\n      // Define your rules here using `can` and `cannot` functions.\n    },\n  }),\n  // You can define more resources as needed.\n});\n```\n\nWhen defining abilities, you have access to the MongoDB-like query language operators to shape your rules.\n\n#### MongoDB Operators:\n\n- `$eq` and `$ne`: Check if a value equals or doesn't equal a specified value.\n- `$lt` and `$lte`: Check if a value is less than or less than and equal to a specified value.\n- `$gt` and `$gte`: Check if a value is greater than or greater than and equal to a specified value.\n- `$in` and `$nin`: Ensure that an object's property matches any of the specified array values. `$nin` is the opposite of `$in`.\n- `$all`: Ensure an object's property contains all elements from a specified array.\n- `$size`: Confirm that an array's length matches a specified value.\n- `$regex`: Test an object's property value with a regular expression.\n- `$exists`: Check if a particular property exists in an object.\n- `$elemMatch`: Examine nested elements' structure and ensure they match specified criteria.\n\nFor a more in-depth explanation and usage of these operators, you can refer to MongoDB's documentation.\n\n### 3. Using the CASL `can` and `cannot` Functions in defineAbility\n\nThe `can` and `cannot` functions from CASL provide the primary means to define your permissions:\n\n```typescript\ndefineAbility: function ({ can, cannot, context }) {\n  const bannedUsers = [\"steve\"]\n  can(\"read\");  // Allows reading\n  cannot(\"update\", { createdBy: { $in: bannedUsers } });  // Disallows updating for banned users\n}\n```\n\nFor a comprehensive understanding of how to employ these functions and more examples, check out the [CASL documentation](https://casl.js.org/v6/en/guide/define-rules).\n\n\n## Using `buildPermissions`\n\nWhen you invoke `buildPermissions`, it returns an ability instance with three main methods: `can`, `cannot`, and `throwErrorIfCannot`. You would need to pass in data with the context schema you defined in order to initialize it.\n\n```typescript\n const currentUserId = \"james\";\n const ability = buildPermissions({\n        userId: currentUserId,\n });\n\nconst workspace = {\n        name: \"test\",\n        createdAt: new Date(),\n        id: \"jeoobeo3\",\n        createdBy: currentUserId,\n      };\n\nconst canReadWorkspace = ability.can({\n  subject: \"workspace\",\n  action: \"read\",\n  data: workspace,\n});\n```\n\n### 1. `can` Method\n\nThe `can` method checks if a certain action on a subject is permissible.\n\n**Usage**:\n\n```typescript\nconst allowed = ability.can({\n  subject: \"workspace\",\n  action: \"read\",\n  data: workspace,\n});\n```\n\nIn this example, it checks if the `workspace` can be read. If the user has permission, it returns `true`, otherwise `false`.\n\n### 2. `cannot` Method\n\nThe `cannot` method is the opposite of the `can` method. It returns `true` if the user cannot perform the action and `false` if they can.\n\n**Usage**:\n\n```typescript\nconst workspace = {\n  id: \"jeoobeo3\",\n  name: \"test\",\n  createdAt: new Date(),\n  createdBy: \"james\"\n};\n\nconst notAllowed = ability.cannot({\n  subject: \"workspace\",\n  action: \"update\",\n  data: workspace,\n});\n```\n\n### 3. `throwErrorIfCannot` Method\n\nWhile `can` and `cannot` return boolean values, the `throwErrorIfCannot` method will throw a ForbiddenError if the user doesn't have the permission. It's particularly useful in scenarios where an operation should not proceed under any circumstance without the required permission.\n\n**Usage**:\n\n```typescript\ntry {\n  ability.throwErrorIfCannot({\n    subject: \"workspace\",\n    action: \"update\",\n    data: workspace,\n  });\n  // proceed with the update\n} catch (error) {\n  console.error(\"Permission denied:\", error.message);\n}\n```\n\nIf you want to customise the error message, you can pass a `reason` string as a third parameter to the `cannot` function when defining the permissions for a resource:\n\n```typescript\ndefineAbility: function ({ can, cannot, context }) {\n      const bannedUsers = [\"steve\"];\n      cannot([\"update\", \"delete\", \"read\"], { createdBy: { $in: bannedUsers } }, \"because steve\");\n    }\n```\n\n## Practical Scenarios:\n\n### Scenario 1: CRUD Operations for a Workspace Creator\n\nLet's say you want to ensure that the user who created a workspace can perform all CRUD operations on it:\n\n```typescript\nconst currentUserId = \"james\";\nconst ability = buildPermissions({\n  userId: currentUserId,\n});\n\nif (ability.can({ subject: \"workspace\", action: \"read\", data: workspace })) {\n  // User can read the workspace\n}\n\nif (ability.can({ subject: \"workspace\", action: \"update\", data: workspace })) {\n  // User can update the workspace\n}\n\n// ... similar checks for \"delete\" and other actions.\n```\n\n### Scenario 2: Guest Permissions\n\nIn another scenario, you might want guests to read workspaces, but not modify them:\n\n```javascript\nconst guestAbility = buildPermissions({\n  userId: \"david\",\n});\n\nif (guestAbility.can({ subject: \"workspace\", action: \"read\", data: workspace })) {\n  // Guest can read the workspace\n}\n\nif (guestAbility.cannot({ subject: \"workspace\", action: \"update\", data: workspace })) {\n  // Guest cannot update the workspace, so don't show the update button or functionality\n}\n```\n\n### Scenario 3: Restricted Workspaces\n\nIn some cases, there might be workspaces created by specific users where no one is allowed to perform any operations:\n\n```javascript\nconst ability = buildPermissions({\n  userId: \"mark\",\n});\n\n[\"read\", \"update\", \"delete\"].forEach(action => {\n  if (ability.cannot({ subject: \"workspace\", action, data: workspace })) {\n    console.log(`User cannot ${action} this workspace.`);\n  }\n});\n```\n\nThe permission system allows developers to create sophisticated rules and enforce them consistently across different parts of an application.\n\n## Contributing\n\nIf you wish to contribute, please check the library's structure and adhere to the established coding and testing practices.\n\n## License\n\nPlease refer to the project's license for more details on usage and distribution.\n\n","readmeFilename":"README.md"}