Skip to content

feat: Replace CryptoJS with Web Crypto #2501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: alpha
Choose a base branch
from

Conversation

dplewis
Copy link
Member

@dplewis dplewis commented Mar 13, 2025

Pull Request

Issue

Active development of CryptoJS has been discontinued. Nowadays, NodeJS and modern browsers have a native crypto module. This allows the SDK to have isomorphic encryption.

Closes: #2494

Approach

  • Replace Crypto-JS ASE-CBC with Web Crypto ASE-GCM-256 due to passphases key diveration being incompatible (could change in the future) and GCM is more secure Potential breaking change
  • Remove Parse.enableEncryptedUser(), Parse.encryptedUser, Parse.isEncryptedUser as they are not needed Breaking Change
  • Remove default support for React-Native, developers will now have to polyfill webcrypto Breaking Change
  • CryptoJS is syncronous while Web Crypto is asyncronous meaning some functions won't be available when this feature is turned on, like when using AsyncStorage. Breaking Change
    • Parse.User.current
    • Parse.User.isCurrent
  • Update Readme with instructions to create a custom crypto controller
  • Remove dependency on crypto-js and react-native-crypto-js
  • Developers will no longer have to install the optional dependency crypto-js along side parse

Due to the encryption and decryption method being different, developers who used this feature before won't be able to decrypt users that are already logged in but not cached on disk. I'm not sure how many developers were using this feature as it was undocumented. There are two ways for devs to solve this.

  1. Forcefully logging out users (deleting sessions). This will clean up the local storage.
  2. Follow the readme instructions and use the old crypto-js CryptoController as your custom controller. This will require the developers to install those packages.

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)

Summary by CodeRabbit

  • New Features

    • Added support for encrypting local storage using the Web Crypto API, with improved documentation and examples for enabling encryption and customizing encryption controllers.
  • Bug Fixes

    • Improved compatibility for encrypted storage across browsers and platforms, including React Native, by using native Web Crypto API and polyfills where necessary.
  • Refactor

    • Transitioned all encryption and decryption operations to asynchronous methods for enhanced security and performance.
    • Removed deprecated properties and methods related to user encryption settings.
  • Chores

    • Updated Node.js version requirement.
    • Removed unused cryptography dependencies.
  • Tests

    • Updated and expanded test coverage to reflect asynchronous encryption, including new and revised tests for encrypted user handling.

Copy link

parse-github-assistant bot commented Mar 13, 2025

🚀 Thanks for opening this pull request!

Copy link

codecov bot commented Mar 13, 2025

Codecov Report

