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

  • New Features

    • Configurable audit logging: structured JSON audit files, daily rotation, retention/size limits, IP tracking, masking of sensitive fields, and runtime enable/disable.
    • Audit events for logins, data view/create/update/delete, ACL/schema changes, and push sends; non-blocking on failure.
  • Configuration

    • New audit options for log folder, date pattern, max size, and retention exposed via server options/env.
  • Documentation

    • Comprehensive GDPR Compliance Guide with implementation and operational guidance.
  • Tests

    • Extensive unit and end-to-end audit logging test coverage added.

Copy link

🚀 Thanks for opening this pull request!

Copy link

coderabbitai bot commented Oct 1, 2025

📝 Walkthrough

Walkthrough

Adds a GDPR-focused audit logging subsystem: logger, adapter, controller, option definitions, and integrations into auth, query, write, push, and schema flows, plus tests and a GDPR guide. Logs are structured JSON, daily-rotated, masked, and configurable via new auditLog options.

Changes

Cohort / File(s) Summary
Documentation
GDPR_COMPLIANCE_GUIDE.md
Adds a comprehensive GDPR compliance guide describing audit logging features, developer obligations, Cloud Code examples, retention/breach guidance, and operational considerations.
Audit Logging Core
src/Adapters/Logger/AuditLogger.js, src/Adapters/Logger/AuditLogAdapter.js
New AuditLogger (Winston DailyRotateFile) and AuditLogAdapter providing structured audit event emission and specialized methods (login, data view/create/update/delete, ACL/schema modify, push send), masking and enable/disable handling.
Audit Controller & Wiring
src/Controllers/AuditLogController.js, src/Controllers/index.js
New AuditLogController with IP/user context extraction and masking; integrated into controllers factory via getAuditLogController and exposed on getControllers output.
Options & Public Types
src/Options/Definitions.js, src/Options/index.js
Adds AuditLogOptions (auditLogFolder, datePattern, maxSize, maxFiles) and wires a new ParseServerOptions.auditLog public option with env keys and defaults.
Read Path Integration
src/RestQuery.js
Hooks into query flow via _UnsafeRestQuery.prototype.logAuditDataView to emit DATA_VIEW events (up to 100 objectIds), skipping master/no-results and non-blocking on errors.
Write Path Integration
src/RestWrite.js
Adds RestWrite.prototype.logAuditDataWrite called during execute to emit DATA_CREATE/DATA_UPDATE and ACL_MODIFY events, detect create vs update, mask data, and avoid failing writes on logging errors.
Routers: Users/Push/Schemas
src/Routers/UsersRouter.js, src/Routers/PushRouter.js, src/Routers/SchemasRouter.js
Emits audit events for login/loginAs (success/failure), push send (success/failure), and schema create/update/delete (success/failure). PushRouter adjusts promise flow and uses request body/channels variables; SchemasRouter converted to async/await and wrapped in try/catch for logging.
Unit Tests: Adapter/Controller/Schemas
spec/AuditLogAdapter.spec.js, spec/AuditLogController.spec.js, spec/AuditLogSchemas.spec.js
New suites validating adapter init, transport config, log file creation/format, controller helpers and masking, and schema operation auditing (create/update/delete and failures).
E2E & Feature Tests
spec/AuditLogging.e2e.spec.js, spec/Auth.spec.js, spec/ParseObject.spec.js, spec/ParseQuery.spec.js
End-to-end and feature tests covering lifecycle (signup/login/CRUD/logout), DATA_VIEW, CRUD/ACL events, auth logging, masking, rotation/date patterns, concurrency, runtime toggling, and log file assertions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Router as Routers (Users/Push/Schemas)
  participant Core as Core Logic
  participant ALC as AuditLogController
  participant ALA as AuditLogAdapter
  participant AL as AuditLogger
  participant FS as File System

  rect rgb(245,248,252)
  note over Router,Core: Normal request handling
  Client->>Router: HTTP request
  Router->>Core: Perform action
  Core-->>Router: Result / Error
  end

  alt Audit logging enabled
    Router->>ALC: log...({ auth, req, ... })
    ALC->>ALA: event (masked, with user/IP)
    ALA->>AL: logAuditEvent(event)
    AL->>FS: append JSON line (rotated file)
  else Disabled or no adapter
    Router-->>Router: Skip logging
  end

  Router-->>Client: HTTP response
