From e057aee8bad549122cc94940aa32d76e8e3f20ae Mon Sep 17 00:00:00 2001 From: xtcnet Date: Tue, 10 Mar 2026 11:25:40 +0700 Subject: [PATCH] feat(wireguard): harden security constraints and fix db manager UI --- backend/internal/audit-log.js | 2 ++ backend/internal/wireguard.js | 34 +++++++++++++------- frontend/src/pages/DatabaseManager/index.tsx | 18 +++++------ 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/backend/internal/audit-log.js b/backend/internal/audit-log.js index 02700dc..f4a2655 100644 --- a/backend/internal/audit-log.js +++ b/backend/internal/audit-log.js @@ -17,6 +17,7 @@ const internalAuditLog = { const query = auditLogModel .query() + .andWhere("user_id", access.token.getUserId(1)) .orderBy("created_on", "DESC") .orderBy("id", "DESC") .limit(100) @@ -49,6 +50,7 @@ const internalAuditLog = { const query = auditLogModel .query() .andWhere("id", data.id) + .andWhere("user_id", access.token.getUserId(1)) .allowGraph("[user]") .first(); diff --git a/backend/internal/wireguard.js b/backend/internal/wireguard.js index b8a6fee..99ff130 100644 --- a/backend/internal/wireguard.js +++ b/backend/internal/wireguard.js @@ -213,8 +213,7 @@ const internalWireguard = { .select("wg_client.*", "wg_interface.name as interface_name") .orderBy("wg_client.created_on", "desc"); - // Filter by owner if not admin - if (access && (!accessData || accessData.permission_visibility !== "all")) { + if (access) { query.andWhere("wg_client.owner_user_id", access.token.getUserId(1)); } @@ -280,6 +279,10 @@ const internalWireguard = { const allocatedIPs = existingClients.map((c) => c.ipv4_address); const ipv4Address = wgHelpers.findNextAvailableIP(iface.ipv4_cidr, allocatedIPs); + if (!ipv4Address) { + throw new Error("No available IP addresses remaining in this WireGuard server subnet."); + } + const clientData = { name: data.name || "Unnamed Client", enabled: true, @@ -309,7 +312,7 @@ const internalWireguard = { */ async deleteClient(knex, clientId, access, accessData) { const query = knex("wg_client").where("id", clientId); - if (access && (!accessData || accessData.permission_visibility !== "all")) { + if (access) { query.andWhere("owner_user_id", access.token.getUserId(1)); } const client = await query.first(); @@ -328,7 +331,7 @@ const internalWireguard = { */ async toggleClient(knex, clientId, enabled, access, accessData) { const query = knex("wg_client").where("id", clientId); - if (access && (!accessData || accessData.permission_visibility !== "all")) { + if (access) { query.andWhere("owner_user_id", access.token.getUserId(1)); } const client = await query.first(); @@ -351,7 +354,7 @@ const internalWireguard = { */ async updateClient(knex, clientId, data, access, accessData) { const query = knex("wg_client").where("id", clientId); - if (access && (!accessData || accessData.permission_visibility !== "all")) { + if (access) { query.andWhere("owner_user_id", access.token.getUserId(1)); } const client = await query.first(); @@ -414,7 +417,17 @@ const internalWireguard = { */ async createInterface(knex, data, access, accessData) { const existingIfaces = await knex("wg_interface").select("name", "listen_port"); - const newIndex = existingIfaces.length; + + if (existingIfaces.length >= 100) { + throw new Error("Maximum limit of 100 WireGuard servers reached."); + } + + // Find the lowest available index between 0 and 99 + const usedPorts = new Set(existingIfaces.map(i => i.listen_port)); + let newIndex = 0; + while (usedPorts.has(51820 + newIndex)) { + newIndex++; + } const name = `wg${newIndex}`; const listen_port = 51820 + newIndex; @@ -470,7 +483,7 @@ const internalWireguard = { */ async updateInterface(knex, id, data, access, accessData) { const query = knex("wg_interface").where("id", id); - if (access && (!accessData || accessData.permission_visibility !== "all")) { + if (access) { query.andWhere("owner_user_id", access.token.getUserId(1)); } const iface = await query.first(); @@ -493,7 +506,7 @@ const internalWireguard = { */ async deleteInterface(knex, id, access, accessData) { const query = knex("wg_interface").where("id", id); - if (access && (!accessData || accessData.permission_visibility !== "all")) { + if (access) { query.andWhere("owner_user_id", access.token.getUserId(1)); } const iface = await query.first(); @@ -519,7 +532,7 @@ const internalWireguard = { async updateInterfaceLinks(knex, id, linkedServers, access, accessData) { // Verify ownership const query = knex("wg_interface").where("id", id); - if (access && (!accessData || accessData.permission_visibility !== "all")) { + if (access) { query.andWhere("owner_user_id", access.token.getUserId(1)); } const iface = await query.first(); @@ -546,8 +559,7 @@ const internalWireguard = { */ async getInterfacesInfo(knex, access, accessData) { const query = knex("wg_interface").select("*"); - // Filter by owner if not admin - if (access && (!accessData || accessData.permission_visibility !== "all")) { + if (access) { query.andWhere("owner_user_id", access.token.getUserId(1)); } const ifaces = await query; diff --git a/frontend/src/pages/DatabaseManager/index.tsx b/frontend/src/pages/DatabaseManager/index.tsx index 4e61748..2364a3a 100644 --- a/frontend/src/pages/DatabaseManager/index.tsx +++ b/frontend/src/pages/DatabaseManager/index.tsx @@ -44,10 +44,10 @@ export default function DatabaseManager() { } }; - const renderTableData = (data: any[], schema?: any[]) => { + const renderTableData = (data: any[]) => { if (!data || data.length === 0) return
No data
; - - const columns = schema ? schema.map((s: any) => s.name || s.Field) : Object.keys(data[0]); + // In SQLite, raw SQL mapping might mismatch explicit schemas, so strictly read keys from the first row. + const columns = Object.keys(data[0]); return (
@@ -119,7 +119,7 @@ export default function DatabaseManager() { - Tables + Tables
{tablesLoading &&
} {tables?.map((table: string) => ( @@ -136,7 +136,7 @@ export default function DatabaseManager() { - +
{activeTable || "Select a table"}
{tableData && {tableData.total} rows}
@@ -144,7 +144,7 @@ export default function DatabaseManager() { {tableDataLoading ? (
) : ( - tableData && renderTableData(tableData.rows, tableData.schema) + tableData && renderTableData(tableData.rows) )}
@@ -154,7 +154,7 @@ export default function DatabaseManager() { {activeTab === "query" && ( - Execute SQL Query + Execute SQL Query
Results
- {Array.isArray(queryResult) ? renderTableData(queryResult) : ( -
+									{Array.isArray(queryResult) && queryResult.length > 0 ? renderTableData(queryResult) : (
+										
 											{JSON.stringify(queryResult, null, 2)}
 										
)}