fix user permissions + improve notification system
This commit is contained in:
@@ -14,6 +14,25 @@ import {
|
||||
import { logAudit, AuditAction } from "../../auditLog";
|
||||
import * as clientManager from "../../clientManager";
|
||||
import { setOnlineState } from "../../db";
|
||||
import {
|
||||
canUserAccessClient,
|
||||
getUserClientAccessScope,
|
||||
listUserClientRuleIdsByAccess,
|
||||
} from "../../users";
|
||||
|
||||
function getClientScopeFilters(userId: number, role: string): { allowedClientIds?: string[]; deniedClientIds?: string[] } {
|
||||
if (role === "admin") return {};
|
||||
const scope = getUserClientAccessScope(userId);
|
||||
if (scope === "none") return { allowedClientIds: [] };
|
||||
if (scope === "all") return {};
|
||||
if (scope === "allowlist") {
|
||||
return { allowedClientIds: listUserClientRuleIdsByAccess(userId, "allow") };
|
||||
}
|
||||
if (scope === "denylist") {
|
||||
return { deniedClientIds: listUserClientRuleIdsByAccess(userId, "deny") };
|
||||
}
|
||||
return { allowedClientIds: [] };
|
||||
}
|
||||
|
||||
let _postApproveHook: ((clientId: string) => void) | undefined;
|
||||
|
||||
@@ -31,7 +50,8 @@ export async function handleEnrollmentRoutes(
|
||||
if (!user) return new Response("Unauthorized", { status: 401 });
|
||||
if (user.role === "viewer") return new Response("Forbidden", { status: 403 });
|
||||
|
||||
const clients = getPendingClients();
|
||||
const scopeFilters = getClientScopeFilters(user.userId, user.role);
|
||||
const clients = getPendingClients(scopeFilters);
|
||||
return Response.json({ items: clients });
|
||||
}
|
||||
|
||||
@@ -40,7 +60,8 @@ export async function handleEnrollmentRoutes(
|
||||
const user = await authenticateRequest(req);
|
||||
if (!user) return new Response("Unauthorized", { status: 401 });
|
||||
|
||||
const stats = getEnrollmentStats();
|
||||
const scopeFilters = getClientScopeFilters(user.userId, user.role);
|
||||
const stats = getEnrollmentStats(scopeFilters);
|
||||
return Response.json(stats);
|
||||
}
|
||||
|
||||
@@ -66,6 +87,10 @@ export async function handleEnrollmentRoutes(
|
||||
const current = getClientEnrollmentStatus(clientId);
|
||||
if (!current) return Response.json({ error: "Client not found" }, { status: 404 });
|
||||
|
||||
if (!canUserAccessClient(user.userId, user.role, clientId)) {
|
||||
return new Response("Forbidden: Client access denied", { status: 403 });
|
||||
}
|
||||
|
||||
setClientEnrollmentStatus(clientId, "approved", user.username);
|
||||
|
||||
_postApproveHook?.(clientId);
|
||||
@@ -94,6 +119,10 @@ export async function handleEnrollmentRoutes(
|
||||
const current = getClientEnrollmentStatus(clientId);
|
||||
if (!current) return Response.json({ error: "Client not found" }, { status: 404 });
|
||||
|
||||
if (!canUserAccessClient(user.userId, user.role, clientId)) {
|
||||
return new Response("Forbidden: Client access denied", { status: 403 });
|
||||
}
|
||||
|
||||
setClientEnrollmentStatus(clientId, "denied");
|
||||
|
||||
logAudit({
|
||||
@@ -120,6 +149,10 @@ export async function handleEnrollmentRoutes(
|
||||
const current = getClientEnrollmentStatus(clientId);
|
||||
if (!current) return Response.json({ error: "Client not found" }, { status: 404 });
|
||||
|
||||
if (!canUserAccessClient(user.userId, user.role, clientId)) {
|
||||
return new Response("Forbidden: Client access denied", { status: 403 });
|
||||
}
|
||||
|
||||
setClientEnrollmentStatus(clientId, "pending");
|
||||
|
||||
return Response.json({ ok: true, status: "pending" });
|
||||
@@ -151,6 +184,7 @@ export async function handleEnrollmentRoutes(
|
||||
if (action === "ban-ip") {
|
||||
let banned = 0;
|
||||
for (const id of ids) {
|
||||
if (!canUserAccessClient(user.userId, user.role, id)) continue;
|
||||
const clientIp = getClientIp(id);
|
||||
if (!clientIp) continue;
|
||||
banIp(clientIp, `Bulk banned from purgatory by ${user.username}`);
|
||||
@@ -178,6 +212,7 @@ export async function handleEnrollmentRoutes(
|
||||
const status = action === "approve" ? "approved" : action === "deny" ? "denied" : "pending";
|
||||
let updated = 0;
|
||||
for (const id of ids) {
|
||||
if (!canUserAccessClient(user.userId, user.role, id)) continue;
|
||||
const ok = setClientEnrollmentStatus(
|
||||
id,
|
||||
status as "approved" | "denied" | "pending",
|
||||
@@ -215,6 +250,10 @@ export async function handleEnrollmentRoutes(
|
||||
const targetIp = getClientIp(clientId);
|
||||
if (!targetIp) return Response.json({ error: "Client IP not found" }, { status: 404 });
|
||||
|
||||
if (!canUserAccessClient(user.userId, user.role, clientId)) {
|
||||
return new Response("Forbidden: Client access denied", { status: 403 });
|
||||
}
|
||||
|
||||
banIp(targetIp, `Banned from purgatory by ${user.username} for client ${clientId}`);
|
||||
setClientEnrollmentStatus(clientId, "denied");
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getConfig, updateSecurityConfig, updateTlsConfig, updateAppearanceConfi
|
||||
import { getClientMetricsSummary } from "../../db";
|
||||
import { metrics } from "../../metrics";
|
||||
import { requirePermission } from "../../rbac";
|
||||
import { getUserTelegramChatId, setUserTelegramChatId } from "../../users";
|
||||
import { getUserTelegramChatId, setUserTelegramChatId, getUserClientAccessScope, listUserClientRuleIdsByAccess } from "../../users";
|
||||
import { runCertbotSetup } from "../certbot-setup";
|
||||
import {
|
||||
getActiveProxies,
|
||||
@@ -311,6 +311,20 @@ export async function handleMiscRoutes(
|
||||
const endDate = Number(url.searchParams.get("endDate") || 0) || undefined;
|
||||
const successOnly = url.searchParams.get("successOnly") === "true";
|
||||
|
||||
let allowedClientIds: string[] | undefined;
|
||||
let deniedClientIds: string[] | undefined;
|
||||
if (user.role !== "admin") {
|
||||
const scope = getUserClientAccessScope(user.userId);
|
||||
if (scope === "none") {
|
||||
return Response.json({ logs: [], total: 0, page, pageSize }, { headers: deps.CORS_HEADERS });
|
||||
}
|
||||
if (scope === "allowlist") {
|
||||
allowedClientIds = listUserClientRuleIdsByAccess(user.userId, "allow");
|
||||
} else if (scope === "denylist") {
|
||||
deniedClientIds = listUserClientRuleIdsByAccess(user.userId, "deny");
|
||||
}
|
||||
}
|
||||
|
||||
const result = getAuditLogs({
|
||||
page,
|
||||
pageSize,
|
||||
@@ -320,6 +334,8 @@ export async function handleMiscRoutes(
|
||||
startDate,
|
||||
endDate,
|
||||
successOnly,
|
||||
allowedClientIds,
|
||||
deniedClientIds,
|
||||
});
|
||||
|
||||
return Response.json(result, { headers: deps.CORS_HEADERS });
|
||||
|
||||
@@ -224,6 +224,9 @@ export async function handleNotificationsConfigRoutes(
|
||||
telegram_bot_token: null,
|
||||
telegram_chat_id: null,
|
||||
telegram_template: null,
|
||||
client_event_webhook: 1,
|
||||
client_event_telegram: 1,
|
||||
client_event_push: 1,
|
||||
};
|
||||
return Response.json({
|
||||
settings,
|
||||
@@ -276,6 +279,15 @@ export async function handleNotificationsConfigRoutes(
|
||||
if (typeof body?.telegram_template === "string") {
|
||||
patch.telegram_template = body.telegram_template.trim() || null;
|
||||
}
|
||||
if (typeof body?.client_event_webhook === "boolean" || typeof body?.client_event_webhook === "number") {
|
||||
patch.client_event_webhook = body.client_event_webhook ? 1 : 0;
|
||||
}
|
||||
if (typeof body?.client_event_telegram === "boolean" || typeof body?.client_event_telegram === "number") {
|
||||
patch.client_event_telegram = body.client_event_telegram ? 1 : 0;
|
||||
}
|
||||
if (typeof body?.client_event_push === "boolean" || typeof body?.client_event_push === "number") {
|
||||
patch.client_event_push = body.client_event_push ? 1 : 0;
|
||||
}
|
||||
|
||||
const result = updateUserNotificationSettings(user.userId, patch);
|
||||
if (!result.success) {
|
||||
|
||||
Reference in New Issue
Block a user