feat: forward X-API-Key header from Next.js proxy routes to ML service

All 12 Next.js API routes that proxy requests to the ML service
(INFERENCE_API_URL / localhost:8001) now include the X-API-Key header
read from process.env.API_KEY. Affected routes:
- /api/predict
- /api/predict/batch
- /api/model/info
- /api/model/load
- /api/training/start
- /api/training/runs
- /api/training/runs/[run_id] (DELETE)
- /api/training/dataset-info
- /api/training/active
- /api/training/build-dataset
- /api/patterns/available
- /api/patterns/detect

Marks task 3.3 as complete in openspec/changes/code-review-fix/tasks.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marko Djordjevic 2026-02-18 11:06:18 +01:00
parent 5f569d9134
commit 4a3e4a48ba
13 changed files with 13 additions and 9 deletions

View file

@ -21,7 +21,7 @@
- [x] 3.1 `[sonnet]` Create `src/middleware.ts` with API key auth middleware: read `API_KEY` env var, check `X-API-Key` header on all `/api/*` routes except `/api/health`, return 401 if invalid
- [x] 3.2 `[sonnet]` Add FastAPI `Depends()` API key dependency in `services/ml/app/main.py`: read `API_KEY` env var, check `X-API-Key` header, exempt `/health` endpoint
- [ ] 3.3 `[sonnet]` Update all Next.js proxy routes to forward `X-API-Key` header to ML service
- [x] 3.3 `[sonnet]` Update all Next.js proxy routes to forward `X-API-Key` header to ML service
- [ ] 3.4 `[haiku]` Add `API_KEY` to `.env.example` with placeholder value and instructions
## 4. API Route Hardening (Next.js)

View file

@ -14,6 +14,7 @@ export async function GET(request: NextRequest) {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.API_KEY || '',
},
signal: controller.signal,
});

View file

@ -12,7 +12,7 @@ export async function POST(request: NextRequest) {
const response = await fetch(`${INFERENCE_API_URL}/model/load`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.API_KEY || '' },
body: JSON.stringify(body),
signal: controller.signal,
});

View file

@ -10,7 +10,7 @@ export async function GET(_request: NextRequest) {
try {
const response = await fetch(`${INFERENCE_API_URL}/patterns/available`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.API_KEY || '' },
signal: controller.signal,
});
clearTimeout(timeoutId);

View file

@ -13,7 +13,7 @@ export async function POST(request: NextRequest) {
const response = await fetch(`${INFERENCE_API_URL}/patterns/detect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.API_KEY || '' },
body: JSON.stringify(body),
signal: controller.signal,
});

View file

@ -16,6 +16,7 @@ export async function POST(request: NextRequest) {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.API_KEY || '',
},
body: JSON.stringify(body),
signal: controller.signal,

View file

@ -16,6 +16,7 @@ export async function POST(request: NextRequest) {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.API_KEY || '',
},
body: JSON.stringify(body),
signal: controller.signal,

View file

@ -10,7 +10,7 @@ export async function GET(_request: NextRequest) {
try {
const response = await fetch(`${INFERENCE_API_URL}/training/active`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.API_KEY || '' },
signal: controller.signal,
});
clearTimeout(timeoutId);

View file

@ -10,7 +10,7 @@ export async function POST() {
try {
const response = await fetch(`${INFERENCE_API_URL}/training/build-dataset`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.API_KEY || '' },
signal: controller.signal,
});
clearTimeout(timeoutId);

View file

@ -10,7 +10,7 @@ export async function GET(_request: NextRequest) {
try {
const response = await fetch(`${INFERENCE_API_URL}/training/dataset-info`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.API_KEY || '' },
signal: controller.signal,
});
clearTimeout(timeoutId);

View file

@ -24,6 +24,7 @@ export async function DELETE(
try {
const response = await fetch(`${INFERENCE_API_URL}/training/runs/${run_id}`, {
method: 'DELETE',
headers: { 'X-API-Key': process.env.API_KEY || '' },
signal: controller.signal,
});
clearTimeout(timeoutId);

View file

@ -10,7 +10,7 @@ export async function GET(_request: NextRequest) {
try {
const response = await fetch(`${INFERENCE_API_URL}/training/runs`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.API_KEY || '' },
signal: controller.signal,
});
clearTimeout(timeoutId);

View file

@ -12,7 +12,7 @@ export async function POST(request: NextRequest) {
const response = await fetch(`${INFERENCE_API_URL}/training/start`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.API_KEY || '' },
body: JSON.stringify(body),
signal: controller.signal,
});