mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-15 07:55:49 +01:00
apply role guard to all routes
This commit is contained in:
@@ -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],
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
19
backend/src/managers/auth/guest.service.ts
Normal file
19
backend/src/managers/auth/guest.service.ts
Normal 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 [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user