├── .env ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── codeql-analysis.yml │ └── npm-ci.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .prettierrc.js ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── coverage-badge.svg ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── classes │ ├── AccessControl.html │ ├── Grant.html │ ├── MemoryDriver.html │ └── Permission.html ├── coverage │ ├── coverage-summary.json │ ├── lcov-report │ │ ├── base.css │ │ ├── block-navigation.js │ │ ├── favicon.png │ │ ├── index.html │ │ ├── prettify.css │ │ ├── prettify.js │ │ ├── sort-arrow-sprite.png │ │ ├── sorter.js │ │ ├── src │ │ │ ├── classes │ │ │ │ ├── acl.class.ts.html │ │ │ │ ├── grant.class.ts.html │ │ │ │ ├── index.html │ │ │ │ ├── index.ts.html │ │ │ │ └── permission.class.ts.html │ │ │ ├── consts │ │ │ │ ├── acl.const.ts.html │ │ │ │ ├── index.html │ │ │ │ └── index.ts.html │ │ │ ├── driver │ │ │ │ ├── index.html │ │ │ │ ├── index.ts.html │ │ │ │ └── memory │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.ts.html │ │ │ │ │ ├── memory.driver.ts.html │ │ │ │ │ └── memory.tools.ts.html │ │ │ ├── index.html │ │ │ ├── index.ts.html │ │ │ ├── types │ │ │ │ ├── cache.type.ts.html │ │ │ │ ├── common.type.ts.html │ │ │ │ ├── index.html │ │ │ │ ├── index.ts.html │ │ │ │ ├── pattern.type.ts.html │ │ │ │ └── policy.type.ts.html │ │ │ └── utils │ │ │ │ ├── index.html │ │ │ │ ├── index.ts.html │ │ │ │ ├── other.util.ts.html │ │ │ │ ├── regex.util.ts.html │ │ │ │ ├── strict.util.ts.html │ │ │ │ └── validate.util.ts.html │ │ └── test │ │ │ ├── index.html │ │ │ └── mock.ts.html │ └── lcov.info ├── functions │ ├── IP_CIDR.html │ ├── accessibility.html │ ├── accumulate.html │ ├── filterByNotation.html │ ├── isCIDR.html │ ├── isCRON.html │ ├── isIP.html │ ├── isStrict.html │ ├── key.html │ ├── memoryIgnore.html │ ├── parse.html │ ├── pattern-1.html │ └── validate.html ├── hierarchy.html ├── index.html ├── interfaces │ ├── AccessControlOptions.html │ ├── CacheInterface.html │ ├── CacheInterfaceOptions.html │ ├── CanOptions.html │ ├── ControlOptions.html │ ├── Pattern.html │ ├── Policy.html │ ├── Time.html │ └── TimeOptions.html ├── modules.html ├── types │ ├── CacheKey.html │ ├── MemoryDriverOptions.html │ ├── PropType.html │ └── PropValue.html └── variables │ ├── ALL.html │ ├── ANY.html │ ├── DefaultMemoryDriverOptions.html │ ├── NULL.html │ ├── OK.html │ ├── SEP.html │ ├── STRICT.html │ ├── cidrRegex.html │ └── ipRegex.html ├── jest.config.ts ├── package.json ├── pnpm-lock.yaml ├── renovate.json ├── src ├── classes │ ├── acl.class.ts │ ├── grant.class.ts │ ├── index.ts │ └── permission.class.ts ├── consts │ ├── acl.const.ts │ └── index.ts ├── driver │ ├── index.ts │ └── memory │ │ ├── index.ts │ │ ├── memory.driver.ts │ │ └── memory.tools.ts ├── index.ts ├── types │ ├── cache.type.ts │ ├── common.type.ts │ ├── index.ts │ ├── pattern.type.ts │ └── policy.type.ts └── utils │ ├── index.ts │ ├── other.util.ts │ ├── regex.util.ts │ ├── strict.util.ts │ └── validate.util.ts ├── test ├── classes │ ├── acl.test.ts │ ├── grant.test.ts │ └── permission.test.ts ├── driver │ └── memory │ │ └── tools.test.ts ├── mock.ts └── utils │ ├── other.test.ts │ ├── strict.test.ts │ └── validate.test.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | DEBUG=* -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | docs 3 | coverage 4 | node_modules 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "prettier", 7 | "eslint:recommended", 8 | "plugin:prettier/recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@typescript-eslint/eslint-recommended" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '40 14 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v3 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v3 73 | -------------------------------------------------------------------------------- /.github/workflows/npm-ci.yml: -------------------------------------------------------------------------------- 1 | name: Build, Test and Publish 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | # Setup .npmrc file to publish to npm 11 | - uses: actions/setup-node@v4 12 | with: 13 | node-version: '20.x' 14 | registry-url: 'https://registry.npmjs.org' 15 | - run: npm install -g pnpm 16 | - run: pnpm install --frozen-lockfile 17 | - run: npm run build --if-present 18 | - run: npm test 19 | - run: npm publish 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | coverage/* 3 | node_modules 4 | !coverage/badge.svg 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | docs 3 | .vscode 4 | coverage 5 | 6 | tslint.json 7 | tsconfig.json 8 | jest.config.ts 9 | .prettierrc.js 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 132, 6 | tabWidth: 2, 7 | }; 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "attach", 10 | "name": "Attach", 11 | "restart": true, 12 | "port": 9229, 13 | "skipFiles": ["/**"] 14 | }, 15 | { 16 | "type": "node", 17 | "request": "launch", 18 | "name": "Launch Program", 19 | "program": "${workspaceFolder}/src/index.ts", 20 | "preLaunchTask": "tsc: build - tsconfig.json", 21 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 22 | }, 23 | { 24 | "type": "node", 25 | "request": "launch", 26 | "name": "Jest watch current file", 27 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 28 | "args": ["${fileBasename}", "--verbose", "-i", "--no-cache", "--watchAll"], 29 | "console": "integratedTerminal", 30 | "internalConsoleOptions": "neverOpen" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": "explicit" 4 | }, 5 | "cSpell.words": ["ABAC", "abacl", "consts", "Vahid"] 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Vahid V. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Attribute-Based Access Control Library 2 | 3 | [![npm](https://img.shields.io/npm/v/abacl)](https://www.npmjs.com/package/abacl) 4 | [![Coverage](https://raw.githubusercontent.com/vhidvz/abacl/master/coverage-badge.svg)](https://htmlpreview.github.io/?https://github.com/vhidvz/abacl/blob/master/docs/coverage/lcov-report/index.html) 5 | ![npm](https://img.shields.io/npm/dm/abacl) 6 | [![GitHub](https://img.shields.io/github/license/vhidvz/abacl?style=flat)](https://vhidvz.github.io/abacl/) 7 | [![Gitter](https://badges.gitter.im/npm-abacl/community.svg)](https://gitter.im/npm-abacl/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 8 | [![documentation](https://img.shields.io/badge/documentation-click_to_read-c27cf4)](https://vhidvz.github.io/abacl/) 9 | [![Build, Test and Publish](https://github.com/vhidvz/abacl/actions/workflows/npm-ci.yml/badge.svg)](https://github.com/vhidvz/abacl/actions/workflows/npm-ci.yml) 10 | 11 | The Attribute-Based Access-Control Library let you define five `can` access ability: 12 | 13 | - Who can? the answer is `subject` - Like RBAC a user can have multiple subjects. 14 | - How can it? the answer is `action` - You can define `any` actions you want (scoped). 15 | - What can? the answer is `object` - You can define `all` objects you want (scoped). 16 | - Where can? the answer is `location` - With IP and CIDR you can find the location of users. 17 | - When can it? the answer is `time` - objects availabilities with cron expression and a duration. 18 | 19 | ## ABAC vs RBAC? 20 | 21 | | **Question** | **RBAC** | **ABAC** | 22 | | ------------------ | --------------------------------- | ------------------------------------------- | 23 | | Who can access? | :white_check_mark: | :heavy_check_mark: With more options | 24 | | How can operate? | :white_check_mark: CRUD | :heavy_check_mark: With more options | 25 | | What resource? | :white_check_mark: Not Bad At All | :heavy_check_mark: More control on resource | 26 | | Where user can do? | :x: | :heavy_check_mark: Supported by IP and CIDR | 27 | | When user can do? | :x: | :heavy_check_mark: Supported by CRON | 28 | | Best structure? | Monolithic Apps | PWA, Restful, GraphQL | 29 | | Suitable for? | Small and medium projects | Medium and large projects | 30 | 31 | ### What's Scope? 32 | 33 | - look at carefully; scan. 34 | - assess or investigate something. 35 | 36 | In this library, We scoped `action`, `object` and `subject` which means you can have more control over these attributes. 37 | 38 | **Note:** if you want to have more control over the scoped attributes send at most three character of the first `subject`, `action`, or `object` for example `so` or `sub|obj` it means `subject` and `object` are in `strict` mode. 39 | 40 | ## Quick Start Guide 41 | 42 | ### installation 43 | 44 | ```sh 45 | npm install --save abacl 46 | ``` 47 | 48 | ### Usage and Dangling 49 | 50 | Define your user policies as a json array (so you can store it in your database): 51 | 52 | ```ts 53 | import { Policy } from 'abacl'; 54 | 55 | enum Role { 56 | Admin = 'admin', 57 | User = 'user', 58 | Guest = 'guest', 59 | Manager = 'manager', 60 | } 61 | 62 | const policies: Policy[] = [ 63 | { 64 | subject: Role.Admin, 65 | action: 'any', 66 | object: 'all', 67 | }, 68 | { 69 | subject: Role.Guest, 70 | action: 'read', 71 | object: 'article:published', 72 | }, 73 | { 74 | subject: Role.Guest, 75 | action: 'create:own', 76 | object: 'article:published', 77 | }, 78 | { 79 | subject: Role.Manager, 80 | action: 'any', 81 | object: 'article', 82 | }, 83 | { 84 | subject: Role.User, 85 | action: 'create:own', 86 | object: 'article', 87 | field: ['*', '!owner'], 88 | location: ['192.168.2.10', '192.168.1.0/24'], 89 | time: [ 90 | { 91 | cron_exp: '* * 7 * * *', // from 7 AM 92 | duration: 9 * 60 * 60, // for 9 hours 93 | }, 94 | ], 95 | }, 96 | { 97 | subject: Role.User, 98 | action: 'read:own', 99 | object: 'article', 100 | }, 101 | { 102 | subject: Role.User, 103 | action: 'read:shared', 104 | object: 'article', 105 | filter: ['*', '!owner'], 106 | }, 107 | { 108 | subject: Role.User, 109 | action: 'delete:own', 110 | object: 'article', 111 | }, 112 | { 113 | subject: Role.User, 114 | action: 'update:own', 115 | object: 'article', 116 | field: ['*', '!id', '!owner'], 117 | }, 118 | ]; 119 | ``` 120 | 121 | Article and User definition objects: 122 | 123 | ```ts 124 | const user = { 125 | id: 1, 126 | subject: Role.User, 127 | ip: '192.168.1.100', 128 | }; 129 | 130 | const article = { 131 | id: 1, 132 | owner: 'user1', 133 | title: 'title', 134 | content: 'content', 135 | }; 136 | ``` 137 | 138 | Create a new access control object, then get the permission grants: 139 | 140 | ```ts 141 | import AccessControl from 'abacl'; 142 | 143 | // The `strict` `AccessControlOption` control the scoped functionality 144 | // default strict value is true, you can change it on the `can` method 145 | 146 | const ac = new AccessControl(policies, { strict: false }); 147 | const permission = await ac.can([user.subject], 'read', 'article'); 148 | 149 | // change strict mode dynamically, Example: 150 | // const strictPermission = await ac.can([user.subject], 'read', 'article', { strict: true }); 151 | 152 | /** 153 | * it('should change strict mode dynamically', () => { 154 | * const ac = new AccessControl(policies, { strict: true }); 155 | * 156 | * expect(await ac.can([Role.User], 'read', 'article:published').granted).toBeFalsy(); 157 | * 158 | * // After changing strict mode 159 | * expect(await ac.can([Role.User], 'read', 'article:published', { strict: false }).granted).toBeTruthy(); 160 | * }); 161 | * 162 | * */ 163 | 164 | if (permission.granted) { 165 | // default scope for action and object is `any` and `all` 166 | 167 | if (permission.has({ action: 'read:own' })) { 168 | // user has read owned article objects 169 | } 170 | 171 | if (permission.has({ action: 'read:shared' })) { 172 | // user can access shared article objects 173 | } 174 | 175 | if (permission.has({ object: 'article:published' })) { 176 | // user can access shared article objects 177 | } 178 | 179 | // do something ... 180 | 181 | // return filtered data based on the permission 182 | const response = await permission.filter(article); 183 | } 184 | ``` 185 | 186 | Time and location access check example: 187 | 188 | ```ts 189 | import { AccessControl, Permission } from 'abacl'; 190 | 191 | // default `strict` value is true 192 | const ac = new AccessControl(policies, { strict: true }); 193 | 194 | const permission = await ac.can([user.subject], 'create', 'article', { 195 | callable: (perm: Permission) => { 196 | return perm.location(user.ip) && perm.time(); 197 | }, 198 | }); 199 | 200 | if (permission.granted) { 201 | const inputData = await permission.field(article); 202 | 203 | // the `inputData` has not `owner` property 204 | // do something and then return results to user 205 | } 206 | ``` 207 | 208 | ## Related Project 209 | 210 | - [abacl-redis](https://www.npmjs.com/package/abacl-redis) redis storage driver. 211 | 212 | ## Thanks a lot 213 | 214 | [accesscontrol](https://www.npmjs.com/package/accesscontrol) - Role and Attribute based Access Control for Node.js 215 | 216 | [CASL](https://casl.js.org/) is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access. 217 | 218 | ## License 219 | 220 | [MIT](https://github.com/vhidvz/abacl/blob/master/LICENSE) 221 | -------------------------------------------------------------------------------- /coverage-badge.svg: -------------------------------------------------------------------------------- 1 | Coverage: 97.74%Coverage97.74% -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #0000FF; 9 | --dark-hl-3: #569CD6; 10 | --light-hl-4: #AF00DB; 11 | --dark-hl-4: #C586C0; 12 | --light-hl-5: #001080; 13 | --dark-hl-5: #9CDCFE; 14 | --light-hl-6: #267F99; 15 | --dark-hl-6: #4EC9B0; 16 | --light-hl-7: #0070C1; 17 | --dark-hl-7: #4FC1FF; 18 | --light-hl-8: #008000; 19 | --dark-hl-8: #6A9955; 20 | --light-hl-9: #098658; 21 | --dark-hl-9: #B5CEA8; 22 | --light-code-background: #FFFFFF; 23 | --dark-code-background: #1E1E1E; 24 | } 25 | 26 | @media (prefers-color-scheme: light) { :root { 27 | --hl-0: var(--light-hl-0); 28 | --hl-1: var(--light-hl-1); 29 | --hl-2: var(--light-hl-2); 30 | --hl-3: var(--light-hl-3); 31 | --hl-4: var(--light-hl-4); 32 | --hl-5: var(--light-hl-5); 33 | --hl-6: var(--light-hl-6); 34 | --hl-7: var(--light-hl-7); 35 | --hl-8: var(--light-hl-8); 36 | --hl-9: var(--light-hl-9); 37 | --code-background: var(--light-code-background); 38 | } } 39 | 40 | @media (prefers-color-scheme: dark) { :root { 41 | --hl-0: var(--dark-hl-0); 42 | --hl-1: var(--dark-hl-1); 43 | --hl-2: var(--dark-hl-2); 44 | --hl-3: var(--dark-hl-3); 45 | --hl-4: var(--dark-hl-4); 46 | --hl-5: var(--dark-hl-5); 47 | --hl-6: var(--dark-hl-6); 48 | --hl-7: var(--dark-hl-7); 49 | --hl-8: var(--dark-hl-8); 50 | --hl-9: var(--dark-hl-9); 51 | --code-background: var(--dark-code-background); 52 | } } 53 | 54 | :root[data-theme='light'] { 55 | --hl-0: var(--light-hl-0); 56 | --hl-1: var(--light-hl-1); 57 | --hl-2: var(--light-hl-2); 58 | --hl-3: var(--light-hl-3); 59 | --hl-4: var(--light-hl-4); 60 | --hl-5: var(--light-hl-5); 61 | --hl-6: var(--light-hl-6); 62 | --hl-7: var(--light-hl-7); 63 | --hl-8: var(--light-hl-8); 64 | --hl-9: var(--light-hl-9); 65 | --code-background: var(--light-code-background); 66 | } 67 | 68 | :root[data-theme='dark'] { 69 | --hl-0: var(--dark-hl-0); 70 | --hl-1: var(--dark-hl-1); 71 | --hl-2: var(--dark-hl-2); 72 | --hl-3: var(--dark-hl-3); 73 | --hl-4: var(--dark-hl-4); 74 | --hl-5: var(--dark-hl-5); 75 | --hl-6: var(--dark-hl-6); 76 | --hl-7: var(--dark-hl-7); 77 | --hl-8: var(--dark-hl-8); 78 | --hl-9: var(--dark-hl-9); 79 | --code-background: var(--dark-code-background); 80 | } 81 | 82 | .hl-0 { color: var(--hl-0); } 83 | .hl-1 { color: var(--hl-1); } 84 | .hl-2 { color: var(--hl-2); } 85 | .hl-3 { color: var(--hl-3); } 86 | .hl-4 { color: var(--hl-4); } 87 | .hl-5 { color: var(--hl-5); } 88 | .hl-6 { color: var(--hl-6); } 89 | .hl-7 { color: var(--hl-7); } 90 | .hl-8 { color: var(--hl-8); } 91 | .hl-9 { color: var(--hl-9); } 92 | pre, code { background: var(--code-background); } 93 | -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAA42V0W6bMBSG34XddmuTpt2auy6pJpQMEMkmVVNVueA0VoyN7ENUNO3dpzgkYDCH3Po/34ftg+0/fz2gH+BNvZRuSMHBu/JyAltv6mUyLTjVX7aQ8U91umMi9aaT0cPk9mby7+rMPyYJ1XomBSjJa0vCidZUX1uxcZ5do/G3hueHIgK6vBnGuJ80k6qcK7anqos3U8wSUZUxrZkUXUedYQZroWEOTApdu5gAqjYkae9IVWiLx3f3DfGMJFvqn3in0i65XIZN01mJqwXuE5dIhvfv8p2LCABVwmmpMhSXnCWlmzYRBq9Z5u7VIRgCseU38sFGL2hj+lDmp7YuaHvuNw9fR3fjnmPVmc5R5SgZskZK5usyp23VafwS/jfhhVNggiHD43JZs3uiGHnjh0O5XNrkrQUFz04oeEag+fHqRDeydvVXI58IfrlXcxhHsHDhgsIFgqyeIhezeoowaB37s7WTMwmCJixVMX2nHy76HCIClvfiVYTAfvQ68+dxDW8KkZhmXFeRDd83H0Ri7nf2xjiD0qWwCnBRkRWcAO2xVCmi2DAOVH0vAwkErMetFrVrEB3TfdtyTHA0DoMeNA4DFPUjN+hHKLYCxRJwo8cMwXfU2bxd++q0oMwcX/9dSOVsWjNHNDlR2smbAAVbL14TNdHnEYLvCWdpz/92yjr4y3+SiRnpTAoAAA==" -------------------------------------------------------------------------------- /docs/assets/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAA61cW2/byA7+L8qrmnqukvLW0y4Ogu1Jg7ZngYURBK6ttjp1bEOSc5oN+t8XoyupkKNxsk9BPBx+HH4k56KRHqNy//8qulg+Rj+K3Sa6kIssEUbG0W51l0cX0XW5P3x+OORRHB3LbXQR1Q+HvHrd/3z+vb7bRnG03q6qKq+iiyj6Fft0/bHaHillze9ebdYYZQddt7c1bdWo6GyQAfri6LAq812NzBlBxELqAeNuVexCEM47wRmY884eBq1a78MGdN5Lnow3JeTtav09/z1/mKD2P/vJNRbrudzVefl1tc4/HOpiv6sGpUXf0GmeCnphsIvyw2laz9oetKNoqxnoQ5l/LX6eiD50ep4BvIvn7ZhJTJ0OmtfbfFUGKjzrhUMGxAF+y+tQuFb0JWBVOFj1YrBNvg0Fa0VfAvZ9FZBiLVgreioYiL7PxR0Zc+738PRdl/vdbf6TzOFB0xmQoi1ujGEgNsdy5dLIDwGkgiCgK67322L9QOlvW06oZscv/8vXZHgCVWejGFPvW4MYkNWacwfEGKSeA7EPGsb+JaOomfiDAJ3Mc9R/LfLtZk5/L/Q8gG2dk0UWI3RSz4HY7tds5EMQIBcIg6ah/a4u91vfFI8kTsiGuizoKCI0ng3STFHDZvJFzTMQ0HxCiTuWzoR3q5qtl2gcWJ4vRjOrk/qvILRG7BQQVPhWdZ2XdHy1TSeQvT+W9CoGqjobxJhI7SxiJsg6r+iqBCE6oVAACyvr+nh33EKmvx53TSGtXo+N/j0NUpdXVfGl2Bb1A61xbA9V2laUfz1c7WtcHEa9UxGvajXuHYrDx/xbPq6I71dlsfqyzavXXUuoonWxKTlVQ1vogIvqE64j40D7pnBVl9ekmsvrcBVvL999JJW4hlA1l9e3jJ6u5QR7Pn64ou35+OEqVM39alts6Ljvm0LJ//A7wfqH30O7f/rtmuj/6Tc/Q1DB54+Xbz9TOpqGUDVvrv4kdLy5+jNYwfv3lIL370MVXP2X1OB+PumM5j/53b58eFcW93k5nRzb0wFCItTId/nX1XFb+zBG03lh/zwjU3IwA0LXC40jfLf85ViApSKr66yXoycWZNiIZAQoi/tdVZfHdb0PMP0MS58EOj3mqJz0POIoOaLV1eZVUb06lPs6X9f55lT0/SQYePRR8kXonoMQj6+9ZyAhSPAEhMfxHX6EoFRBKL5TjxAUeObBo/iOO0JQ4GEHj+I752BQwLR2WJUVNac1v4fOjT9yavH2Y+YwFVmBl9nQjqbllQhVddcM+fLbbl9S44LNJxz17rznu7vTt0yr7dZV/AB9Z0CWO83aPd3FeJ8cBKCdzzxIAP0GA0/f5U7AiR1uV+CK3fe8LCYFjhw4JO5Ns3+Y379TcuFkbvBcG6L3bOhDe5c0/GT38vAnOnrOHLD8QKJPyhdq9Xo4dFnwVGPQugCbGUasB5Kg0zczB6FzCwMC/tSVAYc/nUqZVSRhART+B22glycU5971SRBW/rOo6iB3D5IvQNvk2xzsJn2x1Uu+AO14QHtXD9og+QK09WoXxNmKP5BlMwSUmn+XK2LV3vz6j5SWUVNQSWnNOTGZAcapSezF4/Y0AO/UzcwUT1opNEB0R+hF7h0ikAnzYUgQA/0zwevVzqQ/0D6T9l7tTLoD7TNp7tVO7RSAat8Wwau3uXThVT1IPEd7+5DPr3+UeQYCeoRGaPc+PvNqfvLkidA++9TJi4CfzxHq/c/mZnSjR3Okcu9jOa/2XXeq7iUWCj2z3oGp4Dov74qqoggZm1564DXRNHPcBUw6feqZQoXMPyQgmhS+OQ/m8wMb5V6EFIZzGkropDOFmp15aLS5IjuF8VXaeQSm3E5BZmpuAA5XeJ8gzVXfeSyyBE9x/NcYZjHYYjzFmb8HMItFl+Up0My9iQAUskA/hfFfnqBwtMi0WoBNbvvIYcC622+O27w/phgaYdG8iaNit8l/RheP0X1eNgAXkTxX51kUtx5y12tbhDha7+/unEE3XdsfuatjTqIVeb2I4uUi1upcZebmJl72PZqG5odGTETxUlBiAonJKF7KWKXnMrVITCIxFcVLRWlTSExH8VJTYhqJmSheGkrMIDEbxUtLiVkklkTxMqGGkCCxNIqXKaUtRWJZFC8zSizD7nXeFotY6fNMSuzgCRENE4IyUGAuhPO5INkQmA7h3C4UKYkZEc7zQsdKnutFhiUxKcI5XxhSEvMinP8FyYzA1AhHgUhIScyOcCwIkh+BCRKOCEFSJDBH0hEhyVyRmCPpiJBkushJvkjWSxJzJB0RUlIRIjFH0hEhFSmJOZKOCKlJScyRdERIMskk5kg6IqQlR4Q5ko4ISbIpMUfSESFJNiXmSDUckWwqzJFyRKgFFfMKc6QcEYpkU03KWlPXJCmJOVKOCEWXQMyRckQosgoqzJFyRCiSI4U5Uo4IRWacwhwpR4QiOVKYI+WIUCRHCnOkHRGK5EhjjrQjQpMZpzFH2hGhSY405kg7IjTJkZ7MPs30Q3KkMUfaEaHpmQpzpC03u2hMkebTSGOKtONBk7RrTJF2PGiSdo0pMo4HTdJuMEWmoYik3WCKTEMRSbvBFBnHgyFpN5gi43gw5GRoJmuEZpEgKX8aTJFxRBhyMjSYI+OIMGT5NJgjk7LTu8EcmYyd3g3myC7Y6d1ijqxgp3eLObKSnd4t5sg2HJFRZzFHtuGIXnhhjqxhS7KdLOUajsj4tJgj23BExqfFHFlHhCHj02KObMatci2mKFmwSZxgihLHgyVDPsEUJY4HSwZIgilKFI+OKUocD5asiQmmKOHTKMEUJZa3c7Lg5tMowRQljgdLVuQEU5RkbMIlmKPUEWE1ZWeKOUobjgwpiTlKG44sKYk5Sh0RlgzkFHOUNhylpE7MUcpzlGKOUst6PsUcpQlbFNPJvqjhKCMlMUdpxnsJc5TxHGWYo4znKMMcZXypyzBHmSMiIctShjnKHBEJGfMZ5ijjd0cZ5iiz7Bo9wxxlCbtDyDBHWcruELLJ9tURkZCVIZvuYB0TCZmcbRuUFey83bZBWcmGc9sGZRuqyIVY2wZl+YRq26CsYYO6bYOylt8iLyb72UXChlbbBmVTNrjaNiib8ZvvxYS35nyBDjDx5OxBsCEmpqcPzRkDHWRiev7QnDIk5GpCdCcQzbnXfV7W+eayPf9aLoerVY/RbXcoNhzxP0Yyunj8FUc2c39/jUdh7j+H0L7gsO5vy4w6LFCS6JDew+NjoGQBlAivkuEdjLG30mNvZfjewwshoKsCXXnr26NY4Dhgr1xw3bbIUdqOfTTr5h0amTagT8r06Z7ujL2MGHsZ3RLrKgXdfe1eMR3uYkE92aiGsxd3ppgddVifjuZmJhgCGAHbrb9tCLoBky3nr+Y2B7AwGfukiu9DDM6kAC7huhabsmzfoAFxB0AVa2h7VWjsJUDYiUVLrEnbvwlLEXwiB2wHeWtMp6P7m5o+aCSvlEllCaiTLOXDW8RgcCBUBNuxfSNvM0ljCYiQnCOGRwRjvwQY62qnt2N7I7e9QEcMXIPYM5zfmpvWYMyg/LTwcWS5RO3vXYDQBeanou2esd27W2+gO6gTSTcBJJznx7eygfkgigUX//1lFIAL6nXahXHGVdHuARXgGs5anc+ypAtYwY2+fwAF9ADfy65KZmmvhyOw1fPlYTe8sweSGtChOD827w0AFwoYgV1KcxHcPYYGngRxnw6VnpvHhqflIAE0rChcAjQPi4HNsFOHarv4yWRvBeeA4nDrSiJKHeAEzXm+ODytoqBkKC7+iuoJHCijmguYonJFCnUDgac5VxVVgaqaAlVBczFeVP01Z9ARUKu4gJjOmnC25aJgfLQMcgEwKrvin9k+Fziz2w/rAJOBxVwXUEOR5YASE9KZKsDAY4ajtdVRdG9dAANAKbNc6IKrQCCFQFhkHFG742QxCOAM59/+owyAJ5Aokhvi/gdCAtRqbi1FrW1AP9OV16T7m3IO6l7SAVpASbQcr8NLNSCUQDyorixazvoDuFMFeIFz64Lz8XjdBlRVkLNpPyMsWPTukyOgPoJAFNzirv/60NgN1nK+U3sTFngYDNN0KZtytfDg8mayCQSeZp1U7g/37afBwCjBIJl+3aewQDCCWPT1wQkGLMxUTwc3xOabUqAzWOK0XTUXvNVkZgZ5LfrtMeehKq+J9RWgJuEo7b+iAGIfeFZxs+HTCUOC4i/7sfaJ262yEi4Fh8/YgPGDLBBcXRvvXQGXA/uzYVHCjb/9xAMYPXC74sxtr2EBW+E6vpu/ZL8iGTYz3CCcNmonA2JHsub/hboAnykuVvq73yBQAHVpZ3bKWTu+3w/yCkSaJjPrJo4OxSHfFrs8ulje/Pr1N0iOgr9cUAAA"; -------------------------------------------------------------------------------- /docs/coverage/coverage-summary.json: -------------------------------------------------------------------------------- 1 | {"total": {"lines":{"total":754,"covered":737,"skipped":0,"pct":97.74},"statements":{"total":754,"covered":737,"skipped":0,"pct":97.74},"functions":{"total":60,"covered":58,"skipped":0,"pct":96.66},"branches":{"total":254,"covered":220,"skipped":0,"pct":86.61},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}} 2 | ,"/home/vahid/WorkSpace/github/abacl/src/index.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 3 | ,"/home/vahid/WorkSpace/github/abacl/src/classes/acl.class.ts": {"lines":{"total":83,"covered":81,"skipped":0,"pct":97.59},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":83,"covered":81,"skipped":0,"pct":97.59},"branches":{"total":32,"covered":23,"skipped":0,"pct":71.87}} 4 | ,"/home/vahid/WorkSpace/github/abacl/src/classes/grant.class.ts": {"lines":{"total":211,"covered":199,"skipped":0,"pct":94.31},"functions":{"total":21,"covered":21,"skipped":0,"pct":100},"statements":{"total":211,"covered":199,"skipped":0,"pct":94.31},"branches":{"total":114,"covered":99,"skipped":0,"pct":86.84}} 5 | ,"/home/vahid/WorkSpace/github/abacl/src/classes/index.ts": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 6 | ,"/home/vahid/WorkSpace/github/abacl/src/classes/permission.class.ts": {"lines":{"total":59,"covered":59,"skipped":0,"pct":100},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":59,"covered":59,"skipped":0,"pct":100},"branches":{"total":10,"covered":10,"skipped":0,"pct":100}} 7 | ,"/home/vahid/WorkSpace/github/abacl/src/consts/acl.const.ts": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 8 | ,"/home/vahid/WorkSpace/github/abacl/src/consts/index.ts": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 9 | ,"/home/vahid/WorkSpace/github/abacl/src/driver/index.ts": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 10 | ,"/home/vahid/WorkSpace/github/abacl/src/driver/memory/index.ts": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 11 | ,"/home/vahid/WorkSpace/github/abacl/src/driver/memory/memory.driver.ts": {"lines":{"total":48,"covered":45,"skipped":0,"pct":93.75},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":48,"covered":45,"skipped":0,"pct":93.75},"branches":{"total":9,"covered":8,"skipped":0,"pct":88.88}} 12 | ,"/home/vahid/WorkSpace/github/abacl/src/driver/memory/memory.tools.ts": {"lines":{"total":63,"covered":63,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":63,"covered":63,"skipped":0,"pct":100},"branches":{"total":36,"covered":33,"skipped":0,"pct":91.66}} 13 | ,"/home/vahid/WorkSpace/github/abacl/src/types/cache.type.ts": {"lines":{"total":28,"covered":28,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":28,"covered":28,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 14 | ,"/home/vahid/WorkSpace/github/abacl/src/types/common.type.ts": {"lines":{"total":8,"covered":8,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":8,"covered":8,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 15 | ,"/home/vahid/WorkSpace/github/abacl/src/types/index.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 16 | ,"/home/vahid/WorkSpace/github/abacl/src/types/pattern.type.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 17 | ,"/home/vahid/WorkSpace/github/abacl/src/types/policy.type.ts": {"lines":{"total":16,"covered":16,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":16,"covered":16,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 18 | ,"/home/vahid/WorkSpace/github/abacl/src/utils/index.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 19 | ,"/home/vahid/WorkSpace/github/abacl/src/utils/other.util.ts": {"lines":{"total":44,"covered":44,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":44,"covered":44,"skipped":0,"pct":100},"branches":{"total":21,"covered":17,"skipped":0,"pct":80.95}} 20 | ,"/home/vahid/WorkSpace/github/abacl/src/utils/regex.util.ts": {"lines":{"total":29,"covered":29,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":29,"covered":29,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 21 | ,"/home/vahid/WorkSpace/github/abacl/src/utils/strict.util.ts": {"lines":{"total":22,"covered":22,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":22,"covered":22,"skipped":0,"pct":100},"branches":{"total":7,"covered":7,"skipped":0,"pct":100}} 22 | ,"/home/vahid/WorkSpace/github/abacl/src/utils/validate.util.ts": {"lines":{"total":28,"covered":28,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":28,"covered":28,"skipped":0,"pct":100},"branches":{"total":24,"covered":22,"skipped":0,"pct":91.66}} 23 | ,"/home/vahid/WorkSpace/github/abacl/test/mock.ts": {"lines":{"total":77,"covered":77,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":77,"covered":77,"skipped":0,"pct":100},"branches":{"total":1,"covered":1,"skipped":0,"pct":100}} 24 | } 25 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* yellow */ 156 | .cbranch-no { background: yellow !important; color: #111; } 157 | /* dark red */ 158 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 159 | .low .chart { border:1px solid #C21F39 } 160 | .highlighted, 161 | .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ 162 | background: #C21F39 !important; 163 | } 164 | /* medium red */ 165 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 166 | /* light red */ 167 | .low, .cline-no { background:#FCE1E5 } 168 | /* light green */ 169 | .high, .cline-yes { background:rgb(230,245,208) } 170 | /* medium green */ 171 | .cstat-yes { background:rgb(161,215,106) } 172 | /* dark green */ 173 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 174 | .high .chart { border:1px solid rgb(77,146,33) } 175 | /* dark yellow (gold) */ 176 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 177 | .medium .chart { border:1px solid #f9cd0b; } 178 | /* light yellow */ 179 | .medium { background: #fff4c2; } 180 | 181 | .cstat-skip { background: #ddd; color: #111; } 182 | .fstat-skip { background: #ddd; color: #111 !important; } 183 | .cbranch-skip { background: #ddd !important; color: #111; } 184 | 185 | span.cline-neutral { background: #eaeaea; } 186 | 187 | .coverage-summary td.empty { 188 | opacity: .5; 189 | padding-top: 4px; 190 | padding-bottom: 4px; 191 | line-height: 1; 192 | color: #888; 193 | } 194 | 195 | .cover-fill, .cover-empty { 196 | display:inline-block; 197 | height: 12px; 198 | } 199 | .chart { 200 | line-height: 0; 201 | } 202 | .cover-empty { 203 | background: white; 204 | } 205 | .cover-full { 206 | border-right: none !important; 207 | } 208 | pre.prettyprint { 209 | border: none !important; 210 | padding: 0 !important; 211 | margin: 0 !important; 212 | } 213 | .com { color: #999 !important; } 214 | .ignore-none { color: #999; font-weight: normal; } 215 | 216 | .wrapper { 217 | min-height: 100%; 218 | height: auto !important; 219 | height: 100%; 220 | margin: 0 auto -48px; 221 | } 222 | .footer, .push { 223 | height: 48px; 224 | } 225 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/block-navigation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var jumpToCode = (function init() { 3 | // Classes of code we would like to highlight in the file view 4 | var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; 5 | 6 | // Elements to highlight in the file listing view 7 | var fileListingElements = ['td.pct.low']; 8 | 9 | // We don't want to select elements that are direct descendants of another match 10 | var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` 11 | 12 | // Selecter that finds elements on the page to which we can jump 13 | var selector = 14 | fileListingElements.join(', ') + 15 | ', ' + 16 | notSelector + 17 | missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` 18 | 19 | // The NodeList of matching elements 20 | var missingCoverageElements = document.querySelectorAll(selector); 21 | 22 | var currentIndex; 23 | 24 | function toggleClass(index) { 25 | missingCoverageElements 26 | .item(currentIndex) 27 | .classList.remove('highlighted'); 28 | missingCoverageElements.item(index).classList.add('highlighted'); 29 | } 30 | 31 | function makeCurrent(index) { 32 | toggleClass(index); 33 | currentIndex = index; 34 | missingCoverageElements.item(index).scrollIntoView({ 35 | behavior: 'smooth', 36 | block: 'center', 37 | inline: 'center' 38 | }); 39 | } 40 | 41 | function goToPrevious() { 42 | var nextIndex = 0; 43 | if (typeof currentIndex !== 'number' || currentIndex === 0) { 44 | nextIndex = missingCoverageElements.length - 1; 45 | } else if (missingCoverageElements.length > 1) { 46 | nextIndex = currentIndex - 1; 47 | } 48 | 49 | makeCurrent(nextIndex); 50 | } 51 | 52 | function goToNext() { 53 | var nextIndex = 0; 54 | 55 | if ( 56 | typeof currentIndex === 'number' && 57 | currentIndex < missingCoverageElements.length - 1 58 | ) { 59 | nextIndex = currentIndex + 1; 60 | } 61 | 62 | makeCurrent(nextIndex); 63 | } 64 | 65 | return function jump(event) { 66 | if ( 67 | document.getElementById('fileSearch') === document.activeElement && 68 | document.activeElement != null 69 | ) { 70 | // if we're currently focused on the search input, we don't want to navigate 71 | return; 72 | } 73 | 74 | switch (event.which) { 75 | case 78: // n 76 | case 74: // j 77 | goToNext(); 78 | break; 79 | case 66: // b 80 | case 75: // k 81 | case 80: // p 82 | goToPrevious(); 83 | break; 84 | } 85 | }; 86 | })(); 87 | window.addEventListener('keydown', jumpToCode); 88 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhidvz/abacl/f6021846fe34f7030b2c00c35dc02752d92c627d/docs/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /docs/coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhidvz/abacl/f6021846fe34f7030b2c00c35dc02752d92c627d/docs/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /docs/coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var addSorting = (function() { 3 | 'use strict'; 4 | var cols, 5 | currentSort = { 6 | index: 0, 7 | desc: false 8 | }; 9 | 10 | // returns the summary table element 11 | function getTable() { 12 | return document.querySelector('.coverage-summary'); 13 | } 14 | // returns the thead element of the summary table 15 | function getTableHeader() { 16 | return getTable().querySelector('thead tr'); 17 | } 18 | // returns the tbody element of the summary table 19 | function getTableBody() { 20 | return getTable().querySelector('tbody'); 21 | } 22 | // returns the th element for nth column 23 | function getNthColumn(n) { 24 | return getTableHeader().querySelectorAll('th')[n]; 25 | } 26 | 27 | function onFilterInput() { 28 | const searchValue = document.getElementById('fileSearch').value; 29 | const rows = document.getElementsByTagName('tbody')[0].children; 30 | for (let i = 0; i < rows.length; i++) { 31 | const row = rows[i]; 32 | if ( 33 | row.textContent 34 | .toLowerCase() 35 | .includes(searchValue.toLowerCase()) 36 | ) { 37 | row.style.display = ''; 38 | } else { 39 | row.style.display = 'none'; 40 | } 41 | } 42 | } 43 | 44 | // loads the search box 45 | function addSearchBox() { 46 | var template = document.getElementById('filterTemplate'); 47 | var templateClone = template.content.cloneNode(true); 48 | templateClone.getElementById('fileSearch').oninput = onFilterInput; 49 | template.parentElement.appendChild(templateClone); 50 | } 51 | 52 | // loads all columns 53 | function loadColumns() { 54 | var colNodes = getTableHeader().querySelectorAll('th'), 55 | colNode, 56 | cols = [], 57 | col, 58 | i; 59 | 60 | for (i = 0; i < colNodes.length; i += 1) { 61 | colNode = colNodes[i]; 62 | col = { 63 | key: colNode.getAttribute('data-col'), 64 | sortable: !colNode.getAttribute('data-nosort'), 65 | type: colNode.getAttribute('data-type') || 'string' 66 | }; 67 | cols.push(col); 68 | if (col.sortable) { 69 | col.defaultDescSort = col.type === 'number'; 70 | colNode.innerHTML = 71 | colNode.innerHTML + ''; 72 | } 73 | } 74 | return cols; 75 | } 76 | // attaches a data attribute to every tr element with an object 77 | // of data values keyed by column name 78 | function loadRowData(tableRow) { 79 | var tableCols = tableRow.querySelectorAll('td'), 80 | colNode, 81 | col, 82 | data = {}, 83 | i, 84 | val; 85 | for (i = 0; i < tableCols.length; i += 1) { 86 | colNode = tableCols[i]; 87 | col = cols[i]; 88 | val = colNode.getAttribute('data-value'); 89 | if (col.type === 'number') { 90 | val = Number(val); 91 | } 92 | data[col.key] = val; 93 | } 94 | return data; 95 | } 96 | // loads all row data 97 | function loadData() { 98 | var rows = getTableBody().querySelectorAll('tr'), 99 | i; 100 | 101 | for (i = 0; i < rows.length; i += 1) { 102 | rows[i].data = loadRowData(rows[i]); 103 | } 104 | } 105 | // sorts the table using the data for the ith column 106 | function sortByIndex(index, desc) { 107 | var key = cols[index].key, 108 | sorter = function(a, b) { 109 | a = a.data[key]; 110 | b = b.data[key]; 111 | return a < b ? -1 : a > b ? 1 : 0; 112 | }, 113 | finalSorter = sorter, 114 | tableBody = document.querySelector('.coverage-summary tbody'), 115 | rowNodes = tableBody.querySelectorAll('tr'), 116 | rows = [], 117 | i; 118 | 119 | if (desc) { 120 | finalSorter = function(a, b) { 121 | return -1 * sorter(a, b); 122 | }; 123 | } 124 | 125 | for (i = 0; i < rowNodes.length; i += 1) { 126 | rows.push(rowNodes[i]); 127 | tableBody.removeChild(rowNodes[i]); 128 | } 129 | 130 | rows.sort(finalSorter); 131 | 132 | for (i = 0; i < rows.length; i += 1) { 133 | tableBody.appendChild(rows[i]); 134 | } 135 | } 136 | // removes sort indicators for current column being sorted 137 | function removeSortIndicators() { 138 | var col = getNthColumn(currentSort.index), 139 | cls = col.className; 140 | 141 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 142 | col.className = cls; 143 | } 144 | // adds sort indicators for current column being sorted 145 | function addSortIndicators() { 146 | getNthColumn(currentSort.index).className += currentSort.desc 147 | ? ' sorted-desc' 148 | : ' sorted'; 149 | } 150 | // adds event listeners for all sorter widgets 151 | function enableUI() { 152 | var i, 153 | el, 154 | ithSorter = function ithSorter(i) { 155 | var col = cols[i]; 156 | 157 | return function() { 158 | var desc = col.defaultDescSort; 159 | 160 | if (currentSort.index === i) { 161 | desc = !currentSort.desc; 162 | } 163 | sortByIndex(i, desc); 164 | removeSortIndicators(); 165 | currentSort.index = i; 166 | currentSort.desc = desc; 167 | addSortIndicators(); 168 | }; 169 | }; 170 | for (i = 0; i < cols.length; i += 1) { 171 | if (cols[i].sortable) { 172 | // add the click event handler on the th so users 173 | // dont have to click on those tiny arrows 174 | el = getNthColumn(i).querySelector('.sorter').parentElement; 175 | if (el.addEventListener) { 176 | el.addEventListener('click', ithSorter(i)); 177 | } else { 178 | el.attachEvent('onclick', ithSorter(i)); 179 | } 180 | } 181 | } 182 | } 183 | // adds sorting functionality to the UI 184 | return function() { 185 | if (!getTable()) { 186 | return; 187 | } 188 | cols = loadColumns(); 189 | loadData(); 190 | addSearchBox(); 191 | addSortIndicators(); 192 | enableUI(); 193 | }; 194 | })(); 195 | 196 | window.addEventListener('load', addSorting); 197 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/classes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/classes 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/classes

