fix(webui): enforce 12-char dashboard password policy with backend+frontend validation

This commit is contained in:
Soulter
2026-04-03 17:37:03 +08:00
parent 5886c43752
commit 90b79d53fa
18 changed files with 276 additions and 62 deletions

View File

@@ -1,4 +1,3 @@
import hashlib
import json
import zoneinfo
from collections.abc import Callable
@@ -6,6 +5,11 @@ from typing import Any
import click
from astrbot.core.utils.auth_password import (
hash_dashboard_password,
validate_dashboard_password,
)
from ..utils import check_astrbot_root, get_astrbot_root
@@ -39,9 +43,11 @@ def _validate_dashboard_username(value: str) -> str:
def _validate_dashboard_password(value: str) -> str:
"""Validate Dashboard password"""
if not value:
raise click.ClickException("Password cannot be empty")
return hashlib.md5(value.encode()).hexdigest()
try:
validate_dashboard_password(value)
except ValueError as e:
raise click.ClickException(str(e))
return hash_dashboard_password(value)
def _validate_timezone(value: str) -> str:

View File

@@ -4,6 +4,9 @@ import logging
import os
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
from astrbot.core.utils.auth_password import (
normalize_dashboard_password_hash,
)
from .default import DEFAULT_CONFIG, DEFAULT_VALUE_MAP
@@ -59,6 +62,13 @@ class AstrBotConfig(dict):
# 检查配置完整性,并插入
has_new = self.check_config_integrity(default_config, conf)
if (
"dashboard" in conf
and isinstance(conf["dashboard"], dict)
and not conf["dashboard"].get("password")
):
conf["dashboard"]["password"] = normalize_dashboard_password_hash("")
has_new = True
self.update(conf)
if has_new:
self.save_config()

View File

@@ -235,7 +235,7 @@ DEFAULT_CONFIG = {
"dashboard": {
"enable": True,
"username": "astrbot",
"password": "77b90590a8945a7d36c963981a307dc9",
"password": "",
"jwt_secret": "",
"host": "0.0.0.0",
"port": 6185,

View File

@@ -0,0 +1,113 @@
"""Utilities for dashboard password hashing and verification."""
import hashlib
import hmac
import re
import secrets
_PBKDF2_ITERATIONS = 200_000
_PBKDF2_SALT_BYTES = 16
_PBKDF2_ALGORITHM = "pbkdf2_sha256"
_PBKDF2_FORMAT = f"{_PBKDF2_ALGORITHM}$"
_LEGACY_MD5_LENGTH = 32
_DASHBOARD_PASSWORD_MIN_LENGTH = 12
DEFAULT_DASHBOARD_PASSWORD = "astrbot"
def hash_dashboard_password(raw_password: str) -> str:
"""Return a salted hash for dashboard password using PBKDF2-HMAC-SHA256."""
if not isinstance(raw_password, str) or raw_password == "":
raise ValueError("Password cannot be empty")
salt = secrets.token_hex(_PBKDF2_SALT_BYTES)
digest = hashlib.pbkdf2_hmac(
"sha256",
raw_password.encode("utf-8"),
bytes.fromhex(salt),
_PBKDF2_ITERATIONS,
).hex()
return f"{_PBKDF2_FORMAT}{_PBKDF2_ITERATIONS}${salt}${digest}"
def validate_dashboard_password(raw_password: str) -> None:
"""Validate whether dashboard password meets the minimal complexity policy."""
if not isinstance(raw_password, str) or raw_password == "":
raise ValueError("Password cannot be empty")
if len(raw_password) < _DASHBOARD_PASSWORD_MIN_LENGTH:
raise ValueError(
f"Password must be at least {_DASHBOARD_PASSWORD_MIN_LENGTH} characters long"
)
if not re.search(r"[A-Z]", raw_password):
raise ValueError("Password must include at least one uppercase letter")
if not re.search(r"[a-z]", raw_password):
raise ValueError("Password must include at least one lowercase letter")
if not re.search(r"\d", raw_password):
raise ValueError("Password must include at least one digit")
def normalize_dashboard_password_hash(stored_password: str) -> str:
"""Ensure dashboard password has a value, fallback to default dashboard password hash."""
if not stored_password:
return hash_dashboard_password(DEFAULT_DASHBOARD_PASSWORD)
return stored_password
def _is_legacy_md5_hash(stored: str) -> bool:
return (
isinstance(stored, str)
and len(stored) == _LEGACY_MD5_LENGTH
and all(c in "0123456789abcdefABCDEF" for c in stored)
)
def _is_pbkdf2_hash(stored: str) -> bool:
return isinstance(stored, str) and stored.startswith(_PBKDF2_FORMAT)
def verify_dashboard_password(stored_hash: str, candidate_password: str) -> bool:
"""Verify password against legacy md5 or new PBKDF2-SHA256 format."""
if not isinstance(stored_hash, str) or not isinstance(candidate_password, str):
return False
if _is_legacy_md5_hash(stored_hash):
# Keep compatibility with existing md5-based deployments:
# new clients send plain password, old clients may send md5 of it.
candidate_md5 = hashlib.md5(candidate_password.encode("utf-8")).hexdigest()
return hmac.compare_digest(
stored_hash.lower(), candidate_md5.lower()
) or hmac.compare_digest(
stored_hash.lower(),
candidate_password.lower(),
)
if _is_pbkdf2_hash(stored_hash):
parts: list[str] = stored_hash.split("$")
if len(parts) != 4:
return False
_, iterations_s, salt, digest = parts
try:
iterations = int(iterations_s)
stored_key = bytes.fromhex(digest)
salt_bytes = bytes.fromhex(salt)
except (TypeError, ValueError):
return False
candidate_key = hashlib.pbkdf2_hmac(
"sha256",
candidate_password.encode("utf-8"),
salt_bytes,
iterations,
)
return hmac.compare_digest(stored_key, candidate_key)
return False
def is_default_dashboard_password(stored_hash: str) -> bool:
"""Check whether the password still equals the built-in default value."""
return verify_dashboard_password(stored_hash, DEFAULT_DASHBOARD_PASSWORD)
def is_legacy_dashboard_password(stored_hash: str) -> bool:
"""Check whether the password is still stored with legacy MD5."""
return _is_legacy_md5_hash(stored_hash)

View File

@@ -6,6 +6,13 @@ from quart import request
from astrbot import logger
from astrbot.core import DEMO_MODE
from astrbot.core.utils.auth_password import (
hash_dashboard_password,
is_default_dashboard_password,
is_legacy_dashboard_password,
validate_dashboard_password,
verify_dashboard_password,
)
from .route import Response, Route, RouteContext
@@ -23,15 +30,29 @@ class AuthRoute(Route):
username = self.config["dashboard"]["username"]
password = self.config["dashboard"]["password"]
post_data = await request.json
if post_data["username"] == username and post_data["password"] == password:
req_username = (
post_data.get("username") if isinstance(post_data, dict) else None
)
req_password = (
post_data.get("password") if isinstance(post_data, dict) else None
)
if not isinstance(req_username, str) or not isinstance(req_password, str):
return Response().error("Invalid request payload").__dict__
if req_username == username and verify_dashboard_password(
password, req_password
):
change_pwd_hint = False
legacy_pwd_hint = is_legacy_dashboard_password(password)
if (
username == "astrbot"
and password == "77b90590a8945a7d36c963981a307dc9"
and is_default_dashboard_password(password)
and not DEMO_MODE
):
change_pwd_hint = True
logger.warning("为了保证安全,请尽快修改默认密码。")
logger.warning("检测到默认管理员凭据,请尽快修改密码。")
legacy_pwd_hint = True
return (
Response()
@@ -40,6 +61,7 @@ class AuthRoute(Route):
"token": self.generate_jwt(username),
"username": username,
"change_pwd_hint": change_pwd_hint,
"legacy_pwd_hint": legacy_pwd_hint,
},
)
.__dict__
@@ -57,8 +79,14 @@ class AuthRoute(Route):
password = self.config["dashboard"]["password"]
post_data = await request.json
if not isinstance(post_data, dict):
return Response().error("Invalid request payload").__dict__
if post_data["password"] != password:
req_password = post_data.get("password")
if not isinstance(req_password, str):
return Response().error("Invalid request payload").__dict__
if not verify_dashboard_password(password, req_password):
return Response().error("原密码错误").__dict__
new_pwd = post_data.get("new_password", None)
@@ -68,10 +96,16 @@ class AuthRoute(Route):
# Verify password confirmation
if new_pwd:
if not isinstance(new_pwd, str):
return Response().error("新密码无效").__dict__
confirm_pwd = post_data.get("confirm_password", None)
if confirm_pwd != new_pwd:
if not isinstance(confirm_pwd, str) or confirm_pwd != new_pwd:
return Response().error("两次输入的新密码不一致").__dict__
self.config["dashboard"]["password"] = new_pwd
try:
validate_dashboard_password(new_pwd)
except ValueError as e:
return Response().error(str(e)).__dict__
self.config["dashboard"]["password"] = hash_dashboard_password(new_pwd)
if new_username:
self.config["dashboard"]["username"] = new_username

View File

@@ -12,7 +12,7 @@ from pathlib import Path
import aiohttp
import psutil
from quart import request
from sqlmodel import select
from sqlmodel import col, select
from astrbot.core import DEMO_MODE, logger
from astrbot.core.config import VERSION
@@ -21,6 +21,10 @@ from astrbot.core.db import BaseDatabase
from astrbot.core.db.migration.helper import check_migration_needed_v4
from astrbot.core.db.po import ProviderStat
from astrbot.core.utils.astrbot_path import get_astrbot_path
from astrbot.core.utils.auth_password import (
is_default_dashboard_password,
is_legacy_dashboard_password,
)
from astrbot.core.utils.io import get_dashboard_version
from astrbot.core.utils.storage_cleaner import StorageCleaner
from astrbot.core.utils.version_comparator import VersionComparator
@@ -76,7 +80,7 @@ class StatRoute(Route):
password = self.config["dashboard"]["password"]
return (
username == "astrbot"
and password == "77b90590a8945a7d36c963981a307dc9"
and is_default_dashboard_password(password)
and not DEMO_MODE
)
@@ -90,6 +94,9 @@ class StatRoute(Route):
"version": VERSION,
"dashboard_version": await get_dashboard_version(),
"change_pwd_hint": self.is_default_cred(),
"legacy_pwd_hint": is_legacy_dashboard_password(
self.config["dashboard"]["password"]
),
"need_migration": need_migration,
},
)
@@ -226,7 +233,7 @@ class StatRoute(Route):
ProviderStat.agent_type == "internal",
ProviderStat.created_at >= query_start_utc,
)
.order_by(ProviderStat.created_at.asc())
.order_by(col(ProviderStat.created_at).asc())
)
records = result.scalars().all()

