every proxied request carries who is asking

Officer's session is the only place the acting user is known, and fofos writes it
into its own change history. An email in X-Actor, because that database has no
users to point at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 08:36:34 +01:00
co-authored by Claude Opus 5
parent fc3b9cca4a
commit b165e10f53
2 changed files with 12 additions and 0 deletions
+9
View File
@@ -48,6 +48,14 @@ type RequestOptions = {
/** Already-serialised. Forwarded as given so what fofos receives is what the caller sent. */ /** Already-serialised. Forwarded as given so what fofos receives is what the caller sent. */
body?: string | FormData | null; body?: string | FormData | null;
contentType?: string | null; contentType?: string | null;
/**
* Who is asking, as an email — sent as `X-Actor`, and written into fofos's own change history.
*
* fofos has no users, so it cannot work this out and cannot check it: the header is as trustworthy
* as the token that carried it. That is enough for what it is for, which is telling two colleagues
* apart rather than proving anything.
*/
actor?: string | null;
}; };
export type FofosResponse = { status: number; body: string; contentType: string | null }; export type FofosResponse = { status: number; body: string; contentType: string | null };
@@ -70,6 +78,7 @@ export async function fofosRequest(
// Left unset for FormData: fetch has to write its own, including the multipart boundary, and a // Left unset for FormData: fetch has to write its own, including the multipart boundary, and a
// hand-set Content-Type here produces a body the other end cannot parse. // hand-set Content-Type here produces a body the other end cannot parse.
if (options.contentType) headers['Content-Type'] = options.contentType; if (options.contentType) headers['Content-Type'] = options.contentType;
if (options.actor) headers['X-Actor'] = options.actor;
let response: Response; let response: Response;
try { try {
+3
View File
@@ -762,6 +762,9 @@ router.all('/*', async (ctx) => {
search: url.search, search: url.search,
body, body,
contentType: body ? 'application/json' : null, contentType: body ? 'application/json' : null,
// Officer's session is the only place the acting user is known. fofos writes it into its own
// history — see its record.ts, and the note on `actor` in fofos-client.
actor: ctx.get('user')?.email ?? null,
}); });
return passThrough(response); return passThrough(response);
} catch (error) { } catch (error) {