From 8131136bfb6494557eeef2f68d1c3dfeba9d9147 Mon Sep 17 00:00:00 2001 From: alphaeusmote <41258468-alphaeusmote@users.noreply.replit.com> Date: Thu, 10 Apr 2025 03:15:38 +0000 Subject: [PATCH] Add LDAP and improved authentication configuration. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/75b5c5a0-cf3f-429b-bffd-c6bd4e6c914d.jpg --- client/src/pages/auth-page.tsx | 292 +++++++++++++++++++++++++++------ server/auth.ts | 7 +- 2 files changed, 246 insertions(+), 53 deletions(-) diff --git a/client/src/pages/auth-page.tsx b/client/src/pages/auth-page.tsx index ee11c45..62923e4 100644 --- a/client/src/pages/auth-page.tsx +++ b/client/src/pages/auth-page.tsx @@ -3,7 +3,7 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; import { useAuth } from "@/hooks/use-auth"; import { useOrganization } from "@/context/organization-context"; -import { Redirect } from "wouter"; +import { Redirect, useLocation } from "wouter"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; @@ -11,8 +11,12 @@ import { Input } from "@/components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ThemeToggle } from "@/components/shared/theme-toggle"; import { useTheme } from "@/hooks/use-theme"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Separator } from "@/components/ui/separator"; import { Loader2 } from "lucide-react"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { useQuery } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; const loginSchema = z.object({ username: z.string().min(3, "Username must be at least 3 characters"), @@ -27,10 +31,36 @@ const registerSchema = z.object({ organizationId: z.string().optional(), }); +// Define LDAP login schema +const ldapSchema = z.object({ + username: z.string().min(1, "Username is required"), + password: z.string().min(1, "Password is required"), +}); + +// Type for Auth config from API +interface AuthConfig { + localAuthEnabled: boolean; + registrationEnabled: boolean; + ldapEnabled: boolean; + oidcEnabled: boolean; + oidcButtonText: string; +} + export default function AuthPage() { const { user, loginMutation, registerMutation } = useAuth(); const { theme } = useTheme(); const { organizations, isLoading: orgsLoading } = useOrganization(); + const [location, setLocation] = useLocation(); + + // Fetch authentication configuration + const { data: authConfig, isLoading: configLoading } = useQuery({ + queryKey: ['/api/auth/config'], + staleTime: 1000 * 60 * 5, // 5 minutes + }); + + // State for managing LDAP login + const [ldapLoginPending, setLdapLoginPending] = useState(false); + const [authError, setAuthError] = useState(null); const loginForm = useForm>({ resolver: zodResolver(loginSchema), @@ -40,6 +70,14 @@ export default function AuthPage() { }, }); + const ldapForm = useForm>({ + resolver: zodResolver(ldapSchema), + defaultValues: { + username: "", + password: "", + }, + }); + const registerForm = useForm>({ resolver: zodResolver(registerSchema), defaultValues: { @@ -52,11 +90,53 @@ export default function AuthPage() { }); const onLoginSubmit = (values: z.infer) => { - loginMutation.mutate(values); + setAuthError(null); + loginMutation.mutate(values, { + onError: (error: any) => { + setAuthError(error.message || "Login failed. Please check your credentials."); + } + }); + }; + + const onLdapSubmit = async (values: z.infer) => { + try { + setLdapLoginPending(true); + setAuthError(null); + + const response = await fetch('/api/auth/ldap', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(values), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'LDAP authentication failed'); + } + + // Force refresh user data + window.location.href = '/'; + } catch (error: any) { + setAuthError(error.message || "LDAP login failed. Please check your credentials."); + } finally { + setLdapLoginPending(false); + } }; const onRegisterSubmit = (values: z.infer) => { - registerMutation.mutate(values); + setAuthError(null); + registerMutation.mutate(values, { + onError: (error: any) => { + setAuthError(error.message || "Registration failed. Please try again."); + } + }); + }; + + // Function to handle OIDC login + const handleOidcLogin = () => { + window.location.href = '/api/auth/oidc'; }; // Redirect if already logged in @@ -91,61 +171,173 @@ export default function AuthPage() { Welcome - Sign in to your account or create a new one + {configLoading + ? "Loading..." + : authConfig?.registrationEnabled + ? "Sign in to your account or create a new one" + : "Sign in to your account" + } - + Login - Register + {(!configLoading && authConfig?.registrationEnabled) && ( + Register + )} -
- - ( - - Username or Email - - - - - - )} - /> - ( - - Password - - - - - - )} - /> - - - + {authError && ( + + Authentication Error + {authError} + + )} + + {configLoading ? ( +
+ +
+ ) : ( + <> + {/* Local Authentication */} + {authConfig?.localAuthEnabled && ( +
+ + ( + + Username or Email + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + + + + )} + + {/* LDAP Authentication */} + {authConfig?.ldapEnabled && ( + <> + {authConfig?.localAuthEnabled && ( +
+
+

Or login with LDAP

+
+
+ )} + +
+ + ( + + LDAP Username + + + + + + )} + /> + ( + + LDAP Password + + + + + + )} + /> + + + + + )} + + {/* OIDC Authentication */} + {authConfig?.oidcEnabled && ( + <> + {(authConfig?.localAuthEnabled || authConfig?.ldapEnabled) && ( +
+
+

Or

+
+
+ )} + + + + )} + + {!authConfig?.localAuthEnabled && !authConfig?.ldapEnabled && !authConfig?.oidcEnabled && ( + + Authentication Error + No authentication methods are enabled. Please contact your administrator. + + )} + + )}
diff --git a/server/auth.ts b/server/auth.ts index bf74460..4d1df15 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -12,7 +12,7 @@ import { z } from "zod"; import 'dotenv/config'; // Authentication configuration from environment variables -const CONFIG = { +export const CONFIG = { // Local auth configuration LOCAL_AUTH_ENABLED: process.env.LOCAL_AUTH_ENABLED !== 'false', // Enabled by default DISABLE_REGISTRATION: process.env.DISABLE_REGISTRATION === 'true', // Disabled by default @@ -36,7 +36,8 @@ const CONFIG = { OIDC_CLIENT_SECRET: process.env.OIDC_CLIENT_SECRET || '', OIDC_CALLBACK_URL: process.env.OIDC_CALLBACK_URL || 'http://localhost:5000/api/auth/oidc/callback', OIDC_SCOPE: process.env.OIDC_SCOPE || 'openid profile email', - OIDC_AUTO_REGISTER: process.env.OIDC_AUTO_REGISTER === 'true' + OIDC_AUTO_REGISTER: process.env.OIDC_AUTO_REGISTER === 'true', + OIDC_BUTTON_TEXT: process.env.OIDC_BUTTON_TEXT || 'Sign in with OpenID Connect' }; declare global { @@ -408,7 +409,7 @@ export function setupAuth(app: Express) { registrationEnabled: !CONFIG.DISABLE_REGISTRATION, ldapEnabled: CONFIG.LDAP_ENABLED, oidcEnabled: CONFIG.OIDC_ENABLED, - oidcButtonText: process.env.OIDC_BUTTON_TEXT || "Sign in with OpenID Connect" + oidcButtonText: CONFIG.OIDC_BUTTON_TEXT }); });