perf: 强化强制修改默认密码逻辑

This commit is contained in:
Soulter
2025-06-09 11:47:23 +08:00
parent f83fe43bbb
commit ccbfc3d274
6 changed files with 205 additions and 59 deletions
+1
View File
@@ -43,6 +43,7 @@ class AstrBotConfig(dict):
"""不存在时载入默认配置"""
with open(config_path, "w", encoding="utf-8-sig") as f:
json.dump(default_config, f, indent=4, ensure_ascii=False)
object.__setattr__(self, "first_deploy", True) # 标记第一次部署
with open(config_path, "r", encoding="utf-8-sig") as f:
conf_str = f.read()
+2
View File
@@ -1,5 +1,6 @@
import jwt
import datetime
import asyncio
from .route import Route, Response, RouteContext
from quart import request
from astrbot.core import WEBUI_SK, DEMO_MODE
@@ -41,6 +42,7 @@ class AuthRoute(Route):
.__dict__
)
else:
await asyncio.sleep(3)
return Response().error("用户名或密码错误").__dict__
async def edit_account(self):
+20 -4
View File
@@ -46,11 +46,27 @@ class StatRoute(Route):
h, m = divmod(m, 60)
return f"{h}小时{m}{s}"
def is_default_cred(self):
username = self.config["dashboard"]["username"]
password = self.config["dashboard"]["password"]
return (
username == "astrbot"
and password == "77b90590a8945a7d36c963981a307dc9"
and not DEMO_MODE
)
async def get_version(self):
return Response().ok({
"version": VERSION,
"dashboard_version": await get_dashboard_version(),
}).__dict__
return (
Response()
.ok(
{
"version": VERSION,
"dashboard_version": await get_dashboard_version(),
"change_pwd_hint": self.is_default_cred(),
}
)
.__dict__
)
async def get_start_time(self):
return Response().ok({"start_time": self.core_lifecycle.start_time}).__dict__
+11 -5
View File
@@ -5,18 +5,25 @@
<img width="110" src="@/assets/images/astrbot_logo_mini.webp" alt="AstrBot Logo">
</div>
<div class="logo-text">
<h2 class="text-secondary">AstrBot 仪表盘</h2>
<h2 class="text-secondary">{{ title }}</h2>
<!-- 父子组件传递css变量可能会出错暂时使用十六进制颜色值 -->
<h4 :style="{color: useCustomizerStore().uiTheme === 'PurpleTheme' ? '#000000aa' : '#ffffffcc'}"
class="hint-text">登录以继续</h4>
class="hint-text">{{ subtitle }}</h4>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// No props or other logic needed for this simple component
import {useCustomizerStore} from "@/stores/customizer";
import { useCustomizerStore } from "@/stores/customizer";
const props = withDefaults(defineProps<{
title?: string;
subtitle?: string;
}>(), {
title: 'AstrBot 仪表盘',
subtitle: '欢迎使用'
})
</script>
<style scoped>
@@ -68,5 +75,4 @@ import {useCustomizerStore} from "@/stores/customizer";
font-weight: 400;
letter-spacing: 0.3px;
}
</style>
@@ -1,7 +1,8 @@
<script setup lang="ts">
import {ref} from 'vue';
import {ref, computed} 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';
@@ -24,7 +25,7 @@ let dashboardHasNewVersion = ref(false);
let dashboardCurrentVersion = ref('');
let version = ref('');
let releases = ref([]);
let devCommits = ref([]); // 新增的 ref
let devCommits = ref([]);
let installLoading = ref(false);
@@ -38,12 +39,38 @@ let releasesHeader = [
{title: '操作', key: 'switch'}
];
// Form validation
const formValid = ref(true);
const passwordRules = [
(v: string) => !!v || '请输入密码',
(v: string) => v.length >= 8 || '密码长度至少 8 位'
];
const usernameRules = [
(v: string) => !v || v.length >= 3 || '用户名长度至少3位'
];
// 显示密码相关
const showPassword = ref(false);
const showNewPassword = ref(false);
// 账户修改状态
const accountEditStatus = ref({
loading: false,
success: false,
error: false,
message: ''
});
const open = (link: string) => {
window.open(link, '_blank');
};
// 账户修改
function accountEdit() {
accountEditStatus.value.loading = true;
accountEditStatus.value.error = false;
accountEditStatus.value.success = false;
// md5加密
// @ts-ignore
if (password.value != '') {
@@ -59,23 +86,29 @@ function accountEdit() {
})
.then((res) => {
if (res.data.status == 'error') {
status.value = res.data.message;
accountEditStatus.value.error = true;
accountEditStatus.value.message = res.data.message;
password.value = '';
newPassword.value = '';
return;
}
dialog.value = !dialog.value;
status.value = res.data.message;
accountEditStatus.value.success = true;
accountEditStatus.value.message = res.data.message;
setTimeout(() => {
dialog.value = !dialog.value;
const authStore = useAuthStore();
authStore.logout();
}, 1000);
}, 2000);
})
.catch((err) => {
console.log(err);
status.value = err
accountEditStatus.value.error = true;
accountEditStatus.value.message = typeof err === 'string' ? err : '修改失败,请重试';
password.value = '';
newPassword.value = '';
})
.finally(() => {
accountEditStatus.value.loading = false;
});
}
@@ -84,6 +117,14 @@ function getVersion() {
.then((res) => {
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) {
dialog.value = true;
accountWarning.value = true;
localStorage.setItem('change_pwd_hint', 'true');
} else {
localStorage.removeItem('change_pwd_hint');
}
})
.catch((err) => {
console.log(err);
@@ -119,8 +160,6 @@ function checkUpdate() {
function getReleases() {
axios.get('/api/update/releases')
.then((res) => {
// releases.value = res.data.data;
// 更新 published_at 的时间为本地时间
releases.value = res.data.data.map((item: any) => {
item.published_at = new Date(item.published_at).toLocaleString();
return item;
@@ -202,13 +241,6 @@ const commonStore = useCommonStore();
commonStore.createEventSource(); // log
commonStore.getStartTime();
if (localStorage.getItem('change_pwd_hint') != null && localStorage.getItem('change_pwd_hint') == 'true') {
dialog.value = true;
accountWarning.value = true;
localStorage.removeItem('change_pwd_hint');
}
</script>
<template>
@@ -390,48 +422,118 @@ if (localStorage.getItem('change_pwd_hint') != null && localStorage.getItem('cha
</v-card>
</v-dialog>
<v-dialog v-model="dialog" persistent width="700">
<v-dialog v-model="dialog" persistent max-width="500">
<template v-slot:activator="{ props }">
<v-btn size="small" class="text-primary mr-4" color="var(--v-theme-surface)" variant="flat" rounded="sm" v-bind="props">
<v-icon class="mr-1">mdi-account</v-icon>
账户
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="text-h5">账户</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-card class="account-dialog">
<v-card-text class="py-6">
<div class="d-flex flex-column align-center mb-6">
<logo title="AstrBot 仪表盘" subtitle="修改账户"></logo>
</div>
<v-alert
v-if="accountWarning"
type="warning"
variant="tonal"
border="start"
class="mb-4"
>
<strong>安全提醒:</strong> 请修改默认密码以确保账户安全
</v-alert>
<v-alert v-if="accountWarning" color="warning" style="margin-bottom: 16px;">
<div>为了安全请务必修改默认密码</div>
</v-alert>
<v-alert
v-if="accountEditStatus.success"
type="success"
variant="tonal"
border="start"
class="mb-4"
>
{{ accountEditStatus.message }}
</v-alert>
<v-text-field label="原密码(*)" type="password" v-model="password" required
variant="outlined"/>
<v-alert
v-if="accountEditStatus.error"
type="error"
variant="tonal"
border="start"
class="mb-4"
>
{{ accountEditStatus.message }}
</v-alert>
<v-text-field label="新密码(*)" type="password" v-model="newPassword" required
variant="outlined"/>
<v-form v-model="formValid" @submit.prevent="accountEdit">
<v-text-field
v-model="password"
:append-inner-icon="showPassword ? 'mdi-eye-off' : 'mdi-eye'"
:type="showPassword ? 'text' : 'password'"
label="当前密码"
variant="outlined"
required
clearable
@click:append-inner="showPassword = !showPassword"
prepend-inner-icon="mdi-lock-outline"
hide-details="auto"
class="mb-4"
></v-text-field>
<v-text-field label="新用户名(可选)" v-model="newUsername" variant="outlined"/>
<v-text-field
v-model="newPassword"
:append-inner-icon="showNewPassword ? 'mdi-eye-off' : 'mdi-eye'"
:type="showNewPassword ? 'text' : 'password'"
:rules="passwordRules"
label="新密码"
variant="outlined"
required
clearable
@click:append-inner="showNewPassword = !showNewPassword"
prepend-inner-icon="mdi-lock-plus-outline"
hint="密码长度至少 8 位"
persistent-hint
class="mb-4"
></v-text-field>
</v-col>
</v-row>
</v-container>
<small>默认用户名和密码是 astrbot</small>
<br>
<small>{{ status }}</small>
<v-text-field
v-model="newUsername"
:rules="usernameRules"
label="新用户名 (可选)"
variant="outlined"
clearable
prepend-inner-icon="mdi-account-edit-outline"
hint="留空表示不修改用户名"
persistent-hint
class="mb-3"
></v-text-field>
</v-form>
<div class="text-caption text-medium-emphasis mt-2">
默认用户名和密码均为 astrbot
</div>
</v-card-text>
<v-card-actions>
<v-divider></v-divider>
<v-card-actions class="pa-4">
<v-spacer></v-spacer>
<v-btn v-if="!accountWarning" color="blue-darken-1" variant="text" @click="dialog = false">
关闭
<v-btn
v-if="!accountWarning"
variant="tonal"
color="secondary"
@click="dialog = false"
:disabled="accountEditStatus.loading"
>
取消
</v-btn>
<v-btn color="blue-darken-1" variant="text" @click="accountEdit">
提交
<v-btn
color="primary"
@click="accountEdit"
:loading="accountEditStatus.loading"
:disabled="!formValid"
prepend-icon="mdi-content-save"
>
保存修改
</v-btn>
</v-card-actions>
</v-card>
@@ -457,4 +559,27 @@ if (localStorage.getItem('change_pwd_hint') != null && localStorage.getItem('cha
margin-top: 8px;
margin-bottom: 8px;
}
.account-dialog .v-card-text {
padding-top: 24px;
padding-bottom: 24px;
}
.account-dialog .v-alert {
margin-bottom: 20px;
}
.account-dialog .v-btn {
text-transform: none;
font-weight: 500;
border-radius: 8px;
}
.account-dialog .v-avatar {
transition: transform 0.3s ease;
}
.account-dialog .v-avatar:hover {
transform: scale(1.05);
}
</style>
@@ -64,16 +64,12 @@ async function validate(values: any, { setErrors }: any) {
prepend-inner-icon="mdi-lock"
:disabled="loading"
></v-text-field>
<v-label :style="{color: useCustomizerStore().uiTheme === 'PurpleTheme' ? '#000000aa' : '#ffffffcc'}" class="mt-1 mb-5">
<small>默认用户名和密码为 astrbot</small>
</v-label>
<v-btn
color="secondary"
:loading="isSubmitting || loading"
block
class="login-btn"
class="login-btn mt-8"
variant="flat"
size="large"
:disabled="valid"