From 5779f0f6c9727641c7573244c600529fc97ccbcf Mon Sep 17 00:00:00 2001 From: Schalli Date: Mon, 29 Jun 2026 16:09:36 +0200 Subject: [PATCH] fix(api): reissue JWT with mustChangePassword=false after password change After a successful password change the old cookie still contained mustChangePassword=true, causing the middleware to redirect back to /change-password. Now changePassword issues a fresh session cookie. Co-Authored-By: Claude Sonnet 4.6 --- apps/api/src/auth/auth.controller.ts | 2 ++ apps/api/src/auth/auth.service.ts | 24 ++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/api/src/auth/auth.controller.ts b/apps/api/src/auth/auth.controller.ts index dd82654..a2c8820 100644 --- a/apps/api/src/auth/auth.controller.ts +++ b/apps/api/src/auth/auth.controller.ts @@ -97,11 +97,13 @@ export class AuthController { async changePassword( @CurrentUser() user: any, @Body() dto: ChangePasswordDto, + @Res({ passthrough: true }) res: Response, ) { await this.authService.changePassword( user.id, dto.currentPassword, dto.newPassword, + res, ); return { message: 'Password changed successfully.' }; } diff --git a/apps/api/src/auth/auth.service.ts b/apps/api/src/auth/auth.service.ts index c1b6154..f815260 100644 --- a/apps/api/src/auth/auth.service.ts +++ b/apps/api/src/auth/auth.service.ts @@ -190,6 +190,7 @@ export class AuthService { userId: string, currentPassword: string, newPassword: string, + response: Response, ): Promise { const user = await this.prisma.user.findUnique({ where: { id: userId }, @@ -199,20 +200,31 @@ export class AuthService { throw new UnauthorizedException('User not found or has no local password'); } - // Verify current password const isValid = await argon2.verify(user.passwordHash, currentPassword); if (!isValid) { throw new UnauthorizedException('Current password is incorrect'); } - // Hash new password and update const passwordHash = await argon2.hash(newPassword); await this.prisma.user.update({ where: { id: userId }, - data: { - passwordHash, - mustChangePassword: false, - }, + data: { passwordHash, mustChangePassword: false }, + }); + + const payload = { + sub: user.id, + username: user.username, + role: user.role, + tenantId: user.tenantId, + mustChangePassword: false, + }; + const token = this.jwtService.sign(payload); + (response as any).cookie('session', token, { + httpOnly: true, + secure: this.configService.get('NODE_ENV') === 'production', + sameSite: 'lax', + maxAge: 30 * 24 * 60 * 60 * 1000, + path: '/', }); this.logger.log(`Password changed for user ${userId}`);