mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-10 20:02:26 +00:00
Seperate managed node code to fosrl/pangolin-node
This commit is contained in:
@@ -33,7 +33,9 @@ import createHttpError from "http-errors";
|
||||
import NodeCache from "node-cache";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { getCountryCodeForIp, remoteGetCountryCodeForIp } from "@server/lib/geoip";
|
||||
import {
|
||||
getCountryCodeForIp,
|
||||
} from "@server/lib/geoip";
|
||||
import { getOrgTierData } from "#dynamic/lib/billing";
|
||||
import { TierId } from "@server/lib/billing/tiers";
|
||||
import { verifyPassword } from "@server/auth/password";
|
||||
@@ -106,23 +108,23 @@ export async function verifyResourceSession(
|
||||
|
||||
const clientIp = requestIp
|
||||
? (() => {
|
||||
logger.debug("Request IP:", { requestIp });
|
||||
if (requestIp.startsWith("[") && requestIp.includes("]")) {
|
||||
// if brackets are found, extract the IPv6 address from between the brackets
|
||||
const ipv6Match = requestIp.match(/\[(.*?)\]/);
|
||||
if (ipv6Match) {
|
||||
return ipv6Match[1];
|
||||
}
|
||||
}
|
||||
logger.debug("Request IP:", { requestIp });
|
||||
if (requestIp.startsWith("[") && requestIp.includes("]")) {
|
||||
// if brackets are found, extract the IPv6 address from between the brackets
|
||||
const ipv6Match = requestIp.match(/\[(.*?)\]/);
|
||||
if (ipv6Match) {
|
||||
return ipv6Match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// ivp4
|
||||
// split at last colon
|
||||
const lastColonIndex = requestIp.lastIndexOf(":");
|
||||
if (lastColonIndex !== -1) {
|
||||
return requestIp.substring(0, lastColonIndex);
|
||||
}
|
||||
return requestIp;
|
||||
})()
|
||||
// ivp4
|
||||
// split at last colon
|
||||
const lastColonIndex = requestIp.lastIndexOf(":");
|
||||
if (lastColonIndex !== -1) {
|
||||
return requestIp.substring(0, lastColonIndex);
|
||||
}
|
||||
return requestIp;
|
||||
})()
|
||||
: undefined;
|
||||
|
||||
logger.debug("Client IP:", { clientIp });
|
||||
@@ -137,11 +139,11 @@ export async function verifyResourceSession(
|
||||
const resourceCacheKey = `resource:${cleanHost}`;
|
||||
let resourceData:
|
||||
| {
|
||||
resource: Resource | null;
|
||||
pincode: ResourcePincode | null;
|
||||
password: ResourcePassword | null;
|
||||
headerAuth: ResourceHeaderAuth | null;
|
||||
}
|
||||
resource: Resource | null;
|
||||
pincode: ResourcePincode | null;
|
||||
password: ResourcePassword | null;
|
||||
headerAuth: ResourceHeaderAuth | null;
|
||||
}
|
||||
| undefined = cache.get(resourceCacheKey);
|
||||
|
||||
if (!resourceData) {
|
||||
@@ -213,21 +215,21 @@ export async function verifyResourceSession(
|
||||
headers &&
|
||||
headers[
|
||||
config.getRawConfig().server.resource_access_token_headers.id
|
||||
] &&
|
||||
] &&
|
||||
headers[
|
||||
config.getRawConfig().server.resource_access_token_headers.token
|
||||
]
|
||||
]
|
||||
) {
|
||||
const accessTokenId =
|
||||
headers[
|
||||
config.getRawConfig().server.resource_access_token_headers
|
||||
.id
|
||||
];
|
||||
];
|
||||
const accessToken =
|
||||
headers[
|
||||
config.getRawConfig().server.resource_access_token_headers
|
||||
.token
|
||||
];
|
||||
];
|
||||
|
||||
const { valid, error, tokenItem } = await verifyResourceAccessToken(
|
||||
{
|
||||
@@ -294,10 +296,17 @@ export async function verifyResourceSession(
|
||||
|
||||
// check for HTTP Basic Auth header
|
||||
if (headerAuth && clientHeaderAuth) {
|
||||
if(cache.get(clientHeaderAuth)) {
|
||||
logger.debug("Resource allowed because header auth is valid (cached)");
|
||||
if (cache.get(clientHeaderAuth)) {
|
||||
logger.debug(
|
||||
"Resource allowed because header auth is valid (cached)"
|
||||
);
|
||||
return allowed(res);
|
||||
}else if(await verifyPassword(clientHeaderAuth, headerAuth.headerAuthHash)){
|
||||
} else if (
|
||||
await verifyPassword(
|
||||
clientHeaderAuth,
|
||||
headerAuth.headerAuthHash
|
||||
)
|
||||
) {
|
||||
cache.set(clientHeaderAuth, clientHeaderAuth);
|
||||
logger.debug("Resource allowed because header auth is valid");
|
||||
return allowed(res);
|
||||
@@ -477,7 +486,11 @@ function extractResourceSessionToken(
|
||||
return latest.token;
|
||||
}
|
||||
|
||||
async function notAllowed(res: Response, redirectPath?: string, orgId?: string) {
|
||||
async function notAllowed(
|
||||
res: Response,
|
||||
redirectPath?: string,
|
||||
orgId?: string
|
||||
) {
|
||||
let loginPage: LoginPage | null = null;
|
||||
if (orgId) {
|
||||
const { tier } = await getOrgTierData(orgId); // returns null in oss
|
||||
@@ -491,14 +504,11 @@ async function notAllowed(res: Response, redirectPath?: string, orgId?: string)
|
||||
let endpoint: string;
|
||||
|
||||
if (loginPage && loginPage.domainId && loginPage.fullDomain) {
|
||||
const secure = config.getRawConfig().app.dashboard_url?.startsWith("https");
|
||||
const secure = config
|
||||
.getRawConfig()
|
||||
.app.dashboard_url?.startsWith("https");
|
||||
const method = secure ? "https" : "http";
|
||||
endpoint = `${method}://${loginPage.fullDomain}`;
|
||||
} else if (config.isManagedMode()) {
|
||||
endpoint =
|
||||
config.getRawConfig().managed?.redirect_endpoint ||
|
||||
config.getRawConfig().managed?.endpoint ||
|
||||
"";
|
||||
} else {
|
||||
endpoint = config.getRawConfig().app.dashboard_url!;
|
||||
}
|
||||
@@ -803,11 +813,7 @@ async function isIpInGeoIP(ip: string, countryCode: string): Promise<boolean> {
|
||||
let cachedCountryCode: string | undefined = cache.get(geoIpCacheKey);
|
||||
|
||||
if (!cachedCountryCode) {
|
||||
if (config.isManagedMode()) {
|
||||
cachedCountryCode = await remoteGetCountryCodeForIp(ip);
|
||||
} else {
|
||||
cachedCountryCode = await getCountryCodeForIp(ip); // do it locally
|
||||
}
|
||||
cachedCountryCode = await getCountryCodeForIp(ip); // do it locally
|
||||
// Cache for longer since IP geolocation doesn't change frequently
|
||||
cache.set(geoIpCacheKey, cachedCountryCode, 300); // 5 minutes
|
||||
}
|
||||
@@ -817,7 +823,9 @@ async function isIpInGeoIP(ip: string, countryCode: string): Promise<boolean> {
|
||||
return cachedCountryCode?.toUpperCase() === countryCode.toUpperCase();
|
||||
}
|
||||
|
||||
function extractBasicAuth(headers: Record<string, string> | undefined): string | undefined {
|
||||
function extractBasicAuth(
|
||||
headers: Record<string, string> | undefined
|
||||
): string | undefined {
|
||||
if (!headers || (!headers.authorization && !headers.Authorization)) {
|
||||
return;
|
||||
}
|
||||
@@ -833,8 +841,9 @@ function extractBasicAuth(headers: Record<string, string> | undefined): string |
|
||||
try {
|
||||
// Extract the base64 encoded credentials
|
||||
return authHeader.slice("Basic ".length);
|
||||
|
||||
} catch (error) {
|
||||
logger.debug("Basic Auth: Failed to decode credentials", { error: error instanceof Error ? error.message : "Unknown error" });
|
||||
logger.debug("Basic Auth: Failed to decode credentials", {
|
||||
error: error instanceof Error ? error.message : "Unknown error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import logger from "@server/logger";
|
||||
import config from "@server/lib/config";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { getAllowedIps } from "../target/helpers";
|
||||
import { proxyToRemote } from "@server/lib/remoteProxy";
|
||||
import { createExitNode } from "#dynamic/routers/gerbil/createExitNode";
|
||||
|
||||
// Define Zod schema for request validation
|
||||
@@ -63,16 +62,6 @@ export async function getConfig(
|
||||
);
|
||||
}
|
||||
|
||||
// STOP HERE IN HYBRID MODE
|
||||
if (config.isManagedMode()) {
|
||||
req.body = {
|
||||
...req.body,
|
||||
endpoint: exitNode.endpoint,
|
||||
listenPort: exitNode.listenPort
|
||||
};
|
||||
return proxyToRemote(req, res, next, "hybrid/gerbil/get-config");
|
||||
}
|
||||
|
||||
const configResponse = await generateGerbilConfig(exitNode);
|
||||
|
||||
logger.debug("Sending config: ", configResponse);
|
||||
|
||||
@@ -7,8 +7,6 @@ import * as auth from "@server/routers/auth";
|
||||
import * as supporterKey from "@server/routers/supporterKey";
|
||||
import * as license from "@server/routers/license";
|
||||
import * as idp from "@server/routers/idp";
|
||||
import { proxyToRemote } from "@server/lib/remoteProxy";
|
||||
import config from "@server/lib/config";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import {
|
||||
verifyResourceAccess,
|
||||
@@ -51,34 +49,11 @@ internalRouter.get("/idp/:idpId", idp.getIdp);
|
||||
const gerbilRouter = Router();
|
||||
internalRouter.use("/gerbil", gerbilRouter);
|
||||
|
||||
if (config.isManagedMode()) {
|
||||
// Use proxy router to forward requests to remote cloud server
|
||||
// Proxy endpoints for each gerbil route
|
||||
gerbilRouter.post("/receive-bandwidth", (req, res, next) =>
|
||||
proxyToRemote(req, res, next, "hybrid/gerbil/receive-bandwidth")
|
||||
);
|
||||
|
||||
gerbilRouter.post("/update-hole-punch", (req, res, next) =>
|
||||
proxyToRemote(req, res, next, "hybrid/gerbil/update-hole-punch")
|
||||
);
|
||||
|
||||
gerbilRouter.post("/get-all-relays", (req, res, next) =>
|
||||
proxyToRemote(req, res, next, "hybrid/gerbil/get-all-relays")
|
||||
);
|
||||
|
||||
gerbilRouter.post("/get-resolved-hostname", (req, res, next) =>
|
||||
proxyToRemote(req, res, next, `hybrid/gerbil/get-resolved-hostname`)
|
||||
);
|
||||
|
||||
// GET CONFIG IS HANDLED IN THE ORIGINAL HANDLER
|
||||
// SO IT CAN REGISTER THE LOCAL EXIT NODE
|
||||
} else {
|
||||
// Use local gerbil endpoints
|
||||
gerbilRouter.post("/receive-bandwidth", gerbil.receiveBandwidth);
|
||||
gerbilRouter.post("/update-hole-punch", gerbil.updateHolePunch);
|
||||
gerbilRouter.post("/get-all-relays", gerbil.getAllRelays);
|
||||
gerbilRouter.post("/get-resolved-hostname", gerbil.getResolvedHostname);
|
||||
}
|
||||
// Use local gerbil endpoints
|
||||
gerbilRouter.post("/receive-bandwidth", gerbil.receiveBandwidth);
|
||||
gerbilRouter.post("/update-hole-punch", gerbil.updateHolePunch);
|
||||
gerbilRouter.post("/get-all-relays", gerbil.getAllRelays);
|
||||
gerbilRouter.post("/get-resolved-hostname", gerbil.getResolvedHostname);
|
||||
|
||||
// WE HANDLE THE PROXY INSIDE OF THIS FUNCTION
|
||||
// SO IT REGISTERS THE EXIT NODE LOCALLY AS WELL
|
||||
@@ -90,10 +65,4 @@ internalRouter.use("/badger", badgerRouter);
|
||||
|
||||
badgerRouter.post("/verify-session", badger.verifyResourceSession);
|
||||
|
||||
if (config.isManagedMode()) {
|
||||
badgerRouter.post("/exchange-session", (req, res, next) =>
|
||||
proxyToRemote(req, res, next, "hybrid/badger/exchange-session")
|
||||
);
|
||||
} else {
|
||||
badgerRouter.post("/exchange-session", badger.exchangeSession);
|
||||
}
|
||||
badgerRouter.post("/exchange-session", badger.exchangeSession);
|
||||
|
||||
Reference in New Issue
Block a user