Add getAuthUser() auth guard to all data API routes (task 7.1)
Add 401 Unauthorized check at the top of every handler in: - /api/upload (POST) - /api/candles (GET) - /api/charts (GET) and /api/charts/[id] (GET, DELETE) - /api/annotations (GET, POST, DELETE) and /api/annotations/[id] (PATCH, DELETE) - /api/annotation-types (GET, POST, DELETE) and /api/annotation-types/[id] (PATCH) - /api/span-annotations (GET, POST, DELETE), /[id] (PATCH, DELETE), /export (GET) - /api/span-label-types (GET, POST) and /[id] (PATCH, DELETE) - /api/export (GET) and /api/export/spans (GET) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
aa2c5fdb69
commit
9901d0f3f1
16 changed files with 146 additions and 1 deletions
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
## 7. Update Existing API Routes
|
||||
|
||||
- [ ] 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.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`
|
||||
- [ ] 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`
|
||||
- [ ] 7.4 `[haiku]` Add `X-User-ID` header to all fetch calls from proxy routes to the FastAPI ML service
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { annotationTypes } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
type RouteContext = {
|
||||
params: Promise<{ id: string }>;
|
||||
|
|
@ -12,6 +13,11 @@ export async function PATCH(
|
|||
request: NextRequest,
|
||||
context: RouteContext
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const idNum = parseInt(id, 10);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { annotationTypes, annotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// GET - List all annotation types
|
||||
export async function GET() {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const types = await db.select().from(annotationTypes);
|
||||
return NextResponse.json(types);
|
||||
|
|
@ -19,6 +25,11 @@ export async function GET() {
|
|||
|
||||
// POST - Create new annotation type or seed defaults
|
||||
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();
|
||||
|
||||
|
|
@ -103,6 +114,11 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
// DELETE - Delete annotation type (only if no annotations use it)
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
const id = searchParams.get('id');
|
||||
|
|
|
|||
|
|
@ -2,11 +2,17 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { annotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { id: idParam } = await params;
|
||||
const id = parseInt(idParam, 10);
|
||||
|
|
@ -55,6 +61,11 @@ export async function DELETE(
|
|||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { id: idParam } = await params;
|
||||
const id = parseInt(idParam, 10);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { annotations, charts } from '@/lib/db/schema';
|
||||
import { eq, inArray, and, desc } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// GET annotations scoped by chartId
|
||||
export async function GET(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
let chartId = searchParams.get('chartId');
|
||||
|
|
@ -48,6 +54,11 @@ export async function GET(request: NextRequest) {
|
|||
|
||||
// POST create new annotation (requires chart_id)
|
||||
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();
|
||||
const { timestamp, label_type, chart_id, geometry, color } = body;
|
||||
|
|
@ -101,6 +112,11 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
// DELETE annotations with bulk operations (scoped by chartId)
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
const type = searchParams.get('type');
|
||||
|
|
|
|||
|
|
@ -2,8 +2,14 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { candles, charts } from '@/lib/db/schema';
|
||||
import { asc, desc, eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
let chartId = searchParams.get('chartId');
|
||||
|
|
|
|||
|
|
@ -2,11 +2,17 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { charts, candles, annotations, spanAnnotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const chartId = parseInt(id, 10);
|
||||
|
||||
|
|
@ -27,6 +33,11 @@ export async function DELETE(
|
|||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const { id } = await params;
|
||||
const chartId = parseInt(id, 10);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,14 @@ import { NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { charts } from '@/lib/db/schema';
|
||||
import { desc } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
export async function GET() {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const allCharts = await db.select().from(charts).orderBy(desc(charts.created_at));
|
||||
return NextResponse.json(allCharts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { annotations, candles, charts } from '@/lib/db/schema';
|
||||
import { eq, and, desc } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
function sanitizeCsvCell(value: string): string {
|
||||
if (/^[=+@\-]/.test(value)) {
|
||||
|
|
@ -11,6 +12,11 @@ function sanitizeCsvCell(value: string): string {
|
|||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
let chartId = searchParams.get('chartId');
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { spanAnnotations, candles } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
function toUnix(d: Date): number {
|
||||
return Math.floor(d.getTime() / 1000);
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const chartId = searchParams.get('chartId');
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { spanAnnotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// PATCH - Update span annotation
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { id } = await params;
|
||||
const idNum = parseInt(id, 10);
|
||||
|
|
@ -72,6 +78,11 @@ export async function DELETE(
|
|||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { id } = await params;
|
||||
const idNum = parseInt(id, 10);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { spanAnnotations, charts } from '@/lib/db/schema';
|
||||
import { eq, desc } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
function sanitizeCsvCell(value: string): string {
|
||||
if (/^[=+@\-]/.test(value)) {
|
||||
|
|
@ -37,6 +38,11 @@ function sanitizeCsvCell(value: string): string {
|
|||
* }
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
let chartId = searchParams.get('chartId');
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { spanAnnotations } from '@/lib/db/schema';
|
||||
import { eq, desc, and } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// GET - List all span annotations for a chart
|
||||
export async function GET(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
const chartId = searchParams.get('chartId');
|
||||
|
|
@ -49,6 +55,11 @@ export async function GET(request: NextRequest) {
|
|||
|
||||
// POST - Create new span annotation
|
||||
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();
|
||||
const {
|
||||
|
|
@ -111,6 +122,11 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
// DELETE - Bulk delete span annotations by chartId + optional source/label filters
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
const chartId = searchParams.get('chartId');
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { spanLabelTypes, spanAnnotations } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// PATCH - Update span label type
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { id } = await params;
|
||||
const idNum = parseInt(id, 10);
|
||||
|
|
@ -75,6 +81,11 @@ export async function DELETE(
|
|||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { id } = await params;
|
||||
const idNum = parseInt(id, 10);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||
import { db } from '@/lib/db';
|
||||
import { spanLabelTypes } from '@/lib/db/schema';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
// GET - List all span label types (active only, sorted by sort_order)
|
||||
export async function GET() {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const types = await db
|
||||
.select()
|
||||
|
|
@ -24,6 +30,11 @@ export async function GET() {
|
|||
|
||||
// POST - Create new span label type
|
||||
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();
|
||||
const { name, display_name, color, hotkey, sort_order } = body;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Papa from 'papaparse';
|
|||
import { db } from '@/lib/db';
|
||||
import { candles, charts } from '@/lib/db/schema';
|
||||
import { eq, like } from 'drizzle-orm';
|
||||
import { getAuthUser } from '@/lib/auth';
|
||||
|
||||
async function getUniqueChartName(baseName: string): Promise<string> {
|
||||
// Check if the base name is already taken
|
||||
|
|
@ -25,6 +26,11 @@ async function getUniqueChartName(baseName: string): Promise<string> {
|
|||
}
|
||||
|
||||
export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||
const user = await getAuthUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const file = formData.get('file') as File;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue