Files
tessera-ctl/apps/api/src/auth/strategies/jwt.strategy.ts
T
schalli f2fc39f51c 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
2026-09-21 17:03:24 +02:00

41 lines
1.3 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-jwt';
import { Request } from 'express';
import type { AuthUser, JwtPayload } from '../types/auth-user';
/**
* Custom extractor that reads JWT from the httpOnly "session" cookie.
*/
function cookieExtractor(req: Request): string | null {
if (req?.cookies) {
return req.cookies.session || null;
}
return null;
}
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
jwtFromRequest: cookieExtractor,
ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_SECRET', 'fallback-secret'),
});
}
async validate(payload: JwtPayload): Promise<AuthUser> {
return {
id: payload.sub,
username: payload.username,
role: payload.role,
tenantId: payload.tenantId,
// Ein vor dieser Aenderung ausgestelltes Token traegt diesen Anspruch
// nicht; der strenge Vergleich ergibt dann false, laufende Sitzungen
// verhalten sich unveraendert (260921-fi3, D-01 — keine Aussperrwelle).
mustChangePassword: payload.mustChangePassword === true,
};
}
}