fix(auth): require the request on findSessionOrFail

findSessionOrFail took `req` as optional, so the quiet default was the
unscoped lookup: a route that forgot to pass it compiled cleanly and, in
multi-user mode, returned another user's session. `req` is now required,
and a caller with no request in flight must use the explicit, greppable
findSessionUnscopedOrFail instead.

Every current caller in src/ already passes req (verified by grep and
tsc), so behaviour is unchanged; the new helper has no production
callers and exists so the next request-less caller does not make `req`
optional again.

Tests pin the unscoped helper's behaviour and assert, from the source
AST, that `req` is a required parameter (tsc does not typecheck test/).
This commit is contained in:
Aamer Akhter committed 2026-10-22 17:23:03 -04:00
1 parent 002de32a08
commit 4f9274af3a
2 files changed
+53 -5

No files matched your search

+27 -1
View File
@@ -10,10 +10,16 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import ts from 'typescript';
import { WebServer } from '../src/web/server.js';
import { TmuxManager } from '../src/tmux-manager.js';
import { createUser, invalidateUsersCache } from '../src/user-store.js';
import { canAccessOwned, findSessionOrFail, sessionCapacityState } from '../src/web/route-helpers.js';
import {
canAccessOwned,
findSessionOrFail,
findSessionUnscopedOrFail,
sessionCapacityState,
} from '../src/web/route-helpers.js';
vi.spyOn(TmuxManager, 'isTmuxAvailable').mockReturnValue(true);
@@ -154,6 +160,26 @@ describe('session-scoping helpers (multi-user)', () => {
expect(findSessionOrFail(ctxWith(map), 's1', reqAs('root', 'admin'))).toBeDefined();
});
it('findSessionUnscopedOrFail is the explicit unscoped lookup: returns a foreign session, 404s an unknown id', () => {
const map = new Map<string, unknown>([['s1', fakeSession('alice')]]);
expect(findSessionUnscopedOrFail(ctxWith(map), 's1')).toBeDefined();
expect(() => findSessionUnscopedOrFail(ctxWith(map), 'missing')).toThrow();
});
it('findSessionOrFail declares req as REQUIRED, so an unscoped call must use the explicit helper', async () => {
// tsc does not typecheck test/, so pin the signature from the source AST: an
// optional `req` would make forgetting it compile into an UNSCOPED lookup.
const file = path.join(import.meta.dirname, '../src/web/route-helpers.ts');
const src = ts.createSourceFile(file, await fs.readFile(file, 'utf8'), ts.ScriptTarget.Latest, true);
const decl = src.statements.find(
(s): s is ts.FunctionDeclaration => ts.isFunctionDeclaration(s) && s.name?.text === 'findSessionOrFail'
);
const req = decl?.parameters.find((p) => p.name.getText(src) === 'req');
expect(req).toBeDefined();
expect(req?.questionToken).toBeUndefined();
expect(req?.initializer).toBeUndefined();
});
it('per-user session cap counts only the owner sessions', () => {
const map = new Map<string, unknown>([
['a', fakeSession('alice')],