Files
caddy-proxy-manager/src/lib/models/access-lists.ts
2025-11-02 22:16:13 +01:00

234 lines
5.5 KiB
TypeScript

import bcrypt from "bcryptjs";
import prisma, { nowIso } from "../db";
import { logAuditEvent } from "../audit";
import { applyCaddyConfig } from "../caddy";
export type AccessListEntry = {
id: number;
username: string;
created_at: string;
updated_at: string;
};
export type AccessList = {
id: number;
name: string;
description: string | null;
entries: AccessListEntry[];
created_at: string;
updated_at: string;
};
export type AccessListInput = {
name: string;
description?: string | null;
users?: { username: string; password: string }[];
};
function toAccessList(
row: {
id: number;
name: string;
description: string | null;
createdAt: Date;
updatedAt: Date;
},
entries: {
id: number;
username: string;
createdAt: Date;
updatedAt: Date;
}[]
): AccessList {
return {
id: row.id,
name: row.name,
description: row.description,
entries: entries.map((entry) => ({
id: entry.id,
username: entry.username,
created_at: entry.createdAt.toISOString(),
updated_at: entry.updatedAt.toISOString()
})),
created_at: row.createdAt.toISOString(),
updated_at: row.updatedAt.toISOString()
};
}
export async function listAccessLists(): Promise<AccessList[]> {
const lists = await prisma.accessList.findMany({
orderBy: { name: "asc" },
include: {
entries: {
select: {
id: true,
username: true,
createdAt: true,
updatedAt: true
},
orderBy: { username: "asc" }
}
}
});
return lists.map((list: typeof lists[0]) => toAccessList(list, list.entries));
}
export async function getAccessList(id: number): Promise<AccessList | null> {
const list = await prisma.accessList.findUnique({
where: { id },
include: {
entries: {
select: {
id: true,
username: true,
createdAt: true,
updatedAt: true
},
orderBy: { username: "asc" }
}
}
});
return list ? toAccessList(list, list.entries) : null;
}
export async function createAccessList(input: AccessListInput, actorUserId: number) {
const now = new Date(nowIso());
const accessList = await prisma.accessList.create({
data: {
name: input.name.trim(),
description: input.description ?? null,
createdBy: actorUserId,
createdAt: now,
updatedAt: now,
entries: input.users
? {
create: input.users.map((account) => ({
username: account.username,
passwordHash: bcrypt.hashSync(account.password, 10),
createdAt: now,
updatedAt: now
}))
}
: undefined
}
});
logAuditEvent({
userId: actorUserId,
action: "create",
entityType: "access_list",
entityId: accessList.id,
summary: `Created access list ${input.name}`
});
await applyCaddyConfig();
return (await getAccessList(accessList.id))!;
}
export async function updateAccessList(
id: number,
input: { name?: string; description?: string | null },
actorUserId: number
) {
const existing = await getAccessList(id);
if (!existing) {
throw new Error("Access list not found");
}
const now = new Date(nowIso());
await prisma.accessList.update({
where: { id },
data: {
name: input.name ?? existing.name,
description: input.description ?? existing.description,
updatedAt: now
}
});
logAuditEvent({
userId: actorUserId,
action: "update",
entityType: "access_list",
entityId: id,
summary: `Updated access list ${input.name ?? existing.name}`
});
await applyCaddyConfig();
return (await getAccessList(id))!;
}
export async function addAccessListEntry(
accessListId: number,
entry: { username: string; password: string },
actorUserId: number
) {
const list = await prisma.accessList.findUnique({
where: { id: accessListId }
});
if (!list) {
throw new Error("Access list not found");
}
const now = new Date(nowIso());
const hash = bcrypt.hashSync(entry.password, 10);
await prisma.accessListEntry.create({
data: {
accessListId,
username: entry.username,
passwordHash: hash,
createdAt: now,
updatedAt: now
}
});
logAuditEvent({
userId: actorUserId,
action: "create",
entityType: "access_list_entry",
entityId: accessListId,
summary: `Added user ${entry.username} to access list ${list.name}`
});
await applyCaddyConfig();
return (await getAccessList(accessListId))!;
}
export async function removeAccessListEntry(accessListId: number, entryId: number, actorUserId: number) {
const list = await prisma.accessList.findUnique({
where: { id: accessListId }
});
if (!list) {
throw new Error("Access list not found");
}
await prisma.accessListEntry.delete({
where: { id: entryId }
});
logAuditEvent({
userId: actorUserId,
action: "delete",
entityType: "access_list_entry",
entityId: entryId,
summary: `Removed entry from access list ${list.name}`
});
await applyCaddyConfig();
return (await getAccessList(accessListId))!;
}
export async function deleteAccessList(id: number, actorUserId: number) {
const existing = await prisma.accessList.findUnique({
where: { id }
});
if (!existing) {
throw new Error("Access list not found");
}
await prisma.accessList.delete({
where: { id }
});
logAuditEvent({
userId: actorUserId,
action: "delete",
entityType: "access_list",
entityId: id,
summary: `Deleted access list ${existing.name}`
});
await applyCaddyConfig();
}