mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-20 14:47:18 +02:00
Add scoped completion exchange service transport
This commit is contained in:
1 parent
d909bf281b
commit
7fe60a89aa
7 files changed
+858
-60
No files matched your search
@@ -0,0 +1,348 @@
|
||||
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');
|
||||
});
|
||||
|
||||
});
|
||||
Reference in new issue
Block a user