feat: add global toggles for TOTP and Email 2FA in system settings, conditionally show/hide user 2FA tab
This commit is contained in:
@@ -126,6 +126,8 @@ export default function SystemSettings({ settings }: SystemSettingsProps) {
|
||||
password_require_symbols: settings.password_require_symbols === '1' || settings.password_require_symbols === true,
|
||||
password_require_numbers: settings.password_require_numbers === '1' || settings.password_require_numbers === true,
|
||||
password_require_mixed_case: settings.password_require_mixed_case === '1' || settings.password_require_mixed_case === true,
|
||||
two_factor_totp_enabled: settings.two_factor_totp_enabled === '1' || settings.two_factor_totp_enabled === true,
|
||||
two_factor_email_enabled: settings.two_factor_email_enabled === '1' || settings.two_factor_email_enabled === true,
|
||||
oauth_google_enabled: settings.oauth_google_enabled === '1' || settings.oauth_google_enabled === true,
|
||||
oauth_google_client_id: settings.oauth_google_client_id || '',
|
||||
oauth_google_client_secret: settings.oauth_google_client_secret || '',
|
||||
@@ -237,41 +239,52 @@ export default function SystemSettings({ settings }: SystemSettingsProps) {
|
||||
)}
|
||||
|
||||
{activeTab === 'security' && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 anim-fade">
|
||||
<SectionCard title="Password Standards" description="Complexity requirements for user access" delay="0.1s">
|
||||
<div className="space-y-2">
|
||||
<ToggleItem label="Require Symbols" description="Include special characters (!@#$)" checked={data.settings.password_require_symbols} onChange={v => handleChange('password_require_symbols', v)} />
|
||||
<ToggleItem label="Require Numbers" description="Include numerical digits (0-9)" checked={data.settings.password_require_numbers} onChange={v => handleChange('password_require_numbers', v)} />
|
||||
<ToggleItem label="Require Mixed Case" description="Force Uppercase and Lowercase letters" checked={data.settings.password_require_mixed_case} onChange={v => handleChange('password_require_mixed_case', v)} />
|
||||
<div className="flex items-center justify-between py-4 mt-2 border-t border-gray-50">
|
||||
<span className="text-sm font-bold text-[#3D4E4B]">Minimum Length</span>
|
||||
<input type="number" value={data.settings.password_minimum_length} onChange={e => handleChange('password_minimum_length', parseInt(e.target.value))} className="w-16 h-10 input-field text-center px-2 text-[#D4A017] font-bold" />
|
||||
</div>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard title="OAuth Providers" description="Third-party authentication protocols" delay="0.15s">
|
||||
<div className="space-y-6">
|
||||
{['google', 'github'].map(prov => (
|
||||
<div key={prov} className="p-6 rounded-2xl bg-gray-50/50 border border-gray-100">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="text-sm font-bold text-[#3D4E4B] tracking-tight capitalize flex items-center gap-2">
|
||||
<span className={`w-2 h-2 rounded-full ${data.settings[`oauth_${prov}_enabled` as keyof typeof data.settings] ? 'bg-green-500' : 'bg-gray-300'}`} />
|
||||
{prov} Login
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer scale-90">
|
||||
<input type="checkbox" className="sr-only peer" checked={data.settings[`oauth_${prov}_enabled` as keyof typeof data.settings]} onChange={e => handleChange(`oauth_${prov}_enabled`, e.target.checked)} />
|
||||
<div className="w-10 h-5 bg-gray-300 rounded-full peer peer-checked:bg-[#D4A017] after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-400 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:after:translate-x-full border border-gray-200"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<InputField label="Client ID" value={data.settings[`oauth_${prov}_client_id` as keyof typeof data.settings]} onChange={(v:any) => handleChange(`oauth_${prov}_client_id`, v)} placeholder={`Enter ${prov} Client ID`} />
|
||||
<InputField label="Client Secret" type="password" value={data.settings[`oauth_${prov}_client_secret` as keyof typeof data.settings]} onChange={(v:any) => handleChange(`oauth_${prov}_client_secret`, v)} placeholder="••••••••" />
|
||||
</div>
|
||||
<div className="space-y-8 anim-fade">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<SectionCard title="Password Standards" description="Complexity requirements for user access" delay="0.1s">
|
||||
<div className="space-y-2">
|
||||
<ToggleItem label="Require Symbols" description="Include special characters (!@#$)" checked={data.settings.password_require_symbols} onChange={v => handleChange('password_require_symbols', v)} />
|
||||
<ToggleItem label="Require Numbers" description="Include numerical digits (0-9)" checked={data.settings.password_require_numbers} onChange={v => handleChange('password_require_numbers', v)} />
|
||||
<ToggleItem label="Require Mixed Case" description="Force Uppercase and Lowercase letters" checked={data.settings.password_require_mixed_case} onChange={v => handleChange('password_require_mixed_case', v)} />
|
||||
<div className="flex items-center justify-between py-4 mt-2 border-t border-gray-50">
|
||||
<span className="text-sm font-bold text-[#3D4E4B]">Minimum Length</span>
|
||||
<input type="number" value={data.settings.password_minimum_length} onChange={e => handleChange('password_minimum_length', parseInt(e.target.value))} className="w-16 h-10 input-field text-center px-2 text-[#D4A017] font-bold" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionCard>
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard title="Two-Factor Authentication (2FA)" description="Configure globally available 2FA options for users" delay="0.12s">
|
||||
<div className="space-y-2">
|
||||
<ToggleItem label="Google Authenticator (TOTP)" description="Allow users to use Authenticator Apps (Google, Authy, etc.)" checked={data.settings.two_factor_totp_enabled} onChange={v => handleChange('two_factor_totp_enabled', v)} />
|
||||
<ToggleItem label="Email 2FA" description="Allow users to receive 6-digit OTP codes via email (requires SMTP configuration)" checked={data.settings.two_factor_email_enabled} onChange={v => handleChange('two_factor_email_enabled', v)} />
|
||||
</div>
|
||||
</SectionCard>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<SectionCard title="OAuth Providers" description="Third-party authentication protocols" delay="0.15s">
|
||||
<div className="space-y-6">
|
||||
{['google', 'github'].map(prov => (
|
||||
<div key={prov} className="p-6 rounded-2xl bg-gray-50/50 border border-gray-100">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="text-sm font-bold text-[#3D4E4B] tracking-tight capitalize flex items-center gap-2">
|
||||
<span className={`w-2 h-2 rounded-full ${data.settings[`oauth_${prov}_enabled` as keyof typeof data.settings] ? 'bg-green-500' : 'bg-gray-300'}`} />
|
||||
{prov} Login
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer scale-90">
|
||||
<input type="checkbox" className="sr-only peer" checked={data.settings[`oauth_${prov}_enabled` as keyof typeof data.settings]} onChange={e => handleChange(`oauth_${prov}_enabled`, e.target.checked)} />
|
||||
<div className="w-10 h-5 bg-gray-300 rounded-full peer peer-checked:bg-[#D4A017] after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-400 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:after:translate-x-full border border-gray-200"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<InputField label="Client ID" value={data.settings[`oauth_${prov}_client_id` as keyof typeof data.settings]} onChange={(v:any) => handleChange(`oauth_${prov}_client_id`, v)} placeholder={`Enter ${prov} Client ID`} />
|
||||
<InputField label="Client Secret" type="password" value={data.settings[`oauth_${prov}_client_secret` as keyof typeof data.settings]} onChange={(v:any) => handleChange(`oauth_${prov}_client_secret`, v)} placeholder="••••••••" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</SectionCard>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user