Files
blog/blog-admin/tools/test-human.mjs
T
zqlit a74b3c7127
Deploy to Production / pre-check (push) Successful in 58s
Deploy to Production / build (push) Successful in 4m3s
Deploy to Production / deploy-edgeone (push) Successful in 3m48s
Deploy to Production / finalize (push) Successful in 26s
Deploy to Production / notify-failure (push) Skipped
归档 artalk-cf 评论后端 + rss-robot 到 blog-admin(含技术选型/模块分布 README)
2026-10-04 08:45:40 +08:00

111 lines
3.3 KiB
JavaScript
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.
// 人机验证全链路自测(Node 侧算 PoW,与服务端 webcrypto 同算法)
import crypto from 'node:crypto';
const BASE = 'https://api.200181.xyz/api/v2/human';
const POW_DIFFICULTY = 4;
function sha256Hex(s) {
return crypto.createHash('sha256').update(s).digest('hex');
}
function solve(challenge, difficulty) {
const prefix = '0'.repeat(difficulty);
const t0 = Date.now();
for (let nonce = 0; nonce < 5_000_000; nonce++) {
if (sha256Hex(challenge + nonce).startsWith(prefix)) {
return { nonce, ms: Date.now() - t0, tries: nonce + 1 };
}
}
throw new Error('no solution');
}
async function j(path, init) {
const r = await fetch(BASE + path, init);
const text = await r.text();
try {
return { status: r.status, body: JSON.parse(text) };
} catch {
return { status: r.status, body: text.slice(0, 200) };
}
}
async function main() {
console.log('--- 1) GET /human/challenge');
const ch = await j('/challenge');
console.log(' ', ch.status, JSON.stringify(ch.body).slice(0, 160));
if (!ch.body || ch.body.enabled !== true) {
console.log('开关没开(human_check 不是 1),先设置 KV 再测');
return;
}
console.log('--- 2) POST /human/verify 错误 PoW(应 need_captcha)');
const bad = await j('/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
challenge: ch.body.challenge,
nonce: 12345,
exp: ch.body.exp,
sig: ch.body.sig,
elapsedMs: 5000,
events: 30,
}),
});
console.log(' ', bad.status, JSON.stringify(bad.body));
console.log('--- 3) 正确 PoW + 无交互信号(应 need_click)');
const sol = solve(ch.body.challenge, POW_DIFFICULTY);
console.log(` 解出 nonce=${sol.nonce}(${sol.tries} 次尝试 / ${sol.ms}ms)`);
const mid = await j('/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
challenge: ch.body.challenge,
nonce: sol.nonce,
exp: ch.body.exp,
sig: ch.body.sig,
elapsedMs: 300,
events: 0,
}),
});
console.log(' ', mid.status, JSON.stringify(mid.body));
console.log('--- 4) 蜜罐被填(应 need_captcha)');
const honey = await j('/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
challenge: ch.body.challenge,
nonce: sol.nonce,
exp: ch.body.exp,
sig: ch.body.sig,
elapsedMs: 8000,
events: 12,
honeypot: 'bot@example.com',
}),
});
console.log(' ', honey.status, JSON.stringify(honey.body));
console.log('--- 5) 正确 PoW + 有交互(应 pass:true 并下发通行证)');
const okRes = await j('/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
challenge: ch.body.challenge,
nonce: sol.nonce,
exp: ch.body.exp,
sig: ch.body.sig,
elapsedMs: 8000,
events: 12,
}),
});
console.log(' ', okRes.status, JSON.stringify(okRes.body));
console.log('--- 6) GET /human/status(应 pass:true,说明 KV 通行证生效)');
const st = await j('/status');
console.log(' ', st.status, JSON.stringify(st.body));
}
main().catch((e) => console.error('ERR', e.message));