Merge branch 'dev' into feat/option-to-regenerate-keys

This commit is contained in:
Owen
2025-11-09 10:43:26 -08:00
40 changed files with 1669 additions and 1313 deletions

View File

@@ -30,6 +30,7 @@ import { pickPort } from "@server/routers/target/helpers";
import { resourcePassword } from "@server/db";
import { hashPassword } from "@server/auth/password";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { get } from "http";
export type ProxyResourcesResults = {
proxyResource: Resource;
@@ -544,7 +545,7 @@ export async function updateProxyResources(
if (
existingRule.action !== getRuleAction(rule.action) ||
existingRule.match !== rule.match.toUpperCase() ||
existingRule.value !== rule.value.toUpperCase()
existingRule.value !== getRuleValue(rule.match.toUpperCase(), rule.value)
) {
validateRule(rule);
await trx
@@ -552,7 +553,7 @@ export async function updateProxyResources(
.set({
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase()
value: getRuleValue(rule.match.toUpperCase(), rule.value),
})
.where(
eq(resourceRules.ruleId, existingRule.ruleId)
@@ -564,7 +565,7 @@ export async function updateProxyResources(
resourceId: existingResource.resourceId,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase(),
value: getRuleValue(rule.match.toUpperCase(), rule.value),
priority: index + 1 // start priorities at 1
});
}
@@ -722,7 +723,7 @@ export async function updateProxyResources(
resourceId: newResource.resourceId,
action: getRuleAction(rule.action),
match: rule.match.toUpperCase(),
value: rule.value.toUpperCase(),
value: getRuleValue(rule.match.toUpperCase(), rule.value),
priority: index + 1 // start priorities at 1
});
}
@@ -752,6 +753,14 @@ function getRuleAction(input: string) {
return action;
}
function getRuleValue(match: string, value: string) {
// if the match is a country, uppercase the value
if (match == "COUNTRY") {
return value.toUpperCase();
}
return value;
}
function validateRule(rule: any) {
if (rule.match === "cidr") {
if (!isValidCIDR(rule.value)) {

View File

@@ -80,7 +80,8 @@ export async function getTraefikConfig(
subnet: sites.subnet,
exitNodeId: sites.exitNodeId,
// Domain cert resolver fields
domainCertResolver: domains.certResolver
domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert
})
.from(sites)
.innerJoin(targets, eq(targets.siteId, sites.siteId))
@@ -178,7 +179,8 @@ export async function getTraefikConfig(
rewritePathType: row.rewritePathType,
priority: priority,
// Store domain cert resolver fields
domainCertResolver: row.domainCertResolver
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert
});
}

View File

@@ -111,7 +111,8 @@ export async function getTraefikConfig(
domainNamespaceId: domainNamespaces.domainNamespaceId,
// Certificate
certificateStatus: certificates.status,
domainCertResolver: domains.certResolver
domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert
})
.from(sites)
.innerJoin(targets, eq(targets.siteId, sites.siteId))
@@ -218,7 +219,8 @@ export async function getTraefikConfig(
rewritePath: row.rewritePath,
rewritePathType: row.rewritePathType,
priority: priority, // may be null, we fallback later
domainCertResolver: row.domainCertResolver
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert
});
}

View File

@@ -1 +1,14 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export * from "./sendSupportEmail";

View File

@@ -37,6 +37,7 @@ const updateResourceParamsSchema = z
const updateHttpResourceBodySchema = z
.object({
name: z.string().min(1).max(255).optional(),
niceId: z.string().min(1).max(255).optional(),
subdomain: subdomainSchema.nullable().optional(),
ssl: z.boolean().optional(),
sso: z.boolean().optional(),
@@ -97,6 +98,7 @@ export type UpdateResourceResponse = Resource;
const updateRawResourceBodySchema = z
.object({
name: z.string().min(1).max(255).optional(),
niceId: z.string().min(1).max(255).optional(),
proxyPort: z.number().int().min(1).max(65535).optional(),
stickySession: z.boolean().optional(),
enabled: z.boolean().optional(),
@@ -236,6 +238,30 @@ async function updateHttpResource(
const updateData = parsedBody.data;
if (updateData.niceId) {
const [existingResource] = await db
.select()
.from(resources)
.where(
and(
eq(resources.niceId, updateData.niceId),
eq(resources.orgId, resource.orgId)
)
);
if (
existingResource &&
existingResource.resourceId !== resource.resourceId
) {
return next(
createHttpError(
HttpCode.CONFLICT,
`A resource with niceId "${updateData.niceId}" already exists`
)
);
}
}
if (updateData.domainId) {
const domainId = updateData.domainId;
@@ -362,6 +388,30 @@ async function updateRawResource(
const updateData = parsedBody.data;
if (updateData.niceId) {
const [existingResource] = await db
.select()
.from(resources)
.where(
and(
eq(resources.niceId, updateData.niceId),
eq(resources.orgId, resource.orgId)
)
);
if (
existingResource &&
existingResource.resourceId !== resource.resourceId
) {
return next(
createHttpError(
HttpCode.CONFLICT,
`A resource with niceId "${updateData.niceId}" already exists`
)
);
}
}
const updatedResource = await db
.update(resources)
.set(updateData)

View File

@@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { sites } from "@server/db";
import { eq } from "drizzle-orm";
import { eq, and } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
@@ -20,6 +20,7 @@ const updateSiteParamsSchema = z
const updateSiteBodySchema = z
.object({
name: z.string().min(1).max(255).optional(),
niceId: z.string().min(1).max(255).optional(),
dockerSocketEnabled: z.boolean().optional(),
remoteSubnets: z
.string()
@@ -89,6 +90,29 @@ export async function updateSite(
const { siteId } = parsedParams.data;
const updateData = parsedBody.data;
// if niceId is provided, check if it's already in use by another site
if (updateData.niceId) {
const existingSite = await db
.select()
.from(sites)
.where(
and(
eq(sites.niceId, updateData.niceId),
eq(sites.orgId, sites.orgId)
)
)
.limit(1);
if (existingSite.length > 0 && existingSite[0].siteId !== siteId) {
return next(
createHttpError(
HttpCode.CONFLICT,
`A site with niceId "${updateData.niceId}" already exists`
)
);
}
}
// if remoteSubnets is provided, ensure it's a valid comma-separated list of cidrs
if (updateData.remoteSubnets) {
const subnets = updateData.remoteSubnets.split(",").map((s) => s.trim());

View File

@@ -163,12 +163,8 @@ export async function createTarget(
);
if (existingTarget) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}`
)
);
// log a warning
logger.warn(`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${resourceId}`);
}
let newTarget: Target[] = [];

View File

@@ -48,12 +48,10 @@ export async function deleteTarget(
const { targetId } = parsedParams.data;
const [deletedTarget] = await db.transaction(async (tx) => {
return await tx
.delete(targets)
.where(eq(targets.targetId, targetId))
.returning();
});
const [deletedTarget] = await db
.delete(targets)
.where(eq(targets.targetId, targetId))
.returning();
if (!deletedTarget) {
return next(

View File

@@ -170,12 +170,8 @@ export async function updateTarget(
);
if (foundTarget) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Target with IP ${targetData.ip}, port ${targetData.port}, and method ${targetData.method} already exists on the same site.`
)
);
// log a warning
logger.warn(`Target with IP ${targetData.ip}, port ${targetData.port}, method ${targetData.method} already exists for resource ID ${target.resourceId}`);
}
const { internalPort, targetIps } = await pickPort(site.siteId!, db);