Loading
sequenceDiagram
  autonumber
  participant Client
  participant RQ as RestQuery
  participant DB as DB
  participant ALC as AuditLogController
  participant AL as AuditLogger

  Client->>RQ: find()
  RQ->>DB: execute query
  DB-->>RQ: results
  alt results && logging enabled
    RQ->>ALC: logDataView({ className, query, resultCount, objectIds<=100 })
    ALC->>AL: write event
  else skip
    RQ-->>RQ: no-op
  end
  RQ-->>Client: results
Loading
sequenceDiagram
  autonumber
  participant Client
  participant RW as RestWrite
  participant DB as DB
  participant ALC as AuditLogController
  participant AL as AuditLogger

  Client->>RW: create/update/delete
  RW->>DB: write operation
  DB-->>RW: result
  par Create/Update
    RW->>ALC: logDataCreate/update({ masked data/fields })
  and ACL change
    RW->>ALC: logACLModify({ oldACL, newACL })
  end
  ALC->>AL: write event(s)
  RW-->>Client: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The PR introduces unrelated configuration fields for cacheAdapter and emailAdapter in the options definitions, which are not mentioned in the GDPR logging objectives, indicating there are changes that fall outside the scope of issue #5378. Please remove or separate the unrelated cacheAdapter and emailAdapter configuration changes into a different PR so that this one focuses solely on GDPR audit logging functionality.
Description Check ⚠️ Warning The pull request description follows the template structure and links the issue being closed, but the Approach section is left blank and no implementation details are provided, and the tasks checklist remains entirely unchecked. Please fill in the Approach section with a concise summary of the key changes introduced by this PR and update the tasks checklist to reflect added tests, documentation updates, security checks, and any new error codes implemented.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the primary change by indicating that GDPR compliance support is being added, which aligns with the introduction of audit logging and related features for GDPR requirements.
Linked Issues Check ✅ Passed The implementation introduces structured audit logging for all events specified in issue #5378—including user login, data view, data creation, updates, deletions, ACL modifications, schema changes, and push sends—using a separate log file per GDPR requirements and integrates tests covering each case.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

security/snyk check is complete. No issues have been found. (View Details)

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.

Comment on lines 971 to 982
this.config.auditLogController.logDataView({
auth: this.auth,
req: { config: this.config }, // Minimal req object since we don't have access to full request here
className: this.className,
query: this.restWhere,
resultCount: this.response.results.length,
objectIds: objectIds,
});
} catch (error) {
// Don't fail the query if audit logging fails
console.error('Audit logging error:', 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 | 🟠 Major

Use Parse logger instead of console.error.

console.error violates our lint rules and bypasses the configured logger. Please swap this for this.config.logger.error(...) (or the equivalent) so lint passes and we respect centralized logging.

Here’s a concrete fix:

-  } catch (error) {
-    // Don't fail the query if audit logging fails
-    console.error('Audit logging error:', error);
-  }
+  } catch (error) {
+    // Don't fail the query if audit logging fails
+    this.config.logger?.error?.('Audit logging error', { error });
+  }
📝 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
this.config.auditLogController.logDataView({
auth: this.auth,
req: { config: this.config }, // Minimal req object since we don't have access to full request here
className: this.className,
query: this.restWhere,
resultCount: this.response.results.length,
objectIds: objectIds,
});
} catch (error) {
// Don't fail the query if audit logging fails
console.error('Audit logging error:', error);
}
this.config.auditLogController.logDataView({
auth: this.auth,
req: { config: this.config }, // Minimal req object since we don't have access to full request here
className: this.className,
query: this.restWhere,
resultCount: this.response.results.length,
objectIds: objectIds,
});
} catch (error) {
// Don't fail the query if audit logging fails
this.config.logger?.error?.('Audit logging error', { error });
}
🧰 Tools
🪛 GitHub Check: Lint

[warning] 981-981:
Unexpected console statement

