mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 04:51:11 -05:00
Fix: Add dashboard widget for flow violations
This commit is contained in:
@@ -155,6 +155,11 @@ export function RideForm({ onSubmit, onCancel, initialData, isEditing = false }:
|
||||
const { preferences } = useUnitPreferences();
|
||||
const measurementSystem = preferences.measurement_system;
|
||||
|
||||
// Validate that onSubmit uses submission helpers (dev mode only)
|
||||
useEffect(() => {
|
||||
validateSubmissionHandler(onSubmit, 'ride');
|
||||
}, [onSubmit]);
|
||||
|
||||
// Manufacturer and model state
|
||||
const [selectedManufacturerId, setSelectedManufacturerId] = useState<string>(
|
||||
initialData?.manufacturer_id || ''
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useRef, useEffect, useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FileText, Flag, AlertCircle, Activity } from 'lucide-react';
|
||||
import { FileText, Flag, AlertCircle, Activity, ShieldAlert } from 'lucide-react';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
@@ -12,6 +12,8 @@ import { ReportsQueue } from '@/components/moderation/ReportsQueue';
|
||||
import { RecentActivity } from '@/components/moderation/RecentActivity';
|
||||
import { useModerationStats } from '@/hooks/useModerationStats';
|
||||
import { useAdminSettings } from '@/hooks/useAdminSettings';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
|
||||
export default function AdminDashboard() {
|
||||
const { user, loading: authLoading } = useAuth();
|
||||
@@ -19,6 +21,7 @@ export default function AdminDashboard() {
|
||||
const navigate = useNavigate();
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('moderation');
|
||||
const [suspiciousVersionsCount, setSuspiciousVersionsCount] = useState<number>(0);
|
||||
|
||||
const moderationQueueRef = useRef<ModerationQueueRef>(null);
|
||||
const reportsQueueRef = useRef<any>(null);
|
||||
@@ -38,9 +41,28 @@ export default function AdminDashboard() {
|
||||
pollingInterval: pollInterval,
|
||||
});
|
||||
|
||||
// Check for suspicious versions (bypassed submission flow)
|
||||
const checkSuspiciousVersions = useCallback(async () => {
|
||||
if (!user || !isModerator()) return;
|
||||
|
||||
const { count, error } = await supabase
|
||||
.from('entity_versions')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.is('changed_by', null);
|
||||
|
||||
if (!error && count !== null) {
|
||||
setSuspiciousVersionsCount(count);
|
||||
}
|
||||
}, [user, isModerator]);
|
||||
|
||||
useEffect(() => {
|
||||
checkSuspiciousVersions();
|
||||
}, [checkSuspiciousVersions]);
|
||||
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setIsRefreshing(true);
|
||||
await refreshStats();
|
||||
await checkSuspiciousVersions();
|
||||
|
||||
// Refresh active tab's content
|
||||
switch (activeTab) {
|
||||
@@ -56,7 +78,7 @@ export default function AdminDashboard() {
|
||||
}
|
||||
|
||||
setTimeout(() => setIsRefreshing(false), 500);
|
||||
}, [refreshStats, activeTab]);
|
||||
}, [refreshStats, checkSuspiciousVersions, activeTab]);
|
||||
|
||||
const handleStatCardClick = (cardType: 'submissions' | 'reports' | 'flagged') => {
|
||||
switch (cardType) {
|
||||
@@ -143,6 +165,17 @@ export default function AdminDashboard() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Security Warning for Suspicious Versions */}
|
||||
{suspiciousVersionsCount > 0 && (
|
||||
<Alert variant="destructive" className="border-red-500/50 bg-red-500/10">
|
||||
<ShieldAlert className="h-5 w-5" />
|
||||
<AlertDescription className="ml-2">
|
||||
<strong>Security Alert:</strong> {suspiciousVersionsCount} entity version{suspiciousVersionsCount !== 1 ? 's' : ''} detected without user attribution.
|
||||
This may indicate submission flow bypass. Check admin audit logs for details.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{statCards.map((card) => {
|
||||
const Icon = card.icon;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
-- Update auto_create_entity_version to log suspicious versions without user attribution
|
||||
CREATE OR REPLACE FUNCTION public.auto_create_entity_version()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_entity_type TEXT;
|
||||
v_change_type version_change_type;
|
||||
v_user_id UUID;
|
||||
v_version_data JSONB;
|
||||
BEGIN
|
||||
-- Determine entity type from table name
|
||||
v_entity_type := CASE TG_TABLE_NAME
|
||||
WHEN 'parks' THEN 'park'
|
||||
WHEN 'rides' THEN 'ride'
|
||||
WHEN 'companies' THEN 'company'
|
||||
WHEN 'ride_models' THEN 'ride_model'
|
||||
WHEN 'photos' THEN 'photo'
|
||||
ELSE substring(TG_TABLE_NAME from 1 for length(TG_TABLE_NAME) - 1)
|
||||
END;
|
||||
|
||||
-- Determine change type
|
||||
v_change_type := CASE TG_OP
|
||||
WHEN 'INSERT' THEN 'created'::version_change_type
|
||||
WHEN 'UPDATE' THEN 'updated'::version_change_type
|
||||
ELSE 'updated'::version_change_type
|
||||
END;
|
||||
|
||||
-- Get user from session or auth context
|
||||
BEGIN
|
||||
v_user_id := current_setting('app.current_user_id', true)::UUID;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
v_user_id := auth.uid();
|
||||
END;
|
||||
|
||||
-- Convert NEW record to JSONB
|
||||
v_version_data := to_jsonb(NEW);
|
||||
|
||||
-- Create version (only if we have a user context)
|
||||
IF v_user_id IS NOT NULL THEN
|
||||
PERFORM public.create_entity_version(
|
||||
v_entity_type,
|
||||
NEW.id,
|
||||
v_version_data,
|
||||
v_user_id,
|
||||
CASE TG_OP
|
||||
WHEN 'INSERT' THEN 'Entity created'
|
||||
WHEN 'UPDATE' THEN 'Entity updated'
|
||||
ELSE 'Entity modified'
|
||||
END,
|
||||
NULL,
|
||||
v_change_type
|
||||
);
|
||||
ELSE
|
||||
-- Log suspicious version without user (audit trail)
|
||||
INSERT INTO public.admin_audit_log (
|
||||
action,
|
||||
details,
|
||||
created_at
|
||||
) VALUES (
|
||||
'version_without_user',
|
||||
jsonb_build_object(
|
||||
'entity_type', v_entity_type,
|
||||
'entity_id', NEW.id,
|
||||
'table', TG_TABLE_NAME,
|
||||
'operation', TG_OP,
|
||||
'timestamp', NOW()
|
||||
),
|
||||
NOW()
|
||||
);
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
Reference in New Issue
Block a user