23 |
24 | 25 |
26 | 96.06% 27 | Statements 28 | 342/356 29 |
30 | 31 | 32 |
33 | 84.61% 34 | Branches 35 | 132/156 36 |
37 | 38 | 39 |
40 | 97.36% 41 | Functions 42 | 37/38 43 |
44 | 45 | 46 |
47 | 96.06% 48 | Lines 49 | 342/356 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
FileStatementsBranchesFunctionsLines
acl.class.ts 84 |
85 |
97.59%81/8371.87%23/3285.71%6/797.59%81/83
grant.class.ts 99 |
100 |
94.31%199/21186.84%99/114100%21/2194.31%199/211
index.ts 114 |
115 |
100%3/3100%0/0100%0/0100%3/3
permission.class.ts 129 |
130 |
100%59/59100%10/10100%10/10100%59/59
143 |
144 |
145 |
146 | 151 | 152 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/classes/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/classes/index.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/classes index.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 3/3 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 3/3 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

66 | 
1 67 | 2 68 | 3 69 | 41x 70 | 1x 71 | 1x 72 |  
export * from './acl.class';
73 | export * from './grant.class';
74 | export * from './permission.class';
75 |  
76 | 77 |
78 |
79 | 84 | 85 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/consts/acl.const.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/consts/acl.const.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/consts acl.const.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 10/10 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 10/10 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 111x 77 | 1x 78 | 1x 79 | 1x 80 | 1x 81 | 1x 82 | 1x 83 | 1x 84 | 1x 85 | 1x 86 |  
// Default Values
 87 |  
 88 | export const OK = 'OK';
 89 | export const SEP = ':';
 90 | export const STRICT = true;
 91 |  
 92 | export const ANY = 'any'; // action keyword
 93 | export const ALL = 'all'; // object keyword
 94 |  
 95 | export const NULL = 'null'; // subject keyword
 96 |  
