// src/lib/crypto.ts — 前端 JS 请求签名(反爬一层,非加密) // // 仅做 HMAC-SHA256 计算,由 api.ts 的公开请求原语附带 X-Sign / X-Sign-Ts / X-Sign-Nonce 头, // 供后端 middleware.ClientSign 校验「请求大概率来自真前端」。 // // 安全说明:secret 必须出现在前端 bundle 才能签名(对浏览器可见),故只是「提高成本」, // 不能作为唯一防线;真正有效需配合后端按 IP 限流。生产请改用随机长串并同步两端。 const CLIENT_SIGN_SECRET = "dev-client-sign-secret-change-me" const CLIENT_SIGN_TTL = 30 // 秒,签名有效期(与后端 client_sign.ttl_seconds 一致) // 计算 HMAC-SHA256 签名。 // canonical = METHOD + "\n" + Path + "\n" + RawQuery + "\n" + ts + "\n" + nonce // 与后端 middleware.ClientSign 的拼接方式严格一致(换行符分隔,顺序固定)。 export async function clientSign( method: string, fullPath: string, rawQuery: string, ): Promise<{ sig: string; ts: string; nonce: string }> { const ts = String(Math.floor(Date.now() / 1000)) const nonce = Math.random().toString(36).slice(2) + Date.now().toString(36) const msg = [method, fullPath, rawQuery, ts, nonce].join("\n") const enc = new TextEncoder() const key = await crypto.subtle.importKey( "raw", enc.encode(CLIENT_SIGN_SECRET), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ) const buf = await crypto.subtle.sign("HMAC", key, enc.encode(msg)) const sig = Array.from(new Uint8Array(buf)) .map((b) => b.toString(16).padStart(2, "0")) .join("") return { sig, ts, nonce } }