mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-18 21:57:20 +02:00
349 lines
12 KiB
JavaScript
349 lines
12 KiB
JavaScript
require("ts-node/register/transpile-only");
|
|
const { expect } = require("chai");
|
|
const express = require("express");
|
|
const http = require("http");
|
|
const { createHash } = require("crypto");
|
|
const { setTimeout: delay } = require("timers/promises");
|
|
const {
|
|
createReviewCompletionService,
|
|
} = require("../src/server/service/review-completion-http");
|
|
const {
|
|
ReviewConsentError,
|
|
} = require("../src/server/service/review-owner-consent");
|
|
const client = "1".repeat(32),
|
|
intent = "2".repeat(32),
|
|
token = "3".repeat(64);
|
|
const exchangePath = "/service/v1/completions/exchange";
|
|
const readPath = `/service/v1/intents/${intent}/receipt`;
|
|
const command = () => ({
|
|
contract: "4open.artifacts/1",
|
|
clientId: client,
|
|
intentId: intent,
|
|
code: "4".repeat(64),
|
|
requestId: "5".repeat(32),
|
|
});
|
|
const receipt = () => ({
|
|
contract: "4open.artifacts/1",
|
|
clientId: client,
|
|
intentId: intent,
|
|
bindingId: "6".repeat(32),
|
|
submissionRef: "7".repeat(32),
|
|
entitlementId: "8".repeat(32),
|
|
policy: {
|
|
version: 1,
|
|
access: "restricted-review",
|
|
retainUntil: "2199-01-01T00:00:00Z",
|
|
},
|
|
});
|
|
const config = (scopes = ["completion.exchange", "receipt.read"]) =>
|
|
JSON.stringify({
|
|
version: 1,
|
|
keys: [
|
|
{
|
|
clientId: client,
|
|
keyId: "current",
|
|
tokenSHA256: createHash("sha256").update(token).digest("hex"),
|
|
notBefore: "2026-01-01T00:00:00Z",
|
|
notAfter: "2027-01-01T00:00:00Z",
|
|
scopes,
|
|
},
|
|
],
|
|
});
|
|
describe("review completion service HTTP transport", function () {
|
|
this.timeout(20000);
|
|
let server, backend, calls, clock;
|
|
async function start(raw = config(), beforeParser = true) {
|
|
const app = express();
|
|
app.set("trust proxy", "loopback");
|
|
if (!beforeParser) app.use(express.json());
|
|
app.use(
|
|
createReviewCompletionService(
|
|
raw === null ? undefined : raw,
|
|
backend,
|
|
() => clock,
|
|
),
|
|
);
|
|
server = await new Promise((resolve) => {
|
|
const s = app.listen(0, "127.0.0.1", () => resolve(s));
|
|
});
|
|
}
|
|
beforeEach(async () => {
|
|
clock = Date.parse("2026-09-27T12:00:00Z");
|
|
calls = [];
|
|
backend = {
|
|
exchange: async (id, body) => {
|
|
calls.push({ id, body });
|
|
return receipt();
|
|
},
|
|
receipt: async (id) => {
|
|
calls.push({ id });
|
|
return receipt();
|
|
},
|
|
};
|
|
});
|
|
afterEach(async () => {
|
|
if (server) {
|
|
server.closeAllConnections();
|
|
await new Promise((resolve) => server.close(resolve));
|
|
server = undefined;
|
|
}
|
|
});
|
|
function request(path = exchangePath, options = {}) {
|
|
const body =
|
|
options.raw === undefined
|
|
? path === exchangePath && options.body !== false
|
|
? JSON.stringify(options.body || command())
|
|
: undefined
|
|
: options.raw;
|
|
return new Promise((resolve, reject) => {
|
|
const req = http.request(
|
|
{
|
|
hostname: "127.0.0.1",
|
|
port: server.address().port,
|
|
path,
|
|
method: options.method || (body === undefined ? "GET" : "POST"),
|
|
headers: {
|
|
"X-Forwarded-Proto": "https",
|
|
"X-4open-Artifact-Client-ID": client,
|
|
"X-4open-Artifact-Service-Key-ID": "current",
|
|
Authorization: "Bearer " + token,
|
|
...(body === undefined
|
|
? {}
|
|
: {
|
|
"Content-Type": "application/json",
|
|
"Content-Length": Buffer.byteLength(body),
|
|
}),
|
|
...options.headers,
|
|
},
|
|
},
|
|
(res) => {
|
|
let text = "";
|
|
res.on("data", (chunk) => {
|
|
text += chunk;
|
|
});
|
|
res.on("end", () =>
|
|
resolve({
|
|
status: res.statusCode,
|
|
headers: res.headers,
|
|
body: JSON.parse(text),
|
|
}),
|
|
);
|
|
},
|
|
);
|
|
req.on("error", reject);
|
|
if (body !== undefined) req.write(body);
|
|
req.end();
|
|
});
|
|
}
|
|
it("requires scoped service credentials and returns the exact client receipt", async () => {
|
|
await start();
|
|
const result = await request();
|
|
expect(result.status).equal(200);
|
|
expect(result.body).deep.equal(receipt());
|
|
expect(result.headers["cache-control"]).equal("no-store");
|
|
expect(result.headers["referrer-policy"]).equal("no-referrer");
|
|
expect(result.headers["set-cookie"]).equal(undefined);
|
|
expect(result.headers["access-control-allow-origin"]).equal(undefined);
|
|
expect((await request(readPath)).status).equal(200);
|
|
expect(calls).length(2);
|
|
});
|
|
it("stays disabled without a dedicated registry", async () => {
|
|
await start(null);
|
|
expect((await request()).status).equal(404);
|
|
expect(calls).length(0);
|
|
});
|
|
it("does not permit a read credential to exchange a completion", async () => {
|
|
await start(config(["receipt.read"]));
|
|
expect((await request()).status).equal(401);
|
|
expect((await request(readPath)).status).equal(200);
|
|
expect(calls).length(1);
|
|
});
|
|
it("rejects capabilities-only configuration and malformed or duplicate registry fields", () => {
|
|
const old = JSON.parse(config());
|
|
delete old.keys[0].scopes;
|
|
for (const raw of [
|
|
JSON.stringify(old),
|
|
config().replace('"version":1', '"version":1,"version":1'),
|
|
config().replace('"receipt.read"', '"unknown"'),
|
|
"{",
|
|
])
|
|
expect(() => createReviewCompletionService(raw, backend)).to.throw(
|
|
"Invalid review exchange credentials",
|
|
);
|
|
});
|
|
it("rejects browser context, cleartext, encodings and duplicate authentication headers", async () => {
|
|
await start();
|
|
for (const headers of [
|
|
{ Cookie: "session=untrusted" },
|
|
{ Origin: "https://example.test" },
|
|
{ Referer: "https://example.test" },
|
|
{ "Sec-Fetch-Site": "none" },
|
|
{ "X-Forwarded-Proto": "http" },
|
|
{ "Content-Encoding": "gzip" },
|
|
{ Authorization: ["Bearer " + token, "Bearer " + token] },
|
|
]) {
|
|
const result = await request(exchangePath, { headers });
|
|
expect(result.status).oneOf([400, 401, 403]);
|
|
expect(result.headers.connection).equal("close");
|
|
}
|
|
expect(calls).length(0);
|
|
});
|
|
it("rejects noncanonical routes, queries, read bodies and wrong methods", async () => {
|
|
await start();
|
|
for (const path of [
|
|
exchangePath + "/",
|
|
exchangePath + "?x=1",
|
|
readPath + "?x=1",
|
|
"/service/v1/intents/" + intent.toUpperCase() + "F/receipt",
|
|
])
|
|
expect((await request(path, { method: "POST", raw: "{}" })).status).equal(
|
|
404,
|
|
);
|
|
expect(
|
|
(await request(exchangePath, { method: "GET", body: false })).status,
|
|
).equal(405);
|
|
expect(
|
|
(await request(readPath, { method: "GET", raw: "{}" })).status,
|
|
).equal(400);
|
|
expect(calls).length(0);
|
|
});
|
|
it("rejects duplicate decoded JSON keys, unknown fields, malformed UTF-8 and excessive bodies", async () => {
|
|
await start();
|
|
const valid = JSON.stringify(command());
|
|
for (const raw of [
|
|
valid.replace('"code":', '"code":"' + "0".repeat(64) + '","co\\u0064e":'),
|
|
JSON.stringify({ ...command(), extra: true }),
|
|
valid + "{}",
|
|
"[]",
|
|
Buffer.from([123, 34, 120, 34, 58, 34, 255, 34, 125]),
|
|
" ".repeat(65537),
|
|
])
|
|
expect((await request(exchangePath, { raw })).status).equal(400);
|
|
expect(calls).length(0);
|
|
});
|
|
it("rejects wrong client scope and unsupported wire versions", async () => {
|
|
await start();
|
|
expect(
|
|
(
|
|
await request(exchangePath, {
|
|
body: { ...command(), clientId: "a".repeat(32) },
|
|
})
|
|
).status,
|
|
).equal(403);
|
|
expect(
|
|
(
|
|
await request(exchangePath, {
|
|
body: { ...command(), contract: "4open.artifacts/2" },
|
|
})
|
|
).status,
|
|
).equal(422);
|
|
expect(calls).length(0);
|
|
});
|
|
it("fails closed if mounted after a JSON parser", async () => {
|
|
await start(config(), false);
|
|
expect((await request()).status).equal(503);
|
|
expect(calls).length(0);
|
|
});
|
|
it("rechecks credential expiry after backend work and filters invalid receipts", async () => {
|
|
backend.exchange = async () => {
|
|
clock = Date.parse("2027-01-01T00:00:00Z");
|
|
return receipt();
|
|
};
|
|
await start();
|
|
expect((await request()).status).equal(401);
|
|
clock = Date.parse("2026-09-27T12:00:00Z");
|
|
backend.exchange = async () => ({ ...receipt(), owner: "private-owner" });
|
|
const result = await request();
|
|
expect(result.status).equal(503);
|
|
expect(JSON.stringify(result.body)).not.include("private-owner");
|
|
});
|
|
it("uses fixed errors without exposing backend details", async () => {
|
|
await start();
|
|
for (const [kind, status] of [
|
|
["invalid", 400],
|
|
["forbidden", 403],
|
|
["expired", 410],
|
|
["conflict", 409],
|
|
["unavailable", 503],
|
|
]) {
|
|
backend.exchange = async () => {
|
|
throw new ReviewConsentError(kind);
|
|
};
|
|
const result = await request();
|
|
expect(result.status).equal(status);
|
|
expect(result.body.requestId).match(/^[a-f0-9]{32}$/);
|
|
}
|
|
backend.exchange = async () => {
|
|
throw new Error("secret database diagnostics");
|
|
};
|
|
const result = await request();
|
|
expect(result.status).equal(503);
|
|
expect(JSON.stringify(result.body)).not.include("diagnostics");
|
|
});
|
|
it("limits concurrent operations until the backend settles", async () => {
|
|
const pending = [];
|
|
backend.exchange = () => new Promise((resolve) => pending.push(resolve));
|
|
await start();
|
|
const requests = Array.from({ length: 4 }, () => request());
|
|
for (let i = 0; i < 100 && pending.length < 4; i++) await delay(5);
|
|
expect(pending).length(4);
|
|
expect((await request()).status).equal(503);
|
|
pending.forEach((resolve) => resolve(receipt()));
|
|
expect((await Promise.all(requests)).every((r) => r.status === 200)).equal(
|
|
true,
|
|
);
|
|
backend.exchange = async () => receipt();
|
|
expect((await request()).status).equal(200);
|
|
});
|
|
it("limits client requests and restores capacity in the next minute", async () => {
|
|
await start();
|
|
for (let i = 0; i < 60; i++)
|
|
expect((await request(readPath)).status).equal(200);
|
|
expect((await request(readPath)).status).equal(429);
|
|
clock += 60000;
|
|
expect((await request(readPath)).status).equal(200);
|
|
});
|
|
it("closes unauthorized incomplete uploads before reading their bodies", async () => {
|
|
await start();
|
|
const startTime = Date.now();
|
|
const result = await request(exchangePath, {
|
|
raw: "{",
|
|
headers: {
|
|
Authorization: "Bearer " + "0".repeat(64),
|
|
"Content-Length": "60000",
|
|
},
|
|
});
|
|
expect(result.status).equal(401);
|
|
expect(Date.now() - startTime).lessThan(2000);
|
|
expect(calls).length(0);
|
|
});
|
|
it("bounds authenticated incomplete uploads without invoking the backend", async () => {
|
|
await start();
|
|
const result = await request(exchangePath, {
|
|
raw: "{",
|
|
headers: { "Content-Length": "60000" },
|
|
});
|
|
expect(result.status).equal(503);
|
|
expect(calls).length(0);
|
|
});
|
|
it('supports overlapping rotation keys with independent validity windows', async () => {
|
|
const settings=JSON.parse(config()), nextToken='b'.repeat(64);
|
|
settings.keys[0].notAfter='2026-09-27T12:00:01Z';
|
|
settings.keys.push({...settings.keys[0],keyId:'next',tokenSHA256:createHash('sha256').update(nextToken).digest('hex'),notBefore:'2026-09-27T12:00:00Z',notAfter:'2027-01-01T00:00:00Z'});
|
|
await start(JSON.stringify(settings));
|
|
const headers={'X-4open-Artifact-Service-Key-ID':'next',Authorization:'Bearer '+nextToken};
|
|
expect((await request(readPath)).status).equal(200);
|
|
expect((await request(readPath,{headers})).status).equal(200);
|
|
clock+=1000; expect((await request(readPath)).status).equal(401);
|
|
expect((await request(readPath,{headers})).status).equal(200);
|
|
clock-=2000; expect((await request(readPath,{headers})).status).equal(401);
|
|
});
|
|
it('measures 50 authenticated receipt reads with a synthetic backend', async () => {
|
|
await start(); const times=[]; const process=require('process');
|
|
for(let i=0;i<50;i++) { const startTime=process.hrtime.bigint(); expect((await request(readPath)).status).equal(200); times.push(Number(process.hrtime.bigint()-startTime)/1e6); }
|
|
times.sort((a,b)=>a-b);
|
|
if(process.env.TEST_REVIEW_COMPLETION_HTTP_PERF) require('fs').writeFileSync(process.env.TEST_REVIEW_COMPLETION_HTTP_PERF,JSON.stringify({calls:50,workload:'loopback HTTP with trusted-proxy TLS metadata and synthetic backend, no provider or database',medianMs:times[24],p95Ms:times[47]},null,2)+'\n');
|
|
});
|
|
|
|
});
|