From c6a96b08c3c86e9f047766b5668d502c05b9d208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ana=C3=AFs=20Saraiva?= <53227802+Kisakay@users.noreply.github.com> Date: Sun, 16 Aug 2026 16:04:26 +0200 Subject: [PATCH] new version --- .env.example | 4 ++++ HOSTING.md | 1 + README.md | 1 + deploy/nixos/download.nix | 7 +++++++ src/config.ts | 7 +++++++ src/security.ts | 16 +++++++++++----- src/server.ts | 3 ++- 7 files changed, 33 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 4fe6fcc..7b7facd 100644 --- a/.env.example +++ b/.env.example @@ -25,6 +25,10 @@ MAX_STORAGE_USAGE=0.5 # In milliseconds. Default and hard maximum is 48 hours (172800000). FILE_TTL_MS=172800000 +# Length in characters of the upload/download key token (a-z0-9). +# Default 8. Longer keys are harder to brute-force. +KEY_LENGTH=8 + # Brute-force protection: rate limit per client IP on download and # deletion endpoints. Set RATE_LIMIT_ENABLED=0 to disable. RATE_LIMIT_ENABLED=1 diff --git a/HOSTING.md b/HOSTING.md index 8c81460..1b89757 100644 --- a/HOSTING.md +++ b/HOSTING.md @@ -65,6 +65,7 @@ STORAGE_DIR=/opt/filehost/storage MAX_FILE_SIZE=10GB # must be <= proxy upload limit FILE_TTL_MS=172800000 # expire files after 48 hours (hard maximum) MAX_STORAGE_USAGE=0.5 # block uploads when >50% of the disk is used +KEY_LENGTH=8 # length of the upload/download key token RATE_LIMIT_ENABLED=1 TRUST_PROXY=1 # proxy sets X-Forwarded-For ``` diff --git a/README.md b/README.md index 3bfda49..b96acf9 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ cp .env.example .env | `MAX_FILE_SIZE` | `10GB` | Max upload size. Units: `B`, `KB`, `MB`, `GB`, `TB` (or plain int). | | `MAX_STORAGE_USAGE` | `0.5` | Max fraction (0.0–1.0) of the storage disk that may be used; uploads are rejected with `503 try again later` above this. | | `FILE_TTL_MS` | `172800000` | Milliseconds before an upload expires and is deleted (48 hours, hard maximum). | +| `KEY_LENGTH` | `8` | Length in characters of the upload/download key token (a-z0-9). | | `RATE_LIMIT_ENABLED` | `1` | Enable per-IP rate limiting on `/(key)` and `/api/files/`. | | `RATE_LIMIT_MAX` | `300` | Max requests per IP per window. | | `RATE_LIMIT_WINDOW_MS` | `60000` | Rate-limit window length in ms. | diff --git a/deploy/nixos/download.nix b/deploy/nixos/download.nix index 4c8e701..7d21131 100644 --- a/deploy/nixos/download.nix +++ b/deploy/nixos/download.nix @@ -58,6 +58,12 @@ in description = "Maximum fraction (0.0-1.0) of the storage filesystem that may be used; uploads are rejected above this."; }; + keyLength = lib.mkOption { + type = lib.types.ints.positive; + default = 8; + description = "Length in characters of the upload/download key token."; + }; + publicBaseUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; @@ -134,6 +140,7 @@ in "MAX_FILE_SIZE=${cfg.maxFileSize}" "FILE_TTL_MS=${toString cfg.fileTtlMs}" "MAX_STORAGE_USAGE=${toString cfg.maxStorageUsage}" + "KEY_LENGTH=${toString cfg.keyLength}" ] ++ lib.optionals (cfg.publicBaseUrl != null) [ "PUBLIC_BASE_URL=${cfg.publicBaseUrl}" ] ++ lib.optionals cfg.trustProxy [ "TRUST_PROXY=1" ]; diff --git a/src/config.ts b/src/config.ts index 9e96d9f..7f54902 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,6 +7,7 @@ export interface Config { maxFileSize: number; fileTtlMs: number; maxStorageUsage: number; + keyLength: number; publicBaseUrl?: string; rateLimitEnabled: boolean; rateLimitMax: number; @@ -80,6 +81,11 @@ export function loadConfig(env: Record = process.env const trustProxy = boolFromEnv(env.TRUST_PROXY, false); + const keyLength = Number(env.KEY_LENGTH ?? "8"); + if (!Number.isInteger(keyLength) || keyLength < 4 || keyLength > 64) { + throw new Error(`Invalid KEY_LENGTH: ${env.KEY_LENGTH}`); + } + let publicBaseUrl: string | undefined; const rawBase = env.PUBLIC_BASE_URL?.trim(); if (rawBase) { @@ -97,6 +103,7 @@ export function loadConfig(env: Record = process.env maxFileSize, fileTtlMs, maxStorageUsage, + keyLength, publicBaseUrl, rateLimitEnabled: boolFromEnv(env.RATE_LIMIT_ENABLED, true), rateLimitMax, diff --git a/src/security.ts b/src/security.ts index de4e240..4944a04 100644 --- a/src/security.ts +++ b/src/security.ts @@ -1,11 +1,17 @@ import { createHash, createHmac, randomBytes, scryptSync, timingSafeEqual } from "node:crypto"; -export const KEY_LENGTH = 8; +export let KEY_LENGTH = 8; -// Keys are lowercase alphanumeric only (a-z0-9), 8 characters. -export const KEY_PATTERN = /^[a-z0-9]{8}$/; -// The id and the download key are the same 12-char token. -export const ID_PATTERN = KEY_PATTERN; +// Keys are lowercase alphanumeric only (a-z0-9). +export let KEY_PATTERN: RegExp = /^[a-z0-9]{8}$/; +// The id and the download key are the same token. +export let ID_PATTERN = KEY_PATTERN; + +export function setKeyLength(length: number): void { + KEY_LENGTH = length; + KEY_PATTERN = new RegExp(`^[a-z0-9]{${length}}$`); + ID_PATTERN = KEY_PATTERN; +} const KEY_CHARSET = "abcdefghijklmnopqrstuvwxyz0123456789"; const CHARSET_SIZE = KEY_CHARSET.length; diff --git a/src/server.ts b/src/server.ts index d0374fc..32582fc 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,10 +7,11 @@ import { handleAuth } from "./routes/auth"; import { handleDelete } from "./routes/delete"; import { handleDownload } from "./routes/download"; import { handleUpload } from "./routes/upload"; -import { KEY_PATTERN } from "./security"; +import { KEY_PATTERN, setKeyLength } from "./security"; import { Storage } from "./storage"; const cfg = loadConfig(); +setKeyLength(cfg.keyLength); const storage = new Storage(cfg.storageDir); await storage.init();