refactor: remove security-related hooks and pages
- Deleted `useSecurity.ts` hook which managed authentication users, providers, and policies. - Removed `Policies.tsx`, `Providers.tsx`, and `Users.tsx` pages that utilized the above hook. - Cleaned up the `index.tsx` file in the Security section to remove references to the deleted pages. - Updated mock data by removing unused properties related to forward authentication.
This commit is contained in:
@@ -1,157 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { getForwardAuthConfig, updateForwardAuthConfig, getForwardAuthTemplates, ForwardAuthConfig } from '../api/security';
|
||||
import { Button } from './ui/Button';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { Shield, Check, AlertTriangle } from 'lucide-react';
|
||||
|
||||
export default function ForwardAuthSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
const [formData, setFormData] = useState<ForwardAuthConfig>({
|
||||
provider: 'custom',
|
||||
address: '',
|
||||
trust_forward_header: true,
|
||||
});
|
||||
|
||||
const { data: config, isLoading } = useQuery({
|
||||
queryKey: ['forwardAuth'],
|
||||
queryFn: getForwardAuthConfig,
|
||||
});
|
||||
|
||||
const { data: templates } = useQuery({
|
||||
queryKey: ['forwardAuthTemplates'],
|
||||
queryFn: getForwardAuthTemplates,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setFormData(config);
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: updateForwardAuthConfig,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['forwardAuth'] });
|
||||
toast.success('Forward Auth configuration saved');
|
||||
},
|
||||
onError: (error: Error & { response?: { data?: { error?: string } } }) => {
|
||||
toast.error(error.response?.data?.error || 'Failed to save configuration');
|
||||
},
|
||||
});
|
||||
|
||||
const handleTemplateChange = (provider: string) => {
|
||||
if (templates && templates[provider]) {
|
||||
const template = templates[provider];
|
||||
setFormData({
|
||||
...formData,
|
||||
provider: provider as 'authelia' | 'authentik' | 'pomerium' | 'custom',
|
||||
address: template.address,
|
||||
trust_forward_header: template.trust_forward_header,
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
...formData,
|
||||
provider: 'custom',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
mutation.mutate(formData);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="animate-pulse h-64 bg-gray-100 dark:bg-gray-800 rounded-lg"></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-dark-card rounded-lg shadow-sm border border-gray-200 dark:border-gray-800 p-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
|
||||
<Shield className="w-6 h-6 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Forward Authentication</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Configure a global authentication provider (SSO) for your proxy hosts.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Provider Template
|
||||
</label>
|
||||
<select
|
||||
value={formData.provider}
|
||||
onChange={(e) => handleTemplateChange(e.target.value)}
|
||||
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-dark-bg text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
>
|
||||
<option value="custom">Custom</option>
|
||||
<option value="authelia">Authelia</option>
|
||||
<option value="authentik">Authentik</option>
|
||||
<option value="pomerium">Pomerium</option>
|
||||
</select>
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Select a template to pre-fill configuration or choose Custom.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Auth Service Address
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
required
|
||||
value={formData.address}
|
||||
onChange={(e) => setFormData({ ...formData, address: e.target.value })}
|
||||
placeholder="http://authelia:9091/api/verify"
|
||||
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-dark-bg text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
The internal URL where Caddy will send auth subrequests.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 p-4 bg-gray-50 dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="trust_forward_header"
|
||||
checked={formData.trust_forward_header}
|
||||
onChange={(e) => setFormData({ ...formData, trust_forward_header: e.target.checked })}
|
||||
className="w-4 h-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500 dark:bg-dark-bg dark:border-gray-600"
|
||||
/>
|
||||
<label htmlFor="trust_forward_header" className="flex-1">
|
||||
<span className="block text-sm font-medium text-gray-900 dark:text-white">
|
||||
Trust Forward Headers
|
||||
</span>
|
||||
<span className="block text-xs text-gray-500 dark:text-gray-400">
|
||||
Send X-Forwarded-Method and X-Forwarded-Uri headers to the auth service. Required for most providers.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-800">
|
||||
<div className="flex items-center gap-2 text-sm text-amber-600 dark:text-amber-400">
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
<span>Changes apply immediately to all hosts using Forward Auth.</span>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={mutation.isPending}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Check className="w-4 h-4" />
|
||||
Save Configuration
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -50,7 +50,6 @@ export default function Layout({ children }: LayoutProps) {
|
||||
{ name: 'Uptime', path: '/uptime', icon: '📈' },
|
||||
{ name: 'Notifications', path: '/notifications', icon: '🔔' },
|
||||
{ name: 'Import Caddyfile', path: '/import', icon: '📥' },
|
||||
{ name: 'Security', path: '/security', icon: '🛡️' },
|
||||
{
|
||||
name: 'Settings',
|
||||
path: '/settings',
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { CircleHelp, AlertCircle, Check, X, Loader2, ShieldCheck } from 'lucide-react'
|
||||
import { CircleHelp, AlertCircle, Check, X, Loader2 } from 'lucide-react'
|
||||
import type { ProxyHost } from '../api/proxyHosts'
|
||||
import { testProxyHostConnection } from '../api/proxyHosts'
|
||||
import { useRemoteServers } from '../hooks/useRemoteServers'
|
||||
import { useDomains } from '../hooks/useDomains'
|
||||
import { useCertificates } from '../hooks/useCertificates'
|
||||
import { useDocker } from '../hooks/useDocker'
|
||||
import { useAuthPolicies } from '../hooks/useSecurity'
|
||||
import { parse } from 'tldts'
|
||||
|
||||
interface ProxyHostFormProps {
|
||||
@@ -28,9 +27,6 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
|
||||
hsts_subdomains: host?.hsts_subdomains ?? true,
|
||||
block_exploits: host?.block_exploits ?? true,
|
||||
websocket_support: host?.websocket_support ?? true,
|
||||
forward_auth_enabled: host?.forward_auth_enabled ?? false,
|
||||
forward_auth_bypass: host?.forward_auth_bypass || '',
|
||||
auth_policy_id: host?.auth_policy_id || null,
|
||||
advanced_config: host?.advanced_config || '',
|
||||
enabled: host?.enabled ?? true,
|
||||
certificate_id: host?.certificate_id,
|
||||
@@ -39,7 +35,6 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
|
||||
const { servers: remoteServers } = useRemoteServers()
|
||||
const { domains, createDomain } = useDomains()
|
||||
const { certificates } = useCertificates()
|
||||
const { policies: authPolicies } = useAuthPolicies()
|
||||
const { containers: dockerContainers, isLoading: dockerLoading, error: dockerError } = useDocker(
|
||||
formData.forward_host ? undefined : undefined // Simplified for now, logic below handles it
|
||||
)
|
||||
@@ -489,82 +484,6 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Access Control (SSO & Forward Auth) */}
|
||||
<div className="p-4 bg-gray-800/50 rounded-lg border border-gray-700 space-y-4">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<ShieldCheck className="text-blue-400" size={20} />
|
||||
<h3 className="text-lg font-medium text-white">Access Control</h3>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Access Policy (Built-in SSO)
|
||||
</label>
|
||||
<select
|
||||
value={formData.auth_policy_id || ''}
|
||||
onChange={e => {
|
||||
const val = e.target.value ? parseInt(e.target.value) : null;
|
||||
setFormData({
|
||||
...formData,
|
||||
auth_policy_id: val,
|
||||
// If a policy is selected, disable legacy forward auth to avoid conflicts
|
||||
forward_auth_enabled: val ? false : formData.forward_auth_enabled
|
||||
});
|
||||
}}
|
||||
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Public (No Authentication)</option>
|
||||
{authPolicies.map(policy => (
|
||||
<option key={policy.id} value={policy.id}>
|
||||
{policy.name} {policy.description ? `(${policy.description})` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Select a policy to protect this service with the built-in SSO.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Legacy Forward Auth - Only show if no policy is selected */}
|
||||
{!formData.auth_policy_id && (
|
||||
<div className="pt-4 border-t border-gray-700">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.forward_auth_enabled}
|
||||
onChange={e => setFormData({ ...formData, forward_auth_enabled: e.target.checked })}
|
||||
className="w-4 h-4 text-blue-600 bg-gray-900 border-gray-700 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<span className="text-sm font-medium text-gray-300">Enable External Forward Auth</span>
|
||||
</label>
|
||||
<div title="Protects this service using your configured global authentication provider (e.g. Authelia, Authentik)." className="text-gray-500 hover:text-gray-300 cursor-help">
|
||||
<CircleHelp size={14} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{formData.forward_auth_enabled && (
|
||||
<div className="mt-3">
|
||||
<label htmlFor="forward-auth-bypass" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Bypass Paths (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
id="forward-auth-bypass"
|
||||
value={formData.forward_auth_bypass}
|
||||
onChange={e => setFormData({ ...formData, forward_auth_bypass: e.target.value })}
|
||||
placeholder="/api/webhook, /public/*"
|
||||
rows={2}
|
||||
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white font-mono text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Comma-separated list of paths to exclude from authentication.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Advanced Config */}
|
||||
<div>
|
||||
<label htmlFor="advanced-config" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
|
||||
@@ -66,7 +66,6 @@ describe('Layout', () => {
|
||||
expect(screen.getByText('Remote Servers')).toBeInTheDocument()
|
||||
expect(screen.getByText('Certificates')).toBeInTheDocument()
|
||||
expect(screen.getByText('Import Caddyfile')).toBeInTheDocument()
|
||||
expect(screen.getByText('Security')).toBeInTheDocument()
|
||||
expect(screen.getByText('Settings')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@@ -234,25 +234,4 @@ describe('ProxyHostForm', () => {
|
||||
|
||||
expect(screen.getByLabelText(/Domain Names/i)).toHaveValue('my-app.existing.com')
|
||||
})
|
||||
|
||||
it('toggles forward auth fields', async () => {
|
||||
renderWithClient(
|
||||
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
// The Forward Auth toggle now uses "Enable External Forward Auth" label
|
||||
// and only appears when no Access Policy is selected (default is no policy)
|
||||
const toggle = screen.getByLabelText('Enable External Forward Auth')
|
||||
expect(toggle).not.toBeChecked()
|
||||
|
||||
// Bypass field should not be visible initially
|
||||
expect(screen.queryByLabelText('Bypass Paths (Optional)')).not.toBeInTheDocument()
|
||||
|
||||
// Enable it
|
||||
fireEvent.click(toggle)
|
||||
expect(toggle).toBeChecked()
|
||||
|
||||
// Bypass field should now be visible
|
||||
expect(screen.getByLabelText('Bypass Paths (Optional)')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user