97 | 98 |
99 |
100 | 105 | 106 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/consts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/consts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/consts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 11/11 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 11/11 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
FileStatementsBranchesFunctionsLines
acl.const.ts 84 |
85 |
100%10/10100%0/0100%0/0100%10/10
index.ts 99 |
100 |
100%1/1100%0/0100%0/0100%1/1
113 |
114 |
115 |
116 | 121 | 122 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/consts/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/consts/index.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/consts index.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 1/1 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 1/1 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

66 | 
1 67 | 21x 68 |  
export * from './acl.const';
69 |  
70 | 71 |
72 |
73 | 78 | 79 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/driver/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/driver 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/driver

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 1/1 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 1/1 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
index.ts 84 |
85 |
100%1/1100%0/0100%0/0100%1/1
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/driver/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/driver/index.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/driver index.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 1/1 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 1/1 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

66 | 
1 67 | 21x 68 |  
export * from './memory';
69 |  
70 | 71 |
72 |
73 | 78 | 79 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/driver/memory/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/driver/memory 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/driver/memory

23 |
24 | 25 |
26 | 97.34% 27 | Statements 28 | 110/113 29 |
30 | 31 | 32 |
33 | 91.11% 34 | Branches 35 | 41/45 36 |
37 | 38 | 39 |
40 | 92.3% 41 | Functions 42 | 12/13 43 |
44 | 45 | 46 |
47 | 97.34% 48 | Lines 49 | 110/113 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
FileStatementsBranchesFunctionsLines
index.ts 84 |
85 |
100%2/2100%0/0100%0/0100%2/2
memory.driver.ts 99 |
100 |
93.75%45/4888.88%8/985.71%6/793.75%45/48
memory.tools.ts 114 |
115 |
100%63/6391.66%33/36100%6/6100%63/63
128 |
129 |
130 |
131 | 136 | 137 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/driver/memory/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/driver/memory/index.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/driver/memory index.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 2/2 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 2/2 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

