diff --git a/packages/ui/certd-server/src/controller/sys/authority/user-controller.ts b/packages/ui/certd-server/src/controller/sys/authority/user-controller.ts index e31e915c..ccd5da62 100644 --- a/packages/ui/certd-server/src/controller/sys/authority/user-controller.ts +++ b/packages/ui/certd-server/src/controller/sys/authority/user-controller.ts @@ -1,10 +1,11 @@ -import { Provide, Controller, Post, Inject, Body, Query, ALL } from '@midwayjs/core'; -import { UserService } from '../../../modules/sys/authority/service/user-service.js'; -import { CrudController } from '@certd/lib-server'; -import { RoleService } from '../../../modules/sys/authority/service/role-service.js'; -import { PermissionService } from '../../../modules/sys/authority/service/permission-service.js'; -import { Constants } from '@certd/lib-server'; -import { In } from 'typeorm'; +import {Provide, Controller, Post, Inject, Body, Query, ALL} from '@midwayjs/core'; +import {UserService} from '../../../modules/sys/authority/service/user-service.js'; +import {CrudController} from '@certd/lib-server'; +import {RoleService} from '../../../modules/sys/authority/service/role-service.js'; +import {PermissionService} from '../../../modules/sys/authority/service/permission-service.js'; +import {Constants} from '@certd/lib-server'; +import {In} from 'typeorm'; +import {LoginService} from "../../../modules/login/service/login-service.js"; /** * 系统用户 @@ -20,11 +21,14 @@ export class UserController extends CrudController { @Inject() permissionService: PermissionService; + @Inject() + loginService: LoginService; + getService() { return this.service; } - @Post('/getSimpleUserByIds', { summary: 'sys:auth:user:add' }) + @Post('/getSimpleUserByIds', {summary: 'sys:auth:user:add'}) async getSimpleUserByIds(@Body('ids') ids: number[]) { const users = await this.service.find({ select: { @@ -42,10 +46,10 @@ export class UserController extends CrudController { return this.ok(users); } - @Post('/page', { summary: 'sys:auth:user:view' }) + @Post('/page', {summary: 'sys:auth:user:view'}) async page( @Body(ALL) - body + body ) { const ret = await super.page(body); @@ -74,25 +78,26 @@ export class UserController extends CrudController { return ret; } - @Post('/add', { summary: 'sys:auth:user:add' }) + @Post('/add', {summary: 'sys:auth:user:add'}) async add( @Body(ALL) - bean + bean ) { return await super.add(bean); } - @Post('/update', { summary: 'sys:auth:user:edit' }) + @Post('/update', {summary: 'sys:auth:user:edit'}) async update( @Body(ALL) - bean + bean ) { return await super.update(bean); } - @Post('/delete', { summary: 'sys:auth:user:remove' }) + + @Post('/delete', {summary: 'sys:auth:user:remove'}) async delete( @Query('id') - id: number + id: number ) { if (id === 1) { throw new Error('不能删除默认的管理员角色'); @@ -103,10 +108,23 @@ export class UserController extends CrudController { return await super.delete(id); } + /** + * 解除登录锁定 + */ + @Post('/unlockBlock', {summary: "sys:auth:user:edit"}) + public async unlockBlock(@Body('id') id: number) { + const info = await this.service.info(id, ['password']); + this.loginService.clearCacheOnSuccess(info.username) + if (info.mobile) { + this.loginService.clearCacheOnSuccess(info.mobile) + } + return this.ok(info); + } + /** * 当前登录用户的个人信息 */ - @Post('/mine', { summary: Constants.per.authOnly }) + @Post('/mine', {summary: Constants.per.authOnly}) public async mine() { const id = this.getUserId(); const info = await this.service.info(id, ['password']); @@ -116,7 +134,7 @@ export class UserController extends CrudController { /** * 当前登录用户的权限列表 */ - @Post('/permissions', { summary: Constants.per.authOnly }) + @Post('/permissions', {summary: Constants.per.authOnly}) public async permissions() { const id = this.getUserId(); const permissions = await this.service.getUserPermissions(id); @@ -126,11 +144,13 @@ export class UserController extends CrudController { /** * 当前登录用户的权限树形列表 */ - @Post('/permissionTree', { summary: Constants.per.authOnly }) + @Post('/permissionTree', {summary: Constants.per.authOnly}) public async permissionTree() { const id = this.getUserId(); const permissions = await this.service.getUserPermissions(id); const tree = this.permissionService.buildTree(permissions); return this.ok(tree); } + + } diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts index 88df4f1c..16c8bacc 100644 --- a/packages/ui/certd-server/src/modules/login/service/login-service.ts +++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts @@ -1,20 +1,20 @@ -import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; -import { UserService } from '../../sys/authority/service/user-service.js'; +import {Config, Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core'; +import {UserService} from '../../sys/authority/service/user-service.js'; import jwt from 'jsonwebtoken'; -import { CommonException } from '@certd/lib-server'; -import { RoleService } from '../../sys/authority/service/role-service.js'; -import { UserEntity } from '../../sys/authority/entity/user.js'; -import { SysSettingsService } from '@certd/lib-server'; -import { SysPrivateSettings } from '@certd/lib-server'; -import { cache } from '@certd/basic'; -import { LoginErrorException } from '@certd/lib-server/dist/basic/exception/login-error-exception.js'; -import { CodeService } from '../../basic/service/code-service.js'; +import {CommonException} from '@certd/lib-server'; +import {RoleService} from '../../sys/authority/service/role-service.js'; +import {UserEntity} from '../../sys/authority/entity/user.js'; +import {SysSettingsService} from '@certd/lib-server'; +import {SysPrivateSettings} from '@certd/lib-server'; +import {cache} from '@certd/basic'; +import {LoginErrorException} from '@certd/lib-server/dist/basic/exception/login-error-exception.js'; +import {CodeService} from '../../basic/service/code-service.js'; /** * 系统用户 */ @Provide() -@Scope(ScopeEnum.Request, { allowDowngrade: true }) +@Scope(ScopeEnum.Request, {allowDowngrade: true}) export class LoginService { @Inject() userService: UserService; @@ -29,51 +29,72 @@ export class LoginService { @Inject() sysSettingsService: SysSettingsService; - checkErrorTimes(username: string, errorMessage: string) { - const cacheKey = `login_error_times:${username}`; - const blockTimesKey = `login_block_times:${username}`; - let blockTimes = cache.get(blockTimesKey); - let maxWaitMin = 2; - const maxRetryTimes = 5; - if (blockTimes == null) { - blockTimes = 1; + checkIsBlocked(username: string) { + const blockDurationKey = `login_block_duration:${username}`; + const value = cache.get(blockDurationKey); + if (value) { + const ttl = cache.getRemainingTTL(blockDurationKey) + const leftMin = Math.ceil(ttl / 1000 / 60); + throw new CommonException(`账号被锁定,请${leftMin}分钟后重试`); } - maxWaitMin = maxWaitMin * blockTimes; - let ttl = maxWaitMin * 60 * 1000; + } - let errorTimes = cache.get(cacheKey); + clearCacheOnSuccess(username: string) { + cache.delete(`login_error_times:${username}`); + cache.delete(`login_block_times:${username}`); + cache.delete(`login_block_duration:${username}`); + } + + addErrorTimes(username: string, errorMessage: string) { + const errorTimesKey = `login_error_times:${username}`; + const blockTimesKey = `login_block_times:${username}`; + const blockDurationKey = `login_block_duration:${username}`; + let blockTimes = cache.get(blockTimesKey); + // let maxWaitMin = 2; + const maxRetryTimes = blockTimes > 1 ? 3 : 5; + if (blockTimes == null) { + blockTimes = 0; + } + // maxWaitMin = maxWaitMin * blockTimes; + // let ttl = maxWaitMin * 60 * 1000; + + let errorTimes = cache.get(errorTimesKey); if (errorTimes == null) { errorTimes = 0; - } else { - const remainingTTL = cache.getRemainingTTL(cacheKey); - if (remainingTTL > 0) { - ttl = remainingTTL; - } } errorTimes += 1; - - cache.set(cacheKey, errorTimes, { - ttl: ttl, + const ttl24H = 24 * 60 * 60 * 1000; + cache.set(errorTimesKey, errorTimes, { + ttl: ttl24H, }); - if (errorTimes >= maxRetryTimes) { - if (errorTimes === maxRetryTimes) { - blockTimes += 1; - cache.set(blockTimesKey, blockTimes, { - ttl: 24 * 60 * 60 * 1000, - }); - } + if (errorTimes > maxRetryTimes) { + blockTimes += 1; + cache.set(blockTimesKey, blockTimes, { + ttl: ttl24H, + }); + //按照block次数指数递增,最长24小时 + const ttl = Math.min(blockTimes * blockTimes * 60 * 1000, ttl24H); const leftMin = Math.ceil(ttl / 1000 / 60); + cache.set(blockDurationKey, 1, { + ttl: ttl, + }) + // 清除error次数 + cache.delete(errorTimesKey); throw new LoginErrorException(`登录失败次数过多,请${leftMin}分钟后重试`, 0); } const leftTimes = maxRetryTimes - errorTimes; if (leftTimes < 3) { - throw new LoginErrorException(`登录失败,剩余尝试次数:${leftTimes}`, leftTimes); + throw new LoginErrorException(`登录失败(${errorMessage}),剩余尝试次数:${leftTimes}`, leftTimes); } throw new LoginErrorException(errorMessage, leftTimes); } + async loginBySmsCode(req: { mobile: string; phoneCode: string; smsCode: string; randomStr: string }) { + + this.checkIsBlocked(req.mobile) + const smsChecked = await this.codeService.checkSmsCode({ mobile: req.mobile, phoneCode: req.phoneCode, @@ -82,11 +103,11 @@ export class LoginService { throwError: false, }); - const { mobile, phoneCode } = req; + const {mobile, phoneCode} = req; if (!smsChecked) { - this.checkErrorTimes(mobile, '验证码错误'); + this.addErrorTimes(mobile, '验证码错误'); } - let info = await this.userService.findOne({ phoneCode, mobile: mobile }); + let info = await this.userService.findOne({phoneCode, mobile: mobile}); if (info == null) { //用户不存在,注册 info = await this.userService.register('mobile', { @@ -95,39 +116,30 @@ export class LoginService { password: '', } as any); } + this.clearCacheOnSuccess(mobile); return this.onLoginSuccess(info); } async loginByPassword(req: { username: string; password: string; phoneCode: string }) { - const { username, password, phoneCode } = req; - const info = await this.userService.findOne([{ username: username }, { email: username }, { phoneCode, mobile: username }]); + this.checkIsBlocked(req.username) + const {username, password, phoneCode} = req; + const info = await this.userService.findOne([{username: username}, {email: username}, { + phoneCode, + mobile: username + }]); if (info == null) { throw new CommonException('用户名或密码错误'); } const right = await this.userService.checkPassword(password, info.password, info.passwordVersion); if (!right) { - this.checkErrorTimes(username, '用户名或密码错误'); + this.addErrorTimes(username, '用户名或密码错误'); } + this.clearCacheOnSuccess(username); return this.onLoginSuccess(info); } - // /** - // * login - // */ - // async login(user) { - // console.assert(user.username != null, '用户名不能为空'); - // const info = await this.userService.findOne({ username: user.username }); - // if (info == null) { - // throw new CommonException('用户名或密码错误'); - // } - // const right = await this.userService.checkPassword(user.password, info.password, info.passwordVersion); - // if (!right) { - // this.checkErrorTimes(user.username, '用户名或密码错误'); - // } - // return await this.onLoginSuccess(info); - // } - private async onLoginSuccess(info: UserEntity) { + if (info.status === 0) { throw new CommonException('用户已被禁用'); }