feat(token_lab): 新增令牌工坊工具及配套后端逻辑

新增完整的令牌工坊工具链:
- 添加前端静态资源:登录界面、导航与Tool令牌管理页面,配套样式与脚本文件
- 添加后端接口与中间件:认证相关接口、路由权限控制,以及导航令牌的生成验证API
- 添加默认配置文件,配置令牌工坊默认走导航登录,不对外公网开放
This commit is contained in:
yangxiangyuan
2026-08-02 10:18:44 +08:00
parent 47da1967d3
commit 18e8e787d2
5 changed files with 1499 additions and 0 deletions
+147
View File
@@ -1573,6 +1573,24 @@ const signJwtRS256 = (payload, jwk) => {
const sig = crypto.sign('RSA-SHA256', Buffer.from(data), priv).toString('base64').replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'')
return data + '.' + sig
}
const extractJwtFromInput = (raw) => {
const text = String(raw || '').trim()
if (!text) return ''
if (/^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+$/.test(text)) return text
try {
const u = new URL(text)
const fromQuery = String(u.searchParams.get('token') || '').trim()
if (fromQuery) return fromQuery
} catch {}
const match = text.match(/(?:^|[?&])token=([^&#\s]+)/)
if (match && match[1]) return decodeURIComponent(match[1])
return ''
}
const appendTokenToUrl = (baseUrl, token) => {
const base = String(baseUrl || '').trim()
if (!base) return `/?token=${token}`
return base.includes('?') ? `${base}&token=${token}` : `${base}?token=${token}`
}
const readNavTargets = () => {
try {
const s = String(process.env.NAV_TARGETS_JSON || '')
@@ -4145,6 +4163,98 @@ app.get('/api/debug/nav/private', (req, res) => {
}
})
app.get('/api/token_lab/auth/status', (req, res) => {
try {
if (!hasNavAuthRoot(req)) return res.status(401).json({ ok: false, error: 'unauthorized' })
return res.json({
ok: true,
authenticated: hasTokenLabAuth(req)
})
} catch (e) {
return res.status(500).json({ ok: false, error: String(e.message || e) })
}
})
app.post('/api/token_lab/auth/login', (req, res) => {
try {
if (!hasNavAuthRoot(req)) return res.status(401).json({ ok: false, error: 'unauthorized' })
const username = String(req.body?.username || '').trim()
const password = String(req.body?.password || '')
const expectedUser = String(process.env.TOKEN_LAB_USERNAME || '').trim()
const expectedPass = String(process.env.TOKEN_LAB_PASSWORD || '')
if (!expectedUser || !expectedPass) return res.status(500).json({ ok: false, error: 'token_lab_creds_not_configured' })
if (username !== expectedUser || password !== expectedPass) {
return res.status(401).json({ ok: false, error: '账号或密码错误' })
}
res.cookie(TOKEN_LAB_AUTH_COOKIE, '1', { httpOnly: true, sameSite: 'lax', maxAge: getTokenLabAuthMaxAge(), path: '/' })
return res.json({ ok: true })
} catch (e) {
return res.status(500).json({ ok: false, error: String(e.message || e) })
}
})
app.post('/api/token_lab/auth/logout', (req, res) => {
try {
if (!hasNavAuthRoot(req)) return res.status(401).json({ ok: false, error: 'unauthorized' })
res.clearCookie(TOKEN_LAB_AUTH_COOKIE, { httpOnly: true, sameSite: 'lax', path: '/' })
return res.json({ ok: true })
} catch (e) {
return res.status(500).json({ ok: false, error: String(e.message || e) })
}
})
app.use('/api/token_lab', (req, res, next) => {
try {
if (!hasNavAuthRoot(req)) return res.status(401).json({ ok: false, error: 'unauthorized' })
if (req.path === '/auth/status' || req.path === '/auth/login' || req.path === '/auth/logout') return next()
if (hasTokenLabAuth(req)) return next()
return res.status(401).json({ ok: false, error: 'token_lab_login_required' })
} catch {
return res.status(401).json({ ok: false, error: 'unauthorized' })
}
})
app.post('/api/token_lab/nav/generate', (req, res) => {
try {
const baseUrl = String(req.body?.baseUrl || '').trim()
const exp = parseInt(String(req.body?.exp || ''), 10)
const now = Math.floor(Date.now() / 1000)
if (!Number.isFinite(exp) || exp <= now) return res.status(400).json({ ok: false, error: 'bad_exp' })
if (exp > now + 3600 * 24 * 365 * 2) return res.status(400).json({ ok: false, error: 'exp_too_far' })
const priv = readNavPrivateJwk()
if (!priv) return res.status(500).json({ ok: false, error: 'nav_private_key_missing' })
const payload = {
iss: getNavIssFromFlags(),
iat: now,
exp,
jti: crypto.randomUUID()
}
const token = signJwtRS256(payload, priv)
const link = appendTokenToUrl(baseUrl || '/', token)
return res.json({ ok: true, token, link, payload })
} catch (e) {
return res.status(500).json({ ok: false, error: String(e.message || e) })
}
})
app.post('/api/token_lab/nav/verify', (req, res) => {
try {
const raw = String(req.body?.value || '').trim()
const token = extractJwtFromInput(raw)
if (!token) return res.status(400).json({ ok: false, error: 'missing_token' })
const result = debugVerifyJwt(token, [], { issStrict: computeNavIssStrict() })
return res.json({
ok: true,
token,
result,
payload: result.payload || null,
header: result.header || null
})
} catch (e) {
return res.status(500).json({ ok: false, error: String(e.message || e) })
}
})
app.get('/api/flags', (req, res) => {
const enabled = computeDebugEnabled()
res.json({ ok: true, debugToolEnabled: enabled, debug: { weibo: computeToolDebug('weibo'), markets: computeToolDebug('markets'), wall: computeToolDebug('wall'), funds_guoxin: computeToolDebug('funds_guoxin'), funds_huatai: computeToolDebug('funds_huatai'), weekly: computeToolDebug('weekly') } })
@@ -5545,12 +5655,20 @@ app.get('/api/archives/get', (req, res) => {
})
const NAV_AUTH_COOKIE = 'nav_gate'
const TOKEN_LAB_AUTH_COOKIE = 'token_lab_gate'
const hasNavAuthRoot = (req) => {
try {
const cookies = parseCookie(req.headers.cookie || '')
return cookies[NAV_AUTH_COOKIE] === '1'
} catch { return false }
}
const hasTokenLabAuth = (req) => {
try {
const cookies = parseCookie(req.headers.cookie || '')
return cookies[TOKEN_LAB_AUTH_COOKIE] === '1'
} catch { return false }
}
const getTokenLabAuthMaxAge = () => 1000 * 60 * 60 * 24
app.get('/index.html', (req, res) => {
try {
@@ -6195,6 +6313,35 @@ app.use('/tools/web_order_box', (req, res, next) => {
next()
})
app.use('/tools/web_order_box', express.static(path.join(process.cwd(), 'public', 'tools', 'web_order_box')))
app.get('/tools/token_lab', (req, res) => {
try {
if (hasNavAuthRoot(req)) return res.redirect('/tools/token_lab/index.html')
const token = String(req.query.token || '').trim()
if (!token) return res.status(401).send('未授权')
const payload = verifyJwtWithKeys(token, [])
if (!payload) return res.status(401).send('未授权')
const iss = String(payload.iss || '')
const navIss = getNavIssFromFlags()
if (iss === navIss && !audMatch(payload, 'Tools-token_lab')) return res.status(401).send('未授权')
res.cookie(NAV_AUTH_COOKIE, '1', { httpOnly: true, sameSite: 'lax' })
return res.redirect('/tools/token_lab/index.html')
} catch {
return res.status(401).send('未授权')
}
})
app.use('/tools/token_lab', (req, res, next) => {
try {
if (hasNavAuthRoot(req)) return next()
return res.status(401).send('未授权')
} catch {
return res.status(401).send('未授权')
}
})
app.use('/tools/token_lab', (req, res, next) => {
res.set('X-Frame-Options', 'SAMEORIGIN')
next()
})
app.use('/tools/token_lab', express.static(path.join(process.cwd(), 'public', 'tools', 'token_lab')))
app.use('/tools/investment_ledger', (req, res, next) => {
try {
if (hasNavAuthRoot(req)) return next()