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:
Lucas Berger
2026-06-05 09:32:00 -04:00
parent 75252eb08c
commit 8bd44b33c7
5 changed files with 207 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
/**
* devAuthBypass() middleware — unit tests.
*
* Tests the three behavioral cases:
* 1. NODE_ENV='production' → pure passthrough (hard guard), regardless of DEV_AUTH_BYPASS
* 2. NODE_ENV!='production' + DEV_AUTH_BYPASS unset → passthrough (no user injected)
* 3. NODE_ENV!='production' + DEV_AUTH_BYPASS='true' → DEV_USER injected into context
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Hono } from 'hono'
// We import after env manipulation since devAuthBypass() reads env vars at call time.
// Each test resets the module registry via vi.resetModules() to re-evaluate the function
// with the current process.env values.
describe('devAuthBypass middleware', () => {
const originalNodeEnv = process.env.NODE_ENV
const originalBypassFlag = process.env.DEV_AUTH_BYPASS
afterEach(() => {
// Restore env after each test
process.env.NODE_ENV = originalNodeEnv
if (originalBypassFlag === undefined) {
delete process.env.DEV_AUTH_BYPASS
} else {
process.env.DEV_AUTH_BYPASS = originalBypassFlag
}
})
it('is a pure passthrough in production (NODE_ENV=production), even when DEV_AUTH_BYPASS=true', async () => {
process.env.NODE_ENV = 'production'
process.env.DEV_AUTH_BYPASS = 'true'
// Import after env setup
const { devAuthBypass } = await import('../../src/auth/devBypass.js')
const app = new Hono()
app.use('/api/*', devAuthBypass())
let capturedUser: unknown = undefined
app.get('/api/test', (c) => {
capturedUser = c.get('user')
return c.json({ ok: true })
})
const res = await app.request('/api/test')
expect(res.status).toBe(200)
// Hard guard: user must NOT be injected in production
expect(capturedUser).toBeUndefined()
})
it('is a passthrough when NODE_ENV!=production and DEV_AUTH_BYPASS is not set', async () => {
process.env.NODE_ENV = 'test'
delete process.env.DEV_AUTH_BYPASS
const { devAuthBypass } = await import('../../src/auth/devBypass.js')
const app = new Hono()
app.use('/api/*', devAuthBypass())
let capturedUser: unknown = undefined
app.get('/api/test', (c) => {
capturedUser = c.get('user')
return c.json({ ok: true })
})
const res = await app.request('/api/test')
expect(res.status).toBe(200)
expect(capturedUser).toBeUndefined()
})
it('injects DEV_USER when NODE_ENV!=production and DEV_AUTH_BYPASS=true', async () => {
process.env.NODE_ENV = 'test'
process.env.DEV_AUTH_BYPASS = 'true'
const { devAuthBypass, DEV_USER } = await import('../../src/auth/devBypass.js')
const app = new Hono()
app.use('/api/*', devAuthBypass())
let capturedUser: unknown = undefined
app.get('/api/test', (c) => {
capturedUser = c.get('user')
return c.json({ ok: true })
})
const res = await app.request('/api/test')
expect(res.status).toBe(200)
// User must be the fixed DEV_USER
expect(capturedUser).toBeDefined()
expect(capturedUser).toEqual(DEV_USER)
expect((capturedUser as typeof DEV_USER).displayName).toBe('Dev User')
expect((capturedUser as typeof DEV_USER).oidcSub).toBe('dev-user')
})
})