fix: extract tenantId from session auth instead of request body/headers

AI routes now use requirePermission() + ctx.tenantId to get the tenant,
ensuring getModelForTenant() is always called with the correct tenant ID
so that stored API keys are used. Fixes norms/parse (was falling back to
getModel()) and analyses/structured (was trusting x-tenant-id header).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
CTO (LegalAI)
2026-04-09 14:55:20 +00:00
parent 73cd71b1f6
commit 1493b84787
2 changed files with 16 additions and 25 deletions

View File

@@ -4,19 +4,14 @@
import { type NextRequest } from 'next/server';
import { runStructuredAnalysis } from '@/lib/ai/structured-analysis';
import { AnalyseMode } from '@/types';
import { requirePermission } from '@/lib/auth/rbac';
const VALID_MODES = new Set(Object.values(AnalyseMode));
export async function POST(request: NextRequest) {
const tenantId = request.headers.get('x-tenant-id');
const userId = request.headers.get('x-user-id');
if (!tenantId || !userId) {
return Response.json(
{ error: 'Missing x-tenant-id or x-user-id header' },
{ status: 401 },
);
}
const auth = await requirePermission('analyses:create');
if ('response' in auth) return auth.response;
const { ctx } = auth;
const body = await request.json();
const {
@@ -46,8 +41,8 @@ export async function POST(request: NextRequest) {
try {
const result = await runStructuredAnalysis({
tenantId,
userId,
tenantId: ctx.tenantId,
userId: ctx.userId,
caseId,
mode,
title,

View File

@@ -1,8 +1,8 @@
// POST /api/norms/parse
// Accepts raw law text (Fließtext) or a PDF file and uses AI to parse it into structured provisions.
//
// JSON body: { text: string, tenantId?: string }
// OR multipart/form-data: file (PDF/TXT), tenantId (optional)
// JSON body: { text: string }
// OR multipart/form-data: file (PDF/TXT)
//
// Returns: {
// provisions: Array<{
@@ -13,7 +13,8 @@
// }
import { generateText } from 'ai';
import { getModelForTenant, getModel } from '@/lib/ai/providers';
import { getModelForTenant } from '@/lib/ai/providers';
import { requirePermission } from '@/lib/auth/rbac';
const PARSE_SYSTEM_PROMPT = `Du bist ein Experte fuer deutsches Recht und Gesetzestexte. Deine Aufgabe ist es, einen Fliesstext eines Gesetzes, Tarifvertrags oder einer anderen Rechtsquelle in einzelne Paragraphen zu zerlegen.
@@ -45,15 +46,17 @@ async function extractTextFromPdf(buffer: Buffer): Promise<string> {
}
export async function POST(request: Request) {
const auth = await requirePermission('norms:write');
if ('response' in auth) return auth.response;
const { ctx } = auth;
let text: string;
let tenantId: string | undefined;
const contentType = request.headers.get('content-type') ?? '';
if (contentType.includes('multipart/form-data')) {
const formData = await request.formData();
const file = formData.get('file');
tenantId = (formData.get('tenantId') as string) || undefined;
if (!file || !(file instanceof File)) {
return Response.json({ error: 'file field is required.' }, { status: 400 });
@@ -76,7 +79,7 @@ export async function POST(request: Request) {
text = new TextDecoder('utf-8').decode(buffer);
}
} else {
let body: { text?: string; tenantId?: string };
let body: { text?: string };
try {
body = await request.json();
} catch {
@@ -84,7 +87,6 @@ export async function POST(request: Request) {
}
text = body.text ?? '';
tenantId = body.tenantId;
}
if (!text || typeof text !== 'string' || text.trim().length === 0) {
@@ -102,13 +104,7 @@ export async function POST(request: Request) {
}
try {
let model;
if (tenantId) {
const result = await getModelForTenant(tenantId);
model = result.model;
} else {
model = getModel();
}
const { model } = await getModelForTenant(ctx.tenantId);
const result = await generateText({
model,