66 | 
1 67 | 2 68 | 31x 69 | 1x 70 |  
export * from './memory.driver';
71 | export * from './memory.tools';
72 |  
73 | 74 |
75 |
76 | 81 | 82 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 9/9 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 9/9 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
index.ts 84 |
85 |
100%9/9100%0/0100%0/0100%9/9
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/index.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src index.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 9/9 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 9/9 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 101x 76 | 1x 77 | 1x 78 | 1x 79 | 1x 80 | 1x 81 | 1x 82 | 1x 83 | 1x 84 |  
import { AccessControl } from './classes';
 85 |  
 86 | export * from './types';
 87 | export * from './utils';
 88 | export * from './consts';
 89 | export * from './driver';
 90 | export * from './classes';
 91 |  
 92 | export default AccessControl;
 93 |  
94 | 95 |
96 |
97 | 102 | 103 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/types/cache.type.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/types/cache.type.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/types cache.type.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 28/28 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 28/28 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 24 90 | 25 91 | 26 92 | 27 93 | 28 94 | 291x 95 | 1x 96 | 1x 97 | 1x 98 | 1x 99 | 1x 100 | 1x 101 | 1x 102 | 1x 103 | 1x 104 | 1x 105 | 1x 106 | 1x 107 | 1x 108 | 1x 109 | 1x 110 | 1x 111 | 1x 112 | 1x 113 | 1x 114 | 1x 115 | 1x 116 | 1x 117 | 1x 118 | 1x 119 | 1x 120 | 1x 121 | 1x 122 |  
import { ControlOptions } from './common.type';
123 | import { Policy } from './policy.type';
124 | import { OK } from '../consts';
125 |  
126 | export type PropType = 'subject' | 'action' | 'object';
127 | export type PropValue<M = string, S = string> = { main: M; scope?: S };
128 |  
129 | export type CacheKey<Sub = string, Act = string, Obj = string> = {
130 |   subject?: Sub | (PropValue & ControlOptions);
131 |   action?: Act | (PropValue & ControlOptions);
132 |   object?: Obj | (PropValue & ControlOptions);
133 | } & ControlOptions;
134 |  
135 | export interface CacheInterfaceOptions {
136 |   sep?: string;
137 |   prefix?: string;
138 | }
139 |  
140 | export interface CacheInterface<Sub = string, Act = string, Obj = string> {
141 |   clear(): typeof OK | Promise<typeof OK>;
142 |  
143 |   get(cKey: CacheKey<Sub, Act, Obj>): Policy<Sub, Act, Obj>[] | Promise<Policy<Sub, Act, Obj>[]>;
144 |  
145 |   set(policy: Policy<Sub, Act, Obj>): typeof OK | Promise<typeof OK>;
146 |   del(policy: Policy<Sub, Act, Obj>): typeof OK | Promise<typeof OK>;
147 |  
148 |   has(policy: Policy<Sub, Act, Obj>, options?: ControlOptions): boolean | Promise<boolean>;
149 | }
150 |  
151 | 152 |
153 |
154 | 159 | 160 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/types/common.type.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/types/common.type.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/types common.type.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 8/8 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 8/8 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 91x 75 | 1x 76 | 1x 77 | 1x 78 | 1x 79 | 1x 80 | 1x 81 | 1x 82 |  
export interface ControlOptions {
 83 |   strict?: boolean | string;
 84 | }
 85 |  
 86 | export interface TimeOptions {
 87 |   currentDate?: Date;
 88 |   tz?: string;
 89 | }
 90 |  
91 | 92 |
93 |
94 | 99 | 100 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/types/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/types 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/types

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 60/60 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 60/60 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
FileStatementsBranchesFunctionsLines
cache.type.ts 84 |
85 |
100%28/28100%0/0100%0/0100%28/28
common.type.ts 99 |
100 |
100%8/8100%0/0100%0/0100%8/8
index.ts 114 |
115 |
100%4/4100%0/0100%0/0100%4/4
pattern.type.ts 129 |
130 |
100%4/4100%0/0100%0/0100%4/4
policy.type.ts 144 |
145 |
100%16/16100%0/0100%0/0100%16/16
158 |
159 |
160 |
161 | 166 | 167 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/types/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/types/index.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/types index.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 4/4 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 4/4 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

66 | 
1 67 | 2 68 | 3 69 | 4 70 | 51x 71 | 1x 72 | 1x 73 | 1x 74 |  
export * from './cache.type';
75 | export * from './policy.type';
76 | export * from './common.type';
77 | export * from './pattern.type';
78 |  
79 | 80 |
81 |
82 | 87 | 88 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/types/pattern.type.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/types/pattern.type.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/types pattern.type.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 4/4 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 4/4 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

66 | 
1 67 | 2 68 | 3 69 | 4 70 | 51x 71 | 1x 72 | 1x 73 | 1x 74 |  
export interface Pattern {
75 |   readonly source: string;
76 |   test(val: string): boolean | Promise<boolean>;
77 | }
78 |  
79 | 80 |
81 |
82 | 87 | 88 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/types/policy.type.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/types/policy.type.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/types policy.type.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 16/16 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 16/16 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 171x 83 | 1x 84 | 1x 85 | 1x 86 | 1x 87 | 1x 88 | 1x 89 | 1x 90 | 1x 91 | 1x 92 | 1x 93 | 1x 94 | 1x 95 | 1x 96 | 1x 97 | 1x 98 |  
export interface Time {
 99 |   cron_exp: string;
100 |   duration: number;
101 | }
102 |  
103 | export interface Policy<Sub = string, Act = string, Obj = string> {
104 |   subject: Sub;
105 |  
106 |   action: Act;
107 |   object: Obj;
108 |  
109 |   time?: Time[];
110 |   field?: string[];
111 |   filter?: string[];
112 |   location?: string[];
113 | }
114 |  
115 | 116 |
117 |
118 | 123 | 124 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/utils/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/utils 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files src/utils

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 127/127 29 |
30 | 31 | 32 |
33 | 88.46% 34 | Branches 35 | 46/52 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 9/9 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 127/127 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 |
FileStatementsBranchesFunctionsLines
index.ts 84 |
85 |
100%4/4100%0/0100%0/0100%4/4
other.util.ts 99 |
100 |
100%44/4480.95%17/21100%3/3100%44/44
regex.util.ts 114 |
115 |
100%29/29100%0/0100%0/0100%29/29
strict.util.ts 129 |
130 |
100%22/22100%7/7100%1/1100%22/22
validate.util.ts 144 |
145 |
100%28/2891.66%22/24100%5/5100%28/28
158 |
159 |
160 |
161 | 166 | 167 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/utils/index.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/utils/index.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/utils index.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 4/4 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 4/4 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

