import path from "node:path"; import config from "./config.ts"; // Auth / identity export const WEBAUTHN_RP_NAME = `${config.OWNER_DISPLAY_NAME}'s Hearthforge`; export const ADMIN_USERNAME = "admin"; export const VALID_USERNAME_RE = /^[a-zA-Z0-9_-]+$/; export const VALID_REPO_NAME_RE = /^[a-zA-Z0-9._-]+$/; export const ALLOWED_REACTIONS = new Set([ "👍", "👎", "❤️", "🎉", "😕", "👀", "🚀", ]); export const VALID_KEY_TYPES = new Set([ "ssh-rsa", "ssh-ed25519", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "sk-ssh-ed25519@openssh.com", "sk-ecdsa-sha2-nistp256@openssh.com", ]); export const CHALLENGE_TTL_MS = 5 * 60 * 1000; // Rate limiting export const LOGIN_MAX_ATTEMPTS = 10; export const LOGIN_RATE_WINDOW_MS = 60_000; export const REGISTRATION_MAX_ATTEMPTS = 3; export const REGISTRATION_RATE_WINDOW_MS = 60 * 60_000; // Bounds the per-IP cost of git smart-HTTP basic auth — every call to // verifyBasicAuth runs argon2 (~100ms) and would otherwise be an // unauthenticated event-loop DOS vector and a brute-force oracle. export const GIT_AUTH_MAX_ATTEMPTS = 10; export const GIT_AUTH_RATE_WINDOW_MS = 60_000; // Per-user / per-IP caps on user-content writes. Numbers are deliberately // roomy for a logged-in person clicking around but tight enough that a // scripted client can't fill the database in seconds. export const COMMENT_MAX_PER_MIN = 30; export const REACTION_MAX_PER_MIN = 60; export const ISSUE_CREATE_MAX_PER_MIN = 10; export const PATCH_CREATE_MAX_PER_MIN = 10; export const REPO_CREATE_MAX_PER_HOUR = 30; export const FILE_EDIT_MAX_PER_MIN = 30; export const RELEASE_WRITE_MAX_PER_MIN = 20; export const LABEL_WRITE_MAX_PER_MIN = 30; export const UPLOAD_MAX_PER_MIN = 10; export const RATE_WINDOW_MIN_MS = 60_000; export const RATE_WINDOW_HOUR_MS = 60 * 60_000; // Session export const SESSION_ID_BYTES = 32; export const SESSION_DURATION_MS = 30 * 24 * 60 * 60 * 1000; export const SESSION_DURATION_SECONDS = 30 * 24 * 60 * 60; export const MIN_PASSWORD_LENGTH = 8; // Cookie lifetimes export const YEAR_SECONDS = 365 * 24 * 60 * 60; // File handling export const BINARY_DETECT_BYTES = 8000; // Git ref limits export const MAX_REF_LIST = 1000; // Text preview export const PREVIEW_MAX_LENGTH = 180; export const PREVIEW_TRUNCATION_THRESHOLD = 0.6; // String length limits export const MAX_BRANCH_NAME_LENGTH = 255; export const MAX_TAG_NAME_LENGTH = 255; export const MAX_TAG_MESSAGE_LENGTH = 500; export const MAX_LABEL_NAME_LENGTH = 50; export const MAX_FILE_PATH_LENGTH = 1000; // Pagination export const REPOS_PER_PAGE = 20; export const COMMITS_PER_PAGE = 20; export const ISSUES_PER_PAGE = 20; export const PATCHES_PER_PAGE = 20; export const RELEASES_PER_PAGE = 20; export const CI_RUNS_PER_PAGE = 20; export const BRANCHES_PER_PAGE = 30; export const TAGS_PER_PAGE = 30; // Cache sizes export const MAX_MD_CACHE = 50; export const MAX_FILE_CACHE = 500; export const MAX_DIFF_CACHE = 500; export const MAX_PATCH_CACHE = 100; export const PATCH_CACHE_TTL_MS = 60 * 60 * 1000; export const MAX_BRANCH_CACHE = 200; export const MAX_TAG_CACHE = 200; export const REF_CACHE_TTL_MS = 30_000; // Paths — derived from config.DATA_DIR via getters so they reflect overrides. export const paths = { get DB_PATH() { return path.join(config.DATA_DIR, "hearthforge.db"); }, get REPOS_DIR() { return path.join(config.DATA_DIR, "repos"); }, get AVATARS_DIR() { return path.join(config.DATA_DIR, "avatars"); }, get RELEASES_DIR() { return path.join(config.DATA_DIR, "releases"); }, get SSH_HOST_KEY_PATH() { return ( process.env.SSH_HOST_KEY_PATH ?? path.join(config.DATA_DIR, "ssh_host_key") ); }, get ALLOWED_SIGNERS_PATH() { return path.join(config.DATA_DIR, "allowed_signers"); }, get CI_ARTIFACTS_DIR() { return path.join(config.DATA_DIR, "ci", "artifacts"); }, };