UI-UX/tools/test-otp-store.mjs
iye 9d003a3b6f
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
fix(auth): persist generated OTP so real codes can verify (with in-memory fallback)
之前 send-otp 生成的码只在 Redis 可用时才存. dev 没配 Redis → 码生成即丢, 用户拿到
真实 SMS 验证码也登不进去 (除了万能码 123456). 这是上次"修任意 6 位绕过"留下的回归.

新增 src/lib/otp-store.ts:
- storeOtp / consumeOtp 双方法, 内部按 Redis 可用性自动路由
- Redis 可用 → 走 Redis (生产)
- Redis 缺失 → 走进程内 Map (dev / 联调), 通过 globalThis 抗 HMR
- consumeOtp 校验通过即 del, 防重放

send-otp 与 verifyOtp 改走 otp-store, 不再直接读写 Redis 句柄。

E2E (curl + NextAuth callback):
  发码 → dev 日志拿 code=209988
  错码 000000 → 拒绝, session=null
  真码 209988 → 通过, session=粉丝_0099
  重放 209988 → 拒绝 (一次性消费)

并在 NODE_ENV !== production 时把生成的 code 打到 dev 终端, 方便 QA。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:21:00 +08:00

27 lines
1.1 KiB
JavaScript

// 真实跑 otp-store 的 store/consume 逻辑
import { storeOtp, consumeOtp } from "../src/lib/otp-store.ts";
const phone = "13900000000";
const cases = [];
await storeOtp(phone, "654321");
cases.push(["wrong code 111111 → false", await consumeOtp(phone, "111111"), false]);
cases.push(["right code 654321 → true", await consumeOtp(phone, "654321"), true]);
cases.push(["replay 654321 → false", await consumeOtp(phone, "654321"), false]);
cases.push(["never-stored phone → false", await consumeOtp("13800000001", "654321"), false]);
// 过期测试
await storeOtp(phone, "999000");
// 直接改 globalThis 里的 expiresAt 模拟过期
globalThis.__otpMemStore.set(phone, { code: "999000", expiresAt: Date.now() - 1 });
cases.push(["expired code → false", await consumeOtp(phone, "999000"), false]);
let pass = 0, fail = 0;
for (const [desc, got, expect] of cases) {
const ok = got === expect;
console.log(`${ok ? "✓" : "✗"} ${desc.padEnd(35)} got=${got}`);
ok ? pass++ : fail++;
}
console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail === 0 ? 0 : 1);