2405625f99
feedback.auth.ts and tickets.auth.ts each become one binding to requireAppAccess. Everything downstream was already written against requireAdminAuth and res.locals.admin, and both still mean what they meant, so no router or service changed. What changed is the policy: an activated @nachklang.art account is no longer sufficient, an explicit per-app permission is. Three things followed from that and are not obvious from the diff: - APP_ORIGINS gets a production default. It feeds better-auth's trustedOrigins, and this is the first time the tickets and feedback origins matter there - before, the only browser origin that ever reached /admin/auth was the admin app itself. An origin missing from that list fails in a way that is easy to misread: sign-in works, the app works, and only sign-out returns an origin error. - Nothing reads X-Session-* any more; these two files were the last readers, and the calendar module passes its session in query parameters. The headers stay in the CORS allowedHeaders only so a browser still running a pre-cutover bundle gets a clean 401 rather than a preflight failure, and can come out once both frontends are deployed. - 40 admin operations documented a required X-Session-Id/X-Session-Key in swagger. They now declare the AdminSessionCookie scheme the admin module already defined, and each documents a 403 next to its 401. The integration assertions flip as their own comment predicted: one admin cookie opens both /feedback/admin/me and /tickets/admin/me, a user holding only feedback gets 200 and 403 respectively, and a legacy header session gets 401. The unit test that covered the old header authenticator is replaced by one asserting each module is bound to its own app and that neither consults the calendar users service. 163 unit tests and 43 integration tests green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
39 lines
1.8 KiB
TypeScript
39 lines
1.8 KiB
TypeScript
import {requireAppAccess} from '../admin/admin.middleware.js';
|
|
|
|
/**
|
|
* This file is the ONLY place in the feedback module that knows how admin
|
|
* authentication works. No route handler and no service outside this file may
|
|
* read session headers or resolve a user itself.
|
|
*
|
|
* Today: the shared admin identity in `src/models/admin/`. A session cookie
|
|
* set by /admin/auth on admin.nachklang.art, plus a `feedback` permission on
|
|
* the account. Both are re-checked on every request, so disabling a user or
|
|
* taking their feedback permission away takes effect immediately.
|
|
*
|
|
* Before 2026-09-06 this was a header session against the calendar users
|
|
* table, and any activated @nachklang.art account could administer feedback.
|
|
* That is why the swap is a one-line binding: everything downstream only ever
|
|
* saw `requireAdminAuth` and `res.locals.admin`, and both still mean what
|
|
* they meant. What changed is that access is now granted per user rather than
|
|
* implied by having an account.
|
|
*
|
|
* Explicitly forbidden: accepting session credentials from query parameters,
|
|
* even "temporarily". That is the exact mistake documented in
|
|
* DEFERRED_SECURITY.md item 1 for the Calendar domain, where credentials end
|
|
* up in access logs, browser history, proxy logs, and Referer headers.
|
|
*/
|
|
|
|
// The only thing the rest of the feedback module knows about an admin. The
|
|
// shared middleware puts a superset of this on res.locals.admin.
|
|
export interface AdminIdentity {
|
|
id: string;
|
|
email: string;
|
|
displayName: string;
|
|
}
|
|
|
|
// Express middleware used by every admin route. On success:
|
|
// res.locals.admin = AdminAccess (an AdminIdentity plus permissions), calls
|
|
// next(). On failure: 401 when not signed in, 403 when signed in without the
|
|
// feedback permission.
|
|
export const requireAdminAuth = requireAppAccess('feedback');
|