diff --git a/src/components/admin/PipelineHealthAlerts.tsx b/src/components/admin/PipelineHealthAlerts.tsx index 939c27e4..15b60258 100644 --- a/src/components/admin/PipelineHealthAlerts.tsx +++ b/src/components/admin/PipelineHealthAlerts.tsx @@ -34,6 +34,7 @@ const ALERT_TYPE_LABELS: Record = { validation_error: 'Validation Error', stale_submissions: 'Stale Submissions', circular_dependency: 'Circular Dependency', + rate_limit_violation: 'Rate Limit Violation', }; export function PipelineHealthAlerts() { diff --git a/src/lib/companyHelpers.ts b/src/lib/companyHelpers.ts index bb20b747..e40d7e7d 100644 --- a/src/lib/companyHelpers.ts +++ b/src/lib/companyHelpers.ts @@ -7,6 +7,7 @@ import { withRetry, isRetryableError } from './retryHelpers'; import { logger } from './logger'; import { checkSubmissionRateLimit, recordSubmissionAttempt } from './submissionRateLimiter'; import { sanitizeErrorMessage } from './errorSanitizer'; +import { reportRateLimitViolation, reportBanEvasionAttempt } from './pipelineAlerts'; export type { CompanyFormData, TempCompanyData }; @@ -26,6 +27,11 @@ function checkRateLimitOrThrow(userId: string, action: string): void { retryAfter: rateLimit.retryAfter, }); + // Report to system alerts for admin visibility + reportRateLimitViolation(userId, action, rateLimit.retryAfter || 60).catch(() => { + // Non-blocking - don't fail submission if alert fails + }); + throw new Error(sanitizedMessage); } @@ -59,6 +65,10 @@ export async function submitCompanyCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'company_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -195,6 +205,10 @@ export async function submitCompanyUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'company_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } diff --git a/src/lib/entitySubmissionHelpers.ts b/src/lib/entitySubmissionHelpers.ts index 2362110a..ee1f72d1 100644 --- a/src/lib/entitySubmissionHelpers.ts +++ b/src/lib/entitySubmissionHelpers.ts @@ -19,6 +19,7 @@ import { } from './submissionValidation'; import { checkSubmissionRateLimit, recordSubmissionAttempt } from './submissionRateLimiter'; import { sanitizeErrorMessage } from './errorSanitizer'; +import { reportRateLimitViolation, reportBanEvasionAttempt } from './pipelineAlerts'; // ============================================ // COMPOSITE SUBMISSION TYPES @@ -221,6 +222,11 @@ function checkRateLimitOrThrow(userId: string, action: string): void { retryAfter: rateLimit.retryAfter, }); + // Report to system alerts for admin visibility + reportRateLimitViolation(userId, action, rateLimit.retryAfter || 60).catch(() => { + // Non-blocking - don't fail submission if alert fails + }); + throw new Error(sanitizedMessage); } @@ -281,6 +287,10 @@ async function submitCompositeCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'composite_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -738,6 +748,10 @@ export async function submitParkCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'park_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -945,6 +959,10 @@ export async function submitParkUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'park_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -1283,6 +1301,10 @@ export async function submitRideCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'ride_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -1573,6 +1595,10 @@ export async function submitRideUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'ride_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -1786,6 +1812,10 @@ export async function submitRideModelCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'ride_model_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -1950,6 +1980,10 @@ export async function submitRideModelUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'ride_model_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -2113,6 +2147,10 @@ export async function submitManufacturerCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'manufacturer_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -2220,6 +2258,10 @@ export async function submitManufacturerUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'manufacturer_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -2329,6 +2371,10 @@ export async function submitDesignerCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'designer_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -2436,6 +2482,10 @@ export async function submitDesignerUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'designer_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -2545,6 +2595,10 @@ export async function submitOperatorCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'operator_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -2652,6 +2706,10 @@ export async function submitOperatorUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'operator_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -2761,6 +2819,10 @@ export async function submitPropertyOwnerCreation( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'property_owner_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -2868,6 +2930,10 @@ export async function submitPropertyOwnerUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'property_owner_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -3017,6 +3083,10 @@ export async function submitTimelineEvent( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'timeline_event_creation').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } @@ -3188,6 +3258,10 @@ export async function submitTimelineEventUpdate( ); if (profile?.banned) { + // Report ban evasion attempt + reportBanEvasionAttempt(userId, 'timeline_event_update').catch(() => { + // Non-blocking - don't fail if alert fails + }); throw new Error('Account suspended. Contact support for assistance.'); } diff --git a/src/lib/pipelineAlerts.ts b/src/lib/pipelineAlerts.ts index b43b7640..4c739705 100644 --- a/src/lib/pipelineAlerts.ts +++ b/src/lib/pipelineAlerts.ts @@ -80,3 +80,59 @@ export async function checkAndReportQueueStatus(userId?: string): Promise }); } } + +/** + * Report rate limit violations to system alerts + * Called when checkSubmissionRateLimit() blocks a user + */ +export async function reportRateLimitViolation( + userId: string, + action: string, + retryAfter: number +): Promise { + try { + await supabase.rpc('create_system_alert', { + p_alert_type: 'rate_limit_violation', + p_severity: 'medium', + p_message: `Rate limit exceeded: ${action} (retry after ${retryAfter}s)`, + p_metadata: { + user_id: userId, + action, + retry_after_seconds: retryAfter, + timestamp: new Date().toISOString() + } + }); + } catch (error) { + handleNonCriticalError(error, { + action: 'Report rate limit violation to alerts' + }); + } +} + +/** + * Report ban evasion attempts to system alerts + * Called when banned users attempt to submit content + */ +export async function reportBanEvasionAttempt( + userId: string, + action: string, + username?: string +): Promise { + try { + await supabase.rpc('create_system_alert', { + p_alert_type: 'ban_attempt', + p_severity: 'high', + p_message: `Banned user attempted submission: ${action}${username ? ` (${username})` : ''}`, + p_metadata: { + user_id: userId, + action, + username: username || 'unknown', + timestamp: new Date().toISOString() + } + }); + } catch (error) { + handleNonCriticalError(error, { + action: 'Report ban evasion attempt to alerts' + }); + } +}