View File

@@ -80,20 +80,21 @@
},
"accountDialog": {
"title": "Modify Account",
"securityWarning": "Security Reminder: Please change the default password to ensure account security",
"securityWarning": "Security Reminder: Please change your password to secure your account",
"securityWarningLegacy": "The new AstrBot version has improved security. Please change your password.",
"form": {
"currentPassword": "Current Password",
"newPassword": "New Password",
"confirmPassword": "Confirm New Password",
"newUsername": "New Username (Optional)",
"passwordHint": "Password must be at least 8 characters",
"passwordHint": "Password must be at least 12 characters",
"confirmPasswordHint": "Please enter new password again to confirm",
"usernameHint": "Leave blank to keep current username",
"defaultCredentials": "Default username and password are both astrbot"
"defaultCredentials": "The new AstrBot version has improved security. Please change your password."
},
"validation": {
"passwordRequired": "Please enter password",
"passwordMinLength": "Password must be at least 8 characters",
"passwordMinLength": "Password must be at least 12 characters",
"passwordMatch": "Passwords do not match",
"usernameMinLength": "Username must be at least 3 characters"
},

View File

@@ -2,7 +2,7 @@
"login": "Login",
"username": "Username",
"password": "Password",
"defaultHint": "Default username and password: astrbot",
"defaultHint": "The new AstrBot version has improved security. Please change your password.",
"logo": {
"title": "AstrBot Dashboard",
"subtitle": "Welcome"
@@ -11,4 +11,4 @@
"switchToDark": "Switch to Dark Theme",
"switchToLight": "Switch to Light Theme"
}
}
}

