feat: error audit middleware, impersonation attribution, account lockout logging
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m13s
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m13s
- Add withErrorAudit middleware tracking FORBIDDEN/UNAUTHORIZED/NOT_FOUND per user - Fix impersonation attribution: log real admin ID, prefix IMPERSONATED_ on actions - Add ACCOUNT_LOCKED audit events on login lockout (distinct from LOGIN_FAILED) - Audit export of assignments and audit logs (meta-audit gap) - Update audit page UI with new security event types and colors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -155,7 +155,8 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
// Track failed attempt (don't reveal whether user exists)
|
||||
const current = failedAttempts.get(email) || { count: 0, lockedUntil: 0 }
|
||||
current.count++
|
||||
if (current.count >= MAX_LOGIN_ATTEMPTS) {
|
||||
const wasLocked = current.count >= MAX_LOGIN_ATTEMPTS
|
||||
if (wasLocked) {
|
||||
current.lockedUntil = Date.now() + LOCKOUT_DURATION_MS
|
||||
current.count = 0
|
||||
}
|
||||
@@ -171,6 +172,22 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
},
|
||||
}).catch(() => {})
|
||||
|
||||
// Log account lockout as a distinct security event
|
||||
if (wasLocked) {
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: null,
|
||||
action: 'ACCOUNT_LOCKED',
|
||||
entityType: 'User',
|
||||
detailsJson: {
|
||||
email,
|
||||
reason: 'max_failed_attempts',
|
||||
lockoutDurationMinutes: LOCKOUT_DURATION_MS / 60000,
|
||||
},
|
||||
},
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -185,7 +202,8 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
// Track failed attempt
|
||||
const current = failedAttempts.get(email) || { count: 0, lockedUntil: 0 }
|
||||
current.count++
|
||||
if (current.count >= MAX_LOGIN_ATTEMPTS) {
|
||||
const wasLocked = current.count >= MAX_LOGIN_ATTEMPTS
|
||||
if (wasLocked) {
|
||||
current.lockedUntil = Date.now() + LOCKOUT_DURATION_MS
|
||||
current.count = 0
|
||||
}
|
||||
@@ -202,6 +220,23 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
},
|
||||
}).catch(() => {})
|
||||
|
||||
// Log account lockout as a distinct security event
|
||||
if (wasLocked) {
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
action: 'ACCOUNT_LOCKED',
|
||||
entityType: 'User',
|
||||
entityId: user.id,
|
||||
detailsJson: {
|
||||
email,
|
||||
reason: 'max_failed_attempts',
|
||||
lockoutDurationMinutes: LOCKOUT_DURATION_MS / 60000,
|
||||
},
|
||||
},
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user