apply role guard to all routes

This commit is contained in:
rubikscraft
2022-03-12 00:14:16 +01:00
parent 9b98f3c005
commit 0aa897fa8d
15 changed files with 168 additions and 37 deletions

View File

@@ -14,6 +14,7 @@ import { AuthManagerService } from './auth.service';
import { GuestStrategy } from './guards/guest.strategy';
import { JwtStrategy } from './guards/jwt.strategy';
import { LocalAuthStrategy } from './guards/localauth.strategy';
import { GuestService } from './guest.service';
@Module({
imports: [
@@ -32,6 +33,7 @@ import { LocalAuthStrategy } from './guards/localauth.strategy';
JwtStrategy,
GuestStrategy,
JwtSecretProvider,
GuestService,
],
exports: [AuthManagerService],
})

View File

@@ -4,6 +4,7 @@ import { Request } from 'express';
import { ParamsDictionary } from 'express-serve-static-core';
import { Strategy } from 'passport-strategy';
import { ParsedQs } from 'qs';
import { GuestService } from '../guest.service';
type ReqType = Request<
ParamsDictionary,
@@ -18,10 +19,9 @@ class GuestPassportStrategy extends Strategy {
return undefined;
}
override authenticate(req: ReqType, options?: any): void {
const user = this.validate(req);
req['user'] = user;
this.pass();
override async authenticate(req: ReqType, options?: any) {
const user = await this.validate(req);
this.success(user);
}
}
@@ -32,8 +32,11 @@ export class GuestStrategy extends PassportStrategy(
) {
private readonly logger = new Logger('GuestStrategy');
constructor(private guestService: GuestService) {
super();
}
override async validate(payload: any) {
// TODO: add guest user
return;
return this.guestService.createGuest();
}
}

View File

@@ -1,5 +1,84 @@
import { Injectable } from '@nestjs/common';
import {
ExecutionContext,
Injectable,
InternalServerErrorException,
Logger
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { plainToClass } from 'class-transformer';
import { isArray, isEnum, isString, validate } from 'class-validator';
import { Roles, RolesList } from 'picsur-shared/dist/dto/roles.dto';
import { Fail, Failable, HasFailed } from 'picsur-shared/dist/types';
import { EUserBackend } from '../../../models/entities/user.entity';
@Injectable()
export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {}
export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) {
private readonly logger = new Logger('MainAuthGuard');
constructor(private reflector: Reflector) {
super();
}
override async canActivate(context: ExecutionContext): Promise<boolean> {
const result = await super.canActivate(context);
if (result !== true) {
this.logger.error('Main Auth has denied access, this should not happen');
return false;
}
const user = await this.validateUser(
context.switchToHttp().getRequest().user,
);
const roles = this.extractRoles(context);
if (HasFailed(roles)) {
this.logger.warn(roles.getReason());
return false;
}
// User must have all roles
return roles.every((role) => user.roles.includes(role));
}
private extractRoles(context: ExecutionContext): Failable<Roles> {
const handlerName = context.getHandler().name;
const roles =
this.reflector.get<Roles>('roles', context.getHandler()) ??
this.reflector.get<Roles>('roles', context.getClass());
if (roles === undefined) {
return Fail(
`${handlerName} does not have any roles defined, denying access`,
);
}
if (!this.isRolesArray(roles)) {
return Fail(`Roles for ${handlerName} is not a string array`);
}
return roles;
}
private isRolesArray(value: any): value is Roles {
if (!isArray(value)) return false;
if (!value.every((item: unknown) => isString(item))) return false;
if (!value.every((item: string) => isEnum(item, RolesList))) return false;
return true;
}
private async validateUser(user: EUserBackend): Promise<EUserBackend> {
const userClass = plainToClass(EUserBackend, user);
const errors = await validate(userClass, {
forbidUnknownValues: true,
});
if (errors.length > 0) {
this.logger.error(
'Invalid user object, where it should always be valid: ' + errors,
);
throw new InternalServerErrorException();
}
return userClass;
}
}

View File

@@ -0,0 +1,19 @@
import { Injectable } from '@nestjs/common';
import { Roles } from 'picsur-shared/dist/dto/roles.dto';
import { EUserBackend } from '../../models/entities/user.entity';
@Injectable()
export class GuestService {
public createGuest(): EUserBackend {
const guest = new EUserBackend();
guest.id = -1;
guest.roles = this.createGuestRoles();
guest.username = 'guest';
return guest;
}
private createGuestRoles(): Roles {
return [];
}
}