3.0.1 • Published 2 years ago
@rockts-org/nestjs-access-control v3.0.1
Rockets NestJS Access Control
Advanced access control guard for NestJS
IMPORTANT
When building your ACL, you need to remember these!
This module only helps you apply a pattern. There is no magic, you are ultimately responsible for checking that your ACL works in all contexts.
Here is the pattern:
- Giving
any
access implies that the role IS NOT restricted by ownership, or other rules, to that action/resource combination. - Giving
own
access implies that the role IS restricted by ownership to that action/resource combination (it is often required to enforce this rule with a filter to check the data layer when not all information required to check ownership exists in the parameters or query string.)
!!! Important !!!
All roles that are given
any
access to a resource will NOT be passed through access filters sinceany
implies they should have all access.
Example
These are very rough examples. We intend to improve them ASAP.
Simple User Entity
import { Entity, Column, ManyToMany, Unique } from 'typeorm';
import { Exclude } from 'class-transformer';
import { Role } from '../auth/role.entity';
@Entity()
@Unique(['username'])
export class User {
@Column()
username!: string;
@Column()
@Exclude()
password!: string;
@Column()
@Exclude()
salt!: string;
@ManyToMany(() => Role, (role) => role.users, {
eager: true,
onDelete: 'CASCADE',
})
roles!: Role[];
}
Your custom AccessControlService
import { AccessControlService } from 'nestjs-access-control';
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { User } from '../user/user.entity';
export class ACService implements AccessControlService {
async getUser<T>(context: ExecutionContext): Promise<T> {
const request = context.switchToHttp().getRequest();
return request.user as T;
}
async getUserRoles(context: ExecutionContext): Promise<string | string[]> {
const user = await this.getUser<User>(context);
if (!user || !user.roles) throw new UnauthorizedException();
return user.roles.map((role) => role.name);
}
}
Your custom ACL rules
import { AccessControl } from 'accesscontrol';
export enum AppRole {
SuperAdmin = 'SuperAdmin',
User = 'User',
}
export enum AppResource {
User = 'user',
UserList = 'user-list',
}
const allResources = Object.values(AppResource);
const allRoles = Object.values(AppRole);
export const acRules: AccessControl = new AccessControl();
// admins can do whatever they want
acRules
.grant([AppRole.SuperAdmin])
.resource(allResources)
.createOwn()
.createAny()
.readOwn()
.readAny()
.updateOwn()
.updateAny()
.deleteOwn()
.deleteAny();
// regular users can read self
acRules.grant(AppRole.User).resource([AppResource.User]).readOwn();
// make sure nobody can delete themselves
acRules.deny(allRoles).resource(AppResource.User).deleteOwn();
Import the module into your app
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AccessControlModule } from 'nestjs-access-control';
import { ACService } from './modules/auth/access-control.service';
import { acRules } from './app.acl';
@Module({
imports: [
// ...
AccessControlModule.register({ service: ACService, rules: acRules }),
// ...
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Implement on your controller (nestjsx CRUD module with Passport guard example)
import { Controller, UseGuards } from '@nestjs/common';
import { Crud, CrudController } from '@nestjsx/crud';
import { User } from './user.entity';
import { UserService } from './user.service';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { AuthGuard } from '@nestjs/passport';
import { AppResource } from '../../app.acl';
import {
AccessControlCreateOne,
AccessControlDeleteOne,
AccessControlGuard,
AccessControlReadMany,
AccessControlReadOne,
AccessControlUpdateOne,
UseAccessControl,
} from '@rockts-org/nestjs-access-control';
import { UserDto, CreateUserDto, UpdateUserDto } from './dto';
import { UserAccessControlFilterService } from './user-access-control-filter.service';
@ApiTags(AppResource.User)
@ApiBearerAuth()
@Crud({
model: {
type: UserDto,
},
dto: {
create: CreateUserDto,
update: UpdateUserDto,
},
routes: {
only: [
'getManyBase',
'getOneBase',
'createOneBase',
'updateOneBase',
'deleteOneBase',
],
getManyBase: {
decorators: [
ApiOperation({
operationId: 'user_getMany',
}),
AccessControlReadMany(AppResource.UserList),
],
},
getOneBase: {
decorators: [
ApiOperation({
operationId: 'user_getOne',
}),
AccessControlReadOne(
AppResource.User,
async (
params: { id: string },
user: User,
service: UserAccessControlFilterService,
): Promise<boolean> => {
return (
params.id === user.id && true === service.userCanRead(id, user)
);
},
),
],
},
createOneBase: {
decorators: [
ApiOperation({
operationId: 'user_createOne',
}),
AccessControlCreateOne(AppResource.User),
],
},
updateOneBase: {
decorators: [
ApiOperation({
operationId: 'user_updateOne',
}),
AccessControlUpdateOne(AppResource.User),
],
},
deleteOneBase: {
decorators: [
ApiOperation({
operationId: 'user_deleteOne',
}),
AccessControlDeleteOne(AppResource.User),
],
},
},
})
@Controller(AppResource.User)
@UseGuards(AuthGuard(), AccessControlGuard)
@UseAccessControl({ service: UserAccessControlFilterService })
export class UserController implements CrudController<User> {
constructor(public service: UserService) {}
}
4.0.0-alpha.5
2 years ago
4.0.0-alpha.3
2 years ago
4.0.0-alpha.4
2 years ago
4.0.0-alpha.1
2 years ago
4.0.0-alpha.2
2 years ago
3.0.1
4 years ago
0.0.3
4 years ago