Skip to content

Conversation

@dblythy
Copy link
Member

@dblythy dblythy commented Oct 1, 2025

Pull Request

Issue

Closes: #5378

Approach

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check
  • Add new Parse Error codes to Parse JS SDK

Summary by CodeRabbit

Release Notes

  • New Features
    • Added GDPR-compliant audit logging system that automatically tracks user authentication, data operations (create, read, update, delete), ACL modifications, schema changes, and push notifications with daily log rotation, sensitive data masking, IP address tracking, and configurable filtering options.

@parse-github-assistant
Copy link

🚀 Thanks for opening this pull request!

@coderabbitai
Copy link

coderabbitai bot commented Oct 1, 2025

📝 Walkthrough

Walkthrough

This PR introduces comprehensive GDPR-compliant audit logging for Parse Server. It adds infrastructure to log user authentication, data access, data modifications, ACL changes, schema modifications, and push notifications. The implementation includes a Winston-based daily-rotating file adapter, configurable event filtering, TypeScript-based event types, and integration points across multiple routers to capture relevant operations.

Changes

Cohort / File(s) Change Summary
Documentation & Configuration
GDPR_COMPLIANCE_GUIDE.md, src/Options/Definitions.js, src/Options/index.js, src/Options/docs.js, resources/buildConfigDefinitions.js
Introduces GDPR compliance guide with audit logging patterns, code examples, and organizational guidance. Adds new auditLog configuration option with nested AuditLogOptions, AuditLogFilterOptions, and WinstonFileAuditLogAdapterOptions types for controlling adapter behavior, event filtering, and log retention.
Core Audit Logging Infrastructure
src/Adapters/Logger/AuditLogger.js, src/Adapters/Logger/AuditLogAdapter.js
Implements base audit logger using Winston with daily log rotation and AuditLogAdapter class that routes audit events through the logger with methods for each event type (login, data access/modify, schema/ACL changes).
TypeScript Adapter & Filter Interfaces
src/Adapters/AuditLog/AuditLogAdapterInterface.ts, src/Adapters/AuditLog/AuditLogFilter.ts, src/Adapters/AuditLog/index.ts
Defines strongly-typed audit event interfaces (UserLoginEvent, DataViewEvent, DataCreate/Update/DeleteEvent, ACLModifyEvent, SchemaModifyEvent, PushSendEvent) and AuditLogAdapterInterface abstract contract. Implements AuditLogFilter with multi-stage filtering pipeline for event types, class names, master-key exclusion, and roles.
Winston File Adapter Implementation
src/Adapters/AuditLog/WinstonFileAuditLogAdapter.ts
Concrete implementation of audit log adapter using Winston with daily-rotating file transport, folder creation, and masking of sensitive fields in logged events.
Audit Log Controller
src/Controllers/AuditLogController.ts, src/Controllers/index.js
Introduces AuditLogController extending AdaptableController with public methods for each event type, IP extraction, user context derivation, sensitive data masking, and configurable filtering. Exports getAuditLogController factory function.
Router & Query Integration
src/RestQuery.js, src/RestWrite.js, src/Routers/UsersRouter.js, src/Routers/PushRouter.js, src/Routers/SchemasRouter.js, src/rest.js
Integrates audit logging into data access (RestQuery), write operations (RestCreate/Update/Delete), user authentication (login/loginAs), push sending, schema modifications, and data deletion; includes error handling to prevent audit failures from affecting main operations.
Comprehensive Test Suite
spec/AuditLogAdapter.spec.js, spec/AuditLogController.spec.js, spec/AuditLogSchemas.spec.js, spec/AuditLogging.e2e.spec.js, spec/Auth.spec.js, spec/ParseObject.spec.js, spec/ParseQuery.spec.js, spec/AuditLogFilter.spec.js
End-to-end and unit tests validating adapter initialization, controller behavior, event logging for schema/auth/CRUD/query operations, log file management, masking, filtering, and concurrent operation handling.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/Client
    participant Parse as Parse Server<br/>(Router)
    participant Audit as AuditLogController
    participant Filter as AuditLogFilter
    participant Adapter as WinstonFileAuditLogAdapter
    participant Logger as Winston Logger
    participant FS as File System

    User->>Parse: Login / Data Access / Write
    Parse->>Parse: Process Operation
    Parse->>Audit: log[EventType](params)
    Audit->>Audit: Build AuditEvent
    Audit->>Filter: shouldLog(event)
    Filter->>Filter: Check eventType, class,<br/>masterKey, roles, custom
    Filter-->>Audit: true/false
    alt shouldLog() returns true
        Audit->>Adapter: log[EventType](event)
        Adapter->>Adapter: maskSensitiveData(event)
        Adapter->>Logger: logger.info('audit_event',<br/>auditEntry)
        Logger->>FS: Write to daily rotation file<br/>(parse-server-audit-YYYY-MM-DD.log)
        FS-->>Logger: ✓ Written
        Logger-->>Adapter: ✓ Complete
        Adapter-->>Audit: Promise resolved
        Audit-->>Parse: (fire-and-forget)
    end
    Parse-->>User: Return Response
    Note over Parse: Audit logging errors<br/>do not affect main flow
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Complexity factors:

    • Large scope: ~30 files modified/created across multiple subsystems
    • Heterogeneous changes: New adapter infrastructure, controller logic, integration points across 6+ routers, TypeScript interfaces, filtering pipeline, masking logic
    • High logic density: Multi-stage filtering (event type, class, master-key, roles, custom filter), sensitive data masking, error propagation in fire-and-forget patterns
  • Areas requiring extra attention:

    • AuditLogFilter filtering logic (src/Adapters/AuditLog/AuditLogFilter.ts): Verify correct precedence and behavior across all filter combinations (event type whitelist, include/exclude class filtering, master-key exclusion, role whitelisting/blacklisting, custom filter fail-open semantics)
    • Masking implementation (across AuditLogController and WinstonFileAuditLogAdapter): Confirm sensitive fields (sessionToken, authData, passwords) are consistently masked across all event types
    • Router integration points (UsersRouter.js, RestWrite.js, PushRouter.js, SchemasRouter.js, RestQuery.js): Verify audit logging does not introduce race conditions, exception handling is robust, and fire-and-forget promises do not mask errors in tests
    • Type safety (src/Adapters/AuditLog/*): Ensure TypeScript event interfaces align across adapter, controller, and router usage
    • Test coverage: Validate that e2e tests adequately cover concurrent operations, file rotation edge cases, and filter precedence scenarios

Suggested reviewers

  • @mtrezza

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description includes the required template structure with linked issue #5378 explicitly referenced, but the 'Approach' section explaining the implementation details is empty. Fill in the 'Approach' section with a brief summary of how GDPR compliance is implemented (e.g., audit logging infrastructure, adapter pattern, event types covered).
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feature: add GDPR compliance' clearly summarizes the main change—introducing GDPR audit logging infrastructure.
Linked Issues check ✅ Passed The PR substantially implements all requirements from #5378: logging user login, data access/views, data manipulation (CRUD), schema changes, ACL modifications, and push events via a configurable audit logging adapter with separate file storage.
Out of Scope Changes check ✅ Passed All changes are within scope of GDPR compliance logging: new audit adapters, controllers, filters, type definitions, and integration into existing routers (RestQuery, RestWrite, UsersRouter, etc.) for event capture, plus comprehensive tests and documentation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.39.7)
spec/ParseQuery.spec.js

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.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Oct 1, 2025

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 389de84 and 4ba4916.

📒 Files selected for processing (19)
  • GDPR_COMPLIANCE_GUIDE.md (1 hunks)
  • spec/AuditLogAdapter.spec.js (1 hunks)
  • spec/AuditLogController.spec.js (1 hunks)
  • spec/AuditLogSchemas.spec.js (1 hunks)
  • spec/AuditLogging.e2e.spec.js (1 hunks)
  • spec/Auth.spec.js (1 hunks)
  • spec/ParseObject.spec.js (1 hunks)
  • spec/ParseQuery.spec.js (1 hunks)
  • src/Adapters/Logger/AuditLogAdapter.js (1 hunks)
  • src/Adapters/Logger/AuditLogger.js (1 hunks)
  • src/Controllers/AuditLogController.js (1 hunks)
  • src/Controllers/index.js (5 hunks)
  • src/Options/Definitions.js (2 hunks)
  • src/Options/index.js (2 hunks)
  • src/RestQuery.js (2 hunks)
  • src/RestWrite.js (2 hunks)
  • src/Routers/PushRouter.js (2 hunks)
  • src/Routers/SchemasRouter.js (3 hunks)
  • src/Routers/UsersRouter.js (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
src/Controllers/index.js (3)
src/Adapters/Logger/AuditLogger.js (1)
  • options (81-86)
src/Controllers/AuditLogController.js (1)
  • AuditLogController (8-320)
src/Adapters/Logger/AuditLogAdapter.js (1)
  • AuditLogAdapter (9-293)
src/Adapters/Logger/AuditLogAdapter.js (3)
src/Adapters/Logger/LoggerAdapter.js (1)
  • LoggerAdapter (9-18)
src/Adapters/Logger/AuditLogger.js (4)
  • options (81-86)
  • configureAuditLogger (58-89)
  • isAuditLogEnabled (139-141)
  • logAuditEvent (105-133)
src/Controllers/index.js (11)
  • options (62-71)
  • options (85-85)
  • options (104-112)
  • options (125-125)
  • options (133-133)
  • options (153-153)
  • options (163-163)
  • options (164-164)
  • options (184-184)
  • options (196-196)
  • options (234-234)
spec/ParseObject.spec.js (1)
spec/helper.js (6)
  • require (6-6)
  • require (8-8)
  • require (52-52)
  • path (42-42)
  • reconfigureServer (171-205)
  • Parse (4-4)
spec/AuditLogController.spec.js (2)
src/Controllers/AuditLogController.js (1)
  • AuditLogController (8-320)
src/Adapters/Logger/AuditLogAdapter.js (1)
  • AuditLogAdapter (9-293)
spec/AuditLogging.e2e.spec.js (3)
spec/AuditLogAdapter.spec.js (4)
  • fs (5-5)
  • require (4-4)
  • path (6-6)
  • testLogFolder (9-9)
spec/helper.js (1)
  • reconfigureServer (171-205)
spec/ParseQuery.spec.js (8)
  • Parse (7-7)
  • i (374-374)
  • i (599-599)
  • i (2398-2398)
  • i (2658-2658)
  • i (3067-3067)
  • i (3109-3109)
  • request (8-8)
spec/AuditLogSchemas.spec.js (1)
spec/helper.js (1)
  • reconfigureServer (171-205)
src/Controllers/AuditLogController.js (3)
spec/AuditLogController.spec.js (2)
  • AuditLogController (3-3)
  • AuditLogAdapter (4-4)
src/Controllers/index.js (11)
  • options (62-71)
  • options (85-85)
  • options (104-112)
  • options (125-125)
  • options (133-133)
  • options (153-153)
  • options (163-163)
  • options (164-164)
  • options (184-184)
  • options (196-196)
  • options (234-234)
src/Adapters/Logger/AuditLogAdapter.js (1)
  • AuditLogAdapter (9-293)
spec/Auth.spec.js (1)
spec/helper.js (5)
  • require (6-6)
  • require (8-8)
  • require (52-52)
  • reconfigureServer (171-205)
  • Parse (4-4)
src/Routers/PushRouter.js (2)
src/PromiseRouter.js (1)
  • body (141-141)
src/Controllers/index.js (1)
  • pushController (213-213)
src/Adapters/Logger/AuditLogger.js (1)
src/Controllers/index.js (11)
  • options (62-71)
  • options (85-85)
  • options (104-112)
  • options (125-125)
  • options (133-133)
  • options (153-153)
  • options (163-163)
  • options (164-164)
  • options (184-184)
  • options (196-196)
  • options (234-234)
src/Options/Definitions.js (1)
resources/buildConfigDefinitions.js (1)
  • parsers (12-12)
src/Routers/UsersRouter.js (1)
src/RestWrite.js (9)
  • user (330-330)
  • user (901-901)
  • user (1514-1514)
  • user (1787-1787)
  • userId (537-537)
  • userId (547-547)
  • RestWrite (988-995)
  • RestWrite (1153-1159)
  • sessionData (1010-1019)
spec/ParseQuery.spec.js (1)
spec/helper.js (6)
  • require (6-6)
  • require (8-8)
  • require (52-52)
  • reconfigureServer (171-205)
  • Parse (4-4)
  • i (324-324)
spec/AuditLogAdapter.spec.js (2)
src/Adapters/Logger/AuditLogAdapter.js (1)
  • AuditLogAdapter (9-293)
src/Adapters/Logger/AuditLogger.js (1)
  • logAuditEvent (105-133)
🪛 Biome (2.1.2)
src/Controllers/index.js

[error] 84-84: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 84-84: return types can only be used in TypeScript files

(parse)

src/Options/index.js

[error] 217-217: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 545-545: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 545-545: Expected a statement but instead found '}'.

Expected a statement here.

(parse)


[error] 548-548: Expected a type but instead found '?'.

Expected a type here.

(parse)


[error] 548-548: Expected a property, or a signature but instead found ';'.

Expected a property, or a signature here.

(parse)


[error] 553-553: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 558-558: Expected a statement but instead found '?'.

Expected a statement here.

(parse)

🪛 GitHub Check: Lint
src/RestQuery.js

[warning] 981-981:
Unexpected console statement

src/RestWrite.js

[warning] 1859-1859:
Unexpected console statement

spec/AuditLogging.e2e.spec.js

[failure] 442-442:
'request' is not defined

spec/AuditLogSchemas.spec.js

[failure] 226-226:
'request' is not defined


[failure] 197-197:
'request' is not defined


[failure] 164-164:
'request' is not defined


[failure] 133-133:
'request' is not defined


[failure] 117-117:
'request' is not defined


[failure] 82-82:
'request' is not defined


[failure] 66-66:
'request' is not defined


[failure] 35-35:
'request' is not defined

spec/Auth.spec.js

[failure] 321-321:
Empty block statement

src/Adapters/Logger/AuditLogger.js

[warning] 77-77:
Unexpected console statement


[warning] 46-46:
Unexpected console statement

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Docker Build
  • GitHub Check: Code Analysis (javascript)
🔇 Additional comments (7)
src/RestWrite.js (2)

157-159: LGTM! Audit logging correctly placed after cleanUserAuthData.

The audit logging is invoked at the right point in the pipeline—after cleanUserAuthData ensures sensitive auth data is cleaned from the response, and before the final response assembly. This prevents logging of raw authentication tokens.


1823-1830: AuditLogController masks sensitive fields in create logs
Confirmed that logDataCreate calls _maskSensitiveData(data), replacing any sensitive values with '***masked***'. No further action needed.

src/Routers/SchemasRouter.js (3)

91-119: LGTM! Schema create audit logging is correctly implemented.

The try/catch wrapper ensures both success and failure scenarios are logged before propagating errors. Logging happens after the operation completes, ensuring the audit trail reflects actual outcomes.


122-163: LGTM! Schema modify audit logging is correctly implemented.

The conversion to async/await with try/catch provides clean error handling, and audit events are emitted for both success and failure paths.


166-210: LGTM! Schema delete audit logging is correctly implemented.

The async/await refactor maintains the existing validation logic while adding comprehensive audit logging for both success and failure scenarios.

src/Routers/UsersRouter.js (2)

359-418: LGTM! Master key login audit logging is well-structured.

The handleLogInAs refactor properly wraps the entire flow in try/catch, ensuring that both successful and failed master key login attempts are audited. The loginMethod is correctly set to 'masterkey' to distinguish this authentication path.


202-342: Audit logging covers all login failure paths. Errors thrown synchronously or via promise rejections in _authenticateUserFromRequest always propagate into handleLogIn’s catch block, ensuring auditLogController.logUserLogin records every failure.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
spec/AuditLogging.e2e.spec.js (2)

53-53: Consider making the wait time configurable or event-driven.

The 500ms hardcoded wait on line 53 (and similar waits throughout the suite) could cause flakiness on slower CI systems or pass prematurely on fast systems. Consider either using a polling approach to check for log completion or making the timeout configurable via environment variable.

Example polling approach:

// Helper function to wait for logs
async function waitForLogs(folder, minLines = 1, timeout = 5000) {
  const start = Date.now();
  while (Date.now() - start < timeout) {
    if (fs.existsSync(folder)) {
      const files = fs.readdirSync(folder);
      if (files.length > 0) {
        const content = fs.readFileSync(path.join(folder, files[0]), 'utf8');
        const lines = content.split('\n').filter(l => l.trim());
        if (lines.length >= minLines) return;
      }
    }
    await new Promise(resolve => setTimeout(resolve, 50));
  }
}

// Usage:
await waitForLogs(testLogFolder, 5);

388-426: Consider making performance threshold configurable.

The 10-second threshold on line 415 provides a baseline performance check, but different CI environments may have vastly different performance characteristics. Consider making this threshold configurable via environment variable or adjusting it based on empirical CI data.

Example:

const perfThreshold = parseInt(process.env.AUDIT_LOG_PERF_THRESHOLD) || 10000;
expect(duration).toBeLessThan(perfThreshold);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ba4916 and 30cb136.

📒 Files selected for processing (10)
  • spec/AuditLogController.spec.js (1 hunks)
  • spec/AuditLogSchemas.spec.js (1 hunks)
  • spec/AuditLogging.e2e.spec.js (1 hunks)
  • spec/Auth.spec.js (2 hunks)
  • spec/ParseObject.spec.js (1 hunks)
  • src/Adapters/Logger/AuditLogger.js (1 hunks)
  • src/Controllers/AuditLogController.js (1 hunks)
  • src/RestQuery.js (2 hunks)
  • src/RestWrite.js (2 hunks)
  • src/Routers/UsersRouter.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Adapters/Logger/AuditLogger.js
  • spec/AuditLogController.spec.js
  • src/Controllers/AuditLogController.js
🧰 Additional context used
🧬 Code graph analysis (5)
src/Routers/UsersRouter.js (2)
src/RestWrite.js (16)
  • user (330-330)
  • user (901-901)
  • user (1514-1514)
  • user (1787-1787)
  • authData (453-453)
  • Auth (8-8)
  • Auth (575-578)
  • validatedAuthData (560-563)
  • res (624-628)
  • RestWrite (988-995)
  • RestWrite (1153-1159)
  • expiresAt (1009-1009)
  • sessionData (1010-1019)
  • error (666-666)
  • userId (537-537)
  • userId (547-547)
src/triggers.js (2)
  • maybeRunTrigger (849-952)
  • error (644-644)
spec/AuditLogSchemas.spec.js (2)
src/RestWrite.js (4)
  • request (821-827)
  • request (956-962)
  • schema (346-346)
  • error (666-666)
spec/helper.js (1)
  • reconfigureServer (171-205)
spec/ParseObject.spec.js (1)
spec/helper.js (6)
  • require (6-6)
  • require (8-8)
  • require (52-52)
  • path (42-42)
  • reconfigureServer (171-205)
  • Parse (4-4)
spec/Auth.spec.js (2)
src/RestWrite.js (3)
  • request (821-827)
  • request (956-962)
  • error (666-666)
spec/helper.js (2)
  • reconfigureServer (171-205)
  • Parse (4-4)
spec/AuditLogging.e2e.spec.js (1)
spec/helper.js (1)
  • reconfigureServer (171-205)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: MongoDB 8, ReplicaSet
  • GitHub Check: MongoDB 7, ReplicaSet
  • GitHub Check: Node 20
  • GitHub Check: MongoDB 6, ReplicaSet
  • GitHub Check: PostgreSQL 16, PostGIS 3.5
  • GitHub Check: PostgreSQL 15, PostGIS 3.4
  • GitHub Check: Docker Build
  • GitHub Check: Code Analysis (javascript)
🔇 Additional comments (34)
spec/Auth.spec.js (6)

3-3: LGTM! Clean test suite setup.

The request import and test suite initialization follow best practices with proper temp folder cleanup in beforeEach/afterEach hooks.

Also applies to: 260-276


277-306: LGTM! Comprehensive successful login test.

The test properly creates a user, performs logout/login, waits for logs to flush, and validates all expected log markers including masking.


308-340: LGTM! Failed login test properly handles errors.

The empty catch block from the previous review has been correctly addressed with explicit error assertions (lines 324-327). The test now validates both the expected authentication failure and the corresponding audit log entry.


342-380: LGTM! loginAs test validates master key flow.

The test properly exercises the loginAs endpoint with master key authentication and verifies the corresponding audit log entries.


382-405: LGTM! IP address capture test.

Clean test validating that client IP addresses are captured in authentication logs.


407-422: LGTM! Negative test for disabled logging.

Important test confirming that audit logging is properly disabled when the auditLog configuration is not present.

spec/ParseObject.spec.js (7)

2176-2197: LGTM! Clean CRUD audit test suite setup.

The test suite initialization follows the established pattern with proper folder cleanup and server reconfiguration.


2199-2222: LGTM! Object creation audit test.

Properly validates that DATA_CREATE events are logged with the expected className and objectId.


2224-2256: LGTM! Object update audit test.

The test properly isolates the update operation by clearing initial creation logs, then verifies DATA_UPDATE logging.


2258-2291: LGTM! Object deletion audit test.

Correctly captures the objectId before deletion and verifies DATA_DELETE logging.


2293-2326: LGTM! ACL modification audit test.

Properly tests that ACL changes are logged separately as ACL_MODIFY events.


2328-2371: LGTM! Masking and user context tests.

These tests validate two critical GDPR requirements: sensitive field masking (password) and user context capture (userId in logs).


2373-2389: LGTM! Master key without user test uses correct pattern.

The test correctly uses the per-operation { useMasterKey: true } option (line 2377) rather than the global Parse.Cloud.useMasterKey() toggle. This validates that internal master-key operations without a user context are properly excluded from audit logs.

src/RestQuery.js (2)

305-307: LGTM! Clean integration of audit logging into query execution.

The logAuditDataView call is properly inserted after handleAuthAdapters and before returning the response, ensuring audit logging doesn't interfere with the main query flow.


952-985: LGTM! logAuditDataView implementation with past issue resolved.

The method properly:

  • Guards against disabled/missing audit controller
  • Skips master operations without user context (GDPR requirement)
  • Limits logged objectIds to 100 (performance consideration)
  • Uses this.config.loggerController.error (line 981) instead of console.error as flagged in the previous review
  • Never fails the query on audit logging errors
src/RestWrite.js (2)

157-159: LGTM! Clean integration of audit logging into write execution.

The logAuditDataWrite call is properly positioned after cleanUserAuthData and before returning the response, mirroring the pattern in RestQuery.


1801-1867: LGTM! logAuditDataWrite implementation with all past issues resolved.

The method correctly:

  • Skips only master operations without user context (line 1808), not maintenance mode - addressing previous consistency concern
  • Detects ACL changes including added/removed cases using ?? null fallback (lines 1821-1823) - addressing previous incomplete detection concern
  • Uses this.config.loggerController.error (line 1863) instead of console.error - addressing previous linting concern
  • Logs create events with full data, update events with changed fields only, and ACL modifications separately
  • Never fails the write on audit logging errors
spec/AuditLogSchemas.spec.js (6)

1-26: LGTM! Imports and setup with past issue resolved.

The request helper import (line 5) that was missing in the previous review has been properly added. The test suite setup follows the established pattern with proper temp folder cleanup.


28-57: LGTM! Schema creation audit test.

Properly tests that schema creation via the API generates SCHEMA_MODIFY audit logs with the create operation marker.


59-108: LGTM! Schema update audit test.

Correctly isolates the update operation and verifies SCHEMA_MODIFY logging with the update operation marker.


110-154: LGTM! Schema deletion audit test.

Properly tests that schema deletion generates SCHEMA_MODIFY audit logs with the delete operation marker.


156-215: LGTM! Failed schema creation and user context tests.

The failed creation test properly handles the expected error for invalid class names and verifies failure logging. The user context test confirms that schema operations include user information in audit logs.


217-246: LGTM! Schema field details test.

Validates that schema modifications include field-level details in the audit logs, providing comprehensive audit trail for schema changes.

src/Routers/UsersRouter.js (4)

318-326: SessionToken masking implemented correctly.

The past review concern about sessionToken leakage has been addressed. Line 320 now masks the sessionToken as '***masked***' before passing it to auditLogController.logUserLogin, preventing credential exposure in audit logs.


329-341: LGTM! Failure audit logging is well-structured.

The catch block correctly logs failed login attempts with error details, extracts username from available sources, determines loginMethod, and rethrows the error to preserve error semantics.


393-402: LGTM! Master-key login audit logging is correct.

The audit log correctly masks sessionToken, sets loginMethod to 'masterkey', includes appropriate username fallbacks, and follows the same pattern as handleLogIn.


405-417: LGTM! Master-key login failure logging is consistent.

The catch block appropriately logs failed loginAs attempts with userId extraction, error details, and correct loginMethod, while preserving error semantics by rethrowing.

spec/AuditLogging.e2e.spec.js (7)

3-5: Import issue resolved.

The missing request import flagged in the previous review has been addressed. Line 5 now correctly imports the request helper.


80-151: LGTM! File management tests are comprehensive.

The test suite thoroughly validates log file naming patterns, automatic folder creation, and custom date patterns. The regex patterns correctly match the expected formats based on configuration.


153-203: LGTM! Configuration tests validate dynamic behavior.

The tests properly verify that audit logging can be disabled and dynamically enabled at runtime, which is important for operational flexibility.


205-290: LGTM! Content validation is thorough and security-focused.

The test suite validates required fields, proper JSON formatting, and critically verifies that sensitive data like passwords and session tokens are masked and never appear in plain text in audit logs.


292-330: LGTM! Concurrent operations test validates thread-safety.

The test appropriately validates that the audit logger handles concurrent operations correctly, ensuring all events are captured without loss or corruption.


332-385: LGTM! Error handling tests ensure audit logging is non-blocking.

The tests appropriately verify that audit logging failures don't prevent core operations from succeeding, and that failed operations are properly logged with error details. This is critical for GDPR compliance without compromising system reliability.


428-463: LGTM! Cross-feature test validates schema audit integration.

The test appropriately validates that schema modifications are audited with correct event types and entities, demonstrating integration beyond user operations as required for comprehensive GDPR compliance.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
GDPR_COMPLIANCE_GUIDE.md (4)

26-26: Scope correction: not “database‑level” logging

Audit events are emitted at the Parse Server request/operation layer (RestQuery/RestWrite/routers), not at the database layer. Please adjust wording to avoid overpromising.

- - Automatic, transparent logging at the database level
+ - Automatic, transparent logging at the request/operation layer in Parse Server

468-476: IP capture in Cloud Code: request.ip may be unavailable

request.ip is not guaranteed in Cloud Code. Prefer reading IP from the inbound request context (e.g., Express req.ip) exposed by Parse Server if supported, or from a trusted header behind your proxy (X‑Forwarded‑For), normalized server‑side. Otherwise omit IP.

-  acceptance.set('ipAddress', request.ip);
+  // If available in your Cloud Code runtime:
+  const ip = (request.headers && request.headers['x-forwarded-for'])
+    ? request.headers['x-forwarded-for'].split(',')[0].trim()
+    : undefined;
+  if (ip) acceptance.set('ipAddress', ip);

Please confirm which fields Parse exposes on request in your deployment.


525-565: Breach detection example: hard‑coded log filename and date

Using a fixed filename (2025-10-01) will break with rotation. Parameterize the folder/pattern and iterate recent files.

-  const logFile = './audit-logs/parse-server-audit-2025-10-01.log';
+  const path = require('path');
+  const folder = process.env.AUDIT_LOG_FOLDER || './audit-logs';
+  const files = fs.readdirSync(folder)
+    .filter(f => f.startsWith('parse-server-audit-') && f.endsWith('.log'))
+    .sort()  // latest last if date in name
+    .slice(-3); // check last N files
+  for (const f of files) {
+    const fileStream = fs.createReadStream(path.join(folder, f));
+    // ... process lines as below
+  }

Also define notifySecurityTeam or make the call path explicit.


639-655: Tamper‑evidence: prefer proven WORM/immutability over “blockchain”

Recommend S3 Object Lock, Azure Immutable Blob, or append‑only/WORM filesystems with retention enforcement instead of suggesting blockchain.

-  - Backup to immutable storage
-  - Consider blockchain for tamper-evidence
+  - Backup to immutable storage (e.g., S3 Object Lock, Azure Immutable Blob)
+  - Enforce WORM/retention policies for tamper‑evidence
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30cb136 and 7bc75b2.

📒 Files selected for processing (1)
  • GDPR_COMPLIANCE_GUIDE.md (1 hunks)
🔇 Additional comments (2)
GDPR_COMPLIANCE_GUIDE.md (2)

433-440: Replace in-app scheduling in docs with external scheduler

Using node-schedule in multi-instance servers duplicates jobs. Invert control: remove the schedule.scheduleJob snippet in GDPR_COMPLIANCE_GUIDE.md and instead call the job endpoint from an external scheduler. For example:

-// Run daily at 2 AM
-schedule.scheduleJob('0 2 * * *', async () => {
-  await Parse.Cloud.startJob('enforceDataRetention');
-});
+// Use external scheduler (cron/K8s CronJob/GitHub Actions) to POST /jobs/enforceDataRetention
+// Example (cron):
+// 0 2 * * * curl -X POST "https://your-server/jobs/enforceDataRetention" \
+//   -H "X-Parse-Master-Key: <masterKey>"

Parse.Cloud.startJob is supported by Parse Server and can be invoked via the REST endpoint when called externally.


39-49: Config snippet: align keys/defaults and add security caveat

  • Use exact auditLog config keys and defaults from src/Options/Definitions.js (auditLogFolder, datePattern, maxSize, maxFiles).
  • Add a note that audit logs may contain personal data and must be stored on restricted, immutable storage.

@rehabguruadm
Copy link

@dblythy, this looks great and something we will use extensively rather than our current home-brewed audit log. My only concern is that the logs are stored on the server, which means they will be lost on each deploy for Docker users. Is there an external storage (S3) approach that would be easy to implement?

@mtrezza
Copy link
Member

mtrezza commented Oct 26, 2025

I wonder whether storing in log files serves the purpose. To be efficiently searchable in regular audits it would better be stored in a DB. That also allows to easily set up triggers, automated reports, etc. So maybe there should be an auditLogger adapter interface that allows to plug in adapters to store on S3, in DB, or any other 3rd party system.

@rehabguruadm
Copy link

I wonder whether storing in log files serves the purpose. To be efficiently searchable in regular audits it would better be stored in a DB. That also allows to easily set up triggers, automated reports, etc. So maybe there should be an auditLogger adapter interface that allows to plug in adapters to store on S3, in DB, or any other 3rd party system.

This sounds great to me. From experience running audit logs for write and delete events, it gets huge really quickly. Currently 10x the size of our largest collection. It would be great to have a Redis-style policy for audit logs so that those older than 30 days are automatically deleted; however, it would need a background job in the current parse setup.

@dblythy
Copy link
Member Author

dblythy commented Nov 1, 2025

@mtrezza
Copy link
Member

mtrezza commented Nov 2, 2025

I believe the GDPR auditable information should be logged separately from other server logs; is this going into distinct files? Logging as JSON or CSV is fine I think; then it can be ingested and parsed like with a AWS Firehose pipeline.

@mtrezza
Copy link
Member

mtrezza commented Nov 2, 2025

@coderabbitai explain the architecture of the audit logging this PR adds. Does it allow to save the GDPR logs into distinct log files? Is the feature and logging output suitable for the industry standard of GDPR auditable logging?

@coderabbitai
Copy link

coderabbitai bot commented Nov 2, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the actual implementation files to understand the architecture better

echo "=== AuditLogger.js ==="
cat src/Adapters/Logger/AuditLogger.js

echo -e "\n\n=== AuditLogAdapter.js ==="
cat src/Adapters/Logger/AuditLogAdapter.js

echo -e "\n\n=== AuditLogController.js ==="
cat src/Controllers/AuditLogController.js

echo -e "\n\n=== Options/Definitions.js - auditLog section ==="
grep -A 30 "auditLog" src/Options/Definitions.js | head -40

Length of output: 24771


@mtrezza, I'll explain the audit logging architecture added in this PR:

Architecture Overview

The implementation uses a three-layer architecture:

1. AuditLogger Layer (src/Adapters/Logger/AuditLogger.js)

  • Low-level Winston-based logger with daily rotation using winston-daily-rotate-file
  • Creates distinct audit log files with pattern: parse-server-audit-%DATE%.log (e.g., parse-server-audit-2025-11-02.log)
  • Configuration:
    • auditLogFolder: Directory for audit logs (separate from application logs)
    • datePattern: Rotation pattern (default: YYYY-MM-DD)
    • maxSize: Max file size before rotation (default: 20m)
    • maxFiles: Retention period (default: 14d)
  • JSON-formatted output with timestamps

2. AuditLogAdapter Layer (src/Adapters/Logger/AuditLogAdapter.js)

  • Extends LoggerAdapter and provides domain-specific logging methods:
    • logUserLogin() - Login attempts (success/failure)
    • logDataView() - Query/read operations
    • logDataCreate(), logDataUpdate(), logDataDelete() - Write operations
    • logACLModify() - ACL changes
    • logSchemaModify() - Schema modifications
    • logPushSend() - Push notifications
  • Routes all calls through the audit logger

3. AuditLogController Layer (src/Controllers/AuditLogController.js)

  • Orchestrates logging with context extraction:
    • IP address resolution (X-Forwarded-For, X-Real-IP, fallback to req.ip)
    • User context extraction (userId, sessionToken)
    • Sensitive data masking (password, sessionToken, authData, _hashed_password)
  • Integrated into: RestQuery, RestWrite, UsersRouter, SchemasRouter, PushRouter

Distinct Log Files

Yes, GDPR audit logs are stored in completely separate files:

  • Distinct directory: configured via auditLogFolder
  • Distinct filename pattern: parse-server-audit-*.log (not mixed with application logs)
  • Separate Winston logger instance dedicated to audit events
  • JSON format for easy parsing and ingestion

Industry Standard Suitability

Strengths for GDPR Compliance:

  1. Comprehensive event coverage - All required events logged (login, data access, modifications, ACL/schema changes, push)
  2. Audit trail essentials - userId, IP address, timestamps, success/failure, error messages
  3. Data protection - Sensitive fields masked automatically
  4. Structured format - JSON output for parsing/ingestion (AWS Firehose, log aggregators)
  5. Rotation & retention - Configurable lifecycle management
  6. Immutability - Append-only log files

⚠️ Limitations (Addressing Thread Concerns):

As you and @rehabguruadm correctly identified:

  1. File-based only - No pluggable adapter interface for alternative storage backends

    • Lost on Docker container restarts/redeployments
    • Cannot store to S3, databases, or external SIEM systems
    • Difficult to implement tamper-proof archiving
  2. Searchability - Log files are hard to query at scale

    • No built-in search/filter capabilities
    • Would need external log aggregation tools
    • Cannot easily run compliance reports or automated triggers
  3. No adapter interface - Unlike other Parse Server components, audit logging lacks pluggable backends

    • Your suggestion for an auditLogger adapter interface would be more architecturally sound
    • Would allow: S3 storage, database storage, CloudWatch, Datadog, etc.

Recommendation:

The current implementation provides a solid foundation for GDPR audit logging and meets basic compliance requirements for file-based deployments. However, for production-grade GDPR compliance—especially in containerized or enterprise environments—this PR would benefit from:

  1. Adapter interface pattern (similar to LoggerAdapter, CacheAdapter, etc.)
  2. Support for multiple backends: filesystem (default), S3, MongoDB/PostgreSQL, external SIEM
  3. Retention policies implemented at the adapter level

The current architecture could be refactored to use an AuditLogStorageAdapter interface, making the existing file-based implementation one of several available options.

@mtrezza
Copy link
Member

mtrezza commented Nov 3, 2025

The current architecture could be refactored to use an AuditLogStorageAdapter interface, making the existing file-based implementation one of several available options.

That makes sense to me. For example, storing on S3 would allow to store immutably through S3 bucket settings. This brings a higher data confidence level than storing as log files on the instance and waiting for a job to ingest the log files and store them somewhere else where they only then become immutable.

I think we also need some filter settings to define what should be logged, to reduce unnecessary logging and tailor to purpose and jurisdiction.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

♻️ Duplicate comments (2)
GDPR_COMPLIANCE_GUIDE.md (2)

188-194: Avoid exporting full user JSON; whitelist explicit fields.

Using request.user.toJSON() can leak private fields (e.g., sessionToken). Build the user object explicitly.

Apply:

-  const exportData = {
-    exportDate: new Date().toISOString(),
-    user: request.user.toJSON(),
-    relatedData: {}
-  };
+  const exportData = {
+    exportDate: new Date().toISOString(),
+    user: {
+      id: request.user.id,
+      username: request.user.get('username'),
+      email: request.user.get('email'),
+      createdAt: request.user.createdAt,
+      updatedAt: request.user.updatedAt,
+    },
+    relatedData: {}
+  };

1013-1016: Broken link: GDPR_AUDIT_LOGGING.md is missing.

Fix the reference or add the document to the repo. Validate all intra-doc links.

- - [GDPR_AUDIT_LOGGING.md](./GDPR_AUDIT_LOGGING.md) - Audit logging setup and configuration
+ - [Audit logging setup and configuration](./README.md#audit-logging) <!-- or the correct existing doc path -->
🧹 Nitpick comments (14)
GDPR_COMPLIANCE_GUIDE.md (2)

26-31: Wording: not “database-level” logging.

This is server-level auditing integrated with REST/DB operations, not DB-engine level. Soften phrasing to avoid overclaim.

- - Automatic, transparent logging at the database level
+ - Automatic, transparent server-level logging integrated with database operations

482-489: markdownlint MD036: use a heading instead of bold text.

Convert emphasis-only line to a proper heading.

-**Note on Tracking User Inactivity:**
+#### Note on tracking user inactivity
src/rest.js (1)

167-179: Same as above in error path.

Propagate a real req or omit it; keep logs non-blocking.

-        config.auditLogController?.logDataDelete({
-          auth,
-          req: { config },
+        config.auditLogController?.logDataDelete({
+          auth,
+          req: context?.req,
           className,
           objectId,
           success: false,
           error: error.message,
         });
src/Options/Definitions.js (1)

1011-1017: Generated ‘filter’ is required; should be optional.

After changing src/Options/index.js (make filter optional), regenerate this file to drop required: true here.

Run the generator to refresh Definitions.js.

spec/Auth.spec.js (1)

408-423: Assertion for “audit logging disabled” relies on folder absence.

If shorthand support is added server-side, ensure disabled path still yields no folder. Consider asserting adapter disabled flag instead when available.

spec/ParseObject.spec.js (1)

2212-2213: Replace fixed sleeps with a polling waitForLogs helper

Fixed 200ms waits are brittle under CI load. Poll for expected content with a timeout to eliminate test flakes. Based on learnings.

+const waitForLogs = async (file, predicate, timeoutMs = 5000, intervalMs = 100) => {
+  const start = Date.now();
+  while (Date.now() - start < timeoutMs) {
+    if (fs.existsSync(file)) {
+      const text = fs.readFileSync(file, 'utf8');
+      if (predicate(text)) return text;
+    }
+    await new Promise(r => setTimeout(r, intervalMs));
+  }
+  throw new Error('Timed out waiting for expected log content');
+};
@@
-    await new Promise(resolve => setTimeout(resolve, 200));
+    const logFiles = getLogFiles(testLogFolder);
+    expect(logFiles.length).toBeGreaterThan(0);
+    const logFile = path.join(testLogFolder, logFiles[0]);
+    const logContent = await waitForLogs(logFile, t => t.includes('DATA_CREATE'));
@@
-    await new Promise(resolve => setTimeout(resolve, 200));
+    const logContent = await waitForLogs(path.join(testLogFolder, getLogFiles(testLogFolder)[0]), t =>
+      t.includes('DATA_UPDATE')
+    );

Also applies to: 2240-2241, 2270-2271, 2303-2304, 2327-2328, 2349-2350, 2365-2366

spec/AuditLogFilter.spec.js (3)

91-113: Add test: includeRoles should exclude events with no roles

Spec codifies that when includeRoles is set and event.roles is empty, event should be excluded. Add a direct test.

   describe('role filtering', () => {
@@
     it('should filter by includeRoles', () => {
@@
     });
+
+    it('should exclude events with no roles when includeRoles is set', () => {
+      const filter = new AuditLogFilter({
+        includeRoles: ['admin'],
+      });
+      expect(filter.shouldLog(createEvent({ roles: [] }))).toBe(false);
+      expect(filter.shouldLog(createEvent({ roles: undefined }))).toBe(false);
+    });

91-113: Clarify precedence: excludeRoles should win over includeRoles

Add a test demonstrating that an excluded role on the user prevents logging even if an included role is also present.

   describe('role filtering', () => {
@@
     it('should filter by excludeRoles', () => {
@@
     });
+
+    it('should prioritize excludeRoles over includeRoles on overlap', () => {
+      const filter = new AuditLogFilter({
+        includeRoles: ['admin'],
+        excludeRoles: ['banned'],
+      });
+      expect(filter.shouldLog(createEvent({ roles: ['admin', 'banned'] }))).toBe(false);
+    });

Also applies to: 137-217


38-69: Test includeClasses and excludeClasses together

When both are set, excluded classes should be filtered out even if included, and non-included classes should remain excluded. Add coverage.

   describe('class name filtering', () => {
@@
     it('should handle events without className', () => {
@@
     });
+
+    it('should apply excludeClasses before includeClasses', () => {
+      const filter = new AuditLogFilter({
+        includeClasses: ['_User', 'Order'],
+        excludeClasses: ['_User'],
+      });
+      expect(filter.shouldLog(createEvent({ className: '_User' }))).toBe(false);
+      expect(filter.shouldLog(createEvent({ className: 'Order' }))).toBe(true);
+      expect(filter.shouldLog(createEvent({ className: 'Other' }))).toBe(false);
+    });
spec/ParseQuery.spec.js (1)

5418-5419: Use a reusable waitForLogs instead of fixed 200ms sleeps

Polling for specific markers eliminates timing flakes in CI. Based on learnings.

+const waitForLogs = async (folder, includes, timeoutMs = 5000) => {
+  const start = Date.now();
+  while (Date.now() - start < timeoutMs) {
+    const files = getLogFiles(folder);
+    if (files.length) {
+      const text = fs.readFileSync(path.join(folder, files[0]), 'utf8');
+      if (includes.every(s => text.includes(s))) return text;
+    }
+    await new Promise(r => setTimeout(r, 100));
+  }
+  throw new Error('Timed out waiting for expected log content');
+};
@@
-      await new Promise(resolve => setTimeout(resolve, 200));
-      const logFiles = getLogFiles(testLogFolder);
-      expect(logFiles.length).toBeGreaterThan(0);
-      const logFile = path.join(testLogFolder, logFiles[0]);
-      const logContent = fs.readFileSync(logFile, 'utf8');
+      const logContent = await waitForLogs(testLogFolder, ['DATA_VIEW', 'AuditTest']);

Also applies to: 5451-5452, 5479-5480, 5500-5501, 5524-5525, 5555-5556

spec/AuditLogging.e2e.spec.js (2)

11-15: Add getLogFiles existence guard

Same rationale as other suites: avoid readdirSync ENOENT.

-const getLogFiles = (folder) => fs.readdirSync(folder).filter(f => f.endsWith('.log'));
+const getLogFiles = folder =>
+  fs.existsSync(folder) ? fs.readdirSync(folder).filter(f => f.endsWith('.log')) : [];

56-63: Replace fixed sleeps with a reusable waitForLogs helper

Use a polling helper to wait for specific markers to reduce flakes across e2e tests. Based on learnings.

+const waitForLogs = async (folder, includes, timeoutMs = 10000) => {
+  const start = Date.now();
+  while (Date.now() - start < timeoutMs) {
+    const files = getLogFiles(folder);
+    if (files.length) {
+      const text = fs.readFileSync(path.join(folder, files[0]), 'utf8');
+      if (includes.every(s => text.includes(s))) return text;
+    }
+    await new Promise(r => setTimeout(r, 100));
+  }
+  throw new Error('Timed out waiting for expected log content');
+};
@@
-      await new Promise(resolve => setTimeout(resolve, 500));
-      const logFiles = getLogFiles(testLogFolder);
-      expect(logFiles.length).toBeGreaterThan(0);
-      const logFile = path.join(testLogFolder, logFiles[0]);
-      const logContent = fs.readFileSync(logFile, 'utf8');
+      const logContent = await waitForLogs(testLogFolder, ['USER_LOGIN','DATA_CREATE','DATA_VIEW','DATA_UPDATE','DATA_DELETE']);

Also applies to: 100-107, 124-131, 148-155, 172-175, 186-189, 204-209, 231-248, 265-275, 293-302, 328-336, 389-401, 436-444, 474-483

src/Adapters/AuditLog/AuditLogFilter.ts (1)

125-131: Consider using a type guard instead of type assertion.

The type assertion (event as any).className bypasses TypeScript's type safety. Consider using a type guard to check if the event has a className property in a type-safe manner.

Apply this diff to improve type safety:

  /**
   * Extract className from event based on event structure
   */
  private extractClassName(event: AuditEvent): string | undefined {
-   // Type-safe extraction based on event structure
-   if ('className' in event) {
-     return (event as any).className;
+   // Type-safe extraction using type guard
+   if ('className' in event && typeof (event as Record<string, unknown>).className === 'string') {
+     return (event as Record<string, unknown>).className as string;
    }
    return undefined;
  }
src/Adapters/Logger/AuditLogger.js (1)

45-47: Consider using the application logger instead of console.error.

While console.error is functional for configuration errors, using the application's standard logger would provide better consistency and configurability for production deployments.

Also applies to: 76-78

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bc75b2 and f4f8cb3.

📒 Files selected for processing (21)
  • GDPR_COMPLIANCE_GUIDE.md (1 hunks)
  • resources/buildConfigDefinitions.js (4 hunks)
  • spec/AuditLogAdapter.spec.js (1 hunks)
  • spec/AuditLogController.spec.js (1 hunks)
  • spec/AuditLogFilter.spec.js (1 hunks)
  • spec/AuditLogSchemas.spec.js (1 hunks)
  • spec/AuditLogging.e2e.spec.js (1 hunks)
  • spec/Auth.spec.js (2 hunks)
  • spec/ParseObject.spec.js (1 hunks)
  • spec/ParseQuery.spec.js (1 hunks)
  • src/Adapters/AuditLog/AuditLogAdapterInterface.ts (1 hunks)
  • src/Adapters/AuditLog/AuditLogFilter.ts (1 hunks)
  • src/Adapters/AuditLog/WinstonFileAuditLogAdapter.ts (1 hunks)
  • src/Adapters/AuditLog/index.ts (1 hunks)
  • src/Adapters/Logger/AuditLogger.js (1 hunks)
  • src/Controllers/AuditLogController.ts (1 hunks)
  • src/Controllers/index.js (5 hunks)
  • src/Options/Definitions.js (2 hunks)
  • src/Options/docs.js (2 hunks)
  • src/Options/index.js (2 hunks)
  • src/rest.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • spec/AuditLogSchemas.spec.js
  • spec/AuditLogAdapter.spec.js
  • spec/AuditLogController.spec.js
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-10-16T19:27:05.311Z
Learnt from: Moumouls
Repo: parse-community/parse-server PR: 9883
File: spec/CloudCodeLogger.spec.js:410-412
Timestamp: 2025-10-16T19:27:05.311Z
Learning: In spec/CloudCodeLogger.spec.js, the test "should log cloud function triggers using the silent log level" (around lines 383-420) is known to be flaky and requires the extra `await new Promise(resolve => setTimeout(resolve, 100))` timeout after awaiting `afterSavePromise` for reliability, even though it may appear redundant.

Applied to files:

  • spec/AuditLogging.e2e.spec.js
  • spec/AuditLogFilter.spec.js
  • spec/ParseQuery.spec.js
  • spec/Auth.spec.js
  • spec/ParseObject.spec.js
📚 Learning: 2025-05-09T09:59:06.289Z
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 9445
File: spec/ParseLiveQuery.spec.js:1340-1375
Timestamp: 2025-05-09T09:59:06.289Z
Learning: Tests in the parse-server repository should use promise-based approaches rather than callback patterns with `done()`. Use a pattern where a Promise is created that resolves when the event occurs, then await that promise.

Applied to files:

  • spec/AuditLogging.e2e.spec.js
  • spec/ParseQuery.spec.js
  • spec/Auth.spec.js
  • spec/ParseObject.spec.js
📚 Learning: 2025-04-30T19:31:35.344Z
Learnt from: RahulLanjewar93
Repo: parse-community/parse-server PR: 9744
File: spec/ParseLiveQuery.spec.js:0-0
Timestamp: 2025-04-30T19:31:35.344Z
Learning: In the Parse Server codebase, the functions in QueryTools.js are typically tested through end-to-end behavior tests rather than direct unit tests, even though the functions are exported from the module.

Applied to files:

  • spec/AuditLogging.e2e.spec.js
  • spec/ParseQuery.spec.js
  • spec/Auth.spec.js
  • spec/ParseObject.spec.js
📚 Learning: 2025-05-09T09:59:06.289Z
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 9445
File: spec/ParseLiveQuery.spec.js:1340-1375
Timestamp: 2025-05-09T09:59:06.289Z
Learning: New tests in the parse-server repository should use async/await with promise-based patterns rather than callback patterns with `done()`. The preferred pattern is to create a Promise that resolves when an expected event occurs, then await that Promise.

Applied to files:

  • spec/AuditLogging.e2e.spec.js
  • spec/ParseQuery.spec.js
  • spec/Auth.spec.js
  • spec/ParseObject.spec.js
📚 Learning: 2025-05-04T20:41:05.147Z
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 9445
File: spec/ParseLiveQuery.spec.js:1312-1338
Timestamp: 2025-05-04T20:41:05.147Z
Learning: New tests in the parse-server repository should use async/await with promise-based patterns rather than callback patterns with `done()`.

Applied to files:

  • spec/AuditLogging.e2e.spec.js
  • spec/ParseQuery.spec.js
  • spec/Auth.spec.js
  • spec/ParseObject.spec.js
📚 Learning: 2025-08-27T12:33:06.237Z
Learnt from: EmpiDev
Repo: parse-community/parse-server PR: 9770
File: src/triggers.js:467-477
Timestamp: 2025-08-27T12:33:06.237Z
Learning: In the Parse Server codebase, maybeRunAfterFindTrigger is called in production with Parse.Query objects constructed via withJSON(), so the plain object query handling bug only affects tests, not production code paths.

Applied to files:

  • spec/ParseQuery.spec.js
  • spec/ParseObject.spec.js
📚 Learning: 2025-08-27T09:08:34.252Z
Learnt from: EmpiDev
Repo: parse-community/parse-server PR: 9770
File: src/triggers.js:446-454
Timestamp: 2025-08-27T09:08:34.252Z
Learning: When analyzing function signature changes in Parse Server codebase, verify that call sites are actually incorrect before flagging them. Passing tests are a strong indicator that function calls are already properly aligned with new signatures.

Applied to files:

  • spec/Auth.spec.js
  • spec/ParseObject.spec.js
🧬 Code graph analysis (12)
spec/AuditLogging.e2e.spec.js (3)
src/RestWrite.js (3)
  • request (821-827)
  • request (956-962)
  • error (666-666)
src/defaults.js (1)
  • folder (4-4)
spec/helper.js (1)
  • reconfigureServer (171-205)
spec/AuditLogFilter.spec.js (1)
src/Adapters/AuditLog/AuditLogFilter.ts (1)
  • AuditLogFilter (27-206)
src/Options/Definitions.js (2)
resources/buildConfigDefinitions.js (1)
  • parsers (12-12)
src/Adapters/AdapterLoader.js (1)
  • module (49-49)
src/Adapters/Logger/AuditLogger.js (1)
src/Controllers/index.js (11)
  • options (62-71)
  • options (85-85)
  • options (110-118)
  • options (131-131)
  • options (139-139)
  • options (159-159)
  • options (169-169)
  • options (170-170)
  • options (190-190)
  • options (202-202)
  • options (240-240)
src/Options/index.js (2)
src/Adapters/AuditLog/WinstonFileAuditLogAdapter.ts (1)
  • WinstonFileAuditLogAdapterOptions (22-31)
src/Adapters/AuditLog/index.ts (1)
  • WinstonFileAuditLogAdapterOptions (19-19)
spec/ParseQuery.spec.js (5)
spec/AuditLogAdapter.spec.js (4)
  • fs (4-4)
  • path (5-5)
  • testLogFolder (8-8)
  • getLogFiles (9-9)
spec/AuditLogging.e2e.spec.js (4)
  • fs (3-3)
  • path (4-4)
  • testLogFolder (8-8)
  • getLogFiles (9-9)
spec/ParseObject.spec.js (9)
  • fs (2177-2177)
  • path (2178-2178)
  • testLogFolder (2179-2179)
  • getLogFiles (2180-2180)
  • i (1341-1341)
  • i (1419-1419)
  • i (1496-1496)
  • i (1563-1563)
  • i (1605-1605)
spec/helper.js (6)
  • require (6-6)
  • require (8-8)
  • path (42-42)
  • reconfigureServer (171-205)
  • Parse (4-4)
  • i (324-324)
src/defaults.js (1)
  • folder (4-4)
spec/Auth.spec.js (3)
spec/AuditLogging.e2e.spec.js (5)
  • request (5-5)
  • fs (3-3)
  • path (4-4)
  • testLogFolder (8-8)
  • getLogFiles (9-9)
spec/AuditLogAdapter.spec.js (5)
  • require (3-3)
  • fs (4-4)
  • path (5-5)
  • testLogFolder (8-8)
  • getLogFiles (9-9)
spec/helper.js (6)
  • require (6-6)
  • require (8-8)
  • require (52-52)
  • path (42-42)
  • reconfigureServer (171-205)
  • Parse (4-4)
src/Controllers/index.js (5)
types/Options/index.d.ts (1)
  • ParseServerOptions (29-125)
src/Controllers/AuditLogController.ts (1)
  • AuditLogController (33-423)
spec/helper.js (1)
  • loadAdapter (41-41)
src/Adapters/AuditLog/WinstonFileAuditLogAdapter.ts (1)
  • WinstonFileAuditLogAdapter (37-240)
src/Adapters/AuditLog/index.ts (1)
  • WinstonFileAuditLogAdapter (2-2)
spec/ParseObject.spec.js (1)
spec/helper.js (6)
  • require (6-6)
  • require (8-8)
  • require (52-52)
  • path (42-42)
  • reconfigureServer (171-205)
  • Parse (4-4)
src/Adapters/AuditLog/AuditLogFilter.ts (1)
src/Adapters/AuditLog/AuditLogAdapterInterface.ts (1)
  • AuditEvent (18-29)
src/Adapters/AuditLog/WinstonFileAuditLogAdapter.ts (2)
src/Adapters/Logger/AuditLogger.js (2)
  • logFolder (69-69)
  • auditEntry (111-123)
src/Adapters/AuditLog/AuditLogAdapterInterface.ts (9)
  • AuditEvent (18-29)
  • UserLoginEvent (34-38)
  • DataViewEvent (43-49)
  • DataCreateEvent (54-59)
  • DataUpdateEvent (64-69)
  • DataDeleteEvent (74-78)
  • ACLModifyEvent (83-88)
  • SchemaModifyEvent (93-98)
  • PushSendEvent (103-108)
src/Controllers/AuditLogController.ts (5)
src/Adapters/AuditLog/AuditLogFilter.ts (2)
  • AuditLogFilterConfig (6-21)
  • AuditLogFilter (27-206)
src/Adapters/AuditLog/index.ts (12)
  • AuditLogFilterConfig (18-18)
  • AuditLogAdapterInterface (1-1)
  • AuditLogFilter (3-3)
  • AuditEvent (6-6)
  • UserLoginEvent (8-8)
  • DataViewEvent (9-9)
  • DataCreateEvent (10-10)
  • DataUpdateEvent (11-11)
  • DataDeleteEvent (12-12)
  • ACLModifyEvent (13-13)
  • SchemaModifyEvent (14-14)
  • PushSendEvent (15-15)
src/Controllers/index.js (11)
  • options (62-71)
  • options (85-85)
  • options (110-118)
  • options (131-131)
  • options (139-139)
  • options (159-159)
  • options (169-169)
  • options (170-170)
  • options (190-190)
  • options (202-202)
  • options (240-240)
src/Adapters/AuditLog/AuditLogAdapterInterface.ts (9)
  • AuditEvent (18-29)
  • UserLoginEvent (34-38)
  • DataViewEvent (43-49)
  • DataCreateEvent (54-59)
  • DataUpdateEvent (64-69)
  • DataDeleteEvent (74-78)
  • ACLModifyEvent (83-88)
  • SchemaModifyEvent (93-98)
  • PushSendEvent (103-108)
src/RestWrite.js (2)
  • updatedFields (1837-1837)
  • newACL (1822-1822)
🪛 Biome (2.1.2)
src/Options/index.js

[error] 216-216: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 544-544: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 544-544: Expected a statement but instead found '}'.

Expected a statement here.

(parse)


[error] 549-549: Expected a type but instead found '?'.

Expected a type here.

(parse)


[error] 549-549: Expected a property, or a signature but instead found ';'.

Expected a property, or a signature here.

(parse)


[error] 553-553: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 557-557: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 559-559: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 563-563: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 567-567: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 575-575: Expected a statement but instead found '}'.

Expected a statement here.

(parse)


[error] 578-578: Expected a type but instead found '?'.

Expected a type here.

(parse)


[error] 578-578: Expected a property, or a signature but instead found ';'.

Expected a property, or a signature here.

(parse)


[error] 580-580: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 582-582: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 586-586: Expected a statement but instead found '?'.

Expected a statement here.

(parse)


[error] 586-586: Expected a statement but instead found '}'.

Expected a statement here.

(parse)


[error] 600-600: Expected a type but instead found '?'.

Expected a type here.

(parse)


[error] 601-601: Expected a property, or a signature but instead found ';'.

Expected a property, or a signature here.

(parse)

src/Controllers/index.js

[error] 84-84: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 84-84: return types can only be used in TypeScript files

(parse)

🪛 GitHub Check: Lint
src/Adapters/Logger/AuditLogger.js

[warning] 77-77:
Unexpected console statement


[warning] 46-46:
Unexpected console statement

🪛 markdownlint-cli2 (0.18.1)
GDPR_COMPLIANCE_GUIDE.md

573-573: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Docker Build
🔇 Additional comments (13)
src/Options/docs.js (1)

23-23: LGTM – auditLog option documented clearly.

resources/buildConfigDefinitions.js (2)

34-38: Env prefixes for audit options: good.

Prefixes for AuditLogOptions and AuditLogFilterOptions are consistent with docs.


289-297: Safer union-default parsing guard: good.

The added Array.isArray check prevents crashes on union defaults.

src/Controllers/index.js (1)

30-59: Expose auditLogController in getControllers: LGTM.

Wiring follows existing adapter/controller patterns.

src/Adapters/AuditLog/index.ts (1)

3-3: The verification confirms that shouldLogEvent is properly exported from AuditLogFilter.ts. The script found the export at line 211:

src/Adapters/AuditLog/AuditLogFilter.ts:211:export function shouldLogEvent(event: AuditEvent, config: AuditLogFilterConfig): boolean

The barrel export in src/Adapters/AuditLog/index.ts is correct and will not cause build failures.

Likely an incorrect or invalid review comment.

src/Adapters/AuditLog/AuditLogFilter.ts (1)

188-205: LGTM - Good fail-open error handling.

The fail-open behavior with console.error is appropriate here. It ensures that filter bugs don't block all audit logging, which is critical for compliance. The console.error provides visibility while maintaining system robustness.

src/Adapters/Logger/AuditLogger.js (1)

105-133: LGTM - Solid implementation with good defensive checks.

The audit event logging properly checks for enabled state, masks sensitive data (sessionToken), and removes undefined fields for clean output. The defaults are sensible.

src/Adapters/AuditLog/WinstonFileAuditLogAdapter.ts (2)

115-127: LGTM - Proper cleanup lifecycle.

The close method properly checks state, closes the logger, and updates the enabled flag. Good lifecycle management.


181-239: LGTM - Clean delegation pattern.

All logging methods appropriately delegate to the centralized writeEvent method. This ensures consistent behavior across all event types.

src/Adapters/AuditLog/AuditLogAdapterInterface.ts (1)

1-188: LGTM - Comprehensive and well-structured interface definitions.

The type definitions and abstract class provide a clear, type-safe contract for audit logging:

  • Complete event type coverage for GDPR requirements
  • Consistent base interface with appropriate optional fields
  • Specific event interfaces with relevant data
  • Clear adapter interface with lifecycle hooks
src/Controllers/AuditLogController.ts (3)

50-67: LGTM - Comprehensive IP address extraction.

The IP address extraction properly handles proxied environments by checking X-Forwarded-For and X-Real-IP headers before falling back to the connection's remote address. The X-Forwarded-For parsing correctly takes the first IP in the chain.


112-146: LGTM - Appropriate fire-and-forget pattern.

The fire-and-forget approach ensures audit logging doesn't block application operations. The filter check before logging enables efficient event filtering, and error handling logs failures without throwing, which is correct for audit logging infrastructure.


151-415: LGTM - Consistent logging method implementations.

All logging methods follow a consistent pattern:

  1. Extract user context from auth
  2. Build typed event object with all required fields
  3. Delegate to writeEvent for filtering and persistence

The consistency makes the code maintainable and the type-safe event construction ensures compliance with the adapter interface.

Comment on lines +419 to +421
consent.set('ipAddress', request.ip);

await consent.save(null, { useMasterKey: true });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use reliable client IP source in Cloud Code.

request.ip may be undefined in Parse Cloud. Prefer headers (X-Forwarded-For) or store IP via server middleware/audit logger.

Example:

const ip =
  request.headers?.['x-forwarded-for']?.split(',')[0]?.trim() ||
  request.headers?.['x-real-ip'] ||
  undefined;
// consent.set('ipAddress', ip);

Also applies to: 641-643

🤖 Prompt for AI Agents
In GDPR_COMPLIANCE_GUIDE.md around lines 419-421 (and similarly 641-643), the
code uses request.ip which can be undefined in Parse Cloud; update the Cloud
Code to extract the client IP reliably from request.headers (check
X-Forwarded-For, take the first comma-separated value and trim, then fallback to
X-Real-IP) and only set consent.ipAddress when a value is present, or
alternatively populate the IP via server middleware/audit logger before this
code path; ensure you handle missing headers cleanly (leave undefined) and keep
the existing save call with master key.

Comment on lines +25 to +31
await reconfigureServer({
auditLog: {
adapterOptions: {
auditLogFolder: testLogFolder,
},
},
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Unify auditLog config shape across tests

Some tests set auditLog.auditLogFolder; others set auditLog.adapterOptions.auditLogFolder. Verify which is supported by Options/Definitions and standardize to prevent false negatives.

Would you like me to update the specs once we confirm the canonical shape?

Also applies to: 85-93, 112-116, 134-141, 189-195


🏁 Script executed:

#!/bin/bash
# Inspect Option definitions and adapter for expected auditLog shape.
rg -nP --type=ts -C3 '\bauditLog\b' src/Options src/Adapters src | sed -n '1,120p'
rg -nP -C3 'WinstonFileAuditLogAdapterOptions|auditLogFolder|adapterOptions' src

Length of output: 10338


🏁 Script executed:

#!/bin/bash
# Inspect test file at flagged line ranges to identify config shape usage
sed -n '20,40p' spec/AuditLogging.e2e.spec.js
sed -n '80,100p' spec/AuditLogging.e2e.spec.js
sed -n '107,120p' spec/AuditLogging.e2e.spec.js
sed -n '130,150p' spec/AuditLogging.e2e.spec.js
sed -n '185,200p' spec/AuditLogging.e2e.spec.js

Length of output: 2397


Fix auditLog config shape in two tests to match canonical structure

The codebase standardizes on auditLog.adapterOptions.auditLogFolder, but lines 112–116 and 134–141 use incorrect direct auditLog.auditLogFolder. Update both locations to wrap auditLogFolder inside adapterOptions.

Lines 112–116: Change from auditLog: { auditLogFolder: newFolder, } to auditLog: { adapterOptions: { auditLogFolder: newFolder, }, }

Lines 134–141: Change from auditLog: { auditLogFolder: testLogFolder, datePattern: 'YYYY-MM', } to auditLog: { adapterOptions: { auditLogFolder: testLogFolder, datePattern: 'YYYY-MM', }, }

🤖 Prompt for AI Agents
In spec/AuditLogging.e2e.spec.js around lines 112-116 and 134-141, the auditLog
configuration uses the wrong shape (auditLog.auditLogFolder and
auditLog.datePattern) — update both to nest these under adapterOptions so they
read auditLog: { adapterOptions: { auditLogFolder: <value>, [datePattern:
'YYYY-MM' where applicable], }, } ensuring both tests match the canonical
auditLog.adapterOptions.auditLogFolder structure.

Comment on lines +315 to +344
const userPromises = [];
for (let i = 0; i < 10; i++) {
const user = new Parse.User();
userPromises.push(
user.signUp({
username: `concurrent${i}`,
password: 'password123',
})
);
}

await Promise.all(userPromises);

await new Promise(resolve => setTimeout(resolve, 300));

const logFiles = getLogFiles(testLogFolder);
const logFile = path.join(testLogFolder, logFiles[0]);
const logContent = fs.readFileSync(logFile, 'utf8');
const logLines = logContent.split('\n').filter(line => line.trim().length > 0);

expect(logLines.length).toBeGreaterThanOrEqual(10);

const parsedLogs = logLines.map(line => JSON.parse(line));
expect(parsedLogs.length).toBeGreaterThanOrEqual(10);

for (let i = 0; i < 10; i++) {
const userLogs = logLines.filter(line => line.includes(`concurrent${i}`));
expect(userLogs.length).toBeGreaterThan(0);
}
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Decouple assertions from usernames; assert by event type or userId

Logs may intentionally omit usernames for GDPR. Prefer counting USER_LOGIN events or checking userId presence.

-      for (let i = 0; i < 10; i++) {
-        const userLogs = logLines.filter(line => line.includes(`concurrent${i}`));
-        expect(userLogs.length).toBeGreaterThan(0);
-      }
+      const loginCount = parsedLogs.filter(l => l.eventType === 'USER_LOGIN' && l.success === true).length;
+      expect(loginCount).toBeGreaterThanOrEqual(10);
+      // Or verify userId field exists on each login
+      parsedLogs.filter(l => l.eventType === 'USER_LOGIN').forEach(l => {
+        expect(typeof l.userId).toBe('string');
+      });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const userPromises = [];
for (let i = 0; i < 10; i++) {
const user = new Parse.User();
userPromises.push(
user.signUp({
username: `concurrent${i}`,
password: 'password123',
})
);
}
await Promise.all(userPromises);
await new Promise(resolve => setTimeout(resolve, 300));
const logFiles = getLogFiles(testLogFolder);
const logFile = path.join(testLogFolder, logFiles[0]);
const logContent = fs.readFileSync(logFile, 'utf8');
const logLines = logContent.split('\n').filter(line => line.trim().length > 0);
expect(logLines.length).toBeGreaterThanOrEqual(10);
const parsedLogs = logLines.map(line => JSON.parse(line));
expect(parsedLogs.length).toBeGreaterThanOrEqual(10);
for (let i = 0; i < 10; i++) {
const userLogs = logLines.filter(line => line.includes(`concurrent${i}`));
expect(userLogs.length).toBeGreaterThan(0);
}
});
const userPromises = [];
for (let i = 0; i < 10; i++) {
const user = new Parse.User();
userPromises.push(
user.signUp({
username: `concurrent${i}`,
password: 'password123',
})
);
}
await Promise.all(userPromises);
await new Promise(resolve => setTimeout(resolve, 300));
const logFiles = getLogFiles(testLogFolder);
const logFile = path.join(testLogFolder, logFiles[0]);
const logContent = fs.readFileSync(logFile, 'utf8');
const logLines = logContent.split('\n').filter(line => line.trim().length > 0);
expect(logLines.length).toBeGreaterThanOrEqual(10);
const parsedLogs = logLines.map(line => JSON.parse(line));
expect(parsedLogs.length).toBeGreaterThanOrEqual(10);
const loginCount = parsedLogs.filter(l => l.eventType === 'USER_LOGIN' && l.success === true).length;
expect(loginCount).toBeGreaterThanOrEqual(10);
// Or verify userId field exists on each login
parsedLogs.filter(l => l.eventType === 'USER_LOGIN').forEach(l => {
expect(typeof l.userId).toBe('string');
});
});
🤖 Prompt for AI Agents
In spec/AuditLogging.e2e.spec.js around lines 315 to 344, the test currently
asserts log entries by searching for usernames, which may be omitted for GDPR;
change the assertions to decouple from usernames by instead parsing each log
line as JSON and asserting by event type (e.g., count entries where event ===
'USER_LOGIN' or the equivalent) and/or by checking presence of userId fields
that match the signed-up users' ids (capture the returned user objects/ids when
signUp resolves). Ensure you await Promise.all(signUpPromises) to collect user
ids, parse logLines into JSON first, then assert parsedLogs.filter(l => l.event
=== 'USER_LOGIN').length >= 10 and/or that each created userId appears in at
least one parsed log entry.

