feat: automatic mutation audit logging for all non-super-admin users
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled
Implement withMutationAudit middleware in tRPC that automatically logs every successful mutation for non-SUPER_ADMIN users. Captures procedure path, sanitized input (passwords/tokens redacted), user role, IP, and user agent. Applied to all procedure types except superAdminProcedure. - Input sanitization: strips sensitive fields, truncates long strings (500 chars), limits array size (20 items), caps nesting depth (4) - Entity ID auto-extraction from common input patterns (id, userId, projectId, roundId, etc.) - Action names derived from procedure path (e.g., evaluation.submit becomes EVALUATION_SUBMIT) - Audit page updated with new action types and entity types for filtering auto-generated entries - Failures silently caught — audit logging never breaks operations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,8 +57,9 @@ import { CsvExportDialog } from '@/components/shared/csv-export-dialog'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
// Action type options
|
||||
// Action type options (manual audit actions + auto-generated mutation audit actions)
|
||||
const ACTION_TYPES = [
|
||||
// Manual audit actions
|
||||
'CREATE',
|
||||
'UPDATE',
|
||||
'DELETE',
|
||||
@@ -88,12 +89,48 @@ const ACTION_TYPES = [
|
||||
'APPLY_AI_SUGGESTIONS',
|
||||
'APPLY_SUGGESTIONS',
|
||||
'NOTIFY_JURORS_OF_ASSIGNMENTS',
|
||||
'IMPERSONATION_START',
|
||||
'IMPERSONATION_END',
|
||||
// Auto-generated mutation audit actions (non-super-admin)
|
||||
'EVALUATION_START',
|
||||
'EVALUATION_SUBMIT',
|
||||
'EVALUATION_AUTOSAVE',
|
||||
'EVALUATION_DECLARE_COI',
|
||||
'EVALUATION_ADD_COMMENT',
|
||||
'APPLICANT_SAVE_SUBMISSION',
|
||||
'APPLICANT_SAVE_FILE_METADATA',
|
||||
'APPLICANT_DELETE_FILE',
|
||||
'APPLICANT_REQUEST_MENTORING',
|
||||
'APPLICANT_WITHDRAW_FROM_COMPETITION',
|
||||
'APPLICANT_INVITE_TEAM_MEMBER',
|
||||
'APPLICANT_REMOVE_TEAM_MEMBER',
|
||||
'APPLICANT_SEND_MENTOR_MESSAGE',
|
||||
'APPLICATION_SUBMIT',
|
||||
'APPLICATION_SAVE_DRAFT',
|
||||
'APPLICATION_SUBMIT_DRAFT',
|
||||
'MENTOR_SEND_MESSAGE',
|
||||
'MENTOR_CREATE_NOTE',
|
||||
'MENTOR_DELETE_NOTE',
|
||||
'MENTOR_COMPLETE_MILESTONE',
|
||||
'LIVE_CAST_VOTE',
|
||||
'LIVE_CAST_STAGE_VOTE',
|
||||
'LIVE_VOTING_VOTE',
|
||||
'LIVE_VOTING_CAST_AUDIENCE_VOTE',
|
||||
'DELIBERATION_SUBMIT_VOTE',
|
||||
'NOTIFICATION_MARK_AS_READ',
|
||||
'NOTIFICATION_MARK_ALL_AS_READ',
|
||||
'USER_UPDATE_PROFILE',
|
||||
'USER_SET_PASSWORD',
|
||||
'USER_CHANGE_PASSWORD',
|
||||
'USER_COMPLETE_ONBOARDING',
|
||||
'SPECIAL_AWARD_SUBMIT_VOTE',
|
||||
]
|
||||
|
||||
// Entity type options
|
||||
const ENTITY_TYPES = [
|
||||
'User',
|
||||
'Program',
|
||||
'Competition',
|
||||
'Round',
|
||||
'Project',
|
||||
'Assignment',
|
||||
@@ -101,6 +138,21 @@ const ENTITY_TYPES = [
|
||||
'EvaluationForm',
|
||||
'ProjectFile',
|
||||
'GracePeriod',
|
||||
'Applicant',
|
||||
'Application',
|
||||
'Mentor',
|
||||
'Live',
|
||||
'LiveVoting',
|
||||
'Deliberation',
|
||||
'Notification',
|
||||
'SpecialAward',
|
||||
'File',
|
||||
'Tag',
|
||||
'Message',
|
||||
'Settings',
|
||||
'Ranking',
|
||||
'Filtering',
|
||||
'RoundEngine',
|
||||
]
|
||||
|
||||
// Color map for action types
|
||||
@@ -128,8 +180,35 @@ const actionColors: Record<string, 'default' | 'destructive' | 'secondary' | 'ou
|
||||
APPLY_AI_SUGGESTIONS: 'default',
|
||||
APPLY_SUGGESTIONS: 'default',
|
||||
NOTIFY_JURORS_OF_ASSIGNMENTS: 'outline',
|
||||
IMPERSONATION_START: 'destructive',
|
||||
IMPERSONATION_END: 'secondary',
|
||||
// Auto-generated mutation audit actions
|
||||
EVALUATION_START: 'default',
|
||||
EVALUATION_SUBMIT: 'default',
|
||||
EVALUATION_AUTOSAVE: 'outline',
|
||||
EVALUATION_DECLARE_COI: 'secondary',
|
||||
EVALUATION_ADD_COMMENT: 'outline',
|
||||
APPLICANT_SAVE_SUBMISSION: 'default',
|
||||
APPLICANT_DELETE_FILE: 'destructive',
|
||||
APPLICANT_WITHDRAW_FROM_COMPETITION: 'destructive',
|
||||
APPLICANT_INVITE_TEAM_MEMBER: 'default',
|
||||
APPLICANT_REMOVE_TEAM_MEMBER: 'destructive',
|
||||
APPLICATION_SUBMIT: 'default',
|
||||
MENTOR_SEND_MESSAGE: 'outline',
|
||||
MENTOR_CREATE_NOTE: 'default',
|
||||
MENTOR_DELETE_NOTE: 'destructive',
|
||||
LIVE_CAST_VOTE: 'default',
|
||||
LIVE_CAST_STAGE_VOTE: 'default',
|
||||
LIVE_VOTING_CAST_AUDIENCE_VOTE: 'default',
|
||||
DELIBERATION_SUBMIT_VOTE: 'default',
|
||||
SPECIAL_AWARD_SUBMIT_VOTE: 'default',
|
||||
USER_UPDATE_PROFILE: 'secondary',
|
||||
USER_SET_PASSWORD: 'outline',
|
||||
USER_CHANGE_PASSWORD: 'outline',
|
||||
USER_COMPLETE_ONBOARDING: 'default',
|
||||
}
|
||||
|
||||
|
||||
export default function AuditLogPage() {
|
||||
// Filter state
|
||||
const [filters, setFilters] = useState({
|
||||
|
||||
Reference in New Issue
Block a user