Commit Graph

32 Commits

Author SHA1 Message Date
c6aa030e54 refactor(style): drop custom webfont, use system fonts
Removes the IBM Plex Mono Google Fonts @import, replaces the --font CSS
variable with a cross-platform system sans-serif stack, and adds a
--font-mono variable for the share-URL readonly input so copy/share text
stays monospace. Also adds line-height: 1.5 to body to compensate for
the system fonts' tighter default leading.

No font asset files exist in /public; layout.ts has no font <link> tags
to remove. Acceptance check: build + 112 tests pass; new
tests/integration/style.test.ts asserts no @import / IBM Plex / external
font URL remains and that the system stack is wired up.
2026-05-03 15:20:22 -07:00
bbd292c085 feat(auth): wire lockout, rate-limit, and constant-time login into both routes
All checks were successful
Deploy to Homelab / deploy (push) Successful in 18s
Both POST /login (HTML form) and POST /api/v1/auth/login now flow through
the shared attemptLogin() handler. Locked accounts respond with 401 +
Retry-After (generic body "Invalid credentials" / "Invalid username or
password") so attackers can't use lockout state as a username-existence
oracle.

@fastify/rate-limit registered with global=false; only the two login
routes opt in via per-route rateLimit config. File uploads and downloads
keep full throughput. Custom errorResponseBuilder logs AUTH_RATE_LIMITED
fire-and-forget so fail2ban can pick it up.

createTestApp now accepts Partial<Config> overrides so integration tests
can dial thresholds down without env-var mutation.
2026-05-03 03:41:51 -07:00
ad36b23061 feat(auth): add shared login handler with constant-time clamp
All checks were successful
Deploy to Homelab / deploy (push) Successful in 18s
Single attemptLogin() orchestrates lockout check, bcrypt verify (against
real or dummy hash), success/failure logging, and a configurable minimum
response-time clamp. Both login routes will share this — no duplication.

Adds three logger events for the operator's escalation pipeline:
AUTH_LOCKOUT_TRIGGERED, AUTH_LOCKED_ATTEMPT, and AUTH_RATE_LIMITED.
fail2ban filters can pick these up to escalate persistent attackers from
in-app lockout to IP ban.

Constant-time defense: unknown users still pay bcrypt cost (via dummy hash)
and the clamp ensures locked-vs-unknown-vs-wrong-password aren't
distinguishable by response time. Username is canonicalized (lowercase + trim)
before lookup so attackers can't bypass lockout via case variation.
2026-05-03 03:34:15 -07:00
11e87f353d feat(auth): add login-attempts DB layer and lockout service
All checks were successful
Deploy to Homelab / deploy (push) Successful in 18s
Persists per-username failed-attempt counts and computed locked_until
timestamps. Lockout service computes exponential-backoff durations
(min(base * 2^(count-threshold), max)) with auto-unlock once locked_until
passes. Successful login deletes the row, resetting the counter.

Pure DB-keyed lockout — survives server restarts and shares state across
both login routes (HTML and JSON) when wired in a later step.
2026-05-03 03:29:09 -07:00
f4eaf88495 feat(auth): add login_attempts schema, lockout config, dummy-hash helper
All checks were successful
Deploy to Homelab / deploy (push) Successful in 29s
Lays the foundation for brute-force defense: per-username attempt tracking
table, configurable lockout/rate-limit thresholds, and a memoized dummy
bcrypt hash so unknown-user paths can be timed identically to wrong-password
paths in a later step.

Adds @fastify/rate-limit dependency for upcoming per-IP rate-limit on
login routes.
2026-05-03 03:26:46 -07:00
d30f40ca71 Merge pull request 'chore: patch dependency vulnerabilities via npm audit fix' (#1) from chore/dependency-security-audit into main
All checks were successful
Deploy to Homelab / deploy (push) Successful in 29s
Reviewed-on: #1
2026-05-03 10:08:43 +00:00
f27ba4922a chore: patch dependency vulnerabilities via npm audit fix
Resolves 7 advisories (1 critical, 3 high, 3 moderate) without
package.json range changes:
- fast-jwt: algorithm confusion, cache key collision, ReDoS
- fastify: content-type validation bypass, host spoofing
- @fastify/static: path traversal & encoded-separator route bypass
- vite (dev only): WS file read, fs.deny bypass, .map traversal
- postcss/picomatch/brace-expansion (transitive): XSS, ReDoS, DoS

npm audit clean; 61 tests pass.
2026-05-03 03:06:47 -07:00
4e89e9783c chore: gitignore .claude/settings.local.json
All checks were successful
Deploy to Homelab / deploy (push) Successful in 1m16s
2026-05-03 02:52:37 -07:00
455cb53aa8 docs: authorize autonomous commits and pushes for this project
Standing authorization so any Claude Code instance commits work after
each logical change (build+tests green) and pushes to the upstream
branch, without waiting to be asked. Force-push and pushes to main from
a feature branch still require explicit approval.
2026-05-03 02:52:27 -07:00
418a553429 refactor: remove unused exported type interfaces
All checks were successful
Deploy to Homelab / deploy (push) Successful in 19s
CreateFileParams, UserRow, CreateUserParams, and MultipartFile were
exported but never imported outside their own modules. Narrowed visibility
to module-local to keep the public surface minimal.

Confirmed with knip (zero findings) and all 61 tests passing.
2026-03-17 10:44:23 -07:00
e25c715fb7 Merge branch 'main' of https://gitea.bchen.dev/brendan/nanodrop
All checks were successful
Deploy to Homelab / deploy (push) Successful in 48s
2026-03-16 16:18:03 -07:00
9fefe5c65d Add HTTP range request support for video streaming
Safari and other browsers require Accept-Ranges: bytes and 206 Partial
Content responses to play video. Without this, large videos fail to load
(especially in Safari) because the entire file had to buffer in memory
before sending.

- Replace readFile + Buffer with createReadStream for efficient streaming
- Parse Range header (start-end, start-, and suffix -N forms)
- Return 206 Partial Content with Content-Range for range requests
- Return 416 Range Not Satisfiable for out-of-bounds ranges
- Add Accept-Ranges: bytes to all raw file responses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:12:21 -07:00
e2944fd828 Export max file size variable
All checks were successful
Deploy to Homelab / deploy (push) Successful in 19s
2026-03-05 20:39:45 +00:00
a4e6a5784a Ensure that app is not accessible via LAN
All checks were successful
Deploy to Homelab / deploy (push) Successful in 17s
2026-03-04 09:48:58 -08:00
241078316c Update Docker port mapping to match both to env var
All checks were successful
Deploy to Homelab / deploy (push) Successful in 8s
2026-03-04 09:44:38 -08:00
85b6f8df2c Export additional variables 2026-03-04 09:44:28 -08:00
00ab308280 Use direct deploy flow without Tailscale
Some checks failed
Deploy to Homelab / deploy (push) Failing after 37s
Directly connect to the public IP of the server, which is static anyways.
2026-03-04 17:38:32 +00:00
d616d4e067 Use an experimental deploy flow
Some checks failed
Deploy to Homelab / deploy (push) Failing after 12s
2026-03-04 09:00:51 +00:00
82793c6d0d Add SSH deployment workflow
Some checks failed
Deploy to Homelab / deploy (push) Failing after 1m37s
2026-03-03 17:08:39 -08:00
8b27038793 Update README 2026-03-03 16:36:06 -08:00
191f5298d1 Update button styling and render additional elements on file page if authenticated 2026-03-03 16:35:02 -08:00
c017761bd1 README: add Caddy reverse proxy example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 16:20:47 -08:00
c9e2d36e78 Add README with setup, Docker, and fail2ban instructions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 16:19:35 -08:00
5a47ae938e Fix four bugs and add logo/branding polish
- docker-compose: add register-user service (profiles: [tools]) with YAML anchor to avoid env duplication
- src/index.ts: show localhost instead of 0.0.0.0 in startup message
- file-view: render <img> inline for image/* MIME types
- file-list: add Copy link button per row (requires baseUrl param)
- layout: add hideLogo option; file view page hides the logo
- style.css: remove uppercase from .logo (Nanodrop not NANODROP), add button.copy-link styles, add .file-view img styles, widen last td

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 16:18:34 -08:00
b5ea21d44c Use inline env vars in docker-compose, verify Docker build works
- docker-compose.yml: replace env_file with explicit environment block
  so all variables are passable directly (JWT_SECRET required, rest have defaults)
- Confirmed Docker image builds successfully

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 16:04:56 -08:00
751862a486 Redesign frontend: IBM Plex Mono, black/white editorial aesthetic
- IBM Plex Mono font from Google Fonts for the monospace character
- Pure black/white/gray palette — no colors except red for errors/danger
- 1px solid black borders system throughout (no shadows, no rounding)
- Header nav uses border-left separators with hover backgrounds
- Primary button: solid black; copy button: white outline (visually distinct)
- Share box: readonly input + copy button flush with no gap
- Table: outer border + gray header row + subtle row hover
- File view: centered with border around video/audio players
- Danger button: red outline, fills on hover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 16:04:18 -08:00
8d5e5c8a4d Fix TypeScript config for .ts import extensions
- allowImportingTsExtensions: true + noEmit: true in tsconfig
- build script runs tsc --noEmit (type-check only)
- Dockerfile simplified to single stage using tsx at runtime
  (no tsc compilation needed; tsx handles .ts imports natively)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 16:02:29 -08:00
6d8fb9105d Code review fixes, Docker, and deployment config
- Fix tsconfig: switch to ESNext/Bundler module resolution (tsx compatible)
- Sanitize file extensions against path traversal (^.[a-zA-Z0-9]+$ only)
- Sanitize Content-Disposition filename to prevent header injection
- Extract tokenCookieOptions helper to eliminate duplication across auth handlers
- Remove unused baseUrl param from fileListPage
- Add Dockerfile (multi-stage build with alpine + native tools for bcrypt)
- Add docker-compose.yml with named volume for data persistence
- Add .env.example with all environment variables documented

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:58:39 -08:00
8fd1464b9d Add server, routes, views, CLI, CSS, and integration tests
- Server factory with Fastify plugins (JWT, cookie, multipart, formbody, static)
- Auth middleware: requireAuth preHandler (401 for API, redirect for pages)
- Auth API routes: POST /api/v1/auth/login, POST /api/v1/auth/logout
- File API routes: GET/POST /api/v1/files, DELETE /api/v1/files/:id
- Page routes: /, /login, /logout, /upload, /files, /files/:id/delete, /f/:id, /f/:id/raw
- HTML views: layout, login, upload, file-list, file-view, not-found
- CLI register-user script
- public/style.css dark theme
- Test helpers: createTestApp, loginAs, buildMultipart
- Integration tests for auth API, file API, and page routes (51 tests passing)
- Update CLAUDE.md with red/green TDD and commit instructions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:55:14 -08:00
157d1e8230 Add auth service, storage service, types, and logging middleware
- Auth service: hashPassword/verifyPassword via bcrypt
- Storage service: saveFile, getFilePath, deleteStoredFile with ENOENT handling
- Types: JwtPayload interface
- Logging middleware: createLogger writing AUTH_FAILURE, AUTH_SUCCESS, FILE_NOT_FOUND
- 27 tests passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:49:22 -08:00
b6aa6211a9 Scaffold project and implement config, DB schema/queries
- Set up package.json (ESM, scripts), tsconfig.json, vitest.config.ts
- Install runtime and dev dependencies
- Add CLAUDE.md with architecture notes and code quality rules
- Config module with env var parsing and JWT_SECRET validation
- DB schema: users + files tables with FK cascade
- DB queries: createUser, getUserBy*, createFile, getFileById, getFilesByUserId, deleteFile
- Tests for config, db/users, db/files (15 tests passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:48:21 -08:00
5902cc404a Initial commit 2026-03-03 15:37:50 -08:00