name beim kopieren

This commit is contained in:
Bastian Wagner
2026-06-09 16:03:20 +02:00
parent 06c5b1768e
commit 16605c961f
18 changed files with 693 additions and 44 deletions

View File

@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuditModule } from '../audit/audit.module';
import { AuthController } from './auth.controller';
import { RefreshTokenEntity } from './refresh-token.entity';
import { AuthService } from './auth.service';
@@ -8,7 +9,11 @@ import { JwtAuthGuard } from './jwt-auth.guard';
import { UserEntity } from './user.entity';
@Module({
imports: [JwtModule.register({}), TypeOrmModule.forFeature([UserEntity, RefreshTokenEntity])],
imports: [
AuditModule,
JwtModule.register({}),
TypeOrmModule.forFeature([UserEntity, RefreshTokenEntity]),
],
controllers: [AuthController],
providers: [AuthService, JwtAuthGuard],
exports: [AuthService, JwtAuthGuard],

View File

@@ -2,6 +2,7 @@ import {
BadRequestException,
ConflictException,
Injectable,
Optional,
UnauthorizedException,
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
@@ -9,6 +10,7 @@ import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from 'crypto';
import { Repository } from 'typeorm';
import { AuditLogService } from '../audit/audit-log.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import { RefreshTokenDto } from './dto/refresh-token.dto';
@@ -39,6 +41,8 @@ export class AuthService {
private readonly usersRepository: Repository<UserEntity>,
@InjectRepository(RefreshTokenEntity)
private readonly refreshTokensRepository: Repository<RefreshTokenEntity>,
@Optional()
private readonly auditLogService?: AuditLogService,
) {}
async register(
@@ -67,6 +71,15 @@ export class AuthService {
});
const savedUser = await this.usersRepository.save(user);
await this.auditLogService?.record({
actorUserId: savedUser.id,
actorEmail: savedUser.email,
action: 'user.registered',
entityType: 'user',
entityId: savedUser.id,
metadata: { verified: savedUser.verified },
});
this.eventEmitter.emit(AppEvents.UserRegistered, {
email,
verificationUrl: this.createVerificationUrl(verificationToken),
@@ -98,10 +111,18 @@ export class AuthService {
try {
const savedUser = await this.usersRepository.save(user);
return {
message: 'Email verified successfully.',
user: this.toPublicUser(savedUser),
};
await this.auditLogService?.record({
actorUserId: savedUser.id,
actorEmail: savedUser.email,
action: 'user.email_verified',
entityType: 'user',
entityId: savedUser.id,
});
return {
message: 'Email verified successfully.',
user: this.toPublicUser(savedUser),
};
} catch {
throw new BadRequestException('user not saved.')
}
@@ -122,6 +143,14 @@ export class AuthService {
user.verificationToken = this.createToken();
const savedUser = await this.usersRepository.save(user);
await this.auditLogService?.record({
actorUserId: savedUser.id,
actorEmail: savedUser.email,
action: 'user.verification_resent',
entityType: 'user',
entityId: savedUser.id,
});
this.eventEmitter.emit(AppEvents.UserRegistered, {
email: savedUser.email,
verificationUrl: this.createVerificationUrl(savedUser.verificationToken!),
@@ -136,17 +165,42 @@ export class AuthService {
const user = await this.usersRepository.findOne({ where: { email } });
if (!user || !this.passwordMatches(password, user.passwordHash)) {
await this.auditLogService?.record({
actorEmail: email,
action: 'user.login_failed',
entityType: 'user',
entityId: user?.id,
metadata: { reason: 'invalid_credentials' },
});
throw new UnauthorizedException('Invalid email or password.');
}
if (!user.verified) {
await this.auditLogService?.record({
actorUserId: user.id,
actorEmail: user.email,
action: 'user.login_failed',
entityType: 'user',
entityId: user.id,
metadata: { reason: 'email_not_verified' },
});
throw new UnauthorizedException('Please verify your email before login.');
}
return {
const response = {
...(await this.createAuthTokens(user)),
user: this.toPublicUser(user),
};
await this.auditLogService?.record({
actorUserId: user.id,
actorEmail: user.email,
action: 'user.login_succeeded',
entityType: 'user',
entityId: user.id,
});
return response;
}
async refresh(refreshTokenDto: RefreshTokenDto = {}): Promise<AuthTokenResponse> {
@@ -175,10 +229,20 @@ export class AuthService {
await this.refreshTokensRepository.delete({ jti: payload.jti });
return {
const response = {
...(await this.createAuthTokens(user)),
user: this.toPublicUser(user),
};
await this.auditLogService?.record({
actorUserId: user.id,
actorEmail: user.email,
action: 'user.token_refreshed',
entityType: 'user',
entityId: user.id,
});
return response;
}
async verifyAccessToken(accessToken: string): Promise<JwtTokenPayload> {
@@ -243,7 +307,18 @@ export class AuthService {
user.onboardingCompleted = completed;
return this.toPublicUser(await this.usersRepository.save(user));
const savedUser = await this.usersRepository.save(user);
await this.auditLogService?.record({
actorUserId: savedUser.id,
actorEmail: savedUser.email,
action: 'user.onboarding_updated',
entityType: 'user',
entityId: savedUser.id,
metadata: { completed },
});
return this.toPublicUser(savedUser);
}
private normalizeEmail(email?: string): string {