feat(security): implement comprehensive security event logging with real-time alerting and admin endpoints (Closes #173)#183
Conversation
…, real-time alerting, and admin endpoints (Closes vallabhatech#173)
|
@SakethSumanBathini is attempting to deploy a commit to the vallabhatech's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Review limit reached
More reviews will be available in 40 minutes and 28 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughAdds a comprehensive security event logging subsystem to the Express server. Introduces shared event/severity constants, a ChangesSecurity Event Logging Pipeline
Sequence Diagram(s)sequenceDiagram
participant Client
participant authMiddleware
participant LoginRoute as POST /api/auth/login
participant logSecurityEvent
participant detectSuspiciousLogin
participant SecurityLog as MongoDB SecurityLog
participant Winston
Client->>authMiddleware: request with Authorization header
alt token missing or invalid
authMiddleware->>logSecurityEvent: AUTH_TOKEN_REJECTED (warning)
logSecurityEvent->>Winston: write log entry
logSecurityEvent->>SecurityLog: save document
authMiddleware-->>Client: 401
else token valid
authMiddleware->>LoginRoute: next()
end
LoginRoute->>LoginRoute: validate credentials
alt auth failure
LoginRoute->>logSecurityEvent: AUTH_LOGIN_FAILURE (warning)
logSecurityEvent->>SecurityLog: save document
LoginRoute->>detectSuspiciousLogin: (email, req)
detectSuspiciousLogin->>SecurityLog: count AUTH_LOGIN_FAILURE for IP in window
alt count >= threshold and no duplicate alert
detectSuspiciousLogin->>logSecurityEvent: SUSPICIOUS_LOGIN_PATTERN (critical)
logSecurityEvent->>SecurityLog: save critical document
end
LoginRoute-->>Client: 401
else auth success
LoginRoute->>logSecurityEvent: AUTH_LOGIN_SUCCESS (info)
logSecurityEvent->>SecurityLog: save document
LoginRoute-->>Client: 200 + JWT
end
sequenceDiagram
participant AdminClient
participant authMiddleware
participant adminMiddleware
participant SecurityRouter as GET /api/security/logs or /stats
participant SecurityLog as MongoDB SecurityLog
AdminClient->>authMiddleware: GET /api/security/...
authMiddleware->>adminMiddleware: next() if token valid
alt not admin
adminMiddleware->>logSecurityEvent: ADMIN_ACCESS_DENIED (warning)
adminMiddleware-->>AdminClient: 403
else admin
adminMiddleware->>SecurityRouter: next()
alt GET /logs
SecurityRouter->>SecurityLog: find(filter).sort.skip.limit.populate + countDocuments
SecurityLog-->>SecurityRouter: logs, total
SecurityRouter-->>AdminClient: logs, page, limit, total, totalPages
else GET /stats
SecurityRouter->>SecurityLog: parallel aggregations (eventType, severity, topIPs, critical, total)
SecurityLog-->>SecurityRouter: aggregated metrics
SecurityRouter-->>AdminClient: byEventType, bySeverity, topIPs, recentCritical, total, windowHours, since
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/index.js`:
- Around line 9-11: The hard-coded app.set('trust proxy', 1) on line 11 should
be made environment-driven to accommodate different deployment topologies and
prevent IP spoofing in non-proxied environments. Replace the hard-coded call
with a conditional check of an environment variable (such as TRUST_PROXY) that
determines whether to enable proxy trust. Set the trust proxy value based on
this environment variable, defaulting to a safe value for local development (0
or false), and follow the existing pattern used throughout the codebase for
environment-driven configuration as documented in .env.example.
In `@server/models/SecurityLog.js`:
- Around line 60-67: The SecurityLogSchema indexes do not include compound
indexes for the email and createdAt filtering patterns used by the
/api/security/logs listing endpoint. Add two new indexes to the
SecurityLogSchema: one for the email field combined with createdAt in descending
order (to support email filtering with newest-first sorting), and one for the ip
field combined with createdAt in descending order (to optimize ip-based
filtering without requiring the eventType field). These indexes should be added
after the existing index definitions to ensure efficient query execution for the
admin listing routes.
In `@server/routes/auth.js`:
- Around line 314-316: The updatedFields array currently includes all fields
present in req.body without verifying if they actually changed in the database,
leading to inaccurate audit metadata. Create a snapshot of the original field
values before performing updates, then compare each field against this snapshot
to build updatedFields only with fields that have genuinely changed. Apply this
comparison logic in the section where updatedFields is currently being filtered
(lines 314-326) to ensure only truly modified fields are tracked for audit
purposes.
In `@server/routes/security.js`:
- Around line 17-19: The `page` parameter in the security.js route has no upper
bound, allowing users to request arbitrarily large skip offsets that force
expensive MongoDB scans. Cap the `page` variable using `Math.min()` to establish
a reasonable maximum page number (similar to how `limit` is already capped at
100). This will prevent unbounded offset pagination and ensure the skip
calculation never produces excessively large values that degrade performance as
the SecurityLog collection grows.
In `@server/utils/securityLogger.js`:
- Around line 65-66: The FAILED_LOGIN_THRESHOLD and FAILED_LOGIN_WINDOW_MIN
constants accept invalid environment values like negative numbers, zero, and
non-integers which can break the security detection. Create a helper function or
validation logic that checks if the Number conversion results in a valid
positive integer (greater than or equal to 1), and only use the environment
value if it passes validation; otherwise fall back to the default value. Apply
this validation approach to both the FAILED_LOGIN_THRESHOLD and
FAILED_LOGIN_WINDOW_MIN constant assignments to ensure they always contain safe,
positive integer values.
- Around line 117-133: The SecurityLog.create() call in the try block has no
timeout guard, allowing slow MongoDB operations to block request handling on
auth/admin paths. Wrap the SecurityLog.create() operation with a timeout
mechanism (such as Promise.race with a timeout promise) so that logSecurityEvent
resolves within a bounded time window, and add a fallback path (such as logging
to a fallback store or in-memory queue) when the timeout is exceeded to ensure
audit logging doesn't stall response delivery during database slowdowns.
- Around line 161-167: The de-duplication check in the SecurityLog.exists call
followed by a subsequent insert is race-prone because concurrent requests from
the same IP can both pass the exists check before either has inserted, violating
the once-per-window contract. Replace the separate exists check and insert
operations with a single atomic database operation such as findOneAndUpdate with
the upsert option enabled, using a unique key that combines eventType, ip, and
the createdAt window (e.g., windowStart) to ensure only one
SUSPICIOUS_LOGIN_PATTERN alert is created per IP per window boundary.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 94804a2c-170b-4050-809a-c18f4740c742
⛔ Files ignored due to path filters (1)
server/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (11)
server/.env.exampleserver/README.mdserver/index.jsserver/middleware/adminMiddleware.jsserver/middleware/authMiddleware.jsserver/models/SecurityLog.jsserver/package.jsonserver/routes/auth.jsserver/routes/security.jsserver/utils/securityEvents.jsserver/utils/securityLogger.js
…dexes, accurate updatedFields, pagination cap, env validation, bounded DB latency
|
|
Hi @vallabhatech — the follow-up commit (0a15c8e) addresses all 6 actionable CodeRabbit findings: env-driven trust proxy, two extra MongoDB indexes, accurate updatedFields diff, deep-pagination guard, env int-validation, and bounded DB write latency. SonarCloud Quality Gate passed, Semgrep clean, CI build passing. Ready for your review whenever you have a moment! |



Summary
Implements end-to-end security event logging and real-time alerting for CareSync's backend.
Previously, every security-relevant event (failed logins, token rejections, profile updates, unauthorized admin access) happened silently — only
console.erroron failure, nothing persisted, no pattern detection. This PR closes that gap entirely.Closes #173
What was built
New files (5)
server/models/SecurityLog.jseventType,severity,user,email,ip,userAgent,method,path,statusCode,message,metadata,createdAt. 4 indexes for efficient admin queries.server/utils/securityEvents.jsEVENT_TYPES(9 events) andSEVERITY(info/warning/critical). Single source of truth used by every file so event names never drift.server/utils/securityLogger.jsSECURITY_LOG_TO_FILE=true).logSecurityEvent()persists to DB + emits a real-time console line.detectSuspiciousLogin()counts failed logins from an IP in a rolling window and raises a de-duplicatedCRITICALalert at threshold. Never throws — a logging failure can never break a request.server/middleware/adminMiddleware.jsauthMiddleware, checksuser.role === 'admin', logsADMIN_ACCESS_DENIEDwith WARNING severity if not, returns 403.server/routes/security.jsGET /api/security/logs(paginated, filterable log) andGET /api/security/stats(aggregated overview with brute-force IP table and recent critical events).Modified files (7)
server/routes/auth.jsemailChanged). All responses byte-identical to original.server/middleware/authMiddleware.jsserver/index.jsapp.set('trust proxy', 1)(accurate client IP behind Vercel/Render) + mount `GETserver/package.jsonwinston ^3.19.0server/package-lock.jsonnpm installserver/.env.exampleserver/README.mdEvents logged
AUTH_REGISTER_SUCCESSAUTH_REGISTER_FAILUREAUTH_LOGIN_SUCCESSAUTH_LOGIN_FAILUREAUTH_TOKEN_REJECTEDAUTH_PROFILE_UPDATEDAUTH_PROFILE_UPDATE_FAILUREADMIN_ACCESS_DENIEDSUSPICIOUS_LOGIN_PATTERNPasswords are never logged.
Real-time alerting
CRITICAL SUSPICIOUS_LOGIN_PATTERNevent is raised — de-duplicated so the same IP only alerts once per window, not on every subsequent attempt.Admin API
Both routes require
Authorization: Bearer <token>from anadmin-role user.GET /api/security/logsFilterable, paginated event log. Query params:
page,limit(max 100),eventType,severity,ip,email,from,to.GET /api/security/stats?hours=24Aggregated dashboard: total events, counts by event type and severity, top 10 IPs by failed-login count, and 20 most recent critical events.
Verification
node --checkrequire()-load cleanly (no missing import errors)auth.js: every original response (status + body) byte-identicalauthMiddleware.js+index.js: purely additive diffsChecklist
mainnpm installrun inserver/—package-lock.jsonupdatedserver/.env.exampleandserver/README.mdSummary by CodeRabbit
New Features
Documentation