import { CanActivate, createParamDecorator, ExecutionContext, Injectable, SetMetadata, UnauthorizedException, UseGuards, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { User } from './database/entities'; import { FunkiIdService } from './funki-id.service'; export enum UserRole { EVERYONE = 'EVERYONE', AUTHENTICATED = 'AUTHENTICATED', } export const ROLES_KEY = 'AUTHORIZED_ROLES'; export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles); export type AuthRequest = Request & { funkiUser?: User; }; export const AuthUser = createParamDecorator((data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); return request.funkiUser; }); @Injectable() export class FunkiIdGuardCore implements CanActivate { constructor( protected readonly reflector: Reflector, protected readonly funkiIdService: FunkiIdService, ) {} async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const authorizedRoles = [ ...(this.reflector.get(ROLES_KEY, context.getHandler()) ?? []), UserRole.AUTHENTICATED, ]?.filter(Boolean); const authorizationHeader = request.headers['authorization']; if (!authorizedRoles || authorizedRoles.includes(UserRole.EVERYONE)) { if (authorizationHeader) { return this.funkiIdService.verifyToken(authorizationHeader).then(user => { request.funkiUser = user; return true; }); } return true; } if (!authorizationHeader) { throw new UnauthorizedException('No authorization header'); } return this.funkiIdService.verifyToken(authorizationHeader).then(user => { request.funkiUser = user; return true; }); } } export const UseFunkiIdGuard = () => UseGuards(FunkiIdGuardCore);