From 7cd9d0b74529a1b0ae7708e11493fd0dd4bd1cad Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sun, 28 Sep 2025 01:07:07 -0400 Subject: [PATCH 01/26] Add Microsoft Entra ID SSO configuration UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement complete SSO configuration interface with two tabs - SSO Configuration tab: connection settings, user mapping, advanced options - Security Controls tab: session management, audit integration, access control - Follow VerifyWise design system with 15px bold headers and 34px buttons - Add Entra ID tab to Settings page with proper styling - Install google-auth-library dependency for server 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../EntraIdConfig/SecurityControlsTab.tsx | 220 +++++++++ .../EntraIdConfig/SsoConfigTab.tsx | 462 ++++++++++++++++++ .../SettingsPage/EntraIdConfig/index.tsx | 67 +++ .../presentation/pages/SettingsPage/index.tsx | 13 +- Servers/package-lock.json | 291 +++++++++++ Servers/package.json | 1 + 6 files changed, 1053 insertions(+), 1 deletion(-) create mode 100644 Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SecurityControlsTab.tsx create mode 100644 Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx create mode 100644 Clients/src/presentation/pages/SettingsPage/EntraIdConfig/index.tsx diff --git a/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SecurityControlsTab.tsx b/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SecurityControlsTab.tsx new file mode 100644 index 000000000..64404dd15 --- /dev/null +++ b/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SecurityControlsTab.tsx @@ -0,0 +1,220 @@ +import React, { useState } from "react"; +import { + Box, + Stack, + Typography, + useTheme, +} from "@mui/material"; +import Toggle from "../../../components/Toggle"; +import Alert from "../../../components/Alert"; +import Button from "../../../components/Button"; +import Select from "../../../components/Inputs/Select"; +import { tokenLifetimes } from "./SsoConfigTab"; + +// State interface for Security Configuration +interface SecurityConfig { + tokenLifetime: string; + forceReauthOnRoleChange: boolean; + singleSessionPerUser: boolean; + enableSsoAuditLogging: boolean; +} + +const SecurityControlsTab: React.FC = () => { + const [config, setConfig] = useState({ + tokenLifetime: "8 Hours", + forceReauthOnRoleChange: true, + singleSessionPerUser: false, + enableSsoAuditLogging: true, + }); + + const [isSaving, setIsSaving] = useState(false); + const theme = useTheme(); + + const handleToggleChange = (field: keyof SecurityConfig) => (checked: boolean) => { + setConfig(prev => ({ ...prev, [field]: checked })); + }; + + const handleSelectChange = (field: keyof SecurityConfig) => ( + event: React.ChangeEvent + ) => { + setConfig(prev => ({ ...prev, [field]: event.target.value })); + }; + + const handleSave = async () => { + setIsSaving(true); + try { + // Simulate API call for saving security configuration + await new Promise(resolve => setTimeout(resolve, 1000)); + // Success handling would go here + } catch (error) { + // Error handling would go here + } finally { + setIsSaving(false); + } + }; + + // Card-like container styles + const cardStyles = { + backgroundColor: theme.palette.background.paper, + borderRadius: 2, + border: `1px solid ${theme.palette.divider}`, + p: 3, + }; + + return ( + + + + {/* Session Management Card */} + + + Session Management + + + + + option._id} + sx={{ width: '100%' }} + /> + + + + + + + + {connectionResult && ( + + )} + + + + + + {/* User Mapping Card */} + + + User Mapping + + + + + + + + + + + + + + + + + Comma-separated list of Azure AD group names that should have admin access + + + + + + + Auto-create users + + {config.autoCreateUsers && ( + + )} + + + + + { - + - Force re-authentication on role change + Force re-authentication on role change + + - Require users to log in again when their role or permissions change + - - Require users to log in again when their role or permissions change - - + - + - Single session per user + Single session per user + + - Limit users to one active session at a time across all devices + - - Limit users to one active session at a time across all devices - - + { {/* Audit Integration Card */} - Audit Integration + Audit integration - - + + - Enable SSO audit logging + Enable SSO audit logging for security monitoring and compliance - - Log all SSO authentication events for security monitoring and compliance - - + { - {/* Access Control Information */} - - - Access Control Information - - - - - - - - Key Access Control Features: - - -
  • Conditional Access policies through Azure AD
  • -
  • Multi-factor authentication enforcement
  • -
  • Device compliance requirements
  • -
  • IP address restrictions
  • -
  • Risk-based authentication policies
  • -
    -
    - - - - Recommended Security Setup: - - -
  • Enable "User assignment required" in Azure AD app registration
  • -
  • Configure Conditional Access policies for VerifyWise
  • -
  • Require MFA for all VerifyWise access
  • -
  • Set up device compliance policies
  • -
  • Monitor sign-in logs and audit activities
  • -
    -
    -
    -
    - - - {/* Save/Cancel Buttons */} diff --git a/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx b/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx index 531389afb..d9dbb7004 100644 --- a/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx +++ b/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx @@ -37,8 +37,7 @@ interface ValidationErrors { // Cloud environment options const cloudEnvironments = [ { _id: "AzurePublic", name: "Azure Public Cloud" }, - { _id: "AzureGovernment", name: "Azure Government" }, - { _id: "AzureChina", name: "Azure China" } + { _id: "AzureGovernment", name: "Azure Government" } ]; // Default role options @@ -62,13 +61,13 @@ const SsoConfigTab: React.FC = () => { clientId: "", clientSecret: "", cloudEnvironment: "AzurePublic", - emailClaim: "email", - nameClaim: "name", + emailClaim: "", + nameClaim: "", adminGroups: "", autoCreateUsers: false, defaultRole: "Reviewer", postLogoutRedirectUri: "", - oauthScopes: "openid profile email groups", + oauthScopes: "", customClaims: "", }); @@ -198,12 +197,13 @@ const SsoConfigTab: React.FC = () => { } }; - // Card-like container styles + // Card-like container styles - matching AI Trust Center spacing const cardStyles = { backgroundColor: theme.palette.background.paper, - borderRadius: 2, - border: `1px solid ${theme.palette.divider}`, - p: 3, + borderRadius: theme.shape.borderRadius, + border: `1.5px solid ${theme.palette.border?.light || theme.palette.divider}`, + padding: theme.spacing(5, 6), // 40px top/bottom, 48px left/right - same as AI Trust Center + boxShadow: 'none', }; return ( @@ -223,14 +223,14 @@ const SsoConfigTab: React.FC = () => { {/* Connection Settings Card */} - Connection Settings + Connection settings - + { { - + { { {/* Advanced Configuration */} - Advanced Configuration + Advanced configuration - + + { - - - - JSON format mapping of custom claims from Azure AD to VerifyWise user attributes - - - - + + + + + + + JSON format mapping of custom claims from Azure AD to VerifyWise user attributes + + @@ -452,7 +454,7 @@ const SsoConfigTab: React.FC = () => { disabled={isSaving || Object.keys(errors).length > 0} sx={{ height: '34px', fontSize: 13, fontWeight: 400, textTransform: 'none' }} > - {isSaving ? "Saving..." : "Save Configuration"} + {isSaving ? "Saving..." : "Save configuration"} diff --git a/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/index.tsx b/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/index.tsx index 03040c9e7..51ac9f5d7 100644 --- a/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/index.tsx +++ b/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/index.tsx @@ -47,8 +47,8 @@ const EntraIdConfig: React.FC = () => { "& .MuiTabs-flexContainer": { columnGap: "34px" }, }} > - - + +
    From 9c277804d58a1162a92b29d2e72841ecd8852502 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sun, 28 Sep 2025 10:58:39 -0400 Subject: [PATCH 03/26] Fix TypeScript compilation errors - Fix object indexing type safety in CustomizableButton - Update Select onChange event handlers to use proper types - Remove unsupported onBlur props from Field components - Remove unused handleFieldBlur function --- .../components/Button/CustomizableButton/index.tsx | 6 +++--- .../SettingsPage/EntraIdConfig/SecurityControlsTab.tsx | 2 +- .../pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx | 10 +--------- Servers/config/constants.js | 2 +- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Clients/src/presentation/components/Button/CustomizableButton/index.tsx b/Clients/src/presentation/components/Button/CustomizableButton/index.tsx index a62fe0255..4e3dd037e 100644 --- a/Clients/src/presentation/components/Button/CustomizableButton/index.tsx +++ b/Clients/src/presentation/components/Button/CustomizableButton/index.tsx @@ -179,12 +179,12 @@ const CustomizableButton = memo( ); // Filter out any remaining problematic props that shouldn't reach DOM - const filteredProps = Object.keys(rest).reduce((acc, key) => { + const filteredProps = Object.keys(rest).reduce((acc: Record, key) => { if (!['textColor', 'indicator', 'selectionFollowsFocus'].includes(key)) { - acc[key] = rest[key]; + acc[key] = (rest as any)[key]; } return acc; - }, {} as any); + }, {}); return ( + + + )} + + {/* Step 2: Authentication Options */} + {currentStep === 'auth-options' && userOrgInfo && ( + + {/* Email Confirmation */} + + + + ✓ Email: + + + {values.email} + + + Change + + + + {userOrgInfo.hasOrganization && ( + + Organization: {userOrgInfo.organization?.name} + + )} + + + {/* SSO Option */} + {userOrgInfo.ssoAvailable && userOrgInfo.authMethodPolicy !== 'password_only' && ( + + )} + + {/* Divider */} + {userOrgInfo.ssoAvailable && userOrgInfo.authMethodPolicy === 'both' && ( + + or - - - {isMultiTenant && ( + )} + + {/* Password Option */} + {userOrgInfo.authMethodPolicy !== 'sso_only' && ( + + )} + + {/* Policy message for SSO-only */} + {userOrgInfo.authMethodPolicy === 'sso_only' && !userOrgInfo.ssoAvailable && ( + + Your organization requires SSO authentication, but SSO is not configured. Please contact your administrator. + + )} + + {/* New User Message */} + {!userOrgInfo.userExists && ( + + New user? You'll be able to create an account after entering your password. + + )} + + )} + + {/* Step 3: Password Input */} + {currentStep === 'password' && ( +
    + + {/* Email Confirmation */} + + + + ✓ Email: + + + {values.email} + + + Change + + + + {userOrgInfo?.hasOrganization && ( + + Organization: {userOrgInfo.organization?.name} + + )} + + + + - - Don't have an account yet? - + { + setValues({ ...values, rememberMe: e.target.checked }); + }} + size="small" + /> navigate("/register")} + onClick={() => { + navigate("/forgot-password", { + state: { email: values.email }, + }); + }} > - Register here + Forgot password - )} + + + + {/* Back to auth options */} + {userOrgInfo?.ssoAvailable && userOrgInfo?.authMethodPolicy === 'both' && ( + setCurrentStep('auth-options')} + sx={{ textDecoration: 'none', color: singleTheme.textColors.theme }} + > + ← Back to login options + + )} + +
    + )} + + {/* Registration Link */} + {isMultiTenant && currentStep === 'email' && ( + + + Don't have an account yet? + + navigate("/register")} + > + Register here + - - + )} + ); }; -export default Login; +export default Login; \ No newline at end of file diff --git a/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx b/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx index adb26e67c..ec4c17e6d 100644 --- a/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx +++ b/Clients/src/presentation/pages/SettingsPage/EntraIdConfig/SsoConfigTab.tsx @@ -18,6 +18,7 @@ interface SsoConfig { clientSecret: string; cloudEnvironment: string; isEnabled: boolean; + authMethodPolicy: 'sso_only' | 'password_only' | 'both'; } // Validation errors interface @@ -33,6 +34,13 @@ const cloudEnvironments = [ { _id: "AzureGovernment", name: "Azure Government" } ]; +// Authentication method policy options +const authMethodPolicies = [ + { _id: "both", name: "Allow both SSO and password authentication" }, + { _id: "sso_only", name: "Require SSO authentication only" }, + { _id: "password_only", name: "Allow password authentication only" } +]; + const SsoConfigTab: React.FC = () => { const [config, setConfig] = useState({ tenantId: "", @@ -40,6 +48,7 @@ const SsoConfigTab: React.FC = () => { clientSecret: "", cloudEnvironment: "AzurePublic", isEnabled: false, + authMethodPolicy: "both", }); const [errors, setErrors] = useState({}); @@ -222,6 +231,21 @@ const SsoConfigTab: React.FC = () => {
    + +