constants.ts
Raw
1import path from "node:path";
2import config from "./config.ts";
3
4// Auth / identity
5export const WEBAUTHN_RP_NAME = `${config.OWNER_DISPLAY_NAME}'s Hearthforge`;
6export const ADMIN_USERNAME = "admin";
7export const VALID_USERNAME_RE = /^[a-zA-Z0-9_-]+$/;
8export const VALID_REPO_NAME_RE = /^[a-zA-Z0-9._-]+$/;
9export const ALLOWED_REACTIONS = new Set([
10 "👍",
11 "👎",
12 "❤️",
13 "🎉",
14 "😕",
15 "👀",
16 "🚀",
17]);
18export const VALID_KEY_TYPES = new Set([
19 "ssh-rsa",
20 "ssh-ed25519",
21 "ecdsa-sha2-nistp256",
22 "ecdsa-sha2-nistp384",
23 "ecdsa-sha2-nistp521",
24 "sk-ssh-ed25519@openssh.com",
25 "sk-ecdsa-sha2-nistp256@openssh.com",
26]);
27export const CHALLENGE_TTL_MS = 5 * 60 * 1000;
28
29// Rate limiting
30export const LOGIN_MAX_ATTEMPTS = 10;
31export const LOGIN_RATE_WINDOW_MS = 60_000;
32export const REGISTRATION_MAX_ATTEMPTS = 3;
33export const REGISTRATION_RATE_WINDOW_MS = 60 * 60_000;
34// Bounds the per-IP cost of git smart-HTTP basic auth — every call to
35// verifyBasicAuth runs argon2 (~100ms) and would otherwise be an
36// unauthenticated event-loop DOS vector and a brute-force oracle.
37export const GIT_AUTH_MAX_ATTEMPTS = 10;
38export const GIT_AUTH_RATE_WINDOW_MS = 60_000;
39
40// Per-user / per-IP caps on user-content writes. Numbers are deliberately
41// roomy for a logged-in person clicking around but tight enough that a
42// scripted client can't fill the database in seconds.
43export const COMMENT_MAX_PER_MIN = 30;
44export const REACTION_MAX_PER_MIN = 60;
45export const ISSUE_CREATE_MAX_PER_MIN = 10;
46export const PATCH_CREATE_MAX_PER_MIN = 10;
47export const REPO_CREATE_MAX_PER_HOUR = 30;
48export const FILE_EDIT_MAX_PER_MIN = 30;
49export const RELEASE_WRITE_MAX_PER_MIN = 20;
50export const LABEL_WRITE_MAX_PER_MIN = 30;
51export const UPLOAD_MAX_PER_MIN = 10;
52export const RATE_WINDOW_MIN_MS = 60_000;
53export const RATE_WINDOW_HOUR_MS = 60 * 60_000;
54
55// Session
56export const SESSION_ID_BYTES = 32;
57export const SESSION_DURATION_MS = 30 * 24 * 60 * 60 * 1000;
58export const SESSION_DURATION_SECONDS = 30 * 24 * 60 * 60;
59export const MIN_PASSWORD_LENGTH = 8;
60
61// Cookie lifetimes
62export const YEAR_SECONDS = 365 * 24 * 60 * 60;
63
64// File handling
65export const BINARY_DETECT_BYTES = 8000;
66
67// Git ref limits
68export const MAX_REF_LIST = 1000;
69
70// Text preview
71export const PREVIEW_MAX_LENGTH = 180;
72export const PREVIEW_TRUNCATION_THRESHOLD = 0.6;
73
74// String length limits
75export const MAX_BRANCH_NAME_LENGTH = 255;
76export const MAX_TAG_NAME_LENGTH = 255;
77export const MAX_TAG_MESSAGE_LENGTH = 500;
78export const MAX_LABEL_NAME_LENGTH = 50;
79export const MAX_FILE_PATH_LENGTH = 1000;
80
81// Pagination
82export const REPOS_PER_PAGE = 20;
83export const COMMITS_PER_PAGE = 20;
84export const ISSUES_PER_PAGE = 20;
85export const PATCHES_PER_PAGE = 20;
86export const RELEASES_PER_PAGE = 20;
87export const CI_RUNS_PER_PAGE = 20;
88// Cap a single CI step's captured log so a chatty step can't exhaust server
89// RAM (it is buffered in memory) or bloat the ci_steps row.
90export const CI_MAX_LOG_BYTES = 2 * 1024 * 1024;
91export const BRANCHES_PER_PAGE = 30;
92export const TAGS_PER_PAGE = 30;
93
94// Cache sizes
95export const MAX_MD_CACHE = 50;
96export const MAX_FILE_CACHE = 500;
97export const MAX_DIFF_CACHE = 500;
98export const MAX_PATCH_CACHE = 100;
99export const PATCH_CACHE_TTL_MS = 60 * 60 * 1000;
100export const MAX_BRANCH_CACHE = 200;
101export const MAX_TAG_CACHE = 200;
102export const REF_CACHE_TTL_MS = 30_000;
103
104// Paths — derived from config.DATA_DIR via getters so they reflect overrides.
105export const paths = {
106 get DB_PATH() {
107 return path.join(config.DATA_DIR, "hearthforge.db");
108 },
109 get REPOS_DIR() {
110 return path.join(config.DATA_DIR, "repos");
111 },
112 get AVATARS_DIR() {
113 return path.join(config.DATA_DIR, "avatars");
114 },
115 get RELEASES_DIR() {
116 return path.join(config.DATA_DIR, "releases");
117 },
118 get SSH_HOST_KEY_PATH() {
119 return (
120 process.env.SSH_HOST_KEY_PATH ??
121 path.join(config.DATA_DIR, "ssh_host_key")
122 );
123 },
124 get ALLOWED_SIGNERS_PATH() {
125 return path.join(config.DATA_DIR, "allowed_signers");
126 },
127 get CI_ARTIFACTS_DIR() {
128 return path.join(config.DATA_DIR, "ci", "artifacts");
129 },
130};
131