PersonalWebApplication/Client/src/App.tsx
cc b2a0b563c9 feat(client): 隐藏登录入口改为后端校验 URL hash
去掉起始页右下角设置齿轮(SettingsGear),登录页不再有任何 UI 入口;
改为监听地址栏 `#<hash>`,经 checkGate() 交后端 /api/web/gate 校验,
命中合法开发者才放行到登录页。合法 hash 只存在后端、前端不写死。

- 新增 api/gate.ts;App.tsx 用 hashchange 监听 + 防竞态校验
- 返回起始页时抹掉地址栏 hash,避免密钥留在历史里
- Server/.gitignore 忽略 dev_gate.txt(秘密,单独部署)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:45:30 +08:00

63 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useState } from 'react'
import { CursorFollower } from './components/CursorFollower'
import { LoginPage } from './components/LoginPage'
import { BlogPage } from './components/BlogPage'
import { StartPage } from './components/StartPage'
import { useAuth } from './hooks/useAuth'
import { checkGate } from './api/gate'
function App() {
// 登录态从 localStorage/sessionStorage 恢复:已登录直接进博客页。
const auth = useAuth()
// 是否显示登录页。入口 hash 是否合法由后端判定,故初值为 false等校验通过再开。
const [showLogin, setShowLogin] = useState(false)
const signOut = auth.signOut
// 监听地址栏 hash把 `#` 后那串发给后端校验,命中合法开发者才放行到登录页。
// 合法 hash 列表只存在后端,前端不写死,扒 JS 也看不到合法值。
useEffect(() => {
let alive = true
const check = async () => {
const hash = window.location.hash.replace(/^#/, '')
const ok = hash ? await checkGate(hash) : false
// 防竞态:异步返回时若组件已卸载或 hash 又变了,丢弃这次结果。
if (alive && hash === window.location.hash.replace(/^#/, '')) {
setShowLogin(ok)
}
}
check()
window.addEventListener('hashchange', check)
return () => {
alive = false
window.removeEventListener('hashchange', check)
}
}, [])
// 回到导航页:清掉登录态,并抹掉地址栏里的密钥(防止留在历史里被看到)。
const goStart = () => {
signOut()
setShowLogin(false)
if (window.location.hash) {
history.replaceState(null, '', window.location.pathname + window.location.search)
}
}
let view
if (auth.username) {
view = <BlogPage username={auth.username} onSignOut={goStart} />
} else if (showLogin) {
view = <LoginPage onSignIn={auth.signIn} onBack={goStart} />
} else {
view = <StartPage />
}
return (
<>
<CursorFollower />
{view}
</>
)
}
export default App