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 type { Request } from 'express';
|
||||||
import { AuthService } from './auth.service.js';
|
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 { Public } from './decorators.js';
|
||||||
import { getRequestContext } from '../common/context/request-context.js';
|
import { getRequestContext } from '../common/context/request-context.js';
|
||||||
|
|
||||||
@ -48,4 +48,12 @@ export class AuthController {
|
|||||||
if (!ctx) throw new UnauthorizedException('未登录');
|
if (!ctx) throw new UnauthorizedException('未登录');
|
||||||
return this.auth.getPermissionSet(ctx.tenantCode, ctx.userId);
|
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 { createHash, randomUUID } from 'node:crypto';
|
||||||
import { PrismaService } from '../prisma/prisma.service.js';
|
import { PrismaService } from '../prisma/prisma.service.js';
|
||||||
import { RedisService } from '../common/redis/redis.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 { resolveDataScope, resolveVisibleOrgs } from '../common/data-scope.js';
|
||||||
import type { DataScope } from '../common/enums.js';
|
import type { DataScope } from '../common/enums.js';
|
||||||
import type { RequestContext } from '../common/context/request-context.js';
|
import type { RequestContext } from '../common/context/request-context.js';
|
||||||
@ -140,6 +140,25 @@ export class AuthService {
|
|||||||
return { id: user.id, username: user.username };
|
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 }> {
|
async refresh(dto: RefreshDto): Promise<{ accessToken: string; refreshToken: string; expiresIn: number }> {
|
||||||
const record = await this.prisma.client.refreshToken.findUnique({ where: { jti: dto.refreshToken } });
|
const record = await this.prisma.client.refreshToken.findUnique({ where: { jti: dto.refreshToken } });
|
||||||
if (!record || record.revokedAt || record.expireAt.getTime() < Date.now()) {
|
if (!record || record.revokedAt || record.expireAt.getTime() < Date.now()) {
|
||||||
|
|||||||
@ -45,6 +45,18 @@ export class RegisterDto {
|
|||||||
phone?: string;
|
phone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ChangePasswordDto {
|
||||||
|
@ApiProperty({ description: '原密码' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
oldPassword!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '新密码' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
newPassword!: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class RefreshDto {
|
export class RefreshDto {
|
||||||
@ApiProperty({ description: '刷新令牌' })
|
@ApiProperty({ description: '刷新令牌' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user