Skip to content

Harden security model and refresh UI to an editorial Obsidian Gallery theme#1

Merged
isiliconx merged 1 commit into
mainfrom
refactor/security-hardening-and-luxury-redesign
May 26, 2026
Merged

Harden security model and refresh UI to an editorial Obsidian Gallery theme#1
isiliconx merged 1 commit into
mainfrom
refactor/security-hardening-and-luxury-redesign

Conversation

@isiliconx
Copy link
Copy Markdown
Owner

This pull request was created by @kiro-agent on behalf of @isiliconx 👻

Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro autonomous agent


Summary

This PR addresses every issue from the recent code review and reskins the app to a more refined aesthetic.

Security & correctness

  • Single source of config (server/config.js). Production fail-fast on missing/placeholder JWT_SECRET; development gets an ephemeral random secret with a clear warning. No more duplicate secret reads in auth.js and index.js.
  • Authorization on every transfer event. Only the sender can push chunks or mark a transfer complete; only the recipient can accept or reject; cancel requires the caller to be a party. UUID transferIds alone are not sufficient.
  • Chunk payloads are size-bounded (3 MiB hard cap) and shape-validated before being relayed. Socket.IO maxHttpBufferSize lowered from 100 MiB to 4 MiB.
  • Idle transfer sweep auto-cancels abandoned transfers (default: no chunk activity for 60 s). Prevents memory leaks when a sender ghosts mid-stream. Cancel events now carry a reason field (cancelled_by_peer | peer_disconnected | idle_timeout).
  • Rate limiting (no extra deps): per-IP sliding window on /login (5/15 m) and /register (5/1 h); per-socket on chat messages.
  • Atomic, serialized writes to users.json. The previous read-modify-write was racy; now goes through a single-writer queue with temp-file + rename.
  • Constant-ish-time login: bcrypt compare runs even on missing user, mitigating user-enumeration via timing.
  • Stronger defaults: bcrypt cost 12, password min length 8.
  • Security headers (no helmet dep): X-Content-Type-Options, X-Frame-Options: DENY, Referrer-Policy: no-referrer, Permissions-Policy locking down camera/mic/geo/payment, and a tight CSP allowing self + Google Fonts + Socket.IO CDN. Plus compression.
  • CORS allowlist: defaults to same-origin (recommended for LAN), supports comma-separated allowlist or * via env.
  • Scoped catch-all: /api/* fall-throughs now return JSON 404 instead of the SPA shell.
  • Graceful shutdown on SIGINT/SIGTERM/uncaughtException, with a server:shutdown broadcast and 10 s force-exit safety net.
  • Multi-tab awareness: a user's transfers aren't cancelled when one of several tabs disconnects.

Client cleanups

  • Removed the unused logout import and the dynamic import('./app.js') workaround for the circular dependency in auth.js.
  • escapeHtml rewritten as a regex+lookup table (no DOM allocation).
  • Browser Notifications are now opt-in and only fire when the tab is hidden, ending the prior toast-spam behaviour.
  • pendingFile is now a correlation-id Map, removing the race when multiple transfer requests are in flight.
  • Page-wide drag-and-drop: drop anywhere on the dashboard, not just inside the dropzone.
  • Live transfer rate + ETA on every card (rolling 0.5 s sample window).
  • Cancel button on every active transfer.
  • Multiple incoming requests queue cleanly so none are lost.
  • 429 retryAfter now surfaced in the auth UI.

UI — Obsidian Gallery theme

A full reskin built around restraint:

  • Graded obsidian surfaces (--ink-0 through --ink-4), platinum text scale (--pearl-100 through --pearl-05), champagne-gold accents.
  • Typography: Cormorant Garamond for the wordmark and section headings; Inter for body. The italic Drop in the brand carries the gold gradient.
  • Subtle SVG noise grain over the obsidian field; three slow aurora orbs (champagne, plum, slate) drift over 38–54 s.
  • All emojis replaced with an inline SVG sprite (hex, user, lock, power, upload, download, inbox, chat, send, close).
  • Hairline borders, deep soft shadows, tabular numerals for sizes and percentages.
  • New page-wide drag overlay with a champagne-tinted dashed border.
  • prefers-reduced-motion is honoured.
  • New theme-color / color-scheme metas; ARIA attributes on modals, tabs, and the toast region.

Testing

  • All server and client modules pass node --check.
  • End-to-end HTTP smoke test confirms:
    • 200 on GET / with full security header set
    • Register/login/me/error paths return correct codes (201 / 400 / 409 / 401 / 200)
    • /api/unknown returns JSON 404 (scoped catch-all working)
    • Rate limiter activates exactly per spec (6th attempt → 429)
    • Graceful SIGINT shutdown completes cleanly

Notes for reviewers

  • One new dependency: compression (~10 KB).
  • JWT_SECRET in .env is now blank by default (so dev gets the auto-generated ephemeral secret). For production, set it to a long random string (the example has the one-liner).
  • The old users.json schema is unchanged; existing accounts work without migration. Existing passwords stay valid (any new ones are hashed at cost 12).

…y' theme

Server
------
* New server/config.js as a single source of truth for env settings.
  Production fail-fast on missing/placeholder JWT_SECRET; development
  generates an ephemeral random secret with a clear warning.
* Authorization is now enforced on every transfer event: only the
  sender can push chunks or mark a transfer complete, only the
  recipient can accept or reject, and cancellation requires the
  caller to be a party to the transfer. UUID transferIds were not
  sufficient on their own.
* Chunk payloads are size-bounded (3 MiB hard cap) and shape-validated
  before being relayed.
* Transfers with no chunk activity are auto-cancelled by an idle sweep,
  preventing memory leaks when a sender abandons a transfer without
  disconnecting. Cancel events now carry a reason field
  (cancelled_by_peer | peer_disconnected | idle_timeout).
* Per-socket chat rate limit (sliding window) and per-IP rate limits
  on /login (5/15m) and /register (5/1h), no extra dependencies.
* users.json writes go through a single-writer queue and an atomic
  temp-file + rename, eliminating the prior read-modify-write race.
* Login now performs a bcrypt compare even when the user does not
  exist, mitigating user-enumeration via timing.
* Bcrypt cost bumped to 12; minimum password length is now 8 (with
  a 128-char ceiling).
* Express now sends X-Content-Type-Options, X-Frame-Options, a
  Referrer-Policy, a tight Permissions-Policy, and a CSP that allows
  self plus Google Fonts and the Socket.IO CDN. compression is
  enabled. CORS supports an explicit allowlist (defaults to
  same-origin, which is the recommended LAN posture).
* /api/* fall-throughs now return JSON 404 instead of the SPA shell.
* Socket.IO maxHttpBufferSize lowered from 100 MiB to 4 MiB.
* SIGINT/SIGTERM trigger a graceful shutdown that closes Socket.IO
  and HTTP cleanly, with a 10s force-exit safety net.
* Multi-tab disconnects no longer cancel a user's transfers when other
  tabs are still connected.

Client
------
* public/js/app.js: removed the unused logout import and the dynamic
  import workaround for the circular auth dependency. initAuth now
  receives showNotification through its options object. escapeHtml
  is a regex-based lookup table, no DOM allocation. Browser
  Notifications are now opt-in per call and only fire when the tab
  is hidden, ending the prior toast-spam behaviour. Added a handler
  for the new server:shutdown event.
* public/js/fileTransfer.js: pendingFile is now a correlation-id
  Map, removing the race when multiple requests are in flight.
  Drag-and-drop now works anywhere on the dashboard, not just inside
  the dropzone. Each transfer card shows live transfer rate and ETA
  (rolling 0.5 s sample window), and exposes a cancel button.
  Multiple incoming requests queue cleanly so none are lost.
* public/js/auth.js: no longer dynamically imports app.js, accepts
  showNotification via init, surfaces 429 retryAfter, raises the
  password minimum to 8.
* public/js/chat.js: exposes getActiveChatTarget() so app.js can
  decide whether an inbound chat should toast.
* public/js/dashboard.js: avatar palette refined to the new theme;
  activity feed restructured for typographic detail rather than
  emoji glyphs.

UI
--
* New 'Obsidian Gallery' theme: graded obsidian surfaces, platinum
  text, champagne-gold accents, hairline borders, deep soft shadows.
* Cormorant Garamond serif for the wordmark and section headings,
  Inter for body. The italic 'Drop' in the brand carries the gold
  gradient.
* Subtle SVG noise grain over the obsidian field; three slow aurora
  orbs (champagne, plum, slate) drift over 38–54 s.
* All UI emojis replaced with an inline SVG sprite (hex, user, lock,
  power, upload, download, inbox, chat, send, close).
* Drop zone, transfer cards, modals, and notifications redesigned
  with restrained motion, tracked uppercase labels, and tabular
  numerals for sizes and percentages.
* Page-wide drag overlay shows a champagne-tinted dashed border
  whenever a file enters the window.
* Reduced-motion media query disables animations for users who
  prefer that.
* New theme-color and color-scheme metas; ARIA attributes added
  on modals, tabs, and notification regions.

Tests
-----
* All server and client modules pass node --check.
* End-to-end HTTP smoke test verifies status codes, security
  headers, the scoped 404 for unknown /api routes, and the rate
  limiter triggering on the configured threshold.

Co-authored-by: isilicon <219942559+isiliconx@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant