Skip to content

feat(security): implement comprehensive security event logging with real-time alerting and admin endpoints (Closes #173)#183

Open
SakethSumanBathini wants to merge 2 commits into
vallabhatech:mainfrom
SakethSumanBathini:feat/security-event-logging
Open

feat(security): implement comprehensive security event logging with real-time alerting and admin endpoints (Closes #173)#183
SakethSumanBathini wants to merge 2 commits into
vallabhatech:mainfrom
SakethSumanBathini:feat/security-event-logging

Conversation

@SakethSumanBathini

@SakethSumanBathini SakethSumanBathini commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

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.error on failure, nothing persisted, no pattern detection. This PR closes that gap entirely.

Closes #173


What was built

New files (5)

File Purpose
server/models/SecurityLog.js Mongoose model — stores every security event with eventType, severity, user, email, ip, userAgent, method, path, statusCode, message, metadata, createdAt. 4 indexes for efficient admin queries.
server/utils/securityEvents.js Shared constants — EVENT_TYPES (9 events) and SEVERITY (info/warning/critical). Single source of truth used by every file so event names never drift.
server/utils/securityLogger.js Core logger — winston console transport (always on) + optional file transport (SECURITY_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-duplicated CRITICAL alert at threshold. Never throws — a logging failure can never break a request.
server/middleware/adminMiddleware.js Admin guard — runs after authMiddleware, checks user.role === 'admin', logs ADMIN_ACCESS_DENIED with WARNING severity if not, returns 403.
server/routes/security.js Admin-only endpoints (auth + admin required): GET /api/security/logs (paginated, filterable log) and GET /api/security/stats (aggregated overview with brute-force IP table and recent critical events).

Modified files (7)

File Change
server/routes/auth.js Log every outcome of register, login, and profile update (success + every failure path). +143 lines, -1 (behavior-preserving refactor of emailChanged). All responses byte-identical to original.
server/middleware/authMiddleware.js Log all 3 token-rejection paths: missing header, user-not-found, invalid/expired token. Purely additive.
server/index.js app.set('trust proxy', 1) (accurate client IP behind Vercel/Render) + mount `GET
server/package.json Added winston ^3.19.0
server/package-lock.json Updated by npm install
server/.env.example Documented 4 optional security config vars
server/README.md New "Security Event Logging" section: event table, alerting explanation, admin endpoint reference, config vars

Events logged

Event Severity When
AUTH_REGISTER_SUCCESS info Account created
AUTH_REGISTER_FAILURE warning Missing fields / invalid email / duplicate email / server error
AUTH_LOGIN_SUCCESS info Successful login
AUTH_LOGIN_FAILURE warning Missing fields / unknown account / wrong password / server error
AUTH_TOKEN_REJECTED warning Missing header / user deleted / invalid or expired token
AUTH_PROFILE_UPDATED info Profile saved (records which fields changed)
AUTH_PROFILE_UPDATE_FAILURE warning Profile update rejected or errored
ADMIN_ACCESS_DENIED warning Non-admin reaches an admin-only endpoint
SUSPICIOUS_LOGIN_PATTERN critical IP exceeds failed-login threshold in rolling window (brute-force detection)

Passwords are never logged.


Real-time alerting

  • Every event appears immediately on the server console via winston (visible in Vercel/Render logs).
  • After each failed login, the IP's failure count is checked against a rolling window. When it crosses the threshold a single CRITICAL SUSPICIOUS_LOGIN_PATTERN event is raised — de-duplicated so the same IP only alerts once per window, not on every subsequent attempt.
  • Defaults: 5 failures / 15-minute window. Tunable via env vars without code changes.

Admin API

Both routes require Authorization: Bearer <token> from an admin-role user.

GET /api/security/logs
Filterable, paginated event log. Query params: page, limit (max 100), eventType, severity, ip, email, from, to.

GET /api/security/stats?hours=24
Aggregated dashboard: total events, counts by event type and severity, top 10 IPs by failed-login count, and 20 most recent critical events.


Verification

Check Result
All 8 JS files pass node --check
All modules require()-load cleanly (no missing import errors)
12/12 functional assertions pass (shape, IP/UA capture, threshold detection, de-dupe, error-swallowing)
auth.js: every original response (status + body) byte-identical
authMiddleware.js + index.js: purely additive diffs
Logger never throws even when DB is down
No existing route behavior changed
No passwords or secrets captured in logs

Checklist

  • Assigned before opening this PR
  • Branch based on main
  • npm install run in server/package-lock.json updated
  • All new env vars documented in server/.env.example and server/README.md

Summary by CodeRabbit

  • New Features

    • Added comprehensive security event logging to track authentication and account activities
    • Implemented automatic suspicious login pattern detection with configurable thresholds
    • Added admin-only endpoints to view paginated security logs and dashboard statistics with filters
    • Optional security event file logging capability
  • Documentation

    • Added Security Event Logging configuration guide and environment variable documentation

@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

@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.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@SakethSumanBathini, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1fa61e98-82fb-45d4-9969-30036f946e3f

📥 Commits

Reviewing files that changed from the base of the PR and between 3ee0577 and 0a15c8e.

📒 Files selected for processing (7)
  • server/.env.example
  • server/README.md
  • server/index.js
  • server/models/SecurityLog.js
  • server/routes/auth.js
  • server/routes/security.js
  • server/utils/securityLogger.js
📝 Walkthrough

Walkthrough

Adds a comprehensive security event logging subsystem to the Express server. Introduces shared event/severity constants, a SecurityLog Mongoose model with compound indexes, a Winston-backed logger with brute-force detection and deduplication, instrumented authMiddleware and new adminMiddleware, structured logging across auth routes, and admin-only /api/security endpoints for paginated logs and aggregated stats.

Changes

Security Event Logging Pipeline

Layer / File(s) Summary
Event constants and SecurityLog schema
server/utils/securityEvents.js, server/models/SecurityLog.js, server/package.json
Defines frozen EVENT_TYPES and SEVERITY constants with derived value arrays. Introduces SecurityLogSchema with enumerated eventType/severity, optional user ObjectId linkage, request context fields, flexible metadata, and compound indexes for newest-first listing, event-type filtering, severity dashboards, and per-IP brute-force detection. Adds winston dependency.
Winston logger and suspicious-login detection
server/utils/securityLogger.js
Configures Winston with always-on console transport and optional file transport via SECURITY_LOG_TO_FILE. Implements logSecurityEvent (non-throwing async, persists to Winston and MongoDB) and detectSuspiciousLogin (rolling-window IP failure count with deduplication before emitting a SUSPICIOUS_LOGIN_PATTERN critical event). Exports helper functions and configured threshold/window constants.
Admin and auth middleware instrumentation
server/middleware/adminMiddleware.js, server/middleware/authMiddleware.js
Adds adminMiddleware that checks req.user.role === 'admin' and emits ADMIN_ACCESS_DENIED warning before returning 403. Extends authMiddleware to call logSecurityEvent for missing token, nonexistent user, and catch-block failures, each mapped to AUTH_TOKEN_REJECTED warning events.
Auth route security event instrumentation
server/routes/auth.js
Instruments register, login, and profile-update routes with structured security logs. Register logs warnings for validation failures and duplicate email, plus an info event on success. Login logs warnings for validation failures, logs auth failure reason and calls detectSuspiciousLogin, and emits info on success. Profile update logs a 404 warning, an info event with updatedFields/emailChanged on success, and a categorized failure in the catch handler.
Admin security API endpoints and server wiring
server/routes/security.js, server/index.js
Adds a security router with authMiddleware + adminMiddleware applied globally. GET /api/security/logs returns paginated, filtered, populated results with totalPages. GET /api/security/stats runs concurrent MongoDB aggregations for event-type/severity counts, top-IPs for AUTH_LOGIN_FAILURE, recent critical events, and total count within a bounded time window. Registers the router at /api/security and sets trust proxy in index.js.
Environment config and documentation
server/.env.example, server/README.md
Adds SECURITY_FAILED_LOGIN_THRESHOLD, SECURITY_FAILED_LOGIN_WINDOW_MIN, SECURITY_LOG_TO_FILE, and SECURITY_LOG_DIR to .env.example. Adds a Security Event Logging section to README.md documenting event types, severities, deduplication behavior, optional file logging, admin endpoints, and configuration variables.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • vallabhatech/CareSync#54: Implements the JWT authentication middleware that this PR extends with structured security event logging for token rejection, user-not-found, and exception cases.

Suggested reviewers

  • vallabhatech

🐇 Hoppity hop, the logs now flow,
Each failed login gets a warning glow,
Brute-force attempts? A CRITICAL shout!
Admins can query what it's all about.
From Winston's console to Mongo's store,
This bunny's burrow is secure once more! 🔒

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main feature: implementing comprehensive security event logging with real-time alerting and admin endpoints.
Linked Issues check ✅ Passed All requirements from issue #173 are met: comprehensive logging of security events, real-time alerting via console output, persistence to MongoDB, admin endpoints for filtered queries and statistics, and pattern detection for suspicious logins.
Out of Scope Changes check ✅ Passed All changes directly support the security event logging feature and its requirements; no unrelated modifications detected.
Description check ✅ Passed The PR objectives comprehensively document the implementation details, architecture, and fulfillment of all linked issue requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 636917d and 3ee0577.

⛔ Files ignored due to path filters (1)
  • server/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • server/.env.example
  • server/README.md
  • server/index.js
  • server/middleware/adminMiddleware.js
  • server/middleware/authMiddleware.js
  • server/models/SecurityLog.js
  • server/package.json
  • server/routes/auth.js
  • server/routes/security.js
  • server/utils/securityEvents.js
  • server/utils/securityLogger.js

Comment thread server/index.js Outdated
Comment thread server/models/SecurityLog.js
Comment thread server/routes/auth.js Outdated
Comment thread server/routes/security.js
Comment thread server/utils/securityLogger.js Outdated
Comment thread server/utils/securityLogger.js
Comment thread server/utils/securityLogger.js
…dexes, accurate updatedFields, pagination cap, env validation, bounded DB latency
@sonarqubecloud

Copy link
Copy Markdown

@SakethSumanBathini

Copy link
Copy Markdown
Contributor Author

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security Event Logging

1 participant