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:
@@ -133,7 +133,7 @@ export default function LoginPage() {
|
|||||||
const isMobileHandoff = Boolean(mobileRedirectUri);
|
const isMobileHandoff = Boolean(mobileRedirectUri);
|
||||||
const { login, loginDemo, isLoading, error, clearError, isAuthenticated } = useAuthStore();
|
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 { 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);
|
const resolvedTheme = useThemeStore((s) => s.resolvedTheme);
|
||||||
|
|
||||||
// Login logo sizing: when a max height/width is configured, drop the fixed
|
// Login logo sizing: when a max height/width is configured, drop the fixed
|
||||||
@@ -822,7 +822,7 @@ export default function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<VersionBadge />
|
{loginShowVersion && <VersionBadge />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1154,8 +1154,12 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
</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 ? (
|
{!showTotpField ? (
|
||||||
|
loginShowTotp ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -1167,6 +1171,7 @@ export default function LoginPage() {
|
|||||||
<Shield className="w-3.5 h-3.5" />
|
<Shield className="w-3.5 h-3.5" />
|
||||||
{t("totp_toggle")}
|
{t("totp_toggle")}
|
||||||
</button>
|
</button>
|
||||||
|
) : null
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label htmlFor="totp" className="block text-sm font-medium text-foreground">
|
<label htmlFor="totp" className="block text-sm font-medium text-foreground">
|
||||||
@@ -1361,7 +1366,7 @@ export default function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<VersionBadge />
|
{loginShowVersion && <VersionBadge />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ export async function GET(request: NextRequest) {
|
|||||||
loginLogoMaxWidth: configManager.get<string>('loginLogoMaxWidth', ''),
|
loginLogoMaxWidth: configManager.get<string>('loginLogoMaxWidth', ''),
|
||||||
loginShowHeading: configManager.get<boolean>('loginShowHeading', true),
|
loginShowHeading: configManager.get<boolean>('loginShowHeading', true),
|
||||||
loginShowSubtitle: configManager.get<boolean>('loginShowSubtitle', 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),
|
demoMode: configManager.get<boolean>('demoMode', false),
|
||||||
allowCustomJmapEndpoint: configManager.get<boolean>('allowCustomJmapEndpoint', false),
|
allowCustomJmapEndpoint: configManager.get<boolean>('allowCustomJmapEndpoint', false),
|
||||||
jmapServers: redactJmapServers(parseJmapServers(configManager.get<unknown>('jmapServers', []))),
|
jmapServers: redactJmapServers(parseJmapServers(configManager.get<unknown>('jmapServers', []))),
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ interface ConfigData {
|
|||||||
loginLogoMaxWidth: string;
|
loginLogoMaxWidth: string;
|
||||||
loginShowHeading: boolean;
|
loginShowHeading: boolean;
|
||||||
loginShowSubtitle: boolean;
|
loginShowSubtitle: boolean;
|
||||||
|
loginShowTotp: boolean;
|
||||||
|
loginShowVersion: boolean;
|
||||||
demoMode: boolean;
|
demoMode: boolean;
|
||||||
autoSsoEnabled: boolean;
|
autoSsoEnabled: boolean;
|
||||||
allowCustomJmapEndpoint: boolean;
|
allowCustomJmapEndpoint: boolean;
|
||||||
@@ -113,6 +115,8 @@ export function useConfig(): AppConfig {
|
|||||||
loginLogoMaxWidth: configCache?.loginLogoMaxWidth || '',
|
loginLogoMaxWidth: configCache?.loginLogoMaxWidth || '',
|
||||||
loginShowHeading: configCache?.loginShowHeading ?? true,
|
loginShowHeading: configCache?.loginShowHeading ?? true,
|
||||||
loginShowSubtitle: configCache?.loginShowSubtitle ?? true,
|
loginShowSubtitle: configCache?.loginShowSubtitle ?? true,
|
||||||
|
loginShowTotp: configCache?.loginShowTotp ?? true,
|
||||||
|
loginShowVersion: configCache?.loginShowVersion ?? true,
|
||||||
demoMode: configCache?.demoMode || false,
|
demoMode: configCache?.demoMode || false,
|
||||||
autoSsoEnabled: configCache?.autoSsoEnabled || false,
|
autoSsoEnabled: configCache?.autoSsoEnabled || false,
|
||||||
allowCustomJmapEndpoint: configCache?.allowCustomJmapEndpoint || false,
|
allowCustomJmapEndpoint: configCache?.allowCustomJmapEndpoint || false,
|
||||||
@@ -152,6 +156,8 @@ export function useConfig(): AppConfig {
|
|||||||
loginLogoMaxWidth: configCache.loginLogoMaxWidth,
|
loginLogoMaxWidth: configCache.loginLogoMaxWidth,
|
||||||
loginShowHeading: configCache.loginShowHeading,
|
loginShowHeading: configCache.loginShowHeading,
|
||||||
loginShowSubtitle: configCache.loginShowSubtitle,
|
loginShowSubtitle: configCache.loginShowSubtitle,
|
||||||
|
loginShowTotp: configCache.loginShowTotp,
|
||||||
|
loginShowVersion: configCache.loginShowVersion,
|
||||||
demoMode: configCache.demoMode,
|
demoMode: configCache.demoMode,
|
||||||
autoSsoEnabled: configCache.autoSsoEnabled,
|
autoSsoEnabled: configCache.autoSsoEnabled,
|
||||||
allowCustomJmapEndpoint: configCache.allowCustomJmapEndpoint,
|
allowCustomJmapEndpoint: configCache.allowCustomJmapEndpoint,
|
||||||
@@ -192,6 +198,8 @@ export function useConfig(): AppConfig {
|
|||||||
loginLogoMaxWidth: data.loginLogoMaxWidth,
|
loginLogoMaxWidth: data.loginLogoMaxWidth,
|
||||||
loginShowHeading: data.loginShowHeading,
|
loginShowHeading: data.loginShowHeading,
|
||||||
loginShowSubtitle: data.loginShowSubtitle,
|
loginShowSubtitle: data.loginShowSubtitle,
|
||||||
|
loginShowTotp: data.loginShowTotp,
|
||||||
|
loginShowVersion: data.loginShowVersion,
|
||||||
demoMode: data.demoMode,
|
demoMode: data.demoMode,
|
||||||
autoSsoEnabled: data.autoSsoEnabled,
|
autoSsoEnabled: data.autoSsoEnabled,
|
||||||
allowCustomJmapEndpoint: data.allowCustomJmapEndpoint,
|
allowCustomJmapEndpoint: data.allowCustomJmapEndpoint,
|
||||||
|
|||||||
@@ -178,6 +178,14 @@ export const CONFIG_ENV_MAP: Record<string, { envVar: string; fileEnvVar?: strin
|
|||||||
loginLogoMaxWidth: { envVar: 'LOGIN_LOGO_MAX_WIDTH', type: 'string', defaultValue: '' },
|
loginLogoMaxWidth: { envVar: 'LOGIN_LOGO_MAX_WIDTH', type: 'string', defaultValue: '' },
|
||||||
loginShowHeading: { envVar: 'LOGIN_SHOW_HEADING', type: 'boolean', defaultValue: true },
|
loginShowHeading: { envVar: 'LOGIN_SHOW_HEADING', type: 'boolean', defaultValue: true },
|
||||||
loginShowSubtitle: { envVar: 'LOGIN_SHOW_SUBTITLE', 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 },
|
oauthEnabled: { envVar: 'OAUTH_ENABLED', type: 'boolean', defaultValue: false },
|
||||||
oauthOnly: { envVar: 'OAUTH_ONLY', type: 'boolean', defaultValue: false },
|
oauthOnly: { envVar: 'OAUTH_ONLY', type: 'boolean', defaultValue: false },
|
||||||
oauthClientId: { envVar: 'OAUTH_CLIENT_ID', type: 'string', defaultValue: '' },
|
oauthClientId: { envVar: 'OAUTH_CLIENT_ID', type: 'string', defaultValue: '' },
|
||||||
|
|||||||
Reference in New Issue
Block a user