feat(260911-fh9): getMe/changePassword/adminResetPassword an den Mandanten aus dem Sitzungsnachweis binden
- auth.service.ts: die drei Nach-Anmeldungs-Methoden binden je ueber genau einen Klienten tenantPrisma; adminResetPassword verweigert einem Nicht-SUPER_ADMIN das Kennwort eines SUPER_ADMIN (T-FH9-04); die drei $queryRaw-Anmeldesuchen bleiben unveraendert auf dem ungebundenen Klienten - auth.controller.ts: me/changePassword reichen user.tenantId aus dem Claim durch; adminResetPassword verzweigt ueber resolveTargetTenantId nach Rolle (ADMIN: eigener Mandant; SUPER_ADMIN: gebundener Fan-out UserService.findByIdForPlatformAdmin) — schliesst die Rechteausweitung ueber die Mandantengrenze (T-FH9-01) - auth.module.ts: importiert UserModule, zyklusfrei gemessen - auth.service.spec.ts: Identitaets-Attrappe ersetzt durch zwei unterscheidbare Klienten (__makeBoundClient); 29 Faelle, drei Falsifizierungsnachweise durchgefuehrt und zurueckgenommen Bekannt und erwartet: rls-access-inventory.spec.ts ist nach diesem Commit kurzzeitig rot (Bestandsaufnahme-Zeile auth.service.ts/user zeigt noch "gemischt", gemessen ist jetzt "gebunden") — wird in Aufgabe 3 desselben Plans geschlossen (927/928 Tests gruen, ein bekannter, in Aufgabe 3 behobener Fehlschlag, kein neuer). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Role } from '@prisma/client';
|
||||
import { Request, Response } from 'express';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { AuthService } from './auth.service';
|
||||
import { CurrentUser } from './decorators/current-user.decorator';
|
||||
import { Public } from './decorators/public.decorator';
|
||||
@@ -23,7 +25,33 @@ import { RolesGuard } from './guards/roles.guard';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private userService: UserService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Loest den Mandanten fuer `adminResetPassword` auf (260911-fh9,
|
||||
* Praezedenzfall `user.controller.ts` `resolveTargetUser`, 260910-das):
|
||||
* ein ADMIN wirkt auf seinen EIGENEN Mandanten (Claim), die oberste
|
||||
* Rolle (SUPER_ADMIN) behaelt ihre uebergreifende Reichweite ueber den
|
||||
* gebundenen Fan-out `UserService.findByIdForPlatformAdmin` — sonst
|
||||
* saehe ein SUPER_ADMIN nur noch den eigenen Mandanten, eine stille
|
||||
* Funktionsminderung (Befund D). Nicht gefunden: dieselbe
|
||||
* `BadRequestException('User not found')`, die der Dienst bisher ohne
|
||||
* Mandantenpruefung warf, damit ein API-Aufrufer denselben Statuscode
|
||||
* sieht wie vor dieser Umstellung.
|
||||
*/
|
||||
private async resolveTargetTenantId(currentUser: any, userId: string): Promise<string> {
|
||||
if (currentUser.role === Role.SUPER_ADMIN) {
|
||||
const target = await this.userService.findByIdForPlatformAdmin(userId);
|
||||
if (!target) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
return target.tenantId;
|
||||
}
|
||||
return currentUser.tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/login
|
||||
@@ -55,10 +83,16 @@ export class AuthController {
|
||||
* GET /auth/me
|
||||
* Returns enriched user profile: public fields + isLocalUser + hasAvatar.
|
||||
* T-gbh-03: passwordHash and ldapDn are never serialised in the response.
|
||||
*
|
||||
* Mandant kommt ausschliesslich aus dem Sitzungsnachweis (`@CurrentUser()`,
|
||||
* das Claim), NICHT aus der Anfrageobjekt-Eigenschaft, die `TenantGuard`
|
||||
* fuer die oberste Rolle per Kopfzeile umschaltbar macht — ein
|
||||
* umgeschalteter SUPER_ADMIN muss sich selbst weiterhin sehen (260911-fh9,
|
||||
* Befund C).
|
||||
*/
|
||||
@Get('me')
|
||||
async me(@CurrentUser() user: any) {
|
||||
return this.authService.getMe(user.id);
|
||||
return this.authService.getMe(user.tenantId, user.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,6 +135,7 @@ export class AuthController {
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
await this.authService.changePassword(
|
||||
user.tenantId,
|
||||
user.id,
|
||||
dto.currentPassword,
|
||||
dto.newPassword,
|
||||
@@ -113,6 +148,13 @@ export class AuthController {
|
||||
* POST /auth/admin-reset-password/:userId
|
||||
* Admin resets a user's password (D-03 admin reset).
|
||||
* T-02-15: Only ADMIN/SUPER_ADMIN via RolesGuard.
|
||||
*
|
||||
* Der Mandant des Ziels kommt ausschliesslich aus dem Sitzungsnachweis
|
||||
* des AUFRUFERS bzw. aus dem gebundenen Fan-out fuer die oberste Rolle
|
||||
* (`resolveTargetTenantId` oben) — NICHT aus Pfad, Rumpf oder Kopfzeile
|
||||
* (T-FH9-02). `AdminResetPasswordDto` traegt bewusst kein Mandantenfeld.
|
||||
* Kein Frontend-Aufrufer (gemessen, 260911-fh9 Befund D); der
|
||||
* Schwesterweg ist `PATCH /users/:id`.
|
||||
*/
|
||||
@Post('admin-reset-password/:userId')
|
||||
@Roles(Role.ADMIN, Role.SUPER_ADMIN)
|
||||
@@ -121,8 +163,12 @@ export class AuthController {
|
||||
async adminResetPassword(
|
||||
@Param('userId') userId: string,
|
||||
@Body() dto: AdminResetPasswordDto,
|
||||
@CurrentUser() currentUser: any,
|
||||
) {
|
||||
const tenantId = await this.resolveTargetTenantId(currentUser, userId);
|
||||
await this.authService.adminResetPassword(
|
||||
tenantId,
|
||||
currentUser.role,
|
||||
userId,
|
||||
dto.newPassword,
|
||||
dto.mustChangePassword ?? true,
|
||||
|
||||
Reference in New Issue
Block a user