feat: SSO & LDAP authentication for Team Pro (#209)

* feat: SSO & LDAP authentication for Team Pro

Add SSO integration allowing Team Pro users to authenticate via LDAP/Active Directory, Google, GitHub, and Okta identity providers. SSO works alongside password authentication with auto-provisioning and role mapping.

- LDAP bind+search authentication with group-based role mapping
- OIDC/OAuth2 flows with PKCE and CSRF protection for Google, GitHub, Okta
- Auto-provisioning: first SSO login creates a Sencho account automatically
- Role mapping via LDAP group membership or OIDC JWT claims
- SSO settings UI in Settings → SSO with per-provider config and test connection
- SSO login buttons on login page with LDAP toggle
- Environment variable seeding for infrastructure-as-code workflows
- Secrets encrypted at rest via CryptoService (AES-256-GCM)
- Seat limit enforcement during auto-provisioning
- Full documentation: feature docs, quickstart guides, env var reference

* fix: resolve ESLint errors in SSO feature

- Remove unnecessary escape characters in regex character classes
- Remove unused `issuer` variable from OIDC callback handler
- Fix setState-in-effect lint error in Login.tsx by using useState initializer
- Suppress set-state-in-effect for SSOSection fetch pattern (matches existing codebase convention)
This commit is contained in:
Anso
2026-03-28 03:30:01 -04:00
committed by GitHub
parent b429097fa2
commit bd4008f509
20 changed files with 2247 additions and 14 deletions
+25
View File
@@ -14,6 +14,7 @@ interface AuthContextType {
user: UserInfo | null;
isAdmin: boolean;
login: (username: string, password: string) => Promise<{ success: boolean; error?: string }>;
ssoLdapLogin: (username: string, password: string) => Promise<{ success: boolean; error?: string }>;
logout: () => Promise<void>;
completeSetup: () => void;
checkAuth: () => Promise<void>;
@@ -91,6 +92,29 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
};
const ssoLdapLogin = async (username: string, password: string): Promise<{ success: boolean; error?: string }> => {
try {
const response = await fetch('/api/auth/sso/ldap', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ username, password }),
});
const data = await response.json();
if (response.ok && data.success) {
setAppStatus('authenticated');
await checkAuth();
return { success: true };
} else {
return { success: false, error: data.error || 'LDAP login failed' };
}
} catch {
return { success: false, error: 'Network error. Please try again.' };
}
};
const logout = async () => {
try {
await fetch('/api/auth/logout', {
@@ -118,6 +142,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
user,
isAdmin: user?.role === 'admin',
login,
ssoLdapLogin,
logout,
completeSetup,
checkAuth