View File

@@ -14,7 +14,7 @@
"fileType": "Unsupported file type",
"required_field": "Please fill in the required field",
"invalid_format": "Invalid format",
"password_too_short": "Password must be at least 8 characters",
"password_too_short": "Password must be at least 12 characters",
"password_too_weak": "Password is too weak",
"invalid_phone": "Please enter a valid phone number",
"invalid_date": "Please enter a valid date",
@@ -22,4 +22,4 @@
"upload_failed": "File upload failed",
"network_error": "Network connection error, please try again",
"operation_cannot_be_undone": "⚠️ This operation cannot be undone, please choose carefully!"
}
}

View File

@@ -1,4 +1,4 @@
{
{
"logoTitle": "Панель управления AstrBot",
"version": {
"hasNewVersion": "Доступна новая версия AstrBot!",
@@ -80,20 +80,21 @@
},
"accountDialog": {
"title": "Изменить аккаунт",
"securityWarning": "Безопасность: Пожалуйста, смените пароль по умолчанию для защиты аккаунта",
"securityWarning": "Безопасность: Пожалуйста, смените пароль для защиты аккаунта",
"securityWarningLegacy": "Новая версия AstrBot улучшила безопасность. Пожалуйста, измените пароль.",
"form": {
"currentPassword": "Текущий пароль",
"newPassword": "Новый пароль",
"confirmPassword": "Подтвердите новый пароль",
"newUsername": "Новое имя пользователя (опционально)",
"passwordHint": "Пароль должен быть не менее 8 символов",
"passwordHint": "Пароль должен быть не менее 12 символов",
"confirmPasswordHint": "Введите новый пароль еще раз",
"usernameHint": "Оставьте пустым, если не хотите менять имя пользователя",
"defaultCredentials": "Логин и пароль по умолчанию: astrbot"
"defaultCredentials": "Новая версия AstrBot улучшила безопасность. Пожалуйста, измените пароль."
},
"validation": {
"passwordRequired": "Введите пароль",
"passwordMinLength": "Пароль должен быть не менее 8 символов",
"passwordMinLength": "Пароль должен быть не менее 12 символов",
"passwordMatch": "Паролы не совпадают",
"usernameMinLength": "Имя пользователя должно быть не менее 3 символов"
},
@@ -105,4 +106,4 @@
"updateFailed": "Ошибка обновления, попробуйте еще раз"
}
}
}
}

View File

@@ -2,7 +2,7 @@
"login": "Вход",
"username": "Имя пользователя",
"password": "Пароль",
"defaultHint": "Логин и пароль по умолчанию: astrbot",
"defaultHint": "Новая версия AstrBot улучшила безопасность. Пожалуйста, измените пароль.",
"logo": {
"title": "Панель управления AstrBot",
"subtitle": "Добро пожаловать"
@@ -11,4 +11,4 @@
"switchToDark": "Перейти на темную тему",
"switchToLight": "Перейти на светлую тему"
}
}
}

