mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-24 22:46:39 +02:00
Docker Infrastructure:
- docker-compose.yml with Traefik 3, PostgreSQL 16, PgBouncer, Redis 7, step-ca
- docker-compose.observability.yml with Prometheus, Grafana, Loki, Tempo, Promtail
- Traefik dynamic config (TLS, security headers, CORS, compression)
- PostgreSQL init script (uuid-ossp, pgcrypto, pg_trgm extensions)
- Grafana auto-provisioned datasources (Prometheus, Loki, Tempo)
NestJS Core-Service:
- Auth module: Login (email/password), TOTP 2FA, JWT RS256, token refresh/revocation
- Users module: CRUD, bcrypt cost 12, pagination, role-based access
- Tenants module: CRUD, member management, slug validation
- Prisma schemas: core (Users, AuthProviders, Tenants, Modules, AuditLog)
tenant (Contacts, Activities - CRM reference for Sprint 2)
- TenantPrismaService: Dynamic per-tenant DB connections with caching
- RedisService: Token blocklist, refresh token families, generic cache
- Global JwtAuthGuard with @Public() decorator, RolesGuard, GlobalExceptionFilter
- Health endpoint with DB + Redis status checks
- Swagger API documentation (dev only)
- Multi-stage Dockerfile (dev + production)
React Frontend:
- Vite 6 + React 18 + TypeScript strict
- AuthContext with silent refresh (access token in memory, NOT localStorage)
- Login page with TOTP 2FA support
- App shell with sidebar navigation
- Admin pages: Users + Tenants management tables
- API client with automatic token refresh interceptor
- Multi-stage Dockerfile (dev + nginx production)
CI/CD Pipelines:
- ci.yml: Lint, type-check, test, build on all branches
- deploy.yml: Docker build, push to Forgejo registry, SSH deploy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
import {
|
|
Injectable,
|
|
ExecutionContext,
|
|
UnauthorizedException,
|
|
} from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import { AuthGuard } from '@nestjs/passport';
|
|
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
|
import { RedisService } from '../../redis/redis.service';
|
|
import { JwtPayload } from '../decorators/current-user.decorator';
|
|
|
|
/**
|
|
* JwtAuthGuard - Globaler Guard fuer JWT-Authentifizierung.
|
|
*
|
|
* - Standardmaessig aktiv auf ALLEN Routen
|
|
* - @Public() dekorierte Routen werden uebersprungen
|
|
* - Prueft zusaetzlich ob der Token revoked wurde (Redis Blocklist)
|
|
*/
|
|
@Injectable()
|
|
export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
constructor(
|
|
private readonly reflector: Reflector,
|
|
private readonly redis: RedisService,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
// @Public() Routen ueberspringen
|
|
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
|
context.getHandler(),
|
|
context.getClass(),
|
|
]);
|
|
|
|
if (isPublic) {
|
|
return true;
|
|
}
|
|
|
|
// JWT validieren (Passport Strategy)
|
|
const canActivate = await super.canActivate(context);
|
|
if (!canActivate) {
|
|
return false;
|
|
}
|
|
|
|
// Token-Revocation pruefen (Redis Blocklist)
|
|
const request = context.switchToHttp().getRequest();
|
|
const user = request.user as JwtPayload;
|
|
|
|
if (user?.jti) {
|
|
const isBlocked = await this.redis.isTokenBlocked(user.jti);
|
|
if (isBlocked) {
|
|
throw new UnauthorizedException('Token wurde widerrufen');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
handleRequest<T>(err: Error | null, user: T, info: Error | undefined): T {
|
|
if (err || !user) {
|
|
throw err || new UnauthorizedException('Zugriff verweigert');
|
|
}
|
|
return user;
|
|
}
|
|
}
|