2026-02-14 15:26:42 +01:00
|
|
|
import { initTRPC, TRPCError } from '@trpc/server'
|
|
|
|
|
import superjson from 'superjson'
|
|
|
|
|
import { ZodError } from 'zod'
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
import type { Prisma } from '@prisma/client'
|
2026-02-14 15:26:42 +01:00
|
|
|
import type { Context } from './context'
|
|
|
|
|
import type { UserRole } from '@prisma/client'
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initialize tRPC with context type and configuration
|
|
|
|
|
*/
|
|
|
|
|
const t = initTRPC.context<Context>().create({
|
|
|
|
|
transformer: superjson,
|
|
|
|
|
errorFormatter({ shape, error }) {
|
|
|
|
|
return {
|
|
|
|
|
...shape,
|
|
|
|
|
data: {
|
|
|
|
|
...shape.data,
|
|
|
|
|
zodError:
|
|
|
|
|
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Export reusable router and procedure helpers
|
|
|
|
|
*/
|
|
|
|
|
export const router = t.router
|
|
|
|
|
export const publicProcedure = t.procedure
|
|
|
|
|
export const middleware = t.middleware
|
|
|
|
|
export const createCallerFactory = t.createCallerFactory
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Middleware
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Middleware to require authenticated user
|
|
|
|
|
*/
|
|
|
|
|
const isAuthenticated = middleware(async ({ ctx, next }) => {
|
|
|
|
|
if (!ctx.session?.user) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'UNAUTHORIZED',
|
|
|
|
|
message: 'You must be logged in to perform this action',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return next({
|
|
|
|
|
ctx: {
|
|
|
|
|
...ctx,
|
|
|
|
|
user: ctx.session.user,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-02-24 17:44:55 +01:00
|
|
|
/**
|
|
|
|
|
* Helper to check if a user has any of the specified roles.
|
|
|
|
|
* Checks the roles array first, falls back to [role] for stale JWT tokens.
|
|
|
|
|
*/
|
|
|
|
|
export function userHasRole(user: { role: UserRole; roles?: UserRole[] }, ...checkRoles: UserRole[]): boolean {
|
|
|
|
|
const userRoles = user.roles?.length ? user.roles : [user.role]
|
|
|
|
|
return checkRoles.some(r => userRoles.includes(r))
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 15:26:42 +01:00
|
|
|
/**
|
|
|
|
|
* Middleware to require specific role(s)
|
|
|
|
|
*/
|
|
|
|
|
const hasRole = (...roles: UserRole[]) =>
|
|
|
|
|
middleware(async ({ ctx, next }) => {
|
|
|
|
|
if (!ctx.session?.user) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'UNAUTHORIZED',
|
|
|
|
|
message: 'You must be logged in to perform this action',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 17:44:55 +01:00
|
|
|
// Use roles array, fallback to [role] for stale JWT tokens
|
|
|
|
|
const userRoles = ctx.session.user.roles?.length
|
|
|
|
|
? ctx.session.user.roles
|
|
|
|
|
: [ctx.session.user.role]
|
|
|
|
|
|
|
|
|
|
if (!roles.some(r => userRoles.includes(r))) {
|
2026-02-14 15:26:42 +01:00
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'FORBIDDEN',
|
|
|
|
|
message: 'You do not have permission to perform this action',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return next({
|
|
|
|
|
ctx: {
|
|
|
|
|
...ctx,
|
|
|
|
|
user: ctx.session.user,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
// =============================================================================
|
|
|
|
|
// Mutation Audit Logging
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/** Fields that must never appear in audit log input snapshots. */
|
|
|
|
|
const SENSITIVE_KEYS = new Set([
|
|
|
|
|
'password', 'passwordHash', 'currentPassword', 'newPassword', 'confirmPassword',
|
|
|
|
|
'token', 'secret', 'apiKey', 'accessKey', 'secretKey',
|
|
|
|
|
'creditCard', 'cvv', 'ssn',
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
/** Max depth / size for serialized input to avoid bloating the audit table. */
|
|
|
|
|
const MAX_INPUT_DEPTH = 4
|
|
|
|
|
const MAX_STRING_LENGTH = 500
|
|
|
|
|
const MAX_ARRAY_LENGTH = 20
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Recursively sanitize an input object for safe storage in audit logs.
|
|
|
|
|
* - Strips sensitive fields (passwords, tokens, secrets)
|
|
|
|
|
* - Truncates long strings and arrays
|
|
|
|
|
* - Limits nesting depth
|
|
|
|
|
*/
|
|
|
|
|
function sanitizeInput(value: unknown, depth = 0): unknown {
|
|
|
|
|
if (depth > MAX_INPUT_DEPTH) return '[nested]'
|
|
|
|
|
if (value === null || value === undefined) return value
|
|
|
|
|
if (typeof value === 'boolean' || typeof value === 'number') return value
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
return value.length > MAX_STRING_LENGTH
|
|
|
|
|
? value.slice(0, MAX_STRING_LENGTH) + '...'
|
|
|
|
|
: value
|
|
|
|
|
}
|
|
|
|
|
if (value instanceof Date) return value.toISOString()
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
const truncated = value.slice(0, MAX_ARRAY_LENGTH).map(v => sanitizeInput(v, depth + 1))
|
|
|
|
|
if (value.length > MAX_ARRAY_LENGTH) truncated.push(`[+${value.length - MAX_ARRAY_LENGTH} more]`)
|
|
|
|
|
return truncated
|
|
|
|
|
}
|
|
|
|
|
if (typeof value === 'object') {
|
|
|
|
|
const result: Record<string, unknown> = {}
|
|
|
|
|
for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
|
|
|
|
|
if (SENSITIVE_KEYS.has(key)) {
|
|
|
|
|
result[key] = '[REDACTED]'
|
|
|
|
|
} else {
|
|
|
|
|
result[key] = sanitizeInput(val, depth + 1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
return String(value)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 15:26:42 +01:00
|
|
|
/**
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* Middleware that automatically logs all successful mutations for non-SUPER_ADMIN users.
|
|
|
|
|
* Captures: procedure path, sanitized input, user role, IP, user agent.
|
|
|
|
|
* Failures are silently caught — audit logging never breaks the calling operation.
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
const withMutationAudit = middleware(async ({ ctx, next, path, type, getRawInput }) => {
|
2026-02-14 15:26:42 +01:00
|
|
|
const result = await next()
|
|
|
|
|
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
// Only log mutations, only on success
|
|
|
|
|
if (type !== 'mutation' || !result.ok) return result
|
2026-02-14 15:26:42 +01:00
|
|
|
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
// Must have an authenticated user
|
|
|
|
|
const user = ctx.session?.user
|
|
|
|
|
if (!user?.id) return result
|
|
|
|
|
|
|
|
|
|
// Skip SUPER_ADMIN — they have their own manual audit trail
|
|
|
|
|
if (user.role === 'SUPER_ADMIN') return result
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Extract router name and procedure name from path (e.g., "evaluation.submit")
|
|
|
|
|
const dotIndex = path.indexOf('.')
|
|
|
|
|
const routerName = dotIndex > 0 ? path.slice(0, dotIndex) : path
|
|
|
|
|
const procedureName = dotIndex > 0 ? path.slice(dotIndex + 1) : path
|
|
|
|
|
|
|
|
|
|
// Convert procedure path to readable action (e.g., "evaluation.submit" → "EVALUATION_SUBMIT")
|
|
|
|
|
const action = path.replace(/\./g, '_').replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()
|
|
|
|
|
|
|
|
|
|
// Get and sanitize the raw input
|
|
|
|
|
let sanitizedInput: unknown = undefined
|
|
|
|
|
try {
|
|
|
|
|
const rawInput = await getRawInput()
|
|
|
|
|
if (rawInput !== undefined) {
|
|
|
|
|
sanitizedInput = sanitizeInput(rawInput)
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// getRawInput can fail if input was already consumed; ignore
|
2026-02-14 15:26:42 +01:00
|
|
|
}
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
|
|
|
|
|
// Capitalize first letter of router name for entityType
|
|
|
|
|
const entityType = routerName.charAt(0).toUpperCase() + routerName.slice(1)
|
|
|
|
|
|
|
|
|
|
// Try to extract entityId from common input patterns
|
|
|
|
|
const inputObj = (typeof sanitizedInput === 'object' && sanitizedInput !== null)
|
|
|
|
|
? sanitizedInput as Record<string, unknown>
|
|
|
|
|
: undefined
|
|
|
|
|
const entityId = inputObj?.id ?? inputObj?.userId ?? inputObj?.projectId ??
|
|
|
|
|
inputObj?.roundId ?? inputObj?.competitionId ?? inputObj?.editionId ??
|
|
|
|
|
inputObj?.targetUserId ?? inputObj?.sessionId ?? inputObj?.awardId
|
|
|
|
|
|
|
|
|
|
await ctx.prisma.auditLog.create({
|
|
|
|
|
data: {
|
|
|
|
|
userId: user.id,
|
|
|
|
|
action,
|
|
|
|
|
entityType,
|
|
|
|
|
entityId: entityId ? String(entityId) : undefined,
|
|
|
|
|
detailsJson: {
|
|
|
|
|
procedure: path,
|
|
|
|
|
procedureName,
|
|
|
|
|
role: user.role,
|
|
|
|
|
roles: user.roles,
|
|
|
|
|
input: sanitizedInput,
|
|
|
|
|
} as Prisma.InputJsonValue,
|
|
|
|
|
ipAddress: ctx.ip,
|
|
|
|
|
userAgent: ctx.userAgent,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Never break the calling operation on audit failure
|
|
|
|
|
console.error('[MutationAudit] Failed to log:', path, error)
|
2026-02-14 15:26:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Procedure Types
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Protected procedure - requires authenticated user
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* Mutations are automatically audit-logged for non-SUPER_ADMIN users.
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
export const protectedProcedure = t.procedure.use(isAuthenticated).use(withMutationAudit)
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Admin procedure - requires SUPER_ADMIN or PROGRAM_ADMIN role
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* PROGRAM_ADMIN mutations are audit-logged; SUPER_ADMIN mutations are skipped.
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
export const adminProcedure = t.procedure
|
|
|
|
|
.use(hasRole('SUPER_ADMIN', 'PROGRAM_ADMIN'))
|
|
|
|
|
.use(withMutationAudit)
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Super admin procedure - requires SUPER_ADMIN role
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* No automatic mutation audit (super admins have manual audit trail).
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
|
|
|
|
export const superAdminProcedure = t.procedure.use(hasRole('SUPER_ADMIN'))
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Jury procedure - requires JURY_MEMBER role
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* All mutations are automatically audit-logged.
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
export const juryProcedure = t.procedure.use(hasRole('JURY_MEMBER')).use(withMutationAudit)
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mentor procedure - requires MENTOR role (or admin)
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* MENTOR and PROGRAM_ADMIN mutations are audit-logged.
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
export const mentorProcedure = t.procedure
|
|
|
|
|
.use(hasRole('SUPER_ADMIN', 'PROGRAM_ADMIN', 'MENTOR'))
|
|
|
|
|
.use(withMutationAudit)
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Observer procedure - requires OBSERVER role (read-only access)
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* Mutations (if any) are audit-logged for OBSERVER and PROGRAM_ADMIN.
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
export const observerProcedure = t.procedure
|
|
|
|
|
.use(hasRole('SUPER_ADMIN', 'PROGRAM_ADMIN', 'OBSERVER'))
|
|
|
|
|
.use(withMutationAudit)
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Award master procedure - requires AWARD_MASTER role (or admin)
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* AWARD_MASTER and PROGRAM_ADMIN mutations are audit-logged.
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
export const awardMasterProcedure = t.procedure
|
|
|
|
|
.use(hasRole('SUPER_ADMIN', 'PROGRAM_ADMIN', 'AWARD_MASTER'))
|
|
|
|
|
.use(withMutationAudit)
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Audience procedure - requires any authenticated user
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
* All mutations are automatically audit-logged.
|
2026-02-14 15:26:42 +01:00
|
|
|
*/
|
feat: automatic mutation audit logging for all non-super-admin users
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>
2026-03-04 18:04:52 +01:00
|
|
|
export const audienceProcedure = t.procedure.use(isAuthenticated).use(withMutationAudit)
|