mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-04 01:36:39 +00:00
Merge branch 'main' into dev
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -128,6 +128,7 @@
|
|||||||
"@types/express": "5.0.6",
|
"@types/express": "5.0.6",
|
||||||
"@types/express-session": "1.18.2",
|
"@types/express-session": "1.18.2",
|
||||||
"@types/jmespath": "0.15.2",
|
"@types/jmespath": "0.15.2",
|
||||||
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsonwebtoken": "9.0.10",
|
"@types/jsonwebtoken": "9.0.10",
|
||||||
"@types/node": "24.10.2",
|
"@types/node": "24.10.2",
|
||||||
"@types/nodemailer": "7.0.4",
|
"@types/nodemailer": "7.0.4",
|
||||||
@@ -9292,6 +9293,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/js-yaml": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -34,9 +34,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "8.2.0",
|
"@asteasolutions/zod-to-openapi": "8.2.0",
|
||||||
|
"@aws-sdk/client-s3": "3.947.0",
|
||||||
"@faker-js/faker": "10.1.0",
|
"@faker-js/faker": "10.1.0",
|
||||||
"@headlessui/react": "2.2.9",
|
"@headlessui/react": "2.2.9",
|
||||||
"@aws-sdk/client-s3": "3.947.0",
|
|
||||||
"@hookform/resolvers": "5.2.2",
|
"@hookform/resolvers": "5.2.2",
|
||||||
"@monaco-editor/react": "4.7.0",
|
"@monaco-editor/react": "4.7.0",
|
||||||
"@node-rs/argon2": "2.0.2",
|
"@node-rs/argon2": "2.0.2",
|
||||||
@@ -125,9 +125,8 @@
|
|||||||
"semver": "7.7.3",
|
"semver": "7.7.3",
|
||||||
"stripe": "20.0.0",
|
"stripe": "20.0.0",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"topojson-client": "3.1.0",
|
|
||||||
|
|
||||||
"tailwind-merge": "3.4.0",
|
"tailwind-merge": "3.4.0",
|
||||||
|
"topojson-client": "3.1.0",
|
||||||
"tw-animate-css": "1.4.0",
|
"tw-animate-css": "1.4.0",
|
||||||
"uuid": "13.0.0",
|
"uuid": "13.0.0",
|
||||||
"vaul": "1.1.2",
|
"vaul": "1.1.2",
|
||||||
@@ -155,8 +154,8 @@
|
|||||||
"@types/jmespath": "0.15.2",
|
"@types/jmespath": "0.15.2",
|
||||||
"@types/jsonwebtoken": "9.0.10",
|
"@types/jsonwebtoken": "9.0.10",
|
||||||
"@types/node": "24.10.2",
|
"@types/node": "24.10.2",
|
||||||
"@types/nprogress": "0.2.3",
|
|
||||||
"@types/nodemailer": "7.0.4",
|
"@types/nodemailer": "7.0.4",
|
||||||
|
"@types/nprogress": "0.2.3",
|
||||||
"@types/pg": "8.15.6",
|
"@types/pg": "8.15.6",
|
||||||
"@types/react": "19.2.7",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
@@ -164,15 +163,16 @@
|
|||||||
"@types/swagger-ui-express": "4.1.8",
|
"@types/swagger-ui-express": "4.1.8",
|
||||||
"@types/topojson-client": "3.1.5",
|
"@types/topojson-client": "3.1.5",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"babel-plugin-react-compiler": "1.0.0",
|
|
||||||
"@types/yargs": "17.0.35",
|
"@types/yargs": "17.0.35",
|
||||||
|
"@types/js-yaml": "4.0.9",
|
||||||
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
"drizzle-kit": "0.31.8",
|
"drizzle-kit": "0.31.8",
|
||||||
"esbuild": "0.27.1",
|
"esbuild": "0.27.1",
|
||||||
"esbuild-node-externals": "1.20.1",
|
"esbuild-node-externals": "1.20.1",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
|
"prettier": "3.7.4",
|
||||||
"react-email": "5.0.6",
|
"react-email": "5.0.6",
|
||||||
"tailwindcss": "4.1.17",
|
"tailwindcss": "4.1.17",
|
||||||
"prettier": "3.7.4",
|
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsx": "4.21.0",
|
"tsx": "4.21.0",
|
||||||
"typescript": "5.9.3",
|
"typescript": "5.9.3",
|
||||||
|
|||||||
@@ -116,6 +116,68 @@ function bigIntToIp(num: bigint, version: IPVersion): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an endpoint string (ip:port) handling both IPv4 and IPv6 addresses.
|
||||||
|
* IPv6 addresses may be bracketed like [::1]:8080 or unbracketed like ::1:8080.
|
||||||
|
* For unbracketed IPv6, the last colon-separated segment is treated as the port.
|
||||||
|
*
|
||||||
|
* @param endpoint The endpoint string to parse (e.g., "192.168.1.1:8080" or "[::1]:8080" or "2607:fea8::1:8080")
|
||||||
|
* @returns An object with ip and port, or null if parsing fails
|
||||||
|
*/
|
||||||
|
export function parseEndpoint(endpoint: string): { ip: string; port: number } | null {
|
||||||
|
if (!endpoint) return null;
|
||||||
|
|
||||||
|
// Check for bracketed IPv6 format: [ip]:port
|
||||||
|
const bracketedMatch = endpoint.match(/^\[([^\]]+)\]:(\d+)$/);
|
||||||
|
if (bracketedMatch) {
|
||||||
|
const ip = bracketedMatch[1];
|
||||||
|
const port = parseInt(bracketedMatch[2], 10);
|
||||||
|
if (isNaN(port)) return null;
|
||||||
|
return { ip, port };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this looks like IPv6 (contains multiple colons)
|
||||||
|
const colonCount = (endpoint.match(/:/g) || []).length;
|
||||||
|
|
||||||
|
if (colonCount > 1) {
|
||||||
|
// This is IPv6 - the port is after the last colon
|
||||||
|
const lastColonIndex = endpoint.lastIndexOf(":");
|
||||||
|
const ip = endpoint.substring(0, lastColonIndex);
|
||||||
|
const portStr = endpoint.substring(lastColonIndex + 1);
|
||||||
|
const port = parseInt(portStr, 10);
|
||||||
|
if (isNaN(port)) return null;
|
||||||
|
return { ip, port };
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv4 format: ip:port
|
||||||
|
if (colonCount === 1) {
|
||||||
|
const [ip, portStr] = endpoint.split(":");
|
||||||
|
const port = parseInt(portStr, 10);
|
||||||
|
if (isNaN(port)) return null;
|
||||||
|
return { ip, port };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an IP and port into a consistent endpoint string.
|
||||||
|
* IPv6 addresses are wrapped in brackets for proper parsing.
|
||||||
|
*
|
||||||
|
* @param ip The IP address (IPv4 or IPv6)
|
||||||
|
* @param port The port number
|
||||||
|
* @returns Formatted endpoint string
|
||||||
|
*/
|
||||||
|
export function formatEndpoint(ip: string, port: number): string {
|
||||||
|
// Check if this is IPv6 (contains colons)
|
||||||
|
if (ip.includes(":")) {
|
||||||
|
// Remove brackets if already present
|
||||||
|
const cleanIp = ip.replace(/^\[|\]$/g, "");
|
||||||
|
return `[${cleanIp}]:${port}`;
|
||||||
|
}
|
||||||
|
return `${ip}:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts CIDR to IP range
|
* Converts CIDR to IP range
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ import logger from "@server/logger";
|
|||||||
import {
|
import {
|
||||||
generateAliasConfig,
|
generateAliasConfig,
|
||||||
generateRemoteSubnets,
|
generateRemoteSubnets,
|
||||||
generateSubnetProxyTargets
|
generateSubnetProxyTargets,
|
||||||
|
parseEndpoint,
|
||||||
|
formatEndpoint
|
||||||
} from "@server/lib/ip";
|
} from "@server/lib/ip";
|
||||||
import {
|
import {
|
||||||
addPeerData,
|
addPeerData,
|
||||||
@@ -542,6 +544,13 @@ export async function updateClientSiteDestinations(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the endpoint properly for both IPv4 and IPv6
|
||||||
|
const parsedEndpoint = parseEndpoint(site.clientSitesAssociationsCache.endpoint);
|
||||||
|
if (!parsedEndpoint) {
|
||||||
|
logger.warn(`Failed to parse endpoint ${site.clientSitesAssociationsCache.endpoint}, skipping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// find the destinations in the array
|
// find the destinations in the array
|
||||||
let destinations = exitNodeDestinations.find(
|
let destinations = exitNodeDestinations.find(
|
||||||
(d) => d.reachableAt === site.exitNodes?.reachableAt
|
(d) => d.reachableAt === site.exitNodes?.reachableAt
|
||||||
@@ -553,13 +562,8 @@ export async function updateClientSiteDestinations(
|
|||||||
exitNodeId: site.exitNodes?.exitNodeId || 0,
|
exitNodeId: site.exitNodes?.exitNodeId || 0,
|
||||||
type: site.exitNodes?.type || "",
|
type: site.exitNodes?.type || "",
|
||||||
name: site.exitNodes?.name || "",
|
name: site.exitNodes?.name || "",
|
||||||
sourceIp:
|
sourceIp: parsedEndpoint.ip,
|
||||||
site.clientSitesAssociationsCache.endpoint.split(":")[0] ||
|
sourcePort: parsedEndpoint.port,
|
||||||
"",
|
|
||||||
sourcePort:
|
|
||||||
parseInt(
|
|
||||||
site.clientSitesAssociationsCache.endpoint.split(":")[1]
|
|
||||||
) || 0,
|
|
||||||
destinations: [
|
destinations: [
|
||||||
{
|
{
|
||||||
destinationIP: site.sites.subnet.split("/")[0],
|
destinationIP: site.sites.subnet.split("/")[0],
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { validateOlmSessionToken } from "@server/auth/sessions/olm";
|
|||||||
import { checkExitNodeOrg } from "#dynamic/lib/exitNodes";
|
import { checkExitNodeOrg } from "#dynamic/lib/exitNodes";
|
||||||
import { updatePeer as updateOlmPeer } from "../olm/peers";
|
import { updatePeer as updateOlmPeer } from "../olm/peers";
|
||||||
import { updatePeer as updateNewtPeer } from "../newt/peers";
|
import { updatePeer as updateNewtPeer } from "../newt/peers";
|
||||||
|
import { formatEndpoint } from "@server/lib/ip";
|
||||||
|
|
||||||
// Define Zod schema for request validation
|
// Define Zod schema for request validation
|
||||||
const updateHolePunchSchema = z.object({
|
const updateHolePunchSchema = z.object({
|
||||||
@@ -207,9 +208,12 @@ export async function updateAndGenerateEndpointDestinations(
|
|||||||
// `Updating site ${site.siteId} on exit node ${exitNode.exitNodeId}`
|
// `Updating site ${site.siteId} on exit node ${exitNode.exitNodeId}`
|
||||||
// );
|
// );
|
||||||
|
|
||||||
|
// Format the endpoint properly for both IPv4 and IPv6
|
||||||
|
const formattedEndpoint = formatEndpoint(ip, port);
|
||||||
|
|
||||||
// if the public key or endpoint has changed, update it otherwise continue
|
// if the public key or endpoint has changed, update it otherwise continue
|
||||||
if (
|
if (
|
||||||
site.endpoint === `${ip}:${port}` &&
|
site.endpoint === formattedEndpoint &&
|
||||||
site.publicKey === publicKey
|
site.publicKey === publicKey
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
@@ -218,7 +222,7 @@ export async function updateAndGenerateEndpointDestinations(
|
|||||||
const [updatedClientSitesAssociationsCache] = await db
|
const [updatedClientSitesAssociationsCache] = await db
|
||||||
.update(clientSitesAssociationsCache)
|
.update(clientSitesAssociationsCache)
|
||||||
.set({
|
.set({
|
||||||
endpoint: `${ip}:${port}`,
|
endpoint: formattedEndpoint,
|
||||||
publicKey: publicKey
|
publicKey: publicKey
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
@@ -310,11 +314,14 @@ export async function updateAndGenerateEndpointDestinations(
|
|||||||
|
|
||||||
currentSiteId = newt.siteId;
|
currentSiteId = newt.siteId;
|
||||||
|
|
||||||
|
// Format the endpoint properly for both IPv4 and IPv6
|
||||||
|
const formattedSiteEndpoint = formatEndpoint(ip, port);
|
||||||
|
|
||||||
// Update the current site with the new endpoint
|
// Update the current site with the new endpoint
|
||||||
const [updatedSite] = await db
|
const [updatedSite] = await db
|
||||||
.update(sites)
|
.update(sites)
|
||||||
.set({
|
.set({
|
||||||
endpoint: `${ip}:${port}`,
|
endpoint: formattedSiteEndpoint,
|
||||||
lastHolePunch: timestamp
|
lastHolePunch: timestamp
|
||||||
})
|
})
|
||||||
.where(eq(sites.siteId, newt.siteId))
|
.where(eq(sites.siteId, newt.siteId))
|
||||||
|
|||||||
Reference in New Issue
Block a user