❌ Patch coverage is 92.59259% with 6 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (alpha@c9d487b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/CryptoController.ts 87.75% 6 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             alpha    #2501   +/-   ##
========================================
  Coverage         ?   99.90%           
========================================
  Files            ?       63           
  Lines            ?     6216           
  Branches         ?     1478           
========================================
  Hits             ?     6210           
  Misses           ?        6           
  Partials         ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dplewis dplewis changed the title feat: Replace crypto-js with webcrypto feat: Replace CryptoJS with Web Crypto Mar 13, 2025
@dplewis dplewis requested a review from a team March 13, 2025 16:39
@dplewis dplewis mentioned this pull request Mar 14, 2025
2 tasks
@dplewis
Copy link
Member Author

dplewis commented Mar 14, 2025

@mtrezza Do you want to plan a Version 7.0.0 release?

@mtrezza
Copy link
Member

mtrezza commented Mar 14, 2025

The thing is that we wouldn't be able to upgrade the JS SDK in Parse Server until December 2025 if we merged this. So we can't really merge this so easily with our current branch setup. We could tag this PR for merge on next major release.

@dplewis
Copy link
Member Author

dplewis commented Mar 14, 2025

What does the server have to do with this?

@mtrezza
Copy link
Member

mtrezza commented Mar 15, 2025

The Parse JS SDK is a dependency of Parse Sever.

@dplewis
Copy link
Member Author

dplewis commented Mar 15, 2025

The server has a lot of dependencies, because the SDK breaks doesn’t mean the server breaks.

In the case of this PR. You can’t log in on the server, there is no local storage like the browser, there is nothing to encrypt, so this doesn’t break the server.

@mtrezza
Copy link
Member

mtrezza commented Mar 16, 2025

The difference is that the Parse JS SDK is a fully exposed API in Cloud Code, not merely a "hidden" dependency (which we want to change). The "hidden" dependencies are tested indirectly through the Parse Server API tests. The fully exposed JS SDK API may not be completely tested in Parse Server because not all of its APIs apply there, as you mentioned. Because of that risk, major version increments in tandem are safer. If we decide to go ahead and merge a major version upgrade of the JS SDK, we'd need to make sure that even ineffective APIs of the JS SDK do not break anything, for example methods that wouldn't make sense to be called in Cloud Code, but fail gracefully or have no effect at all. I agree that we could do the upgrade, but it requires some extra care. And it would need to be a dedicated major release, meaning we cannot combine it with other breaking changes, as we usually would do to minimize the number of major releases. Are there any other PRs that should go into a 7.0.0 release?

@dplewis
Copy link
Member Author

dplewis commented Mar 20, 2025

For version 7.0.0

Signed-off-by: Diamond Lewis <findlewis@gmail.com>
@mtrezza
Copy link
Member

mtrezza commented Mar 21, 2025

Which one of these are breaking and are there interdependencies? If only this crypto PR is breaking, then we can do a 7.0.0 release without waiting for the other ones. Since this is a "special" major release, it would make it also easier to control.

Copy link

coderabbitai bot commented Jul 29, 2025

📝 Walkthrough

Walkthrough

This update replaces the SDK’s user data encryption implementation from CryptoJS to the Web Crypto API (and Node.js crypto module for Node environments). All encryption and decryption methods are now asynchronous, and the API for custom CryptoControllers has changed accordingly. Related tests, documentation, and type declarations were updated to reflect these changes.

Changes

Cohort / File(s) Change Summary
Node.js Version & Dependencies
.nvmrc, package.json
Updated Node.js version from 20.15.0 to 20.19.0. Removed react-native-crypto-js and crypto-js dependencies.
Documentation
README.md
Added detailed documentation for encrypting local storage, using the CryptoController, and guidance for custom controllers and polyfills.
Crypto Controller Implementation
src/CryptoController.ts
Replaced CryptoJS-based encryption with Web Crypto API (and Node.js crypto). All encryption/decryption is now async.
CoreManager & Types
src/CoreManager.ts, types/CoreManager.d.ts, types/CryptoController.d.ts, types/Parse.d.ts
Changed CryptoController to a discriminated union supporting both sync and async variants. Updated method signatures and removed ENCRYPTED_USER config. Updated type declarations accordingly.
Parse API Changes
src/Parse.ts, types/Parse.d.ts
Removed encryptedUser property and related methods. Only secret remains for encryption key management.
User Data Encryption Logic
src/ParseUser.ts
Made user disk operations and current user retrieval fully async for encrypted storage. Updated logic to use ENCRYPTED_KEY and enforce async APIs when encryption is enabled.
Integration & Unit Tests
integration/test/ParseDistTest.js, integration/test/ParseReactNativeTest.js, integration/test/ParseUserTest.js, src/__tests__/Parse-test.js, src/__tests__/ParseUser-test.js, src/__tests__/browser-test.js, src/__tests__/react-native-test.js
Updated and added tests to use async encryption APIs, removed references to deprecated flags, set up global Web Crypto API polyfills for tests, and added/adjusted tests to verify new async behavior and error handling.

Sequence Diagram(s)

sequenceDiagram
    participant App
    participant Parse
    participant CoreManager
    participant CryptoController
    participant Storage

    App->>Parse: Set secret key
    App->>Parse: Sign up or log in user
    Parse->>CoreManager: Get CryptoController
    CoreManager->>CryptoController: encrypt(json, secret)
    CryptoController-->>CoreManager: Promise<encryptedData>
    CoreManager-->>Parse: encryptedData
    Parse->>Storage: Store encryptedData

    App->>Parse: Retrieve current user (async)
    Parse->>Storage: Get encryptedData
    Parse->>CoreManager: Get CryptoController
    CoreManager->>CryptoController: decrypt(encryptedData, secret)
    CryptoController-->>CoreManager: Promise<json>
    CoreManager-->>Parse: json
    Parse-->>App: user object
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
Replace crypto-js and react-native-crypto-js with node:crypto and document isomorphic/async CryptoController (Issue #2494)
Remove deprecated/optional dependencies (Issue #2494)
Update documentation for new encryption approach and custom controller usage (Issue #2494)
Ensure encryption logic uses Web Crypto API and async patterns (Issue #2494)

Suggested reviewers

  • mtrezza

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 708f939 and dcf12df.

📒 Files selected for processing (1)
  • src/CryptoController.ts (1 hunks)
⏰ 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). (3)
  • GitHub Check: build (Node 20, 20.15.1)
  • GitHub Check: build (Node 22, 22.4.1)
  • GitHub Check: build (Node 18, 18.20.4)
🔇 Additional comments (6)
src/CryptoController.ts (6)

1-13: LGTM! Solid environment detection logic.

The implementation correctly detects browser vs Node.js environments and properly initializes the Web Crypto API and text encoding/decoding utilities for each context.


15-28: Good implementation with proper environment handling.

The base64 conversion functions correctly handle both browser and Node.js environments. Note that the spread operator in String.fromCharCode(...new Uint8Array(buff)) may hit call stack limits with very large buffers, but this should be acceptable for typical encryption scenarios.


30-31: Secure key import implementation.

The key import correctly uses the Web Crypto API with appropriate security settings (non-extractable, restricted usage).


33-45: Excellent cryptographic configuration.

The PBKDF2 parameters are well-chosen: 250,000 iterations exceed security recommendations, SHA-256 is robust, and AES-GCM-256 provides strong authenticated encryption. The parameterized key usage properly separates encrypt/decrypt operations.


47-63: Robust encryption implementation.

The encrypt method properly generates random salt and IV, uses secure AES-GCM encryption, and concatenates the components in a logical order. The async: 1 property correctly indicates this controller's asynchronous nature.


65-81: Well-implemented decryption with proper error handling.

The decrypt method includes appropriate validation, error handling, and matches the encryption format. The buffer length check prevents malformed data issues, and the try-catch provides meaningful error messages.


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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Jul 29, 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: 4

🧹 Nitpick comments (4)
src/CoreManager.ts (1)

32-42: Excellent discriminated union design for supporting both sync and async crypto controllers.

The type definition properly:

  • Uses async property for type discrimination
  • Provides clear return types (string vs Promise<string>)
  • Updates parameter names for better clarity (json, parseSecret)

Consider making the decrypt method parameter naming consistent between variants - both should use parseSecret instead of secretKey for the second parameter to match the encrypt method.

 type CryptoController =
   | {
       async: 0;
       encrypt: (json: any, parseSecret: any) => string;
-      decrypt: (encryptedJSON: string, secretKey: any) => string;
+      decrypt: (encryptedJSON: string, parseSecret: any) => string;
     }
   | {
       async: 1;
       encrypt: (json: any, parseSecret: any) => Promise<string>;
-      decrypt: (encryptedJSON: string, secretKey: any) => Promise<string>;
+      decrypt: (encryptedJSON: string, parseSecret: any) => Promise<string>;
     };
README.md (1)

118-147: Excellent documentation for the new encryption functionality.

The documentation clearly explains:

  • How to enable encryption using Parse.secret
  • Web Crypto API usage and polyfill requirements
  • Custom CryptoController implementation pattern

The custom controller example correctly demonstrates the new async interface with async: 1 and Promise-returning methods.

Add language specifiers to the code blocks to address linting warnings:

-```
+```js
 // Set your key to enable encryption, this key will be passed to the CryptoController
 Parse.secret = 'MY_SECRET_KEY'; // or Parse.CoreManager.set('ENCRYPTED_KEY', 'MY_SECRET_KEY');

- +js
const CustomCryptoController = {


</blockquote></details>
<details>
<summary>src/ParseUser.ts (1)</summary><blockquote>

`1081-1117`: **Clean refactor to async/await pattern.**

The conversion from promise chains to async/await improves readability and maintains the same functionality. The null checks and error handling are properly preserved.


Consider extracting the user data processing logic (lines 1099-1113) into a helper function to reduce duplication with the synchronous `currentUser` method:

```diff
+  _processUserData(userData: any): ParseUser {
+    userData = JSON.parse(userData);
+    if (!userData.className) {
+      userData.className = '_User';
+    }
+    if (userData._id) {
+      if (userData.objectId !== userData._id) {
+        userData.objectId = userData._id;
+      }
+      delete userData._id;
+    }
+    if (userData._sessionToken) {
+      userData.sessionToken = userData._sessionToken;
+      delete userData._sessionToken;
+    }
+    return ParseObject.fromJSON(userData) as ParseUser;
+  },

   async currentUserAsync(): Promise<ParseUser | null> {
     // ... existing code ...
-    userData = JSON.parse(userData);
-    if (!userData.className) {
-      userData.className = '_User';
-    }
-    if (userData._id) {
-      if (userData.objectId !== userData._id) {
-        userData.objectId = userData._id;
-      }
-      delete userData._id;
-    }
-    if (userData._sessionToken) {
-      userData.sessionToken = userData._sessionToken;
-      delete userData._sessionToken;
-    }
-    const current = ParseObject.fromJSON(userData) as ParseUser;
+    const current = DefaultController._processUserData(userData);
     currentUserCache = current;
     current._synchronizeAllAuthData();
     return Promise.resolve(current);
   },
types/Parse.d.ts (1)

53-70: Fix parameter naming inconsistency.

The second parameter of the decrypt method is named secretKey in the type definition but parseSecret in the implementation. They should be consistent.

         setCryptoController(controller: {
             async: 0;
             encrypt: (json: any, parseSecret: any) => string;
-            decrypt: (encryptedJSON: string, secretKey: any) => string;
+            decrypt: (encryptedJSON: string, parseSecret: any) => string;
         } | {
             async: 1;
             encrypt: (json: any, parseSecret: any) => Promise<string>;
-            decrypt: (encryptedJSON: string, secretKey: any) => Promise<string>;
+            decrypt: (encryptedJSON: string, parseSecret: any) => Promise<string>;
         }): void;
         getCryptoController(): {
             async: 0;
             encrypt: (json: any, parseSecret: any) => string;
-            decrypt: (encryptedJSON: string, secretKey: any) => string;
+            decrypt: (encryptedJSON: string, parseSecret: any) => string;
         } | {
             async: 1;
             encrypt: (json: any, parseSecret: any) => Promise<string>;
-            decrypt: (encryptedJSON: string, secretKey: any) => Promise<string>;
+            decrypt: (encryptedJSON: string, parseSecret: any) => Promise<string>;
         };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9d487b and 52ad04c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • .nvmrc (1 hunks)
  • README.md (3 hunks)
  • integration/test/ParseDistTest.js (1 hunks)
  • integration/test/ParseReactNativeTest.js (1 hunks)
  • integration/test/ParseUserTest.js (1 hunks)
  • package.json (0 hunks)
  • src/CoreManager.ts (1 hunks)
  • src/CryptoController.ts (1 hunks)
  • src/Parse.ts (1 hunks)
  • src/ParseUser.ts (3 hunks)
  • src/__tests__/Parse-test.js (2 hunks)
  • src/__tests__/ParseUser-test.js (3 hunks)
  • src/__tests__/browser-test.js (1 hunks)
  • src/__tests__/react-native-test.js (3 hunks)
  • types/CoreManager.d.ts (1 hunks)
  • types/CryptoController.d.ts (1 hunks)
  • types/Parse.d.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • package.json
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/__tests__/browser-test.js (3)
src/__tests__/Parse-test.js (1)
  • require (13-13)
src/__tests__/RESTController-test.js (1)
  • require (11-11)
src/__tests__/react-native-test.js (1)
  • require (30-30)
src/__tests__/Parse-test.js (3)
src/__tests__/RESTController-test.js (1)
  • require (11-11)
src/__tests__/browser-test.js (1)
  • require (16-16)
src/__tests__/react-native-test.js (1)
  • require (30-30)
integration/test/ParseDistTest.js (3)
integration/test/cloud/main.js (1)
  • user (17-17)
src/ParseUser.ts (1)
  • current (602-608)
src/ParseGeoPoint.ts (1)
  • current (195-205)
src/CoreManager.ts (2)
integration/test/ParseReactNativeTest.js (1)
  • CryptoController (5-5)
src/__tests__/ParseUser-test.js (1)
  • CryptoController (35-35)
src/ParseUser.ts (2)
src/ParseConfig.ts (2)
  • current (63-66)
  • current (137-167)
src/ParseSession.ts (1)
  • current (58-69)
src/__tests__/react-native-test.js (5)
src/__tests__/Parse-test.js (1)
  • require (13-13)
src/__tests__/RESTController-test.js (1)
  • require (11-11)
integration/test/ParseReactNativeTest.js (2)
  • require (4-4)
  • CryptoController (5-5)
src/__tests__/browser-test.js (1)
  • require (16-16)
src/__tests__/ParseUser-test.js (1)
  • CryptoController (35-35)
🪛 markdownlint-cli2 (0.17.2)
README.md

123-123: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


132-132: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


174-174: Link and image reference definitions should be needed
Unused link or image reference definition: "react-native-webview-crypto"

(MD053, link-image-reference-definitions)


175-175: Link and image reference definitions should be needed
Unused link or image reference definition: "types-parse"

(MD053, link-image-reference-definitions)

🔇 Additional comments (21)
.nvmrc (1)

1-1: LGTM - Node.js version update supports Web Crypto API migration.

The Node.js version bump from 20.15.0 to 20.19.0 aligns well with the migration from CryptoJS to Web Crypto API, ensuring better support for native crypto modules and asynchronous cryptographic operations.

integration/test/ParseReactNativeTest.js (1)

54-57: LGTM - Correctly updated to asynchronous crypto operations.

The change from synchronous to asynchronous decryption using await properly adapts to the new Web Crypto API-based CryptoController interface. This maintains the test's functionality while aligning with the SDK's migration from CryptoJS to Web Crypto API.

src/__tests__/browser-test.js (1)

16-18: LGTM - Essential setup for Web Crypto API testing.

Adding TextEncoder and TextDecoder to the global scope is necessary for Web Crypto API operations in the test environment. This change is consistent with similar setups in other test files and ensures proper string-to-ArrayBuffer conversions during cryptographic operations.

src/__tests__/Parse-test.js (2)

13-15: LGTM - Essential setup for Web Crypto API testing.

Adding TextEncoder and TextDecoder to the global scope is necessary for Web Crypto API operations in the test environment. This follows the same pattern established in other test files and ensures proper cryptographic functionality.


255-255: LGTM - Proper console output management in tests.

Adding the console.log spy helps manage console output during the IndexedDB storage test, which is a standard testing practice for cleaner test output.

integration/test/ParseUserTest.js (1)

1158-1161: LGTM - Correctly updated to asynchronous crypto operations.

The change from synchronous to asynchronous decryption using await properly adapts to the new Web Crypto API-based CryptoController interface. This maintains the test's functionality while ensuring compatibility with the SDK's migration from CryptoJS to Web Crypto API.

integration/test/ParseDistTest.js (1)

87-101: LGTM! Good test coverage for the new async encryption functionality.

The test correctly demonstrates the new encryption workflow:

  • Uses Parse.secret to enable encryption
  • Leverages Parse.User.currentAsync() for async user retrieval when encryption is enabled
  • Properly cleans up the secret key after testing

This validates the end-to-end encryption behavior in a browser environment.

types/CryptoController.d.ts (1)

1-6: Type definitions correctly reflect the async CryptoController interface.

The updated interface properly:

  • Adds the async: number discriminator property
  • Changes methods to return Promise<string> for async operations
  • Updates parameter names to match the implementation (json, parseSecret)
src/Parse.ts (1)

252-252: Good documentation improvement.

Changing from @property to @member is more appropriate since this field has both getter and setter methods.

src/__tests__/ParseUser-test.js (5)

779-792: LGTM! Correct migration to async methods.

The changes properly replace synchronous calls with their async counterparts to support the new asynchronous crypto controller.


796-796: LGTM! Proper async/await usage for decrypt.

The decrypt method is correctly awaited since the crypto controller is now asynchronous.


806-806: Good practice: Proper test cleanup.

Storing and restoring the original CryptoController ensures test isolation and prevents side effects on other tests.

Also applies to: 856-856


830-830: LGTM! Correct discriminated union type implementation.

The async: 0 property correctly identifies this as a synchronous crypto controller, matching the discriminated union type definition.


860-870: Good test coverage for error handling.

This test ensures that attempting to use synchronous methods with async encryption properly throws an error, preventing runtime issues.

src/__tests__/react-native-test.js (2)

30-40: LGTM! Proper Web Crypto API setup for React Native.

The setup correctly configures TextEncoder/TextDecoder and the global crypto object with Web Crypto API's subtle interface, which is required for the new encryption implementation.


56-60: LGTM! Test correctly validates async encryption.

The test properly validates that the CryptoController uses the Web Crypto API's subtle.encrypt method.

types/CoreManager.d.ts (1)

31-39: Excellent type design with discriminated union.

The discriminated union type elegantly supports both synchronous and asynchronous crypto controllers, enabling backward compatibility while supporting the new Web Crypto API. The updated parameter names (json, parseSecret, encryptedJSON) are more descriptive than the previous generic names.

src/ParseUser.ts (2)

1002-1015: LGTM! Proper async conversion of updateUserOnDisk.

The method correctly handles async encryption and storage operations. The configuration key change from 'ENCRYPTED_USER' to 'ENCRYPTED_KEY' is consistently applied.


1044-1050: Good error handling for async crypto with sync calls.

The error check prevents runtime issues by throwing a descriptive error when attempting synchronous access with async encryption enabled.

src/CryptoController.ts (2)

20-35: LGTM! Secure key derivation implementation.

The key derivation uses industry-standard PBKDF2 with appropriate parameters (250,000 iterations, SHA-256, AES-GCM-256).


1-13: Environment detection and global APIs are covered by Node.js ≥18

The package.json already enforces engines.node: "18 || 19 || 20 || 22", and Node.js 18+ provides both crypto.webcrypto and global btoa/atob out of the box. No additional polyfills are needed—keeping the existing browser vs. Node.js branch logic is sufficient.

@dplewis
Copy link
Member Author

dplewis commented Jul 29, 2025

@mtrezza I fix the conflicts this is ready for review

mtrezza
mtrezza previously approved these changes Jul 29, 2025
Copy link
Member

@mtrezza mtrezza left a comment

Choose a reason for hiding this comment

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

LGTM, what of the following is a breaking change? Could you rewrite this into individual points? I'm a bit lost with the "Breaking Change" tag

  • Remove Parse.enableEncryptedUser(), Parse.encryptedUser, Parse.isEncryptedUser as they are not needed Breaking Change
  • Remove default support for React-Native, developers will now have to polyfill webcrypto Breaking Change
  • CryptoJS is syncronous while Web Crypto is asyncronous meaning some functions won't be available when this feature is turned on, like when using AsyncStorage. Breaking Change
    • Parse.User.current
    • Parse.User.isCurrent
  • Update Readme with instructions to create a custom crypto controller
  • Remove dependency on crypto-js and react-native-crypto-js
  • Developers will no longer have to install the optional dependency crypto-js along side parse

Due to the encryption and decryption method being different, developers who used this feature before won't be able to decrypt users that are already logged in but not cached on disk. I'm not sure how many developers were using this feature as it was undocumented. There are two ways for devs to solve this.

  1. Forcefully logging out users (deleting sessions). This will clean up the local storage.
  2. Follow the readme instructions and use the old crypto-js CryptoController as your custom controller. This will require the developers to install those packages.

@mtrezza mtrezza added the state:breaking Breaking change requires major version increment and `BREAKING CHANGE` commit message label Jul 29, 2025
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: 1

♻️ Duplicate comments (5)
README.md (3)

33-33: TOC list item syntax is correct.

The table of contents entry uses proper markdown list syntax with a hyphen.


128-128: Reference link syntax is correct.

The markdown reference link syntax for [react-native-webview-crypto] is properly formatted.


170-175: Reference links are properly used.

All reference links in the bottom section are correctly used in the documentation above, so no cleanup is needed.

src/CryptoController.ts (2)

15-28: Buffer conversion functions are correctly implemented.

The base64 conversion functions properly handle both browser and Node.js environments. The previous bug with charCodeAt(null) has been fixed to use charCodeAt(0).


65-74: Decrypt method should return parsed JSON object.

The decrypt method currently returns a decoded string, but based on the interface and usage patterns, it should return the parsed JSON object.

   async decrypt(encryptedJSON: string, parseSecret: any): Promise<string> {
     const buffer = base64ToBuffer(encryptedJSON);
     const salt = buffer.slice(0, 16);
     const iv = buffer.slice(16, 16 + 12);
     const data = buffer.slice(16 + 12);
     const key = await importKey(parseSecret);
     const aesKey = await deriveKey(key, salt, ['decrypt']);
     const decrypted = await webcrypto.subtle.decrypt({ name: 'AES-GCM', iv }, aesKey, data);
-    return decoder.decode(decrypted);
+    const jsonString = decoder.decode(decrypted);
+    return JSON.parse(jsonString);
   },
🧹 Nitpick comments (2)
README.md (2)

123-126: Add language specifier to code block.

The code block should specify the language for proper syntax highlighting.

-```
+```js
 // Set your key to enable encryption, this key will be passed to the CryptoController
 Parse.secret = 'MY_SECRET_KEY'; // or Parse.CoreManager.set('ENCRYPTED_KEY', 'MY_SECRET_KEY');
-```
+```

132-146: Add language specifier to code block.

The code block should specify the language for better readability and syntax highlighting.

-```
+```js
 const CustomCryptoController = {
   async: 1,
   async encrypt(json: any, parseSecret: any): Promise<string> {
     const encryptedJSON = await customEncrypt(json);
     return encryptedJSON;
   },
   async decrypt(encryptedJSON: string, parseSecret: any): Promise<string> {
     const json = await customDecrypt(encryptedJSON);
     return JSON.stringify(json);
   },
 };
 // Must be called before Parse.initialize
 Parse.CoreManager.setCryptoController(CustomCryptoController);
-```
+```
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52ad04c and 708f939.

📒 Files selected for processing (2)
  • README.md (3 hunks)
  • src/CryptoController.ts (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
README.md

123-123: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


132-132: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ 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). (3)
  • GitHub Check: build (Node 20, 20.15.1)
  • GitHub Check: build (Node 18, 18.20.4)
  • GitHub Check: build (Node 22, 22.4.1)
🔇 Additional comments (4)
src/CryptoController.ts (4)

1-13: LGTM! Cross-platform Web Crypto API setup.

The conditional logic properly handles Web Crypto API access across browser and Node.js environments, with appropriate fallbacks for TextEncoder/TextDecoder.


30-31: LGTM! Secure key import implementation.

The key import uses PBKDF2 which is appropriate for password-based key derivation.


33-45: Excellent security parameters for key derivation.

The PBKDF2 configuration uses strong security parameters:

  • 250,000 iterations (exceeds OWASP recommendations)
  • SHA-256 hash function
  • AES-GCM with 256-bit key length

This provides robust protection against brute force attacks.


47-63: LGTM! Secure encryption implementation.

The encryption method follows cryptographic best practices:

  • Uses cryptographically secure random values for salt and IV
  • Proper salt and IV sizes (16 bytes salt, 12 bytes IV for GCM)
  • Correctly concatenates salt + IV + ciphertext
  • Uses AES-GCM which provides authenticated encryption

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
state:breaking Breaking change requires major version increment and `BREAKING CHANGE` commit message
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Replace crypto-js with node:crypto
3 participants