feat: add OAuth2/OIDC with PKCE for SSO login

Add opt-in SSO authentication alongside Basic Auth. OAuth endpoints are
auto-discovered via .well-known, with support for external IdPs
(Keycloak, Authentik) via configurable OAUTH_ISSUER_URL. Sessions
persist through httpOnly refresh token cookies with automatic renewal.
This commit is contained in:
Matthieu MALVACHE
2026-02-25 23:41:37 +01:00
committed by Matthieu MALVACHE
parent 110dd98ad4
commit ec06b0c494
20 changed files with 857 additions and 92 deletions
+19 -5
View File
@@ -2,18 +2,23 @@
import { useState, useEffect } from 'react';
interface AppConfig {
interface ConfigData {
appName: string;
jmapServerUrl: string;
oauthEnabled: boolean;
oauthClientId: string;
oauthIssuerUrl: string;
}
interface AppConfig extends ConfigData {
isLoading: boolean;
error: string | null;
}
// Cache the config to avoid multiple fetches
let configCache: { appName: string; jmapServerUrl: string } | null = null;
let configPromise: Promise<{ appName: string; jmapServerUrl: string }> | null = null;
let configCache: ConfigData | null = null;
let configPromise: Promise<ConfigData> | null = null;
async function fetchConfig(): Promise<{ appName: string; jmapServerUrl: string }> {
async function fetchConfig(): Promise<ConfigData> {
// Return cached config if available
if (configCache) {
return configCache;
@@ -55,6 +60,9 @@ export function useConfig(): AppConfig {
const [config, setConfig] = useState<AppConfig>({
appName: configCache?.appName || 'Webmail',
jmapServerUrl: configCache?.jmapServerUrl || '',
oauthEnabled: configCache?.oauthEnabled || false,
oauthClientId: configCache?.oauthClientId || '',
oauthIssuerUrl: configCache?.oauthIssuerUrl || '',
isLoading: !configCache,
error: null,
});
@@ -65,6 +73,9 @@ export function useConfig(): AppConfig {
setConfig({
appName: configCache.appName,
jmapServerUrl: configCache.jmapServerUrl,
oauthEnabled: configCache.oauthEnabled,
oauthClientId: configCache.oauthClientId,
oauthIssuerUrl: configCache.oauthIssuerUrl,
isLoading: false,
error: null,
});
@@ -76,6 +87,9 @@ export function useConfig(): AppConfig {
setConfig({
appName: data.appName,
jmapServerUrl: data.jmapServerUrl,
oauthEnabled: data.oauthEnabled,
oauthClientId: data.oauthClientId,
oauthIssuerUrl: data.oauthIssuerUrl,
isLoading: false,
error: null,
});