This commit is contained in:
Owen
2025-12-22 16:28:41 -05:00
parent e28ab19ed4
commit 5c67a1cb12
55 changed files with 636 additions and 490 deletions

View File

@@ -99,7 +99,7 @@ async function query(query: Q) {
.where(and(baseConditions, not(isNull(requestAuditLog.location))))
.groupBy(requestAuditLog.location)
.orderBy(desc(totalQ))
.limit(DISTINCT_LIMIT+1);
.limit(DISTINCT_LIMIT + 1);
if (requestsPerCountry.length > DISTINCT_LIMIT) {
// throw an error

View File

@@ -189,22 +189,22 @@ async function queryUniqueFilterAttributes(
.selectDistinct({ actor: requestAuditLog.actor })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
.limit(DISTINCT_LIMIT + 1),
primaryDb
.selectDistinct({ locations: requestAuditLog.location })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
.limit(DISTINCT_LIMIT + 1),
primaryDb
.selectDistinct({ hosts: requestAuditLog.host })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
.limit(DISTINCT_LIMIT + 1),
primaryDb
.selectDistinct({ paths: requestAuditLog.path })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
.limit(DISTINCT_LIMIT + 1),
primaryDb
.selectDistinct({
id: requestAuditLog.resourceId,
@@ -216,7 +216,7 @@ async function queryUniqueFilterAttributes(
eq(requestAuditLog.resourceId, resources.resourceId)
)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1)
.limit(DISTINCT_LIMIT + 1)
]);
// TODO: for stuff like the paths this is too restrictive so lets just show some of the paths and the user needs to
@@ -309,10 +309,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)
);
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

@@ -52,7 +52,7 @@ export async function getConfig(
}
// clean up the public key - keep only valid base64 characters (A-Z, a-z, 0-9, +, /, =)
const cleanedPublicKey = publicKey.replace(/[^A-Za-z0-9+/=]/g, '');
const cleanedPublicKey = publicKey.replace(/[^A-Za-z0-9+/=]/g, "");
const exitNode = await createExitNode(cleanedPublicKey, reachableAt);

View File

