feat(auth): PATCH /auth/me/password 修改本人密码
- 校验原密码 + 更新后 tokenVersion 吊销其它会话 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
13510ee216
commit
6578db51c7
@ -1,7 +1,7 @@
|
||||
import { Body, Controller, Get, Post, Req, UnauthorizedException } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Patch, Post, Req, UnauthorizedException } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { AuthService } from './auth.service.js';
|
||||
import { LoginDto, RefreshDto, RegisterDto } from './dto.js';
|
||||
import { ChangePasswordDto, LoginDto, RefreshDto, RegisterDto } from './dto.js';
|
||||
import { Public } from './decorators.js';
|
||||
import { getRequestContext } from '../common/context/request-context.js';
|
||||
|
||||
@ -48,4 +48,12 @@ export class AuthController {
|
||||
if (!ctx) throw new UnauthorizedException('未登录');
|
||||
return this.auth.getPermissionSet(ctx.tenantCode, ctx.userId);
|
||||
}
|
||||
|
||||
/** 修改本人密码(仅需登录)。 */
|
||||
@Patch('me/password')
|
||||
changePassword(@Body() dto: ChangePasswordDto) {
|
||||
const ctx = getRequestContext();
|
||||
if (!ctx) throw new UnauthorizedException('未登录');
|
||||
return this.auth.changePassword(ctx.tenantCode, ctx.userId, dto);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import bcrypt from 'bcryptjs';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { PrismaService } from '../prisma/prisma.service.js';
|
||||
import { RedisService } from '../common/redis/redis.service.js';
|
||||
import { LoginDto, RefreshDto, RegisterDto } from './dto.js';
|
||||
import { LoginDto, RefreshDto, RegisterDto, ChangePasswordDto } from './dto.js';
|
||||
import { resolveDataScope, resolveVisibleOrgs } from '../common/data-scope.js';
|
||||
import type { DataScope } from '../common/enums.js';
|
||||
import type { RequestContext } from '../common/context/request-context.js';
|
||||
@ -140,6 +140,25 @@ export class AuthService {
|
||||
return { id: user.id, username: user.username };
|
||||
}
|
||||
|
||||
/** 修改本人密码:校验原密码,更新后吊销其它会话。 */
|
||||
async changePassword(tenantCode: string, userId: string, dto: ChangePasswordDto): Promise<null> {
|
||||
const user = await this.prisma.client.user.findFirst({
|
||||
where: { tenantCode, id: userId, deletedAt: null },
|
||||
});
|
||||
if (!user) throw new UnauthorizedException('账号不存在');
|
||||
const ok = await bcrypt.compare(dto.oldPassword, user.passwordHash);
|
||||
if (!ok) throw new BadRequestException('原密码错误');
|
||||
const passwordHash = await bcrypt.hash(dto.newPassword, 10);
|
||||
await this.prisma.client.user.updateMany({
|
||||
where: { tenantCode, id: userId },
|
||||
data: { passwordHash, tokenVersion: { increment: 1 } },
|
||||
});
|
||||
await this.prisma.client.refreshToken.deleteMany({ where: { tenantCode, userId } });
|
||||
await this.redis.del(permCacheKey(tenantCode, userId));
|
||||
await this.redis.del(userStateCacheKey(tenantCode, userId));
|
||||
return null;
|
||||
}
|
||||
|
||||
async refresh(dto: RefreshDto): Promise<{ accessToken: string; refreshToken: string; expiresIn: number }> {
|
||||
const record = await this.prisma.client.refreshToken.findUnique({ where: { jti: dto.refreshToken } });
|
||||
if (!record || record.revokedAt || record.expireAt.getTime() < Date.now()) {
|
||||
|
||||
@ -45,6 +45,18 @@ export class RegisterDto {
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
export class ChangePasswordDto {
|
||||
@ApiProperty({ description: '原密码' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
oldPassword!: string;
|
||||
|
||||
@ApiProperty({ description: '新密码' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
newPassword!: string;
|
||||
}
|
||||
|
||||
export class RefreshDto {
|
||||
@ApiProperty({ description: '刷新令牌' })
|
||||
@IsString()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user