Second review pass covering security, performance, admin tooling and the
public/room flows. All HIGH and MEDIUM findings from the audit are resolved;
nothing in this commit changes the visible feature set.
Authentication & session security
- CSP is now set on the request headers in the proxy, which is what Next.js
uses to derive the render nonce, so the nonce is effective.
- 2FA: an already-enabled user cannot re-enroll, the setup endpoint is
rate-limited per account, and confirmed codes are persisted so the second
secret no longer silently never applies.
- Password reset revokes the ticket, authTicket and all personal access
tokens, and bumps the token version so existing sessions die. The same
revocation is now wired into the staff-side password reset.
- /reset and /verify return a stable error code instead of raw text; the
mail lookups are ordered by id so duplicates cannot vary between runs.
- Resending the verification mail gets a per-address cooldown on top of the
per-user limit.
- Issue API tokens with the narrower radio/ticket ability set instead of "*".
Authorization & input handling
- Mid-rank staff can no longer keep dynamically granted non-view admin.*
permissions: existing grants are revoked by migration and the grant lookup
is restricted to "%.view". Rank guards use the dynamic super-admin check.
- Alerting a user is permission-checked and audited like the other tools.
- Material mutations (giveCredits/giveDuckets/giveDiamonds, the admin user
actions route, bulk user actions) are capped and rank-guarded, and bulk
ids are bounded.
- updateRoom / updateRoomItem write through a field allowlist, and items
may only be edited through their own room.
- Classnames reaching the filesystem are validated before use so a crafted
value cannot escape the asset directories.
- The word filter now also covers offline mails, guild forum threads and
replies, and user mottos.
- Media uploads are validated by magic bytes, /api/media requires the page
edit permission, APP_URL must be configured once mail is enabled, and the
diagnostics error route checks the fetch site header.
Admin tooling
- Secret settings render masked and cannot be overwritten with a blank or
an arbitrary raw key; radio credentials are new password inputs.
- Commandocentrum balance changes are audited.
- Admin list pagination reads the caller's per-page instead of the max, and
the log exporter caps offset and search length.
Performance
- Catalog translations are cached per module, with a cheap revision hash;
the public online count uses a stale window instead of hammering the DB.
- The cache warmup now primes the payload the home route actually reads.
- TopHeader batches its queries into one round trip, and LCP avatars load
eagerly.
- motion/react and sonner are no longer part of the root layout; the nav
dropdown and mobile nav panels are lazy client chunks. Anonymous visitors
again get the navigation chrome, and public pages get an edge cacheable
response.
Accessibility
- Nested <main> elements in phase pages became <section>; the page entrance
and route progress animations are pure CSS that respect reduced motion.
- Stack the /me dashboard hero (avatar + info) on small screens instead of
crowding them side by side; center the avatar and info on mobile
- Respect prefers-reduced-motion in the Reveal scroll animation
- Make the desktop pitch-in (NavDropdown) and mobile menu more consistent;
give the mobile menu a max-height with overscroll containment so long
navs scroll instead of overflowing on small phones
- Add safe-area insets for notched devices on header, nav and footer
- Polish whole-link content-cards (community tiles) with hover lift and a
visible focus ring
- Enforce a comfortable min touch height on all .btn buttons
Drop the hardcoded FALLBACK_HOTEL_NAME ("Atom") preset and the brand.ts
module. HOTEL_NAME is now a required env var: if it (and the CMS hotel_name
setting) is missing the site fails validation at startup/build with a clear
message instead of silently rendering a placeholder hotel name.
resolveHotelName() resolves CMS hotel_name -> required HOTEL_NAME only.
Callers that used the preset (api/home route catch branch, CMS settings form
default, mobile-nav/logo-generator prop defaults) now use the configured name
or an empty default; the real name is already passed in by server parents.
Route all user-facing Atom hotel defaults through resolveHotelName (settings then HOTEL_NAME env then brand constant). Exclude Playwright e2e from tsconfig until deps are installed.
Co-authored-by: Cursor <[email protected]>
- Add framer-motion and tailwindcss-animate for transitions
- Add page transitions via AnimatePresence in the site layout
- Convert nav dropdown and mobile menu to animated motion components
- Add entry animation to the radio player widget
- Add reveal/stagger animations to the home page views
- Add shared motion utilities and reusable animated components
- Mobile nav: uses <details>/<summary> for toggle, no useState, no useEffect
- Nav dropdown: uses <details>/<summary>, no useState, no event listeners
- Desktop: separate div with desktop nav items (hidden on mobile)
- Mobile: hamburger dropdown with absolute positioned panel
- CSS: removed ::after pseudo-element with transform, replaced hover
with background-color only, added details[open] CSS rules
- No backdrop-filter, no will-change, no transition-all, no GPU hacks
- Works on every device without JS state management
- mobile-nav: pure block/hidden toggle, no transforms, no transition-all
- nav-dropdown: clean block/hidden toggle, no CSS animation hacks
- navigation: removed shadow-sm, no will-change, no GPU compositing
- top-header: consistent mobile layout, clean details/summary dropdowns
- admin-mobile-wrapper: inline transition instead of CSS class for sidebar
- globals.css: removed transition-all from nav-item/dropdown-item, replaced
with specific color/background-color transitions only, added hover bg
- Remove duplicate home link in mobile nav
- Remove overflow-hidden clipping main content
- Improve mobile menu scrolling and spacing
- Make top-header currencies wrap on small screens
- Reduce site-header height on mobile
- Add global mobile CSS overrides for tables, padding, fonts
- Reduce dropdown background opacity from 97% to 80% so backdrop-filter is visible
- Add glass-dropdown CSS class with blur(16px) and saturate(180%)
- Add backdrop blur to mobile nav overlay for smoother feel
- Increase photo lightbox backdrop blur from 2px to 8px
- Add background-blend-mode: overlay to site-bg for richer texture