feat(02-01): dev-auth bypass middleware with production hard guard
- Create apps/api/src/auth/devBypass.ts: devAuthBypass() middleware with NODE_ENV=production hard guard as first conditional (T-02-01 mitigation) - Exports DEV_USER const (id:1, color:COLOR_PALETTE[0]) for test reference - Mount devAuthBypass() before oidcAuthMiddleware on /api/* in index.ts - Add devBypass.test.ts: all three behavioral cases pass (production guard, unset-flag passthrough, active-injection) - Add DEV_AUTH_BYPASS to .env.example with production warning comment - Extend docs/deployment.md with dev-auth bypass section and production prohibition
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Dev-auth bypass middleware (Pitfall 7 — T-02-01).
|
||||
*
|
||||
* Active ONLY when DEV_AUTH_BYPASS=true AND NODE_ENV !== 'production'.
|
||||
* Injects a fixed dev user into the Hono context so the OIDC auth guard is effectively
|
||||
* bypassed for local development WITHOUT live Authelia (D-14).
|
||||
*
|
||||
* Mount BEFORE oidcAuthMiddleware on /api/* in index.ts.
|
||||
* When the bypass is inactive (wrong env, or NODE_ENV=production) the middleware is a
|
||||
* pure no-op passthrough — production behaviour is unchanged.
|
||||
*
|
||||
* Context key: 'user' — matches the key read by downstream consumers (e.g. routes/me.ts
|
||||
* calls getAuth(c) from @hono/oidc-auth; the events route will read c.get('user') directly).
|
||||
* In dev bypass mode, c.get('user') returns DEV_USER. getAuth(c) is still called by me.ts
|
||||
* but will return null because no OIDC session cookie is present; me.ts guards this with
|
||||
* `if (!auth) return 401`. When using the bypass, consume c.get('user') directly in routes
|
||||
* that need the user object (events route pattern in Plan 02).
|
||||
*
|
||||
* Security:
|
||||
* - The FIRST conditional is always `NODE_ENV === 'production'` — checked before reading
|
||||
* any other env var. This is the hard guard (T-02-01). Even if DEV_AUTH_BYPASS is
|
||||
* accidentally set in production config, the guard fires and returns a no-op.
|
||||
* - The production Docker Compose MUST NOT set DEV_AUTH_BYPASS. See docs/deployment.md.
|
||||
* - This file must never be removed — the pattern is referenced by Plan 02 routes.
|
||||
*/
|
||||
|
||||
import type { MiddlewareHandler } from 'hono'
|
||||
import { COLOR_PALETTE } from './user.js'
|
||||
|
||||
export const DEV_USER = {
|
||||
id: 1,
|
||||
oidcIss: 'dev',
|
||||
oidcSub: 'dev-user',
|
||||
displayName: 'Dev User',
|
||||
color: COLOR_PALETTE[0], // '#4A90D9' — first palette slot
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Returns a Hono MiddlewareHandler that injects DEV_USER into the request context
|
||||
* when the dev-auth bypass is active, or a pure passthrough when inactive.
|
||||
*
|
||||
* The function evaluates env vars at call time (when the app starts), not at request time.
|
||||
* This means the middleware choice is fixed for the lifetime of the process — intentional,
|
||||
* since changing auth mode requires a restart.
|
||||
*/
|
||||
export function devAuthBypass(): MiddlewareHandler {
|
||||
// Hard production guard — FIRST check, before reading any other env var.
|
||||
// Ensures this middleware can never grant access in production regardless of config.
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return async (_c, next) => next()
|
||||
}
|
||||
|
||||
// Bypass flag not set — passthrough; OIDC auth proceeds normally.
|
||||
if (process.env.DEV_AUTH_BYPASS !== 'true') {
|
||||
return async (_c, next) => next()
|
||||
}
|
||||
|
||||
// Bypass active: inject fixed dev user into Hono context.
|
||||
// Routes that read c.get('user') will receive DEV_USER.
|
||||
return async (c, next) => {
|
||||
c.set('user', DEV_USER)
|
||||
await next()
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { meRouter } from './routes/me.js'
|
||||
import { eventsRouter } from './routes/events.js'
|
||||
import { sseRouter } from './routes/sse.js'
|
||||
import { oidcAuthMiddleware, processOAuthCallback } from './auth/middleware.js'
|
||||
import { devAuthBypass } from './auth/devBypass.js'
|
||||
import { startBrokerPoller } from './broker/poller.js'
|
||||
|
||||
export const app = new Hono()
|
||||
@@ -17,6 +18,11 @@ app.get('/callback', (c) => processOAuthCallback(c))
|
||||
// GET /health — unauthenticated, mounted BEFORE the OIDC guard (T-01-03, T-02-05)
|
||||
app.route('/health', healthRouter)
|
||||
|
||||
// Dev-auth bypass — no-op passthrough unless DEV_AUTH_BYPASS=true AND NODE_ENV!='production'.
|
||||
// When active, injects a fixed dev user so the OIDC guard below is not required for local dev.
|
||||
// Must be mounted BEFORE oidcAuthMiddleware (T-02-01 mitigation; see auth/devBypass.ts).
|
||||
app.use('/api/*', devAuthBypass())
|
||||
|
||||
// Protect all /api/* routes with OIDC session middleware (AUTH-01, T-02-05).
|
||||
// Unauthenticated requests receive a 302 redirect to Authelia's authorize endpoint.
|
||||
// OIDC_AUTH_EXTERNAL_URL is MANDATORY behind Pangolin to construct the correct
|
||||
|
||||
Reference in New Issue
Block a user