🤖 Prompt for AI Agents
In src/RestQuery.js around lines 971 to 982, replace the console.error call in
the audit logging catch block with the Parse-configured logger: call
this.config.logger.error(...) (or this.config.logger.warn(...) if appropriate)
and pass a descriptive message plus the caught error object so the centralized
logger records the failure without throwing (e.g., "Audit logging error" and the
error). Keep the surrounding try/catch behavior so the query does not fail when
audit logging fails.

Comment on lines 1857 to 1860
} catch (error) {
// Don't fail the write if audit logging fails
console.error('Audit logging error:', 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

Replace console.error with logger.

Using console.error is flagged by the linter and inconsistent with the rest of the codebase. The file already imports logger at line 18.

Apply this diff:

  } catch (error) {
    // Don't fail the write if audit logging fails
-    console.error('Audit logging error:', error);
+    logger.error('Audit logging error:', error);
  }
📝 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
} catch (error) {
// Don't fail the write if audit logging fails
console.error('Audit logging error:', error);
}
} catch (error) {
// Don't fail the write if audit logging fails
logger.error('Audit logging error:', error);
}
🧰 Tools
🪛 GitHub Check: Lint

[warning] 1859-1859:
Unexpected console statement

🤖 Prompt for AI Agents
In src/RestWrite.js around lines 1857 to 1860, replace the console.error call
used for audit logging failures with the project's logger (imported at line 18);
call logger.error with the same descriptive message and include the error
details so the log remains informative and consistent with the codebase's
logging practice.

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.

Comment on lines +34 to +37
- You get automatic compliance with GDPR Article 30 (Records of Processing Activities)
- You have an audit trail for Article 33 (Data Breach Notification)
- You have evidence for Article 32 (Security of Processing)

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

Avoid compliance guarantees; soften language

The guide currently implies “automatic compliance” with Articles 30/33/32. The feature “supports” compliance but does not guarantee it. Please rephrase.

- You get automatic compliance with GDPR Article 30 (Records of Processing Activities)
- You have an audit trail for Article 33 (Data Breach Notification)
- You have evidence for Article 32 (Security of Processing)
+ Helps you maintain records relevant to Article 30 (Records of Processing Activities)
+ Provides audit trails that support Article 33 (Breach Notification)
+ Provides evidence that can support Article 32 (Security of Processing)
📝 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
- You get automatic compliance with GDPR Article 30 (Records of Processing Activities)
- You have an audit trail for Article 33 (Data Breach Notification)
- You have evidence for Article 32 (Security of Processing)
- Helps you maintain records relevant to Article 30 (Records of Processing Activities)
- Provides audit trails that support Article 33 (Breach Notification)
- Provides evidence that can support Article 32 (Security of Processing)
🤖 Prompt for AI Agents
In GDPR_COMPLIANCE_GUIDE.md around lines 34 to 37, the current wording
incorrectly promises "automatic compliance" and definitive claims for Articles
30/33/32; change these lines to soften the language to say the product or
feature "helps support" or "can assist with" meeting the requirements, that it
"provides tools or an audit trail to help with" Article 33, and that it "can
help provide evidence for" Article 32, so the copy indicates support rather than
guaranteed compliance.

