parent
99739064cc
commit
1c6908b6a5
@ -6,6 +6,7 @@ export enum EXCEPTION_CODE {
|
|||||||
USER_EXISTS = 2001, // 用户已存在
|
USER_EXISTS = 2001, // 用户已存在
|
||||||
USER_NOT_EXISTS = 2002, // 用户不存在
|
USER_NOT_EXISTS = 2002, // 用户不存在
|
||||||
USER_PASSWORD_WRONG = 2003, // 用户名或密码错误
|
USER_PASSWORD_WRONG = 2003, // 用户名或密码错误
|
||||||
|
PASSWORD_INVALID = 2004, // 密码无效
|
||||||
NO_SURVEY_PERMISSION = 3001, // 没有问卷权限
|
NO_SURVEY_PERMISSION = 3001, // 没有问卷权限
|
||||||
SURVEY_STATUS_TRANSFORM_ERROR = 3002, // 问卷状态转换报错
|
SURVEY_STATUS_TRANSFORM_ERROR = 3002, // 问卷状态转换报错
|
||||||
SURVEY_TYPE_ERROR = 3003, // 问卷类型错误
|
SURVEY_TYPE_ERROR = 3003, // 问卷类型错误
|
||||||
|
@ -82,6 +82,19 @@ describe('AuthController', () => {
|
|||||||
new HttpException('验证码不正确', EXCEPTION_CODE.CAPTCHA_INCORRECT),
|
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', () => {
|
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 { ConfigService } from '@nestjs/config';
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
import { CaptchaService } from '../services/captcha.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 { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { create } from 'svg-captcha';
|
import { create } from 'svg-captcha';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
const passwordReg = /^[a-zA-Z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]+$/;
|
||||||
|
|
||||||
@ApiTags('auth')
|
@ApiTags('auth')
|
||||||
@Controller('/api/auth')
|
@Controller('/api/auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
@ -28,6 +31,24 @@ export class AuthController {
|
|||||||
captcha: string;
|
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({
|
const isCorrect = await this.captchaService.checkCaptchaIsCorrect({
|
||||||
captcha: userInfo.captcha,
|
captcha: userInfo.captcha,
|
||||||
id: userInfo.captchaId,
|
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) => {
|
export const login = (data) => {
|
||||||
return axios.post('/auth/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-input type="password" v-model="formData.password" size="large"></el-input>
|
||||||
</el-form-item>
|
</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">
|
<el-form-item label="验证码" prop="captcha">
|
||||||
<div class="captcha-wrapper">
|
<div class="captcha-wrapper">
|
||||||
<el-input style="width: 150px" v-model="formData.captcha" size="large"></el-input>
|
<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 { ElMessage } from 'element-plus'
|
||||||
import 'element-plus/theme-chalk/src/message.scss'
|
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 { refreshCaptcha as refreshCaptchaApi } from '@/management/api/captcha'
|
||||||
import { CODE_MAP } from '@/management/api/base'
|
import { CODE_MAP } from '@/management/api/base'
|
||||||
import { useUserStore } from '@/management/stores/user'
|
import { useUserStore } from '@/management/stores/user'
|
||||||
@ -89,6 +98,55 @@ const formData = reactive<FormData>({
|
|||||||
captchaId: ''
|
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 = {
|
const rules = {
|
||||||
name: [
|
name: [
|
||||||
{ required: true, message: '请输入账号', trigger: 'blur' },
|
{ required: true, message: '请输入账号', trigger: 'blur' },
|
||||||
@ -100,11 +158,8 @@ const rules = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
|
||||||
{
|
{
|
||||||
min: 8,
|
validator: passwordValidator,
|
||||||
max: 16,
|
|
||||||
message: '长度在 8 到 16 个字符',
|
|
||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -128,6 +183,7 @@ const pending = reactive<Pending>({
|
|||||||
|
|
||||||
const captchaImgData = ref<string>('')
|
const captchaImgData = ref<string>('')
|
||||||
const formDataRef = ref<any>(null)
|
const formDataRef = ref<any>(null)
|
||||||
|
const passwordStrength = ref<'Strong' | 'Medium' | 'Weak'>()
|
||||||
|
|
||||||
const submitForm = (type: 'login' | 'register') => {
|
const submitForm = (type: 'login' | 'register') => {
|
||||||
formDataRef.value.validate(async (valid: boolean) => {
|
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>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user