diff --git a/.env.example b/.env.example index 34e78622..668a2442 100644 --- a/.env.example +++ b/.env.example @@ -27,6 +27,10 @@ JMAP_SERVER_URL=https://your-jmap-server.com # Set to "true" to use OAuth instead of basic JMAP authentication # OAUTH_ENABLED=true +# Set to "true" to only allow OAuth login (hides username/password form) +# Requires OAUTH_ENABLED=true +# OAUTH_ONLY=true + # OAuth client ID registered with your identity provider # OAUTH_CLIENT_ID=your-client-id diff --git a/app/[locale]/login/page.tsx b/app/[locale]/login/page.tsx index 7ffe5cf6..bd770b9e 100644 --- a/app/[locale]/login/page.tsx +++ b/app/[locale]/login/page.tsx @@ -29,7 +29,7 @@ export default function LoginPage() { const params = useParams(); const { login, isLoading, error, clearError, isAuthenticated } = useAuthStore(); const { theme, setTheme, initializeTheme } = useThemeStore(); - const { appName, jmapServerUrl: serverUrl, oauthEnabled, oauthClientId, oauthIssuerUrl, rememberMeEnabled, devMode, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, isLoading: configLoading, error: configError } = useConfig(); + const { appName, jmapServerUrl: serverUrl, oauthEnabled, oauthOnly, oauthClientId, oauthIssuerUrl, rememberMeEnabled, devMode, loginCompanyName, loginImprintUrl, loginPrivacyPolicyUrl, loginWebsiteUrl, isLoading: configLoading, error: configError } = useConfig(); const [formData, setFormData] = useState({ username: "", @@ -491,6 +491,41 @@ export default function LoginPage() { Dev mode — logging in as dev@localhost

+ ) : oauthOnly ? ( + /* OAuth-only mode: show SSO button only */ +
+ {oauthMetadata ? ( + + ) : oauthDiscoveryDone ? ( +
+ +

+ {t("error.oauth_discovery_failed")} +

+
+ ) : ( +
+ +
+ )} +
) : ( /* Login Form */
diff --git a/app/api/auth/session/route.ts b/app/api/auth/session/route.ts index cf33808f..92af2d3d 100644 --- a/app/api/auth/session/route.ts +++ b/app/api/auth/session/route.ts @@ -14,6 +14,10 @@ const COOKIE_OPTIONS = { export async function POST(request: NextRequest) { try { + if (process.env.OAUTH_ENABLED === 'true' && process.env.OAUTH_ONLY === 'true') { + return NextResponse.json({ error: 'Basic authentication is disabled' }, { status: 403 }); + } + const { serverUrl, username, password } = await request.json(); if (!serverUrl || !username || !password) { return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); diff --git a/app/api/config/route.ts b/app/api/config/route.ts index 239c4f2e..c5bde963 100644 --- a/app/api/config/route.ts +++ b/app/api/config/route.ts @@ -19,6 +19,7 @@ export async function GET() { appName: process.env.APP_NAME || process.env.NEXT_PUBLIC_APP_NAME || 'Webmail', jmapServerUrl: process.env.JMAP_SERVER_URL || process.env.NEXT_PUBLIC_JMAP_SERVER_URL || '', oauthEnabled: process.env.OAUTH_ENABLED === 'true', + oauthOnly: process.env.OAUTH_ENABLED === 'true' && process.env.OAUTH_ONLY === 'true', oauthClientId: process.env.OAUTH_CLIENT_ID || '', oauthIssuerUrl: process.env.OAUTH_ISSUER_URL || '', rememberMeEnabled: !!process.env.SESSION_SECRET, diff --git a/hooks/use-config.ts b/hooks/use-config.ts index bddb7935..31fc5394 100644 --- a/hooks/use-config.ts +++ b/hooks/use-config.ts @@ -6,6 +6,7 @@ interface ConfigData { appName: string; jmapServerUrl: string; oauthEnabled: boolean; + oauthOnly: boolean; oauthClientId: string; oauthIssuerUrl: string; rememberMeEnabled: boolean; @@ -69,6 +70,7 @@ export function useConfig(): AppConfig { appName: configCache?.appName || 'Webmail', jmapServerUrl: configCache?.jmapServerUrl || '', oauthEnabled: configCache?.oauthEnabled || false, + oauthOnly: configCache?.oauthOnly || false, oauthClientId: configCache?.oauthClientId || '', oauthIssuerUrl: configCache?.oauthIssuerUrl || '', rememberMeEnabled: configCache?.rememberMeEnabled || false, @@ -90,6 +92,7 @@ export function useConfig(): AppConfig { appName: configCache.appName, jmapServerUrl: configCache.jmapServerUrl, oauthEnabled: configCache.oauthEnabled, + oauthOnly: configCache.oauthOnly, oauthClientId: configCache.oauthClientId, oauthIssuerUrl: configCache.oauthIssuerUrl, rememberMeEnabled: configCache.rememberMeEnabled, @@ -112,6 +115,7 @@ export function useConfig(): AppConfig { appName: data.appName, jmapServerUrl: data.jmapServerUrl, oauthEnabled: data.oauthEnabled, + oauthOnly: data.oauthOnly, oauthClientId: data.oauthClientId, oauthIssuerUrl: data.oauthIssuerUrl, rememberMeEnabled: data.rememberMeEnabled,