This commit is contained in:
Bastian Wagner
2026-07-15 14:09:28 +02:00
commit 3e5348b7ec
104 changed files with 30367 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common';
import { Request } from 'express';
import { RequestUser } from '../common/request-user';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { JwtAuthGuard } from './jwt-auth.guard';
@Controller('auth')
export class AuthController {
constructor(private readonly auth: AuthService) {}
@Post('login')
login(@Body() dto: LoginDto, @Req() request: Request) {
return this.auth.login(
dto.username,
dto.password,
request.ip,
request.headers['user-agent'],
);
}
@UseGuards(JwtAuthGuard)
@Get('me')
me(@Req() request: Request & { user: RequestUser }) {
return { user: request.user };
}
}

View File

@@ -0,0 +1,26 @@
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { AuditModule } from '../audit/audit.module';
import { LldapModule } from '../lldap/lldap.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';
@Module({
imports: [
JwtModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.getOrThrow<string>('JWT_SECRET'),
signOptions: { expiresIn: '8h' },
}),
}),
LldapModule,
AuditModule,
],
controllers: [AuthController],
providers: [AuthService, JwtAuthGuard],
exports: [AuthService, JwtAuthGuard, JwtModule],
})
export class AuthModule {}

View File

@@ -0,0 +1,27 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { AuditService } from '../audit/audit.service';
import { LdapAuthService } from '../lldap/ldap-auth.service';
@Injectable()
export class AuthService {
constructor(
private readonly ldapAuth: LdapAuthService,
private readonly jwt: JwtService,
private readonly audit: AuditService,
) {}
async login(username: string, password: string, ipAddress?: string, userAgent?: string) {
const valid = await this.ldapAuth.verifyPassword(username, password);
if (!valid) {
await this.audit.record({ type: 'auth.login_failed', username, ipAddress, userAgent });
throw new UnauthorizedException('Ungueltige Zugangsdaten.');
}
await this.audit.record({ type: 'auth.login_success', username, ipAddress, userAgent });
return {
accessToken: await this.jwt.signAsync({ sub: username, username }),
user: { username },
};
}
}

View File

@@ -0,0 +1,11 @@
import { IsString, Length } from 'class-validator';
export class LoginDto {
@IsString()
@Length(1, 128)
username!: string;
@IsString()
@Length(1, 256)
password!: string;
}

View File

@@ -0,0 +1,34 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { RequestUser } from '../common/request-user';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private readonly jwt: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request & { user?: RequestUser }>();
const token = this.extractBearerToken(request);
if (!token) {
throw new UnauthorizedException('Nicht angemeldet.');
}
try {
request.user = await this.jwt.verifyAsync<RequestUser>(token);
return true;
} catch {
throw new UnauthorizedException('Session ist ungueltig oder abgelaufen.');
}
}
private extractBearerToken(request: Request): string | undefined {
const header = request.headers.authorization;
if (!header) {
return undefined;
}
const [type, token] = header.split(' ');
return type?.toLowerCase() === 'bearer' ? token : undefined;
}
}