Comment on lines +422 to +435
const startTime = Date.now();

for (let i = 0; i < 100; i++) {
const obj = new TestClass();
obj.set('index', i);
await obj.save();
}

const endTime = Date.now();
const duration = endTime - startTime;

// Should complete in reasonable time even with audit logging enabled
expect(duration).toBeLessThan(10000);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid brittle wall-clock assertions in perf test

A 10s cap for 100 sequential saves can flake on CI. Either parallelize with bounded concurrency or drop the timing check and assert only on log count.

-      const startTime = Date.now();
-      for (let i = 0; i < 100; i++) {
-        const obj = new TestClass();
-        obj.set('index', i);
-        await obj.save();
-      }
-      const duration = Date.now() - startTime;
-      expect(duration).toBeLessThan(10000);
+      const tasks = [];
+      for (let i = 0; i < 100; i++) {
+        const obj = new TestClass();
+        obj.set('index', i);
+        tasks.push(obj.save());
+      }
+      await Promise.all(tasks);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In spec/AuditLogging.e2e.spec.js around lines 422 to 435, the test uses a
brittle wall-clock assertion (expect(duration).toBeLessThan(10000)) for 100
sequential saves which flakes on CI; replace this by either running the 100
saves with bounded concurrency (e.g., use a small concurrency limiter like
p-limit or implement batches and await Promise.all for each batch) to keep
runtime fast and stable, or remove the timing assertion entirely and instead
assert deterministically on side-effects such as verifying the audit log count
equals 100 (or expected entries) after all saves complete; implement one of
these two changes and remove the flaky duration expectation.

