fix(api): reissue JWT with mustChangePassword=false after password change
Tessera CI/CD / Lint & Type Check (push) Successful in 39s
Tessera CI/CD / Tests (push) Successful in 36s
Tessera CI/CD / Build & Publish Images (push) Successful in 23s

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 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 16:09:36 +02:00
parent b28ee472c5
commit 5779f0f6c9
2 changed files with 20 additions and 6 deletions
+2
View File
@@ -97,11 +97,13 @@ export class AuthController {
async changePassword( async changePassword(
@CurrentUser() user: any, @CurrentUser() user: any,
@Body() dto: ChangePasswordDto, @Body() dto: ChangePasswordDto,
@Res({ passthrough: true }) res: Response,
) { ) {
await this.authService.changePassword( await this.authService.changePassword(
user.id, user.id,
dto.currentPassword, dto.currentPassword,
dto.newPassword, dto.newPassword,
res,
); );
return { message: 'Password changed successfully.' }; return { message: 'Password changed successfully.' };
} }
+17 -5
View File
@@ -190,6 +190,7 @@ export class AuthService {
userId: string, userId: string,
currentPassword: string, currentPassword: string,
newPassword: string, newPassword: string,
response: Response,
): Promise<void> { ): Promise<void> {
const user = await this.prisma.user.findUnique({ const user = await this.prisma.user.findUnique({
where: { id: userId }, where: { id: userId },
@@ -199,20 +200,31 @@ export class AuthService {
throw new UnauthorizedException('User not found or has no local password'); throw new UnauthorizedException('User not found or has no local password');
} }
// Verify current password
const isValid = await argon2.verify(user.passwordHash, currentPassword); const isValid = await argon2.verify(user.passwordHash, currentPassword);
if (!isValid) { if (!isValid) {
throw new UnauthorizedException('Current password is incorrect'); throw new UnauthorizedException('Current password is incorrect');
} }
// Hash new password and update
const passwordHash = await argon2.hash(newPassword); const passwordHash = await argon2.hash(newPassword);
await this.prisma.user.update({ await this.prisma.user.update({
where: { id: userId }, where: { id: userId },
data: { data: { passwordHash, mustChangePassword: false },
passwordHash, });
const payload = {
sub: user.id,
username: user.username,
role: user.role,
tenantId: user.tenantId,
mustChangePassword: false, 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}`); this.logger.log(`Password changed for user ${userId}`);