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,125 @@
import { Body, Controller, Get, Patch, Post, Req, UseGuards } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Repository } from 'typeorm';
import { AuditService } from '../audit/audit.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RequestUser } from '../common/request-user';
import { hashToken, randomToken } from '../common/token.util';
import { LldapService } from '../lldap/lldap.service';
import { PortalMailService } from '../mail/portal-mail.service';
import { AccountDeleteRequest } from './account-delete-request.entity';
import { ConfirmEmailChangeDto } from './dto/confirm-email-change.dto';
import { DeleteRequestDto } from './dto/delete-request.dto';
import { RequestEmailChangeDto } from './dto/request-email-change.dto';
import { UpdateProfileDto } from './dto/update-profile.dto';
import { EmailChangeRequest } from './email-change-request.entity';
@Controller('account')
@UseGuards(JwtAuthGuard)
export class AccountController {
constructor(
private readonly lldap: LldapService,
private readonly mail: PortalMailService,
private readonly config: ConfigService,
private readonly audit: AuditService,
@InjectRepository(EmailChangeRequest)
private readonly emailChanges: Repository<EmailChangeRequest>,
@InjectRepository(AccountDeleteRequest)
private readonly deleteRequests: Repository<AccountDeleteRequest>,
) {}
@Get('me')
me(@Req() request: Request & { user: RequestUser }) {
return this.lldap.getAccount(request.user.username);
}
@Patch('profile')
async updateProfile(
@Body() dto: UpdateProfileDto,
@Req() request: Request & { user: RequestUser },
) {
await this.lldap.updateUser(request.user.username, dto);
await this.audit.record({
type: 'account.profile_updated',
username: request.user.username,
ipAddress: request.ip,
userAgent: request.headers['user-agent'],
});
return this.lldap.getAccount(request.user.username);
}
@Post('email-change/request')
async requestEmailChange(
@Body() dto: RequestEmailChangeDto,
@Req() request: Request & { user: RequestUser },
) {
const token = randomToken();
await this.emailChanges.save(
this.emailChanges.create({
username: request.user.username,
newEmail: dto.newEmail.toLowerCase(),
tokenHash: hashToken(token, this.tokenSecret),
expiresAt: new Date(Date.now() + 24 * 60 * 60_000),
}),
);
await this.mail.sendEmailChangeMail(dto.newEmail, token);
await this.audit.record({
type: 'account.email_change_requested',
username: request.user.username,
ipAddress: request.ip,
userAgent: request.headers['user-agent'],
metadata: { newEmail: dto.newEmail.toLowerCase() },
});
return { message: 'Bitte bestaetige die neue E-Mail-Adresse.' };
}
@Post('email-change/confirm')
async confirmEmailChange(
@Body() dto: ConfirmEmailChangeDto,
@Req() request: Request & { user: RequestUser },
) {
const tokenHash = hashToken(dto.token, this.tokenSecret);
const record = await this.emailChanges.findOne({ where: { tokenHash, consumedAt: IsNull() } });
if (!record || record.expiresAt.getTime() < Date.now() || record.username !== request.user.username) {
return { message: 'Der Bestaetigungslink ist ungueltig oder abgelaufen.' };
}
await this.lldap.updateUser(request.user.username, { email: record.newEmail });
record.consumedAt = new Date();
await this.emailChanges.save(record);
await this.audit.record({
type: 'account.email_changed',
username: request.user.username,
ipAddress: request.ip,
userAgent: request.headers['user-agent'],
metadata: { newEmail: record.newEmail },
});
return { message: 'Die E-Mail-Adresse wurde aktualisiert.' };
}
@Post('delete-request')
async requestDeletion(
@Body() dto: DeleteRequestDto,
@Req() request: Request & { user: RequestUser },
) {
await this.deleteRequests.save(
this.deleteRequests.create({
username: request.user.username,
reason: dto.reason,
}),
);
await this.audit.record({
type: 'account.delete_requested',
username: request.user.username,
ipAddress: request.ip,
userAgent: request.headers['user-agent'],
});
return { message: 'Die Anfrage wurde gespeichert.' };
}
private get tokenSecret(): string {
return this.config.getOrThrow<string>('TOKEN_SECRET');
}
}