// 回归测试 #3:订单查询 IDOR。GET /store/order/:id 只有携带正确的一次性 pollSecret // 才下发 redeemToken —— 枚举订单 id 偷不到他人登录令牌。 // 需要本地 MongoDB;连不上则整组 skip(不阻塞无 DB 的 CI)。 process.env.NODE_ENV = 'test'; // 非生产 → stub checkout 会立即开通,便于构造已支付订单 process.env.MONGO_DB = process.env.MONGO_DB_TEST || 'digital_me_test'; import { test, before, after } from 'node:test'; import assert from 'node:assert/strict'; import express from 'express'; import http from 'node:http'; let server, base, db, closeDb, mongoUp = true; before(async () => { const dbm = await import('../src/db.js'); closeDb = dbm.closeDb; try { db = await dbm.connectDb(); } catch { mongoUp = false; return; } const { storeRouter } = await import('../src/routes/store.js'); const app = express(); app.use(express.json()); app.use('/api/store', storeRouter); server = http.createServer(app).listen(0); await new Promise((ok) => server.once('listening', ok)); base = `http://127.0.0.1:${server.address().port}`; }); after(async () => { if (server) server.close(); if (mongoUp && closeDb) await closeDb(); }); const j = async (p, opts) => (await fetch(base + p, opts)).json(); test('order IDOR:无/错 secret 不下发 redeemToken,正确 secret 才下发', async (t) => { if (!mongoUp) return t.skip('无本地 MongoDB,跳过 DB 集成测试'); const prods = await j('/api/store/products'); const sku = (prods.products.find((p) => p.available) || {}).id; if (!sku) return t.skip('无可售商品'); const co = await j('/api/store/checkout', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sku, email: `buyer_${Date.now()}@example.com` }), }); assert.ok(co.orderId, 'checkout 应返回 orderId'); // 攻击者视角:只有订单 id const noSecret = await j(`/api/store/order/${co.orderId}`); assert.equal(noSecret.status, 'paid'); assert.equal(noSecret.redeemToken, '', 'IDOR:无 secret 不应下发 redeemToken'); const wrong = await j(`/api/store/order/${co.orderId}?secret=not-the-secret`); assert.equal(wrong.redeemToken, '', '错误 secret 也不应下发'); // 本人视角:拿到正确 pollSecret(stub 响应里不含,从 DB 取真实值) const { ObjectId } = await import('mongodb'); const order = await db.collection('store_orders').findOne({ _id: new ObjectId(co.orderId) }); const ok = await j(`/api/store/order/${co.orderId}?secret=${order.pollSecret}`); assert.ok(ok.redeemToken && ok.redeemToken.length > 0, '正确 secret 应下发 redeemToken'); await db.collection('store_orders').deleteOne({ _id: new ObjectId(co.orderId) }); });