refactor(quick-260921-m34): Aufgabe 2a - gemeinsamer Aufrufer-Typ, aus den Signierstellen abgeleitet

apps/api/src/auth/types/auth-user.ts angelegt: AuthUser, AuthenticatedRequest,
LocalAuthenticatedRequest, LoginUser, JwtPayload, UploadedFileLike. Jedes Feld
traegt seine Herkunft als Kommentar.

tenantId ist string, hergeleitet und nicht gewaehlt: die Spalte User.tenantId
ist in schema.prisma Pflicht, beide Signierstellen schreiben genau sie, und
der Bestand beschreibt dasselbe Objekt in SessionUser schon so. Der
SUPER_ADMIN-Zweig in TenantGuard spricht nicht dagegen - der Waechter liest
AuthUser gar nicht, und dass es den Zweig gibt, steht als null in
AuthenticatedRequest.tenantId weiter im Typsystem. tenant.guard.ts bleibt
unberuehrt.

role ist die Aufzaehlung Role: schema.prisma deklariert die Spalte so, die
SQL-Funktion auth_lookup_user_by_username gibt sie als "Role" zurueck. Die
Handannotation role: string in AuthLookupUserByUsernameRow war eine zweite
Fassung desselben Wertes und faellt damit weg.

SessionUser und UploadedPng in bug-reports.service.ts sind jetzt Pick<> der
neuen Typen statt eigener Beschreibungen.

Fixtures in auth.controller.spec.ts ergaenzt: sie uebergaben einen Aufrufer
ohne username und ohne mustChangePassword - eine Form, die JwtStrategy nie
erzeugt. Testzahlen unveraendert.

noExplicitAny in apps/api/src: 149 -> 137. type-check 4/4, lint 5/5,
apps/api 72/1143, apps/web 73/531.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 17:03:24 +02:00
parent b188946e31
commit f2fc39f51c
10 changed files with 212 additions and 38 deletions
@@ -8,6 +8,7 @@ import {
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
import type { AuthenticatedRequest } from '../types/auth-user';
/**
* Global interceptor: forces users with mustChangePassword=true to change
@@ -30,7 +31,7 @@ const ALLOWED_ROUTES = Object.freeze([
{ method: 'GET', path: '/auth/me' },
]);
function normalizePath(request: any): string {
function normalizePath(request: AuthenticatedRequest): string {
const raw = request.route?.path || request.url || '';
const withoutQuery = raw.split('?')[0];
const withoutTrailingSlash = withoutQuery.replace(/\/+$/, '');
@@ -41,7 +42,7 @@ function normalizePath(request: any): string {
export class ForcePasswordChangeInterceptor implements NestInterceptor {
constructor(private reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
// Skip public routes (login, health, reset-password)
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
@@ -51,7 +52,7 @@ export class ForcePasswordChangeInterceptor implements NestInterceptor {
return next.handle();
}
const request = context.switchToHttp().getRequest();
const request = context.switchToHttp().getRequest<AuthenticatedRequest>();
const user = request.user;
// No user on request (shouldn't happen after auth guard, but be defensive)