feat: initialize backend with NestJS, PostgreSQL, and Prisma
- Add package.json for backend dependencies and scripts. - Create Prisma schema for multi-tenant event management. - Implement main application module and configure global settings. - Develop authentication module with JWT and Authentik integration. - Create DTOs for guest account creation and KC management. - Implement role-based access control with custom guards and decorators. - Add services and controllers for managing KCs and guest accounts. - Set up global validation and CORS in the main application entry point. - Establish Prisma module for database access throughout the application. - Document project plan and architecture for multi-tenant platform.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
/// Re-exported from the Prisma client so guards and strategies share one
|
||||
/// enum type with the database schema. Guests are not part of this enum
|
||||
/// since they authenticate separately and never hold elevated rights.
|
||||
export { Role } from '@prisma/client';
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { Role } from './role.enum';
|
||||
|
||||
export const ROLES_KEY = 'roles';
|
||||
|
||||
/// Marks a route as requiring at least one of the given roles (scope-checked by RolesGuard).
|
||||
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Role } from './role.enum';
|
||||
import { ROLES_KEY } from './roles.decorator';
|
||||
import { AuthenticatedRequest } from '../auth/authenticated-request';
|
||||
|
||||
/// Checks the caller holds one of the required roles, scoped to the KC in the
|
||||
/// request (route param `kcId`, falling back to body.kcId). LEITUNGSTEAM
|
||||
/// memberships are global and satisfy any KC scope.
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private readonly reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (!requiredRoles || requiredRoles.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
|
||||
const user = request.user;
|
||||
if (!user) {
|
||||
throw new ForbiddenException('Not authenticated');
|
||||
}
|
||||
|
||||
const kcId = request.params?.kcId ?? request.body?.kcId;
|
||||
const hasRole = user.memberships.some((membership) => {
|
||||
if (!requiredRoles.includes(membership.role)) {
|
||||
return false;
|
||||
}
|
||||
if (membership.role === Role.LEITUNGSTEAM) {
|
||||
return true;
|
||||
}
|
||||
return kcId ? membership.kcId === kcId : true;
|
||||
});
|
||||
|
||||
if (!hasRole) {
|
||||
throw new ForbiddenException('Insufficient role for this KC');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user