├── .gitignore ├── LICENSE ├── README.md ├── model.conf ├── nest-cli.json ├── nodemon-debug.json ├── nodemon.json ├── package-lock.json ├── package.json ├── policy.csv ├── src ├── app.module.ts ├── config.module.ts ├── controllers │ ├── app.controller.ts │ ├── auth.controller.ts │ ├── role.controller.ts │ ├── user-permission.controller.ts │ ├── user-role.controller.ts │ └── user.controller.ts ├── dto │ ├── add-role-permission.input.ts │ ├── assign-user-role.input.ts │ ├── create-role.input.ts │ ├── login.input.ts │ └── register.input.ts ├── fake-data.ts ├── interfaces │ ├── core-rbac.interface.ts │ ├── index.ts │ └── jwt.interface.ts ├── main.ts ├── resources.ts └── services │ ├── auth.service.ts │ ├── config.service.ts │ ├── index.ts │ ├── jwt.strategy.ts │ ├── role.service.ts │ └── user.service.ts ├── test ├── app.e2e-spec.ts ├── casbin.spec.ts └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | #IDE settings files 107 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mingkai Guo 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 | # nest-authz-example 2 | 3 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 4 | 5 | ## Description 6 | 7 | A demo shows how to use nest-authz to implement role-based access control. 8 | 9 | ## Installation 10 | 11 | ```bash 12 | $ npm install 13 | ``` 14 | 15 | ## Running the app 16 | 17 | ```bash 18 | # development 19 | $ npm run start 20 | 21 | # watch mode 22 | $ npm run start:dev 23 | 24 | # production mode 25 | $ npm run start:prod 26 | ``` 27 | 28 | Open [http://localhost:3000/api](http://localhost:3000/api) to see api. 29 | The api uses Bear auth schema, so if you want to access protected api, you should click the `Authorize` button and set `Bearer yourjwttoken` after login. 30 | 31 | ## Test 32 | 33 | ```bash 34 | # unit tests 35 | $ npm run test 36 | ``` 37 | 38 | ## License 39 | 40 | [MIT licensed](LICENSE). 41 | -------------------------------------------------------------------------------- /model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [role_definition] 8 | g = _, _ 9 | g2 = _, _ 10 | 11 | [policy_effect] 12 | e = some(where (p.eft == allow)) 13 | 14 | [matchers] 15 | m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act || r.sub == "root" 16 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-authz-example", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc -p tsconfig.build.json", 9 | "commit": "git-cz", 10 | "format": "prettier --write \"src/**/*.ts\"", 11 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 12 | "start:dev": "nodemon", 13 | "start:debug": "nodemon --config nodemon-debug.json", 14 | "prestart:prod": "rimraf dist && npm run build", 15 | "start:prod": "node dist/main.js", 16 | "lint": "tslint -p tsconfig.json -c tslint.json", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs/common": "7.0.13", 25 | "@nestjs/core": "7.0.13", 26 | "@nestjs/jwt": "7.0.0", 27 | "@nestjs/passport": "7.0.0", 28 | "@nestjs/platform-express": "7.0.13", 29 | "@nestjs/swagger": "4.5.7", 30 | "class-transformer": "0.3.1", 31 | "class-validator": "0.12.2", 32 | "dotenv": "8.2.0", 33 | "nest-authz": "2.0.0", 34 | "passport": "0.4.1", 35 | "passport-jwt": "4.0.0", 36 | "reflect-metadata": "0.1.13", 37 | "rimraf": "3.0.1", 38 | "rxjs": "6.5.4", 39 | "swagger-ui-express": "4.1.3", 40 | "uuid": "7.0.3" 41 | }, 42 | "devDependencies": { 43 | "@nestjs/testing": "7.0.13", 44 | "@types/express": "4.17.2", 45 | "@types/jest": "24.9.0", 46 | "@types/node": "10.17.13", 47 | "@types/passport-jwt": "3.0.3", 48 | "@types/supertest": "2.0.8", 49 | "@types/uuid": "7.0.3", 50 | "casbin": "4.7.2", 51 | "commitizen": "^4.1.2", 52 | "cz-conventional-changelog": "^3.2.0", 53 | "jest": "24.9.0", 54 | "nodemon": "2.0.2", 55 | "prettier": "1.19.1", 56 | "supertest": "4.0.2", 57 | "ts-jest": "24.3.0", 58 | "ts-node": "8.6.2", 59 | "tsconfig-paths": "3.9.0", 60 | "tslint": "5.12.1", 61 | "typescript": "3.7.5" 62 | }, 63 | "jest": { 64 | "moduleFileExtensions": [ 65 | "js", 66 | "json", 67 | "ts" 68 | ], 69 | "roots": [ 70 | "./test", 71 | "./src" 72 | ], 73 | "testRegex": ".spec.ts$", 74 | "transform": { 75 | "^.+\\.(t|j)s$": "ts-jest" 76 | }, 77 | "coverageDirectory": "../coverage", 78 | "testEnvironment": "node" 79 | }, 80 | "config": { 81 | "commitizen": { 82 | "path": "./node_modules/cz-conventional-changelog" 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /policy.csv: -------------------------------------------------------------------------------- 1 | p, superuser, user, read:any 2 | p, manager, user_roles, read:any 3 | p, guest, user, read:own 4 | 5 | g, alice, superuser 6 | g, bob, guest 7 | g, tom, manager 8 | 9 | g, users_list, user 10 | g, user_roles, user 11 | g, user_permissions, user 12 | g, roles_list, role 13 | g, role_permissions, role 14 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, ExecutionContext } from '@nestjs/common'; 2 | import * as casbin from 'casbin'; 3 | 4 | import { AppController } from './controllers/app.controller'; 5 | import { UserController } from './controllers/user.controller'; 6 | import { RoleController } from './controllers/role.controller'; 7 | import { AuthController } from './controllers/auth.controller'; 8 | import { UserPermissionController } from './controllers/user-permission.controller'; 9 | import { UserRoleController } from './controllers/user-role.controller'; 10 | 11 | import { AuthZModule, AUTHZ_ENFORCER } from 'nest-authz'; 12 | import { PassportModule } from '@nestjs/passport'; 13 | import { JwtModule } from '@nestjs/jwt'; 14 | import { ConfigModule } from './config.module'; 15 | 16 | import { 17 | AuthService, 18 | UserService, 19 | RoleService, 20 | JwtStrategy, 21 | ConfigService, 22 | } from './services'; 23 | 24 | @Module({ 25 | imports: [ 26 | ConfigModule, 27 | PassportModule.register({ 28 | defaultStrategy: 'jwt', 29 | }), 30 | JwtModule.register({ 31 | secret: 'secretKey', 32 | }), 33 | AuthZModule.register({ 34 | imports: [ConfigModule], 35 | enforcerProvider: { 36 | provide: AUTHZ_ENFORCER, 37 | useFactory: async (configSrv: ConfigService) => { 38 | const config = await configSrv.getAuthConfig(); 39 | return casbin.newEnforcer(config.model, config.policy); 40 | }, 41 | inject: [ConfigService], 42 | }, 43 | usernameFromContext: (ctx: ExecutionContext) => { 44 | const request = ctx.switchToHttp().getRequest(); 45 | return request.user && request.user.username; 46 | }, 47 | }), 48 | ], 49 | controllers: [ 50 | AppController, 51 | AuthController, 52 | UserController, 53 | RoleController, 54 | UserRoleController, 55 | UserPermissionController, 56 | ], 57 | providers: [AuthService, UserService, JwtStrategy, RoleService], 58 | }) 59 | export class AppModule {} 60 | -------------------------------------------------------------------------------- /src/config.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | import { ConfigService } from './services'; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [ConfigService], 9 | exports: [ConfigService], 10 | }) 11 | export class ConfigModule {} 12 | -------------------------------------------------------------------------------- /src/controllers/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | import { Resources } from '../resources'; 4 | 5 | @Controller() 6 | export class AppController { 7 | @Get() 8 | root() { 9 | return 'Hello World!'; 10 | } 11 | 12 | @Get('/resources') 13 | resources() { 14 | return Resources; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/controllers/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body } from '@nestjs/common'; 2 | import uuid = require('uuid'); 3 | 4 | import { AuthService } from '../services/auth.service'; 5 | 6 | import { ApiTags, ApiOperation } from '@nestjs/swagger'; 7 | import { LoginInput } from '../dto/login.input'; 8 | import { RegisterInput } from '../dto/register.input'; 9 | import { User } from '../interfaces'; 10 | 11 | @ApiTags('Auth') 12 | @Controller('auth') 13 | export class AuthController { 14 | constructor(private readonly authSrv: AuthService) {} 15 | 16 | @ApiOperation({ 17 | summary: 'User login', 18 | }) 19 | @Post('login') 20 | async login(@Body() credentials: LoginInput) { 21 | return this.authSrv.login(credentials.username, credentials.password); 22 | } 23 | 24 | @ApiOperation({ 25 | summary: 'User register', 26 | }) 27 | @Post('register') 28 | async register(@Body() userDto: RegisterInput) { 29 | const data: User = Object.assign({ id: uuid.v1() }, userDto); 30 | return this.authSrv.register(data); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/controllers/role.controller.ts: -------------------------------------------------------------------------------- 1 | import { AddRolePermissionInput } from '../dto/add-role-permission.input'; 2 | import { 3 | Controller, 4 | Get, 5 | UseGuards, 6 | Param, 7 | Post, 8 | Body, 9 | NotFoundException, 10 | } from '@nestjs/common'; 11 | import { RoleService } from '../services/role.service'; 12 | 13 | import { 14 | AuthZGuard, 15 | AuthActionVerb, 16 | AuthPossession, 17 | UsePermissions, 18 | } from 'nest-authz'; 19 | 20 | import { AuthGuard } from '@nestjs/passport'; 21 | import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; 22 | 23 | import { Role } from '../interfaces'; 24 | 25 | import { Resource, ResourceGroup } from '../resources'; 26 | 27 | import { CreateRoleInput } from '../dto/create-role.input'; 28 | import { v4 as uuidv4 } from 'uuid'; 29 | 30 | @ApiTags('Role') 31 | @ApiBearerAuth() 32 | @Controller() 33 | export class RoleController { 34 | constructor(private readonly roleSrv: RoleService) {} 35 | 36 | @ApiOperation({ 37 | summary: 'Find all roles', 38 | }) 39 | @Get('roles') 40 | @UseGuards(AuthGuard(), AuthZGuard) 41 | @UsePermissions({ 42 | action: AuthActionVerb.READ, 43 | resource: Resource.ROLES_LIST, 44 | possession: AuthPossession.ANY, 45 | }) 46 | async findAllRoles() { 47 | return await this.roleSrv.findAllRoles(); 48 | } 49 | 50 | @ApiOperation({ 51 | summary: 'Create a new role', 52 | }) 53 | @Post('roles') 54 | @UseGuards(AuthGuard(), AuthZGuard) 55 | @UsePermissions({ 56 | action: AuthActionVerb.CREATE, 57 | resource: ResourceGroup.USER, 58 | possession: AuthPossession.ANY, 59 | }) 60 | async createRole(@Body() role: CreateRoleInput) { 61 | const data: Role = Object.assign( 62 | { 63 | id: uuidv4(), 64 | }, 65 | role, 66 | ); 67 | return this.roleSrv.addRole(data); 68 | } 69 | 70 | @ApiOperation({ 71 | summary: 'Get all permissions assigned to the given role', 72 | }) 73 | @Get('/roles/:id/permissions') 74 | @UseGuards(AuthGuard(), AuthZGuard) 75 | @UsePermissions({ 76 | action: AuthActionVerb.READ, 77 | resource: Resource.ROLE_PERMISSIONS, 78 | possession: AuthPossession.ANY, 79 | }) 80 | async findRolePermissions(@Param('id') id: string) { 81 | const role = await this.roleSrv.findById(id); 82 | 83 | if (!role) { 84 | throw new NotFoundException('The role not found'); 85 | } 86 | return this.roleSrv.findPermissionsForRole(role.name); 87 | } 88 | 89 | @ApiOperation({ 90 | summary: 'Grant a permission to the given role', 91 | }) 92 | @Post('/roles/:id/permissions') 93 | @UseGuards(AuthGuard(), AuthZGuard) 94 | @UsePermissions({ 95 | action: AuthActionVerb.CREATE, 96 | resource: Resource.ROLE_PERMISSIONS, 97 | possession: AuthPossession.ANY, 98 | }) 99 | async addRolePermission( 100 | @Param('id') id: string, 101 | @Body() addPermissionDto: AddRolePermissionInput, 102 | ) { 103 | const role = await this.roleSrv.findById(id); 104 | 105 | if (!role) { 106 | throw new NotFoundException('The role not found'); 107 | } 108 | 109 | const { operation, resource } = addPermissionDto; 110 | return this.roleSrv.grantPermission(role.name, operation, resource); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/controllers/user-permission.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | UseGuards, 5 | Param, 6 | Post, 7 | Body, 8 | NotFoundException, 9 | Delete, 10 | Req, 11 | } from '@nestjs/common'; 12 | import { UserService } from '../services/user.service'; 13 | import { 14 | AuthZGuard, 15 | AuthActionVerb, 16 | AuthPossession, 17 | UsePermissions, 18 | } from 'nest-authz'; 19 | 20 | import { AuthGuard } from '@nestjs/passport'; 21 | import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; 22 | 23 | import { Resource } from '../resources'; 24 | 25 | @ApiTags('UserPermission') 26 | @ApiBearerAuth() 27 | @Controller() 28 | export class UserPermissionController { 29 | constructor(private readonly usersSrv: UserService) {} 30 | 31 | @ApiOperation({ 32 | summary: 'Get all permissions owned by the given user', 33 | }) 34 | @Get('/users/:id/permissions') 35 | @UseGuards(AuthGuard(), AuthZGuard) 36 | @UsePermissions({ 37 | action: AuthActionVerb.READ, 38 | resource: Resource.USER_ROLES, 39 | possession: AuthPossession.OWN_ANY, 40 | isOwn: (req: any): boolean => { 41 | return Number(req.user.id) === Number(req.params.id); 42 | }, 43 | }) 44 | async findUserPermissions(@Param('id') id: string) { 45 | const user = await this.usersSrv.findById(id); 46 | 47 | if (!user) { 48 | throw new NotFoundException('The user not found'); 49 | } 50 | 51 | if (user.username === 'root') { 52 | // built-in superuser with all permissions 53 | return ['*']; 54 | } 55 | 56 | return this.usersSrv.userPermissions(user.username); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/controllers/user-role.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | UseGuards, 5 | Param, 6 | Post, 7 | Delete, 8 | Body, 9 | NotFoundException, 10 | } from '@nestjs/common'; 11 | import { RoleService, UserService } from '../services'; 12 | 13 | import { 14 | AuthZGuard, 15 | AuthActionVerb, 16 | AuthPossession, 17 | UsePermissions, 18 | } from 'nest-authz'; 19 | 20 | import { AuthGuard } from '@nestjs/passport'; 21 | import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; 22 | 23 | import { AssignUserRoleInput } from '../dto/assign-user-role.input'; 24 | 25 | import { Resource } from '../resources'; 26 | import { BadRequestException } from '@nestjs/common'; 27 | import { Query } from '@nestjs/common'; 28 | 29 | @ApiTags('UserRole') 30 | @ApiBearerAuth() 31 | @Controller() 32 | export class UserRoleController { 33 | constructor( 34 | private readonly usersSrv: UserService, 35 | private readonly roleSrv: RoleService, 36 | ) {} 37 | 38 | // Only subject who can read any USER_ROLES or the resource owner can access this route. 39 | // The function `isOwn` is used to determine whether the subject is the owner of this resource or not. 40 | // A default `isOwn` function which returns false will be used if you omit it. 41 | @ApiOperation({ 42 | summary: 'Get all roles owned by the given user', 43 | }) 44 | @Get('/users/:id/roles') 45 | @UseGuards(AuthGuard(), AuthZGuard) 46 | @UsePermissions({ 47 | action: AuthActionVerb.READ, 48 | resource: Resource.USER_ROLES, 49 | possession: AuthPossession.OWN_ANY, 50 | isOwn: (req: any): boolean => { 51 | return Number(req.user.id) === Number(req.params.id); 52 | }, 53 | }) 54 | async findUserRoles(@Param('id') id: string) { 55 | const user = await this.usersSrv.findById(id); 56 | 57 | if (!user) { 58 | throw new NotFoundException('The user not found'); 59 | } 60 | 61 | return this.usersSrv.assignedRoles(user.username); 62 | } 63 | 64 | @ApiOperation({ 65 | summary: 'Assign a role to the given user', 66 | }) 67 | @Post('/users/:id/roles') 68 | @UseGuards(AuthGuard(), AuthZGuard) 69 | @UsePermissions({ 70 | action: AuthActionVerb.CREATE, 71 | resource: Resource.USER_ROLES, 72 | possession: AuthPossession.ANY, 73 | }) 74 | async assignUserRole( 75 | @Param('id') id: string, 76 | @Body() role: AssignUserRoleInput, 77 | ) { 78 | const user = await this.usersSrv.findById(id); 79 | 80 | if (!user) { 81 | throw new NotFoundException('The user not found'); 82 | } 83 | 84 | if (user.username === 'root') { 85 | throw new BadRequestException( 86 | 'Built in root user role can not be changed', 87 | ); 88 | } 89 | 90 | return this.roleSrv.assignUser(user.username, role.roleName); 91 | } 92 | 93 | @ApiOperation({ 94 | summary: 'Delete one of the roles owned by a given user', 95 | }) 96 | @Delete('/users/:id/roles/:roleId') 97 | @UseGuards(AuthGuard(), AuthZGuard) 98 | @UsePermissions({ 99 | action: AuthActionVerb.DELETE, 100 | resource: Resource.USER_ROLES, 101 | possession: AuthPossession.ANY, 102 | }) 103 | async deAssignUserRole( 104 | @Param('id') id: string, 105 | @Param('roleId') roleId: string, 106 | ) { 107 | const [user, role] = await Promise.all([ 108 | this.usersSrv.findById(id), 109 | this.roleSrv.findById(roleId), 110 | ]); 111 | 112 | if (!user) { 113 | throw new NotFoundException('The user not found'); 114 | } 115 | 116 | if (!role) { 117 | throw new NotFoundException('The role not found'); 118 | } 119 | 120 | return this.roleSrv.deAssignUser(user.username, role.name); 121 | } 122 | 123 | @ApiOperation({ 124 | summary: 'Delete roles owned by a given user according to the where filter', 125 | }) 126 | @Delete('/users/:id/roles') 127 | @UseGuards(AuthGuard(), AuthZGuard) 128 | @UsePermissions({ 129 | action: AuthActionVerb.DELETE, 130 | resource: Resource.USER_ROLES, 131 | possession: AuthPossession.ANY, 132 | }) 133 | async deAssignUserRoles(@Param('id') id: string, @Query('where') where: any) { 134 | throw new BadRequestException('The method not implemented'); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/controllers/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | UseGuards, 5 | Req, 6 | } from '@nestjs/common'; 7 | import { UserService } from '../services/user.service'; 8 | import { 9 | AuthZGuard, 10 | AuthActionVerb, 11 | AuthPossession, 12 | UsePermissions, 13 | } from 'nest-authz'; 14 | 15 | import { AuthGuard } from '@nestjs/passport'; 16 | import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; 17 | 18 | import { Resource } from '../resources'; 19 | 20 | import { Request } from 'express'; 21 | 22 | @ApiTags('User') 23 | @ApiBearerAuth() 24 | @Controller() 25 | export class UserController { 26 | constructor(private readonly usersSrv: UserService) {} 27 | 28 | @ApiOperation({ 29 | summary: 'Find all users', 30 | }) 31 | @Get('users') 32 | @UseGuards(AuthGuard(), AuthZGuard) 33 | @UsePermissions({ 34 | action: AuthActionVerb.READ, 35 | resource: Resource.USERS_LIST, 36 | possession: AuthPossession.ANY, 37 | }) 38 | async findUsers() { 39 | return await this.usersSrv.findAll(); 40 | } 41 | 42 | @ApiOperation({ 43 | summary: 'Get own info', 44 | }) 45 | @Get('users/me') 46 | @UseGuards(AuthGuard()) 47 | async printCurrentUser(@Req() request: Request) { 48 | return request.user; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/dto/add-role-permission.input.ts: -------------------------------------------------------------------------------- 1 | import { AuthAction } from 'nest-authz'; 2 | import { Resource, ResourceGroup } from '../resources'; 3 | import { ApiProperty } from '@nestjs/swagger'; 4 | 5 | export class AddRolePermissionInput { 6 | @ApiProperty({ 7 | enum: [ 8 | AuthAction.CREATE_ANY, 9 | AuthAction.CREATE_OWN, 10 | AuthAction.DELETE_ANY, 11 | AuthAction.DELETE_OWN, 12 | AuthAction.READ_ANY, 13 | AuthAction.READ_OWN, 14 | AuthAction.UPDATE_ANY, 15 | AuthAction.UPDATE_OWN, 16 | ], 17 | }) 18 | operation: AuthAction; 19 | 20 | @ApiProperty({ 21 | enum: [ 22 | Resource.ROLES_LIST, 23 | Resource.ROLE_PERMISSIONS, 24 | Resource.USERS_LIST, 25 | Resource.USER_PERMISSIONS, 26 | Resource.USER_ROLES, 27 | ResourceGroup.ROLE, 28 | ResourceGroup.USER, 29 | ], 30 | }) 31 | resource: Resource | ResourceGroup; 32 | } 33 | -------------------------------------------------------------------------------- /src/dto/assign-user-role.input.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class AssignUserRoleInput { 4 | @ApiProperty() 5 | roleName: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/dto/create-role.input.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; 2 | 3 | export class CreateRoleInput { 4 | @ApiProperty() 5 | name: string; 6 | @ApiPropertyOptional() 7 | description: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/dto/login.input.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class LoginInput { 4 | @ApiProperty({ 5 | minLength: 6, 6 | example: 'root', 7 | }) 8 | username: string; 9 | 10 | @ApiProperty({ 11 | maxLength: 65, 12 | example: 'password', 13 | }) 14 | password: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/dto/register.input.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class RegisterInput { 4 | @ApiProperty({ 5 | minLength: 8, 6 | }) 7 | username: string; 8 | @ApiProperty({ 9 | maxLength: 20, 10 | minLength: 6, 11 | }) 12 | password: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/fake-data.ts: -------------------------------------------------------------------------------- 1 | import { User } from './interfaces'; 2 | 3 | export const users: User[] = [ 4 | { 5 | id: 'c96da562-96f1-4250-aecb-bc552485733f', 6 | username: 'root', 7 | password: 'password', 8 | }, 9 | { 10 | id: '09466d6c-151e-4fe6-aa29-5ca85c586b6c', 11 | username: 'alice', 12 | password: 'password', 13 | }, 14 | { 15 | id: '3e59af18-d38a-4c19-92d4-7bd3032b36f0', 16 | username: 'bob', 17 | password: 'password', 18 | }, 19 | { 20 | id: '4cf3292b-ee40-4e91-89d6-027bc877d193', 21 | username: 'tom', 22 | password: 'password', 23 | }, 24 | ]; 25 | -------------------------------------------------------------------------------- /src/interfaces/core-rbac.interface.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: string; 3 | username: string; 4 | password: string; 5 | } 6 | 7 | export interface Role { 8 | id: string; 9 | name: string; 10 | description: string; 11 | } 12 | 13 | export interface CoreRBACUser { 14 | // creates a new RBAC user 15 | addUser(user: User): Promise; 16 | // deletes an existing user from the RBAC database. 17 | deleteUser(username: string): Promise; 18 | // returns the set of roles assigned to a given user 19 | assignedRoles(username: string): Promise; 20 | // returns the permissions a given user gets through his/her assigned roles. 21 | userPermissions(username: string): Promise; 22 | } 23 | 24 | export interface CoreRBACRole { 25 | // creates a new role 26 | addRole(role: Role): Promise; 27 | // deletes an existing role from the RBAC database 28 | deleteRole(role: string): Promise; 29 | // assigns a user to a role 30 | assignUser(user: string, role: string): Promise; 31 | // deletes the assignment of the user `user` to the role `role` 32 | deAssignUser(user: string, role: string): Promise; 33 | // grants a role the permission to perform an operation on an object to a role. 34 | grantPermission(role: string, operation: any, object: any): Promise; 35 | // revokes the permission to perform an operation on an object from the set of permissions assigned to a role 36 | revokePermission(role: string, operation: any, object: any): Promise; 37 | 38 | // review functions for core rbac 39 | 40 | // returns the set of users assigned to a given role 41 | assignedUsers(role: string): Promise; 42 | // returns the set of permissions(object, operation) granted to a given role 43 | rolePermissions(role: string): Promise; 44 | } 45 | -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './core-rbac.interface'; 2 | export * from './jwt.interface'; 3 | -------------------------------------------------------------------------------- /src/interfaces/jwt.interface.ts: -------------------------------------------------------------------------------- 1 | export interface JwtPayload { 2 | id: string; 3 | username: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | 8 | const options = new DocumentBuilder() 9 | .setTitle('nest-authz example') 10 | .setDescription('An nestjs-authz example demonstrating its usage') 11 | .setVersion('1.0') 12 | .addBearerAuth({ 13 | type: 'apiKey', 14 | name: 'Authorization', 15 | in: 'header', 16 | }) 17 | .build(); 18 | const document = SwaggerModule.createDocument(app, options); 19 | SwaggerModule.setup('api', app, document); 20 | 21 | await app.listen(3000); 22 | } 23 | bootstrap(); 24 | -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- 1 | export enum ResourceGroup { 2 | USER = 'user', 3 | ROLE = 'role', 4 | // ... other resource groups 5 | } 6 | 7 | export enum Resource { 8 | USERS_LIST = 'users_list', 9 | USER_ROLES = 'user_roles', 10 | USER_PERMISSIONS = 'user_permissions', 11 | ROLES_LIST = 'roles_list', 12 | ROLE_PERMISSIONS = 'role_permission', 13 | } 14 | 15 | export interface ResourceMeta { 16 | name: ResourceGroup | Resource; 17 | displayName?: string; 18 | description?: string; 19 | isGroup: boolean; 20 | children?: ResourceMeta[]; 21 | } 22 | 23 | export const Resources: ResourceMeta[] = [ 24 | { 25 | name: ResourceGroup.USER, 26 | displayName: 'User', 27 | isGroup: true, 28 | children: [ 29 | { 30 | name: Resource.USERS_LIST, 31 | displayName: 'Users List', 32 | description: 'Users list', 33 | isGroup: false, 34 | }, 35 | { 36 | name: Resource.USER_ROLES, 37 | displayName: 'User Roles', 38 | description: 'Roles owned by user', 39 | isGroup: false, 40 | }, 41 | { 42 | name: Resource.USER_PERMISSIONS, 43 | displayName: 'User Permissions', 44 | description: 'Permissions owned by user', 45 | isGroup: false, 46 | }, 47 | ], 48 | }, 49 | { 50 | name: ResourceGroup.ROLE, 51 | displayName: 'Role', 52 | isGroup: true, 53 | children: [ 54 | { 55 | name: Resource.ROLES_LIST, 56 | displayName: 'Roles List', 57 | description: 'Roles List', 58 | isGroup: false, 59 | }, 60 | { 61 | name: Resource.ROLE_PERMISSIONS, 62 | displayName: 'Role Permission', 63 | description: 'Role Permission', 64 | isGroup: false, 65 | }, 66 | ], 67 | }, 68 | ]; 69 | -------------------------------------------------------------------------------- /src/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { JwtService } from '@nestjs/jwt'; 3 | import { UserService } from './user.service'; 4 | import { JwtPayload, User } from '../interfaces'; 5 | 6 | @Injectable() 7 | export class AuthService { 8 | constructor( 9 | private readonly userSrv: UserService, 10 | private readonly jwtSrv: JwtService, 11 | ) {} 12 | 13 | async validateJwt(payload: JwtPayload): Promise { 14 | return await this.userSrv.findById(payload.id); 15 | } 16 | 17 | async login(username: string, password: string) { 18 | const user = await this.userSrv.verifyCredentials(username, password); 19 | 20 | if (!user) { 21 | throw new UnauthorizedException('username or password incorrect!'); 22 | } 23 | 24 | const payload = { 25 | id: user.id, 26 | username: user.username, 27 | }; 28 | 29 | const token = this.jwtSrv.sign(payload); 30 | return { 31 | userId: user.id, 32 | accessToken: token, 33 | }; 34 | } 35 | 36 | async register(user: User) { 37 | await this.userSrv.addUser(user); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/services/config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class ConfigService { 5 | async getAuthConfig() { 6 | return new Promise<{ model: string; policy: string }>((resolve, reject) => { 7 | setTimeout(() => { 8 | return resolve({ 9 | model: 'model.conf', 10 | policy: 'policy.csv', 11 | }); 12 | }, 200); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth.service'; 2 | export * from './jwt.strategy'; 3 | export * from './role.service'; 4 | export * from './user.service'; 5 | export * from './config.service'; 6 | -------------------------------------------------------------------------------- /src/services/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | import { PassportStrategy } from '@nestjs/passport'; 4 | import { Strategy, ExtractJwt } from 'passport-jwt'; 5 | import { JwtPayload } from '../interfaces'; 6 | 7 | @Injectable() 8 | export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { 9 | constructor(private readonly authSrv: AuthService) { 10 | super({ 11 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 12 | secretOrKey: 'secretKey', 13 | }); 14 | } 15 | 16 | async validate(payload: JwtPayload) { 17 | const user = await this.authSrv.validateJwt(payload); 18 | 19 | if (!user) { 20 | throw new UnauthorizedException(); 21 | } 22 | 23 | return user; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/services/role.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BadRequestException, 3 | NotFoundException, 4 | Injectable, 5 | } from '@nestjs/common'; 6 | import { Role, CoreRBACRole } from '../interfaces'; 7 | import { AuthAction } from 'nest-authz'; 8 | import { AuthZRBACService } from 'nest-authz'; 9 | import { UserService } from './user.service'; 10 | import { ResourceGroup, Resource } from '../resources'; 11 | 12 | const roles: Role[] = [ 13 | { 14 | id: '8ec0934d-7732-4f05-9c3b-2e0284bf54c6', 15 | name: 'superuser', 16 | description: 'can read any user', 17 | }, 18 | { 19 | id: '2d6f41f2-33ca-43e2-a415-ef163649c60d', 20 | name: 'guest', 21 | description: 'can read own user', 22 | }, 23 | { 24 | id: '2d6f41f2-33ca-43e2-a415-ef163649c60d', 25 | name: 'manager', 26 | description: ' can read any user', 27 | }, 28 | ]; 29 | 30 | @Injectable() 31 | export class RoleService implements CoreRBACRole { 32 | constructor( 33 | private readonly userSrv: UserService, 34 | private readonly authzService: AuthZRBACService, 35 | ) {} 36 | async addRole(role: Role) { 37 | const isExists = await this.exists(role.name); 38 | if (isExists) { 39 | throw new BadRequestException(`The role ${role.name} is already exists`); 40 | } 41 | 42 | const data: Role = Object.assign({}, { id: '1' }, role); 43 | roles.push(data); 44 | } 45 | 46 | async deleteRole(role: string) { 47 | const index = await this.findIndexByName(role); 48 | 49 | if (index === -1) { 50 | throw new NotFoundException(`The role ${role} not found.`); 51 | } 52 | 53 | roles.splice(index, 1); 54 | 55 | await this.authzService.deleteRole(role); 56 | } 57 | 58 | async assignUser(user: string, role: string): Promise { 59 | const [isUserExists, isRoleExists] = await Promise.all([ 60 | this.userSrv.exists(user), 61 | this.exists(role), 62 | ]); 63 | if (!isUserExists) { 64 | throw new NotFoundException(`The user ${user} not found`); 65 | } 66 | 67 | if (!isRoleExists) { 68 | throw new NotFoundException(`The role ${role} not found`); 69 | } 70 | 71 | return this.authzService.addRoleForUser(user, role); 72 | } 73 | 74 | async deAssignUser(user: string, role: string): Promise { 75 | const [isUserExists, isRoleExists] = await Promise.all([ 76 | this.userSrv.exists(user), 77 | this.exists(role), 78 | ]); 79 | if (!isUserExists) { 80 | throw new NotFoundException(`The user ${user} not found`); 81 | } 82 | 83 | if (!isRoleExists) { 84 | throw new NotFoundException(`The role ${role} not found`); 85 | } 86 | 87 | const hasRole = await this.authzService.hasRoleForUser(user, role); 88 | 89 | if (!hasRole) { 90 | throw new BadRequestException( 91 | `The user ${user} does not have role ${role}`, 92 | ); 93 | } 94 | 95 | return this.authzService.deleteRoleForUser(user, role); 96 | } 97 | 98 | async grantPermission( 99 | role: string, 100 | operation: AuthAction, 101 | object: ResourceGroup | Resource, 102 | ): Promise { 103 | const isRoleExists = await this.exists(role); 104 | if (!isRoleExists) { 105 | throw new NotFoundException(`The role ${role} not found`); 106 | } 107 | 108 | return this.authzService.addPermissionForUser(role, object, operation); 109 | } 110 | 111 | async revokePermission( 112 | role: string, 113 | operation: AuthAction, 114 | object: ResourceGroup | Resource, 115 | ) { 116 | const isRoleExists = await this.exists(role); 117 | if (!isRoleExists) { 118 | throw new NotFoundException(`The role ${role} not found`); 119 | } 120 | 121 | if (!this.authzService.hasPermissionForUser(role, object, operation)) { 122 | throw new BadRequestException( 123 | `The permission ${operation} ${object} isn't assigned to the role ${role}`, 124 | ); 125 | } 126 | 127 | return this.authzService.deletePermissionForUser(role, object, operation); 128 | } 129 | 130 | async assignedUsers(role: string): Promise { 131 | const isRoleExists = await this.exists(role); 132 | if (!isRoleExists) { 133 | throw new NotFoundException(`The role ${role} not found`); 134 | } 135 | 136 | return this.authzService.getUsersForRole(role); 137 | } 138 | 139 | async rolePermissions(role: string): Promise { 140 | const isRoleExists = await this.exists(role); 141 | if (!isRoleExists) { 142 | throw new NotFoundException(`The role ${role} not found`); 143 | } 144 | 145 | return this.authzService.getPermissionsForUser(role); 146 | } 147 | 148 | async findPermissionsForRole(role: string) { 149 | const isRoleExists = await this.exists(role); 150 | if (!isRoleExists) { 151 | throw new NotFoundException(`The role ${role} not found`); 152 | } 153 | 154 | return this.authzService.getPermissionsForUser(role); 155 | } 156 | 157 | async exists(name: string) { 158 | for (const role of roles) { 159 | if (role.name === name) { 160 | return true; 161 | } 162 | } 163 | return false; 164 | } 165 | 166 | private async findIndexByName(name: string): Promise { 167 | let index = -1; 168 | for (let i = 0; i < roles.length; i++) { 169 | const item = roles[i]; 170 | if (item.name === name) { 171 | index = i; 172 | } 173 | } 174 | return index; 175 | } 176 | 177 | async findAllRoles(): Promise { 178 | return roles; 179 | } 180 | 181 | async findById(id: string): Promise { 182 | for (const role of roles) { 183 | if (role.id === id) { 184 | return role; 185 | } 186 | } 187 | return null; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NotFoundException, 4 | BadRequestException, 5 | } from '@nestjs/common'; 6 | 7 | import { users } from '../fake-data'; 8 | import { User, CoreRBACUser } from '../interfaces'; 9 | import { AuthZRBACService } from 'nest-authz'; 10 | 11 | @Injectable() 12 | export class UserService implements CoreRBACUser { 13 | constructor(private readonly authzSrv: AuthZRBACService) {} 14 | 15 | async addUser(user: User) { 16 | const isExist = await this.exists(user.username); 17 | if (isExist) { 18 | throw new BadRequestException( 19 | `The user ${user.username} is already exists.`, 20 | ); 21 | } 22 | users.push(user); 23 | } 24 | 25 | async deleteUser(username: string) { 26 | const isExist = await this.exists(username); 27 | 28 | if (!isExist) { 29 | throw new NotFoundException(`The user ${username} not found.`); 30 | } 31 | 32 | for (let i = 0, j = users.length; i < j; i++) { 33 | const user = users[i]; 34 | if (user.username === username) { 35 | users.splice(i, 1); 36 | break; 37 | } 38 | } 39 | 40 | return await this.authzSrv.deleteUser(username); 41 | } 42 | 43 | async assignedRoles(username: string): Promise { 44 | const isExists = await this.exists(username); 45 | 46 | if (!isExists) { 47 | throw new NotFoundException(`The user ${username} not found.`); 48 | } 49 | 50 | return this.authzSrv.getImplicitRolesForUser(username); 51 | } 52 | 53 | async userPermissions(username: string): Promise { 54 | const isExists = await this.exists(username); 55 | 56 | if (!isExists) { 57 | throw new NotFoundException(`The user ${username} not found.`); 58 | } 59 | 60 | return this.authzSrv.getImplicitPermissionsForUser(username); 61 | } 62 | 63 | async exists(username: string): Promise { 64 | for (const user of users) { 65 | if (user.username === username) { 66 | return true; 67 | } 68 | } 69 | return false; 70 | } 71 | 72 | async findAll(): Promise { 73 | return users; 74 | } 75 | 76 | async findById(id: string): Promise { 77 | for (const o of users) { 78 | if (o.id === id) { 79 | return o; 80 | } 81 | } 82 | return null; 83 | } 84 | 85 | async findByUserName(username: string): Promise { 86 | for (const o of users) { 87 | if (o.username === username) { 88 | return o; 89 | } 90 | } 91 | return null; 92 | } 93 | 94 | async verifyCredentials( 95 | username: string, 96 | password: string, 97 | ): Promise { 98 | const user = await this.findByUserName(username); 99 | 100 | if (!user) { 101 | return null; 102 | } 103 | 104 | const isPasswordCorrect = user.password === password; 105 | if (!isPasswordCorrect) { 106 | return null; 107 | } 108 | 109 | return user; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import * as request from 'supertest'; 3 | import { AppModule } from './../src/app.module'; 4 | 5 | describe('AppController (e2e)', () => { 6 | let app; 7 | 8 | beforeEach(async () => { 9 | const moduleFixture: TestingModule = await Test.createTestingModule({ 10 | imports: [AppModule], 11 | }).compile(); 12 | 13 | app = moduleFixture.createNestApplication(); 14 | await app.init(); 15 | }); 16 | 17 | it('/ (GET)', () => { 18 | return request(app.getHttpServer()) 19 | .get('/') 20 | .expect(200) 21 | .expect('Hello World!'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/casbin.spec.ts: -------------------------------------------------------------------------------- 1 | import * as casbin from 'casbin'; 2 | import * as path from 'path'; 3 | 4 | let enforcer: casbin.Enforcer; 5 | const modelPath = path.join(__dirname, '../model.conf'); 6 | const policyPath = path.join(__dirname, '../policy.csv'); 7 | 8 | beforeEach(async () => { 9 | enforcer = await casbin.newEnforcer(modelPath, policyPath); 10 | }); 11 | 12 | describe('Root user root', () => { 13 | it('can read any user roles', async () => { 14 | const result = await enforcer.enforce('root', 'user_roles', 'read:any'); 15 | expect(result).toBe(true); 16 | }); 17 | it('can read any user', async () => { 18 | const result = await enforcer.enforce('root', 'user', 'read:any'); 19 | expect(result).toBe(true); 20 | }); 21 | }); 22 | 23 | describe('manager tom', () => { 24 | it('can read any user roles', async () => { 25 | const result = await enforcer.enforce('tom', 'user_roles', 'read:any'); 26 | expect(result).toBe(true); 27 | }); 28 | it('can not read any user', async () => { 29 | const result = await enforcer.enforce('tom', 'user', 'read:any'); 30 | expect(result).toBe(false); 31 | }); 32 | }); 33 | 34 | describe('guest bob', () => { 35 | it('can read own user', async () => { 36 | const result = await enforcer.enforce('bob', 'user', 'read:own'); 37 | expect(result).toBe(true); 38 | }); 39 | it('can not read any user', async () => { 40 | const result = await enforcer.enforce('bob', 'user', 'read:any'); 41 | expect(result).toBe(false); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es6", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./" 12 | }, 13 | "exclude": ["node_modules"] 14 | } 15 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | --------------------------------------------------------------------------------