Merge pull request #520 from maartendra/feat/login-show-totp-version

feat(login): add LOGIN_SHOW_TOTP and LOGIN_SHOW_VERSION config flags
This commit is contained in:
Linus Rath
2026-07-22 08:27:44 +02:00
committed by GitHub
4 changed files with 27 additions and 4 deletions
+9 -4
View File
@@ -133,7 +133,7 @@ export default function LoginPage() {
const isMobileHandoff = Boolean(mobileRedirectUri);
const { login, loginDemo, isLoading, error, clearError, isAuthenticated } = useAuthStore();
const { theme, setTheme, initializeTheme } = useThemeStore(useShallow((s) => ({ theme: s.theme, setTheme: s.setTheme, initializeTheme: s.initializeTheme })));
const { appName, jmapServerUrl: configuredServerUrl, oauthEnabled, oauthOnly, oauthClientId: globalOauthClientId, oauthIssuerUrl: globalOauthIssuerUrl, oauthScopes, rememberMeEnabled, devMode, demoMode, loginLogoLightUrl, loginLogoDarkUrl, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, loginLogoMaxHeight, loginLogoMaxWidth, loginShowHeading, loginShowSubtitle, isLoading: configLoading, error: configError, autoSsoEnabled, embeddedMode: _embeddedMode, allowCustomJmapEndpoint, jmapServers, jmapServerAutoPickByDomain } = useConfig();
const { appName, jmapServerUrl: configuredServerUrl, oauthEnabled, oauthOnly, oauthClientId: globalOauthClientId, oauthIssuerUrl: globalOauthIssuerUrl, oauthScopes, rememberMeEnabled, devMode, demoMode, loginLogoLightUrl, loginLogoDarkUrl, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, loginLogoMaxHeight, loginLogoMaxWidth, loginShowHeading, loginShowSubtitle, loginShowTotp, loginShowVersion, isLoading: configLoading, error: configError, autoSsoEnabled, embeddedMode: _embeddedMode, allowCustomJmapEndpoint, jmapServers, jmapServerAutoPickByDomain } = useConfig();
const resolvedTheme = useThemeStore((s) => s.resolvedTheme);
// Login logo sizing: when a max height/width is configured, drop the fixed
@@ -822,7 +822,7 @@ export default function LoginPage() {
)}
</div>
)}
<VersionBadge />
{loginShowVersion && <VersionBadge />}
</div>
</div>
</div>
@@ -1154,8 +1154,12 @@ export default function LoginPage() {
</div>
</div>
{/* 2FA toggle / field */}
{/* 2FA toggle / field. The manual toggle can be hidden via
LOGIN_SHOW_TOTP (loginShowTotp) for deployments whose mail
server has no per-account TOTP (auth delegated to an
external directory); server-required TOTP still shows. */}
{!showTotpField ? (
loginShowTotp ? (
<button
type="button"
onClick={() => {
@@ -1167,6 +1171,7 @@ export default function LoginPage() {
<Shield className="w-3.5 h-3.5" />
{t("totp_toggle")}
</button>
) : null
) : (
<div className="space-y-1.5">
<label htmlFor="totp" className="block text-sm font-medium text-foreground">
@@ -1361,7 +1366,7 @@ export default function LoginPage() {
)}
</div>
)}
<VersionBadge />
{loginShowVersion && <VersionBadge />}
</div>
</div>
</div>
+2
View File
@@ -78,6 +78,8 @@ export async function GET(request: NextRequest) {
loginLogoMaxWidth: configManager.get<string>('loginLogoMaxWidth', ''),
loginShowHeading: configManager.get<boolean>('loginShowHeading', true),
loginShowSubtitle: configManager.get<boolean>('loginShowSubtitle', true),
loginShowTotp: configManager.get<boolean>('loginShowTotp', true),
loginShowVersion: configManager.get<boolean>('loginShowVersion', true),
demoMode: configManager.get<boolean>('demoMode', false),
allowCustomJmapEndpoint: configManager.get<boolean>('allowCustomJmapEndpoint', false),
jmapServers: redactJmapServers(parseJmapServers(configManager.get<unknown>('jmapServers', []))),
+8
View File
@@ -30,6 +30,8 @@ interface ConfigData {
loginLogoMaxWidth: string;
loginShowHeading: boolean;
loginShowSubtitle: boolean;
loginShowTotp: boolean;
loginShowVersion: boolean;
demoMode: boolean;
autoSsoEnabled: boolean;
allowCustomJmapEndpoint: boolean;
@@ -113,6 +115,8 @@ export function useConfig(): AppConfig {
loginLogoMaxWidth: configCache?.loginLogoMaxWidth || '',
loginShowHeading: configCache?.loginShowHeading ?? true,
loginShowSubtitle: configCache?.loginShowSubtitle ?? true,
loginShowTotp: configCache?.loginShowTotp ?? true,
loginShowVersion: configCache?.loginShowVersion ?? true,
demoMode: configCache?.demoMode || false,
autoSsoEnabled: configCache?.autoSsoEnabled || false,
allowCustomJmapEndpoint: configCache?.allowCustomJmapEndpoint || false,
@@ -152,6 +156,8 @@ export function useConfig(): AppConfig {
loginLogoMaxWidth: configCache.loginLogoMaxWidth,
loginShowHeading: configCache.loginShowHeading,
loginShowSubtitle: configCache.loginShowSubtitle,
loginShowTotp: configCache.loginShowTotp,
loginShowVersion: configCache.loginShowVersion,
demoMode: configCache.demoMode,
autoSsoEnabled: configCache.autoSsoEnabled,
allowCustomJmapEndpoint: configCache.allowCustomJmapEndpoint,
@@ -192,6 +198,8 @@ export function useConfig(): AppConfig {
loginLogoMaxWidth: data.loginLogoMaxWidth,
loginShowHeading: data.loginShowHeading,
loginShowSubtitle: data.loginShowSubtitle,
loginShowTotp: data.loginShowTotp,
loginShowVersion: data.loginShowVersion,
demoMode: data.demoMode,
autoSsoEnabled: data.autoSsoEnabled,
allowCustomJmapEndpoint: data.allowCustomJmapEndpoint,
+8
View File
@@ -178,6 +178,14 @@ export const CONFIG_ENV_MAP: Record<string, { envVar: string; fileEnvVar?: strin
loginLogoMaxWidth: { envVar: 'LOGIN_LOGO_MAX_WIDTH', type: 'string', defaultValue: '' },
loginShowHeading: { envVar: 'LOGIN_SHOW_HEADING', type: 'boolean', defaultValue: true },
loginShowSubtitle: { envVar: 'LOGIN_SHOW_SUBTITLE', type: 'boolean', defaultValue: true },
// Hide the manual "I have a 2FA code" toggle on the login form. Deployments
// that delegate auth to an external directory (LDAP/OIDC) where 2FA lives in
// the IdP have no server-side TOTP, so the toggle only leads to a failed
// login. Server-required TOTP (totp_required) still shows regardless.
loginShowTotp: { envVar: 'LOGIN_SHOW_TOTP', type: 'boolean', defaultValue: true },
// Show the build version in the login footer. Off keeps the exact version
// from being disclosed to unauthenticated visitors.
loginShowVersion: { envVar: 'LOGIN_SHOW_VERSION', type: 'boolean', defaultValue: true },
oauthEnabled: { envVar: 'OAUTH_ENABLED', type: 'boolean', defaultValue: false },
oauthOnly: { envVar: 'OAUTH_ONLY', type: 'boolean', defaultValue: false },
oauthClientId: { envVar: 'OAUTH_CLIENT_ID', type: 'string', defaultValue: '' },