Comment on lines +278 to +284
it('should log successful user login', async () => {
await reconfigureServer({
auditLog: {
auditLogFolder: testLogFolder,
},
});

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Config shape mismatch with new auditLog options.

Tests use shorthand auditLog: { auditLogFolder }, but the code expects { adapter, adapterOptions }. Either update tests or make server accept shorthand.

Option A (update test config):

-    await reconfigureServer({
-      auditLog: {
-        auditLogFolder: testLogFolder,
-      },
-    });
+    await reconfigureServer({
+      auditLog: {
+        adapter: 'winston-file',
+        adapterOptions: { auditLogFolder: testLogFolder },
+      },
+    });

Option B (preferred): keep tests and add shorthand support in getAuditLogController (see suggested diff in src/Controllers/index.js).

🤖 Prompt for AI Agents
In spec/Auth.spec.js around lines 278 to 284 the test passes auditLog: {
auditLogFolder } but the server now expects auditLog to be { adapter,
adapterOptions }; update getAuditLogController to accept the shorthand shape:
detect when auditLog.auditLogFolder (or auditLog.auditLogPath) is present and
normalize it into the expected object (e.g. set adapter to the file adapter and
populate adapterOptions.folder/path from auditLogFolder), otherwise leave
existing {adapter, adapterOptions} untouched; add minimal validation and
unit-test-friendly defaults so current tests using the shorthand continue to
work.

Comment on lines +72 to +87
private getUserContext(auth: any): UserContext {
if (!auth) {
return {
userId: undefined,
sessionToken: undefined,
isMasterKey: false,
roles: [],
};
}
return {
userId: auth.user?.id || auth.user?.objectId,
sessionToken: auth.sessionToken,
isMasterKey: auth.isMaster || auth.isMasterKey || false,
roles: auth.user?.get('roles') || [],
};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify roles extraction works with all auth types.

Line 85 uses auth.user?.get('roles') which assumes a Parse Object with a get() method. Verify that all authentication flows provide user objects in this format, or handle cases where roles might be stored as a direct property.

Run the following script to check how roles are accessed across the codebase:


🏁 Script executed:

#!/bin/bash
# Search for role access patterns in auth/user objects
rg -nP --type=ts --type=js -C3 '\bauth\.user\b.*\broles\b|\buser\b.*\.roles\b'

Length of output: 496



---

🏁 Script executed:

```shell
#!/bin/bash
# Check the type definition of auth parameter and imports in AuditLogController
head -30 src/Controllers/AuditLogController.ts

Length of output: 658


🏁 Script executed:

#!/bin/bash
# Search for Parse Object usage patterns (.get method calls)
rg -nP --type=ts --type=js '\.get\(' -C2 | head -50

Length of output: 2540


🏁 Script executed:

#!/bin/bash
# Search for auth object type definitions or interfaces
rg -nP --type=ts 'interface.*[Aa]uth|type.*[Aa]uth' -C3

Length of output: 366



---

🏁 Script executed:

```shell
#!/bin/bash
# Get the full getUserContext method signature with context
rg -nP --type=ts 'private\s+getUserContext|getUserContext\s*\(' -A 20 src/Controllers/AuditLogController.ts

Length of output: 5420


🏁 Script executed:

#!/bin/bash
# Search for calls to getUserContext
rg -nP --type=ts 'getUserContext\(' src/Controllers/AuditLogController.ts -B 3 -A 1

Length of output: 1645


🏁 Script executed:

#!/bin/bash
# Check for Parse imports and usage in AuditLogController
rg -nP --type=ts '^import|^from' src/Controllers/AuditLogController.ts | head -20

Length of output: 73


🏁 Script executed:

#!/bin/bash
# Search for how auth object is used elsewhere in this file
rg -nP --type=ts '\bauth\.' src/Controllers/AuditLogController.ts -B 2 -A 2

Length of output: 251



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for where auth object is created or populated in middleware/controllers
rg -nP --type=ts --type=js 'auth\s*[:=]|auth\s*\{' -B 2 -A 3 | head -80

Length of output: 3780


🏁 Script executed:

#!/bin/bash
# Search for test files that might show auth object structure
fd -e 'test.ts' -e 'spec.ts' -e 'test.js' -e 'spec.js' | xargs rg -l 'AuditLogController|getUserContext' 2>/dev/null || echo "No test files found"

Length of output: 31


🏁 Script executed:

#!/bin/bash
# Check if Parse Objects allow both direct property access and .get() method
rg -nP --type=ts --type=js 'auth\.user\?\.(get|roles|id|objectId)' -C 1

Length of output: 694



---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the spec file to understand auth object structure
cat spec/AuditLogController.spec.js

Length of output: 9064



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for where roles are set on user objects
rg -nP --type=ts --type=js 'user.*roles\s*=|user\[.*roles|roles.*=' -B 2 -A 2 | head -60

Length of output: 2905


🏁 Script executed:

#!/bin/bash
# Search for Auth class/module to understand auth.user structure
rg -nP --type=ts --type=js 'class Auth|module.exports.*Auth|export.*Auth' -A 5 | head -40

Length of output: 2319


🏁 Script executed:

#!/bin/bash
# Check if Parse is imported in RestWrite.js where auth is used
head -50 src/RestWrite.js | grep -E 'import|require|Parse'

Length of output: 622



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for Auth class definition and getUserRoles method
fd -name 'Auth.js' -o -name 'Auth.ts' | xargs cat | head -150

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Search for how getUserRoles is implemented
rg -nP --type=js 'getUserRoles\s*\(' -B 2 -A 10

Length of output: 5331


🏁 Script executed:

#!/bin/bash
# Check if auth.user ever has a get() method or if it's always a plain object
rg -nP --type=js --type=ts 'this\.auth\.user\s*=' -B 2 -A 5 | head -80

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Find and examine Auth.js file
find . -name 'Auth.js' -type f

Length of output: 13


🏁 Script executed:

#!/bin/bash
# Search for Auth class definition more carefully
rg -nP --type=js 'class Auth|function Auth' -A 30 | head -100

Length of output: 3563


🏁 Script executed:

#!/bin/bash
# Look for where auth.user is a Parse Object vs plain object
rg -nP --type=js 'auth\.user.*get\(' -B 5 -A 2

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the full Auth.js file to see getUserRoles implementation and how user is constructed
cat src/Auth.js

Length of output: 18201


🏁 Script executed:

#!/bin/bash
# Check how auth object is constructed in middleware/rest handlers
rg -nP --type=js 'new Auth\(|Auth\s*\(' -B 3 -A 8 | head -100

Length of output: 5083


🏁 Script executed:

#!/bin/bash
# Verify if Parse Object's get() method is ever used in the context of auth.user
rg -nP --type=ts --type=js 'get\([\'"]roles[\'"]\)' -B 2 -A 1

Length of output: 68



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for where roles attribute is set on user Parse Objects
rg -nP --type=js 'user\.set\(' -B 2 -A 2 | grep -A 3 -B 3 'roles'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for any evidence of roles being stored on user objects
rg -nP --type=js 'set.*roles|roles.*set' -B 2 -A 2

Length of output: 2920


🏁 Script executed:

#!/bin/bash
# Check what Parse Object properties are actually set on users in real usage
rg -nP --type=js 'fromJSON.*user|user.*fromJSON' -B 3 -A 3 | head -50

Length of output: 2841


Fix roles extraction to use auth.getUserRoles() or make the method async.

Line 85 calls auth.user?.get('roles') which will always return undefined because roles are not stored as an attribute on user objects in Parse. Roles are loaded separately from the _Role table via auth.getUserRoles(). The current fallback || [] silently masks this failure, causing audit logs to always record empty role arrays.

Either:

  1. Make getUserContext async and call await auth.getUserRoles(), or
  2. Pass the roles separately to getUserContext() from callers that have already loaded them
🤖 Prompt for AI Agents
In src/Controllers/AuditLogController.ts around lines 72 to 87, getUserContext
currently reads roles via auth.user?.get('roles') which is incorrect for Parse
(roles are loaded via auth.getUserRoles()) so audit logs end up with empty
arrays; change getUserContext to accept or fetch real roles: either make
getUserContext async and call const roles = await auth.getUserRoles() (and
return roles in the UserContext), or add a roles parameter and have callers pass
in roles they already loaded; also update all callers to await the new async
method or supply roles accordingly and remove the fallback
auth.user?.get('roles') || [].

Comment on lines +92 to +107
private maskSensitiveData(data: any): any {
if (!data || typeof data !== 'object') {
return data;
}

const masked = { ...data };
const sensitiveFields = ['password', 'sessionToken', 'authData', '_hashed_password'];

sensitiveFields.forEach(field => {
if (masked[field]) {
masked[field] = '***masked***';
}
});

return masked;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: Shallow masking may leak nested sensitive data.

The current implementation only masks top-level fields. Nested sensitive data (e.g., authData.facebook.token, nested password fields) will not be masked, potentially violating GDPR requirements by logging sensitive information.

Consider implementing deep masking with recursive traversal:

  private maskSensitiveData(data: any): any {
    if (!data || typeof data !== 'object') {
      return data;
    }

    if (Array.isArray(data)) {
      return data.map(item => this.maskSensitiveData(item));
    }

    const masked = { ...data };
    const sensitiveFields = ['password', 'sessionToken', 'authData', '_hashed_password', 'token'];

    Object.keys(masked).forEach(key => {
      if (sensitiveFields.includes(key)) {
        masked[key] = '***masked***';
      } else if (typeof masked[key] === 'object' && masked[key] !== null) {
        // Recursively mask nested objects
        masked[key] = this.maskSensitiveData(masked[key]);
      }
    });

    return masked;
  }
🤖 Prompt for AI Agents
In src/Controllers/AuditLogController.ts around lines 92 to 107, the
maskSensitiveData function only masks top-level fields which can leak nested
sensitive values; change it to perform a deep recursive traversal: detect arrays
and map over them, iterate object keys, if a key is in the sensitiveFields list
replace its value with the mask, otherwise if the value is a non-null object or
array call maskSensitiveData recursively, and return a new cloned structure
(avoid mutating original input); also include common sensitive keys like "token"
and ensure null checks to prevent errors.

Comment on lines +84 to +107
export function getAuditLogController(options: ParseServerOptions): AuditLogController {
const { appId, auditLog } = options;

// If no audit log config, return a controller with no adapter (disabled)
if (!auditLog) {
return new AuditLogController(null, appId, {});
}

// Extract configuration
const { adapter, logFilter, adapterOptions } = auditLog;

// Load the audit log adapter using loadAdapter pattern
// Defaults to WinstonFileAuditLogAdapter if no adapter specified
const auditLogAdapter = loadAdapter(
adapter,
WinstonFileAuditLogAdapter,
adapterOptions || {}
);

// Create controller with adapter and filter configuration
return new AuditLogController(auditLogAdapter, appId, {
logFilter: logFilter || {},
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Support shorthand auditLog config and keep adapter defaulting.

Accept auditLog as either full AuditLogOptions or WinstonFileAuditLogAdapterOptions to match tests/docs.

-export function getAuditLogController(options: ParseServerOptions): AuditLogController {
+export function getAuditLogController(options: ParseServerOptions): AuditLogController {
   const { appId, auditLog } = options;

   // If no audit log config, return a controller with no adapter (disabled)
   if (!auditLog) {
     return new AuditLogController(null, appId, {});
   }

-  // Extract configuration
-  const { adapter, logFilter, adapterOptions } = auditLog;
+  // Support both full and shorthand shapes:
+  // - Full: { adapter, adapterOptions, logFilter }
+  // - Shorthand: { auditLogFolder, datePattern, maxSize, maxFiles }
+  const hasShorthand =
+    auditLog &&
+    typeof auditLog === 'object' &&
+    !('adapter' in auditLog) &&
+    ('auditLogFolder' in auditLog || 'datePattern' in auditLog || 'maxFiles' in auditLog || 'maxSize' in auditLog);
+  const adapter = hasShorthand ? undefined : auditLog.adapter;
+  const adapterOptions = hasShorthand ? auditLog : auditLog.adapterOptions || {};
+  const logFilter = hasShorthand ? undefined : auditLog.logFilter;

   // Load the audit log adapter using loadAdapter pattern
   // Defaults to WinstonFileAuditLogAdapter if no adapter specified
   const auditLogAdapter = loadAdapter(
     adapter,
     WinstonFileAuditLogAdapter,
-    adapterOptions || {}
+    adapterOptions
   );

   // Create controller with adapter and filter configuration
   return new AuditLogController(auditLogAdapter, appId, {
     logFilter: logFilter || {},
   });
 }
🧰 Tools
🪛 Biome (2.1.2)

[error] 84-84: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 84-84: return types can only be used in TypeScript files

(parse)

Comment on lines +566 to +573
excludeRoles: ?(string[]);
/* Custom filter function for advanced filtering.
<br><br>
Function receives an audit event object and returns true to log or false to skip.
<br><br>
Example: (event) => event.userId !== 'system' */
filter: any;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make AuditLogFilterOptions.filter optional.

Docs show filter is optional; Definitions currently require it. Align by marking as optional.

-  filter: any;
+  filter: ?any;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
excludeRoles: ?(string[]);
/* Custom filter function for advanced filtering.
<br><br>
Function receives an audit event object and returns true to log or false to skip.
<br><br>
Example: (event) => event.userId !== 'system' */
filter: any;
}
excludeRoles: ?(string[]);
/* Custom filter function for advanced filtering.
<br><br>
Function receives an audit event object and returns true to log or false to skip.
<br><br>
Example: (event) => event.userId !== 'system' */
filter: ?any;
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 567-567: Expected a statement but instead found '?'.

Expected a statement here.

(parse)

🤖 Prompt for AI Agents
In src/Options/index.js around lines 566 to 573, the type definition for
AuditLogFilterOptions currently requires the filter property but the docs state
it is optional; update the definition to mark filter as optional (e.g., add a ?
or make its type include undefined) so callers can omit it, and ensure any
related exports/types are updated consistently.

Comment on lines +144 to +155
// Audit log successful delete
try {
config.auditLogController?.logDataDelete({
auth,
req: { config },
className,
objectId,
success: true,
});
} catch (error) {
config.loggerController.error('Audit logging error in rest.del', { error });
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pass a real request context (or omit) instead of req: { config }.

AuditLogController expects a request (for IP). { config } is not a request and adds no value.

-        config.auditLogController?.logDataDelete({
-          auth,
-          req: { config },
+        config.auditLogController?.logDataDelete({
+          auth,
+          req: context?.req, // if available; otherwise omit this field
           className,
           objectId,
           success: true,
         });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/rest.js around lines 144 to 155, the audit log call is passing req: {
config } which is not a real request object; replace that with the actual
request context (e.g., pass the existing req variable so the AuditLogController
can read IP/user info) or remove the req property entirely if no request is
available; update the call signature accordingly and keep the try/catch logging
behavior unchanged.

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.

GDPR compliance

4 participants