feat(admin-web/login): replace default test credentials with remembered login
Why: showing the seed admin account on the login screen is unsafe for production deployments. How to apply: store identifier/password in localStorage when "记住密码" is checked, restore them on mount, and drop the demo credentials panel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,8 +95,8 @@ const enUS = {
|
||||
email: "Email",
|
||||
loginIdentifier: "Phone / Email",
|
||||
password: "Password",
|
||||
defaultTestAccount: "Default account",
|
||||
defaultTestPassword: "Default password",
|
||||
rememberPassword: "Remember password",
|
||||
forgotPassword: "Forgot password",
|
||||
},
|
||||
membershipBlocked: {
|
||||
eyebrow: "Access Paused",
|
||||
|
||||
@@ -96,8 +96,8 @@ const zhCN = {
|
||||
email: "邮箱",
|
||||
loginIdentifier: "手机号 / 邮箱",
|
||||
password: "密码",
|
||||
defaultTestAccount: "默认测试账号",
|
||||
defaultTestPassword: "默认测试密码",
|
||||
rememberPassword: "记住密码",
|
||||
forgotPassword: "忘记密码",
|
||||
},
|
||||
membershipBlocked: {
|
||||
eyebrow: "访问已暂停",
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<label for="login-password">{{ t('auth.password') }}</label>
|
||||
<div class="password-wrap">
|
||||
<input id="login-password" :type="showPassword ? 'text' : 'password'"
|
||||
placeholder="Admin@123" v-model="formState.password" required />
|
||||
:placeholder="t('auth.password')" v-model="formState.password" required />
|
||||
<button type="button" class="eye-btn" @click="showPassword = !showPassword">
|
||||
<EyeInvisibleOutlined v-if="showPassword" />
|
||||
<EyeOutlined v-else />
|
||||
@@ -48,33 +48,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="options">
|
||||
<label class="remember"><input type="checkbox" v-model="remember" /> Remember for 30 days</label>
|
||||
<a href="#" class="forgot">Forgot password?</a>
|
||||
<label class="remember"><input type="checkbox" v-model="remember" /> {{ t('auth.rememberPassword') }}</label>
|
||||
<a href="#" class="forgot">{{ t('auth.forgotPassword') }}</a>
|
||||
</div>
|
||||
|
||||
|
||||
<button type="submit" class="btn-primary" :disabled="submitting"
|
||||
:style="{ background: primaryColor }">
|
||||
{{ submitting ? t('auth.loginAndEnter') + '...' : t('auth.loginAndEnter') }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="login-credentials">
|
||||
<div class="login-credentials__row">
|
||||
<span>{{ t("auth.defaultTestAccount") }}</span>
|
||||
<strong>admin@geo.local</strong>
|
||||
</div>
|
||||
<div class="login-credentials__row">
|
||||
<span>{{ t("auth.defaultTestPassword") }}</span>
|
||||
<strong>Admin@123</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { message } from "ant-design-vue";
|
||||
@@ -84,6 +73,8 @@ import { formatError } from "@/lib/errors";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import AnimatedCharacters from "@/components/login-animation/AnimatedCharacters.vue";
|
||||
|
||||
const REMEMBER_STORAGE_KEY = "geo:admin:login:remember";
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -97,10 +88,44 @@ const isTyping = ref(false);
|
||||
const remember = ref(false);
|
||||
|
||||
const formState = reactive({
|
||||
identifier: "admin@geo.local",
|
||||
password: "Admin@123",
|
||||
identifier: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const raw = window.localStorage.getItem(REMEMBER_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
const parsed = JSON.parse(raw) as { identifier?: string; password?: string };
|
||||
if (parsed && typeof parsed.identifier === "string" && typeof parsed.password === "string") {
|
||||
formState.identifier = parsed.identifier;
|
||||
formState.password = parsed.password;
|
||||
remember.value = true;
|
||||
}
|
||||
} catch {
|
||||
window.localStorage.removeItem(REMEMBER_STORAGE_KEY);
|
||||
}
|
||||
});
|
||||
|
||||
function persistRememberedCredentials(): void {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
if (remember.value) {
|
||||
window.localStorage.setItem(
|
||||
REMEMBER_STORAGE_KEY,
|
||||
JSON.stringify({ identifier: formState.identifier, password: formState.password }),
|
||||
);
|
||||
} else {
|
||||
window.localStorage.removeItem(REMEMBER_STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(): Promise<void> {
|
||||
if (submitting.value) {
|
||||
return;
|
||||
@@ -110,6 +135,7 @@ async function handleSubmit(): Promise<void> {
|
||||
|
||||
try {
|
||||
await authStore.login(formState);
|
||||
persistRememberedCredentials();
|
||||
const redirect = typeof route.query.redirect === "string" ? route.query.redirect : "/workspace";
|
||||
await router.replace(redirect);
|
||||
} catch (error) {
|
||||
@@ -186,30 +212,6 @@ async function handleSubmit(): Promise<void> {
|
||||
.btn-primary:hover { opacity: 0.9; }
|
||||
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.login-credentials {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e6edf5;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.login-credentials__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.login-credentials__row + .login-credentials__row {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.login-credentials__row span {
|
||||
color: var(--muted, #71717a);
|
||||
font-size: 13px;
|
||||
}
|
||||
.login-credentials__row strong {
|
||||
color: #18181b;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.page { grid-template-columns: 1fr; }
|
||||
.left { display: none; }
|
||||
|
||||
Reference in New Issue
Block a user