Merge branch 'dev' into feat/device-approvals

This commit is contained in:
Fred KISSIE
2025-12-19 20:03:05 +01:00
40 changed files with 2382 additions and 1493 deletions

View File

@@ -81,6 +81,7 @@ function createDb() {
export const db = createDb();
export default db;
export const primaryDb = db.$primary;
export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0]
>[0];

View File

@@ -20,6 +20,7 @@ function createDb() {
export const db = createDb();
export default db;
export const primaryDb = db;
export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0]
>[0];

View File

@@ -1,4 +1,4 @@
import { db, requestAuditLog, driver } from "@server/db";
import { db, requestAuditLog, driver, primaryDb } from "@server/db";
import { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
@@ -74,12 +74,12 @@ async function query(query: Q) {
);
}
const [all] = await db
const [all] = await primaryDb
.select({ total: count() })
.from(requestAuditLog)
.where(baseConditions);
const [blocked] = await db
const [blocked] = await primaryDb
.select({ total: count() })
.from(requestAuditLog)
.where(and(baseConditions, eq(requestAuditLog.action, false)));
@@ -88,7 +88,9 @@ async function query(query: Q) {
.mapWith(Number)
.as("total");
const requestsPerCountry = await db
const DISTINCT_LIMIT = 500;
const requestsPerCountry = await primaryDb
.selectDistinct({
code: requestAuditLog.location,
count: totalQ
@@ -96,7 +98,16 @@ async function query(query: Q) {
.from(requestAuditLog)
.where(and(baseConditions, not(isNull(requestAuditLog.location))))
.groupBy(requestAuditLog.location)
.orderBy(desc(totalQ));
.orderBy(desc(totalQ))
.limit(DISTINCT_LIMIT+1);
if (requestsPerCountry.length > DISTINCT_LIMIT) {
// throw an error
throw createHttpError(
HttpCode.BAD_REQUEST,
`Too many distinct countries. Please narrow your query.`
);
}
const groupByDayFunction =
driver === "pg"
@@ -106,7 +117,7 @@ async function query(query: Q) {
const booleanTrue = driver === "pg" ? sql`true` : sql`1`;
const booleanFalse = driver === "pg" ? sql`false` : sql`0`;
const requestsPerDay = await db
const requestsPerDay = await primaryDb
.select({
day: groupByDayFunction.as("day"),
allowedCount:

View File

@@ -1,4 +1,4 @@
import { db, requestAuditLog, resources } from "@server/db";
import { db, primaryDb, requestAuditLog, resources } from "@server/db";
import { registry } from "@server/openApi";
import { NextFunction } from "express";
import { Request, Response } from "express";
@@ -107,7 +107,7 @@ function getWhere(data: Q) {
}
export function queryRequest(data: Q) {
return db
return primaryDb
.select({
id: requestAuditLog.id,
timestamp: requestAuditLog.timestamp,
@@ -143,7 +143,7 @@ export function queryRequest(data: Q) {
}
export function countRequestQuery(data: Q) {
const countQuery = db
const countQuery = primaryDb
.select({ count: count() })
.from(requestAuditLog)
.where(getWhere(data));
@@ -173,50 +173,61 @@ async function queryUniqueFilterAttributes(
eq(requestAuditLog.orgId, orgId)
);
// Get unique actors
const uniqueActors = await db
.selectDistinct({
actor: requestAuditLog.actor
})
.from(requestAuditLog)
.where(baseConditions);
const DISTINCT_LIMIT = 500;
// Get unique locations
const uniqueLocations = await db
.selectDistinct({
locations: requestAuditLog.location
})
.from(requestAuditLog)
.where(baseConditions);
// TODO: SOMEONE PLEASE OPTIMIZE THIS!!!!!
// Get unique actors
const uniqueHosts = await db
.selectDistinct({
hosts: requestAuditLog.host
})
.from(requestAuditLog)
.where(baseConditions);
// Run all queries in parallel
const [
uniqueActors,
uniqueLocations,
uniqueHosts,
uniquePaths,
uniqueResources
] = await Promise.all([
primaryDb
.selectDistinct({ actor: requestAuditLog.actor })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
primaryDb
.selectDistinct({ locations: requestAuditLog.location })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
primaryDb
.selectDistinct({ hosts: requestAuditLog.host })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
primaryDb
.selectDistinct({ paths: requestAuditLog.path })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
primaryDb
.selectDistinct({
id: requestAuditLog.resourceId,
name: resources.name
})
.from(requestAuditLog)
.leftJoin(
resources,
eq(requestAuditLog.resourceId, resources.resourceId)
)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1)
]);
// Get unique actors
const uniquePaths = await db
.selectDistinct({
paths: requestAuditLog.path
})
.from(requestAuditLog)
.where(baseConditions);
// Get unique resources with names
const uniqueResources = await db
.selectDistinct({
id: requestAuditLog.resourceId,
name: resources.name
})
.from(requestAuditLog)
.leftJoin(
resources,
eq(requestAuditLog.resourceId, resources.resourceId)
)
.where(baseConditions);
if (
uniqueActors.length > DISTINCT_LIMIT ||
uniqueLocations.length > DISTINCT_LIMIT ||
uniqueHosts.length > DISTINCT_LIMIT ||
uniquePaths.length > DISTINCT_LIMIT ||
uniqueResources.length > DISTINCT_LIMIT
) {
throw new Error("Too many distinct filter attributes to retrieve. Please refine your time range.");
}
return {
actors: uniqueActors
@@ -295,6 +306,12 @@ export async function queryRequestAuditLogs(
});
} catch (error) {
logger.error(error);
// if the message is "Too many distinct filter attributes to retrieve. Please refine your time range.", return a 400 and the message
if (error instanceof Error && error.message === "Too many distinct filter attributes to retrieve. Please refine your time range.") {
return next(
createHttpError(HttpCode.BAD_REQUEST, error.message)
);
}
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);

View File

@@ -6,8 +6,8 @@ export type GetCertificateResponse = {
status: string; // pending, requested, valid, expired, failed
expiresAt: string | null;
lastRenewalAttempt: Date | null;
createdAt: string;
updatedAt: string;
createdAt: number;
updatedAt: number;
errorMessage?: string | null;
renewalCount: number;
};

View File

@@ -194,11 +194,23 @@ export async function getOlmToken(
.where(inArray(exitNodes.exitNodeId, exitNodeIds));
}
// Map exitNodeId to siteIds
const exitNodeIdToSiteIds: Record<number, number[]> = {};
for (const { sites: site } of clientSites) {
if (site.exitNodeId !== null) {
if (!exitNodeIdToSiteIds[site.exitNodeId]) {
exitNodeIdToSiteIds[site.exitNodeId] = [];
}
exitNodeIdToSiteIds[site.exitNodeId].push(site.siteId);
}
}
const exitNodesHpData = allExitNodes.map((exitNode: ExitNode) => {
return {
publicKey: exitNode.publicKey,
relayPort: config.getRawConfig().gerbil.clients_start_port,
endpoint: exitNode.endpoint
endpoint: exitNode.endpoint,
siteIds: exitNodeIdToSiteIds[exitNode.exitNodeId] ?? []
};
});

View File

@@ -2,6 +2,7 @@ import {
clientSiteResources,
db,
newts,
orgs,
roles,
roleSiteResources,
SiteResource,
@@ -10,7 +11,7 @@ import {
userSiteResources
} from "@server/db";
import { getUniqueSiteResourceName } from "@server/db/names";
import { getNextAvailableAliasAddress, portRangeStringSchema } from "@server/lib/ip";
import { getNextAvailableAliasAddress, isIpInCidr, portRangeStringSchema } from "@server/lib/ip";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import response from "@server/lib/response";
import logger from "@server/logger";
@@ -84,8 +85,7 @@ const createSiteResourceSchema = z
if (data.mode === "cidr") {
// Check if it's a valid CIDR (v4 or v6)
const isValidCIDR = z
// .union([z.cidrv4(), z.cidrv6()])
.union([z.cidrv4()]) // for now lets just do ipv4 until we verify ipv6 works everywhere
.union([z.cidrv4(), z.cidrv6()])
.safeParse(data.destination).success;
return isValidCIDR;
}
@@ -175,6 +175,39 @@ export async function createSiteResource(
return next(createHttpError(HttpCode.NOT_FOUND, "Site not found"));
}
const [org] = await db
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (!org) {
return next(createHttpError(HttpCode.NOT_FOUND, "Organization not found"));
}
if (!org.subnet || !org.utilitySubnet) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Organization with ID ${orgId} has no subnet or utilitySubnet defined defined`
)
);
}
// Only check if destination is an IP address
const isIp = z.union([z.ipv4(), z.ipv6()]).safeParse(destination).success;
if (
isIp &&
(isIpInCidr(destination, org.subnet) || isIpInCidr(destination, org.utilitySubnet))
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"IP can not be in the CIDR range of the organization's subnet or utility subnet"
)
);
}
// // check if resource with same protocol and proxy port already exists (only for port mode)
// if (mode === "port" && protocol && proxyPort) {
// const [existingResource] = await db

View File

@@ -5,6 +5,7 @@ import {
clientSiteResourcesAssociationsCache,
db,
newts,
orgs,
roles,
roleSiteResources,
sites,
@@ -24,6 +25,7 @@ import {
generateAliasConfig,
generateRemoteSubnets,
generateSubnetProxyTargets,
isIpInCidr,
portRangeStringSchema
} from "@server/lib/ip";
import {
@@ -96,8 +98,7 @@ const updateSiteResourceSchema = z
if (data.mode === "cidr" && data.destination) {
// Check if it's a valid CIDR (v4 or v6)
const isValidCIDR = z
// .union([z.cidrv4(), z.cidrv6()])
.union([z.cidrv4()]) // for now lets just do ipv4 until we verify ipv6 works everywhere
.union([z.cidrv4(), z.cidrv6()])
.safeParse(data.destination).success;
return isValidCIDR;
}
@@ -196,6 +197,39 @@ export async function updateSiteResource(
);
}
const [org] = await db
.select()
.from(orgs)
.where(eq(orgs.orgId, existingSiteResource.orgId))
.limit(1);
if (!org) {
return next(createHttpError(HttpCode.NOT_FOUND, "Organization not found"));
}
if (!org.subnet || !org.utilitySubnet) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Organization with ID ${existingSiteResource.orgId} has no subnet or utilitySubnet defined defined`
)
);
}
// Only check if destination is an IP address
const isIp = z.union([z.ipv4(), z.ipv6()]).safeParse(destination).success;
if (
isIp &&
(isIpInCidr(destination!, org.subnet) || isIpInCidr(destination!, org.utilitySubnet))
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"IP can not be in the CIDR range of the organization's subnet or utility subnet"
)
);
}
let existingSite = site;
let siteChanged = false;
if (existingSiteResource.siteId !== siteId) {