Backend Architecture
The backend architecture implements all security logic through a layered approach: authentication validation, role-based authorization, and transparent tenant filtering.
Controller Template (Security-First)​
// clients.controller.ts - Enhanced existing controller with security
import { Controller, Get, Post, Param, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
import { TenantAccessGuard } from '../auth/guards/tenant-access.guard';
import { Roles } from '../auth/decorators/roles.decorator';
import { UserRole } from '../auth/enums/user-role.enum';
@UseGuards(JwtAuthGuard, RolesGuard, TenantAccessGuard)
@Controller('clients')
export class ClientsController {
constructor(private readonly clientsService: ClientsService) {}
@Get()
@Roles(UserRole.ADMIN, UserRole.ACCOUNT_MANAGER, UserRole.TAX_SPECIALIST, UserRole.CLIENT)
async findAll() {
// Service automatically filters by user's client_access - no explicit filtering needed
return this.clientsService.findAll();
}
@Get(':id')
@Roles(UserRole.ADMIN, UserRole.ACCOUNT_MANAGER, UserRole.TAX_SPECIALIST, UserRole.CLIENT)
async findOne(@Param('id') id: string) {
// TenantAccessGuard already validated user can access this client
return this.clientsService.findOne(id);
}
}
Prisma Middleware for Automatic Filtering​
// middleware/prisma-tenant.middleware.ts - Automatic query filtering
export function configurePrismaTenantMiddleware(prisma: PrismaClient, tenantContext: TenantContextService) {
prisma.$use(async (params, next) => {
const user = tenantContext.getCurrentUser();
if (!user) {
return next(params);
}
// Apply tenant filtering based on model
if (params.model === 'Client') {
if (params.action === 'findMany' || params.action === 'findFirst') {
params.args.where = {
...params.args.where,
id: { in: user.clientAccess },
};
}
}
if (params.model === 'Reclamation') {
params.args.where = {
...params.args.where,
clientId: { in: user.clientAccess },
};
}
return next(params);
});
}