diff --git a/easytier-web/frontend-lib/src/locales/cn.yaml b/easytier-web/frontend-lib/src/locales/cn.yaml index 9f3751b2..203f4929 100644 --- a/easytier-web/frontend-lib/src/locales/cn.yaml +++ b/easytier-web/frontend-lib/src/locales/cn.yaml @@ -363,6 +363,10 @@ web: success: 成功 warning: 警告 info: 提示 + password_empty: 密码不能为空 + password_min_length: 密码至少需要 8 位 + password_too_weak: 密码强度不足 + password_strength_hint: 密码至少 8 位,且需包含大小写字母、数字、特殊字符中的至少 2 类 enable: 开启 disable: 关闭 address: 地址 diff --git a/easytier-web/frontend-lib/src/locales/en.yaml b/easytier-web/frontend-lib/src/locales/en.yaml index ebbc361b..1e017af8 100644 --- a/easytier-web/frontend-lib/src/locales/en.yaml +++ b/easytier-web/frontend-lib/src/locales/en.yaml @@ -363,6 +363,10 @@ web: success: Success warning: Warning info: Info + password_empty: Password cannot be empty + password_min_length: Password must be at least 8 characters long + password_too_weak: Password is too weak + password_strength_hint: Password must be at least 8 characters and include at least 2 of uppercase letters, lowercase letters, numbers, or special characters enable: Enable disable: Disable address: Address diff --git a/easytier-web/frontend/src/components/ChangePassword.vue b/easytier-web/frontend/src/components/ChangePassword.vue index 29f1be60..8de920bd 100644 --- a/easytier-web/frontend/src/components/ChangePassword.vue +++ b/easytier-web/frontend/src/components/ChangePassword.vue @@ -6,6 +6,7 @@ import { useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import ApiClient from '../modules/api'; import { clearMustChangePasswordFlag } from '../modules/auth-status'; +import { validatePasswordStrength } from '../modules/password-policy'; const dialogRef = inject('dialogRef'); @@ -15,14 +16,21 @@ const password = ref(''); const toast = useToast(); const router = useRouter(); const { t } = useI18n(); -const passwordIsEmpty = computed(() => password.value.trim().length === 0); +const passwordValidation = computed(() => validatePasswordStrength(password.value)); +const passwordErrorMessage = computed(() => { + if (password.value.length === 0 || passwordValidation.value.valid) { + return ''; + } + + return t(passwordValidation.value.reasonKey!); +}); const changePassword = async () => { - if (passwordIsEmpty.value) { + if (!passwordValidation.value.valid) { toast.add({ severity: 'warn', summary: t('web.common.warning'), - detail: t('web.settings.new_password_empty'), + detail: t(passwordValidation.value.reasonKey!), life: 3000, }); return; @@ -61,8 +69,14 @@ const changePassword = async () => {
+ + {{ t('web.common.password_strength_hint') }} + + + {{ passwordErrorMessage }} +
diff --git a/easytier-web/frontend/src/components/Login.vue b/easytier-web/frontend/src/components/Login.vue index 5dd33135..6420351e 100644 --- a/easytier-web/frontend/src/components/Login.vue +++ b/easytier-web/frontend/src/components/Login.vue @@ -8,6 +8,7 @@ import { getInitialApiHost, cleanAndLoadApiHosts, saveApiHost } from "../modules import { useI18n } from 'vue-i18n' import ApiClient, { Credential, RegisterData } from '../modules/api'; import { setMustChangePasswordFlag } from '../modules/auth-status'; +import { validatePasswordStrength } from '../modules/password-policy'; const { t } = useI18n() @@ -25,6 +26,14 @@ const registerUsername = ref(''); const registerPassword = ref(''); const captcha = ref(''); const captchaSrc = computed(() => api.value.captcha_url()); +const registerPasswordValidation = computed(() => validatePasswordStrength(registerPassword.value)); +const registerPasswordErrorMessage = computed(() => { + if (registerPassword.value.length === 0 || registerPasswordValidation.value.valid) { + return ''; + } + + return t(registerPasswordValidation.value.reasonKey!); +}); const onSubmit = async () => { @@ -45,6 +54,16 @@ const onSubmit = async () => { }; const onRegister = async () => { + if (!registerPasswordValidation.value.valid) { + toast.add({ + severity: 'warn', + summary: t('web.common.warning'), + detail: t(registerPasswordValidation.value.reasonKey!), + life: 3000, + }); + return; + } + saveApiHost(apiHost.value); const credential: Credential = { username: registerUsername.value, password: registerPassword.value }; const registerReq: RegisterData = { credentials: credential, captcha: captcha.value }; @@ -158,6 +177,12 @@ onBeforeUnmount(() => { }} + + {{ t('web.common.password_strength_hint') }} + + + {{ registerPasswordErrorMessage }} +
@@ -165,7 +190,8 @@ onBeforeUnmount(() => { Captcha
-