@@ -858,7 +858,6 @@ authenticated.put(
blueprints.applyJSONBlueprint
);
authenticated.get(
"/org/:orgId/blueprint/:blueprintId",
verifyApiKeyOrgAccess,
@@ -866,7 +865,6 @@ authenticated.get(
blueprints.getBlueprint
);
authenticated.get(
"/org/:orgId/blueprints",
verifyApiKeyOrgAccess,

View File

@@ -21,8 +21,7 @@ export async function pickOrgDefaults(
// const subnet = await getNextAvailableOrgSubnet();
// Just hard code the subnet for now for everyone
const subnet = config.getRawConfig().orgs.subnet_group;
const utilitySubnet =
config.getRawConfig().orgs.utility_subnet_group;
const utilitySubnet = config.getRawConfig().orgs.utility_subnet_group;
return response<PickOrgDefaultsResponse>(res, {
data: {

View File

@@ -154,4 +154,4 @@ export async function updateOrg(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}
}

View File

@@ -1,19 +1,20 @@
import {Request, Response, NextFunction} from "express";
import {z} from "zod";
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
db,
resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility,
resourcePassword,
resourcePincode,
resources
} from "@server/db";
import {eq} from "drizzle-orm";
import { eq } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import {fromError} from "zod-validation-error";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import {build} from "@server/build";
import { build } from "@server/build";
const getResourceAuthInfoSchema = z.strictObject({
resourceGuid: z.string()
@@ -52,68 +53,68 @@ export async function getResourceAuthInfo(
);
}
const {resourceGuid} = parsedParams.data;
const { resourceGuid } = parsedParams.data;
const isGuidInteger = /^\d+$/.test(resourceGuid);
const [result] =
isGuidInteger && build === "saas"
? await db
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceId, Number(resourceGuid)))
.limit(1)
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceId, Number(resourceGuid)))
.limit(1)
: await db
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceGuid, resourceGuid))
.limit(1);
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceGuid, resourceGuid))
.limit(1);
const resource = result?.resources;
if (!resource) {
@@ -125,7 +126,8 @@ export async function getResourceAuthInfo(
const pincode = result?.resourcePincode;
const password = result?.resourcePassword;
const headerAuth = result?.resourceHeaderAuth;
const headerAuthExtendedCompatibility = result?.resourceHeaderAuthExtendedCompatibility;
const headerAuthExtendedCompatibility =
result?.resourceHeaderAuthExtendedCompatibility;
const url = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
@@ -138,7 +140,8 @@ export async function getResourceAuthInfo(
password: password !== null,
pincode: pincode !== null,
headerAuth: headerAuth !== null,
headerAuthExtendedCompatibility: headerAuthExtendedCompatibility !== null,
headerAuthExtendedCompatibility:
headerAuthExtendedCompatibility !== null,
sso: resource.sso,
blockAccess: resource.blockAccess,
url,

View File

@@ -1,6 +1,10 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {db, resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility} from "@server/db";
import {
db,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility
} from "@server/db";
import {
resources,
userResources,
@@ -109,7 +113,8 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
domainId: resources.domainId,
niceId: resources.niceId,
headerAuthId: resourceHeaderAuth.headerAuthId,
headerAuthExtendedCompatibilityId: resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
headerAuthExtendedCompatibilityId:
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
targetId: targets.targetId,
targetIp: targets.ip,
targetPort: targets.port,
@@ -133,7 +138,10 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(resourceHeaderAuthExtendedCompatibility.resourceId, resources.resourceId)
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
.leftJoin(

View File

@@ -1,14 +1,18 @@
import {Request, Response, NextFunction} from "express";
import {z} from "zod";
import {db, resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility} from "@server/db";
import {eq} from "drizzle-orm";
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
db,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility
} from "@server/db";
import { eq } from "drizzle-orm";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import {fromError} from "zod-validation-error";
import {response} from "@server/lib/response";
import { fromError } from "zod-validation-error";
import { response } from "@server/lib/response";
import logger from "@server/logger";
import {hashPassword} from "@server/auth/password";
import {OpenAPITags, registry} from "@server/openApi";
import { hashPassword } from "@server/auth/password";
import { OpenAPITags, registry } from "@server/openApi";
const setResourceAuthMethodsParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.int().positive())
@@ -67,29 +71,40 @@ export async function setResourceHeaderAuth(
);
}
const {resourceId} = parsedParams.data;
const {user, password, extendedCompatibility} = parsedBody.data;
const { resourceId } = parsedParams.data;
const { user, password, extendedCompatibility } = parsedBody.data;
await db.transaction(async (trx) => {
await trx
.delete(resourceHeaderAuth)
.where(eq(resourceHeaderAuth.resourceId, resourceId));
await trx.delete(resourceHeaderAuthExtendedCompatibility).where(eq(resourceHeaderAuthExtendedCompatibility.resourceId, resourceId));
await trx
.delete(resourceHeaderAuthExtendedCompatibility)
.where(
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resourceId
)
);
if (user && password && extendedCompatibility !== null) {
const headerAuthHash = await hashPassword(Buffer.from(`${user}:${password}`).toString("base64"));
const headerAuthHash = await hashPassword(
Buffer.from(`${user}:${password}`).toString("base64")
);
await Promise.all([
trx
.insert(resourceHeaderAuth)
.values({resourceId, headerAuthHash}),
.values({ resourceId, headerAuthHash }),
trx
.insert(resourceHeaderAuthExtendedCompatibility)
.values({resourceId, extendedCompatibilityIsActivated: extendedCompatibility})
.values({
resourceId,
extendedCompatibilityIsActivated:
extendedCompatibility
})
]);
}
});
return response(res, {

View File

@@ -7,4 +7,4 @@ export type GetMaintenanceInfoResponse = {
maintenanceTitle: string | null;
maintenanceMessage: string | null;
maintenanceEstimatedTime: string | null;
}
};

View File

@@ -343,7 +343,9 @@ async function updateHttpResource(
const isLicensed = await isLicensedOrSubscribed(resource.orgId);
if (build == "enterprise" && !isLicensed) {
logger.warn("Server is not licensed! Clearing set maintenance screen values");
logger.warn(
"Server is not licensed! Clearing set maintenance screen values"
);
// null the maintenance mode fields if not licensed
updateData.maintenanceModeEnabled = undefined;
updateData.maintenanceModeType = undefined;

View File

@@ -11,7 +11,11 @@ import {
userSiteResources
} from "@server/db";
import { getUniqueSiteResourceName } from "@server/db/names";
import { getNextAvailableAliasAddress, isIpInCidr, 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";
@@ -69,7 +73,10 @@ const createSiteResourceSchema = z
const domainRegex =
/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
const isValidDomain = domainRegex.test(data.destination);
const isValidAlias = data.alias !== undefined && data.alias !== null && data.alias.trim() !== "";
const isValidAlias =
data.alias !== undefined &&
data.alias !== null &&
data.alias.trim() !== "";
return isValidDomain && isValidAlias; // require the alias to be set in the case of domain
}
@@ -182,7 +189,9 @@ export async function createSiteResource(
.limit(1);
if (!org) {
return next(createHttpError(HttpCode.NOT_FOUND, "Organization not found"));
return next(
createHttpError(HttpCode.NOT_FOUND, "Organization not found")
);
}
if (!org.subnet || !org.utilitySubnet) {
@@ -195,10 +204,13 @@ export async function createSiteResource(
}
// Only check if destination is an IP address
const isIp = z.union([z.ipv4(), z.ipv6()]).safeParse(destination).success;
const isIp = z
.union([z.ipv4(), z.ipv6()])
.safeParse(destination).success;
if (
isIp &&
(isIpInCidr(destination, org.subnet) || isIpInCidr(destination, org.utilitySubnet))
(isIpInCidr(destination, org.subnet) ||
isIpInCidr(destination, org.utilitySubnet))
) {
return next(
createHttpError(

View File

@@ -88,9 +88,7 @@ export async function deleteSiteResource(
);
});
logger.info(
`Deleted site resource ${siteResourceId}`
);
logger.info(`Deleted site resource ${siteResourceId}`);
return response(res, {
data: { message: "Site resource deleted successfully" },

View File

@@ -204,7 +204,9 @@ export async function updateSiteResource(
.limit(1);
if (!org) {
return next(createHttpError(HttpCode.NOT_FOUND, "Organization not found"));
return next(
createHttpError(HttpCode.NOT_FOUND, "Organization not found")
);
}
if (!org.subnet || !org.utilitySubnet) {
@@ -217,10 +219,13 @@ export async function updateSiteResource(
}
// Only check if destination is an IP address
const isIp = z.union([z.ipv4(), z.ipv6()]).safeParse(destination).success;
const isIp = z
.union([z.ipv4(), z.ipv6()])
.safeParse(destination).success;
if (
isIp &&
(isIpInCidr(destination!, org.subnet) || isIpInCidr(destination!, org.utilitySubnet))
(isIpInCidr(destination!, org.subnet) ||
isIpInCidr(destination!, org.utilitySubnet))
) {
return next(
createHttpError(
@@ -295,7 +300,7 @@ export async function updateSiteResource(
const [insertedSiteResource] = await trx
.insert(siteResources)
.values({
...existingSiteResource,
...existingSiteResource
})
.returning();
@@ -517,9 +522,14 @@ export async function handleMessagingForUpdatedSiteResource(
site: { siteId: number; orgId: string },
trx: Transaction
) {
logger.debug("handleMessagingForUpdatedSiteResource: existingSiteResource is: ", existingSiteResource);
logger.debug("handleMessagingForUpdatedSiteResource: updatedSiteResource is: ", updatedSiteResource);
logger.debug(
"handleMessagingForUpdatedSiteResource: existingSiteResource is: ",
existingSiteResource
);
logger.debug(
"handleMessagingForUpdatedSiteResource: updatedSiteResource is: ",
updatedSiteResource
);
const { mergedAllClients } =
await rebuildClientAssociationsFromSiteResource(