66 | 
1 67 | 2 68 | 3 69 | 4 70 | 51x 71 | 1x 72 | 1x 73 | 1x 74 |  
export * from './other.util';
75 | export * from './regex.util';
76 | export * from './strict.util';
77 | export * from './validate.util';
78 |  
79 | 80 |
81 |
82 | 87 | 88 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/utils/regex.util.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/utils/regex.util.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/utils regex.util.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 29/29 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 29/29 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 24 90 | 25 91 | 26 92 | 27 93 | 28 94 | 29 95 | 301x 96 | 1x 97 | 1x 98 | 1x 99 | 1x 100 | 1x 101 | 1x 102 | 1x 103 | 1x 104 | 1x 105 | 1x 106 | 1x 107 | 1x 108 | 1x 109 | 1x 110 | 1x 111 | 1x 112 | 1x 113 | 1x 114 | 1x 115 | 1x 116 | 1x 117 | 1x 118 | 1x 119 | 1x 120 | 1x 121 | 1x 122 | 1x 123 | 1x 124 |  
// https://github.com/sindresorhus/ip-regex
125 |  
126 | const v4 = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}';
127 |  
128 | const v6segment = '[a-fA-F\\d]{1,4}';
129 | const v6 = `
130 | (?:
131 | (?:${v6segment}:){7}(?:${v6segment}|:)|                                    // 1:2:3:4:5:6:7::  1:2:3:4:5:6:7:8
132 | (?:${v6segment}:){6}(?:${v4}|:${v6segment}|:)|                             // 1:2:3:4:5:6::    1:2:3:4:5:6::8   1:2:3:4:5:6::8  1:2:3:4:5:6::1.2.3.4
133 | (?:${v6segment}:){5}(?::${v4}|(?::${v6segment}){1,2}|:)|                   // 1:2:3:4:5::      1:2:3:4:5::7:8   1:2:3:4:5::8    1:2:3:4:5::7:1.2.3.4
134 | (?:${v6segment}:){4}(?:(?::${v6segment}){0,1}:${v4}|(?::${v6segment}){1,3}|:)| // 1:2:3:4::        1:2:3:4::6:7:8   1:2:3:4::8      1:2:3:4::6:7:1.2.3.4
135 | (?:${v6segment}:){3}(?:(?::${v6segment}){0,2}:${v4}|(?::${v6segment}){1,4}|:)| // 1:2:3::          1:2:3::5:6:7:8   1:2:3::8        1:2:3::5:6:7:1.2.3.4
136 | (?:${v6segment}:){2}(?:(?::${v6segment}){0,3}:${v4}|(?::${v6segment}){1,5}|:)| // 1:2::            1:2::4:5:6:7:8   1:2::8          1:2::4:5:6:7:1.2.3.4
137 | (?:${v6segment}:){1}(?:(?::${v6segment}){0,4}:${v4}|(?::${v6segment}){1,6}|:)| // 1::              1::3:4:5:6:7:8   1::8            1::3:4:5:6:7:1.2.3.4
138 | (?::(?:(?::${v6segment}){0,5}:${v4}|(?::${v6segment}){1,7}|:))             // ::2:3:4:5:6:7:8  ::2:3:4:5:6:7:8  ::8             ::1.2.3.4
139 | )(?:%[0-9a-zA-Z]{1,})?                                             // %eth0            %1
140 | `
141 |   .replace(/\s*\/\/.*$/gm, '')
142 |   .replace(/\n/g, '')
143 |   .trim();
144 |  
145 | export const ipRegex = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`);
146 |  
147 | // https://github.com/silverwind/cidr-regex
148 |  
149 | const v4str = `${v4}\\/(3[0-2]|[12]?[0-9])`;
150 | const v6str = `${v6}\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])`;
151 |  
152 | export const cidrRegex = new RegExp(`(?:^${v4str}$)|(?:^${v6str}$)`);
153 |  
154 | 155 |
156 |
157 | 162 | 163 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/utils/strict.util.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/utils/strict.util.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/utils strict.util.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 22/22 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 7/7 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 22/22 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 231x 89 | 1x 90 | 1x 91 | 1x 92 | 1x 93 | 1x 94 | 1x 95 | 1x 96 | 1x 97 | 1x 98 | 1x 99 | 1x 100 | 1x 101 | 1x 102 | 1x 103 | 1x 104 | 1x 105 | 1x 106 | 232x 107 | 45x 108 | 232x 109 | 232x 110 |  
import { PropType } from '../types';
111 | import { STRICT } from '../consts';
112 |  
113 | /**
114 |  * The function `isStrict` checks if a given property type matches a strict mode, returning a boolean
115 |  * value.
116 |  *
117 |  * @param {PropType} prop - The `prop` parameter is of type `PropType`. It represents a property that
118 |  * is being checked for strictness.
119 |  *
120 |  * @param {boolean | string} strict - The `strict` parameter is a boolean or string that determines
121 |  * whether the code should be executed in strict mode. If it is a boolean, the function will return the
122 |  * value of `strict`. If it is a string, the function will check if the first character of the `prop`
123 |  * parameter is included
124 |  *
125 |  * @returns a boolean value.
126 |  */
127 | export const isStrict = (prop: PropType, strict: boolean | string = STRICT) => {
128 |   if (typeof strict === 'boolean') return strict;
129 |  
130 |   return (strict ?? 'sao').toLowerCase().includes(prop[0]);
131 | };
132 |  
133 | 134 |
135 |
136 | 141 | 142 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/src/utils/validate.util.ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for src/utils/validate.util.ts 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / src/utils validate.util.ts

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 28/28 29 |
30 | 31 | 32 |
33 | 91.66% 34 | Branches 35 | 22/24 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 5/5 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 28/28 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 24 90 | 25 91 | 26 92 | 27 93 | 28 94 | 291x 95 | 1x 96 | 1x 97 | 1x 98 | 1x 99 | 19x 100 | 19x 101 | 19x 102 | 1x 103 | 24x 104 | 24x 105 | 24x 106 | 1x 107 | 15x 108 | 15x 109 | 15x 110 | 1x 111 | 5x 112 | 5x 113 | 5x 114 | 1x 115 | 53x 116 | 53x 117 | 53x 118 | 53x 119 | 53x 120 | 53x 121 | 53x 122 |  
import { cidrRegex, ipRegex } from './regex.util';
123 | import { Policy } from '../types';
124 |  
125 | import { isValidCron } from 'cron-validator';
126 |  
127 | export function isIP(str: string): boolean {
128 |   return ipRegex.test(str);
129 | }
130 |  
131 | export function isCIDR(str: string): boolean {
132 |   return cidrRegex.test(str);
133 | }
134 |  
135 | export function IP_CIDR(str: string): boolean {
136 |   return isIP(str) || isCIDR(str);
137 | }
138 |  
139 | export function isCRON(str: string) {
140 |   return !/^(\*\s)+\*$/.test(str) && isValidCron(str, { seconds: true, alias: true });
141 | }
142 |  
143 | export function validate<Sub = string, Act = string, Obj = string>(policy: Policy<Sub, Act, Obj>): void {
144 |   const { subject, action, object, location, time } = policy;
145 |  
146 |   if (!subject || !action || !object) throw new Error('Policy is not valid');
147 |   if (location?.length && !location?.every((l) => IP_CIDR(l))) throw new Error('Policy location is not valid');
148 |   if (time?.length && !time?.every((t) => isCRON(t.cron_exp) && t.duration > 0)) throw new Error('Policy time is not valid');
149 | }
150 |  
151 | 152 |
153 |
154 | 159 | 160 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/coverage/lcov-report/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for test 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files test

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 77/77 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 1/1 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 77/77 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
mock.ts 84 |
85 |
100%77/77100%1/1100%0/0100%77/77
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | collectCoverage: true, 3 | coverageDirectory: 'docs/coverage', 4 | coveragePathIgnorePatterns: ['/node_modules/'], 5 | coverageProvider: 'v8', 6 | preset: 'ts-jest', 7 | roots: ['/test'], 8 | testEnvironment: 'node', 9 | coverageReporters: ['json-summary', 'text', 'lcov'], 10 | watchman: true, 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "abacl", 3 | "version": "8.0.8", 4 | "description": "Attribute Based Access Control Library", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "test:debug": "jest --watch", 9 | "doc": "typedoc --out docs src/", 10 | "build": "tsc -p ./tsconfig.json", 11 | "check": "npm run lint && npm run format", 12 | "fresh": "npm run check && npm run clean", 13 | "lint": "eslint . '*/**/*.{js,ts}' --quiet --fix", 14 | "format": "prettier --write \"(src|test)/**/*.(ts|js)\"", 15 | "debug": "node -r ts-node/register --inspect src/index.ts", 16 | "clean": "rm -rf ./dist && rm -rf ./docs && npm run doc && npm run test", 17 | "test": "jest && make-coverage-badge --report-path docs/coverage/coverage-summary.json --output-path coverage-badge.svg" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/vhidvz/abacl.git" 22 | }, 23 | "author": "Vahid V. ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/vhidvz/abacl/issues" 27 | }, 28 | "keywords": [ 29 | "abac", 30 | "authorization", 31 | "access-control", 32 | "attribute-based", 33 | "attribute-based-access-control" 34 | ], 35 | "homepage": "https://github.com/vhidvz/abacl#readme", 36 | "dependencies": { 37 | "cron-parser": "^4.9.0", 38 | "cron-validator": "^1.3.1", 39 | "is-in-subnet": "^4.0.1", 40 | "notation": "^2.0.0" 41 | }, 42 | "devDependencies": { 43 | "@types/jest": "29.5.12", 44 | "@types/node": "^20.11.24", 45 | "@typescript-eslint/eslint-plugin": "7.1.0", 46 | "@typescript-eslint/parser": "7.1.0", 47 | "eslint": "8.57.0", 48 | "eslint-config-prettier": "9.1.0", 49 | "eslint-plugin-prettier": "5.1.3", 50 | "jest": "^29.7.0", 51 | "make-coverage-badge": "1.2.0", 52 | "prettier": "3.2.5", 53 | "ts-jest": "^29.1.2", 54 | "ts-node": "10.9.2", 55 | "typedoc": "0.25.9", 56 | "typescript": "5.3.3" 57 | }, 58 | "publishConfig": { 59 | "access": "public", 60 | "registry": "https://registry.npmjs.org/" 61 | }, 62 | "files": [ 63 | "/dist" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/classes/acl.class.ts: -------------------------------------------------------------------------------- 1 | import { CacheInterface, ControlOptions, Policy, PropValue } from '../types'; 2 | import { ALL, ANY, OK, STRICT } from '../consts'; 3 | import { Permission } from './permission.class'; 4 | import { MemoryDriver } from '../driver'; 5 | import { Grant } from './grant.class'; 6 | import { validate } from '../utils'; 7 | 8 | export interface CanOptions extends ControlOptions { 9 | callable?: (perm: Permission) => boolean | Promise; 10 | } 11 | 12 | export interface AccessControlOptions extends ControlOptions { 13 | driver?: 14 | | 'memory' 15 | | CacheInterface 16 | | (() => CacheInterface | Promise>); 17 | } 18 | 19 | export class AccessControl { 20 | protected driver!: CacheInterface; 21 | protected readonly options: ControlOptions = {}; 22 | 23 | constructor(policies?: Policy[], options?: AccessControlOptions) { 24 | const { strict, driver } = options ?? {}; 25 | 26 | this.setDriver(driver); 27 | this.options.strict = strict ?? STRICT; 28 | 29 | if (policies?.length) policies.forEach((policy) => this.update(policy)); 30 | } 31 | 32 | protected async setDriver(driver?: AccessControlOptions['driver']) { 33 | if (!driver || driver === 'memory') { 34 | this.driver = MemoryDriver.build(); 35 | } else this.driver = typeof driver === 'function' ? await driver() : driver; 36 | } 37 | 38 | async clear(): Promise { 39 | return this.driver.clear(); 40 | } 41 | 42 | async exists(policy: Policy): Promise { 43 | return this.driver.has(policy); 44 | } 45 | 46 | async delete(policy: Policy): Promise { 47 | return this.driver.del(policy); 48 | } 49 | 50 | async update(policy: Policy): Promise { 51 | validate(policy); 52 | 53 | const { action, object, subject, field, filter, location, time } = policy; 54 | const times = time?.map(({ cron_exp, duration }) => ({ cron_exp, duration })); 55 | 56 | return this.driver.set({ action, object, subject, field, filter, location, time: times }); 57 | } 58 | 59 | async can( 60 | subjects: (Sub | (PropValue & ControlOptions))[], 61 | action: Act | (PropValue & ControlOptions), 62 | object: Obj | (PropValue & ControlOptions), 63 | options?: CanOptions, 64 | ): Promise> { 65 | const strict = options?.strict ?? this.options.strict ?? STRICT; 66 | 67 | if (!subjects?.length) throw new Error('No subjects given'); 68 | 69 | const keys = subjects.map((subject) => ({ subject, action, object })); 70 | keys.push(...subjects.map((subject) => ({ subject, action: ANY as Act, object }))); 71 | keys.push(...subjects.map((subject) => ({ subject, action, object: ALL as Obj }))); 72 | keys.push(...subjects.map((subject) => ({ subject, action: ANY as Act, object: ALL as Obj }))); 73 | 74 | const policies = (await Promise.all(keys.map((key) => this.driver.get({ strict, ...key })))).flat(); 75 | 76 | let granted = !!policies?.length; 77 | const grant = new Grant(policies, { strict }); 78 | 79 | if (granted && options?.callable) granted &&= !!(await options.callable(new Permission(granted, grant))); 80 | 81 | return new Permission(granted, grant); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/classes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './acl.class'; 2 | export * from './grant.class'; 3 | export * from './permission.class'; 4 | -------------------------------------------------------------------------------- /src/classes/permission.class.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { CacheKey, ControlOptions, Policy, PropType, TimeOptions } from '../types'; 3 | import { Grant } from './grant.class'; 4 | 5 | export class Permission { 6 | readonly granted: boolean; 7 | readonly grant: Grant; 8 | 9 | constructor(granted: boolean, grant: Grant) { 10 | this.granted = granted; 11 | this.grant = grant; 12 | } 13 | 14 | get policies() { 15 | return this.grant.policies; 16 | } 17 | 18 | has(cKey: CacheKey): boolean { 19 | return this.grant.has(cKey); 20 | } 21 | 22 | scopes(prop: PropType, cKey?: CacheKey): Scope[] { 23 | return this.grant.scopes(prop, cKey); 24 | } 25 | 26 | subjects(cKey?: CacheKey): Sub[] { 27 | return this.grant.subjects(cKey); 28 | } 29 | 30 | time(options?: TimeOptions, cKey?: CacheKey): boolean { 31 | return this.grant.time(options, cKey); 32 | } 33 | 34 | location(ip: string, cKey?: CacheKey): boolean { 35 | return this.grant.location(ip, cKey); 36 | } 37 | 38 | field( 39 | data: any, 40 | cKey?: CacheKey | ((data: any) => CacheKey | Promise>), 41 | ): Promise { 42 | return this.grant.field(data, cKey); 43 | } 44 | 45 | filter( 46 | data: any, 47 | cKey?: CacheKey | ((data: any) => CacheKey | Promise>), 48 | ): Promise { 49 | return this.grant.filter(data, cKey); 50 | } 51 | 52 | static build( 53 | granted: boolean, 54 | policies: Policy[], 55 | options?: ControlOptions, 56 | ): Permission { 57 | return new Permission(granted, new Grant(policies, options)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/consts/acl.const.ts: -------------------------------------------------------------------------------- 1 | // Default Values 2 | 3 | export const OK = 'OK'; 4 | export const SEP = ':'; 5 | export const STRICT = true; 6 | 7 | export const ANY = 'any'; // action keyword 8 | export const ALL = 'all'; // object keyword 9 | 10 | export const NULL = 'null'; // subject keyword 11 | -------------------------------------------------------------------------------- /src/consts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './acl.const'; 2 | -------------------------------------------------------------------------------- /src/driver/index.ts: -------------------------------------------------------------------------------- 1 | export * from './memory'; 2 | -------------------------------------------------------------------------------- /src/driver/memory/index.ts: -------------------------------------------------------------------------------- 1 | export * from './memory.driver'; 2 | export * from './memory.tools'; 3 | -------------------------------------------------------------------------------- /src/driver/memory/memory.driver.ts: -------------------------------------------------------------------------------- 1 | import { CacheInterface, CacheInterfaceOptions, CacheKey, Policy } from '../../types'; 2 | import { key, pattern } from './memory.tools'; 3 | import { OK, SEP } from '../../consts'; 4 | 5 | export type MemoryDriverOptions = CacheInterfaceOptions; 6 | export const DefaultMemoryDriverOptions: MemoryDriverOptions = { sep: SEP }; 7 | 8 | export class MemoryDriver implements CacheInterface { 9 | protected present: Record> = {}; 10 | 11 | constructor(protected options: MemoryDriverOptions = DefaultMemoryDriverOptions) { 12 | this.options.sep = options.sep || SEP; 13 | } 14 | 15 | clear(): typeof OK { 16 | this.present = {}; 17 | return OK; 18 | } 19 | 20 | get(cKey: CacheKey): Policy[] { 21 | const p = pattern(cKey, this.options); 22 | 23 | const policies: Policy[] = []; 24 | for (const index of Object.keys(this.present)) { 25 | if (p.test(index)) policies.push(this.present[index]); 26 | } 27 | 28 | return policies; 29 | } 30 | 31 | set(policy: Policy): typeof OK { 32 | this.present[key(policy, this.options)] = policy; 33 | return OK; 34 | } 35 | 36 | del(policy: Policy): typeof OK { 37 | delete this.present[key(policy, this.options)]; 38 | return OK; 39 | } 40 | 41 | has(policy: Policy): boolean { 42 | return key(policy, this.options) in this.present; 43 | } 44 | 45 | static build(options: MemoryDriverOptions = DefaultMemoryDriverOptions) { 46 | return new MemoryDriver(options); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/driver/memory/memory.tools.ts: -------------------------------------------------------------------------------- 1 | import { CacheKey, ControlOptions, Pattern, Policy, PropType, PropValue } from '../../types'; 2 | import { DefaultMemoryDriverOptions, MemoryDriverOptions } from './memory.driver'; 3 | import { ALL, ANY, NULL, SEP, STRICT } from '../../consts'; 4 | import { isStrict } from '../../utils'; 5 | 6 | export const memoryIgnore = (sep: string) => `[^${sep}][^${sep}]*`; 7 | 8 | export function parse( 9 | prop: Prop, 10 | options: MemoryDriverOptions = DefaultMemoryDriverOptions, 11 | ): PropValue { 12 | options.sep = options.sep || SEP; 13 | const { sep, prefix } = options; 14 | 15 | if (prefix) prop = String(prop).replace(prefix + sep, '') as Prop; 16 | 17 | const [main, scope] = String(prop).split(sep) as [Main, Scope]; 18 | return { main, scope }; 19 | } 20 | 21 | export function key( 22 | polity: Policy, 23 | options: MemoryDriverOptions = DefaultMemoryDriverOptions, 24 | ): string { 25 | options.sep = options.sep || SEP; 26 | const { sep, prefix } = options; 27 | 28 | const subject = parse(polity.subject, options); 29 | const action = parse(polity.action, options); 30 | const object = parse(polity.object, options); 31 | 32 | const subject_key = `${subject.main}${sep}${subject.scope ?? NULL}`; 33 | const action_key = `${action.main}${sep}${action.scope ?? ANY}`; 34 | const object_key = `${object.main}${sep}${object.scope ?? ALL}`; 35 | 36 | if (!prefix) return [subject_key, action_key, object_key].join(sep); 37 | else return [prefix, subject_key, action_key, object_key].join(sep); 38 | } 39 | 40 | export function pattern( 41 | cKey: CacheKey, 42 | options: MemoryDriverOptions = DefaultMemoryDriverOptions, 43 | ): Pattern { 44 | options.sep = options.sep || SEP; 45 | const { sep, prefix } = options; 46 | 47 | const ignore = memoryIgnore(sep); 48 | const ignored = (prop: PropType, scope: T, options: ControlOptions) => 49 | isStrict(prop, options.strict ?? STRICT) ? scope : ignore; 50 | 51 | const regex = (prop?: PropType): string => { 52 | if (prop && cKey[prop]) { 53 | const val = typeof cKey[prop] === 'string' ? parse(cKey[prop]) : (cKey[prop] as PropValue & ControlOptions); 54 | val.scope = val.scope ?? String((prop === 'subject' && NULL) || (prop === 'action' && ANY) || (prop === 'object' && ALL)); 55 | 56 | const { main, scope } = val; 57 | return `${main}${sep}${ignored(prop, scope, { strict: (val as ControlOptions).strict ?? cKey.strict })}`; 58 | } else return [ignore, ignore].join(sep); 59 | }; 60 | 61 | if (!prefix) return RegExp(`^${[regex('subject'), regex('action'), regex('object')].join(sep)}$`); 62 | else return RegExp(`^${[prefix, regex('subject'), regex('action'), regex('object')].join(sep)}$`); 63 | } 64 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { AccessControl } from './classes'; 2 | 3 | export * from './types'; 4 | export * from './utils'; 5 | export * from './consts'; 6 | export * from './driver'; 7 | export * from './classes'; 8 | 9 | export default AccessControl; 10 | -------------------------------------------------------------------------------- /src/types/cache.type.ts: -------------------------------------------------------------------------------- 1 | import { ControlOptions } from './common.type'; 2 | import { Policy } from './policy.type'; 3 | import { OK } from '../consts'; 4 | 5 | export type PropType = 'subject' | 'action' | 'object'; 6 | export type PropValue = { main: M; scope?: S }; 7 | 8 | export type CacheKey = { 9 | subject?: Sub | (PropValue & ControlOptions); 10 | action?: Act | (PropValue & ControlOptions); 11 | object?: Obj | (PropValue & ControlOptions); 12 | } & ControlOptions; 13 | 14 | export interface CacheInterfaceOptions { 15 | sep?: string; 16 | prefix?: string; 17 | } 18 | 19 | export interface CacheInterface { 20 | clear(): typeof OK | Promise; 21 | 22 | get(cKey: CacheKey): Policy[] | Promise[]>; 23 | 24 | set(policy: Policy): typeof OK | Promise; 25 | del(policy: Policy): typeof OK | Promise; 26 | 27 | has(policy: Policy, options?: ControlOptions): boolean | Promise; 28 | } 29 | -------------------------------------------------------------------------------- /src/types/common.type.ts: -------------------------------------------------------------------------------- 1 | export interface ControlOptions { 2 | strict?: boolean | string; 3 | } 4 | 5 | export interface TimeOptions { 6 | currentDate?: Date; 7 | tz?: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cache.type'; 2 | export * from './policy.type'; 3 | export * from './common.type'; 4 | export * from './pattern.type'; 5 | -------------------------------------------------------------------------------- /src/types/pattern.type.ts: -------------------------------------------------------------------------------- 1 | export interface Pattern { 2 | readonly source: string; 3 | test(val: string): boolean | Promise; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/policy.type.ts: -------------------------------------------------------------------------------- 1 | export interface Time { 2 | cron_exp: string; 3 | duration: number; 4 | } 5 | 6 | export interface Policy { 7 | subject: Sub; 8 | 9 | action: Act; 10 | object: Obj; 11 | 12 | time?: Time[]; 13 | field?: string[]; 14 | filter?: string[]; 15 | location?: string[]; 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './other.util'; 2 | export * from './regex.util'; 3 | export * from './strict.util'; 4 | export * from './validate.util'; 5 | -------------------------------------------------------------------------------- /src/utils/other.util.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { Time } from '../types'; 3 | 4 | import parser from 'cron-parser'; 5 | 6 | // eslint-disable-next-line @typescript-eslint/no-var-requires 7 | const { Notation } = require('notation'); 8 | 9 | export function accumulate(...notations: string[][]): string[] { 10 | notations = notations.filter((f) => f.length > 0); 11 | 12 | const first = notations.shift(); 13 | if (!first) return []; 14 | 15 | let neg = first.filter((f) => f.startsWith('!')); 16 | let pos = first.filter((f) => !f.startsWith('!')); 17 | 18 | for (const notation of notations) { 19 | pos = [...new Set([...pos, ...notation.filter((f) => !f.startsWith('!'))])]; 20 | neg = neg.filter((n) => notation.filter((f) => f.startsWith('!')).includes(n)); 21 | } 22 | 23 | return [...pos, ...neg]; 24 | } 25 | 26 | export function accessibility(time: Time, options?: { currentDate?: Date; tz?: string }): boolean { 27 | const { cron_exp, duration } = time; 28 | 29 | const currentDate = options?.currentDate ?? new Date(); 30 | 31 | const prevDate = parser.parseExpression(cron_exp, options).prev(); 32 | const nextDate = new Date(prevDate.getTime() + duration * 1000); 33 | 34 | return currentDate >= prevDate.toDate() && currentDate < nextDate; 35 | } 36 | 37 | export function filterByNotation(data: any, notation: string[], deep_copy = false) { 38 | if (!notation.length) throw new Error('Notation should not empty'); 39 | 40 | if (deep_copy) data = JSON.parse(JSON.stringify(data)); 41 | 42 | if (Array.isArray(data)) return data.map((datum) => new Notation(datum).filter(notation).value); 43 | else return new Notation(data).filter(notation).value; 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/regex.util.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/sindresorhus/ip-regex 2 | 3 | const v4 = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}'; 4 | 5 | const v6segment = '[a-fA-F\\d]{1,4}'; 6 | const v6 = ` 7 | (?: 8 | (?:${v6segment}:){7}(?:${v6segment}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8 9 | (?:${v6segment}:){6}(?:${v4}|:${v6segment}|:)| // 1:2:3:4:5:6:: 1:2:3:4:5:6::8 1:2:3:4:5:6::8 1:2:3:4:5:6::1.2.3.4 10 | (?:${v6segment}:){5}(?::${v4}|(?::${v6segment}){1,2}|:)| // 1:2:3:4:5:: 1:2:3:4:5::7:8 1:2:3:4:5::8 1:2:3:4:5::7:1.2.3.4 11 | (?:${v6segment}:){4}(?:(?::${v6segment}){0,1}:${v4}|(?::${v6segment}){1,3}|:)| // 1:2:3:4:: 1:2:3:4::6:7:8 1:2:3:4::8 1:2:3:4::6:7:1.2.3.4 12 | (?:${v6segment}:){3}(?:(?::${v6segment}){0,2}:${v4}|(?::${v6segment}){1,4}|:)| // 1:2:3:: 1:2:3::5:6:7:8 1:2:3::8 1:2:3::5:6:7:1.2.3.4 13 | (?:${v6segment}:){2}(?:(?::${v6segment}){0,3}:${v4}|(?::${v6segment}){1,5}|:)| // 1:2:: 1:2::4:5:6:7:8 1:2::8 1:2::4:5:6:7:1.2.3.4 14 | (?:${v6segment}:){1}(?:(?::${v6segment}){0,4}:${v4}|(?::${v6segment}){1,6}|:)| // 1:: 1::3:4:5:6:7:8 1::8 1::3:4:5:6:7:1.2.3.4 15 | (?::(?:(?::${v6segment}){0,5}:${v4}|(?::${v6segment}){1,7}|:)) // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::1.2.3.4 16 | )(?:%[0-9a-zA-Z]{1,})? // %eth0 %1 17 | ` 18 | .replace(/\s*\/\/.*$/gm, '') 19 | .replace(/\n/g, '') 20 | .trim(); 21 | 22 | export const ipRegex = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`); 23 | 24 | // https://github.com/silverwind/cidr-regex 25 | 26 | const v4str = `${v4}\\/(3[0-2]|[12]?[0-9])`; 27 | const v6str = `${v6}\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])`; 28 | 29 | export const cidrRegex = new RegExp(`(?:^${v4str}$)|(?:^${v6str}$)`); 30 | -------------------------------------------------------------------------------- /src/utils/strict.util.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from '../types'; 2 | import { STRICT } from '../consts'; 3 | 4 | /** 5 | * The function `isStrict` checks if a given property type matches a strict mode, returning a boolean 6 | * value. 7 | * 8 | * @param {PropType} prop - The `prop` parameter is of type `PropType`. It represents a property that 9 | * is being checked for strictness. 10 | * 11 | * @param {boolean | string} strict - The `strict` parameter is a boolean or string that determines 12 | * whether the code should be executed in strict mode. If it is a boolean, the function will return the 13 | * value of `strict`. If it is a string, the function will check if the first character of the `prop` 14 | * parameter is included 15 | * 16 | * @returns a boolean value. 17 | */ 18 | export const isStrict = (prop: PropType, strict: boolean | string = STRICT) => { 19 | if (typeof strict === 'boolean') return strict; 20 | 21 | return (strict ?? 'sao').toLowerCase().includes(prop[0]); 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/validate.util.ts: -------------------------------------------------------------------------------- 1 | import { cidrRegex, ipRegex } from './regex.util'; 2 | import { Policy } from '../types'; 3 | 4 | import { isValidCron } from 'cron-validator'; 5 | 6 | export function isIP(str: string): boolean { 7 | return ipRegex.test(str); 8 | } 9 | 10 | export function isCIDR(str: string): boolean { 11 | return cidrRegex.test(str); 12 | } 13 | 14 | export function IP_CIDR(str: string): boolean { 15 | return isIP(str) || isCIDR(str); 16 | } 17 | 18 | export function isCRON(str: string) { 19 | return !/^(\*\s)+\*$/.test(str) && isValidCron(str, { seconds: true, alias: true }); 20 | } 21 | 22 | export function validate(policy: Policy): void { 23 | const { subject, action, object, location, time } = policy; 24 | 25 | if (!subject || !action || !object) throw new Error('Policy is not valid'); 26 | if (location?.length && !location?.every((l) => IP_CIDR(l))) throw new Error('Policy location is not valid'); 27 | if (time?.length && !time?.every((t) => isCRON(t.cron_exp) && t.duration > 0)) throw new Error('Policy time is not valid'); 28 | } 29 | -------------------------------------------------------------------------------- /test/classes/acl.test.ts: -------------------------------------------------------------------------------- 1 | import { AccessControl } from '../../src'; 2 | import { Role, policies } from '../mock'; 3 | 4 | describe('test acl class', () => { 5 | let acl: AccessControl; 6 | 7 | it('should define acl instance', async () => { 8 | acl = new AccessControl(policies); 9 | 10 | expect(acl).toBeDefined(); 11 | }); 12 | 13 | it('should check exists policy in db', async () => { 14 | expect(await acl.exists(policies[0])).toBeTruthy(); 15 | 16 | expect(await acl.exists({ subject: 'nothing', action: 'nothing', object: 'nothing' })).toBeFalsy(); 17 | }); 18 | 19 | it('should delete policy from policies', async () => { 20 | expect(await acl.delete(policies[1])).toBeTruthy(); 21 | expect(await acl.exists(policies[1])).toBeFalsy(); 22 | 23 | expect(async () => await acl.update(policies[1])).not.toThrowError(); 24 | expect(await acl.exists(policies[1])).toBeTruthy(); 25 | }); 26 | 27 | it('should return permission by can method', async () => { 28 | expect((await acl.can([Role.User], 'read', 'article')).granted).toBeFalsy(); 29 | expect((await acl.can([Role.User], 'read:own', 'article')).granted).toBeTruthy(); 30 | 31 | expect((await acl.can([Role.User], 'read', 'article:published')).granted).toBeFalsy(); 32 | expect((await acl.can([Role.User], 'read', 'article:published', { strict: true })).granted).toBeFalsy(); 33 | expect((await acl.can([Role.User], 'read', 'article:published', { strict: false })).granted).toBeTruthy(); 34 | 35 | expect((await acl.can([Role.User], 'read', 'article:published', { strict: false, callable: () => false })).granted).toBeFalsy(); 36 | }); 37 | 38 | it('should return true granted on any/all', async () => { 39 | expect((await acl.can([Role.Admin], 'read', 'article')).granted).toBeTruthy(); 40 | expect((await acl.can([Role.Manager], 'read', 'article')).granted).toBeTruthy(); 41 | }); 42 | 43 | it('should return permission with callable', async () => { 44 | const article = { 45 | id: '5f4d1e2c-a7b2-40', 46 | owner: 'vhid.vz@gmail.com', 47 | title: 'sample title', 48 | content: 'sample content', 49 | tags: ['tag'], 50 | }; 51 | 52 | const createPermission = await acl.can([Role.Manager], 'create', 'article'); 53 | expect(await createPermission.field(article, () => ({ action: 'create ' }))).toEqual({ 54 | id: '5f4d1e2c-a7b2-40', 55 | owner: 'vhid.vz@gmail.com', 56 | title: 'sample title', 57 | content: 'sample content', 58 | tags: ['tag'], 59 | }); 60 | 61 | const updatePermission = await acl.can([Role.Manager], 'update', 'article', { strict: false }); 62 | expect(await updatePermission.field(article, async () => ({ action: 'update', strict: false }))).toEqual({ 63 | title: 'sample title', 64 | content: 'sample content', 65 | tags: ['tag'], 66 | }); 67 | }); 68 | 69 | it('should return permission for scopes any/all', async () => { 70 | const permission1 = await acl.can([Role.Manager], 'read', 'article:all', { strict: 'a' }); 71 | const permission2 = await acl.can([Role.Manager], 'read:any', 'article', { strict: 'a' }); 72 | 73 | expect(permission1.policies).toStrictEqual(permission2.policies); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/classes/grant.test.ts: -------------------------------------------------------------------------------- 1 | import { ALL, ANY, Grant } from '../../src'; 2 | import { Role, policies } from '../mock'; 3 | 4 | describe('test grant class', () => { 5 | let grant: Grant; 6 | 7 | it('should define grant instance', () => { 8 | grant = new Grant(policies); 9 | 10 | expect(grant).toBeDefined(); 11 | expect(grant.policies).toEqual(policies); 12 | }); 13 | 14 | it('should check exists policy in db', () => { 15 | expect(grant.exists(policies[0])).toBeTruthy(); 16 | 17 | expect(grant.exists({ subject: 'nothing', action: 'nothing', object: 'nothing' })).toBeFalsy(); 18 | }); 19 | 20 | it('should delete policy from policies', () => { 21 | expect(grant.delete(policies[1])).toBeTruthy(); 22 | expect(grant.exists(policies[1])).toBeFalsy(); 23 | 24 | expect(() => grant.update(policies[1])).not.toThrowError(); 25 | expect(grant.exists(policies[1])).toBeTruthy(); 26 | }); 27 | 28 | it('should verify has pattern exists', () => { 29 | expect(grant.has({ subject: Role.Admin })).toBeTruthy(); 30 | expect(grant.has({ subject: { main: Role.Admin } })).toBeTruthy(); 31 | 32 | expect(grant.has({ action: ANY, object: ALL })).toBeTruthy(); 33 | }); 34 | 35 | it('should return grant scopes', () => { 36 | expect(grant.scopes('subject')).toEqual([]); 37 | 38 | expect(grant.scopes('object')).toEqual(['published']); 39 | expect(grant.scopes('action')).toEqual(['own', 'shared']); 40 | 41 | expect(grant.scopes('action', { subject: 'user', action: 'read', strict: true })).toEqual([]); 42 | expect(grant.scopes('action', { subject: 'user', action: 'read', strict: false })).toEqual(['own', 'shared']); 43 | }); 44 | 45 | it('should return grant subjects', () => { 46 | expect(grant.subjects()).toEqual(['admin', 'guest', 'manager', 'user']); 47 | 48 | expect(grant.subjects({ action: 'read' })).toEqual(['manager', 'guest']); 49 | 50 | expect(grant.subjects({ action: 'read', strict: true })).toEqual(['manager', 'guest']); 51 | expect(grant.subjects({ action: 'read', strict: false })).toEqual(['manager', 'user', 'guest']); 52 | }); 53 | 54 | it('should check time accessibility', () => { 55 | const positiveDate = new Date('Fri Jun 23 2023 10:07:34 GMT+0330 (Iran Standard Time)'); 56 | const negativeDate = new Date('Fri Jun 23 2023 19:07:34 GMT+0330 (Iran Standard Time)'); 57 | 58 | expect(grant.time({ currentDate: positiveDate, tz: 'Asia/Tehran' })).toBeTruthy(); 59 | expect(grant.time({ currentDate: negativeDate, tz: 'Asia/Tehran' })).toBeFalsy(); 60 | 61 | expect(grant.time({ currentDate: positiveDate, tz: 'Asia/Tehran' }, { subject: 'admin' })).toBeTruthy(); 62 | expect(grant.time({ currentDate: negativeDate, tz: 'Asia/Tehran' }, { subject: 'admin' })).toBeTruthy(); 63 | }); 64 | 65 | it('should check location accessibility', () => { 66 | const negativeIP = '192.168.0.10'; 67 | const positiveIP0 = '192.168.2.10'; 68 | const positiveIP1 = '192.168.1.10'; 69 | 70 | expect(grant.location(negativeIP)).toBeFalsy(); 71 | expect(grant.location(positiveIP0)).toBeTruthy(); 72 | expect(grant.location(positiveIP1)).toBeTruthy(); 73 | 74 | expect(grant.location(negativeIP, { subject: 'admin' })).toBeTruthy(); 75 | expect(grant.location(positiveIP0, { subject: 'admin' })).toBeTruthy(); 76 | expect(grant.location(positiveIP1, { subject: 'admin' })).toBeTruthy(); 77 | }); 78 | 79 | it('should field input data', async () => { 80 | const article = { 81 | id: '5f4d1e2c-a7b2-40', 82 | owner: 'vhid.vz@gmail.com', 83 | title: 'sample title', 84 | content: 'sample content', 85 | tags: ['tag'], 86 | }; 87 | 88 | expect(await grant.field(article)).toEqual({ 89 | id: '5f4d1e2c-a7b2-40', 90 | title: 'sample title', 91 | content: 'sample content', 92 | tags: ['tag'], 93 | }); 94 | 95 | expect(await grant.field(article, { subject: 'user' })).toEqual({ 96 | id: '5f4d1e2c-a7b2-40', 97 | title: 'sample title', 98 | content: 'sample content', 99 | tags: ['tag'], 100 | }); 101 | 102 | expect(await grant.field(article, { subject: 'user', action: 'update' })).toEqual({ 103 | id: '5f4d1e2c-a7b2-40', 104 | owner: 'vhid.vz@gmail.com', 105 | title: 'sample title', 106 | content: 'sample content', 107 | tags: ['tag'], 108 | }); 109 | expect(await grant.field(article, { subject: 'user', action: 'update', strict: false })).toEqual({ 110 | title: 'sample title', 111 | content: 'sample content', 112 | tags: ['tag'], 113 | }); 114 | }); 115 | 116 | it('should filter output data', async () => { 117 | const article = { 118 | id: '5f4d1e2c-a7b2-40', 119 | owner: 'vhid.vz@gmail.com', 120 | title: 'sample title', 121 | content: 'sample content', 122 | tags: ['tag'], 123 | }; 124 | 125 | expect(await grant.filter(article)).toEqual({ 126 | id: '5f4d1e2c-a7b2-40', 127 | title: 'sample title', 128 | content: 'sample content', 129 | tags: ['tag'], 130 | }); 131 | 132 | expect(await grant.filter(article, { subject: 'user' })).toEqual({ 133 | id: '5f4d1e2c-a7b2-40', 134 | title: 'sample title', 135 | content: 'sample content', 136 | tags: ['tag'], 137 | }); 138 | 139 | expect(await grant.filter(article, { subject: 'user', action: 'read' })).toEqual({ 140 | id: '5f4d1e2c-a7b2-40', 141 | owner: 'vhid.vz@gmail.com', 142 | title: 'sample title', 143 | content: 'sample content', 144 | tags: ['tag'], 145 | }); 146 | expect(await grant.filter(article, { subject: 'user', action: 'read', strict: false })).toEqual({ 147 | id: '5f4d1e2c-a7b2-40', 148 | title: 'sample title', 149 | content: 'sample content', 150 | tags: ['tag'], 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /test/classes/permission.test.ts: -------------------------------------------------------------------------------- 1 | import { ALL, ANY, Permission } from '../../src'; 2 | import { Role, policies } from '../mock'; 3 | 4 | describe('test permission class', () => { 5 | let perm: Permission; 6 | 7 | it('should define perm instance', () => { 8 | perm = Permission.build(true, policies); 9 | 10 | expect(perm).toBeDefined(); 11 | expect(perm.policies).toEqual(policies); 12 | }); 13 | 14 | it('should verify has pattern exists', () => { 15 | expect(perm.has({ subject: Role.Admin })).toBeTruthy(); 16 | 17 | expect(perm.has({ action: ANY, object: ALL })).toBeTruthy(); 18 | }); 19 | 20 | it('should return perm scopes', () => { 21 | expect(perm.scopes('subject')).toEqual([]); 22 | 23 | expect(perm.scopes('object')).toEqual(['published']); 24 | expect(perm.scopes('action')).toEqual(['own', 'shared']); 25 | 26 | expect(perm.scopes('action', { subject: 'user', action: 'read', strict: true })).toEqual([]); 27 | expect(perm.scopes('action', { subject: 'user', action: 'read', strict: false })).toEqual(['own', 'shared']); 28 | }); 29 | 30 | it('should return perm subjects', () => { 31 | expect(perm.subjects()).toEqual(['admin', 'guest', 'manager', 'user']); 32 | 33 | expect(perm.subjects({ action: 'read' })).toEqual(['guest', 'manager']); 34 | 35 | expect(perm.subjects({ action: 'read', strict: true })).toEqual(['guest', 'manager']); 36 | expect(perm.subjects({ action: 'read', strict: false })).toEqual(['guest', 'manager', 'user']); 37 | }); 38 | 39 | it('should check time accessibility', () => { 40 | const positiveDate = new Date('Fri Jun 23 2023 10:07:34 GMT+0330 (Iran Standard Time)'); 41 | const negativeDate = new Date('Fri Jun 23 2023 19:07:34 GMT+0330 (Iran Standard Time)'); 42 | 43 | expect(perm.time({ currentDate: positiveDate, tz: 'Asia/Tehran' })).toBeTruthy(); 44 | expect(perm.time({ currentDate: negativeDate, tz: 'Asia/Tehran' })).toBeFalsy(); 45 | 46 | expect(perm.time({ currentDate: positiveDate, tz: 'Asia/Tehran' }, { subject: 'admin' })).toBeTruthy(); 47 | expect(perm.time({ currentDate: negativeDate, tz: 'Asia/Tehran' }, { subject: 'admin' })).toBeTruthy(); 48 | }); 49 | 50 | it('should check location accessibility', () => { 51 | const negativeIP = '192.168.0.10'; 52 | const positiveIP0 = '192.168.2.10'; 53 | const positiveIP1 = '192.168.1.10'; 54 | 55 | expect(perm.location(negativeIP)).toBeFalsy(); 56 | expect(perm.location(positiveIP0)).toBeTruthy(); 57 | expect(perm.location(positiveIP1)).toBeTruthy(); 58 | 59 | expect(perm.location(negativeIP, { subject: 'admin' })).toBeTruthy(); 60 | expect(perm.location(positiveIP0, { subject: 'admin' })).toBeTruthy(); 61 | expect(perm.location(positiveIP1, { subject: 'admin' })).toBeTruthy(); 62 | }); 63 | 64 | it('should field input data', async () => { 65 | const article = { 66 | id: '5f4d1e2c-a7b2-40', 67 | owner: 'vhid.vz@gmail.com', 68 | title: 'sample title', 69 | content: 'sample content', 70 | tags: ['tag'], 71 | }; 72 | 73 | expect(await perm.field(article)).toEqual({ 74 | id: '5f4d1e2c-a7b2-40', 75 | title: 'sample title', 76 | content: 'sample content', 77 | tags: ['tag'], 78 | }); 79 | 80 | expect(await perm.field(article, { subject: 'user' })).toEqual({ 81 | id: '5f4d1e2c-a7b2-40', 82 | title: 'sample title', 83 | content: 'sample content', 84 | tags: ['tag'], 85 | }); 86 | 87 | expect(await perm.field(article, { subject: 'user', action: 'update' })).toEqual({ 88 | id: '5f4d1e2c-a7b2-40', 89 | owner: 'vhid.vz@gmail.com', 90 | title: 'sample title', 91 | content: 'sample content', 92 | tags: ['tag'], 93 | }); 94 | expect(await perm.field(article, { subject: 'user', action: 'update', strict: false })).toEqual({ 95 | title: 'sample title', 96 | content: 'sample content', 97 | tags: ['tag'], 98 | }); 99 | }); 100 | 101 | it('should filter output data', async () => { 102 | const article = { 103 | id: '5f4d1e2c-a7b2-40', 104 | owner: 'vhid.vz@gmail.com', 105 | title: 'sample title', 106 | content: 'sample content', 107 | tags: ['tag'], 108 | }; 109 | 110 | expect(await perm.filter(article)).toEqual({ 111 | id: '5f4d1e2c-a7b2-40', 112 | title: 'sample title', 113 | content: 'sample content', 114 | tags: ['tag'], 115 | }); 116 | 117 | expect(await perm.filter(article, { subject: 'user' })).toEqual({ 118 | id: '5f4d1e2c-a7b2-40', 119 | title: 'sample title', 120 | content: 'sample content', 121 | tags: ['tag'], 122 | }); 123 | 124 | expect(await perm.filter(article, { subject: 'user', action: 'read' })).toEqual({ 125 | id: '5f4d1e2c-a7b2-40', 126 | owner: 'vhid.vz@gmail.com', 127 | title: 'sample title', 128 | content: 'sample content', 129 | tags: ['tag'], 130 | }); 131 | expect(await perm.filter(article, { subject: 'user', action: 'read', strict: false })).toEqual({ 132 | id: '5f4d1e2c-a7b2-40', 133 | title: 'sample title', 134 | content: 'sample content', 135 | tags: ['tag'], 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /test/driver/memory/tools.test.ts: -------------------------------------------------------------------------------- 1 | import { key, parse, pattern } from '../../../src'; 2 | 3 | describe('test policy utils', () => { 4 | it('should parse scoped value', () => { 5 | expect(parse('create:own')).toEqual({ main: 'create', scope: 'own' }); 6 | }); 7 | 8 | it('should generate key for indexing', () => { 9 | expect(key({ subject: 'vhid.vz@gmail.com:author', action: 'any', object: 'all' })).toEqual( 10 | 'vhid.vz@gmail.com:author:any:any:all:all', 11 | ); 12 | 13 | expect(key({ subject: 'vahid@wenex.org', action: 'read:group', object: 'articles' })).toEqual( 14 | 'vahid@wenex.org:null:read:group:articles:all', 15 | ); 16 | 17 | expect(key({ subject: 'vahid@wenex.org', action: 'read', object: 'articles#published' }, { sep: '#' })).toEqual( 18 | 'vahid@wenex.org#null#read#any#articles#published', 19 | ); 20 | expect( 21 | key({ subject: 'vahid@wenex.org', action: 'read', object: 'articles#published' }, { sep: '#', prefix: 'abacl' }), 22 | ).toEqual('abacl#vahid@wenex.org#null#read#any#articles#published'); 23 | }); 24 | 25 | it('should return full pattern of policy', () => { 26 | expect(pattern({ subject: 'root' })).toEqual(/^root:null:[^:][^:]*:[^:][^:]*:[^:][^:]*:[^:][^:]*$/); 27 | 28 | expect(pattern({ action: { strict: true, main: 'read', scope: 'own' } })).toEqual( 29 | /^[^:][^:]*:[^:][^:]*:read:own:[^:][^:]*:[^:][^:]*$/, 30 | ); 31 | expect(pattern({ action: { strict: false, main: 'read', scope: 'own' } })).toEqual( 32 | /^[^:][^:]*:[^:][^:]*:read:[^:][^:]*:[^:][^:]*:[^:][^:]*$/, 33 | ); 34 | 35 | expect(pattern({ object: { main: 'article', scope: 'published' } }, { sep: '#' })).toEqual( 36 | /^[^#][^#]*#[^#][^#]*#[^#][^#]*#[^#][^#]*#article#published$/, 37 | ); 38 | expect(pattern({ object: { main: 'article', scope: 'published' } }, { sep: '#', prefix: 'abacl' })).toEqual( 39 | /^abacl#[^#][^#]*#[^#][^#]*#[^#][^#]*#[^#][^#]*#article#published$/, 40 | ); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/mock.ts: -------------------------------------------------------------------------------- 1 | import { Policy } from '../src'; 2 | 3 | export enum Role { 4 | Admin = 'admin', 5 | User = 'user', 6 | Guest = 'guest', 7 | Manager = 'manager', 8 | } 9 | 10 | export const policies: Policy[] = [ 11 | { 12 | subject: Role.Admin, 13 | action: 'any', 14 | object: 'all', 15 | }, 16 | { 17 | subject: Role.Guest, 18 | action: 'read', 19 | object: 'article:published', 20 | }, 21 | { 22 | subject: Role.Guest, 23 | action: 'create:own', 24 | object: 'article:published', 25 | }, 26 | { 27 | subject: Role.Manager, 28 | action: 'any', 29 | object: 'article', 30 | }, 31 | { 32 | subject: Role.Manager, 33 | action: 'read', 34 | object: 'article:published', 35 | }, 36 | { 37 | subject: Role.Manager, 38 | action: 'update:shared', 39 | object: 'article', 40 | field: ['*', '!id', '!owner'], 41 | }, 42 | { 43 | subject: Role.User, 44 | action: 'create:own', 45 | object: 'article', 46 | field: ['*', '!owner'], 47 | location: ['192.168.2.10', '192.168.1.0/24'], 48 | time: [ 49 | { 50 | cron_exp: '* * 7 * * *', // from 7 AM 51 | duration: 9 * 60 * 60, // for 9 hours 52 | }, 53 | ], 54 | }, 55 | { 56 | subject: Role.User, 57 | action: 'read:own', 58 | object: 'article', 59 | }, 60 | { 61 | subject: Role.User, 62 | action: 'read:shared', 63 | object: 'article', 64 | filter: ['*', '!owner'], 65 | }, 66 | { 67 | subject: Role.User, 68 | action: 'delete:own', 69 | object: 'article', 70 | }, 71 | { 72 | subject: Role.User, 73 | action: 'update:own', 74 | object: 'article', 75 | field: ['*', '!id', '!owner'], 76 | }, 77 | ]; 78 | -------------------------------------------------------------------------------- /test/utils/other.test.ts: -------------------------------------------------------------------------------- 1 | import { accumulate, accessibility, filterByNotation, Time } from '../../src'; 2 | 3 | describe('test other utils', () => { 4 | it('should accumulate array of filter', () => { 5 | const filter0 = ['*', 'owner', 'status', 'test', '!id', '!all']; 6 | const filter1 = ['*', '!owner', '!status', 'test', '!id', 'any']; 7 | 8 | const acc0 = accumulate(filter0, filter1); 9 | const acc1 = accumulate(filter1, filter0); 10 | 11 | expect(acc0).toEqual(['*', 'owner', 'status', 'test', 'any', '!id']); 12 | expect(acc1).toEqual(['*', 'test', 'any', 'owner', 'status', '!id']); 13 | }); 14 | 15 | it('should check time for accessibility', () => { 16 | const time: Time = { 17 | cron_exp: '* * 7 * * *', // from 7 AM 18 | duration: 9 * 60 * 60, // for 9 hours 19 | }; 20 | 21 | const positiveDate = new Date('Fri Jun 23 2023 10:07:34 GMT+0330 (Iran Standard Time)'); 22 | const negativeDate = new Date('Fri Jun 23 2023 19:07:34 GMT+0330 (Iran Standard Time)'); 23 | 24 | expect(accessibility(time, { currentDate: positiveDate, tz: 'Asia/Tehran' })).toBeTruthy(); 25 | expect(accessibility(time, { currentDate: negativeDate, tz: 'Asia/Tehran' })).toBeFalsy(); 26 | }); 27 | 28 | it('should filter article by notations', () => { 29 | const article = { 30 | id: 1, 31 | owner: 'user', 32 | title: 'title', 33 | content: 'content', 34 | }; 35 | 36 | expect(filterByNotation(article, ['*', '!id', '!owner'])).toEqual({ 37 | title: 'title', 38 | content: 'content', 39 | }); 40 | expect(filterByNotation([article], ['*', '!id', '!owner'])).toEqual([ 41 | { 42 | title: 'title', 43 | content: 'content', 44 | }, 45 | ]); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/utils/strict.test.ts: -------------------------------------------------------------------------------- 1 | import { isStrict } from '../../src'; 2 | 3 | describe('test strict utils', () => { 4 | it('should check subject', () => { 5 | // boolean test 6 | expect(isStrict('subject', true)).toBeTruthy(); 7 | expect(isStrict('subject', false)).toBeFalsy(); 8 | 9 | // string type test 10 | expect(isStrict('subject', 's')).toBeTruthy(); 11 | expect(isStrict('subject', 'sa')).toBeTruthy(); 12 | expect(isStrict('subject', 'sao')).toBeTruthy(); 13 | 14 | expect(isStrict('subject', '')).toBeFalsy(); 15 | expect(isStrict('subject', 'a')).toBeFalsy(); 16 | expect(isStrict('subject', 'ao')).toBeFalsy(); 17 | 18 | expect(isStrict('subject', undefined)).toBeTruthy(); 19 | expect(isStrict('subject', null as unknown as boolean)).toBeTruthy(); 20 | }); 21 | 22 | it('should check action', () => { 23 | // boolean test 24 | expect(isStrict('action', true)).toBeTruthy(); 25 | expect(isStrict('action', false)).toBeFalsy(); 26 | 27 | // string type test 28 | expect(isStrict('action', 'a')).toBeTruthy(); 29 | expect(isStrict('action', 'sa')).toBeTruthy(); 30 | expect(isStrict('action', 'sao')).toBeTruthy(); 31 | 32 | expect(isStrict('action', '')).toBeFalsy(); 33 | expect(isStrict('action', 's')).toBeFalsy(); 34 | expect(isStrict('action', 'so')).toBeFalsy(); 35 | 36 | expect(isStrict('action', undefined)).toBeTruthy(); 37 | expect(isStrict('action', null as unknown as boolean)).toBeTruthy(); 38 | }); 39 | 40 | it('should check object', () => { 41 | // boolean test 42 | expect(isStrict('object', true)).toBeTruthy(); 43 | expect(isStrict('object', false)).toBeFalsy(); 44 | 45 | // string type test 46 | expect(isStrict('object', 'o')).toBeTruthy(); 47 | expect(isStrict('object', 'so')).toBeTruthy(); 48 | expect(isStrict('object', 'sao')).toBeTruthy(); 49 | 50 | expect(isStrict('object', '')).toBeFalsy(); 51 | expect(isStrict('object', 's')).toBeFalsy(); 52 | expect(isStrict('object', 'sa')).toBeFalsy(); 53 | 54 | expect(isStrict('object', undefined)).toBeTruthy(); 55 | expect(isStrict('object', null as unknown as boolean)).toBeTruthy(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/utils/validate.test.ts: -------------------------------------------------------------------------------- 1 | import { IP_CIDR, Policy, isCIDR, isIP, validate } from '../../src'; 2 | 3 | describe('test validate utils', () => { 4 | it('should verify ip address', () => { 5 | expect(isIP('192.168.1.1')).toBeTruthy(); 6 | expect(isIP('192.168.1.1/24')).toBeFalsy(); 7 | 8 | expect(isIP('fe80:1e15:1fff:fecc')).toBeFalsy(); 9 | expect(isIP('fe80::1e15:1fff:fecc:d300')).toBeTruthy(); 10 | }); 11 | 12 | it('should verify cidr address', () => { 13 | expect(isCIDR('192.168.1.1')).toBeFalsy(); 14 | expect(isCIDR('192.168.1.1/24')).toBeTruthy(); 15 | 16 | expect(isCIDR('fe80::1e15:1fff:fecc:d300')).toBeFalsy(); 17 | expect(isCIDR('fe80::1e15:1fff:fecc:d300/36')).toBeTruthy(); 18 | }); 19 | 20 | it('should verify ip/cidr address', () => { 21 | expect(IP_CIDR('192.168.1.1')).toBeTruthy(); 22 | expect(IP_CIDR('192.168.1.1/24')).toBeTruthy(); 23 | 24 | expect(IP_CIDR('fe80::1e15:1fff:fecc:d300')).toBeTruthy(); 25 | expect(IP_CIDR('fe80::1e15:1fff:fecc:d300/36')).toBeTruthy(); 26 | }); 27 | 28 | it('should validate policy with location and time', () => { 29 | const policy: Policy = { 30 | subject: 'vhid.vz@gmail.com:author', 31 | action: 'read:shared', 32 | object: 'articles:published', 33 | time: [ 34 | { 35 | cron_exp: '* * 7 * * *', // from 7 AM 36 | duration: 9 * 60 * 60, // for 9 hours 37 | }, 38 | ], 39 | location: ['192.168.2.10', '192.168.1.0/24'], 40 | }; 41 | 42 | expect(() => validate(policy)).not.toThrow(); 43 | 44 | expect(() => validate({ ...policy, action: '' })).toThrowError('Policy is not valid'); 45 | 46 | const time = [ 47 | { 48 | cron_exp: '* * * * *', 49 | duration: 28800, 50 | }, 51 | ]; 52 | expect(() => validate({ ...policy, time })).toThrowError('Policy time is not valid'); 53 | 54 | const location = ['192.168.1.', '::::']; 55 | expect(() => validate({ ...policy, location })).toThrowError('Policy location is not valid'); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "allowJs": true, 5 | "sourceMap": true, 6 | "baseUrl": "./src", 7 | "declaration": true, 8 | "module": "CommonJS", 9 | "esModuleInterop": true, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "types": ["node", "jest"], 13 | "removeComments": false, 14 | "outDir": "./dist", 15 | "strict": true 16 | }, 17 | "include": ["src/**/*"] 18 | } 19 | --------------------------------------------------------------------------------