mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
Remove Phase 3: Lite Fix documentation and add regression test for CSRF token fallback mechanism
This commit is contained in:
81
ccw/tests/csrf-header-cookie-fallback.test.js
Normal file
81
ccw/tests/csrf-header-cookie-fallback.test.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Regression test: tolerate stale X-CSRF-Token headers by falling back to the cookie token.
|
||||
*
|
||||
* Background:
|
||||
* - Tokens are single-use and rotated after each successful state-changing request.
|
||||
* - Older dashboards may cache the header token while other requests rotate the cookie token.
|
||||
* - If the server prioritizes the stale header token and does not fall back, valid requests can 403.
|
||||
*/
|
||||
|
||||
import { afterEach, describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
function createMockRes() {
|
||||
const headers = {};
|
||||
const response = {
|
||||
status: null,
|
||||
headers,
|
||||
body: '',
|
||||
writeHead: (status, nextHeaders) => {
|
||||
response.status = status;
|
||||
if (nextHeaders) {
|
||||
for (const [k, v] of Object.entries(nextHeaders)) {
|
||||
headers[String(k).toLowerCase()] = v;
|
||||
}
|
||||
}
|
||||
},
|
||||
setHeader: (name, value) => {
|
||||
headers[String(name).toLowerCase()] = value;
|
||||
},
|
||||
getHeader: (name) => headers[String(name).toLowerCase()],
|
||||
end: (body) => {
|
||||
response.body = body ? String(body) : '';
|
||||
},
|
||||
};
|
||||
return response;
|
||||
}
|
||||
|
||||
const middlewareUrl = new URL('../dist/core/auth/csrf-middleware.js', import.meta.url);
|
||||
const managerUrl = new URL('../dist/core/auth/csrf-manager.js', import.meta.url);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let middleware;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let csrfManager;
|
||||
|
||||
describe('csrf middleware (header-cookie fallback)', async () => {
|
||||
middleware = await import(middlewareUrl.href);
|
||||
csrfManager = await import(managerUrl.href);
|
||||
|
||||
afterEach(() => {
|
||||
csrfManager.resetCsrfTokenManager();
|
||||
});
|
||||
|
||||
it('accepts request when header token is stale but cookie token is valid', async () => {
|
||||
const sessionId = 'session-1';
|
||||
const manager = csrfManager.getCsrfTokenManager({ cleanupIntervalMs: 0 });
|
||||
const staleHeaderToken = manager.generateToken(sessionId);
|
||||
const validCookieToken = manager.generateToken(sessionId);
|
||||
|
||||
// Mark the header token as already used (simulates a previous successful request).
|
||||
assert.equal(manager.validateToken(staleHeaderToken, sessionId), true);
|
||||
|
||||
const req = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
cookie: `ccw_session_id=${sessionId}; XSRF-TOKEN=${validCookieToken}`,
|
||||
'x-csrf-token': staleHeaderToken,
|
||||
},
|
||||
};
|
||||
const res = createMockRes();
|
||||
|
||||
const ok = await middleware.csrfValidation({ pathname: '/api/remove-recent-path', req, res });
|
||||
assert.equal(ok, true);
|
||||
assert.equal(res.status, null);
|
||||
|
||||
const rotated = res.headers['x-csrf-token'];
|
||||
assert.ok(typeof rotated === 'string' && rotated.length > 0);
|
||||
assert.notEqual(rotated, staleHeaderToken);
|
||||
assert.notEqual(rotated, validCookieToken);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user