mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-20 14:47:18 +02:00
206 lines
6.7 KiB
TypeScript
206 lines
6.7 KiB
TypeScript
import { resolve } from "path";
|
|
import { randomBytes } from "crypto";
|
|
|
|
interface Config {
|
|
RESEND_API_KEY: string;
|
|
EMAIL_FROM: string;
|
|
GITHUB_APP_ENABLED: boolean;
|
|
GITHUB_APP_NEW_CONNECTIONS: boolean;
|
|
GITHUB_OAUTH_ENABLED: boolean;
|
|
GITHUB_APP_ID: string;
|
|
GITHUB_APP_SLUG: string;
|
|
GITHUB_APP_CLIENT_ID: string;
|
|
GITHUB_APP_CLIENT_SECRET: string;
|
|
GITHUB_APP_PRIVATE_KEY: string;
|
|
GITHUB_APP_PRIVATE_KEY_FILE: string;
|
|
GITHUB_APP_CALLBACK: string;
|
|
GITHUB_APP_WEBHOOK_SECRET: string;
|
|
|
|
CREDENTIAL_KEYS: string;
|
|
CREDENTIAL_ACTIVE_KEY_ID: string;
|
|
CREDENTIAL_LEGACY_READS: boolean;
|
|
SESSION_SECRET: string;
|
|
REDIS_PORT: number;
|
|
REDIS_HOSTNAME: string;
|
|
CLIENT_ID: string;
|
|
CLIENT_SECRET: string;
|
|
GITHUB_TOKEN: string;
|
|
DEFAULT_QUOTA: number;
|
|
MAX_FILE_FOLDER: number;
|
|
MAX_FILE_SIZE: number;
|
|
MAX_REPO_SIZE: number;
|
|
AUTO_DOWNLOAD_REPO_SIZE: number;
|
|
FREE_DOWNLOAD_REPO_SIZE: number;
|
|
AUTH_CALLBACK: string;
|
|
/**
|
|
* Allow to download repository and files
|
|
*/
|
|
ENABLE_DOWNLOAD: boolean;
|
|
STREAMER_ENTRYPOINT: string | null;
|
|
ANONYMIZATION_MASK: string;
|
|
PORT: number;
|
|
APP_HOSTNAME: string;
|
|
DB_USERNAME: string;
|
|
DB_PASSWORD: string;
|
|
DB_HOSTNAME: string;
|
|
/**
|
|
* Complete MongoDB connection string. When set, this takes precedence over
|
|
* DB_USERNAME, DB_PASSWORD, and DB_HOSTNAME and supports replica-set seed
|
|
* lists and options.
|
|
*/
|
|
MONGODB_URI: string;
|
|
FOLDER: string;
|
|
additionalExtensions: string[];
|
|
S3_BUCKET: string | null;
|
|
S3_CLIENT_ID: string | null;
|
|
S3_CLIENT_SECRET: string | null;
|
|
S3_ENDPOINT: string | null;
|
|
S3_REGION: string | null;
|
|
STORAGE: "filesystem" | "s3";
|
|
/**
|
|
* Value for Express "trust proxy": either a fixed hop count ("1", "2")
|
|
* or a comma-separated list of trusted subnets — named subnets
|
|
* ("loopback", "linklocal", "uniquelocal"), IPs/CIDRs, and the keyword
|
|
* "cloudflare" (expands to Cloudflare's published ranges). The subnet
|
|
* form keeps request.ip pointing at the real visitor even when the
|
|
* proxy chain gains or loses hops. See src/server/trustProxy.ts.
|
|
*/
|
|
TRUST_PROXY: string;
|
|
RATE_LIMIT: number;
|
|
}
|
|
const config: Config = {
|
|
RESEND_API_KEY: "",
|
|
EMAIL_FROM: "",
|
|
GITHUB_APP_ENABLED: false,
|
|
GITHUB_APP_NEW_CONNECTIONS: true,
|
|
GITHUB_OAUTH_ENABLED: true,
|
|
GITHUB_APP_ID: "",
|
|
GITHUB_APP_SLUG: "",
|
|
GITHUB_APP_CLIENT_ID: "",
|
|
GITHUB_APP_CLIENT_SECRET: "",
|
|
GITHUB_APP_PRIVATE_KEY: "",
|
|
GITHUB_APP_PRIVATE_KEY_FILE: "",
|
|
GITHUB_APP_CALLBACK: "http://localhost:5000/github/app/callback",
|
|
GITHUB_APP_WEBHOOK_SECRET: "",
|
|
|
|
// Predictable defaults are dangerous: a known SESSION_SECRET lets anyone
|
|
// forge session cookies. Default to empty and resolve below — random in
|
|
// dev, required in production. See the post-env block.
|
|
CREDENTIAL_KEYS: "",
|
|
CREDENTIAL_ACTIVE_KEY_ID: "",
|
|
CREDENTIAL_LEGACY_READS: false,
|
|
SESSION_SECRET: "",
|
|
CLIENT_ID: "CLIENT_ID",
|
|
CLIENT_SECRET: "CLIENT_SECRET",
|
|
GITHUB_TOKEN: "",
|
|
DEFAULT_QUOTA: 2 * 1024 * 1024 * 1024 * 8,
|
|
MAX_FILE_FOLDER: 1000,
|
|
MAX_FILE_SIZE: 100 * 1024 * 1024, // in b, 100MB
|
|
MAX_REPO_SIZE: 60000, // in kb, 60MB
|
|
AUTO_DOWNLOAD_REPO_SIZE: 150, // in kb, 150kb
|
|
FREE_DOWNLOAD_REPO_SIZE: 150, // in kb, 150kb
|
|
ENABLE_DOWNLOAD: true,
|
|
AUTH_CALLBACK: "http://localhost:5000/github/auth",
|
|
ANONYMIZATION_MASK: "XXXX",
|
|
PORT: 5000,
|
|
// Trust the local reverse proxy (docker network / host nginx) and
|
|
// Cloudflare's edges by subnet rather than by hop count, so client-IP
|
|
// resolution survives Cloudflare adding or removing X-Forwarded-For
|
|
// entries. Set a plain number to restore the legacy hop-count behavior.
|
|
TRUST_PROXY: "loopback,uniquelocal,cloudflare",
|
|
RATE_LIMIT: 350,
|
|
APP_HOSTNAME: "anonymous.4open.science",
|
|
DB_USERNAME: "admin",
|
|
DB_PASSWORD: "password",
|
|
DB_HOSTNAME: "mongodb",
|
|
MONGODB_URI: "",
|
|
REDIS_HOSTNAME: "redis",
|
|
REDIS_PORT: 6379,
|
|
FOLDER: resolve(__dirname, "..", "repositories"),
|
|
additionalExtensions: [
|
|
"license",
|
|
"dockerfile",
|
|
"sbt",
|
|
"ipynb",
|
|
"gp",
|
|
"out",
|
|
"sol",
|
|
"in",
|
|
"jsonl",
|
|
"ndjson",
|
|
],
|
|
STORAGE: "filesystem",
|
|
STREAMER_ENTRYPOINT: null,
|
|
S3_BUCKET: null,
|
|
S3_CLIENT_ID: null,
|
|
S3_CLIENT_SECRET: null,
|
|
S3_ENDPOINT: null,
|
|
S3_REGION: null,
|
|
};
|
|
|
|
for (const conf in process.env) {
|
|
const configRecord = config as unknown as Record<string, unknown>;
|
|
if (configRecord[conf] !== undefined) {
|
|
const currentValue = configRecord[conf];
|
|
const envValue = process.env[conf] as string;
|
|
if (typeof currentValue === "number") {
|
|
const parsed = Number(envValue);
|
|
if (!isNaN(parsed)) {
|
|
configRecord[conf] = parsed;
|
|
}
|
|
} else if (typeof currentValue === "boolean") {
|
|
configRecord[conf] = envValue === "true" || envValue === "1";
|
|
} else {
|
|
configRecord[conf] = envValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Harden security-sensitive secrets that still hold an unset/predictable
|
|
// value after reading the environment (CWE-798).
|
|
const isProduction = process.env.NODE_ENV === "production";
|
|
|
|
// SESSION_SECRET: a known value allows session forgery. Require it in
|
|
// production; in development fall back to a per-process random value so the
|
|
// app still boots without shipping a guessable secret.
|
|
if (!config.SESSION_SECRET || config.SESSION_SECRET === "SESSION_SECRET") {
|
|
if (isProduction) {
|
|
throw new Error(
|
|
"SESSION_SECRET must be set to a strong random value in production"
|
|
);
|
|
}
|
|
config.SESSION_SECRET = randomBytes(32).toString("hex");
|
|
// eslint-disable-next-line no-console
|
|
console.warn(
|
|
"SESSION_SECRET not set — generated a random development secret. " +
|
|
"Sessions will not persist across restarts. Set SESSION_SECRET in production."
|
|
);
|
|
}
|
|
|
|
// Refuse to start in production with the placeholder OAuth credentials or the
|
|
// default database password baked into the image.
|
|
if (isProduction) {
|
|
const insecureDefaults: [string, string][] = config.GITHUB_OAUTH_ENABLED ? [
|
|
["CLIENT_ID", "CLIENT_ID"],
|
|
["CLIENT_SECRET", "CLIENT_SECRET"],
|
|
] : [];
|
|
if (!config.MONGODB_URI) {
|
|
insecureDefaults.push(["DB_PASSWORD", "password"]);
|
|
}
|
|
for (const [key, badValue] of insecureDefaults) {
|
|
if ((config as unknown as Record<string, unknown>)[key] === badValue) {
|
|
throw new Error(
|
|
`${key} is using its insecure default value; set it via the environment in production`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config.GITHUB_APP_ENABLED) {
|
|
for (const name of ["GITHUB_APP_ID", "GITHUB_APP_SLUG", "GITHUB_APP_CLIENT_ID", "GITHUB_APP_CLIENT_SECRET", "GITHUB_APP_CALLBACK", "GITHUB_APP_WEBHOOK_SECRET"] as const) {
|
|
if (!config[name]) throw new Error(`${name} is required when GITHUB_APP_ENABLED=true`);
|
|
}
|
|
if (!config.GITHUB_APP_PRIVATE_KEY && !config.GITHUB_APP_PRIVATE_KEY_FILE) throw new Error("A GitHub App private key is required");
|
|
}
|
|
export default config;
|