Comment on lines +79 to +116
```javascript
// Cloud Code function to export user data
Parse.Cloud.define('exportMyData', async (request) => {
const exportData = {
exportDate: new Date().toISOString(),
user: request.user.toJSON(),
relatedData: {}
};

// Query all classes in parallel for better performance
const [orders, reviews, comments] = await Promise.all([
new Parse.Query('Order')
.equalTo('user', request.user)
.find({ useMasterKey: true }),

new Parse.Query('Review')
.equalTo('author', request.user)
.find({ useMasterKey: true }),

new Parse.Query('Comment')
.equalTo('author', request.user)
.find({ useMasterKey: true }),

// Add more queries as needed for your application
]);

exportData.relatedData.orders = orders.map(o => o.toJSON());
exportData.relatedData.reviews = reviews.map(r => r.toJSON());
exportData.relatedData.comments = comments.map(c => c.toJSON());

// Include audit logs for this user
// (You'll need to query your audit log files)

return exportData;
}, {
requireUser: 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 | 🟠 Major

Export example: avoid leaking sensitive fields; select explicit user fields

request.user.toJSON() may include fields you don’t intend to export (e.g., sessionToken under some flows or private attrs). Recommend explicit whitelisting.

-  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: {}
+  };
📝 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
```javascript
// Cloud Code function to export user data
Parse.Cloud.define('exportMyData', async (request) => {
const exportData = {
exportDate: new Date().toISOString(),
user: request.user.toJSON(),
relatedData: {}
};
// Query all classes in parallel for better performance
const [orders, reviews, comments] = await Promise.all([
new Parse.Query('Order')
.equalTo('user', request.user)
.find({ useMasterKey: true }),
new Parse.Query('Review')
.equalTo('author', request.user)
.find({ useMasterKey: true }),
new Parse.Query('Comment')
.equalTo('author', request.user)
.find({ useMasterKey: true }),
// Add more queries as needed for your application
]);
exportData.relatedData.orders = orders.map(o => o.toJSON());
exportData.relatedData.reviews = reviews.map(r => r.toJSON());
exportData.relatedData.comments = comments.map(c => c.toJSON());
// Include audit logs for this user
// (You'll need to query your audit log files)
return exportData;
}, {
requireUser: true
});
```
// Cloud Code function to export user data
Parse.Cloud.define('exportMyData', async (request) => {
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: {}
};
// Query all classes in parallel for better performance
const [orders, reviews, comments] = await Promise.all([
new Parse.Query('Order')
.equalTo('user', request.user)
.find({ useMasterKey: true }),
new Parse.Query('Review')
.equalTo('author', request.user)
.find({ useMasterKey: true }),
new Parse.Query('Comment')
.equalTo('author', request.user)
.find({ useMasterKey: true }),
// Add more queries as needed for your application
]);
exportData.relatedData.orders = orders.map(o => o.toJSON());
exportData.relatedData.reviews = reviews.map(r => r.toJSON());
exportData.relatedData.comments = comments.map(c => c.toJSON());
// Include audit logs for this user
// (You'll need to query your audit log files)
return exportData;
}, {
requireUser: true
});
🤖 Prompt for AI Agents
In GDPR_COMPLIANCE_GUIDE.md around lines 79 to 116, the example uses
request.user.toJSON() which can leak sensitive/private fields (e.g.,
sessionToken, internal attrs); replace that call with an explicit whitelist of
user fields to export (e.g., id, username, email, createdAt) by building a new
object from request.user.get(...) or by picking only allowed keys, ensure you
explicitly omit sessionToken and other privateAttrs, and apply the same
explicit-field selection/redaction to related objects (map each Parse.Object to
only the approved fields) before returning exportData.

Comment on lines +168 to +176
// ANONYMIZE financial records (legal requirement to retain)
for (const order of allOrders) {
order.set('user', null);
order.set('userName', 'DELETED_USER');
order.set('userEmail', '[email protected]');
order.set('userPhone', null);
await order.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 | 🟠 Major

Anonymization example: avoid email collisions and pseudo‑PII

Using a fixed value '[email protected]' will violate unique email constraints and can trigger accidental messages. Prefer nulling or generating a non‑contactable, unique placeholder.

-    order.set('userName', 'DELETED_USER');
-    order.set('userEmail', '[email protected]');
-    order.set('userPhone', null);
+    order.set('userName', 'DELETED_USER');
+    order.unset('userEmail'); // or set to a unique nonce if column is required
+    order.set('userPhone', null);

Optionally, if email is required:

const nonce = `deleted+${order.id}@example.invalid`; // RFC‑compliant, non‑deliverable TLD
order.set('userEmail', nonce);
🤖 Prompt for AI Agents
In GDPR_COMPLIANCE_GUIDE.md around lines 168 to 176, the anonymization example
uses a fixed '[email protected]' which can violate unique email constraints
and risk accidental messaging; change the guidance to either set the email field
to null (preferred if schema allows) or generate a unique, non‑deliverable
placeholder per record (e.g., using the record ID or a nonce and a reserved TLD
like example.invalid) so the value is RFC‑compliant, non‑contactable and unique
across records; update the example text to mention uniqueness and the use of
non‑deliverable domains rather than a single constant.

Comment on lines +382 to +387
const inactiveUsers = await new Parse.Query('_User')
.lessThan('lastLoginAt', inactiveThreshold)
.find({ useMasterKey: true });

message(`Found ${inactiveUsers.length} inactive users`);

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

Inactive user logic: _User.lastLoginAt is not a default field

Parse Server does not populate lastLoginAt by default. Use _Session to derive last activity or populate a lastLoginAt field via a login hook/audit event.

-  const inactiveUsers = await new Parse.Query('_User')
-    .lessThan('lastLoginAt', inactiveThreshold)
-    .find({ useMasterKey: true });
+  // Derive last activity from sessions (latest session per user older than threshold)
+  const sessions = await new Parse.Query('_Session')
+    .lessThan('createdAt', inactiveThreshold)
+    .include('user')
+    .find({ useMasterKey: true });
+  const inactiveUsers = [...new Set(sessions.map(s => s.get('user')))];

Alternatively: maintain lastLoginAt yourself in an auth flow and query that field.

📝 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 inactiveUsers = await new Parse.Query('_User')
.lessThan('lastLoginAt', inactiveThreshold)
.find({ useMasterKey: true });
message(`Found ${inactiveUsers.length} inactive users`);
// Derive last activity from sessions (latest session per user older than threshold)
const sessions = await new Parse.Query('_Session')
.lessThan('createdAt', inactiveThreshold)
.include('user')
.find({ useMasterKey: true });
const inactiveUsers = [...new Set(sessions.map(s => s.get('user')))];
message(`Found ${inactiveUsers.length} inactive users`);
🤖 Prompt for AI Agents
In GDPR_COMPLIANCE_GUIDE.md around lines 382 to 387, the snippet queries
_User.lastLoginAt which is not a default Parse Server field; update the guidance
to either (a) compute last activity from the _Session class (query _Session for
newest session per user and derive lastLoginAt), or (b) instruct implementers to
populate and maintain a lastLoginAt field during login/logout hooks or audit
events and then query that field; clearly show the recommended implementation
approach and example query flow for using _Session or the login hook approach.

Comment on lines +847 to +853
### Parse Server GDPR Documentation
- [GDPR_AUDIT_LOGGING.md](./GDPR_AUDIT_LOGGING.md) - Audit logging setup and configuration

### External Resources
- [Official GDPR Text](https://gdpr-info.eu/) - Complete regulation text
- [ICO GDPR Guide](https://ico.org.uk/for-organisations/guide-to-data-protection/guide-to-the-general-data-protection-regulation-gdpr/) - UK guidance
- [CNIL GDPR Resources](https://www.cnil.fr/en/home) - French supervisory authority
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

Cross‑doc consistency: ensure referenced doc exists and matches options

Verify that GDPR_AUDIT_LOGGING.md exists in this PR and that its option names/examples match this guide.


🏁 Script executed:

#!/bin/bash
# Check doc presence and option consistency
fd -a GDPR_AUDIT_LOGGING.md || true
rg -n "auditLog" GDPR_AUDIT_LOGGING.md -C2 || true

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Search for any markdown files related to audit or GDPR
fd -e md | grep -i audit || true
fd -e md | grep -i gdpr || true
# Search within markdown files for audit logging references
grep -Rin "audit logging" --include "*.md" . || true

Length of output: 819


Fix broken link to GDPR_AUDIT_LOGGING.md
The guide references GDPR_AUDIT_LOGGING.md (GDPR_COMPLIANCE_GUIDE.md line 848), but that file is missing. Add the file or update the link to the correct document.

🤖 Prompt for AI Agents
In GDPR_COMPLIANCE_GUIDE.md around lines 847–853 the markdown links include a
reference to GDPR_AUDIT_LOGGING.md which does not exist; either add a new
GDPR_AUDIT_LOGGING.md file with the audit logging content into the repo (and
commit it alongside updating any frontmatter/indexes), or update the link here
to point to the correct existing document (find the actual audit-logging
filename/path in the repo and replace ./GDPR_AUDIT_LOGGING.md with that path);
ensure the link is relative and valid, and run a link-check or preview to
confirm no broken links remain.

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
2 participants