Add docs navigation and tighten API request security

This commit is contained in:
2026-10-11 18:53:46 +05:00
parent a54a3e7705
commit 3cc3136762
28 changed files with 915 additions and 29 deletions

View File

@@ -14,6 +14,8 @@ import {
import { mediaUrl, mediaUrls } from "./catalog";
import { ServiceError, checkServiceError } from "./service-error";
import { requestOrigin, verifyOrigin } from "./request-origin";
import { browserAccount } from "./request-security";
import { jsonBody } from "./json-body";
import { accountApi } from "./account-api";
import { multiplayerApi } from "./multiplayer-api";
import { bugReportApi } from "./bug-report-api";
@@ -45,10 +47,11 @@ const uuid = (value: string) => idSchema.parse(value);
function cookieSecret(request: NextRequest) {
return request.cookies.get("mg_guest")?.value ?? "";
}
async function contributor(request: NextRequest) {
async function contributor(request: NextRequest, browserOnly = false) {
const token = request.headers.get("authorization")?.replace(/^Bearer /, "");
let owner: string | undefined;
if (token) {
if (browserOnly) owner = (await browserAccount(request, user)).id;
else if (token) {
const row = required(
await admin()
.from("contributor_tokens")
@@ -67,7 +70,7 @@ async function contributor(request: NextRequest) {
return owner;
}
async function moderator(request: NextRequest, adminOnly = false) {
const owner = await contributor(request);
const owner = await contributor(request, true);
const profile = required(
await admin().from("profiles").select("role").eq("id", owner).single(),
);
@@ -112,23 +115,6 @@ async function session(request: NextRequest, id: string) {
if (account && record.owner_id === account.id) return record;
throw new Error("Session not found");
}
async function jsonBody(request: NextRequest) {
const reader = request.body?.getReader();
if (!reader) throw new Error("JSON request required");
const chunks: Uint8Array[] = [];
let size = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
size += value.byteLength;
if (size > 2 * 1024 * 1024) {
await reader.cancel();
throw new Error("Request too large");
}
chunks.push(value);
}
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
}
export async function handle(request: NextRequest, path: string[]) {
try {
verifyOrigin(request);
@@ -802,7 +788,7 @@ export async function handle(request: NextRequest, path: string[]) {
}
}
if (path[0] === "tokens") {
const owner = await contributor(request);
const owner = await contributor(request, true);
if (method === "GET")
return NextResponse.json({
tokens: required(
@@ -871,7 +857,7 @@ export async function handle(request: NextRequest, path: string[]) {
});
}
if (method === "POST" && path[1] === "approve") {
const owner = await contributor(request);
const owner = await contributor(request, true);
const code = z
.string()
.regex(/^[A-F0-9]{8}$/)