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>
150 lines
4.9 KiB
TypeScript
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>
|
|
)
|
|
}
|