归档 artalk-cf 评论后端 + rss-robot 到 blog-admin(含技术选型/模块分布 README)
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
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
This commit is contained in:
1 parent
855a001046
commit
a74b3c7127
98 files changed
+15657
No files matched your search
@@ -0,0 +1,110 @@
|
||||
// 人机验证全链路自测(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));
|
||||
Reference in new issue
Block a user