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
+14 -5
View File
@@ -16,6 +16,7 @@ import { LdapService } from '../ldap/ldap.service';
import { MailService } from '../mail/mail.service';
import { PrismaService } from '../prisma/prisma.service';
import { forTenant } from '../prisma/prisma-tenant.extension';
import type { JwtPayload, LoginUser } from './types/auth-user';
/**
* Zeilenform der drei auth_lookup_*-Datenbankfunktionen
@@ -29,7 +30,12 @@ interface AuthLookupUserByUsernameRow {
passwordHash: string | null;
ldapDn: string | null;
isActive: boolean;
role: string;
/**
* Die SQL-Funktion deklariert diese Spalte als `role "Role"` (Migration
* 20260909160000, Zeile 64) — `string` war hier eine weitere Fassung
* desselben Wertes, nicht seine Beschreibung (quick-260921-m34).
*/
role: Role;
displayName: string | null;
mustChangePassword: boolean;
}
@@ -104,7 +110,10 @@ export class AuthService {
* T-02-01: Returns null on any failure (never reveals which field is wrong).
* Pitfall 6: Checks isActive to prevent deactivated users from logging in.
*/
async validateUser(username: string, password: string): Promise<any> {
async validateUser(
username: string,
password: string,
): Promise<AuthLookupUserByUsernameRow | null> {
// Usernames are stored lowercase (case-insensitive login).
const rows = await this.prisma.$queryRaw<AuthLookupUserByUsernameRow[]>`
SELECT * FROM auth_lookup_user_by_username(${username.toLowerCase()})
@@ -167,8 +176,8 @@ export class AuthService {
* D-02: 30-day session.
* T-02-02: httpOnly + secure (prod) + sameSite=lax.
*/
async login(user: any, response: Response) {
const payload = {
async login(user: LoginUser, response: Response) {
const payload: JwtPayload = {
sub: user.id,
username: user.username,
role: user.role,
@@ -373,7 +382,7 @@ export class AuthService {
data: { passwordHash, mustChangePassword: false },
});
const payload = {
const payload: JwtPayload = {
sub: user.id,
username: user.username,
role: user.role,