feat(auth): implement role-based access control and user management features

This commit is contained in:
Nils-Johan Gynther
2026-04-18 09:34:22 +02:00
parent 20330f6410
commit c5ccef2313
22 changed files with 358 additions and 10 deletions
+5 -4
View File
@@ -27,7 +27,7 @@ export class AuthService {
passwordHash,
});
return this.issueToken(user.id, user.username);
return this.issueToken(user.id, user.username, user.role);
}
async login(dto: LoginDto) {
@@ -37,15 +37,16 @@ export class AuthService {
const valid = await bcrypt.compare(dto.password, user.passwordHash);
if (!valid) throw new UnauthorizedException('Felaktigt användarnamn eller lösenord');
return this.issueToken(user.id, user.username);
return this.issueToken(user.id, user.username, user.role);
}
private issueToken(userId: number, username: string) {
const payload = { sub: userId, username };
private issueToken(userId: number, username: string, role: string) {
const payload = { sub: userId, username, role };
return {
accessToken: this.jwtService.sign(payload),
userId,
username,
role,
};
}
}
@@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
+2 -2
View File
@@ -12,7 +12,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
});
}
async validate(payload: { sub: number; username: string }) {
return { userId: payload.sub, username: payload.username };
async validate(payload: { sub: number; username: string; role: string }) {
return { userId: payload.sub, username: payload.username, role: payload.role ?? 'user' };
}
}
+24
View File
@@ -0,0 +1,24 @@
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
// No @Roles() decorator — allow any authenticated user
if (!requiredRoles || requiredRoles.length === 0) return true;
const { user } = context.switchToHttp().getRequest();
if (!user?.role || !requiredRoles.includes(user.role)) {
throw new ForbiddenException('Åtkomst nekad — admin-behörighet krävs');
}
return true;
}
}