mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-19 21:32:20 +03:00
/api/files, /api/files/[id]/content, /api/batches and /api/batches/[id] only gated on requireManagementAuth(request), which returns null unconditionally when settings.requireLogin===false, and never applied any per-record ownership check. On an instance with login disabled, an unauthenticated caller could enumerate/download every tenant's files and batches — the hardened /api/v1/files and /api/v1/batches siblings already scope via getApiKeyRequestScope()/resolveListScope()/ canAccessOwnedRecord() from the GHSA-2jm2-mpx8-6523 and GHSA-m3hp-hq9g-fpmv fixes. Port that exact scoping onto the 4 management routes: an API key sees only its own files/batches, a dashboard session keeps instance-wide access, and any other caller is rejected instead of falling through to an unscoped read.
This commit is contained in:
committed by
GitHub
parent
5b61937f17
commit
25d35179fd
@@ -1,15 +1,19 @@
|
||||
import { NextResponse } from "next/server";
|
||||
export const dynamic = "force-dynamic";
|
||||
import { getBatch } from "@/lib/db/batches";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { getApiKeyRequestScope, canAccessOwnedRecord } from "@/app/api/v1/_helpers/apiKeyScope";
|
||||
|
||||
export async function GET(request: Request, { params }: { params: { id: string } }) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
const scope = await getApiKeyRequestScope(request);
|
||||
if (scope.rejection) return scope.rejection;
|
||||
|
||||
try {
|
||||
const batch = getBatch(params.id);
|
||||
if (!batch) {
|
||||
// Session = operator, key = own rows only, null owner = denied. Mirrors
|
||||
// /api/v1/batches/[id] (GHSA-2jm2-mpx8-6523) so this management sibling
|
||||
// cannot leak a foreign tenant's batch metadata to an unauthenticated
|
||||
// caller (#13882).
|
||||
if (!batch || !canAccessOwnedRecord(scope, batch.apiKeyId)) {
|
||||
return NextResponse.json({ error: "Batch not found" }, { status: 404 });
|
||||
}
|
||||
return NextResponse.json({ batch });
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { NextResponse } from "next/server";
|
||||
export const dynamic = "force-dynamic";
|
||||
import { listBatches } from "@/lib/db/batches";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { getApiKeyRequestScope, resolveListScope } from "@/app/api/v1/_helpers/apiKeyScope";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
const scope = await getApiKeyRequestScope(request);
|
||||
if (scope.rejection) return scope.rejection;
|
||||
|
||||
// Key → own batches only; dashboard session without a key → instance-wide;
|
||||
// anonymous / unresolvable bearer → 401. Mirrors /api/v1/batches (GHSA-2jm2-
|
||||
// mpx8-6523 / GHSA-m3hp-hq9g-fpmv) so this management sibling cannot leak
|
||||
// every tenant's batches to an unauthenticated caller (#13882).
|
||||
const listScope = resolveListScope(scope);
|
||||
if (listScope.mode === "rejected") return listScope.response;
|
||||
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const limit = Number.parseInt(url.searchParams.get("limit") || "100", 10);
|
||||
const batches = listBatches(undefined, limit);
|
||||
const ownerFilter = listScope.mode === "api_key" ? listScope.apiKeyId : undefined;
|
||||
const batches = listBatches(ownerFilter, limit);
|
||||
return NextResponse.json({ batches });
|
||||
} catch (error) {
|
||||
console.log("Error fetching batches:", error);
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { getFile, getFileContent } from "@/lib/db/files";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { getApiKeyRequestScope, canAccessOwnedRecord } from "@/app/api/v1/_helpers/apiKeyScope";
|
||||
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
const scope = await getApiKeyRequestScope(request);
|
||||
if (scope.rejection) return scope.rejection;
|
||||
|
||||
const { id } = await params;
|
||||
const file = getFile(id);
|
||||
|
||||
if (!file) {
|
||||
// `getFileContent` has no ownership check of its own — this guard is the only
|
||||
// thing between a caller and the raw bytes. Mirrors /api/v1/files/[id]/content
|
||||
// (GHSA-2jm2-mpx8-6523) so this management sibling cannot leak a foreign
|
||||
// tenant's file content to an unauthenticated caller (#13882).
|
||||
if (!file || !canAccessOwnedRecord(scope, file.apiKeyId)) {
|
||||
return NextResponse.json(
|
||||
{ error: { message: "File not found", type: "invalid_request_error" } },
|
||||
{ status: 404 }
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { listFiles } from "@/lib/db/files";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
import { getApiKeyRequestScope, resolveListScope } from "@/app/api/v1/_helpers/apiKeyScope";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
const scope = await getApiKeyRequestScope(request);
|
||||
if (scope.rejection) return scope.rejection;
|
||||
|
||||
// Key → own files only; dashboard session without a key → instance-wide;
|
||||
// anonymous / unresolvable bearer → 401. Mirrors /api/v1/files (GHSA-2jm2-
|
||||
// mpx8-6523 / GHSA-m3hp-hq9g-fpmv) so this management sibling cannot leak
|
||||
// every tenant's files to an unauthenticated caller (#13882).
|
||||
const listScope = resolveListScope(scope);
|
||||
if (listScope.mode === "rejected") return listScope.response;
|
||||
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const limit = Number.parseInt(url.searchParams.get("limit") || "100", 10);
|
||||
const files = listFiles({ limit });
|
||||
const files =
|
||||
listScope.mode === "api_key"
|
||||
? listFiles({ limit, apiKeyId: listScope.apiKeyId })
|
||||
: listFiles({ limit });
|
||||
return NextResponse.json({ files });
|
||||
} catch (error) {
|
||||
console.log("Error fetching files:", error);
|
||||
|
||||
Reference in New Issue
Block a user