feat(branding): customize login page title and logo via env vars (#3773)#3781
Conversation
Add CUSTOM_TITLE and CUSTOM_LOGO_URL environment variables so operators deploying MeshMonitor for a community/organization can rebrand the login page without forking and rebuilding the image. - environment.ts: parse both vars; validate CUSTOM_LOGO_URL scheme (http(s)/data:image/relative only, rejecting javascript: etc.) - authRoutes /status: expose branding (null when unset) pre-auth so the login page can read it - AuthContext: optional customTitle/customLogoUrl on AuthStatus - LoginPage: render custom logo image + title, falling back to the built-in SVG and "MeshMonitor" heading when unset - docs + .env.example: document the new variables - tests: cover default/null, set values, and unsafe-scheme rejection Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: feat(branding) - Custom Login Page Branding
Overall this is a clean, well-scoped feature. The approach of piggybacking on the existing Unrelated Files in this PRThree files in the diff have nothing to do with login branding and should be removed before merge:
These are likely worktree merge artifacts. Please remove them before merging. Security
Logo URL is served pre-auth The branding values are returned to unauthenticated users in the Code QualityDouble-trim on
v.startsWith('/')This allows paths like
The approach of building
customTitle: null,
customLogoUrl: null,This is correct and explicit — good practice. Test CoverageThe new tests in
Missing test: whitespace-only
Missing test: Given the security concern above about SVG data URIs, a test either asserting it's accepted or rejected (depending on the decision made) would be valuable to document the intent.
customTitle?: string | null;
customLogoUrl?: string | null;Using MinorThe SummaryAction required before merge:
Nice to have:
The core feature logic is solid — the URL validation, fallback behavior, and test structure are all well done. |
…/tests - Remove three unrelated files that `git add -A` swept into the branch (.claude/agent-memory/* and docs/internal/dev-notes/MCP_SERVER_PLAN.md) - CUSTOM_LOGO_URL: reject data:image/svg+xml (SVG can embed scripts); whitelist raster data: URIs (png/jpeg/gif/webp/avif) only - AuthStatus.customTitle/customLogoUrl are now non-optional (string | null) to match the runtime guarantee that /status always sends them - Add edge-case tests: whitespace-only title, SVG data URI rejected, raster data URI accepted - Docs/.env.example note the SVG exclusion and relative-path origin Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: feat(branding) - Custom Login Page Branding
This is a follow-up review after the What Was Fixed ✅All major issues from the prior review have been addressed:
Remaining Issue: Unrelated Files Still in PRDespite
Please run Minor Code Quality NotesDouble-trim still present ( const customTitle = authStatus?.customTitle?.trim() || 'MeshMonitor';
const customLogoUrl = authStatus?.customLogoUrl?.trim() || null;
const customTitle = authStatus?.customTitle || 'MeshMonitor';
const customLogoUrl = authStatus?.customLogoUrl || null;Relative path validation allows any server path ( v.startsWith('/')This accepts paths like Security: No New Concerns
Test Coverage: SolidThe
No gaps remain in the test coverage for the core validation paths. SummaryMust fix before merge:
Nice to have (not blocking):
The core feature logic, validation, fallback behavior, and test structure are all solid. ✅ |
These were untracked working-tree artifacts at branch creation that a prior `git add -A` swept in. Untrack them (kept on disk locally). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: feat(branding) - Custom Login Page Branding
This is a solid update — the previous review feedback has been addressed well. Here's the current state: What Was Fixed Since the Previous ReviewSecurity: TypeScript types: Test coverage: All four edge cases are now covered (lines 1140–1168):
Remaining Issue: Unrelated Files Still PresentThe Remaining Minor IssuesDouble-trim on const customTitle = authStatus?.customTitle?.trim() || 'MeshMonitor';
const customTitle = authStatus?.customTitle || 'MeshMonitor';
<img src={customLogoUrl} ... />This is fine as a default, but if the operator sets a
const customLogoUrl = authStatus?.customLogoUrl?.trim() || null;The value from the server is already trimmed and validated. Same minor redundancy as above. Code Quality: Good
SummaryThis PR is ready to merge. The core security concern (SVG data URIs) was fixed, types were tightened, and edge-case tests were added. The only remaining items are cosmetic (unnecessary |
|
Thanks for the thorough review! Addressed in cb2ca4c and 4874ec4: Action required:
Nice to have:
Intentionally left as-is:
|
The server already trims and normalizes CUSTOM_TITLE/CUSTOM_LOGO_URL (null when unset), so the client-side .trim() was dead code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: feat(branding) - Custom Login Page Branding
This is now the fourth review cycle. The latest commit ( What Was Addressed in the Latest Commit ✅Redundant // Before (redundant):
const customTitle = authStatus?.customTitle?.trim() || 'MeshMonitor';
const customLogoUrl = authStatus?.customLogoUrl?.trim() || null;
// After (clean):
const customTitle = authStatus?.customTitle || 'MeshMonitor';
const customLogoUrl = authStatus?.customLogoUrl || null;The comment at line 33–35 explains the reasoning clearly — the server already trims and normalizes these values. ✅ No Remaining IssuesUnrelated files: All three previously flagged files ( Security — SVG data URIs blocked ( Security — TypeScript types ( Test coverage ( Documentation ( Minor Observations (Non-blocking)
SummaryAll previous action-required items have been resolved. The feature is complete, secure, well-tested, and documented. Ready to merge. |
Summary
Closes #3773. Adds native support for custom login-page branding via environment variables, so operators deploying MeshMonitor as a dashboard for a community or organization can rebrand the login screen without forking and rebuilding the Docker image.
CUSTOM_TITLEMeshMonitorCUSTOM_LOGO_URLBoth are optional and gracefully fall back to the defaults when unset.
How it works
The login page renders client-side, so branding is delivered at runtime through the existing public
/api/auth/statusendpoint (same channel already used foroidcEnabled/localAuthDisabled). No new endpoint, no build-time baking.environment.ts— parses both vars.CUSTOM_LOGO_URLis validated to allow onlyhttp(s)://, protocol-relative,data:image/, or same-origin relative paths; anything else (e.g.javascript:) is logged and ignored.authRoutes.ts—/statusexposescustomTitle/customLogoUrl(null when unset) in all three response branches.AuthContext.tsx— optional fields added toAuthStatus.LoginPage.tsx— renders an<img>when a logo URL is set, otherwise the original SVG; the<h1>uses the custom title orMeshMonitor..env.exampleanddocs/configuration/index.mdupdated.Testing
authRoutes.test.tscases: null-by-default, values exposed when set, unsafe-scheme rejection.success: true, 0 failures);AuthContext.test.tsxpasses (120/120).tscerrors introduced over baseline.🤖 Generated with Claude Code