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 <noreply@anthropic.com>
This commit is contained in:
@@ -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.' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user