feat(wireguard): harden security constraints and fix db manager UI
This commit is contained in:
parent
b99b623355
commit
e057aee8ba
3 changed files with 34 additions and 20 deletions
|
|
@ -17,6 +17,7 @@ const internalAuditLog = {
|
||||||
|
|
||||||
const query = auditLogModel
|
const query = auditLogModel
|
||||||
.query()
|
.query()
|
||||||
|
.andWhere("user_id", access.token.getUserId(1))
|
||||||
.orderBy("created_on", "DESC")
|
.orderBy("created_on", "DESC")
|
||||||
.orderBy("id", "DESC")
|
.orderBy("id", "DESC")
|
||||||
.limit(100)
|
.limit(100)
|
||||||
|
|
@ -49,6 +50,7 @@ const internalAuditLog = {
|
||||||
const query = auditLogModel
|
const query = auditLogModel
|
||||||
.query()
|
.query()
|
||||||
.andWhere("id", data.id)
|
.andWhere("id", data.id)
|
||||||
|
.andWhere("user_id", access.token.getUserId(1))
|
||||||
.allowGraph("[user]")
|
.allowGraph("[user]")
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -213,8 +213,7 @@ const internalWireguard = {
|
||||||
.select("wg_client.*", "wg_interface.name as interface_name")
|
.select("wg_client.*", "wg_interface.name as interface_name")
|
||||||
.orderBy("wg_client.created_on", "desc");
|
.orderBy("wg_client.created_on", "desc");
|
||||||
|
|
||||||
// Filter by owner if not admin
|
if (access) {
|
||||||
if (access && (!accessData || accessData.permission_visibility !== "all")) {
|
|
||||||
query.andWhere("wg_client.owner_user_id", access.token.getUserId(1));
|
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 allocatedIPs = existingClients.map((c) => c.ipv4_address);
|
||||||
const ipv4Address = wgHelpers.findNextAvailableIP(iface.ipv4_cidr, allocatedIPs);
|
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 = {
|
const clientData = {
|
||||||
name: data.name || "Unnamed Client",
|
name: data.name || "Unnamed Client",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
@ -309,7 +312,7 @@ const internalWireguard = {
|
||||||
*/
|
*/
|
||||||
async deleteClient(knex, clientId, access, accessData) {
|
async deleteClient(knex, clientId, access, accessData) {
|
||||||
const query = knex("wg_client").where("id", clientId);
|
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));
|
query.andWhere("owner_user_id", access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
const client = await query.first();
|
const client = await query.first();
|
||||||
|
|
@ -328,7 +331,7 @@ const internalWireguard = {
|
||||||
*/
|
*/
|
||||||
async toggleClient(knex, clientId, enabled, access, accessData) {
|
async toggleClient(knex, clientId, enabled, access, accessData) {
|
||||||
const query = knex("wg_client").where("id", clientId);
|
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));
|
query.andWhere("owner_user_id", access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
const client = await query.first();
|
const client = await query.first();
|
||||||
|
|
@ -351,7 +354,7 @@ const internalWireguard = {
|
||||||
*/
|
*/
|
||||||
async updateClient(knex, clientId, data, access, accessData) {
|
async updateClient(knex, clientId, data, access, accessData) {
|
||||||
const query = knex("wg_client").where("id", clientId);
|
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));
|
query.andWhere("owner_user_id", access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
const client = await query.first();
|
const client = await query.first();
|
||||||
|
|
@ -414,7 +417,17 @@ const internalWireguard = {
|
||||||
*/
|
*/
|
||||||
async createInterface(knex, data, access, accessData) {
|
async createInterface(knex, data, access, accessData) {
|
||||||
const existingIfaces = await knex("wg_interface").select("name", "listen_port");
|
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 name = `wg${newIndex}`;
|
||||||
const listen_port = 51820 + newIndex;
|
const listen_port = 51820 + newIndex;
|
||||||
|
|
@ -470,7 +483,7 @@ const internalWireguard = {
|
||||||
*/
|
*/
|
||||||
async updateInterface(knex, id, data, access, accessData) {
|
async updateInterface(knex, id, data, access, accessData) {
|
||||||
const query = knex("wg_interface").where("id", id);
|
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));
|
query.andWhere("owner_user_id", access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
const iface = await query.first();
|
const iface = await query.first();
|
||||||
|
|
@ -493,7 +506,7 @@ const internalWireguard = {
|
||||||
*/
|
*/
|
||||||
async deleteInterface(knex, id, access, accessData) {
|
async deleteInterface(knex, id, access, accessData) {
|
||||||
const query = knex("wg_interface").where("id", id);
|
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));
|
query.andWhere("owner_user_id", access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
const iface = await query.first();
|
const iface = await query.first();
|
||||||
|
|
@ -519,7 +532,7 @@ const internalWireguard = {
|
||||||
async updateInterfaceLinks(knex, id, linkedServers, access, accessData) {
|
async updateInterfaceLinks(knex, id, linkedServers, access, accessData) {
|
||||||
// Verify ownership
|
// Verify ownership
|
||||||
const query = knex("wg_interface").where("id", id);
|
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));
|
query.andWhere("owner_user_id", access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
const iface = await query.first();
|
const iface = await query.first();
|
||||||
|
|
@ -546,8 +559,7 @@ const internalWireguard = {
|
||||||
*/
|
*/
|
||||||
async getInterfacesInfo(knex, access, accessData) {
|
async getInterfacesInfo(knex, access, accessData) {
|
||||||
const query = knex("wg_interface").select("*");
|
const query = knex("wg_interface").select("*");
|
||||||
// Filter by owner if not admin
|
if (access) {
|
||||||
if (access && (!accessData || accessData.permission_visibility !== "all")) {
|
|
||||||
query.andWhere("owner_user_id", access.token.getUserId(1));
|
query.andWhere("owner_user_id", access.token.getUserId(1));
|
||||||
}
|
}
|
||||||
const ifaces = await query;
|
const ifaces = await query;
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,10 @@ export default function DatabaseManager() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTableData = (data: any[], schema?: any[]) => {
|
const renderTableData = (data: any[]) => {
|
||||||
if (!data || data.length === 0) return <div className="text-muted p-3">No data</div>;
|
if (!data || data.length === 0) return <div className="text-muted p-3">No data</div>;
|
||||||
|
// In SQLite, raw SQL mapping might mismatch explicit schemas, so strictly read keys from the first row.
|
||||||
const columns = schema ? schema.map((s: any) => s.name || s.Field) : Object.keys(data[0]);
|
const columns = Object.keys(data[0]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-responsive">
|
<div className="table-responsive">
|
||||||
|
|
@ -119,7 +119,7 @@ export default function DatabaseManager() {
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={3}>
|
<Col md={3}>
|
||||||
<Card className="shadow-sm">
|
<Card className="shadow-sm">
|
||||||
<Card.Header className="bg-light fw-bold">Tables</Card.Header>
|
<Card.Header className="bg-body-tertiary text-body fw-bold">Tables</Card.Header>
|
||||||
<div className="list-group list-group-flush" style={{ maxHeight: "70vh", overflowY: "auto" }}>
|
<div className="list-group list-group-flush" style={{ maxHeight: "70vh", overflowY: "auto" }}>
|
||||||
{tablesLoading && <div className="p-3"><Loading /></div>}
|
{tablesLoading && <div className="p-3"><Loading /></div>}
|
||||||
{tables?.map((table: string) => (
|
{tables?.map((table: string) => (
|
||||||
|
|
@ -136,7 +136,7 @@ export default function DatabaseManager() {
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={9}>
|
<Col md={9}>
|
||||||
<Card className="shadow-sm">
|
<Card className="shadow-sm">
|
||||||
<Card.Header className="bg-light d-flex justify-content-between align-items-center">
|
<Card.Header className="bg-body-tertiary text-body d-flex justify-content-between align-items-center">
|
||||||
<h5 className="mb-0 fw-bold">{activeTable || "Select a table"}</h5>
|
<h5 className="mb-0 fw-bold">{activeTable || "Select a table"}</h5>
|
||||||
{tableData && <Badge bg="secondary">{tableData.total} rows</Badge>}
|
{tableData && <Badge bg="secondary">{tableData.total} rows</Badge>}
|
||||||
</Card.Header>
|
</Card.Header>
|
||||||
|
|
@ -144,7 +144,7 @@ export default function DatabaseManager() {
|
||||||
{tableDataLoading ? (
|
{tableDataLoading ? (
|
||||||
<div className="p-5 d-flex justify-content-center"><Loading /></div>
|
<div className="p-5 d-flex justify-content-center"><Loading /></div>
|
||||||
) : (
|
) : (
|
||||||
tableData && renderTableData(tableData.rows, tableData.schema)
|
tableData && renderTableData(tableData.rows)
|
||||||
)}
|
)}
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -154,7 +154,7 @@ export default function DatabaseManager() {
|
||||||
|
|
||||||
{activeTab === "query" && (
|
{activeTab === "query" && (
|
||||||
<Card className="shadow-sm">
|
<Card className="shadow-sm">
|
||||||
<Card.Header className="bg-light fw-bold">Execute SQL Query</Card.Header>
|
<Card.Header className="bg-body-tertiary text-body fw-bold">Execute SQL Query</Card.Header>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<div className="mb-3 border rounded">
|
<div className="mb-3 border rounded">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
|
@ -185,8 +185,8 @@ export default function DatabaseManager() {
|
||||||
{queryResult && (
|
{queryResult && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h5 className="mb-3 border-bottom pb-2">Results</h5>
|
<h5 className="mb-3 border-bottom pb-2">Results</h5>
|
||||||
{Array.isArray(queryResult) ? renderTableData(queryResult) : (
|
{Array.isArray(queryResult) && queryResult.length > 0 ? renderTableData(queryResult) : (
|
||||||
<pre className="p-3 bg-light rounded border">
|
<pre className="p-3 bg-body-tertiary text-body rounded border">
|
||||||
{JSON.stringify(queryResult, null, 2)}
|
{JSON.stringify(queryResult, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue