// ================================
// AllScale Response Signature Verify
// ================================
const API_SECRET = (pm.environment.get("API_SECRET") || "").trim();
function sha256Hex(str) {
return CryptoJS.SHA256(str || "").toString(CryptoJS.enc.Hex);
}
function hmacSha256Base64(key, msg) {
const mac = CryptoJS.HmacSHA256(msg, key);
return CryptoJS.enc.Base64.stringify(mac);
}
function getHeader(name) {
return pm.response.headers.get(name) || "";
}
pm.test("API_SECRET is set", function () {
pm.expect(API_SECRET).to.not.eql("");
});
// ---- request-side values ----
const path = pm.request.url.getPath();
const requestNonce = pm.request.headers.get("X-Nonce") || "";
const requestBodyRaw = pm.request.body?.raw || "";
const requestBodySha = sha256Hex(requestBodyRaw);
// ---- response-side values ----
const statusCode = pm.response.code;
const responseTimestamp = getHeader("X-Response-Timestamp");
const responseNonce = getHeader("X-Response-Nonce");
const responseSigHeader = getHeader("X-Response-Signature");
const echoedRequestNonce = getHeader("X-Request-Nonce");
// ---- sanity checks ----
pm.test("Response signing headers present", function () {
pm.expect(responseTimestamp).to.not.eql("");
pm.expect(responseNonce).to.not.eql("");
pm.expect(responseSigHeader).to.match(/^v1=.+/);
});
// ---- timestamp check ----
pm.test("Response timestamp within Β±5 minutes", function () {
const now = Math.floor(Date.now() / 1000);
const ts = parseInt(responseTimestamp, 10);
pm.expect(Math.abs(now - ts)).to.be.below(300);
});
// ---- response body hash ----
const responseBody = pm.response.text() || "";
const responseBodySha = sha256Hex(responseBody);
// ---- canonical string ----
const canonical = [
String(statusCode),
path,
echoedRequestNonce || requestNonce,
requestBodySha,
String(responseTimestamp),
responseNonce,
responseBodySha,
].join("\n");
// ---- signature verification ----
const expectedSig = "v1=" + hmacSha256Base64(API_SECRET, canonical);
pm.test("Response signature matches", function () {
pm.expect(responseSigHeader).to.eql(expectedSig);
});
// ---- optional replay protection ----
pm.test("Response nonce not replayed", function () {
const key = "RESP_NONCE_CACHE";
const ttl = 600;
const now = Math.floor(Date.now() / 1000);
let cache = [];
try {
cache = JSON.parse(pm.environment.get(key) || "[]");
} catch {
}
cache = cache.filter(x => now - x.ts < ttl);
pm.expect(cache.some(x => x.nonce === responseNonce)).to.eql(false);
cache.push({nonce: responseNonce, ts: now});
pm.environment.set(key, JSON.stringify(cache));
});