From 685639a0d33b552cea99486fbf20b5dec1ecab7c Mon Sep 17 00:00:00 2001 From: Marko Djordjevic Date: Fri, 20 Feb 2026 13:10:23 +0100 Subject: [PATCH] Add getAuthUser() auth guard to all ML proxy API routes (task 7.3) Adds authentication check at the top of each handler in: - /api/predict - /api/predict/batch - /api/model/info - /api/model/load - /api/patterns/detect - /api/patterns/available - /api/training/start - /api/training/runs Returns 401 Unauthorized for unauthenticated requests. Proxy/fetch logic unchanged. Co-Authored-By: Claude Sonnet 4.6 --- openspec/changes/user-accounts/tasks.md | 2 +- src/app/api/model/info/route.ts | 6 ++++++ src/app/api/model/load/route.ts | 6 ++++++ src/app/api/patterns/available/route.ts | 6 ++++++ src/app/api/patterns/detect/route.ts | 6 ++++++ src/app/api/predict/batch/route.ts | 6 ++++++ src/app/api/predict/route.ts | 6 ++++++ src/app/api/training/runs/route.ts | 6 ++++++ src/app/api/training/start/route.ts | 6 ++++++ 9 files changed, 49 insertions(+), 1 deletion(-) diff --git a/openspec/changes/user-accounts/tasks.md b/openspec/changes/user-accounts/tasks.md index 740d6cb..7aa6b41 100644 --- a/openspec/changes/user-accounts/tasks.md +++ b/openspec/changes/user-accounts/tasks.md @@ -39,7 +39,7 @@ - [x] 7.1 `[sonnet]` Add `getAuthUser()` check to all data API routes: `/api/upload`, `/api/candles`, `/api/charts`, `/api/annotations`, `/api/annotation-types`, `/api/span-annotations`, `/api/span-label-types`, `/api/export` - [x] 7.2 `[opus]` Update all Drizzle queries to filter by `user_id` from authenticated session (SELECT, INSERT, DELETE) -- [ ] 7.3 `[sonnet]` Add `getAuthUser()` check to all proxy API routes: `/api/predict`, `/api/predict/batch`, `/api/model/info`, `/api/model/load`, `/api/patterns/detect`, `/api/patterns/available`, `/api/training/start`, `/api/training/runs` +- [x] 7.3 `[sonnet]` Add `getAuthUser()` check to all proxy API routes: `/api/predict`, `/api/predict/batch`, `/api/model/info`, `/api/model/load`, `/api/patterns/detect`, `/api/patterns/available`, `/api/training/start`, `/api/training/runs` - [ ] 7.4 `[haiku]` Add `X-User-ID` header to all fetch calls from proxy routes to the FastAPI ML service ## 8. Frontend Routing Restructure diff --git a/src/app/api/model/info/route.ts b/src/app/api/model/info/route.ts index 756a064..486af44 100644 --- a/src/app/api/model/info/route.ts +++ b/src/app/api/model/info/route.ts @@ -1,9 +1,15 @@ import { NextRequest, NextResponse } from 'next/server'; +import { getAuthUser } from '@/lib/auth'; const INFERENCE_API_URL = process.env.INFERENCE_API_URL || 'http://localhost:8001'; const INFERENCE_API_TIMEOUT = parseInt(process.env.INFERENCE_API_TIMEOUT || '10000', 10); export async function GET(request: NextRequest) { + const user = await getAuthUser(); + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + try { // Forward request to Python inference service const controller = new AbortController(); diff --git a/src/app/api/model/load/route.ts b/src/app/api/model/load/route.ts index 1293409..bf0d846 100644 --- a/src/app/api/model/load/route.ts +++ b/src/app/api/model/load/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; +import { getAuthUser } from '@/lib/auth'; const INFERENCE_API_URL = process.env.INFERENCE_API_URL || 'http://localhost:8001'; const INFERENCE_API_TIMEOUT = parseInt(process.env.INFERENCE_API_TIMEOUT || '30000', 10); @@ -9,6 +10,11 @@ const ModelLoadRequestSchema = z.object({ }); export async function POST(request: NextRequest) { + const user = await getAuthUser(); + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), INFERENCE_API_TIMEOUT); diff --git a/src/app/api/patterns/available/route.ts b/src/app/api/patterns/available/route.ts index 4f95a4d..9b53d79 100644 --- a/src/app/api/patterns/available/route.ts +++ b/src/app/api/patterns/available/route.ts @@ -1,9 +1,15 @@ import { NextRequest, NextResponse } from 'next/server'; +import { getAuthUser } from '@/lib/auth'; const INFERENCE_API_URL = process.env.INFERENCE_API_URL || 'http://localhost:8001'; const INFERENCE_API_TIMEOUT = parseInt(process.env.INFERENCE_API_TIMEOUT || '10000', 10); export async function GET(_request: NextRequest) { + const user = await getAuthUser(); + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), INFERENCE_API_TIMEOUT); diff --git a/src/app/api/patterns/detect/route.ts b/src/app/api/patterns/detect/route.ts index cd4f5aa..0014dfe 100644 --- a/src/app/api/patterns/detect/route.ts +++ b/src/app/api/patterns/detect/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; +import { getAuthUser } from '@/lib/auth'; const INFERENCE_API_URL = process.env.INFERENCE_API_URL || 'http://localhost:8001'; // Pattern detection may take longer on large datasets @@ -20,6 +21,11 @@ const PatternDetectRequestSchema = z.object({ }); export async function POST(request: NextRequest) { + const user = await getAuthUser(); + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), INFERENCE_API_TIMEOUT); diff --git a/src/app/api/predict/batch/route.ts b/src/app/api/predict/batch/route.ts index 556b9b9..1ef3c78 100644 --- a/src/app/api/predict/batch/route.ts +++ b/src/app/api/predict/batch/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; +import { getAuthUser } from '@/lib/auth'; const INFERENCE_API_URL = process.env.INFERENCE_API_URL || 'http://localhost:8001'; const INFERENCE_BATCH_TIMEOUT = parseInt(process.env.INFERENCE_BATCH_TIMEOUT || '120000', 10); @@ -12,6 +13,11 @@ const BatchPredictRequestSchema = z.object({ }); export async function POST(request: NextRequest) { + const user = await getAuthUser(); + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + try { const body = await request.json(); diff --git a/src/app/api/predict/route.ts b/src/app/api/predict/route.ts index 0c72985..c4cd8ce 100644 --- a/src/app/api/predict/route.ts +++ b/src/app/api/predict/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; +import { getAuthUser } from '@/lib/auth'; const INFERENCE_API_URL = process.env.INFERENCE_API_URL || 'http://localhost:8001'; const INFERENCE_API_TIMEOUT = parseInt(process.env.INFERENCE_API_TIMEOUT || '30000', 10); @@ -20,6 +21,11 @@ const PredictRequestSchema = z.object({ }); export async function POST(request: NextRequest) { + const user = await getAuthUser(); + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + try { const body = await request.json(); diff --git a/src/app/api/training/runs/route.ts b/src/app/api/training/runs/route.ts index 42ab14a..887abea 100644 --- a/src/app/api/training/runs/route.ts +++ b/src/app/api/training/runs/route.ts @@ -1,9 +1,15 @@ import { NextRequest, NextResponse } from 'next/server'; +import { getAuthUser } from '@/lib/auth'; const INFERENCE_API_URL = process.env.INFERENCE_API_URL || 'http://localhost:8001'; const INFERENCE_API_TIMEOUT = parseInt(process.env.INFERENCE_API_TIMEOUT || '10000', 10); export async function GET(_request: NextRequest) { + const user = await getAuthUser(); + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), INFERENCE_API_TIMEOUT); diff --git a/src/app/api/training/start/route.ts b/src/app/api/training/start/route.ts index 699bf20..2be2772 100644 --- a/src/app/api/training/start/route.ts +++ b/src/app/api/training/start/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; +import { getAuthUser } from '@/lib/auth'; const INFERENCE_API_URL = process.env.INFERENCE_API_URL || 'http://localhost:8001'; const INFERENCE_API_TIMEOUT = parseInt(process.env.INFERENCE_API_TIMEOUT || '10000', 10); @@ -10,6 +11,11 @@ const TrainingStartRequestSchema = z.object({ }); export async function POST(request: NextRequest) { + const user = await getAuthUser(); + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), INFERENCE_API_TIMEOUT);