Merge branch 'main' into dev

This commit is contained in:
Owen
2026-09-21 15:21:12 -04:00
17 changed files with 539 additions and 58 deletions
@@ -386,6 +386,38 @@ function runSpecialCharacterTests() {
console.log("All special character tests passed!");
}
function runEncodedPatternTests() {
console.log("\nRunning percent-encoded pattern tests...");
// isValidUrlGlobPattern accepts percent-encoded sequences and rejects
// raw spaces / non-ASCII, so `%20` and `%C3%A9` are the only way to write
// a PATH rule for such a path. Badger sends the request path already
// decoded (Go's req.URL.Path), and isPathAllowed decodes it again, so the
// rule pattern must be decoded the same way or it can never match.
assertEquals(
isPathAllowed("/my%20docs/*", "/my docs/report.pdf"),
true,
"Percent-encoded space in pattern should match decoded request path"
);
assertEquals(
isPathAllowed("/my%20docs/*", "/my%20docs/report.pdf"),
true,
"Percent-encoded space in pattern should match raw-encoded request path"
);
assertEquals(
isPathAllowed("/caf%C3%A9", "/café"),
true,
"Percent-encoded UTF-8 in pattern should match decoded request path"
);
assertEquals(
isPathAllowed("/my%20docs/*", "/my-docs/report.pdf"),
false,
"Decoded pattern must still reject a different path"
);
console.log("All percent-encoded pattern tests passed!");
}
function runRegionTests() {
console.log("\nRunning isIpInRegion tests...");
@@ -446,6 +478,7 @@ function runRegionTests() {
try {
runTests();
runSpecialCharacterTests();
runEncodedPatternTests();
runRegionTests();
console.log("\n✅ All tests passed!");
} catch (error) {
+1 -1
View File
@@ -31,7 +31,7 @@ export async function addPeer(
.where(eq(newts.siteId, siteId))
.limit(1);
if (!newt) {
throw new Error(`Site found for site ${siteId}`);
throw new Error(`Newt not found for site ${siteId}`);
}
newtId = newt.newtId;
}
+2 -1
View File
@@ -509,7 +509,8 @@ async function updateHttpResource(
}
// catch when the resource policy changes or gets cleared
if (resource.resourcePolicyId != updateData.resourcePolicyId) {
if (updateData.resourcePolicyId !== undefined &&
resource.resourcePolicyId !== updateData.resourcePolicyId) {
await clearResourceSpecificSettings(
resource.resourceId,
resource.orgId,
+1 -1
View File
@@ -263,7 +263,7 @@ export async function createSite(
const { value: newClientAddress, release } =
await getNextAvailableClientSubnet(orgId);
releaseSubnetLock = release;
updatedAddress = newClientAddress.split("/")[0];
updatedAddress = `${newClientAddress.split("/")[0]}/${org.subnet ? org.subnet.split("/")[1] : "32"}`;
}
let newSite: Site | undefined;
+1 -1
View File
@@ -113,7 +113,7 @@ export async function updateSite(
.where(
and(
eq(sites.niceId, updateData.niceId),
eq(sites.orgId, sites.orgId),
eq(sites.orgId, existingSite.orgId),
ne(sites.siteId, siteId)
)
)
@@ -0,0 +1,58 @@
import { assertEquals } from "@test/assert";
import { getSiteResourceParamsSchema } from "./getSiteResource";
function testSiteResourceIdOnlyParams() {
const result = getSiteResourceParamsSchema.safeParse({
siteResourceId: "42"
});
assertEquals(
result.success,
true,
"siteResourceId-only integration routes should pass validation"
);
if (result.success) {
assertEquals(
result.data.siteResourceId,
42,
"siteResourceId should be parsed as a number"
);
assertEquals(
result.data.orgId,
undefined,
"orgId should remain optional"
);
}
}
function testOrgScopedParamsRemainSupported() {
const result = getSiteResourceParamsSchema.safeParse({
siteResourceId: "42",
orgId: "org-id"
});
assertEquals(
result.success,
true,
"org-scoped routes should continue to pass validation"
);
}
function testInvalidSiteResourceId() {
const result = getSiteResourceParamsSchema.safeParse({
siteResourceId: "not-a-number"
});
assertEquals(
result.success,
false,
"non-numeric siteResourceIds should fail validation"
);
}
testSiteResourceIdOnlyParams();
testOrgScopedParamsRemainSupported();
testInvalidSiteResourceId();
console.log("All getSiteResource parameter validation tests passed.");
@@ -10,7 +10,7 @@ import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
const getSiteResourceParamsSchema = z.strictObject({
export const getSiteResourceParamsSchema = z.strictObject({
siteResourceId: z
.string()
.optional()
@@ -22,15 +22,17 @@ const getSiteResourceParamsSchema = z.strictObject({
});
async function query(siteResourceId?: number, niceId?: string, orgId?: string) {
if (siteResourceId && orgId) {
if (siteResourceId) {
const [siteResource] = await db
.select()
.from(siteResources)
.where(
and(
eq(siteResources.siteResourceId, siteResourceId),
eq(siteResources.orgId, orgId)
)
orgId
? and(
eq(siteResources.siteResourceId, siteResourceId),
eq(siteResources.orgId, orgId)
)
: eq(siteResources.siteResourceId, siteResourceId)
)
.limit(1);
return siteResource;