mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-10 20:02:26 +00:00
Merge branch 'dev' into distribution
This commit is contained in:
8
server/routers/auth/types.ts
Normal file
8
server/routers/auth/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type TransferSessionResponse = {
|
||||
valid: boolean;
|
||||
cookie?: string;
|
||||
};
|
||||
|
||||
export type GetSessionTransferTokenRenponse = {
|
||||
token: string;
|
||||
};
|
||||
@@ -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"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
17
server/routers/billing/types.ts
Normal file
17
server/routers/billing/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Limit, Subscription, SubscriptionItem, Usage } from "@server/db";
|
||||
|
||||
export type GetOrgSubscriptionResponse = {
|
||||
subscription: Subscription | null;
|
||||
items: SubscriptionItem[];
|
||||
};
|
||||
|
||||
export type GetOrgUsageResponse = {
|
||||
usage: Usage[];
|
||||
limits: Limit[];
|
||||
};
|
||||
|
||||
export type GetOrgTierResponse = {
|
||||
tier: string | null;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
13
server/routers/certificates/types.ts
Normal file
13
server/routers/certificates/types.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type GetCertificateResponse = {
|
||||
certId: number;
|
||||
domain: string;
|
||||
domainId: string;
|
||||
wildcard: boolean;
|
||||
status: string; // pending, requested, valid, expired, failed
|
||||
expiresAt: string | null;
|
||||
lastRenewalAttempt: Date | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
errorMessage?: string | null;
|
||||
renewalCount: number;
|
||||
}
|
||||
8
server/routers/domain/types.ts
Normal file
8
server/routers/domain/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type CheckDomainAvailabilityResponse = {
|
||||
available: boolean;
|
||||
options: {
|
||||
domainNamespaceId: string;
|
||||
domainId: string;
|
||||
fullDomain: string;
|
||||
}[];
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -6,8 +6,6 @@ import * as badger from "./badger";
|
||||
import * as auth from "@server/routers/auth";
|
||||
import * as supporterKey from "@server/routers/supporterKey";
|
||||
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,
|
||||
@@ -48,34 +46,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
|
||||
@@ -87,10 +62,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);
|
||||
|
||||
11
server/routers/loginPage/types.ts
Normal file
11
server/routers/loginPage/types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { LoginPage } from "@server/db";
|
||||
|
||||
export type CreateLoginPageResponse = LoginPage;
|
||||
|
||||
export type DeleteLoginPageResponse = LoginPage;
|
||||
|
||||
export type GetLoginPageResponse = LoginPage;
|
||||
|
||||
export type UpdateLoginPageResponse = LoginPage;
|
||||
|
||||
export type LoadLoginPageResponse = LoginPage & { orgId: string };
|
||||
27
server/routers/orgIdp/types.ts
Normal file
27
server/routers/orgIdp/types.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Idp, IdpOidcConfig } from "@server/db";
|
||||
|
||||
export type CreateOrgIdpResponse = {
|
||||
idpId: number;
|
||||
redirectUrl: string;
|
||||
};
|
||||
|
||||
export type GetOrgIdpResponse = {
|
||||
idp: Idp,
|
||||
idpOidcConfig: IdpOidcConfig | null,
|
||||
redirectUrl: string
|
||||
}
|
||||
|
||||
export type ListOrgIdpsResponse = {
|
||||
idps: {
|
||||
idpId: number;
|
||||
orgId: string;
|
||||
name: string;
|
||||
type: string;
|
||||
variant: string;
|
||||
}[],
|
||||
pagination: {
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
};
|
||||
};
|
||||
34
server/routers/remoteExitNode/types.ts
Normal file
34
server/routers/remoteExitNode/types.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { RemoteExitNode } from "@server/db";
|
||||
|
||||
export type CreateRemoteExitNodeResponse = {
|
||||
token: string;
|
||||
remoteExitNodeId: string;
|
||||
secret: string;
|
||||
};
|
||||
|
||||
export type PickRemoteExitNodeDefaultsResponse = {
|
||||
remoteExitNodeId: string;
|
||||
secret: string;
|
||||
};
|
||||
|
||||
export type QuickStartRemoteExitNodeResponse = {
|
||||
remoteExitNodeId: string;
|
||||
secret: string;
|
||||
};
|
||||
|
||||
export type ListRemoteExitNodesResponse = {
|
||||
remoteExitNodes: {
|
||||
remoteExitNodeId: string;
|
||||
dateCreated: string;
|
||||
version: string | null;
|
||||
exitNodeId: number | null;
|
||||
name: string;
|
||||
address: string;
|
||||
endpoint: string;
|
||||
online: boolean;
|
||||
type: string | null;
|
||||
}[];
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
};
|
||||
|
||||
export type GetRemoteExitNodeResponse = { remoteExitNodeId: string; dateCreated: string; version: string | null; exitNodeId: number | null; name: string; address: string; endpoint: string; online: boolean; type: string | null; }
|
||||
Reference in New Issue
Block a user