From c7a40d59b7cd050f689b133ea85bf76cf722f956 Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 12 Oct 2025 16:34:36 -0700 Subject: [PATCH] Seperate managed node code to fosrl/pangolin-node --- server/auth/sessions/resource.ts | 26 -- server/db/queries/verifySessionQueries.ts | 172 ------------- server/emails/index.ts | 5 - server/hybridServer.ts | 151 ------------ server/index.ts | 26 +- server/lib/certificates.ts | 67 ----- server/lib/config.ts | 9 +- server/lib/geoip.ts | 33 +-- server/lib/readConfigFile.ts | 22 +- server/lib/remoteProxy.ts | 73 ------ server/lib/tokenManager.ts | 274 --------------------- server/lib/traefik/TraefikConfigManager.ts | 54 ++-- server/private/lib/certificates.ts | 18 +- server/routers/badger/verifySession.ts | 97 ++++---- server/routers/gerbil/getConfig.ts | 11 - server/routers/internal.ts | 43 +--- server/setup/ensureSetupToken.ts | 6 - 17 files changed, 90 insertions(+), 997 deletions(-) delete mode 100644 server/hybridServer.ts delete mode 100644 server/lib/remoteProxy.ts delete mode 100644 server/lib/tokenManager.ts diff --git a/server/auth/sessions/resource.ts b/server/auth/sessions/resource.ts index a378202e..31ab2b38 100644 --- a/server/auth/sessions/resource.ts +++ b/server/auth/sessions/resource.ts @@ -4,9 +4,6 @@ import { resourceSessions, ResourceSession } from "@server/db"; import { db } from "@server/db"; import { eq, and } from "drizzle-orm"; import config from "@server/lib/config"; -import axios from "axios"; -import logger from "@server/logger"; -import { tokenManager } from "@server/lib/tokenManager"; export const SESSION_COOKIE_NAME = config.getRawConfig().server.session_cookie_name; @@ -65,29 +62,6 @@ export async function validateResourceSessionToken( token: string, resourceId: number ): Promise { - if (config.isManagedMode()) { - try { - const response = await axios.post(`${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/resource/${resourceId}/session/validate`, { - token: token - }, await tokenManager.getAuthHeader()); - return response.data.data; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error validating resource session token in hybrid mode:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error validating resource session token in hybrid mode:", error); - } - return { resourceSession: null }; - } - } - const sessionId = encodeHexLowerCase( sha256(new TextEncoder().encode(token)) ); diff --git a/server/db/queries/verifySessionQueries.ts b/server/db/queries/verifySessionQueries.ts index 09c465b5..8944a491 100644 --- a/server/db/queries/verifySessionQueries.ts +++ b/server/db/queries/verifySessionQueries.ts @@ -17,10 +17,6 @@ import { users } from "@server/db"; import { and, eq } from "drizzle-orm"; -import axios from "axios"; -import config from "@server/lib/config"; -import logger from "@server/logger"; -import { tokenManager } from "@server/lib/tokenManager"; export type ResourceWithAuth = { resource: Resource | null; @@ -40,30 +36,6 @@ export type UserSessionWithUser = { export async function getResourceByDomain( domain: string ): Promise { - if (config.isManagedMode()) { - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/resource/domain/${domain}`, - await tokenManager.getAuthHeader() - ); - return response.data.data; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error fetching config in verify session:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error fetching config in verify session:", error); - } - return null; - } - } - const [result] = await db .select() .from(resources) @@ -100,30 +72,6 @@ export async function getResourceByDomain( export async function getUserSessionWithUser( userSessionId: string ): Promise { - if (config.isManagedMode()) { - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/session/${userSessionId}`, - await tokenManager.getAuthHeader() - ); - return response.data.data; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error fetching config in verify session:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error fetching config in verify session:", error); - } - return null; - } - } - const [res] = await db .select() .from(sessions) @@ -144,30 +92,6 @@ export async function getUserSessionWithUser( * Get user organization role */ export async function getUserOrgRole(userId: string, orgId: string) { - if (config.isManagedMode()) { - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/user/${userId}/org/${orgId}/role`, - await tokenManager.getAuthHeader() - ); - return response.data.data; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error fetching config in verify session:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error fetching config in verify session:", error); - } - return null; - } - } - const userOrgRole = await db .select() .from(userOrgs) @@ -184,30 +108,6 @@ export async function getRoleResourceAccess( resourceId: number, roleId: number ) { - if (config.isManagedMode()) { - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/role/${roleId}/resource/${resourceId}/access`, - await tokenManager.getAuthHeader() - ); - return response.data.data; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error fetching config in verify session:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error fetching config in verify session:", error); - } - return null; - } - } - const roleResourceAccess = await db .select() .from(roleResources) @@ -229,30 +129,6 @@ export async function getUserResourceAccess( userId: string, resourceId: number ) { - if (config.isManagedMode()) { - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/user/${userId}/resource/${resourceId}/access`, - await tokenManager.getAuthHeader() - ); - return response.data.data; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error fetching config in verify session:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error fetching config in verify session:", error); - } - return null; - } - } - const userResourceAccess = await db .select() .from(userResources) @@ -273,30 +149,6 @@ export async function getUserResourceAccess( export async function getResourceRules( resourceId: number ): Promise { - if (config.isManagedMode()) { - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/resource/${resourceId}/rules`, - await tokenManager.getAuthHeader() - ); - return response.data.data; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error fetching config in verify session:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error fetching config in verify session:", error); - } - return []; - } - } - const rules = await db .select() .from(resourceRules) @@ -311,30 +163,6 @@ export async function getResourceRules( export async function getOrgLoginPage( orgId: string ): Promise { - if (config.isManagedMode()) { - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/org/${orgId}/login-page`, - await tokenManager.getAuthHeader() - ); - return response.data.data; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error fetching config in verify session:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error fetching config in verify session:", error); - } - return null; - } - } - const [result] = await db .select() .from(loginPageOrg) diff --git a/server/emails/index.ts b/server/emails/index.ts index 2cdef8a1..42cfa39c 100644 --- a/server/emails/index.ts +++ b/server/emails/index.ts @@ -6,11 +6,6 @@ import logger from "@server/logger"; import SMTPTransport from "nodemailer/lib/smtp-transport"; function createEmailClient() { - if (config.isManagedMode()) { - // LETS NOT WORRY ABOUT EMAILS IN HYBRID - return; - } - const emailConfig = config.getRawConfig().email; if (!emailConfig) { logger.warn( diff --git a/server/hybridServer.ts b/server/hybridServer.ts deleted file mode 100644 index 7e9ce095..00000000 --- a/server/hybridServer.ts +++ /dev/null @@ -1,151 +0,0 @@ -import logger from "@server/logger"; -import config from "@server/lib/config"; -import { createWebSocketClient } from "./routers/ws/client"; -import { addPeer, deletePeer } from "./routers/gerbil/peers"; -import { db, exitNodes } from "./db"; -import { TraefikConfigManager } from "./lib/traefik/TraefikConfigManager"; -import { tokenManager } from "./lib/tokenManager"; -import { APP_VERSION } from "./lib/consts"; -import axios from "axios"; - -export async function createHybridClientServer() { - logger.info("Starting hybrid client server..."); - - // Start the token manager - await tokenManager.start(); - - const token = await tokenManager.getToken(); - - const monitor = new TraefikConfigManager(); - - await monitor.start(); - - // Create client - const client = createWebSocketClient( - token, - config.getRawConfig().managed!.endpoint!, - { - reconnectInterval: 5000, - pingInterval: 30000, - pingTimeout: 10000 - } - ); - - // Register message handlers - client.registerHandler("remoteExitNode/peers/add", async (message) => { - const { publicKey, allowedIps } = message.data; - - // TODO: we are getting the exit node twice here - // NOTE: there should only be one gerbil registered so... - const [exitNode] = await db.select().from(exitNodes).limit(1); - await addPeer(exitNode.exitNodeId, { - publicKey: publicKey, - allowedIps: allowedIps || [] - }); - }); - - client.registerHandler("remoteExitNode/peers/remove", async (message) => { - const { publicKey } = message.data; - - // TODO: we are getting the exit node twice here - // NOTE: there should only be one gerbil registered so... - const [exitNode] = await db.select().from(exitNodes).limit(1); - await deletePeer(exitNode.exitNodeId, publicKey); - }); - - // /update-proxy-mapping - client.registerHandler("remoteExitNode/update-proxy-mapping", async (message) => { - try { - const [exitNode] = await db.select().from(exitNodes).limit(1); - if (!exitNode) { - logger.error("No exit node found for proxy mapping update"); - return; - } - - const response = await axios.post(`${exitNode.endpoint}/update-proxy-mapping`, message.data); - logger.info(`Successfully updated proxy mapping: ${response.status}`); - } catch (error) { - // pull data out of the axios error to log - if (axios.isAxiosError(error)) { - logger.error("Error updating proxy mapping:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error updating proxy mapping:", error); - } - } - }); - - // /update-destinations - client.registerHandler("remoteExitNode/update-destinations", async (message) => { - try { - const [exitNode] = await db.select().from(exitNodes).limit(1); - if (!exitNode) { - logger.error("No exit node found for destinations update"); - return; - } - - const response = await axios.post(`${exitNode.endpoint}/update-destinations`, message.data); - logger.info(`Successfully updated destinations: ${response.status}`); - } catch (error) { - // pull data out of the axios error to log - if (axios.isAxiosError(error)) { - logger.error("Error updating destinations:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error updating destinations:", error); - } - } - }); - - client.registerHandler("remoteExitNode/traefik/reload", async (message) => { - await monitor.HandleTraefikConfig(); - }); - - // Listen to connection events - client.on("connect", () => { - logger.info("Connected to WebSocket server"); - client.sendMessage("remoteExitNode/register", { - remoteExitNodeVersion: APP_VERSION - }); - }); - - client.on("disconnect", () => { - logger.info("Disconnected from WebSocket server"); - }); - - client.on("message", (message) => { - logger.info( - `Received message: ${message.type} ${JSON.stringify(message.data)}` - ); - }); - - // Connect to the server - try { - await client.connect(); - logger.info("Connection initiated"); - } catch (error) { - logger.error("Failed to connect:", error); - } - - // Store the ping interval stop function for cleanup if needed - const stopPingInterval = client.sendMessageInterval( - "remoteExitNode/ping", - { timestamp: Date.now() / 1000 }, - 60000 - ); // send every minute - - // Return client and cleanup function for potential use - return { client, stopPingInterval }; -} diff --git a/server/index.ts b/server/index.ts index f4571422..a92968a6 100644 --- a/server/index.ts +++ b/server/index.ts @@ -5,9 +5,15 @@ import { runSetupFunctions } from "./setup"; import { createApiServer } from "./apiServer"; import { createNextServer } from "./nextServer"; import { createInternalServer } from "./internalServer"; -import { ApiKey, ApiKeyOrg, RemoteExitNode, Session, User, UserOrg } from "@server/db"; +import { + ApiKey, + ApiKeyOrg, + RemoteExitNode, + Session, + User, + UserOrg +} from "@server/db"; import { createIntegrationApiServer } from "./integrationApiServer"; -import { createHybridClientServer } from "./hybridServer"; import config from "@server/lib/config"; import { setHostMeta } from "@server/lib/hostMeta"; import { initTelemetryClient } from "./lib/telemetry.js"; @@ -26,16 +32,11 @@ async function startServers() { const apiServer = createApiServer(); const internalServer = createInternalServer(); - let hybridClientServer; let nextServer; - if (config.isManagedMode()) { - hybridClientServer = await createHybridClientServer(); - } else { - nextServer = await createNextServer(); - if (config.getRawConfig().traefik.file_mode) { - const monitor = new TraefikConfigManager(); - await monitor.start(); - } + nextServer = await createNextServer(); + if (config.getRawConfig().traefik.file_mode) { + const monitor = new TraefikConfigManager(); + await monitor.start(); } let integrationServer; @@ -49,8 +50,7 @@ async function startServers() { apiServer, nextServer, internalServer, - integrationServer, - hybridClientServer + integrationServer }; } diff --git a/server/lib/certificates.ts b/server/lib/certificates.ts index 4032d1ec..a6c51c96 100644 --- a/server/lib/certificates.ts +++ b/server/lib/certificates.ts @@ -1,70 +1,3 @@ -import axios from "axios"; -import { tokenManager } from "./tokenManager"; -import logger from "@server/logger"; -import config from "./config"; - -/** - * Get valid certificates for the specified domains - */ -export async function getValidCertificatesForDomainsHybrid(domains: Set): Promise< - Array<{ - id: number; - domain: string; - wildcard: boolean | null; - certFile: string | null; - keyFile: string | null; - expiresAt: number | null; - updatedAt?: number | null; - }> -> { - if (domains.size === 0) { - return []; - } - - const domainArray = Array.from(domains); - - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/certificates/domains`, - { - params: { - domains: domainArray - }, - headers: (await tokenManager.getAuthHeader()).headers - } - ); - - if (response.status !== 200) { - logger.error( - `Failed to fetch certificates for domains: ${response.status} ${response.statusText}`, - { responseData: response.data, domains: domainArray } - ); - return []; - } - - // logger.debug( - // `Successfully retrieved ${response.data.data?.length || 0} certificates for ${domainArray.length} domains` - // ); - - return response.data.data; - } catch (error) { - // pull data out of the axios error to log - if (axios.isAxiosError(error)) { - logger.error("Error getting certificates:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error getting certificates:", error); - } - return []; - } -} - export async function getValidCertificatesForDomains(domains: Set): Promise< Array<{ id: number; diff --git a/server/lib/config.ts b/server/lib/config.ts index 103ea5ae..63053af8 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -101,10 +101,7 @@ export class Config { if (!this.rawConfig) { throw new Error("Config not loaded. Call load() first."); } - if (this.rawConfig.managed) { - // LETS NOT WORRY ABOUT THE SERVER SECRET WHEN MANAGED - return; - } + license.setServerSecret(this.rawConfig.server.secret!); await this.checkKeyStatus(); @@ -157,10 +154,6 @@ export class Config { return false; } - public isManagedMode() { - return typeof this.rawConfig?.managed === "object"; - } - public async checkSupporterKey() { const [key] = await db.select().from(supporterKey).limit(1); diff --git a/server/lib/geoip.ts b/server/lib/geoip.ts index d6252360..ac739fa3 100644 --- a/server/lib/geoip.ts +++ b/server/lib/geoip.ts @@ -1,8 +1,5 @@ import logger from "@server/logger"; import { maxmindLookup } from "@server/db/maxmind"; -import axios from "axios"; -import config from "./config"; -import { tokenManager } from "./tokenManager"; export async function getCountryCodeForIp( ip: string @@ -33,32 +30,4 @@ export async function getCountryCodeForIp( } return; -} - -export async function remoteGetCountryCodeForIp( - ip: string -): Promise { - try { - const response = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/geoip/${ip}`, - await tokenManager.getAuthHeader() - ); - - return response.data.data.countryCode; - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error fetching config in verify session:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error fetching config in verify session:", error); - } - } - - return; -} +} \ No newline at end of file diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts index ea872252..0340e21d 100644 --- a/server/lib/readConfigFile.ts +++ b/server/lib/readConfigFile.ts @@ -39,15 +39,6 @@ export const configSchema = z anonymous_usage: true } }), - managed: z - .object({ - name: z.string().optional(), - id: z.string().optional(), - secret: z.string().optional(), - endpoint: z.string().optional().default("https://pangolin.fossorial.io"), - redirect_endpoint: z.string().optional() - }) - .optional(), domains: z .record( z.string(), @@ -320,10 +311,7 @@ export const configSchema = z if (data.flags?.disable_config_managed_domains) { return true; } - // If hybrid is defined, domains are not required - if (data.managed) { - return true; - } + if (keys.length === 0) { return false; } @@ -335,10 +323,6 @@ export const configSchema = z ) .refine( (data) => { - // If hybrid is defined, server secret is not required - if (data.managed) { - return true; - } // If hybrid is not defined, server secret must be defined. If its not defined already then pull it from env if (data.server?.secret === undefined) { data.server.secret = process.env.SERVER_SECRET; @@ -351,10 +335,6 @@ export const configSchema = z ) .refine( (data) => { - // If hybrid is defined, dashboard_url is not required - if (data.managed) { - return true; - } // If hybrid is not defined, dashboard_url must be defined return data.app.dashboard_url !== undefined && data.app.dashboard_url.length > 0; }, diff --git a/server/lib/remoteProxy.ts b/server/lib/remoteProxy.ts deleted file mode 100644 index c9016071..00000000 --- a/server/lib/remoteProxy.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { Router } from "express"; -import axios from "axios"; -import HttpCode from "@server/types/HttpCode"; -import createHttpError from "http-errors"; -import logger from "@server/logger"; -import config from "@server/lib/config"; -import { tokenManager } from "./tokenManager"; - -/** - * Proxy function that forwards requests to the remote cloud server - */ - -export const proxyToRemote = async ( - req: Request, - res: Response, - next: NextFunction, - endpoint: string -): Promise => { - try { - const remoteUrl = `${config.getRawConfig().managed?.endpoint?.replace(/\/$/, '')}/api/v1/${endpoint}`; - - logger.debug(`Proxying request to remote server: ${remoteUrl}`); - - // Forward the request to the remote server - const response = await axios({ - method: req.method as any, - url: remoteUrl, - data: req.body, - headers: { - 'Content-Type': 'application/json', - ...(await tokenManager.getAuthHeader()).headers - }, - params: req.query, - timeout: 30000, // 30 second timeout - validateStatus: () => true // Don't throw on non-2xx status codes - }); - - logger.debug(`Proxy response: ${JSON.stringify(response.data)}`); - - // Forward the response status and data - return res.status(response.status).json(response.data); - - } catch (error) { - logger.error("Error proxying request to remote server:", error); - - if (axios.isAxiosError(error)) { - if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') { - return next( - createHttpError( - HttpCode.SERVICE_UNAVAILABLE, - "Remote server is unavailable" - ) - ); - } - if (error.code === 'ECONNABORTED') { - return next( - createHttpError( - HttpCode.REQUEST_TIMEOUT, - "Request to remote server timed out" - ) - ); - } - } - - return next( - createHttpError( - HttpCode.INTERNAL_SERVER_ERROR, - "Error communicating with remote server" - ) - ); - } -}; \ No newline at end of file diff --git a/server/lib/tokenManager.ts b/server/lib/tokenManager.ts deleted file mode 100644 index 2e0e1118..00000000 --- a/server/lib/tokenManager.ts +++ /dev/null @@ -1,274 +0,0 @@ -import axios from "axios"; -import config from "@server/lib/config"; -import logger from "@server/logger"; - -export interface TokenResponse { - success: boolean; - message?: string; - data: { - token: string; - }; -} - -/** - * Token Manager - Handles automatic token refresh for hybrid server authentication - * - * Usage throughout the application: - * ```typescript - * import { tokenManager } from "@server/lib/tokenManager"; - * - * // Get the current valid token - * const token = await tokenManager.getToken(); - * - * // Force refresh if needed - * await tokenManager.refreshToken(); - * ``` - * - * The token manager automatically refreshes tokens every 24 hours by default - * and is started once in the privateHybridServer.ts file. - */ - -export class TokenManager { - private token: string | null = null; - private refreshInterval: NodeJS.Timeout | null = null; - private isRefreshing: boolean = false; - private refreshIntervalMs: number; - private retryInterval: NodeJS.Timeout | null = null; - private retryIntervalMs: number; - private tokenAvailablePromise: Promise | null = null; - private tokenAvailableResolve: (() => void) | null = null; - - constructor(refreshIntervalMs: number = 24 * 60 * 60 * 1000, retryIntervalMs: number = 5000) { - // Default to 24 hours for refresh, 5 seconds for retry - this.refreshIntervalMs = refreshIntervalMs; - this.retryIntervalMs = retryIntervalMs; - this.setupTokenAvailablePromise(); - } - - /** - * Set up promise that resolves when token becomes available - */ - private setupTokenAvailablePromise(): void { - this.tokenAvailablePromise = new Promise((resolve) => { - this.tokenAvailableResolve = resolve; - }); - } - - /** - * Resolve the token available promise - */ - private resolveTokenAvailable(): void { - if (this.tokenAvailableResolve) { - this.tokenAvailableResolve(); - this.tokenAvailableResolve = null; - } - } - - /** - * Start the token manager - gets initial token and sets up refresh interval - * If initial token fetch fails, keeps retrying every few seconds until successful - */ - async start(): Promise { - logger.info("Starting token manager..."); - - try { - await this.refreshToken(); - this.setupRefreshInterval(); - this.resolveTokenAvailable(); - logger.info("Token manager started successfully"); - } catch (error) { - logger.warn(`Failed to get initial token, will retry in ${this.retryIntervalMs / 1000} seconds:`, error); - this.setupRetryInterval(); - } - } - - /** - * Set up retry interval for initial token acquisition - */ - private setupRetryInterval(): void { - if (this.retryInterval) { - clearInterval(this.retryInterval); - } - - this.retryInterval = setInterval(async () => { - try { - logger.debug("Retrying initial token acquisition"); - await this.refreshToken(); - this.setupRefreshInterval(); - this.clearRetryInterval(); - this.resolveTokenAvailable(); - logger.info("Token manager started successfully after retry"); - } catch (error) { - logger.debug("Token acquisition retry failed, will try again"); - } - }, this.retryIntervalMs); - } - - /** - * Clear retry interval - */ - private clearRetryInterval(): void { - if (this.retryInterval) { - clearInterval(this.retryInterval); - this.retryInterval = null; - } - } - - /** - * Stop the token manager and clear all intervals - */ - stop(): void { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - this.refreshInterval = null; - } - this.clearRetryInterval(); - logger.info("Token manager stopped"); - } - - /** - * Get the current valid token - */ - - // TODO: WE SHOULD NOT BE GETTING A TOKEN EVERY TIME WE REQUEST IT - async getToken(): Promise { - // If we don't have a token yet, wait for it to become available - if (!this.token && this.tokenAvailablePromise) { - await this.tokenAvailablePromise; - } - - if (!this.token) { - if (this.isRefreshing) { - // Wait for current refresh to complete - await this.waitForRefresh(); - } else { - throw new Error("No valid token available"); - } - } - - if (!this.token) { - throw new Error("No valid token available"); - } - - return this.token; - } - - async getAuthHeader() { - return { - headers: { - Authorization: `Bearer ${await this.getToken()}`, - "X-CSRF-Token": "x-csrf-protection", - } - }; - } - - /** - * Force refresh the token - */ - async refreshToken(): Promise { - if (this.isRefreshing) { - await this.waitForRefresh(); - return; - } - - this.isRefreshing = true; - - try { - const hybridConfig = config.getRawConfig().managed; - - if ( - !hybridConfig?.id || - !hybridConfig?.secret || - !hybridConfig?.endpoint - ) { - throw new Error("Hybrid configuration is not defined"); - } - - const tokenEndpoint = `${hybridConfig.endpoint}/api/v1/auth/remoteExitNode/get-token`; - - const tokenData = { - remoteExitNodeId: hybridConfig.id, - secret: hybridConfig.secret - }; - - logger.debug("Requesting new token from server"); - - const response = await axios.post( - tokenEndpoint, - tokenData, - { - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": "x-csrf-protection" - }, - timeout: 10000 // 10 second timeout - } - ); - - if (!response.data.success) { - throw new Error( - `Failed to get token: ${response.data.message}` - ); - } - - if (!response.data.data.token) { - throw new Error("Received empty token from server"); - } - - this.token = response.data.data.token; - logger.debug("Token refreshed successfully"); - } catch (error) { - if (axios.isAxiosError(error)) { - logger.error("Error updating proxy mapping:", { - message: error.message, - code: error.code, - status: error.response?.status, - statusText: error.response?.statusText, - url: error.config?.url, - method: error.config?.method - }); - } else { - logger.error("Error updating proxy mapping:", error); - } - - throw new Error("Failed to refresh token"); - } finally { - this.isRefreshing = false; - } - } - - /** - * Set up automatic token refresh interval - */ - private setupRefreshInterval(): void { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - - this.refreshInterval = setInterval(async () => { - try { - logger.debug("Auto-refreshing token"); - await this.refreshToken(); - } catch (error) { - logger.error("Failed to auto-refresh token:", error); - } - }, this.refreshIntervalMs); - } - - /** - * Wait for current refresh operation to complete - */ - private async waitForRefresh(): Promise { - return new Promise((resolve) => { - const checkInterval = setInterval(() => { - if (!this.isRefreshing) { - clearInterval(checkInterval); - resolve(); - } - }, 100); - }); - } -} - -// Export a singleton instance for use throughout the application -export const tokenManager = new TokenManager(); diff --git a/server/lib/traefik/TraefikConfigManager.ts b/server/lib/traefik/TraefikConfigManager.ts index 435a749f..017678f1 100644 --- a/server/lib/traefik/TraefikConfigManager.ts +++ b/server/lib/traefik/TraefikConfigManager.ts @@ -6,12 +6,10 @@ import * as yaml from "js-yaml"; import axios from "axios"; import { db, exitNodes } from "@server/db"; import { eq } from "drizzle-orm"; -import { tokenManager } from "../tokenManager"; import { getCurrentExitNodeId } from "@server/lib/exitNodes"; import { getTraefikConfig } from "#dynamic/lib/traefik"; import { getValidCertificatesForDomains, - getValidCertificatesForDomainsHybrid } from "#dynamic/lib/certificates"; import { sendToExitNode } from "#dynamic/lib/exitNodes"; import { build } from "@server/build"; @@ -348,17 +346,8 @@ export class TraefikConfigManager { if (domainsToFetch.size > 0) { // Get valid certificates for domains not covered by wildcards - if (config.isManagedMode()) { - validCertificates = - await getValidCertificatesForDomainsHybrid( - domainsToFetch - ); - } else { - validCertificates = - await getValidCertificatesForDomains( - domainsToFetch - ); - } + validCertificates = + await getValidCertificatesForDomains(domainsToFetch); this.lastCertificateFetch = new Date(); this.lastKnownDomains = new Set(domains); @@ -448,32 +437,15 @@ export class TraefikConfigManager { } | null> { let traefikConfig; try { - if (config.isManagedMode()) { - const resp = await axios.get( - `${config.getRawConfig().managed?.endpoint}/api/v1/hybrid/traefik-config`, - await tokenManager.getAuthHeader() - ); - - if (resp.status !== 200) { - logger.error( - `Failed to fetch traefik config: ${resp.status} ${resp.statusText}`, - { responseData: resp.data } - ); - return null; - } - - traefikConfig = resp.data.data; - } else { - const currentExitNode = await getCurrentExitNodeId(); - // logger.debug(`Fetching traefik config for exit node: ${currentExitNode}`); - traefikConfig = await getTraefikConfig( - // this is called by the local exit node to get its own config - currentExitNode, - config.getRawConfig().traefik.site_types, - build == "oss", // filter out the namespace domains in open source - build != "oss" // generate the login pages on the cloud and hybrid - ); - } + const currentExitNode = await getCurrentExitNodeId(); + // logger.debug(`Fetching traefik config for exit node: ${currentExitNode}`); + traefikConfig = await getTraefikConfig( + // this is called by the local exit node to get its own config + currentExitNode, + config.getRawConfig().traefik.site_types, + build == "oss", // filter out the namespace domains in open source + build != "oss" // generate the login pages on the cloud and hybrid + ); const domains = new Set(); @@ -842,7 +814,9 @@ export class TraefikConfigManager { const lastUpdateStr = fs .readFileSync(lastUpdatePath, "utf8") .trim(); - lastUpdateTime = Math.floor(new Date(lastUpdateStr).getTime() / 1000); + lastUpdateTime = Math.floor( + new Date(lastUpdateStr).getTime() / 1000 + ); } catch { lastUpdateTime = null; } diff --git a/server/private/lib/certificates.ts b/server/private/lib/certificates.ts index 0924daf7..93eb5603 100644 --- a/server/private/lib/certificates.ts +++ b/server/private/lib/certificates.ts @@ -97,20 +97,4 @@ export async function getValidCertificatesForDomains( }); return validCertsDecrypted; -} - -export async function getValidCertificatesForDomainsHybrid( - domains: Set -): Promise< - Array<{ - id: number; - domain: string; - wildcard: boolean | null; - certFile: string | null; - keyFile: string | null; - expiresAt: number | null; - updatedAt?: number | null; - }> -> { - return []; // stub -} +} \ No newline at end of file diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 4a000144..44bc32a5 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -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 { 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 { return cachedCountryCode?.toUpperCase() === countryCode.toUpperCase(); } -function extractBasicAuth(headers: Record | undefined): string | undefined { +function extractBasicAuth( + headers: Record | undefined +): string | undefined { if (!headers || (!headers.authorization && !headers.Authorization)) { return; } @@ -833,8 +841,9 @@ function extractBasicAuth(headers: Record | 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" + }); } } diff --git a/server/routers/gerbil/getConfig.ts b/server/routers/gerbil/getConfig.ts index 1604dc30..bb581ced 100644 --- a/server/routers/gerbil/getConfig.ts +++ b/server/routers/gerbil/getConfig.ts @@ -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); diff --git a/server/routers/internal.ts b/server/routers/internal.ts index 10966bb5..30cde061 100644 --- a/server/routers/internal.ts +++ b/server/routers/internal.ts @@ -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); -} \ No newline at end of file +badgerRouter.post("/exchange-session", badger.exchangeSession); diff --git a/server/setup/ensureSetupToken.ts b/server/setup/ensureSetupToken.ts index 078c99ee..1734b5e6 100644 --- a/server/setup/ensureSetupToken.ts +++ b/server/setup/ensureSetupToken.ts @@ -3,7 +3,6 @@ import { eq } from "drizzle-orm"; import { generateRandomString, RandomReader } from "@oslojs/crypto/random"; import moment from "moment"; import logger from "@server/logger"; -import config from "@server/lib/config"; const random: RandomReader = { read(bytes: Uint8Array): void { @@ -23,11 +22,6 @@ function generateId(length: number): string { } export async function ensureSetupToken() { - if (config.isManagedMode()) { - // LETS NOT WORRY ABOUT THE SERVER SECRET WHEN HYBRID - return; - } - try { // Check if a server admin already exists const [existingAdmin] = await db