Files
MOPC-Portal/src/components/settings/security-settings-form.tsx
Matt a606292aaa Initial commit: MOPC platform with Docker deployment setup
Full Next.js 15 platform with tRPC, Prisma, PostgreSQL, NextAuth.
Includes production Dockerfile (multi-stage, port 7600), docker-compose
with registry-based image pull, Gitea Actions CI workflow, nginx config
for portal.monaco-opc.com, deployment scripts, and DEPLOYMENT.md guide.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 13:41:32 +01:00

150 lines
4.9 KiB
TypeScript

'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { toast } from 'sonner'
import { Loader2, Shield } from 'lucide-react'
import { trpc } from '@/lib/trpc/client'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
const formSchema = z.object({
session_duration_hours: z.string().regex(/^\d+$/, 'Must be a number'),
magic_link_expiry_minutes: z.string().regex(/^\d+$/, 'Must be a number'),
rate_limit_requests_per_minute: z.string().regex(/^\d+$/, 'Must be a number'),
})
type FormValues = z.infer<typeof formSchema>
interface SecuritySettingsFormProps {
settings: {
session_duration_hours?: string
magic_link_expiry_minutes?: string
rate_limit_requests_per_minute?: string
}
}
export function SecuritySettingsForm({ settings }: SecuritySettingsFormProps) {
const utils = trpc.useUtils()
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
session_duration_hours: settings.session_duration_hours || '24',
magic_link_expiry_minutes: settings.magic_link_expiry_minutes || '15',
rate_limit_requests_per_minute: settings.rate_limit_requests_per_minute || '60',
},
})
const updateSettings = trpc.settings.updateMultiple.useMutation({
onSuccess: () => {
toast.success('Security settings saved successfully')
utils.settings.getByCategory.invalidate({ category: 'SECURITY' })
},
onError: (error) => {
toast.error(`Failed to save settings: ${error.message}`)
},
})
const onSubmit = (data: FormValues) => {
updateSettings.mutate({
settings: [
{ key: 'session_duration_hours', value: data.session_duration_hours },
{ key: 'magic_link_expiry_minutes', value: data.magic_link_expiry_minutes },
{ key: 'rate_limit_requests_per_minute', value: data.rate_limit_requests_per_minute },
],
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="session_duration_hours"
render={({ field }) => (
<FormItem>
<FormLabel>Session Duration (hours)</FormLabel>
<FormControl>
<Input type="number" min="1" max="720" placeholder="24" {...field} />
</FormControl>
<FormDescription>
How long user sessions remain valid before requiring re-authentication.
Recommended: 24 hours for jury members, up to 168 hours (1 week) for admins.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="magic_link_expiry_minutes"
render={({ field }) => (
<FormItem>
<FormLabel>Magic Link Expiry (minutes)</FormLabel>
<FormControl>
<Input type="number" min="5" max="60" placeholder="15" {...field} />
</FormControl>
<FormDescription>
How long magic link authentication links remain valid.
Shorter is more secure. Recommended: 15 minutes.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="rate_limit_requests_per_minute"
render={({ field }) => (
<FormItem>
<FormLabel>API Rate Limit (requests/minute)</FormLabel>
<FormControl>
<Input type="number" min="10" max="300" placeholder="60" {...field} />
</FormControl>
<FormDescription>
Maximum API requests allowed per minute per user.
Helps prevent abuse and ensures fair resource usage.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4">
<p className="text-sm text-amber-800">
<strong>Security Note:</strong> Changing these settings affects all users immediately.
Reducing session duration will not log out existing sessions but will prevent renewal.
</p>
</div>
<Button type="submit" disabled={updateSettings.isPending}>
{updateSettings.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Saving...
</>
) : (
<>
<Shield className="mr-2 h-4 w-4" />
Save Security Settings
</>
)}
</Button>
</form>
</Form>
)
}