Harden CMS security and theme contrast

This commit is contained in:
Simo committed 2026-07-11 20:27:20 +02:00
1 parent 2465ff2170
commit 4a1e1115b3
57 files changed
+1023 -231

No files matched your search

+6 -5
View File
@@ -2,18 +2,19 @@
//
// Reads the NextAuth session, then re-queries prisma.user by the session id to
// return a safe field set (never password / auth_ticket / 2FA secrets / pincode
// / mail). Returns { user: null } when unauthenticated or on DB failure.
// / mail). Returns { user: null } only when unauthenticated or missing.
import { apiJson } from "@/lib/api";
import { apiJson, apiUnavailable } from "@/lib/api";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { sessionUserId } from "@/lib/auth/session-user";
export const dynamic = "force-dynamic";
export async function GET(_req: Request) {
const session = await auth();
const id = session?.user?.id ? Number(session.user.id) : null;
if (!id || Number.isNaN(id)) {
const id = sessionUserId(session?.user?.id);
if (!id) {
return apiJson({ user: null });
}
@@ -55,6 +56,6 @@ export async function GET(_req: Request) {
},
});
} catch {
return apiJson({ user: null });
return apiUnavailable("Account data is temporarily unavailable");
}
}
+10 -8
View File
@@ -8,16 +8,17 @@
// to the user via personal_access_tokens.tokenable_id (a BigInt). NOTE: the live
// table has no expires_at column, so it is never read or written here.
import { apiError, apiJson } from "@/lib/api";
import { apiError, apiJson, apiUnavailable, positiveBigInt } from "@/lib/api";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { sessionUserId } from "@/lib/auth/session-user";
import { personalTokenScope } from "@/lib/auth/personal-token-scope";
export const dynamic = "force-dynamic";
async function currentUserId(): Promise<number | null> {
const session = await auth();
const id = session?.user?.id ? Number(session.user.id) : null;
return id && !Number.isNaN(id) ? id : null;
return sessionUserId(session?.user?.id);
}
export async function GET(_req: Request) {
@@ -26,14 +27,14 @@ export async function GET(_req: Request) {
try {
const tokens = await prisma.personalAccessTokens.findMany({
where: { tokenableId: BigInt(id) },
where: personalTokenScope(id),
select: { id: true, name: true, lastUsedAt: true },
orderBy: { id: "desc" },
});
// Never expose the token hash.
return apiJson({ data: tokens });
} catch {
return apiJson({ data: [] });
return apiUnavailable("Token data is temporarily unavailable");
}
}
@@ -42,20 +43,21 @@ export async function DELETE(req: Request) {
if (!id) return apiError("Unauthorized", 401);
const tokenId = new URL(req.url).searchParams.get("id");
if (!tokenId || !/^\d+$/.test(tokenId)) {
const parsedTokenId = positiveBigInt(tokenId);
if (!parsedTokenId) {
return apiError("A valid token id is required", 422);
}
try {
// Scope the delete to the owner so users cannot revoke others' tokens.
const result = await prisma.personalAccessTokens.deleteMany({
where: { id: BigInt(tokenId), tokenableId: BigInt(id) },
where: { id: parsedTokenId, ...personalTokenScope(id) },
});
if (result.count === 0) {
return apiError("Token not found", 404);
}
return apiJson({ ok: true });
} catch {
return apiError("Could not revoke token", 400);
return apiUnavailable("Could not revoke token");
}
}