style: format code with prettier
Local Build and Deploy / deploy (push) Successful in 49s

This commit is contained in:
openhands committed 2026-07-12 21:07:34 +02:00
1 parent e85e4d74ea
commit 2e4ed76121
378 files changed
+22404 -20686

No files matched your search

+14 -3
View File
@@ -9,7 +9,10 @@ export interface AuthorizationEvent {
error?: unknown;
}
const clean = (value: string) => value.replace(/(token|password|secret|cookie|authorization|select|insert|update|delete)[^\s]*/gi, "[REDACTED]").slice(0, 160);
const clean = (value: string) =>
value
.replace(/(token|password|secret|cookie|authorization|select|insert|update|delete)[^\s]*/gi, "[REDACTED]")
.slice(0, 160);
export function authorizationActivity(event: AuthorizationEvent) {
const description = [
@@ -18,6 +21,14 @@ export function authorizationActivity(event: AuthorizationEvent) {
event.permission ? `permission=${clean(event.permission)}` : null,
`source=${clean(event.source)}`,
`reason=${clean(event.reason)}`,
].filter(Boolean).join("; ");
return { staffId: event.userId, action: event.kind, description, targetType: "user", targetId: event.userId };
]
.filter(Boolean)
.join("; ");
return {
staffId: event.userId,
action: event.kind,
description,
targetType: "user",
targetId: event.userId,
};
}
+16 -2
View File
@@ -3,14 +3,28 @@ import { authorizationActivity } from "@/lib/admin/authorization-event";
describe("authorizationActivity", () => {
it("creates a safe rank-aware denial record", () => {
const record = authorizationActivity({ kind: "permission.denied", userId: 42, username: "admin", rank: 11, permission: "admin.logs.view", source: "/admin/logs", reason: "missing permission" });
const record = authorizationActivity({
kind: "permission.denied",
userId: 42,
username: "admin",
rank: 11,
permission: "admin.logs.view",
source: "/admin/logs",
reason: "missing permission",
});
expect(record.action).toBe("permission.denied");
expect(record.description).toContain("rank=11");
expect(record.description).toContain("permission=admin.logs.view");
});
it("redacts secrets and technical details", () => {
const record = authorizationActivity({ kind: "permission.load_error", userId: 42, rank: 11, source: "permissions", reason: "token=abc password=hunter2 SELECT * FROM users" });
const record = authorizationActivity({
kind: "permission.load_error",
userId: 42,
rank: 11,
source: "permissions",
reason: "token=abc password=hunter2 SELECT * FROM users",
});
expect(record.description).not.toMatch(/abc|hunter2|SELECT/i);
});
});
+12 -2
View File
@@ -4,6 +4,16 @@ import { authorizationActivity, type AuthorizationEvent } from "@/lib/admin/auth
export async function logAuthorizationEvent(event: AuthorizationEvent): Promise<void> {
const correlationId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
await logStaffActivity({ ...authorizationActivity(event), description: `${authorizationActivity(event).description}; correlation=${correlationId}` });
if (event.error) logServerError(event.kind, event.error, { correlationId, userId: event.userId, rank: event.rank, permission: event.permission ?? null, source: event.source });
await logStaffActivity({
...authorizationActivity(event),
description: `${authorizationActivity(event).description}; correlation=${correlationId}`,
});
if (event.error)
logServerError(event.kind, event.error, {
correlationId,
userId: event.userId,
rank: event.rank,
permission: event.permission ?? null,
source: event.source,
});
}
+27 -5
View File
@@ -2,7 +2,11 @@ import { describe, expect, it } from "vitest";
import { decideAuthorization, isDynamicSuperAdmin } from "@/lib/admin/authorization-policy";
describe("isDynamicSuperAdmin", () => {
it.each([[7, 7], [11, 11], [2000, 2000]])("accepts highest rank %i", (rank, highest) => {
it.each([
[7, 7],
[11, 11],
[2000, 2000],
])("accepts highest rank %i", (rank, highest) => {
expect(isDynamicSuperAdmin(rank, highest)).toBe(true);
});
it("demotes the previous highest rank", () => expect(isDynamicSuperAdmin(2000, 2001)).toBe(false));
@@ -11,8 +15,26 @@ describe("isDynamicSuperAdmin", () => {
describe("decideAuthorization", () => {
const actor = { id: 1, username: "admin", rank: 11 };
it("allows the dynamically highest rank", () => expect(decideAuthorization({ actor, highestRank: 11, permission: "admin.any", hasPermission: false }).allowed).toBe(true));
it("allows explicit ACL permission below highest", () => expect(decideAuthorization({ actor, highestRank: 12, permission: "admin.news.view", hasPermission: true }).allowed).toBe(true));
it("denies invalid ranks", () => expect(decideAuthorization({ actor: { ...actor, rank: 0 }, highestRank: 11, permission: "admin.any", hasPermission: true })).toMatchObject({ allowed: false, reason: "invalid_rank" }));
it("denies missing permission", () => expect(decideAuthorization({ actor, highestRank: 12, permission: "admin.any", hasPermission: false })).toMatchObject({ allowed: false, reason: "permission_denied" }));
it("allows the dynamically highest rank", () =>
expect(
decideAuthorization({ actor, highestRank: 11, permission: "admin.any", hasPermission: false }).allowed,
).toBe(true));
it("allows explicit ACL permission below highest", () =>
expect(
decideAuthorization({ actor, highestRank: 12, permission: "admin.news.view", hasPermission: true })
.allowed,
).toBe(true));
it("denies invalid ranks", () =>
expect(
decideAuthorization({
actor: { ...actor, rank: 0 },
highestRank: 11,
permission: "admin.any",
hasPermission: true,
}),
).toMatchObject({ allowed: false, reason: "invalid_rank" }));
it("denies missing permission", () =>
expect(
decideAuthorization({ actor, highestRank: 12, permission: "admin.any", hasPermission: false }),
).toMatchObject({ allowed: false, reason: "permission_denied" }));
});
+8 -4
View File
@@ -1,8 +1,11 @@
export interface AuthorizationActor { id: number; username: string; rank: number }
export interface AuthorizationActor {
id: number;
username: string;
rank: number;
}
export type AuthorizationDenialReason = "invalid_rank" | "permission_denied" | "no_ranks";
export type AuthorizationDecision =
| { allowed: true; superAdmin: boolean }
| { allowed: false; reason: AuthorizationDenialReason };
{ allowed: true; superAdmin: boolean } | { allowed: false; reason: AuthorizationDenialReason };
export function isDynamicSuperAdmin(rank: number, highestRank: number | null): boolean {
return Number.isInteger(rank) && rank > 0 && highestRank !== null && rank === highestRank;
@@ -14,7 +17,8 @@ export function decideAuthorization(input: {
permission?: string;
hasPermission: boolean;
}): AuthorizationDecision {
if (!Number.isInteger(input.actor.rank) || input.actor.rank <= 0) return { allowed: false, reason: "invalid_rank" };
if (!Number.isInteger(input.actor.rank) || input.actor.rank <= 0)
return { allowed: false, reason: "invalid_rank" };
if (input.highestRank === null) return { allowed: false, reason: "no_ranks" };
if (isDynamicSuperAdmin(input.actor.rank, input.highestRank)) return { allowed: true, superAdmin: true };
if (!input.permission || input.hasPermission) return { allowed: true, superAdmin: false };
+3 -1
View File
@@ -3,7 +3,9 @@ import { buildStaffActivityWhere } from "@/lib/admin/log-filters";
describe("buildStaffActivityWhere", () => {
it("filters authorization events by prefix", () => {
expect(buildStaffActivityWhere({ authorizationOnly: true })).toEqual({ action: { startsWith: "permission." } });
expect(buildStaffActivityWhere({ authorizationOnly: true })).toEqual({
action: { startsWith: "permission." },
});
});
it("combines staff and search filters", () => {
const result = buildStaffActivityWhere({ q: "rank", staffId: 11, authorizationOnly: true });
+12 -6
View File
@@ -1,14 +1,20 @@
import type { Prisma } from "@/generated/prisma/client";
export interface StaffActivityFilters { q?: string; staffId?: number | null; action?: string | null; authorizationOnly?: boolean }
export interface StaffActivityFilters {
q?: string;
staffId?: number | null;
action?: string | null;
authorizationOnly?: boolean;
}
export function buildStaffActivityWhere(filters: StaffActivityFilters): Prisma.StaffActivitiesWhereInput {
const where: Prisma.StaffActivitiesWhereInput = {};
if (filters.q?.trim()) where.OR = [
{ action: { contains: filters.q.trim() } },
{ description: { contains: filters.q.trim() } },
{ ipAddress: { contains: filters.q.trim() } },
];
if (filters.q?.trim())
where.OR = [
{ action: { contains: filters.q.trim() } },
{ description: { contains: filters.q.trim() } },
{ ipAddress: { contains: filters.q.trim() } },
];
if (filters.staffId) where.userId = BigInt(filters.staffId);
if (filters.authorizationOnly) where.action = { startsWith: "permission." };
else if (filters.action) where.action = { contains: filters.action };
+1 -4
View File
@@ -4,10 +4,7 @@ export interface AdminMutationNotice {
message: string;
}
export function adminMutationNotice(params: {
saved?: string;
error?: string;
}): AdminMutationNotice | null {
export function adminMutationNotice(params: { saved?: string; error?: string }): AdminMutationNotice | null {
if (params.error) {
return {
tone: "danger",
+10 -3
View File
@@ -10,8 +10,15 @@ function db(user: { id: number; username: string; rank: number } | null, highest
describe("resolveAuthorizationState", () => {
it("uses current database rank and highest rank 2000", async () => {
await expect(resolveAuthorizationState(7, db({ id: 7, username: "root", rank: 2000 }, 2000))).resolves.toEqual({ actor: { id: 7, username: "root", rank: 2000 }, highestRank: 2000 });
await expect(
resolveAuthorizationState(7, db({ id: 7, username: "root", rank: 2000 }, 2000)),
).resolves.toEqual({ actor: { id: 7, username: "root", rank: 2000 }, highestRank: 2000 });
});
it("returns null for a deleted user", async () => expect(resolveAuthorizationState(7, db(null, 2000))).resolves.toBeNull());
it("fails closed when there are no ranks", async () => expect(resolveAuthorizationState(7, db({ id: 7, username: "root", rank: 1 }, null))).resolves.toEqual({ actor: { id: 7, username: "root", rank: 1 }, highestRank: null }));
it("returns null for a deleted user", async () =>
expect(resolveAuthorizationState(7, db(null, 2000))).resolves.toBeNull());
it("fails closed when there are no ranks", async () =>
expect(resolveAuthorizationState(7, db({ id: 7, username: "root", rank: 1 }, null))).resolves.toEqual({
actor: { id: 7, username: "root", rank: 1 },
highestRank: null,
}));
});
+10 -2
View File
@@ -1,11 +1,19 @@
import type { AuthorizationActor } from "@/lib/admin/authorization-policy";
export interface RankAuthorityDb {
user: { findUnique(args: { where: { id: number }; select: { id: true; username: true; rank: true } }): Promise<{ id: number; username: string; rank: number } | null> };
user: {
findUnique(args: {
where: { id: number };
select: { id: true; username: true; rank: true };
}): Promise<{ id: number; username: string; rank: number } | null>;
};
highestRank(): Promise<number | null>;
}
export async function resolveAuthorizationState(userId: number, db: RankAuthorityDb): Promise<{ actor: AuthorizationActor; highestRank: number | null } | null> {
export async function resolveAuthorizationState(
userId: number,
db: RankAuthorityDb,
): Promise<{ actor: AuthorizationActor; highestRank: number | null } | null> {
const [user, highestRank] = await Promise.all([
db.user.findUnique({ where: { id: userId }, select: { id: true, username: true, rank: true } }),
db.highestRank(),