fix(dev-lan): unblock phone-on-LAN testing of the dev server

Branding URLs were baked with env.APP_URL=http://localhost:3000 at
upload time and stored verbatim in system_settings, so any logo/
background loaded from a non-localhost origin (an iPhone hitting the
Mac's LAN IP) failed to resolve. Same pattern bit Socket.IO (CORS +
client connection target) and the portal logout redirect.

- Branding: getPortBrandingConfig normalizes localhost/private-LAN
  hosts to path-only; both upload routes store path-only going
  forward; email shell re-absolutizes via absolutizeBrandingUrl() so
  inboxes (no app origin) still get fetchable URLs. DB backfilled to
  strip http://localhost:3000 from existing rows.
- Socket.IO: client connects to window.location.origin (io() with no
  URL); server CORS allows localhost + private-LAN ranges in dev,
  stays locked to APP_URL in prod.
- Portal logout: redirect target built from the request URL instead
  of env.APP_URL.
- next.config: allowedDevOrigins widened from a hardcoded IP to
  192.168/10/172.16-31 wildcards so HMR works across networks
  without an edit per-network. (Without HMR the login form's React
  click handler never hydrates and the form falls back to GET,
  leaking the password into the URL.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 12:28:34 +02:00
parent 6aaccb6d33
commit be261f3f90
10 changed files with 124 additions and 30 deletions

View File

@@ -8,6 +8,7 @@
* env var when neither is set.
*/
import { env } from '@/lib/env';
import { normalizeBrandingUrl } from '@/lib/branding/url';
import { getSetting } from '@/lib/services/settings.service';
// ─── Setting key constants ───────────────────────────────────────────────────
@@ -572,8 +573,14 @@ export async function getPortBrandingConfig(portId: string): Promise<PortBrandin
]);
return {
logoUrl: logoUrl ?? DEFAULT_BRANDING.logoUrl,
emailBackgroundUrl: emailBackgroundUrl ?? DEFAULT_BRANDING.emailBackgroundUrl,
// Branding URLs that bake a localhost/LAN host (uploaded while running
// on the dev's Mac) don't resolve from any other device. Normalize
// here so in-app consumers get a path-only URL the browser resolves
// against the current origin. Email surfaces re-absolutize via
// `absolutizeBrandingUrl()` because mail clients have no app origin.
logoUrl: normalizeBrandingUrl(logoUrl) ?? DEFAULT_BRANDING.logoUrl,
emailBackgroundUrl:
normalizeBrandingUrl(emailBackgroundUrl) ?? DEFAULT_BRANDING.emailBackgroundUrl,
primaryColor: primaryColor ?? DEFAULT_BRANDING.primaryColor,
appName: appName ?? DEFAULT_BRANDING.appName,
emailHeaderHtml: emailHeaderHtml ?? DEFAULT_BRANDING.emailHeaderHtml,