## 安全修复 - 修复地图标记与悬浮卡片中的存储型 XSS - 限制登录后的重定向目标为当前站点 - 提前清除邮箱确认和密码重置 URL 中的令牌 - 将 Vite 升级到 8.0.16,修复已公开的高危漏洞 - 收紧 Referrer Policy 并增加 HSTS ## 验证 - `npm audit --audit-level=low`:0 个漏洞 - `npm run build` - 6 组正常与恶意重定向用例 - 危险 DOM API 残留检查Reviewed-on: #5
91 lines
2.8 KiB
Vue
91 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { NButton, NCard, NResult, NSpin } from 'naive-ui'
|
|
import { apiRequest } from '@/lib/api'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const state = ref<'loading' | 'success' | 'failed'>('loading')
|
|
const countdown = ref(5)
|
|
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
|
const confirmationToken = typeof route.query.token === 'string' ? route.query.token : ''
|
|
|
|
if (route.query.token !== undefined) {
|
|
window.history.replaceState(window.history.state, '', window.location.pathname)
|
|
}
|
|
|
|
function startCountdown() {
|
|
countdownTimer = setInterval(() => {
|
|
countdown.value--
|
|
if (countdown.value <= 0) {
|
|
if (countdownTimer) clearInterval(countdownTimer)
|
|
router.push('/login')
|
|
}
|
|
}, 1000)
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
if (!confirmationToken) {
|
|
state.value = 'failed'
|
|
return
|
|
}
|
|
|
|
await apiRequest('/auth/confirm-email', {
|
|
method: 'POST',
|
|
auth: false,
|
|
body: { token: confirmationToken },
|
|
})
|
|
state.value = 'success'
|
|
startCountdown()
|
|
} catch {
|
|
state.value = 'failed'
|
|
}
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (countdownTimer) clearInterval(countdownTimer)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="min-h-[calc(100vh-4rem)] px-4 py-10">
|
|
<div class="mx-auto max-w-3xl">
|
|
<NCard class="shadow-[12px_12px_0_0_rgba(15,23,42,0.08)]">
|
|
<template v-if="state === 'success'">
|
|
<NResult status="success" title="邮箱认证成功" description="你的邮箱已确认,现在可以登录了。">
|
|
<template #footer>
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-slate-500">{{ countdown }} 秒后自动跳转登录页面...</p>
|
|
<RouterLink to="/login" class="no-underline">
|
|
<NButton type="primary" class="oc-primary-button oc-primary-button--teal">立即登录</NButton>
|
|
</RouterLink>
|
|
</div>
|
|
</template>
|
|
</NResult>
|
|
</template>
|
|
|
|
<template v-else-if="state === 'failed'">
|
|
<NResult status="error" title="认证失败" description="邮箱确认链接无效或已过期,请重新注册。">
|
|
<template #footer>
|
|
<RouterLink to="/register" class="no-underline">
|
|
<NButton type="primary" class="oc-primary-button oc-primary-button--sky">重新注册</NButton>
|
|
</RouterLink>
|
|
</template>
|
|
</NResult>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<div class="flex flex-col items-center justify-center py-12 text-center">
|
|
<NSpin size="large" />
|
|
<h1 class="mt-6 text-2xl font-bold text-slate-900">正在验证...</h1>
|
|
<p class="mt-2 text-slate-500">请稍候</p>
|
|
</div>
|
|
</template>
|
|
</NCard>
|
|
</div>
|
|
</div>
|
|
</template>
|