parent
99739064cc
commit
1c6908b6a5
@ -6,6 +6,7 @@ export enum EXCEPTION_CODE {
|
||||
USER_EXISTS = 2001, // 用户已存在
|
||||
USER_NOT_EXISTS = 2002, // 用户不存在
|
||||
USER_PASSWORD_WRONG = 2003, // 用户名或密码错误
|
||||
PASSWORD_INVALID = 2004, // 密码无效
|
||||
NO_SURVEY_PERMISSION = 3001, // 没有问卷权限
|
||||
SURVEY_STATUS_TRANSFORM_ERROR = 3002, // 问卷状态转换报错
|
||||
SURVEY_TYPE_ERROR = 3003, // 问卷类型错误
|
||||
|
@ -82,6 +82,19 @@ describe('AuthController', () => {
|
||||
new HttpException('验证码不正确', EXCEPTION_CODE.CAPTCHA_INCORRECT),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw HttpException with PASSWORD_INVALID code when password is invalid', async () => {
|
||||
const mockUserInfo = {
|
||||
username: 'testUser',
|
||||
password: '无效的密码abc123',
|
||||
captchaId: 'testCaptchaId',
|
||||
captcha: 'testCaptcha',
|
||||
};
|
||||
|
||||
await expect(controller.register(mockUserInfo)).rejects.toThrow(
|
||||
new HttpException('密码无效', EXCEPTION_CODE.PASSWORD_INVALID),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
||||
import { Controller, Post, Body, HttpCode, Get, Query } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { UserService } from '../services/user.service';
|
||||
import { CaptchaService } from '../services/captcha.service';
|
||||
@ -7,6 +7,9 @@ import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { create } from 'svg-captcha';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
const passwordReg = /^[a-zA-Z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+$/;
|
||||
|
||||
@ApiTags('auth')
|
||||
@Controller('/api/auth')
|
||||
export class AuthController {
|
||||
@ -28,6 +31,24 @@ export class AuthController {
|
||||
captcha: string;
|
||||
},
|
||||
) {
|
||||
if (!userInfo.password) {
|
||||
throw new HttpException('密码无效', EXCEPTION_CODE.PASSWORD_INVALID);
|
||||
}
|
||||
|
||||
if (userInfo.password.length < 6 || userInfo.password.length > 16) {
|
||||
throw new HttpException(
|
||||
'密码长度在 6 到 16 个字符',
|
||||
EXCEPTION_CODE.PASSWORD_INVALID,
|
||||
);
|
||||
}
|
||||
|
||||
if (!passwordReg.test(userInfo.password)) {
|
||||
throw new HttpException(
|
||||
'密码只能输入数字、字母、特殊字符',
|
||||
EXCEPTION_CODE.PASSWORD_INVALID,
|
||||
);
|
||||
}
|
||||
|
||||
const isCorrect = await this.captchaService.checkCaptchaIsCorrect({
|
||||
captcha: userInfo.captcha,
|
||||
id: userInfo.captchaId,
|
||||
@ -162,4 +183,35 @@ export class AuthController {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码强度
|
||||
*/
|
||||
@Get('register/password/strength')
|
||||
@HttpCode(200)
|
||||
async getPasswordStrength(@Query('password') password: string) {
|
||||
const numberReg = /[0-9]/.test(password);
|
||||
const letterReg = /[a-zA-Z]/.test(password);
|
||||
const symbolReg = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password);
|
||||
// 包含三种、且长度大于8
|
||||
if (numberReg && letterReg && symbolReg && password.length >= 8) {
|
||||
return {
|
||||
code: 200,
|
||||
data: 'Strong',
|
||||
};
|
||||
}
|
||||
|
||||
// 满足任意两种
|
||||
if ([numberReg, letterReg, symbolReg].filter(Boolean).length >= 2) {
|
||||
return {
|
||||
code: 200,
|
||||
data: 'Medium',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
data: 'Weak',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -7,3 +7,12 @@ export const register = (data) => {
|
||||
export const login = (data) => {
|
||||
return axios.post('/auth/login', data)
|
||||
}
|
||||
|
||||
/** 获取密码强度 */
|
||||
export const getPasswordStrength = (password) => {
|
||||
return axios.get('/auth/register/password/strength', {
|
||||
params: {
|
||||
password
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -27,6 +27,15 @@
|
||||
<el-input type="password" v-model="formData.password" size="large"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="" v-if="passwordStrength">
|
||||
<span
|
||||
class="strength"
|
||||
v-for="item in 3"
|
||||
:key="item"
|
||||
:style="{ backgroundColor: strengthColor[item - 1][passwordStrength] }"
|
||||
></span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="验证码" prop="captcha">
|
||||
<div class="captcha-wrapper">
|
||||
<el-input style="width: 150px" v-model="formData.captcha" size="large"></el-input>
|
||||
@ -62,7 +71,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import 'element-plus/theme-chalk/src/message.scss'
|
||||
|
||||
import { login, register } from '@/management/api/auth'
|
||||
import { getPasswordStrength, login, register } from '@/management/api/auth'
|
||||
import { refreshCaptcha as refreshCaptchaApi } from '@/management/api/captcha'
|
||||
import { CODE_MAP } from '@/management/api/base'
|
||||
import { useUserStore } from '@/management/stores/user'
|
||||
@ -89,6 +98,55 @@ const formData = reactive<FormData>({
|
||||
captchaId: ''
|
||||
})
|
||||
|
||||
// 每个滑块不同强度的颜色,索引0对应第一个滑块
|
||||
const strengthColor = reactive([
|
||||
{
|
||||
Strong: '#67C23A',
|
||||
Medium: '#ebb563',
|
||||
Weak: '#f78989'
|
||||
},
|
||||
{
|
||||
Strong: '#67C23A',
|
||||
Medium: '#ebb563',
|
||||
Weak: '#2a598a'
|
||||
},
|
||||
{
|
||||
Strong: '#67C23A',
|
||||
Medium: '#2a598a',
|
||||
Weak: '#2a598a'
|
||||
}
|
||||
])
|
||||
|
||||
// 密码内容校验
|
||||
const passwordValidator = (_: any, value: any, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请输入密码'))
|
||||
passwordStrength.value = undefined
|
||||
return
|
||||
}
|
||||
|
||||
if (value.length < 6 || value.length > 16) {
|
||||
callback(new Error('长度在 6 到 16 个字符'))
|
||||
passwordStrength.value = undefined
|
||||
return
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+$/.test(value)) {
|
||||
callback(new Error('只能输入数字、字母、特殊字符'))
|
||||
passwordStrength.value = undefined
|
||||
return
|
||||
}
|
||||
passwordStrengthHandle(value)
|
||||
callback()
|
||||
}
|
||||
|
||||
const passwordStrengthHandle = async (value: string) => {
|
||||
const res: any = await getPasswordStrength(value)
|
||||
if (res.code === CODE_MAP.SUCCESS) {
|
||||
passwordStrength.value = res.data
|
||||
}
|
||||
}
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: '请输入账号', trigger: 'blur' },
|
||||
@ -100,11 +158,8 @@ const rules = {
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{
|
||||
min: 8,
|
||||
max: 16,
|
||||
message: '长度在 8 到 16 个字符',
|
||||
validator: passwordValidator,
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
@ -128,6 +183,7 @@ const pending = reactive<Pending>({
|
||||
|
||||
const captchaImgData = ref<string>('')
|
||||
const formDataRef = ref<any>(null)
|
||||
const passwordStrength = ref<'Strong' | 'Medium' | 'Weak'>()
|
||||
|
||||
const submitForm = (type: 'login' | 'register') => {
|
||||
formDataRef.value.validate(async (valid: boolean) => {
|
||||
@ -258,5 +314,16 @@ const refreshCaptcha = async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.strength {
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
height: 6px;
|
||||
border-radius: 8px;
|
||||
background: red;
|
||||
&:not(:first-child) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user