View File

@@ -14,7 +14,7 @@
"fileType": "Неподдерживаемый тип файла",
"required_field": "Заполните обязательные поля",
"invalid_format": "Некорректный формат",
"password_too_short": "Пароль должен быть не менее 8 символов",
"password_too_short": "Пароль должен быть не менее 12 символов",
"password_too_weak": "Пароль слишком слабый",
"invalid_phone": "Некорректный номер телефона",
"invalid_date": "Некорректная дата",
@@ -22,4 +22,4 @@
"upload_failed": "Загрузка не удалась",
"network_error": "Ошибка сети, попробуйте снова",
"operation_cannot_be_undone": "⚠️ Это действие нельзя отменить, будьте осторожны!"
}
}

View File

@@ -80,20 +80,21 @@
},
"accountDialog": {
"title": "修改账户",
"securityWarning": "安全提醒: 请修改默认密码以确保账户安全",
"securityWarning": "安全提醒: 请修改密码以确保账户安全",
"securityWarningLegacy": "新的 AstrBot 版本强化了安全策略,请重新修改密码",
"form": {
"currentPassword": "当前密码",
"newPassword": "新密码",
"confirmPassword": "确认新密码",
"newUsername": "新用户名 (可选)",
"passwordHint": "密码长度至少 8 位",
"passwordHint": "密码长度至少 12 位",
"confirmPasswordHint": "请再次输入新密码以确认",
"usernameHint": "留空表示不修改用户名",
"defaultCredentials": "默认用户名和密码为 astrbot"
"defaultCredentials": "默认用户名和密码为 astrbot,请在登录后立即修改以确保安全。"
},
"validation": {
"passwordRequired": "请输入密码",
"passwordMinLength": "密码长度至少 8 位",
"passwordMinLength": "密码长度至少 12 位",
"passwordMatch": "两次输入的密码不一致",
"usernameMinLength": "用户名长度至少3位"
},

View File

@@ -2,7 +2,7 @@
"login": "登录",
"username": "用户名",
"password": "密码",
"defaultHint": "默认账户和密码均为astrbot",
"defaultHint": "默认用户名和密码为 astrbot请在登录后立即修改以确保安全。",
"logo": {
"title": "AstrBot WebUI",
"subtitle": "欢迎使用"
@@ -11,4 +11,4 @@
"switchToDark": "切换到深色主题",
"switchToLight": "切换到浅色主题"
}
}
}

View File

@@ -14,7 +14,7 @@
"fileType": "不支持的文件类型",
"required_field": "请填写必填字段",
"invalid_format": "格式无效",
"password_too_short": "密码至少需要8个字符",
"password_too_short": "密码至少需要12个字符",
"password_too_weak": "密码强度太弱",
"invalid_phone": "请输入有效的手机号码",
"invalid_date": "请输入有效的日期",
@@ -22,4 +22,4 @@
"upload_failed": "文件上传失败",
"network_error": "网络连接错误,请重试",
"operation_cannot_be_undone": "⚠️ 此操作无法撤销,请谨慎选择!"
}
}

View File

