feat(enterprise): add SAML 2.0 SSO with SP-initiated login

Implements SAML SSO using @node-saml/node-saml, gated behind
SAML_ENABLED env var and the saml_sso enterprise license feature.

- SAML env vars (entity ID, callback URL, IdP SSO URL, IdP cert,
  auto-create/auto-link users, default role, provider name,
  username/email attribute mapping) with validation in superRefine
- SAML plugin with three routes: metadata (GET), login (GET),
  and ACS callback (POST with form-urlencoded content type parser)
- Callback uses the shared external-auth resolver for user
  resolution (same pattern as OIDC: match/link/create/deny)
- Auth config endpoint exposes samlEnabled and samlProviderName
- Session loginMethod detection updated for SAML auth provider
- Frontend login page shows SAML SSO button when enabled
- i18n strings for SAML error messages across all 21 locales
This commit is contained in:
SnapOtter
2026-06-13 22:27:56 +08:00
parent 6920035f5a
commit 54132d1833
27 changed files with 383 additions and 13 deletions
+10
View File
@@ -14,6 +14,8 @@ interface AuthState {
analyticsConsentRemindAt: number | null;
oidcEnabled: boolean;
oidcProviderName: string | null;
samlEnabled: boolean;
samlProviderName: string | null;
loginMethod: string | null;
hasLocalPassword: boolean;
}
@@ -48,6 +50,8 @@ export function useAuth() {
analyticsConsentRemindAt: null,
oidcEnabled: false,
oidcProviderName: null,
samlEnabled: false,
samlProviderName: null,
loginMethod: null,
hasLocalPassword: false,
});
@@ -74,6 +78,8 @@ export function useAuth() {
analyticsConsentRemindAt: null,
oidcEnabled: false,
oidcProviderName: null,
samlEnabled: false,
samlProviderName: null,
loginMethod: null,
hasLocalPassword: false,
});
@@ -102,6 +108,8 @@ export function useAuth() {
analyticsConsentRemindAt: session.user?.analyticsConsentRemindAt ?? null,
oidcEnabled: config.oidcEnabled ?? false,
oidcProviderName: config.oidcProviderName ?? null,
samlEnabled: config.samlEnabled ?? false,
samlProviderName: config.samlProviderName ?? null,
loginMethod: session.user?.loginMethod ?? null,
hasLocalPassword: session.user?.hasLocalPassword ?? false,
});
@@ -120,6 +128,8 @@ export function useAuth() {
analyticsConsentRemindAt: null,
oidcEnabled: config.oidcEnabled ?? false,
oidcProviderName: config.oidcProviderName ?? null,
samlEnabled: config.samlEnabled ?? false,
samlProviderName: config.samlProviderName ?? null,
loginMethod: null,
hasLocalPassword: false,
});