mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-19 22:27:20 +02:00
Revalidate consent sessions after request body upload
This commit is contained in:
1 parent
7b60979c96
commit
c24f6d4e94
2 files changed
+69
No files matched your search
@@ -186,6 +186,25 @@ export function createReviewConsentRouter(
|
||||
}
|
||||
});
|
||||
router.use(express.json({ limit: 12288, strict: true, inflate: false }));
|
||||
// Uploads can outlive the session checked before body parsing. Revalidate
|
||||
// before any provider call or consent write, not only before the response.
|
||||
router.use(async (req, res, next) => {
|
||||
try {
|
||||
const actor = res.locals.consentActor as Context;
|
||||
const fresh = await current(req);
|
||||
if (res.destroyed || res.headersSent || res.locals.consentSignal.aborted)
|
||||
return;
|
||||
if (
|
||||
!fresh ||
|
||||
fresh.accountId !== actor.accountId ||
|
||||
fresh.sessionId !== actor.sessionId
|
||||
)
|
||||
return reject(res, 401, "unauthorized");
|
||||
next();
|
||||
} catch {
|
||||
reject(res, 503, "unavailable");
|
||||
}
|
||||
});
|
||||
router.post(["/preview", "/confirm"], async (req, res) => {
|
||||
const actor = res.locals.consentActor as Context;
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,7 @@ const { expect } = require("chai");
|
||||
const express = require("express");
|
||||
const session = require("express-session");
|
||||
const http = require("http");
|
||||
const { setImmediate } = require("timers");
|
||||
const { randomBytes } = require("crypto");
|
||||
const {
|
||||
createReviewConsentRouter,
|
||||
@@ -259,6 +260,55 @@ describe("review consent browser HTTP transport", function () {
|
||||
).to.equal(403);
|
||||
expect(calls).to.have.length(0);
|
||||
});
|
||||
for (const operation of ["preview", "confirm"]) {
|
||||
for (const change of ["logout", "account replacement"]) {
|
||||
it(`rejects ${operation} before backend work after ${change} during upload`, async () => {
|
||||
const original = store.get.bind(store);
|
||||
let reads = 0, ready;
|
||||
const authenticated = new Promise((resolve) => { ready = resolve; });
|
||||
store.get = (id, callback) => original(id, (error, value) => {
|
||||
callback(error, value);
|
||||
// The first read restores the session; the second authenticates
|
||||
// the request before the JSON parser waits for the remaining body.
|
||||
if (++reads === 2) setImmediate(ready);
|
||||
});
|
||||
const body = JSON.stringify(operation === "preview" ? previewInput() : confirmInput());
|
||||
let upload;
|
||||
const response = new Promise((resolve, reject) => {
|
||||
upload = http.request({
|
||||
host: "127.0.0.1", port: server.address().port,
|
||||
path: "/api/review-consent/" + operation, method: "POST",
|
||||
headers: {
|
||||
"X-Forwarded-Proto": "https", Cookie: cookie, Origin: origin,
|
||||
"Content-Type": "application/json", "Content-Length": Buffer.byteLength(body),
|
||||
"X-Review-CSRF": csrf,
|
||||
},
|
||||
}, (res) => {
|
||||
res.resume();
|
||||
res.on("end", () => resolve(res.statusCode));
|
||||
});
|
||||
upload.on("error", reject);
|
||||
upload.write(body.slice(0, 1));
|
||||
});
|
||||
try {
|
||||
await authenticated;
|
||||
if (change === "logout") {
|
||||
await new Promise((resolve) => store.destroy(sid, resolve));
|
||||
} else {
|
||||
const saved = await new Promise((resolve) => original(sid, (_error, value) => resolve(value)));
|
||||
saved.passport.user = "b".repeat(24);
|
||||
await new Promise((resolve) => store.set(sid, saved, resolve));
|
||||
}
|
||||
upload.end(body.slice(1));
|
||||
expect(await response).to.equal(401);
|
||||
expect(calls).to.have.length(0);
|
||||
} finally {
|
||||
upload.destroy();
|
||||
store.get = original;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
it("reloads session authority before returning a pending policy preview", async () => {
|
||||
let arrived, release;
|
||||
const ready = new Promise((resolve) => {
|
||||
|
||||
Reference in new issue
Block a user