(req: Request, res: Response, next: NextFunction)
| 53 | } |
| 54 | |
| 55 | export function apiKeyAuth(req: Request, res: Response, next: NextFunction): void { |
| 56 | const validKeys = getValidKeys() |
| 57 | |
| 58 | // If no keys configured, allow all requests (local dev mode) |
| 59 | if (!validKeys) { |
| 60 | req.apiKeyId = 'anonymous' |
| 61 | const tier = resolveTier(null) |
| 62 | req.tier = tier |
| 63 | req.tierConfig = getTierConfig(tier) |
| 64 | next() |
| 65 | return |
| 66 | } |
| 67 | |
| 68 | const authHeader = req.headers.authorization |
| 69 | if (!authHeader || !authHeader.startsWith('Bearer ')) { |
| 70 | res.status(401).json({ |
| 71 | error: 'Missing or invalid Authorization header. Use: Bearer <your-api-key>', |
| 72 | }) |
| 73 | return |
| 74 | } |
| 75 | |
| 76 | const key = authHeader.slice(7).trim() |
| 77 | |
| 78 | // C-1: Constant-time comparison to prevent timing attacks |
| 79 | const match = validKeys.some(valid => safeEqual(key, valid)) |
| 80 | if (!match) { |
| 81 | res.status(403).json({ error: 'Invalid API key' }) |
| 82 | return |
| 83 | } |
| 84 | |
| 85 | // H-2: Hash the full key for rate-limit bucketing (never expose raw key material) |
| 86 | req.apiKeyId = hashKey(key) |
| 87 | |
| 88 | // T-1: Resolve tier from raw key |
| 89 | const tier = resolveTier(key) |
| 90 | req.tier = tier |
| 91 | req.tierConfig = getTierConfig(tier) |
| 92 | |
| 93 | next() |
| 94 | } |
nothing calls this directly
no test coverage detected