revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s
Local Build and Deploy / deploy (push) Successful in 1m11s
revert style: clean up code with prettier and eslint
This commit is contained in:
1 parent
27de078f54
commit
e85e4d74ea
378 files changed
+20710
-22428
No files matched your search
@@ -9,10 +9,7 @@ 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 = [
|
||||
@@ -21,14 +18,6 @@ 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 };
|
||||
}
|
||||
@@ -3,28 +3,14 @@ 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);
|
||||
});
|
||||
});
|
||||
@@ -4,16 +4,6 @@ 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 });
|
||||
}
|
||||
@@ -2,11 +2,7 @@ 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));
|
||||
@@ -15,26 +11,8 @@ 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" }));
|
||||
});
|
||||
@@ -1,11 +1,8 @@
|
||||
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;
|
||||
@@ -17,8 +14,7 @@ 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,9 +3,7 @@ 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 });
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
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 };
|
||||
|
||||
@@ -4,7 +4,10 @@ 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,15 +10,8 @@ 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 }));
|
||||
});
|
||||
@@ -1,19 +1,11 @@
|
||||
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(),
|
||||
|
||||
Reference in new issue
Block a user