diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx index ef7e4f6..12c4479 100644 --- a/client/src/pages/Login.jsx +++ b/client/src/pages/Login.jsx @@ -1,8 +1,9 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Navigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline'; import toast from 'react-hot-toast'; +import api from '../services/api'; const Login = () => { const [credentials, setCredentials] = useState({ @@ -10,8 +11,54 @@ const Login = () => { password: '' }); const [showPassword, setShowPassword] = useState(false); + const [tenantConfig, setTenantConfig] = useState(null); + const [configLoading, setConfigLoading] = useState(true); const { login, loading, isAuthenticated } = useAuth(); + // Fetch tenant configuration on mount + useEffect(() => { + const fetchTenantConfig = async () => { + try { + const response = await api.get('/auth/config'); + setTenantConfig(response.data.data); + } catch (error) { + console.error('Failed to fetch tenant config:', error); + toast.error('Failed to load authentication configuration'); + } finally { + setConfigLoading(false); + } + }; + + fetchTenantConfig(); + }, []); + + if (isAuthenticated) { + return ; + } + + // Show loading while fetching config + if (configLoading) { + return ( +
+
+
+

Loading...

+
+
+ ); + } + + // Handle different auth providers + const handleSSO = (provider) => { + if (provider === 'saml' && tenantConfig?.saml?.login_url) { + window.location.href = tenantConfig.saml.login_url; + } else if (provider === 'oauth' && tenantConfig?.oauth?.login_url) { + window.location.href = tenantConfig.oauth.login_url; + } else { + toast.error(`${provider.toUpperCase()} authentication not configured`); + } + }; + if (isAuthenticated) { return ; } @@ -50,74 +97,127 @@ const Login = () => {

- Drone Detection System + {tenantConfig?.tenant_name || 'Drone Detection System'}

Sign in to your account

+ {tenantConfig?.auth_provider && ( +

+ Authentication: {tenantConfig.auth_provider.toUpperCase()} +

+ )} -
-
-
- - + {/* Local/LDAP Authentication Form */} + {(tenantConfig?.auth_provider === 'local' || tenantConfig?.auth_provider === 'ldap') && ( + +
+
+ + +
+
+ + + +
-
- - + +
-
+ + )} -
+ {/* SAML Authentication */} + {tenantConfig?.auth_provider === 'saml' && ( +
+ )} + {/* OAuth Authentication */} + {tenantConfig?.auth_provider === 'oauth' && ( +
+ +
+ )} + + {/* Registration link for local auth if enabled */} + {tenantConfig?.auth_provider === 'local' && tenantConfig?.local?.allow_registration && ( +
+

+ Don't have an account?{' '} + + Sign up + +

+
+ )} + + {/* Demo credentials for local/ldap auth */} + {(tenantConfig?.auth_provider === 'local' || tenantConfig?.auth_provider === 'ldap') && (

Demo credentials:
@@ -125,7 +225,16 @@ const Login = () => { Password: admin123

- + )} + + {/* Error message if auth provider not configured */} + {!tenantConfig?.auth_provider && ( +
+

+ Authentication not configured for this tenant. Please contact your administrator. +

+
+ )}
); diff --git a/server/routes/auth.js b/server/routes/auth.js index 973256c..cb26cbb 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -84,6 +84,73 @@ router.get('/config/:tenantId', async (req, res) => { } }); +/** + * GET /auth/config + * Get authentication configuration for current tenant (auto-detected) + */ +router.get('/config', async (req, res) => { + try { + // Auto-determine tenant from request + const tenantId = await multiAuth.determineTenant(req); + if (!tenantId) { + return res.status(400).json({ + success: false, + message: 'Unable to determine tenant from request' + }); + } + + const tenant = await Tenant.findOne({ where: { slug: tenantId } }); + if (!tenant) { + return res.status(404).json({ + success: false, + message: 'Tenant not found' + }); + } + + // Return public auth configuration (no secrets) + const publicConfig = { + tenant_id: tenant.slug, + tenant_name: tenant.name, + auth_provider: tenant.auth_provider, + branding: tenant.branding + }; + + // Add provider-specific configuration + if (tenant.auth_provider === 'local') { + const authConfig = JSON.parse(tenant.auth_config || '{}'); + publicConfig.local = { + allow_registration: authConfig.allow_registration || false, + require_email_verification: authConfig.require_email_verification || false + }; + } else if (tenant.auth_provider === 'saml') { + publicConfig.saml = { + login_url: `/auth/saml/${tenantId}/login`, + metadata_url: `/auth/saml/${tenantId}/metadata` + }; + } else if (tenant.auth_provider === 'oauth') { + publicConfig.oauth = { + login_url: `/auth/oauth/${tenantId}/login` + }; + } else if (tenant.auth_provider === 'ldap') { + publicConfig.ldap = { + // LDAP uses same form as local but with different backend + }; + } + + res.json({ + success: true, + data: publicConfig + }); + + } catch (error) { + console.error('Error fetching auth config:', error); + res.status(500).json({ + success: false, + message: 'Failed to fetch authentication configuration' + }); + } +}); + /** * POST /auth/login * Universal login endpoint that routes to appropriate provider @@ -129,6 +196,39 @@ router.post('/login', async (req, res, next) => { } }); +/** + * POST /auth/local + * Local authentication endpoint with tenant isolation + */ +router.post('/local', async (req, res, next) => { + try { + // Determine tenant + const tenantId = await multiAuth.determineTenant(req); + const authConfig = await multiAuth.getTenantAuthConfig(tenantId); + + // Verify tenant supports local authentication + if (authConfig.type !== 'local') { + return res.status(400).json({ + success: false, + message: `This tenant uses ${authConfig.type} authentication. Please use the appropriate login method.`, + auth_provider: authConfig.type + }); + } + + req.tenant = { id: tenantId, authConfig }; + + // Call tenant-aware local login + return require('../routes/user').loginLocal(req, res, next); + + } catch (error) { + console.error('Local login error:', error); + res.status(500).json({ + success: false, + message: 'Local login failed' + }); + } +}); + /** * SAML Authentication Routes */