Chungus 2.0

This commit is contained in:
Owen
2025-10-10 11:27:15 -07:00
parent f64a477c3d
commit d92b87b7c8
224 changed files with 1507 additions and 1586 deletions

View File

@@ -2,15 +2,10 @@ import { defineConfig } from "drizzle-kit";
import path from "path"; import path from "path";
import { build } from "@server/build"; import { build } from "@server/build";
let schema; const schema = [
if (build === "oss") { path.join("server", "db", "pg", "schema.ts"),
schema = [path.join("server", "db", "pg", "schema.ts")]; path.join("server", "db", "pg", "pSchema.ts")
} else { ];
schema = [
path.join("server", "db", "pg", "schema.ts"),
path.join("server", "db", "pg", "privateSchema.ts")
];
}
export default defineConfig({ export default defineConfig({
dialect: "postgresql", dialect: "postgresql",

View File

@@ -3,15 +3,10 @@ import { APP_PATH } from "@server/lib/consts";
import { defineConfig } from "drizzle-kit"; import { defineConfig } from "drizzle-kit";
import path from "path"; import path from "path";
let schema; const schema = [
if (build === "oss") { path.join("server", "db", "sqlite", "schema.ts"),
schema = [path.join("server", "db", "sqlite", "schema.ts")]; path.join("server", "db", "sqlite", "pSchema.ts")
} else { ];
schema = [
path.join("server", "db", "sqlite", "schema.ts"),
path.join("server", "db", "sqlite", "privateSchema.ts")
];
}
export default defineConfig({ export default defineConfig({
dialect: "sqlite", dialect: "sqlite",

View File

@@ -2,8 +2,9 @@ import esbuild from "esbuild";
import yargs from "yargs"; import yargs from "yargs";
import { hideBin } from "yargs/helpers"; import { hideBin } from "yargs/helpers";
import { nodeExternalsPlugin } from "esbuild-node-externals"; import { nodeExternalsPlugin } from "esbuild-node-externals";
import path from "path";
import fs from "fs";
// import { glob } from "glob"; // import { glob } from "glob";
// import path from "path";
const banner = ` const banner = `
// patch __dirname // patch __dirname
@@ -18,7 +19,7 @@ const require = topLevelCreateRequire(import.meta.url);
`; `;
const argv = yargs(hideBin(process.argv)) const argv = yargs(hideBin(process.argv))
.usage("Usage: $0 -entry [string] -out [string]") .usage("Usage: $0 -entry [string] -out [string] -build [string]")
.option("entry", { .option("entry", {
alias: "e", alias: "e",
describe: "Entry point file", describe: "Entry point file",
@@ -31,6 +32,13 @@ const argv = yargs(hideBin(process.argv))
type: "string", type: "string",
demandOption: true, demandOption: true,
}) })
.option("build", {
alias: "b",
describe: "Build type (oss, saas, enterprise)",
type: "string",
choices: ["oss", "saas", "enterprise"],
default: "oss",
})
.help() .help()
.alias("help", "h").argv; .alias("help", "h").argv;
@@ -46,6 +54,179 @@ function getPackagePaths() {
return ["package.json"]; return ["package.json"];
} }
// Plugin to guard against bad imports from #private
function privateImportGuardPlugin() {
return {
name: "private-import-guard",
setup(build) {
const violations = [];
build.onResolve({ filter: /^#private\// }, (args) => {
const importingFile = args.importer;
// Check if the importing file is NOT in server/private
const normalizedImporter = path.normalize(importingFile);
const isInServerPrivate = normalizedImporter.includes(path.normalize("server/private"));
if (!isInServerPrivate) {
const violation = {
file: importingFile,
importPath: args.path,
resolveDir: args.resolveDir
};
violations.push(violation);
console.log(`PRIVATE IMPORT VIOLATION:`);
console.log(` File: ${importingFile}`);
console.log(` Import: ${args.path}`);
console.log(` Resolve dir: ${args.resolveDir || 'N/A'}`);
console.log('');
}
// Return null to let the default resolver handle it
return null;
});
build.onEnd((result) => {
if (violations.length > 0) {
console.log(`\nSUMMARY: Found ${violations.length} private import violation(s):`);
violations.forEach((v, i) => {
console.log(` ${i + 1}. ${path.relative(process.cwd(), v.file)} imports ${v.importPath}`);
});
console.log('');
result.errors.push({
text: `Private import violations detected: ${violations.length} violation(s) found`,
location: null,
notes: violations.map(v => ({
text: `${path.relative(process.cwd(), v.file)} imports ${v.importPath}`,
location: null
}))
});
}
});
}
};
}
// Plugin to guard against bad imports from #private
function dynamicImportGuardPlugin() {
return {
name: "dynamic-import-guard",
setup(build) {
const violations = [];
build.onResolve({ filter: /^#dynamic\// }, (args) => {
const importingFile = args.importer;
// Check if the importing file is NOT in server/private
const normalizedImporter = path.normalize(importingFile);
const isInServerPrivate = normalizedImporter.includes(path.normalize("server/private"));
if (isInServerPrivate) {
const violation = {
file: importingFile,
importPath: args.path,
resolveDir: args.resolveDir
};
violations.push(violation);
console.log(`DYNAMIC IMPORT VIOLATION:`);
console.log(` File: ${importingFile}`);
console.log(` Import: ${args.path}`);
console.log(` Resolve dir: ${args.resolveDir || 'N/A'}`);
console.log('');
}
// Return null to let the default resolver handle it
return null;
});
build.onEnd((result) => {
if (violations.length > 0) {
console.log(`\nSUMMARY: Found ${violations.length} dynamic import violation(s):`);
violations.forEach((v, i) => {
console.log(` ${i + 1}. ${path.relative(process.cwd(), v.file)} imports ${v.importPath}`);
});
console.log('');
result.errors.push({
text: `Dynamic import violations detected: ${violations.length} violation(s) found`,
location: null,
notes: violations.map(v => ({
text: `${path.relative(process.cwd(), v.file)} imports ${v.importPath}`,
location: null
}))
});
}
});
}
};
}
// Plugin to dynamically switch imports based on build type
function dynamicImportSwitcherPlugin(buildValue) {
return {
name: "dynamic-import-switcher",
setup(build) {
const switches = [];
build.onStart(() => {
console.log(`Dynamic import switcher using build type: ${buildValue}`);
});
build.onResolve({ filter: /^#dynamic\// }, (args) => {
// Extract the path after #dynamic/
const dynamicPath = args.path.replace(/^#dynamic\//, '');
// Determine the replacement based on build type
let replacement;
if (buildValue === "oss") {
replacement = `#open/${dynamicPath}`;
} else if (buildValue === "saas" || buildValue === "enterprise") {
replacement = `#closed/${dynamicPath}`; // We use #closed here so that the route guards dont complain after its been changed but this is the same as #private
} else {
console.warn(`Unknown build type '${buildValue}', defaulting to #open/`);
replacement = `#open/${dynamicPath}`;
}
const switchInfo = {
file: args.importer,
originalPath: args.path,
replacementPath: replacement,
buildType: buildValue
};
switches.push(switchInfo);
console.log(`DYNAMIC IMPORT SWITCH:`);
console.log(` File: ${args.importer}`);
console.log(` Original: ${args.path}`);
console.log(` Switched to: ${replacement} (build: ${buildValue})`);
console.log('');
// Rewrite the import path and let the normal resolution continue
return build.resolve(replacement, {
importer: args.importer,
namespace: args.namespace,
resolveDir: args.resolveDir,
kind: args.kind
});
});
build.onEnd((result) => {
if (switches.length > 0) {
console.log(`\nDYNAMIC IMPORT SUMMARY: Switched ${switches.length} import(s) for build type '${buildValue}':`);
switches.forEach((s, i) => {
console.log(` ${i + 1}. ${path.relative(process.cwd(), s.file)}`);
console.log(` ${s.originalPath}${s.replacementPath}`);
});
console.log('');
}
});
}
};
}
esbuild esbuild
.build({ .build({
entryPoints: [argv.entry], entryPoints: [argv.entry],
@@ -59,6 +240,9 @@ esbuild
platform: "node", platform: "node",
external: ["body-parser"], external: ["body-parser"],
plugins: [ plugins: [
privateImportGuardPlugin(),
dynamicImportGuardPlugin(),
dynamicImportSwitcherPlugin(argv.build),
nodeExternalsPlugin({ nodeExternalsPlugin({
packagePath: getPackagePaths(), packagePath: getPackagePaths(),
}), }),
@@ -66,7 +250,27 @@ esbuild
sourcemap: "inline", sourcemap: "inline",
target: "node22", target: "node22",
}) })
.then(() => { .then((result) => {
// Check if there were any errors in the build result
if (result.errors && result.errors.length > 0) {
console.error(`Build failed with ${result.errors.length} error(s):`);
result.errors.forEach((error, i) => {
console.error(`${i + 1}. ${error.text}`);
if (error.notes) {
error.notes.forEach(note => {
console.error(` - ${note.text}`);
});
}
});
// remove the output file if it was created
if (fs.existsSync(argv.out)) {
fs.unlinkSync(argv.out);
}
process.exit(1);
}
console.log("Build completed successfully"); console.log("Build completed successfully");
}) })
.catch((error) => { .catch((error) => {

View File

@@ -7,21 +7,21 @@ import {
errorHandlerMiddleware, errorHandlerMiddleware,
notFoundMiddleware notFoundMiddleware
} from "@server/middlewares"; } from "@server/middlewares";
import { corsWithLoginPageSupport } from "@server/middlewares/private/corsWithLoginPage"; import { authenticated, unauthenticated } from "#dynamic/routers/external";
import { authenticated, unauthenticated } from "@server/routers/external"; import { router as wsRouter, handleWSUpgrade } from "#dynamic/routers/ws";
import { router as wsRouter, handleWSUpgrade } from "@server/routers/ws";
import { logIncomingMiddleware } from "./middlewares/logIncoming"; import { logIncomingMiddleware } from "./middlewares/logIncoming";
import { csrfProtectionMiddleware } from "./middlewares/csrfProtection"; import { csrfProtectionMiddleware } from "./middlewares/csrfProtection";
import helmet from "helmet"; import helmet from "helmet";
import { stripeWebhookHandler } from "@server/routers/private/billing/webhooks";
import { build } from "./build"; import { build } from "./build";
import rateLimit, { ipKeyGenerator } from "express-rate-limit"; import rateLimit, { ipKeyGenerator } from "express-rate-limit";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "./types/HttpCode"; import HttpCode from "./types/HttpCode";
import requestTimeoutMiddleware from "./middlewares/requestTimeout"; import requestTimeoutMiddleware from "./middlewares/requestTimeout";
import { createStore } from "@server/lib/private/rateLimitStore"; import { createStore } from "#dynamic/lib/rateLimitStore";
import hybridRouter from "@server/routers/private/hybrid";
import { stripDuplicateSesions } from "./middlewares/stripDuplicateSessions"; import { stripDuplicateSesions } from "./middlewares/stripDuplicateSessions";
import { corsWithLoginPageSupport } from "@server/lib/corsWithLoginPage";
import { hybridRouter } from "#dynamic/routers/hybrid";
import { billingWebhookHandler } from "#dynamic/routers/billing/webhooks";
const dev = config.isDev; const dev = config.isDev;
const externalPort = config.getRawConfig().server.external_port; const externalPort = config.getRawConfig().server.external_port;
@@ -39,7 +39,7 @@ export function createApiServer() {
apiServer.post( apiServer.post(
`${prefix}/billing/webhooks`, `${prefix}/billing/webhooks`,
express.raw({ type: "application/json" }), express.raw({ type: "application/json" }),
stripeWebhookHandler billingWebhookHandler
); );
} }

View File

@@ -4,7 +4,6 @@ import { userActions, roleActions, userOrgs } from "@server/db";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { sendUsageNotification } from "@server/routers/org";
export enum ActionsEnum { export enum ActionsEnum {
createOrgUser = "createOrgUser", createOrgUser = "createOrgUser",

13
server/cleanup.ts Normal file
View File

@@ -0,0 +1,13 @@
import { cleanup as wsCleanup } from "@server/routers/ws";
async function cleanup() {
await wsCleanup();
process.exit(0);
}
export async function initCleanup() {
// Handle process termination
process.on("SIGTERM", () => cleanup());
process.on("SIGINT", () => cleanup());
}

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { import {
pgTable, pgTable,
serial, serial,

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { import {
sqliteTable, sqliteTable,
integer, integer,

View File

@@ -2,7 +2,6 @@ import { render } from "@react-email/render";
import { ReactElement } from "react"; import { ReactElement } from "react";
import emailClient from "@server/emails"; import emailClient from "@server/emails";
import logger from "@server/logger"; import logger from "@server/logger";
import config from "@server/lib/config";
export async function sendEmail( export async function sendEmail(
template: ReactElement, template: ReactElement,
@@ -25,7 +24,7 @@ export async function sendEmail(
const emailHtml = await render(template); const emailHtml = await render(template);
const appName = config.getRawPrivateConfig().branding?.app_name || "Pangolin"; const appName = process.env.BRANDING_APP_NAME || "Pangolin"; // From the private config loading into env vars to seperate away the private config
await emailClient.sendMail({ await emailClient.sendMail({
from: { from: {

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import React from "react"; import React from "react";
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
import { themeColors } from "./lib/theme"; import { themeColors } from "./lib/theme";

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import React from "react"; import React from "react";
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components"; import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
import { themeColors } from "./lib/theme"; import { themeColors } from "./lib/theme";

View File

@@ -12,6 +12,7 @@ import config from "@server/lib/config";
import { setHostMeta } from "@server/lib/hostMeta"; import { setHostMeta } from "@server/lib/hostMeta";
import { initTelemetryClient } from "./lib/telemetry.js"; import { initTelemetryClient } from "./lib/telemetry.js";
import { TraefikConfigManager } from "./lib/traefik/TraefikConfigManager.js"; import { TraefikConfigManager } from "./lib/traefik/TraefikConfigManager.js";
import { initCleanup } from "#dynamic/cleanup";
async function startServers() { async function startServers() {
await setHostMeta(); await setHostMeta();
@@ -42,6 +43,8 @@ async function startServers() {
integrationServer = createIntegrationApiServer(); integrationServer = createIntegrationApiServer();
} }
await initCleanup();
return { return {
apiServer, apiServer,
nextServer, nextServer,

View File

@@ -7,7 +7,7 @@ import {
errorHandlerMiddleware, errorHandlerMiddleware,
notFoundMiddleware, notFoundMiddleware,
} from "@server/middlewares"; } from "@server/middlewares";
import { authenticated, unauthenticated } from "@server/routers/integration"; import { authenticated, unauthenticated } from "#dynamic/routers/integration";
import { logIncomingMiddleware } from "./middlewares/logIncoming"; import { logIncomingMiddleware } from "./middlewares/logIncoming";
import helmet from "helmet"; import helmet from "helmet";
import swaggerUi from "swagger-ui-express"; import swaggerUi from "swagger-ui-express";

View File

@@ -8,7 +8,7 @@ import {
errorHandlerMiddleware, errorHandlerMiddleware,
notFoundMiddleware notFoundMiddleware
} from "@server/middlewares"; } from "@server/middlewares";
import internal from "@server/routers/internal"; import { internalRouter } from "#dynamic/routers/internal";
import { stripDuplicateSesions } from "./middlewares/stripDuplicateSessions"; import { stripDuplicateSesions } from "./middlewares/stripDuplicateSessions";
const internalPort = config.getRawConfig().server.internal_port; const internalPort = config.getRawConfig().server.internal_port;
@@ -23,7 +23,7 @@ export function createInternalServer() {
internalServer.use(express.json()); internalServer.use(express.json());
const prefix = `/api/v1`; const prefix = `/api/v1`;
internalServer.use(prefix, internal); internalServer.use(prefix, internalRouter);
internalServer.use(notFoundMiddleware); internalServer.use(notFoundMiddleware);
internalServer.use(errorHandlerMiddleware); internalServer.use(errorHandlerMiddleware);

View File

@@ -0,0 +1,6 @@
export async function createCustomer(
orgId: string,
email: string | null | undefined
): Promise<string | undefined> {
return;
}

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import Stripe from "stripe"; import Stripe from "stripe";
export enum FeatureId { export enum FeatureId {

View File

@@ -0,0 +1,8 @@
export async function getOrgTierData(
orgId: string
): Promise<{ tier: string | null; active: boolean }> {
let tier = null;
let active = false;
return { tier, active };
}

View File

@@ -0,0 +1,5 @@
export * from "./limitSet";
export * from "./features";
export * from "./limitsService";
export * from "./getOrgTierData";
export * from "./createCustomer";

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { FeatureId } from "./features"; import { FeatureId } from "./features";
export type LimitSet = { export type LimitSet = {

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { db, limits } from "@server/db"; import { db, limits } from "@server/db";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { LimitSet } from "./limitSet"; import { LimitSet } from "./limitSet";

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export enum TierId { export enum TierId {
STANDARD = "standard", STANDARD = "standard",
} }

View File

@@ -1,21 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { eq, sql, and } from "drizzle-orm"; import { eq, sql, and } from "drizzle-orm";
import NodeCache from "node-cache"; import NodeCache from "node-cache";
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { PutObjectCommand } from "@aws-sdk/client-s3"; import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3Client } from "../s3";
import * as fs from "fs/promises"; import * as fs from "fs/promises";
import * as path from "path"; import * as path from "path";
import { import {
@@ -30,10 +16,10 @@ import {
Transaction Transaction
} from "@server/db"; } from "@server/db";
import { FeatureId, getFeatureMeterId } from "./features"; import { FeatureId, getFeatureMeterId } from "./features";
import config from "@server/lib/config";
import logger from "@server/logger"; import logger from "@server/logger";
import { sendToClient } from "@server/routers/ws"; import { sendToClient } from "#dynamic/routers/ws";
import { build } from "@server/build"; import { build } from "@server/build";
import { s3Client } from "@server/lib/s3";
interface StripeEvent { interface StripeEvent {
identifier?: string; identifier?: string;
@@ -58,8 +44,10 @@ export class UsageService {
if (build !== "saas") { if (build !== "saas") {
return; return;
} }
this.bucketName = config.getRawPrivateConfig().stripe?.s3Bucket; // this.bucketName = privateConfig.getRawPrivateConfig().stripe?.s3Bucket;
this.eventsDir = config.getRawPrivateConfig().stripe?.localFilePath; // this.eventsDir = privateConfig.getRawPrivateConfig().stripe?.localFilePath;
this.bucketName = process.env.S3_BUCKET || undefined;
this.eventsDir = process.env.LOCAL_FILE_PATH || undefined;
// Ensure events directory exists // Ensure events directory exists
this.initializeEventsDirectory().then(() => { this.initializeEventsDirectory().then(() => {

View File

@@ -1,4 +1,4 @@
import { sendToClient } from "@server/routers/ws"; import { sendToClient } from "#dynamic/routers/ws";
import { processContainerLabels } from "./parseDockerContainers"; import { processContainerLabels } from "./parseDockerContainers";
import { applyBlueprint } from "./applyBlueprint"; import { applyBlueprint } from "./applyBlueprint";
import { db, sites } from "@server/db"; import { db, sites } from "@server/db";

View File

@@ -25,7 +25,7 @@ import {
TargetData TargetData
} from "./types"; } from "./types";
import logger from "@server/logger"; import logger from "@server/logger";
import { createCertificate } from "@server/routers/private/certificates/createCertificate"; import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
import { pickPort } from "@server/routers/target/helpers"; import { pickPort } from "@server/routers/target/helpers";
import { resourcePassword } from "@server/db"; import { resourcePassword } from "@server/db";
import { hashPassword } from "@server/auth/password"; import { hashPassword } from "@server/auth/password";

View File

@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
import { tokenManager } from "../tokenManager"; import { tokenManager } from "./tokenManager";
import logger from "@server/logger"; import logger from "@server/logger";
import config from "../config"; import config from "./config";
/** /**
* Get valid certificates for the specified domains * Get valid certificates for the specified domains

View File

@@ -6,16 +6,10 @@ import { eq } from "drizzle-orm";
import { license } from "@server/license/license"; import { license } from "@server/license/license";
import { configSchema, readConfigFile } from "./readConfigFile"; import { configSchema, readConfigFile } from "./readConfigFile";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import {
privateConfigSchema,
readPrivateConfigFile
} from "@server/lib/private/readConfigFile";
import logger from "@server/logger";
import { build } from "@server/build"; import { build } from "@server/build";
export class Config { export class Config {
private rawConfig!: z.infer<typeof configSchema>; private rawConfig!: z.infer<typeof configSchema>;
private rawPrivateConfig!: z.infer<typeof privateConfigSchema>;
supporterData: SupporterKey | null = null; supporterData: SupporterKey | null = null;
@@ -37,19 +31,6 @@ export class Config {
throw new Error(`Invalid configuration file: ${errors}`); throw new Error(`Invalid configuration file: ${errors}`);
} }
const privateEnvironment = readPrivateConfigFile();
const {
data: parsedPrivateConfig,
success: privateSuccess,
error: privateError
} = privateConfigSchema.safeParse(privateEnvironment);
if (!privateSuccess) {
const errors = fromError(privateError);
throw new Error(`Invalid private configuration file: ${errors}`);
}
if ( if (
// @ts-ignore // @ts-ignore
parsedConfig.users || parsedConfig.users ||
@@ -109,110 +90,11 @@ export class Config {
? "true" ? "true"
: "false"; : "false";
if (parsedPrivateConfig.branding?.colors) {
process.env.BRANDING_COLORS = JSON.stringify(
parsedPrivateConfig.branding?.colors
);
}
if (parsedPrivateConfig.branding?.logo?.light_path) {
process.env.BRANDING_LOGO_LIGHT_PATH =
parsedPrivateConfig.branding?.logo?.light_path;
}
if (parsedPrivateConfig.branding?.logo?.dark_path) {
process.env.BRANDING_LOGO_DARK_PATH =
parsedPrivateConfig.branding?.logo?.dark_path || undefined;
}
process.env.HIDE_SUPPORTER_KEY = parsedPrivateConfig.flags
?.hide_supporter_key
? "true"
: "false";
if (build != "oss") {
if (parsedPrivateConfig.branding?.logo?.light_path) {
process.env.BRANDING_LOGO_LIGHT_PATH =
parsedPrivateConfig.branding?.logo?.light_path;
}
if (parsedPrivateConfig.branding?.logo?.dark_path) {
process.env.BRANDING_LOGO_DARK_PATH =
parsedPrivateConfig.branding?.logo?.dark_path || undefined;
}
process.env.BRANDING_LOGO_AUTH_WIDTH = parsedPrivateConfig.branding
?.logo?.auth_page?.width
? parsedPrivateConfig.branding?.logo?.auth_page?.width.toString()
: undefined;
process.env.BRANDING_LOGO_AUTH_HEIGHT = parsedPrivateConfig.branding
?.logo?.auth_page?.height
? parsedPrivateConfig.branding?.logo?.auth_page?.height.toString()
: undefined;
process.env.BRANDING_LOGO_NAVBAR_WIDTH = parsedPrivateConfig
.branding?.logo?.navbar?.width
? parsedPrivateConfig.branding?.logo?.navbar?.width.toString()
: undefined;
process.env.BRANDING_LOGO_NAVBAR_HEIGHT = parsedPrivateConfig
.branding?.logo?.navbar?.height
? parsedPrivateConfig.branding?.logo?.navbar?.height.toString()
: undefined;
process.env.BRANDING_FAVICON_PATH =
parsedPrivateConfig.branding?.favicon_path;
process.env.BRANDING_APP_NAME =
parsedPrivateConfig.branding?.app_name || "Pangolin";
if (parsedPrivateConfig.branding?.footer) {
process.env.BRANDING_FOOTER = JSON.stringify(
parsedPrivateConfig.branding?.footer
);
}
process.env.LOGIN_PAGE_TITLE_TEXT =
parsedPrivateConfig.branding?.login_page?.title_text || "";
process.env.LOGIN_PAGE_SUBTITLE_TEXT =
parsedPrivateConfig.branding?.login_page?.subtitle_text || "";
process.env.SIGNUP_PAGE_TITLE_TEXT =
parsedPrivateConfig.branding?.signup_page?.title_text || "";
process.env.SIGNUP_PAGE_SUBTITLE_TEXT =
parsedPrivateConfig.branding?.signup_page?.subtitle_text || "";
process.env.RESOURCE_AUTH_PAGE_HIDE_POWERED_BY =
parsedPrivateConfig.branding?.resource_auth_page
?.hide_powered_by === true
? "true"
: "false";
process.env.RESOURCE_AUTH_PAGE_SHOW_LOGO =
parsedPrivateConfig.branding?.resource_auth_page?.show_logo ===
true
? "true"
: "false";
process.env.RESOURCE_AUTH_PAGE_TITLE_TEXT =
parsedPrivateConfig.branding?.resource_auth_page?.title_text ||
"";
process.env.RESOURCE_AUTH_PAGE_SUBTITLE_TEXT =
parsedPrivateConfig.branding?.resource_auth_page
?.subtitle_text || "";
if (parsedPrivateConfig.branding?.background_image_path) {
process.env.BACKGROUND_IMAGE_PATH =
parsedPrivateConfig.branding?.background_image_path;
}
if (parsedPrivateConfig.server.reo_client_id) {
process.env.REO_CLIENT_ID =
parsedPrivateConfig.server.reo_client_id;
}
}
if (parsedConfig.server.maxmind_db_path) { if (parsedConfig.server.maxmind_db_path) {
process.env.MAXMIND_DB_PATH = parsedConfig.server.maxmind_db_path; process.env.MAXMIND_DB_PATH = parsedConfig.server.maxmind_db_path;
} }
this.rawConfig = parsedConfig; this.rawConfig = parsedConfig;
this.rawPrivateConfig = parsedPrivateConfig;
} }
public async initServer() { public async initServer() {
@@ -231,7 +113,6 @@ export class Config {
private async checkKeyStatus() { private async checkKeyStatus() {
const licenseStatus = await license.check(); const licenseStatus = await license.check();
if ( if (
!this.rawPrivateConfig.flags?.hide_supporter_key &&
build != "oss" && build != "oss" &&
!licenseStatus.isHostLicensed !licenseStatus.isHostLicensed
) { ) {
@@ -243,10 +124,6 @@ export class Config {
return this.rawConfig; return this.rawConfig;
} }
public getRawPrivateConfig() {
return this.rawPrivateConfig;
}
public getNoReplyEmail(): string | undefined { public getNoReplyEmail(): string | undefined {
return ( return (
this.rawConfig.email?.no_reply || this.rawConfig.email?.smtp_user this.rawConfig.email?.no_reply || this.rawConfig.email?.smtp_user

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import cors, { CorsOptions } from "cors"; import cors, { CorsOptions } from "cors";
import config from "@server/lib/config"; import config from "@server/lib/config";

View File

@@ -1,16 +1,3 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { isValidCIDR } from "@server/lib/validators"; import { isValidCIDR } from "@server/lib/validators";
import { getNextAvailableOrgSubnet } from "@server/lib/ip"; import { getNextAvailableOrgSubnet } from "@server/lib/ip";
import { import {
@@ -28,9 +15,9 @@ import {
} from "@server/db"; } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { defaultRoleAllowedActions } from "@server/routers/role"; import { defaultRoleAllowedActions } from "@server/routers/role";
import { FeatureId, limitsService, sandboxLimitSet } from "@server/lib/private/billing"; import { FeatureId, limitsService, sandboxLimitSet } from "@server/lib/billing";
import { createCustomer } from "@server/routers/private/billing/createCustomer"; import { createCustomer } from "@server/private/lib/billing/createCustomer";
import { usageService } from "@server/lib/private/billing/usageService"; import { usageService } from "@server/lib/billing/usageService";
export async function createUserAccountOrg( export async function createUserAccountOrg(
userId: string, userId: string,

View File

@@ -16,7 +16,11 @@ export async function verifyExitNodeOrgAccess(
return { hasAccess: true, exitNode }; return { hasAccess: true, exitNode };
} }
export async function listExitNodes(orgId: string, filterOnline = false, noCloud = false) { export async function listExitNodes(
orgId: string,
filterOnline = false,
noCloud = false
) {
// TODO: pick which nodes to send and ping better than just all of them that are not remote // TODO: pick which nodes to send and ping better than just all of them that are not remote
const allExitNodes = await db const allExitNodes = await db
.select({ .select({
@@ -59,7 +63,16 @@ export async function checkExitNodeOrg(exitNodeId: number, orgId: string) {
return false; return false;
} }
export async function resolveExitNodes(hostname: string, publicKey: string) { export async function resolveExitNodes(
hostname: string,
publicKey: string
): Promise<
{
endpoint: string;
publicKey: string;
orgId: string;
}[]
> {
// OSS version: simple implementation that returns empty array // OSS version: simple implementation that returns empty array
return []; return [];
} }

View File

@@ -1,33 +1,4 @@
import { build } from "@server/build"; export * from "./exitNodes";
export * from "./exitNodeComms";
// Import both modules
import * as exitNodesModule from "./exitNodes";
import * as privateExitNodesModule from "./privateExitNodes";
// Conditionally export exit nodes implementation based on build type
const exitNodesImplementation = build === "oss" ? exitNodesModule : privateExitNodesModule;
// Re-export all items from the selected implementation
export const {
verifyExitNodeOrgAccess,
listExitNodes,
selectBestExitNode,
checkExitNodeOrg,
resolveExitNodes
} = exitNodesImplementation;
// Import communications modules
import * as exitNodeCommsModule from "./exitNodeComms";
import * as privateExitNodeCommsModule from "./privateExitNodeComms";
// Conditionally export communications implementation based on build type
const exitNodeCommsImplementation = build === "oss" ? exitNodeCommsModule : privateExitNodeCommsModule;
// Re-export communications functions from the selected implementation
export const {
sendToExitNode
} = exitNodeCommsImplementation;
// Re-export shared modules
export * from "./subnet"; export * from "./subnet";
export * from "./getCurrentExitNodeId"; export * from "./getCurrentExitNodeId";

View File

@@ -1,25 +0,0 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import RedisStore from "@server/db/private/redisStore";
import { MemoryStore, Store } from "express-rate-limit";
export function createStore(): Store {
const rateLimitStore: Store = new RedisStore({
prefix: 'api-rate-limit', // Optional: customize Redis key prefix
skipFailedRequests: true, // Don't count failed requests
skipSuccessfulRequests: false, // Count successful requests
});
return rateLimitStore;
}

View File

@@ -1,19 +0,0 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { S3Client } from "@aws-sdk/client-s3";
import config from "@server/lib/config";
export const s3Client = new S3Client({
region: config.getRawPrivateConfig().stripe?.s3Region || "us-east-1",
});

View File

@@ -1,14 +0,0 @@
import { build } from "@server/build";
// Import both modules
import * as certificateModule from "./certificates";
import * as privateCertificateModule from "./privateCertificates";
// Conditionally export Remote Certificates implementation based on build type
const remoteCertificatesImplementation = build === "oss" ? certificateModule : privateCertificateModule;
// Re-export all items from the selected implementation
export const {
getValidCertificatesForDomains,
getValidCertificatesForDomainsHybrid
} = remoteCertificatesImplementation;

15
server/lib/resend.ts Normal file
View File

@@ -0,0 +1,15 @@
export enum AudienceIds {
General = "",
Subscribed = "",
Churned = ""
}
let resend;
export default resend;
export async function moveEmailToAudience(
email: string,
audienceId: AudienceIds
) {
return
}

5
server/lib/s3.ts Normal file
View File

@@ -0,0 +1,5 @@
import { S3Client } from "@aws-sdk/client-s3";
export const s3Client = new S3Client({
region: process.env.S3_REGION || "us-east-1",
});

View File

@@ -8,12 +8,12 @@ import { db, exitNodes } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { tokenManager } from "../tokenManager"; import { tokenManager } from "../tokenManager";
import { getCurrentExitNodeId } from "@server/lib/exitNodes"; import { getCurrentExitNodeId } from "@server/lib/exitNodes";
import { getTraefikConfig } from "./"; import { getTraefikConfig } from "#dynamic/lib/traefik";
import { import {
getValidCertificatesForDomains, getValidCertificatesForDomains,
getValidCertificatesForDomainsHybrid getValidCertificatesForDomainsHybrid
} from "../remoteCertificates"; } from "#dynamic/lib/certificates";
import { sendToExitNode } from "../exitNodes"; import { sendToExitNode } from "#dynamic/lib/exitNodes";
import { build } from "@server/build"; import { build } from "@server/build";
export class TraefikConfigManager { export class TraefikConfigManager {

View File

@@ -1,11 +1 @@
import { build } from "@server/build"; export * from "./getTraefikConfig";
// Import both modules
import * as traefikModule from "./getTraefikConfig";
import * as privateTraefikModule from "./privateGetTraefikConfig";
// Conditionally export Traefik configuration implementation based on build type
const traefikImplementation = build === "oss" ? traefikModule : privateTraefikModule;
// Re-export all items from the selected implementation
export const { getTraefikConfig } = traefikImplementation;

15
server/private/cleanup.ts Normal file
View File

@@ -0,0 +1,15 @@
import { rateLimitService } from "#private/lib/rateLimit";
import { cleanup as wsCleanup } from "#private/routers/ws";
async function cleanup() {
await rateLimitService.cleanup();
await wsCleanup();
process.exit(0);
}
export async function initCleanup() {
// Handle process termination
process.on("SIGTERM", () => cleanup());
process.on("SIGINT", () => cleanup());
}

View File

@@ -13,7 +13,7 @@
import { customers, db } from "@server/db"; import { customers, db } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import stripe from "@server/lib/private/stripe"; import stripe from "#private/lib/stripe";
import { build } from "@server/build"; import { build } from "@server/build";
export async function createCustomer( export async function createCustomer(

View File

@@ -0,0 +1,46 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { getTierPriceSet } from "@server/lib/billing/tiers";
import { getOrgSubscriptionData } from "#private/routers/billing/getOrgSubscription";
import { build } from "@server/build";
export async function getOrgTierData(
orgId: string
): Promise<{ tier: string | null; active: boolean }> {
let tier = null;
let active = false;
if (build !== "saas") {
return { tier, active };
}
const { subscription, items } = await getOrgSubscriptionData(orgId);
if (items && items.length > 0) {
const tierPriceSet = getTierPriceSet();
// Iterate through tiers in order (earlier keys are higher tiers)
for (const [tierId, priceId] of Object.entries(tierPriceSet)) {
// Check if any subscription item matches this tier's price ID
const matchingItem = items.find((item) => item.priceId === priceId);
if (matchingItem) {
tier = tierId;
break;
}
}
}
if (subscription && subscription.status === "active") {
active = true;
}
return { tier, active };
}

View File

@@ -11,6 +11,5 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
export * from "./limitSet"; export * from "./getOrgTierData";
export * from "./features"; export * from "./createCustomer";
export * from "./limitsService";

View File

@@ -11,10 +11,10 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
import config from "../config"; import config from "./config";
import { certificates, db } from "@server/db"; import { certificates, db } from "@server/db";
import { and, eq, isNotNull } from "drizzle-orm"; import { and, eq, isNotNull } from "drizzle-orm";
import { decryptData } from "../encryption"; import { decryptData } from "@server/lib/encryption";
import * as fs from "fs"; import * as fs from "fs";
export async function getValidCertificatesForDomains( export async function getValidCertificatesForDomains(

View File

@@ -0,0 +1,163 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { z } from "zod";
import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
import { db } from "@server/db";
import { SupporterKey, supporterKey } from "@server/db";
import { eq } from "drizzle-orm";
import { license } from "@server/license/license";
import { fromError } from "zod-validation-error";
import {
privateConfigSchema,
readPrivateConfigFile
} from "#private/lib/readConfigFile";
import { build } from "@server/build";
export class PrivateConfig {
private rawPrivateConfig!: z.infer<typeof privateConfigSchema>;
supporterData: SupporterKey | null = null;
supporterHiddenUntil: number | null = null;
isDev: boolean = process.env.ENVIRONMENT !== "prod";
constructor() {
const privateEnvironment = readPrivateConfigFile();
const {
data: parsedPrivateConfig,
success: privateSuccess,
error: privateError
} = privateConfigSchema.safeParse(privateEnvironment);
if (!privateSuccess) {
const errors = fromError(privateError);
throw new Error(`Invalid private configuration file: ${errors}`);
}
if (parsedPrivateConfig.branding?.colors) {
process.env.BRANDING_COLORS = JSON.stringify(
parsedPrivateConfig.branding?.colors
);
}
if (parsedPrivateConfig.branding?.logo?.light_path) {
process.env.BRANDING_LOGO_LIGHT_PATH =
parsedPrivateConfig.branding?.logo?.light_path;
}
if (parsedPrivateConfig.branding?.logo?.dark_path) {
process.env.BRANDING_LOGO_DARK_PATH =
parsedPrivateConfig.branding?.logo?.dark_path || undefined;
}
if (build != "oss") {
if (parsedPrivateConfig.branding?.logo?.light_path) {
process.env.BRANDING_LOGO_LIGHT_PATH =
parsedPrivateConfig.branding?.logo?.light_path;
}
if (parsedPrivateConfig.branding?.logo?.dark_path) {
process.env.BRANDING_LOGO_DARK_PATH =
parsedPrivateConfig.branding?.logo?.dark_path || undefined;
}
process.env.BRANDING_LOGO_AUTH_WIDTH = parsedPrivateConfig.branding
?.logo?.auth_page?.width
? parsedPrivateConfig.branding?.logo?.auth_page?.width.toString()
: undefined;
process.env.BRANDING_LOGO_AUTH_HEIGHT = parsedPrivateConfig.branding
?.logo?.auth_page?.height
? parsedPrivateConfig.branding?.logo?.auth_page?.height.toString()
: undefined;
process.env.BRANDING_LOGO_NAVBAR_WIDTH = parsedPrivateConfig
.branding?.logo?.navbar?.width
? parsedPrivateConfig.branding?.logo?.navbar?.width.toString()
: undefined;
process.env.BRANDING_LOGO_NAVBAR_HEIGHT = parsedPrivateConfig
.branding?.logo?.navbar?.height
? parsedPrivateConfig.branding?.logo?.navbar?.height.toString()
: undefined;
process.env.BRANDING_FAVICON_PATH =
parsedPrivateConfig.branding?.favicon_path;
process.env.BRANDING_APP_NAME =
parsedPrivateConfig.branding?.app_name || "Pangolin";
if (parsedPrivateConfig.branding?.footer) {
process.env.BRANDING_FOOTER = JSON.stringify(
parsedPrivateConfig.branding?.footer
);
}
process.env.LOGIN_PAGE_TITLE_TEXT =
parsedPrivateConfig.branding?.login_page?.title_text || "";
process.env.LOGIN_PAGE_SUBTITLE_TEXT =
parsedPrivateConfig.branding?.login_page?.subtitle_text || "";
process.env.SIGNUP_PAGE_TITLE_TEXT =
parsedPrivateConfig.branding?.signup_page?.title_text || "";
process.env.SIGNUP_PAGE_SUBTITLE_TEXT =
parsedPrivateConfig.branding?.signup_page?.subtitle_text || "";
process.env.RESOURCE_AUTH_PAGE_HIDE_POWERED_BY =
parsedPrivateConfig.branding?.resource_auth_page
?.hide_powered_by === true
? "true"
: "false";
process.env.RESOURCE_AUTH_PAGE_SHOW_LOGO =
parsedPrivateConfig.branding?.resource_auth_page?.show_logo ===
true
? "true"
: "false";
process.env.RESOURCE_AUTH_PAGE_TITLE_TEXT =
parsedPrivateConfig.branding?.resource_auth_page?.title_text ||
"";
process.env.RESOURCE_AUTH_PAGE_SUBTITLE_TEXT =
parsedPrivateConfig.branding?.resource_auth_page
?.subtitle_text || "";
if (parsedPrivateConfig.branding?.background_image_path) {
process.env.BACKGROUND_IMAGE_PATH =
parsedPrivateConfig.branding?.background_image_path;
}
if (parsedPrivateConfig.server.reo_client_id) {
process.env.REO_CLIENT_ID =
parsedPrivateConfig.server.reo_client_id;
}
if (parsedPrivateConfig.stripe?.s3Bucket) {
process.env.S3_BUCKET = parsedPrivateConfig.stripe.s3Bucket;
}
if (parsedPrivateConfig.stripe?.localFilePath) {
process.env.LOCAL_FILE_PATH = parsedPrivateConfig.stripe.localFilePath;
}
if (parsedPrivateConfig.stripe?.s3Region) {
process.env.S3_REGION = parsedPrivateConfig.stripe.s3Region;
}
}
this.rawPrivateConfig = parsedPrivateConfig;
}
public getRawPrivateConfig() {
return this.rawPrivateConfig;
}
}
export const privateConfig = new PrivateConfig();
export default privateConfig;

View File

@@ -15,8 +15,9 @@ import axios from "axios";
import logger from "@server/logger"; import logger from "@server/logger";
import { db, ExitNode, remoteExitNodes } from "@server/db"; import { db, ExitNode, remoteExitNodes } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { sendToClient } from "../../routers/ws"; import { sendToClient } from "#private/routers/ws";
import { config } from "../config"; import privateConfig from "#private/lib/config";
import config from "@server/lib/config";
interface ExitNodeRequest { interface ExitNodeRequest {
remoteType?: string; remoteType?: string;
@@ -65,7 +66,7 @@ export async function sendToExitNode(
logger.debug(`Configured local exit node name: ${config.getRawConfig().gerbil.exit_node_name}`); logger.debug(`Configured local exit node name: ${config.getRawConfig().gerbil.exit_node_name}`);
if (exitNode.name == config.getRawConfig().gerbil.exit_node_name) { if (exitNode.name == config.getRawConfig().gerbil.exit_node_name) {
hostname = config.getRawPrivateConfig().gerbil.local_exit_node_reachable_at; hostname = privateConfig.getRawPrivateConfig().gerbil.local_exit_node_reachable_at;
} }
if (!hostname) { if (!hostname) {

View File

@@ -0,0 +1,15 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export * from "./exitNodeComms";
export * from "./exitNodes";

View File

@@ -12,7 +12,7 @@
*/ */
import logger from "@server/logger"; import logger from "@server/logger";
import redisManager from "@server/db/private/redis"; import redisManager from "@server/private/lib/redis";
import { build } from "@server/build"; import { build } from "@server/build";
// Rate limiting configuration // Rate limiting configuration
@@ -451,8 +451,4 @@ export class RateLimitService {
} }
// Export singleton instance // Export singleton instance
export const rateLimitService = new RateLimitService(); export const rateLimitService = new RateLimitService();
// Handle process termination
process.on("SIGTERM", () => rateLimitService.cleanup());
process.on("SIGINT", () => rateLimitService.cleanup());

View File

@@ -0,0 +1,32 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { build } from "@server/build";
import privateConfig from "#private/lib/config";
import { MemoryStore, Store } from "express-rate-limit";
import RedisStore from "#private/lib/redisStore";
export function createStore(): Store {
if (build != "oss" && privateConfig.getRawPrivateConfig().flags?.enable_redis) {
const rateLimitStore: Store = new RedisStore({
prefix: "api-rate-limit", // Optional: customize Redis key prefix
skipFailedRequests: true, // Don't count failed requests
skipSuccessfulRequests: false // Count successful requests
});
return rateLimitStore;
} else {
const rateLimitStore: Store = new MemoryStore();
return rateLimitStore;
}
}

View File

@@ -74,7 +74,6 @@ export const privateConfigSchema = z
flags: z flags: z
.object({ .object({
enable_redis: z.boolean().optional(), enable_redis: z.boolean().optional(),
hide_supporter_key: z.boolean().optional()
}) })
.optional(), .optional(),
branding: z branding: z

View File

@@ -13,7 +13,7 @@
import Redis, { RedisOptions } from "ioredis"; import Redis, { RedisOptions } from "ioredis";
import logger from "@server/logger"; import logger from "@server/logger";
import config from "@server/lib/config"; import privateConfig from "#private/lib/config";
import { build } from "@server/build"; import { build } from "@server/build";
class RedisManager { class RedisManager {
@@ -46,7 +46,7 @@ class RedisManager {
this.isEnabled = false; this.isEnabled = false;
return; return;
} }
this.isEnabled = config.getRawPrivateConfig().flags?.enable_redis || false; this.isEnabled = privateConfig.getRawPrivateConfig().flags?.enable_redis || false;
if (this.isEnabled) { if (this.isEnabled) {
this.initializeClients(); this.initializeClients();
} }
@@ -93,7 +93,7 @@ class RedisManager {
} }
private getRedisConfig(): RedisOptions { private getRedisConfig(): RedisOptions {
const redisConfig = config.getRawPrivateConfig().redis!; const redisConfig = privateConfig.getRawPrivateConfig().redis!;
const opts: RedisOptions = { const opts: RedisOptions = {
host: redisConfig.host!, host: redisConfig.host!,
port: redisConfig.port!, port: redisConfig.port!,
@@ -108,7 +108,7 @@ class RedisManager {
} }
private getReplicaRedisConfig(): RedisOptions | null { private getReplicaRedisConfig(): RedisOptions | null {
const redisConfig = config.getRawPrivateConfig().redis!; const redisConfig = privateConfig.getRawPrivateConfig().redis!;
if (!redisConfig.replicas || redisConfig.replicas.length === 0) { if (!redisConfig.replicas || redisConfig.replicas.length === 0) {
return null; return null;
} }

View File

@@ -12,7 +12,7 @@
*/ */
import { Resend } from "resend"; import { Resend } from "resend";
import config from "../config"; import privateConfig from "#private/lib/config";
import logger from "@server/logger"; import logger from "@server/logger";
export enum AudienceIds { export enum AudienceIds {
@@ -22,7 +22,7 @@ export enum AudienceIds {
} }
const resend = new Resend( const resend = new Resend(
config.getRawPrivateConfig().server.resend_api_key || "missing" privateConfig.getRawPrivateConfig().server.resend_api_key || "missing"
); );
export default resend; export default resend;

View File

@@ -12,13 +12,13 @@
*/ */
import Stripe from "stripe"; import Stripe from "stripe";
import config from "@server/lib/config"; import privateConfig from "#private/lib/config";
import logger from "@server/logger"; import logger from "@server/logger";
import { build } from "@server/build"; import { build } from "@server/build";
let stripe: Stripe | undefined = undefined; let stripe: Stripe | undefined = undefined;
if (build == "saas") { if (build == "saas") {
const stripeApiKey = config.getRawPrivateConfig().stripe?.secret_key; const stripeApiKey = privateConfig.getRawPrivateConfig().stripe?.secret_key;
if (!stripeApiKey) { if (!stripeApiKey) {
logger.error("Stripe secret key is not configured"); logger.error("Stripe secret key is not configured");
} }

View File

@@ -25,7 +25,7 @@ import HttpCode from "@server/types/HttpCode";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { orgs, resources, sites, Target, targets } from "@server/db"; import { orgs, resources, sites, Target, targets } from "@server/db";
import { build } from "@server/build"; import { build } from "@server/build";
import { sanitize } from "./utils"; import { sanitize } from "@server/lib/traefik/utils";
const redirectHttpsMiddlewareName = "redirect-to-https"; const redirectHttpsMiddlewareName = "redirect-to-https";
const redirectToRootMiddlewareName = "redirect-to-root"; const redirectToRootMiddlewareName = "redirect-to-root";

View File

@@ -0,0 +1,14 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export * from "./getTraefikConfig";

View File

@@ -15,4 +15,4 @@ export * from "./verifyCertificateAccess";
export * from "./verifyRemoteExitNodeAccess"; export * from "./verifyRemoteExitNodeAccess";
export * from "./verifyIdpAccess"; export * from "./verifyIdpAccess";
export * from "./verifyLoginPageAccess"; export * from "./verifyLoginPageAccess";
export * from "./corsWithLoginPage"; export * from "../../lib/corsWithLoginPage";

View File

@@ -16,7 +16,7 @@ import ErrorResponse from "@server/types/ErrorResponse";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { unauthorized } from "@server/auth/unauthorizedResponse"; import { unauthorized } from "@server/auth/unauthorizedResponse";
import logger from "@server/logger"; import logger from "@server/logger";
import { validateRemoteExitNodeSessionToken } from "@server/auth/sessions/privateRemoteExitNode"; import { validateRemoteExitNodeSessionToken } from "#private/auth/sessions/remoteExitNode";
export const verifySessionRemoteExitNodeMiddleware = async ( export const verifySessionRemoteExitNodeMiddleware = async (
req: any, req: any,

View File

@@ -0,0 +1,16 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export * from "./transferSession";
export * from "./getSessionTransferToken";
export * from "./quickStart";

View File

@@ -50,16 +50,16 @@ import config from "@server/lib/config";
import logger from "@server/logger"; import logger from "@server/logger";
import { hashPassword } from "@server/auth/password"; import { hashPassword } from "@server/auth/password";
import { UserType } from "@server/types/UserTypes"; import { UserType } from "@server/types/UserTypes";
import { createUserAccountOrg } from "@server/lib/private/createUserAccountOrg"; import { createUserAccountOrg } from "@server/lib/createUserAccountOrg";
import { sendEmail } from "@server/emails"; import { sendEmail } from "@server/emails";
import WelcomeQuickStart from "@server/emails/templates/WelcomeQuickStart"; import WelcomeQuickStart from "@server/emails/templates/WelcomeQuickStart";
import { alphabet, generateRandomString } from "oslo/crypto"; import { alphabet, generateRandomString } from "oslo/crypto";
import { createDate, TimeSpan } from "oslo"; import { createDate, TimeSpan } from "oslo";
import { getUniqueResourceName, getUniqueSiteName } from "@server/db/names"; import { getUniqueResourceName, getUniqueSiteName } from "@server/db/names";
import { pickPort } from "../target/helpers"; import { pickPort } from "@server/routers/target/helpers";
import { addTargets } from "../newt/targets"; import { addTargets } from "@server/routers/newt/targets";
import { isTargetValid } from "@server/lib/validators"; import { isTargetValid } from "@server/lib/validators";
import { listExitNodes } from "@server/lib/exitNodes"; import { listExitNodes } from "#private/lib/exitNodes";
const bodySchema = z.object({ const bodySchema = z.object({
email: z.string().toLowerCase().email(), email: z.string().toLowerCase().email(),

View File

@@ -21,9 +21,9 @@ import createHttpError from "http-errors";
import logger from "@server/logger"; import logger from "@server/logger";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import stripe from "@server/lib/private/stripe"; import stripe from "#private/lib/stripe";
import { getLineItems, getStandardFeaturePriceSet } from "@server/lib/private/billing"; import { getLineItems, getStandardFeaturePriceSet } from "@server/lib/billing";
import { getTierPriceSet, TierId } from "@server/lib/private/billing/tiers"; import { getTierPriceSet, TierId } from "@server/lib/billing/tiers";
const createCheckoutSessionSchema = z const createCheckoutSessionSchema = z
.object({ .object({

View File

@@ -21,7 +21,7 @@ import createHttpError from "http-errors";
import logger from "@server/logger"; import logger from "@server/logger";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import stripe from "@server/lib/private/stripe"; import stripe from "#private/lib/stripe";
const createPortalSessionSchema = z const createPortalSessionSchema = z
.object({ .object({

View File

@@ -23,8 +23,8 @@ import logger from "@server/logger";
import { fromZodError } from "zod-validation-error"; import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import { Limit, limits, Usage, usage } from "@server/db"; import { Limit, limits, Usage, usage } from "@server/db";
import { usageService } from "@server/lib/private/billing/usageService"; import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/private/billing"; import { FeatureId } from "@server/lib/billing";
const getOrgSchema = z const getOrgSchema = z
.object({ .object({

View File

@@ -22,9 +22,9 @@ import {
} from "@server/db"; } from "@server/db";
import { eq, and } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import stripe from "@server/lib/private/stripe"; import stripe from "#private/lib/stripe";
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle"; import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
import { AudienceIds, moveEmailToAudience } from "@server/lib/private/resend"; import { AudienceIds, moveEmailToAudience } from "#private/lib/resend";
export async function handleSubscriptionCreated( export async function handleSubscriptionCreated(
subscription: Stripe.Subscription subscription: Stripe.Subscription

View File

@@ -16,7 +16,7 @@ import { subscriptions, db, subscriptionItems, customers, userOrgs, users } from
import { eq, and } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle"; import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
import { AudienceIds, moveEmailToAudience } from "@server/lib/private/resend"; import { AudienceIds, moveEmailToAudience } from "#private/lib/resend";
export async function handleSubscriptionDeleted( export async function handleSubscriptionDeleted(
subscription: Stripe.Subscription subscription: Stripe.Subscription

View File

@@ -23,8 +23,8 @@ import {
} from "@server/db"; } from "@server/db";
import { eq, and } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { getFeatureIdByMetricId } from "@server/lib/private/billing/features"; import { getFeatureIdByMetricId } from "@server/lib/billing/features";
import stripe from "@server/lib/private/stripe"; import stripe from "#private/lib/stripe";
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle"; import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
export async function handleSubscriptionUpdated( export async function handleSubscriptionUpdated(

View File

@@ -18,9 +18,7 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import logger from "@server/logger"; import logger from "@server/logger";
import { fromZodError } from "zod-validation-error"; import { fromZodError } from "zod-validation-error";
import { getTierPriceSet } from "@server/lib/private/billing/tiers"; import { getOrgTierData } from "#private/lib/billing";
import { getOrgSubscriptionData } from "./getOrgSubscription";
import { build } from "@server/build";
const getOrgSchema = z const getOrgSchema = z
.object({ .object({
@@ -87,33 +85,3 @@ export async function getOrgTier(
); );
} }
} }
export async function getOrgTierData(
orgId: string
): Promise<{ tier: string | null; active: boolean }> {
let tier = null;
let active = false;
if (build !== "saas") {
return { tier, active };
}
const { subscription, items } = await getOrgSubscriptionData(orgId);
if (items && items.length > 0) {
const tierPriceSet = getTierPriceSet();
// Iterate through tiers in order (earlier keys are higher tiers)
for (const [tierId, priceId] of Object.entries(tierPriceSet)) {
// Check if any subscription item matches this tier's price ID
const matchingItem = items.find((item) => item.priceId === priceId);
if (matchingItem) {
tier = tierId;
break;
}
}
}
if (subscription && subscription.status === "active") {
active = true;
}
return { tier, active };
}

View File

@@ -11,8 +11,8 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
import { freeLimitSet, limitsService, subscribedLimitSet } from "@server/lib/private/billing"; import { freeLimitSet, limitsService, subscribedLimitSet } from "@server/lib/billing";
import { usageService } from "@server/lib/private/billing/usageService"; import { usageService } from "@server/lib/billing/usageService";
import logger from "@server/logger"; import logger from "@server/logger";
export async function handleSubscriptionLifesycle(orgId: string, status: string) { export async function handleSubscriptionLifesycle(orgId: string, status: string) {

View File

@@ -11,8 +11,8 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
import stripe from "@server/lib/private/stripe"; import stripe from "#private/lib/stripe";
import config from "@server/lib/config"; import privateConfig from "#private/lib/config";
import logger from "@server/logger"; import logger from "@server/logger";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { response } from "@server/lib/response"; import { response } from "@server/lib/response";
@@ -26,13 +26,13 @@ import { handleCustomerUpdated } from "./hooks/handleCustomerUpdated";
import { handleSubscriptionDeleted } from "./hooks/handleSubscriptionDeleted"; import { handleSubscriptionDeleted } from "./hooks/handleSubscriptionDeleted";
import { handleCustomerDeleted } from "./hooks/handleCustomerDeleted"; import { handleCustomerDeleted } from "./hooks/handleCustomerDeleted";
export async function stripeWebhookHandler( export async function billingWebhookHandler(
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
): Promise<any> { ): Promise<any> {
let event: Stripe.Event = req.body; let event: Stripe.Event = req.body;
const endpointSecret = config.getRawPrivateConfig().stripe?.webhook_secret; const endpointSecret = privateConfig.getRawPrivateConfig().stripe?.webhook_secret;
if (!endpointSecret) { if (!endpointSecret) {
logger.warn("Stripe webhook secret is not configured. Webhook events will not be priocessed."); logger.warn("Stripe webhook secret is not configured. Webhook events will not be priocessed.");
return next( return next(

View File

@@ -0,0 +1,15 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export * from "./checkDomainNamespaceAvailability";
export * from "./listDomainNamespaces";

View File

@@ -0,0 +1,262 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import * as certificates from "#private/routers/certificates";
import { createStore } from "#private/lib/rateLimitStore";
import * as billing from "#private/routers/billing";
import * as remoteExitNode from "#private/routers/remoteExitNode";
import * as loginPage from "#private/routers/loginPage";
import * as orgIdp from "#private/routers/orgIdp";
import * as domain from "#private/routers/domain";
import * as auth from "#private/routers/auth";
import { Router } from "express";
import { verifyOrgAccess, verifySessionUserMiddleware, verifyUserHasAction } from "@server/middlewares";
import { ActionsEnum } from "@server/auth/actions";
import {
verifyCertificateAccess,
verifyIdpAccess,
verifyLoginPageAccess,
verifyRemoteExitNodeAccess
} from "#private/middlewares";
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { unauthenticated as ua, authenticated as a } from "@server/routers/external";
export const authenticated = a;
export const unauthenticated = ua;
unauthenticated.post(
"/quick-start",
rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
keyGenerator: (req) => req.path,
handler: (req, res, next) => {
const message = `We're too busy right now. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
},
store: createStore()
}),
auth.quickStart
);
unauthenticated.post(
"/remote-exit-node/quick-start",
rateLimit({
windowMs: 60 * 60 * 1000,
max: 5,
keyGenerator: (req) => `${req.path}:${ipKeyGenerator(req.ip || "")}`,
handler: (req, res, next) => {
const message = `You can only create 5 remote exit nodes every hour. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
},
store: createStore()
}),
remoteExitNode.quickStartRemoteExitNode
);
authenticated.put(
"/org/:orgId/idp/oidc",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createIdp),
orgIdp.createOrgOidcIdp
);
authenticated.post(
"/org/:orgId/idp/:idpId/oidc",
verifyOrgAccess,
verifyIdpAccess,
verifyUserHasAction(ActionsEnum.updateIdp),
orgIdp.updateOrgOidcIdp
);
authenticated.delete(
"/org/:orgId/idp/:idpId",
verifyOrgAccess,
verifyIdpAccess,
verifyUserHasAction(ActionsEnum.deleteIdp),
orgIdp.deleteOrgIdp
);
authenticated.get(
"/org/:orgId/idp/:idpId",
verifyOrgAccess,
verifyIdpAccess,
verifyUserHasAction(ActionsEnum.getIdp),
orgIdp.getOrgIdp
);
authenticated.get(
"/org/:orgId/idp",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.listIdps),
orgIdp.listOrgIdps
);
authenticated.get("/org/:orgId/idp", orgIdp.listOrgIdps); // anyone can see this; it's just a list of idp names and ids
authenticated.get(
"/org/:orgId/certificate/:domainId/:domain",
verifyOrgAccess,
verifyCertificateAccess,
verifyUserHasAction(ActionsEnum.getCertificate),
certificates.getCertificate
);
authenticated.post(
"/org/:orgId/certificate/:certId/restart",
verifyOrgAccess,
verifyCertificateAccess,
verifyUserHasAction(ActionsEnum.restartCertificate),
certificates.restartCertificate
);
authenticated.post(
"/org/:orgId/billing/create-checkout-session",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
billing.createCheckoutSession
);
authenticated.post(
"/org/:orgId/billing/create-portal-session",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
billing.createPortalSession
);
authenticated.get(
"/org/:orgId/billing/subscription",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
billing.getOrgSubscription
);
authenticated.get(
"/org/:orgId/billing/usage",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
billing.getOrgUsage
);
authenticated.get("/domain/namespaces", domain.listDomainNamespaces);
authenticated.get(
"/domain/check-namespace-availability",
domain.checkDomainNamespaceAvailability
);
authenticated.put(
"/org/:orgId/remote-exit-node",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createRemoteExitNode),
remoteExitNode.createRemoteExitNode
);
authenticated.get(
"/org/:orgId/remote-exit-nodes",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.listRemoteExitNode),
remoteExitNode.listRemoteExitNodes
);
authenticated.get(
"/org/:orgId/remote-exit-node/:remoteExitNodeId",
verifyOrgAccess,
verifyRemoteExitNodeAccess,
verifyUserHasAction(ActionsEnum.getRemoteExitNode),
remoteExitNode.getRemoteExitNode
);
authenticated.get(
"/org/:orgId/pick-remote-exit-node-defaults",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createRemoteExitNode),
remoteExitNode.pickRemoteExitNodeDefaults
);
authenticated.delete(
"/org/:orgId/remote-exit-node/:remoteExitNodeId",
verifyOrgAccess,
verifyRemoteExitNodeAccess,
verifyUserHasAction(ActionsEnum.deleteRemoteExitNode),
remoteExitNode.deleteRemoteExitNode
);
authenticated.put(
"/org/:orgId/login-page",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.createLoginPage),
loginPage.createLoginPage
);
authenticated.post(
"/org/:orgId/login-page/:loginPageId",
verifyOrgAccess,
verifyLoginPageAccess,
verifyUserHasAction(ActionsEnum.updateLoginPage),
loginPage.updateLoginPage
);
authenticated.delete(
"/org/:orgId/login-page/:loginPageId",
verifyOrgAccess,
verifyLoginPageAccess,
verifyUserHasAction(ActionsEnum.deleteLoginPage),
loginPage.deleteLoginPage
);
authenticated.get(
"/org/:orgId/login-page",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.getLoginPage),
loginPage.getLoginPage
);
export const authRouter = Router();
authRouter.post(
"/remoteExitNode/get-token",
rateLimit({
windowMs: 15 * 60 * 1000,
max: 900,
keyGenerator: (req) =>
`remoteExitNodeGetToken:${req.body.newtId || ipKeyGenerator(req.ip || "")}`,
handler: (req, res, next) => {
const message = `You can only request an remoteExitNodeToken token ${900} times every ${15} minutes. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
},
store: createStore()
}),
remoteExitNode.getRemoteExitNodeToken
);
authRouter.post(
"/transfer-session-token",
rateLimit({
windowMs: 1 * 60 * 1000,
max: 60,
keyGenerator: (req) =>
`transferSessionToken:${ipKeyGenerator(req.ip || "")}`,
handler: (req, res, next) => {
const message = `You can only transfer a session token ${5} times every ${1} minute. Please try again later.`;
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
},
store: createStore()
}),
auth.transferSession
);

View File

@@ -11,7 +11,7 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
import { verifySessionRemoteExitNodeMiddleware } from "@server/middlewares/private/verifyRemoteExitNode"; import { verifySessionRemoteExitNodeMiddleware } from "#private/middlewares/verifyRemoteExitNode";
import { Router } from "express"; import { Router } from "express";
import { import {
db, db,
@@ -55,21 +55,22 @@ import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { z } from "zod"; import { z } from "zod";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { getTraefikConfig } from "../../lib/traefik"; import { getTraefikConfig } from "#private/lib/traefik";
import { import {
generateGerbilConfig, generateGerbilConfig,
generateRelayMappings, generateRelayMappings,
updateAndGenerateEndpointDestinations, updateAndGenerateEndpointDestinations,
updateSiteBandwidth updateSiteBandwidth
} from "../gerbil"; } from "@server/routers/gerbil";
import * as gerbil from "@server/routers/gerbil"; import * as gerbil from "@server/routers/gerbil";
import logger from "@server/logger"; import logger from "@server/logger";
import { decryptData } from "@server/lib/encryption"; import { decryptData } from "@server/lib/encryption";
import { config } from "@server/lib/config"; import config from "@server/lib/config";
import privateConfig from "#private/lib/config";
import * as fs from "fs"; import * as fs from "fs";
import { exchangeSession } from "../badger"; import { exchangeSession } from "@server/routers/badger";
import { validateResourceSessionToken } from "@server/auth/sessions/resource"; import { validateResourceSessionToken } from "@server/auth/sessions/resource";
import { checkExitNodeOrg, resolveExitNodes } from "@server/lib/exitNodes"; import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
import { maxmindLookup } from "@server/db/maxmind"; import { maxmindLookup } from "@server/db/maxmind";
// Zod schemas for request validation // Zod schemas for request validation
@@ -211,7 +212,7 @@ export type UserSessionWithUser = {
}; };
// Root routes // Root routes
const hybridRouter = Router(); export const hybridRouter = Router();
hybridRouter.use(verifySessionRemoteExitNodeMiddleware); hybridRouter.use(verifySessionRemoteExitNodeMiddleware);
hybridRouter.get( hybridRouter.get(
@@ -387,7 +388,7 @@ hybridRouter.get(
} }
const encryptionKeyPath = const encryptionKeyPath =
config.getRawPrivateConfig().server.encryption_key_path; privateConfig.getRawPrivateConfig().server.encryption_key_path;
if (!fs.existsSync(encryptionKeyPath)) { if (!fs.existsSync(encryptionKeyPath)) {
throw new Error( throw new Error(
@@ -1488,6 +1489,4 @@ hybridRouter.post(
); );
} }
} }
); );
export default hybridRouter;

View File

@@ -0,0 +1,42 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import * as orgIdp from "#private/routers/orgIdp";
import * as org from "#private/routers/org";
import { Router } from "express";
import {
verifyApiKey,
verifyApiKeyHasAction,
verifyApiKeyIsRoot,
} from "@server/middlewares";
import { ActionsEnum } from "@server/auth/actions";
import { unauthenticated as ua, authenticated as a } from "@server/routers/integration";
export const unauthenticated = ua;
export const authenticated = a;
authenticated.post(
`/org/:orgId/send-usage-notification`,
verifyApiKeyIsRoot, // We are the only ones who can use root key so its fine
verifyApiKeyHasAction(ActionsEnum.sendUsageNotification),
org.sendUsageNotification
);
authenticated.delete(
"/idp/:idpId",
verifyApiKeyIsRoot,
verifyApiKeyHasAction(ActionsEnum.deleteIdp),
orgIdp.deleteOrgIdp
);

View File

@@ -0,0 +1,36 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import * as loginPage from "#private/routers/loginPage";
import * as auth from "#private/routers/auth";
import * as orgIdp from "#private/routers/orgIdp";
import * as billing from "#private/routers/billing";
import { Router } from "express";
import { verifySessionUserMiddleware } from "@server/middlewares";
import { internalRouter as ir } from "@server/routers/internal";
export const internalRouter = ir;
internalRouter.get("/org/:orgId/idp", orgIdp.listOrgIdps);
internalRouter.get("/org/:orgId/billing/tier", billing.getOrgTier);
internalRouter.get("/login-page", loginPage.loadLoginPage);
internalRouter.post(
"/get-session-transfer-token",
verifySessionUserMiddleware,
auth.getSessionTransferToken
);

View File

@@ -29,9 +29,9 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import { validateAndConstructDomain } from "@server/lib/domainUtils"; import { validateAndConstructDomain } from "@server/lib/domainUtils";
import { createCertificate } from "@server/routers/private/certificates/createCertificate"; import { createCertificate } from "#private/routers/certificates/createCertificate";
import { getOrgTierData } from "@server/routers/private/billing"; import { getOrgTierData } from "#private/lib/billing";
import { TierId } from "@server/lib/private/billing/tiers"; import { TierId } from "@server/lib/billing/tiers";
import { build } from "@server/build"; import { build } from "@server/build";
const paramsSchema = z const paramsSchema = z

View File

@@ -22,9 +22,9 @@ import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import { validateAndConstructDomain } from "@server/lib/domainUtils"; import { validateAndConstructDomain } from "@server/lib/domainUtils";
import { subdomainSchema } from "@server/lib/schemas"; import { subdomainSchema } from "@server/lib/schemas";
import { createCertificate } from "@server/routers/private/certificates/createCertificate"; import { createCertificate } from "#private/routers/certificates/createCertificate";
import { getOrgTierData } from "@server/routers/private/billing"; import { getOrgTierData } from "#private/lib/billing";
import { TierId } from "@server/lib/private/billing/tiers"; import { TierId } from "@server/lib/billing/tiers";
import { build } from "@server/build"; import { build } from "@server/build";
const paramsSchema = z const paramsSchema = z

Some files were not shown because too many files have changed in this diff Show More