@@ -3,7 +3,6 @@ import { ref, computed, watch, onMounted } from 'vue';
import { useCustomizerStore } from '@/stores/customizer';
import axios from 'axios';
import Logo from '@/components/shared/Logo.vue';
import { md5 } from 'js-md5';
import { useAuthStore } from '@/stores/auth';
import { useCommonStore } from '@/stores/common';
import { MarkdownRender, enableKatex, enableMermaid } from 'markstream-vue';
@@ -19,6 +18,7 @@ import { useLanguageSwitcher } from '@/i18n/composables';
import type { Locale } from '@/i18n/types';
import AboutPage from '@/views/AboutPage.vue';
import { getDesktopRuntimeInfo } from '@/utils/desktopRuntime';
import LanguageSwitcher from '@/components/shared/LanguageSwitcher.vue';
enableKatex();
enableMermaid();
@@ -31,6 +31,7 @@ const LAST_BOT_ROUTE_KEY = 'astrbot:last_bot_route';
const LAST_CHAT_ROUTE_KEY = 'astrbot:last_chat_route';
let dialog = ref(false);
let accountWarning = ref(false)
let accountWarningLegacy = ref(false);
let updateStatusDialog = ref(false);
let aboutDialog = ref(false);
const username = localStorage.getItem('user');
@@ -100,7 +101,7 @@ const releasesHeader = computed(() => [
const formValid = ref(true);
const passwordRules = computed(() => [
(v: string) => !!v || t('core.header.accountDialog.validation.passwordRequired'),
(v: string) => v.length >= 8 || t('core.header.accountDialog.validation.passwordMinLength')
(v: string) => v.length >= 12 || t('core.header.accountDialog.validation.passwordMinLength')
]);
const confirmPasswordRules = computed(() => [
(v: string) => !newPassword.value || !!v || t('core.header.accountDialog.validation.passwordRequired'),
@@ -225,9 +226,9 @@ function accountEdit() {
accountEditStatus.value.error = false;
accountEditStatus.value.success = false;
const passwordHash = password.value ? md5(password.value) : '';
const newPasswordHash = newPassword.value ? md5(newPassword.value) : '';
const confirmPasswordHash = confirmPassword.value ? md5(confirmPassword.value) : '';
const passwordHash = password.value ? password.value : '';
const newPasswordHash = newPassword.value ? newPassword.value : '';
const confirmPasswordHash = confirmPassword.value ? confirmPassword.value : '';
axios.post('/api/auth/account/edit', {
password: passwordHash,
@@ -271,12 +272,21 @@ function getVersion() {
botCurrVersion.value = "v" + res.data.data.version;
dashboardCurrentVersion.value = res.data.data?.dashboard_version;
let change_pwd_hint = res.data.data?.change_pwd_hint;
if (change_pwd_hint) {
const legacy_pwd_hint = res.data.data?.legacy_pwd_hint;
if (change_pwd_hint || legacy_pwd_hint) {
dialog.value = true;
accountWarning.value = true;
accountWarningLegacy.value = !!legacy_pwd_hint;
localStorage.setItem('change_pwd_hint', 'true');
if (legacy_pwd_hint) {
localStorage.setItem('legacy_pwd_hint', 'true');
} else {
localStorage.removeItem('legacy_pwd_hint');
}
} else {
accountWarningLegacy.value = false;
localStorage.removeItem('change_pwd_hint');
localStorage.removeItem('legacy_pwd_hint');
}
})
.catch((err) => {
@@ -284,6 +294,16 @@ function getVersion() {
});
}
function initPasswordWarningFromStorage() {
const hasChangePwdHint = localStorage.getItem('change_pwd_hint') === 'true';
const hasLegacyPwdHint = localStorage.getItem('legacy_pwd_hint') === 'true';
if (hasChangePwdHint || hasLegacyPwdHint) {
dialog.value = true;
accountWarning.value = true;
accountWarningLegacy.value = hasLegacyPwdHint;
}
}
function checkUpdate() {
updateStatus.value = t('core.header.updateDialog.status.checking');
axios.get('/api/update/check')
@@ -392,6 +412,7 @@ function handleLogoClick() {
getVersion();
checkUpdate();
initPasswordWarningFromStorage();
const commonStore = useCommonStore();
commonStore.createEventSource(); // log
@@ -889,14 +910,18 @@ onMounted(async () => {
</v-dialog>
<!-- 账户对话框 -->
<v-dialog v-model="dialog" persistent :max-width="$vuetify.display.xs ? '90%' : '500'">
<v-dialog v-model="dialog" persistent :max-width="$vuetify.display.xs ? '90%' : '500'">
<v-card class="account-dialog">
<v-card-text class="py-6">
<div class="d-flex flex-column align-center mb-6">
<logo :title="t('core.header.logoTitle')" :subtitle="t('core.header.accountDialog.title')"></logo>
<div class="account-dialog-header mb-6">
<div class="d-flex justify-space-between align-center w-100">
<img width="80" src="@/assets/images/icon-no-shadow.svg" alt="AstrBot Logo">
</div>
<div class="ml-2" style="font-size: 26px;">{{ t('core.header.logoTitle') }}</div>
<div class="mt-2 ml-2" style="font-size: 14px; color: grey;">{{ t('core.header.accountDialog.title') }}</div>
</div>
<v-alert v-if="accountWarning" type="warning" variant="tonal" border="start" class="mb-4">
<strong>{{ t('core.header.accountDialog.securityWarning') }}</strong>
<strong>{{ t(accountWarningLegacy ? 'core.header.accountDialog.securityWarningLegacy' : 'core.header.accountDialog.securityWarning') }}</strong>
</v-alert>
<v-alert v-if="accountEditStatus.success" type="success" variant="tonal" border="start" class="mb-4">
@@ -936,8 +961,6 @@ onMounted(async () => {
</div>
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pa-4">
<v-spacer></v-spacer>
<v-btn v-if="!accountWarning" variant="tonal" color="secondary" @click="dialog = false"
@@ -1006,6 +1029,20 @@ onMounted(async () => {
transform: scale(1.05);
}
.account-dialog-header {
.theme-toggle-btn {
opacity: 0.85;
&:hover {
opacity: 1;
}
}
}
.theme-toggle-btn {
margin-left: 0;
}
/* 响应式布局样式 */
.logo-container {
margin-left: 10px;

View File

@@ -24,7 +24,17 @@ export const useAuthStore = defineStore({
this.username = res.data.data.username
localStorage.setItem('user', this.username);
localStorage.setItem('token', res.data.data.token);
localStorage.setItem('change_pwd_hint', res.data.data?.change_pwd_hint);
if (res.data.data?.change_pwd_hint || res.data.data?.legacy_pwd_hint) {
localStorage.setItem('change_pwd_hint', 'true');
if (res.data.data?.legacy_pwd_hint) {
localStorage.setItem('legacy_pwd_hint', 'true');
} else {
localStorage.removeItem('legacy_pwd_hint');
}
} else {
localStorage.removeItem('change_pwd_hint');
localStorage.removeItem('legacy_pwd_hint');
}
const onboardingCompleted = await this.checkOnboardingCompleted();
this.returnUrl = null;
@@ -70,6 +80,8 @@ export const useAuthStore = defineStore({
this.username = '';
localStorage.removeItem('user');
localStorage.removeItem('token');
localStorage.removeItem('change_pwd_hint');
localStorage.removeItem('legacy_pwd_hint');
router.push('/auth/login');
},
has_token(): boolean {

View File

@@ -2,7 +2,6 @@
import { ref, useCssModule } from 'vue';
import { useAuthStore } from '@/stores/auth';
import { Form } from 'vee-validate';
import md5 from 'js-md5';
import { useModuleI18n } from '@/i18n/composables';
const { tm: t } = useModuleI18n('features/auth');
@@ -17,17 +16,10 @@ const loading = ref(false);
async function validate(values: any, { setErrors }: any) {
loading.value = true;
// md5加密
let password_ = password.value;
if (password.value != '') {
// @ts-ignore
password_ = md5(password.value);
}
const authStore = useAuthStore();
// @ts-ignore
authStore.returnUrl = new URLSearchParams(window.location.search).get('redirect');
return authStore.login(username.value, password_).then((res) => {
return authStore.login(username.value, password.value).then((res) => {
console.log(res);
loading.value = false;
}).catch((err) => {