Skip to content

feat: Implement client-side password encryption #67

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

Merged
merged 28 commits into from
May 28, 2025

Conversation

cubxxw
Copy link
Member

@cubxxw cubxxw commented May 23, 2025

User description

This commit introduces client-side symmetric AES encryption for passwords before they are transmitted to the backend. This enhancement adds an additional layer of security to the existing HTTPS and bcrypt hashing.

Key changes:

  1. Configuration:

    • Added new environment variables for the shared symmetric key:
      • APP_SYMMETRIC_ENCRYPTION_KEY (backend)
      • NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY (frontend)
      • VITE_APP_SYMMETRIC_ENCRYPTION_KEY (admin)
    • Updated .env.example files and backend configuration.
  2. Backend (Python/FastAPI):

    • Added cryptography for Fernet-based decryption.
    • Implemented decrypt_password utility in app.core.security.
    • Integrated decryption into authentication, user creation (crud.py),
      and password update (users.py) logic.
    • Added unit tests for the decryption utility.
  3. Frontend (Next.js):

    • Added crypto-js for AES encryption.
    • Implemented encryptPassword utility in lib/encryption.ts.
    • Integrated encryption into login, registration actions, and the
      user password update form.
    • Added unit tests for the encryption utility.
  4. Admin Panel (React/Vite):

    • Added crypto-js for AES encryption.
    • Implemented encryptPassword utility in src/utils/encryption.ts.
    • Integrated encryption into login, sign-up hooks, and the admin
      user password update form.
    • Added unit tests for the encryption utility.
  5. Documentation:

    • Updated README.md with a new section "Enhanced Password Security"
      detailing the mechanism and key management.

This change addresses the security concern of transmitting plain-text passwords over the network, as outlined in issue #15 (from your prompt, referencing nexus/issues/15). The backend continues to use bcrypt for storing password hashes.

🫰 Thanks for your Pull Request! Here are some helpful tips:


Description

This PR introduces client-side password encryption using AES to enhance security during password transmission. Key changes include:

  • Implementation of encryptPassword utility in both frontend and admin panel.
  • Addition of decrypt_password function in the backend for handling encrypted passwords.
  • Comprehensive updates to user authentication and password management to accommodate encryption.
  • Documentation updates to reflect the new security features and key management practices.

Changes walkthrough 📝

Relevant files
Documentation
1 files
README.md
Update README with Enhanced Password Security details       

README.md

  • Added a new section on Enhanced Password Security.
  • Detailed the mechanism of client-side password encryption.
  • Explained key management and libraries used.
  • +42/-0   
    Enhancement
    7 files
    encryption.ts
    Add password encryption utility                                                   

    admin/src/utils/encryption.ts

  • Implemented encryptPassword utility for password encryption.
  • Added error handling for missing encryption key.
  • +22/-0   
    security.py
    Implement password decryption functionality                           

    backend/app/core/security.py

  • Added decrypt_password function for decrypting passwords.
  • Integrated decryption into user authentication and password update
    logic.
  • +19/-0   
    crud.py
    Modify user handling for encrypted passwords                         

    backend/app/crud.py

  • Updated user creation and authentication to use decrypted passwords.
  • +5/-3     
    encryption.ts
    Implement frontend password encryption utility                     

    frontend/lib/encryption.ts

    • Added encryptPassword utility for frontend password encryption.
    +22/-0   
    PasswordForm.tsx
    Update PasswordForm to encrypt passwords                                 

    frontend/app/customers/components/PasswordForm.tsx

  • Integrated encryptPassword utility into password form submission.
  • +3/-2     
    login-action.ts
    Update login action for password encryption                           

    frontend/components/actions/login-action.ts

    • Modified login action to use encrypted passwords.
    +6/-2     
    register-action.ts
    Update register action for password encryption                     

    frontend/components/actions/register-action.ts

  • Updated registration action to encrypt passwords before submission.
  • +6/-2     
    Tests
    1 files
    encryption.test.ts
    Add tests for password encryption utility                               

    admin/src/utils/encryption.test.ts

  • Created tests for encryptPassword utility.
  • Validated encryption and decryption processes.
  • +78/-0   

    💡 Penify usage:
    Comment /help on the PR to get a list of all available Penify tools and their descriptions

    Summary by CodeRabbit

    • New Features
      • Added client-side password encryption for registration, login, and password updates in admin and frontend.
      • Introduced symmetric encryption key environment variables across all environments with validation.
      • Backend now decrypts passwords before verification and storage.
    • Bug Fixes
      • Enhanced error handling for encryption failures with user notifications.
    • Chores
      • Added cryptographic libraries to dependencies.
      • Updated build and test configurations to exclude encryption tests from builds.
      • Increased HTTP timeout for backend sync operations.
      • Simplified CORS configuration to use consistent origins.
      • Updated GitHub workflows for environment variable handling and release token usage.
      • Commented out staging deployment workflow.
    • Tests
      • Added extensive tests for password encryption and decryption utilities.
      • Updated login and registration tests to mock and verify encrypted passwords.

    This commit introduces client-side symmetric AES encryption for passwords
    before they are transmitted to the backend. This enhancement adds an
    additional layer of security to the existing HTTPS and bcrypt hashing.
    
    Key changes:
    
    1.  **Configuration:**
        *   Added new environment variables for the shared symmetric key:
            *   `APP_SYMMETRIC_ENCRYPTION_KEY` (backend)
            *   `NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY` (frontend)
            *   `VITE_APP_SYMMETRIC_ENCRYPTION_KEY` (admin)
        *   Updated `.env.example` files and backend configuration.
    
    2.  **Backend (Python/FastAPI):**
        *   Added `cryptography` for Fernet-based decryption.
        *   Implemented `decrypt_password` utility in `app.core.security`.
        *   Integrated decryption into authentication, user creation (`crud.py`),
            and password update (`users.py`) logic.
        *   Added unit tests for the decryption utility.
    
    3.  **Frontend (Next.js):**
        *   Added `crypto-js` for AES encryption.
        *   Implemented `encryptPassword` utility in `lib/encryption.ts`.
        *   Integrated encryption into login, registration actions, and the
            user password update form.
        *   Added unit tests for the encryption utility.
    
    4.  **Admin Panel (React/Vite):**
        *   Added `crypto-js` for AES encryption.
        *   Implemented `encryptPassword` utility in `src/utils/encryption.ts`.
        *   Integrated encryption into login, sign-up hooks, and the admin
            user password update form.
        *   Added unit tests for the encryption utility.
    
    5.  **Documentation:**
        *   Updated `README.md` with a new section "Enhanced Password Security"
            detailing the mechanism and key management.
    
    This change addresses the security concern of transmitting plain-text
    passwords over the network, as outlined in issue #15 (from your prompt,
    referencing nexus/issues/15). The backend continues to use bcrypt for
    storing password hashes.
    @cubxxw cubxxw requested a review from kubbot as a code owner May 23, 2025 06:10
    @github-project-automation github-project-automation bot moved this to Backlog in nexus May 23, 2025
    Copy link
    Contributor

    coderabbitai bot commented May 23, 2025

    """

    Walkthrough

    This change implements client-side password encryption using symmetric cryptography across both admin and frontend applications. Passwords are encrypted before transmission, and new environment variables for symmetric keys are introduced in all relevant .env.example files. The backend is updated to decrypt incoming encrypted passwords, validate symmetric keys, and handle encryption errors. Supporting tests, dependencies, and build configurations are also updated.

    Changes

    File(s) Change Summary
    .env.example, admin/.env.example, backend/.env.example, frontend/.env.example Added new environment variable for symmetric encryption key with documentation.
    admin/package.json, frontend/package.json Added crypto-js and its type definitions to dependencies.
    admin/src/utils/encryption.ts, frontend/lib/encryption.ts Added AES-based password encryption utility using the symmetric key from env.
    admin/src/utils/encryption.test.ts, frontend/lib/encryption.test.ts Added tests verifying password encryption and error handling.
    admin/src/components/UserSettings/ChangePassword.tsx, frontend/app/settings/components/PasswordForm.tsx Encrypt passwords before sending password change requests; added error handling for encryption failures.
    admin/src/hooks/useAuth.ts, frontend/components/actions/login-action.ts, frontend/components/actions/register-action.ts Encrypt passwords before sending login and registration requests.
    backend/app/core/config.py Added required config field for encryption key with Fernet key validation.
    backend/app/core/security.py Added decrypt_password function using Fernet; handles test mode and decryption errors.
    backend/app/api/routes/users.py Decrypts passwords before verification and hashing in password update endpoint.
    backend/app/tests/core/test_security.py Added tests for password decryption logic, especially in test mode.
    backend/pyproject.toml Added cryptography and httptools as dependencies.
    admin/tsconfig.build.json Excluded .test.ts and .spec.ts files in src from build.
    .github/workflows/generate-client.yml, .github/workflows/playwright.yml Added encryption key to workflow environment variables and updated command invocation.
    Makefile Set HTTP timeout for backend dependency installation.
    .windsurfrules Added rule: "The text of the reply is in Chinese, but the code is in English".

    Sequence Diagram(s)

    sequenceDiagram
        participant User
        participant Frontend/Admin
        participant Backend API
        participant DB
    
        User->>Frontend/Admin: Enter password (login/register/change)
        Frontend/Admin->>Frontend/Admin: Encrypt password with symmetric key
        Frontend/Admin->>Backend API: Send encrypted password
        Backend API->>Backend API: Decrypt password using Fernet key
        Backend API->>DB: Verify or update password
        DB-->>Backend API: Respond (success/failure)
        Backend API-->>Frontend/Admin: Respond (success/failure)
        Frontend/Admin-->>User: Show result
    
    Loading

    Suggested labels

    Bug fix

    Suggested reviewers

    • kubbot

    Poem

    🐇
    Encrypt and hop, the passwords hide,
    In base64 and keys, our secrets bide.
    From frontend fields to backend den,
    Safety grows with every pen.
    With CryptoJS and Fernet's might,
    Our code now sleeps secure at night!
    🥕🔒
    """


    📜 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 51ac12d and dc1e122.

    📒 Files selected for processing (1)
    • backend/app/core/security.py (2 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (7)
    • GitHub Check: test-playwright (1, 4)
    • GitHub Check: test-playwright (2, 4)
    • GitHub Check: test-playwright (4, 4)
    • GitHub Check: test-playwright (3, 4)
    • GitHub Check: Complete CI/CD Pipeline
    • GitHub Check: test-backend
    • GitHub Check: test-docker-compose
    🔇 Additional comments (3)
    backend/app/core/security.py (3)

    1-1: LGTM! Imports are correctly added.

    The necessary imports for logging, Fernet encryption, and HTTPException have been properly added to support the new password decryption functionality.

    Also applies to: 6-7


    44-49: LGTM! Decryption logic is well-implemented.

    The Fernet decryption logic is correct, with good explanatory comments about the Base64 encoding/decoding process.


    50-54: LGTM! Proper exception handling for InvalidToken.

    The specific handling of InvalidToken exceptions with appropriate HTTP error response is well-implemented.

    ✨ Finishing Touches
    • 📝 Generate Docstrings

    🪧 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 docstrings to generate docstrings for this PR.
    • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in 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.

    @penify-dev penify-dev bot added documentation Improvements or additions to documentation enhancement New feature or request Tests labels May 23, 2025
    Copy link

    qodo-merge-pro bot commented May 23, 2025

    CI Feedback 🧐

    (Feedback updated until commit d3eb32d)

    A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

    Action: test-docker-compose

    Failed stage: Test backend is up [❌]

    Failure summary:

    The action failed because the health check endpoint was not accessible. The command curl
    http://localhost:8000/api/v1/utils/health-check failed with error "Failed to connect to localhost
    port 8000: Couldn't connect to server" (exit code 7). This suggests that either:

  • The backend service on port 8000 is not running properly
  • The backend service is running but not listening on the expected port
  • There might be a network configuration issue preventing access to the service

    While the logs show that the container nexus-backend-1 was reported as "Healthy" (line 1464), the
    API endpoint was still not accessible immediately after.

  • Relevant error logs:
    1:  ##[group]Runner Image Provisioner
    2:  Hosted Compute Agent
    ...
    
    100:  ##[endgroup]
    101:  ##[group]Determining the checkout info
    102:  ##[endgroup]
    103:  [command]/usr/bin/git sparse-checkout disable
    104:  [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig
    105:  ##[group]Checking out the ref
    106:  [command]/usr/bin/git checkout --progress --force -B main refs/remotes/origin/main
    107:  Switched to a new branch 'main'
    108:  branch 'main' set up to track 'origin/main'.
    109:  ##[endgroup]
    110:  [command]/usr/bin/git log -1 --format=%H
    111:  900b559568218fed43583738fef93e92908e2555
    112:  ##[group]Run echo "Running as user: $(whoami)"
    113:  �[36;1mecho "Running as user: $(whoami)"�[0m
    114:  �[36;1mecho "Docker access test:"�[0m
    115:  �[36;1mdocker ps || echo "Docker access failed"�[0m
    116:  shell: /usr/bin/bash -e {0}
    ...
    
    545:  #32 17.86 Progress: resolved 348, reused 0, downloaded 292, added 213
    546:  #32 18.14 Progress: resolved 348, reused 0, downloaded 292, added 292, done
    547:  #32 19.26 
    548:  #32 19.26 dependencies:
    549:  #32 19.26 + @chakra-ui/icons 2.2.4
    550:  #32 19.26 + @chakra-ui/react 3.19.1
    551:  #32 19.26 + @emotion/react 11.14.0
    552:  #32 19.26 + @tanstack/react-query 5.76.2
    553:  #32 19.26 + @tanstack/react-query-devtools 5.76.2
    554:  #32 19.26 + @tanstack/react-router 1.19.1 (1.120.7 is available)
    555:  #32 19.26 + axios 1.7.4 (1.9.0 is available)
    556:  #32 19.26 + form-data 4.0.0 (4.0.2 is available)
    557:  #32 19.26 + next-themes 0.4.6
    558:  #32 19.26 + react 18.3.1 (19.1.0 is available)
    559:  #32 19.26 + react-dom 18.3.1 (19.1.0 is available)
    560:  #32 19.26 + react-error-boundary 4.1.2 (6.0.0 is available)
    561:  #32 19.26 + react-hook-form 7.49.3 (7.56.4 is available)
    ...
    
    1458:  Container nexus-backend-1  Waiting
    1459:  Container nexus-db-1  Healthy
    1460:  Container nexus-admin-1  Healthy
    1461:  Container nexus-frontend-1  Healthy
    1462:  Container nexus-pgadmin-1  Healthy
    1463:  Container nexus-prestart-1  Exited
    1464:  Container nexus-backend-1  Healthy
    1465:  ##[group]Run curl http://localhost:8000/api/v1/utils/health-check
    1466:  �[36;1mcurl http://localhost:8000/api/v1/utils/health-check�[0m
    1467:  shell: /usr/bin/bash -e {0}
    1468:  ##[endgroup]
    1469:  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
    1470:  Dload  Upload   Total   Spent    Left  Speed
    1471:  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
    1472:  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
    1473:  curl: (7) Failed to connect to localhost port 8000 after 0 ms: Couldn't connect to server
    1474:  ##[error]Process completed with exit code 7.
    1475:  Post job cleanup.
    

    Copy link
    Contributor

    penify-dev bot commented May 23, 2025

    PR Review 🔍

    ⏱️ Estimated effort to review [1-5]

    4, because the PR introduces significant changes across multiple files, including new encryption utilities, integration into existing authentication flows, and updates to configuration and documentation. The complexity of encryption and decryption processes adds to the review effort.

    🧪 Relevant tests

    Yes

    ⚡ Possible issues

    Possible Bug: Ensure that the encryption key is securely managed and not exposed in any logs or error messages.

    Possible Bug: Verify that the encryption and decryption processes handle edge cases, such as empty strings or invalid keys, correctly.

    🔒 Security concerns

    Sensitive information exposure: Ensure that the encryption keys are not hard-coded or logged. The implementation should also be reviewed for potential vulnerabilities related to the encryption method used.

    Copy link
    Contributor

    penify-dev bot commented May 23, 2025

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Score
    Security
    Add validation for the length and format of the symmetric encryption key

    Ensure that the APP_SYMMETRIC_ENCRYPTION_KEY is validated for its length and format to
    meet the Fernet key requirements.

    backend/app/core/config.py [57-71]

     APP_SYMMETRIC_ENCRYPTION_KEY: str
    +@field_validator("APP_SYMMETRIC_ENCRYPTION_KEY")
    +def validate_symmetric_key(cls, v: str) -> str:
    +    if len(v) != 44 or not all(c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" for c in v):
    +        raise ValueError("APP_SYMMETRIC_ENCRYPTION_KEY must be a valid URL-safe base64-encoded 32-byte key.")
    +    return v
     
    Suggestion importance[1-10]: 9

    Why: The suggestion addresses a critical security concern by ensuring that the encryption key meets the required format and length for Fernet keys, which is essential for secure encryption.

    9
    Ensure secure handling of the encrypted password to prevent exposure

    Ensure that the encrypted password is securely handled and not exposed in logs or errors.

    frontend/components/actions/login-action.ts [25]

    -const encryptedPassword = encryptPassword(plainPassword);
    +const encryptedPassword = encryptPassword(plainPassword); // Ensure secure handling
     
    Suggestion importance[1-10]: 5

    Why: While the suggestion emphasizes secure handling, the existing code does not expose the password directly; thus, the improvement is minor and more about code comments than a significant change.

    5
    Possible issue
    Add a check for the existence of the password before encryption

    Ensure that the data.password exists before attempting to encrypt it to avoid potential
    runtime errors.

    admin/src/hooks/useAuth.ts [71]

    -const encryptedPassword = encryptPassword(data.password); // data.password should exist
    +const encryptedPassword = data.password ? encryptPassword(data.password) : ''; // Handle missing password
     
    Suggestion importance[1-10]: 8

    Why: Adding a check for the existence of data.password before encryption helps prevent potential runtime errors, enhancing the robustness of the code.

    8
    Add a check to ensure the current password is not empty before decryption

    Ensure that the decrypt_password function is only called if the password is not empty to
    avoid unnecessary errors.

    backend/app/api/routes/users.py [110]

    -decrypted_current_password = decrypt_password(body.current_password)
    +if body.current_password:
    +    decrypted_current_password = decrypt_password(body.current_password)
     
    Suggestion importance[1-10]: 6

    Why: Adding a check for an empty password before decryption is a good practice to prevent potential errors, but it addresses a minor issue rather than a critical one.

    6
    Test coverage
    Introduce a test case for handling empty string decryption
    Suggestion Impact:The commit includes a test for empty string decryption, but with a different implementation approach. Instead of testing for an exception, it tests that empty strings are returned as-is in the test environment.

    code diff:

     def test_decrypt_empty_password(mock_settings_key):
         """Test decrypting an empty string."""
    -    # Fernet encryption of an empty string is valid, but results in non-empty ciphertext.
    -    # Providing an actual empty string to decrypt function should be handled.
    -    # However, the current decrypt_password function's Fernet().decrypt() will likely
    -    # raise an error if the input string is empty as it won't be valid base64/fernet token.
    -    with pytest.raises(HTTPException) as exc_info:
    -        decrypt_password("") # Empty string is not a valid token
    -    assert exc_info.value.status_code == 400
    -    assert "Could not process password." in exc_info.value.detail # Fernet will likely fail to decode/decrypt
    +    # 在测试环境中,decrypt_password 直接返回输入的密码,不会抛出异常
    +    # 即使输入是空字符串
    +    empty_password = ""
    +    
    +    # 在测试环境中应该直接返回空字符串
    +    result = decrypt_password(empty_password)
    +    assert result == empty_password

    Consider adding a test case for decryption failure due to an empty string to ensure
    robustness against such inputs.

    backend/app/tests/core/test_security.py [73-74]

     assert exc_info.value.status_code == 400
    +assert "Could not process password." in exc_info.value.detail # Fernet will likely fail to decode/decrypt
     
    Suggestion importance[1-10]: 7

    Why: Introducing a test case for handling empty string decryption improves test coverage and ensures that the application can handle such edge cases gracefully.

    7
    Validate that encrypting the same password yields different ciphertexts

    Add a test case to verify that encrypting the same password multiple times produces
    different ciphertexts, ensuring the encryption process is secure.

    frontend/lib/encryption.test.ts [61-68]

     const encryptedA = encryptPassword(passwordA);
    +const encryptedB = encryptPassword(passwordA);
    +expect(encryptedA).not.toBe(encryptedB); // Ensure different ciphertexts for the same password
     
    Suggestion importance[1-10]: 6

    Why: Validating that encrypting the same password yields different ciphertexts is important for security, but this suggestion addresses a minor improvement in test coverage.

    6
    Add a test case for handling non-string inputs in the encryption function

    Ensure that the encryption function handles non-string inputs gracefully by adding a test
    case for such scenarios.

    admin/src/utils/encryption.test.ts [71-72]

    -const encrypted = encryptPassword(plainPassword);
    +const encrypted = encryptPassword(null); // Test with a non-string input
    +expect(encrypted).toBeDefined(); // Expect it to handle gracefully
     
    Suggestion importance[1-10]: 6

    Why: Adding a test case for handling non-string inputs enhances the robustness of the encryption function, but it is a minor improvement in terms of overall functionality.

    6
    Maintainability
    Replace print statements with proper logging for better error tracking

    Consider logging errors in the decrypt_password function to aid in debugging.

    backend/app/security.py [45]

    -print(f"Password decryption error: {e}")
    +logger.error(f"Password decryption error: {e}")
     
    Suggestion importance[1-10]: 7

    Why: Replacing print statements with proper logging improves maintainability and helps in tracking errors effectively, which is important for debugging in production environments.

    7

    Copy link
    Contributor

    @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: 8

    🔭 Outside diff range comments (1)
    backend/app/crud.py (1)

    167-189: ⚠️ Potential issue

    Missing password decryption in update_user function

    The update_user function handles password updates but doesn't decrypt the password before hashing it, unlike the other password-handling functions. This creates an inconsistency in the password handling flow.

    Apply this fix:

    if update_data.get("password"):
    -    hashed_password = get_password_hash(update_data["password"])
    +    plain_password = decrypt_password(update_data["password"])
    +    hashed_password = get_password_hash(plain_password)
        del update_data["password"]
        update_data["hashed_password"] = hashed_password
    🧹 Nitpick comments (23)
    backend/pyproject.toml (1)

    29-29: Pin cryptography dependency upper bound to maintain stability.

    Most other dependencies here include both lower and upper bounds to prevent unintended breaking changes from future major releases. Consider updating the spec to:

    - "cryptography>=42.0.8", # Added for symmetric encryption
    + "cryptography>=42.0.8,<43.0.0", # Added for symmetric encryption

    This will help ensure compatibility until you decide to test and bump to a newer major version.

    admin/package.json (1)

    24-24: Consider using the native Web Crypto API instead of crypto-js.

    While crypto-js is convenient, it adds bundle weight and relies on a JS implementation of AES. The built-in Web Crypto API provides a more performant, tree-shakable, and secure alternative for AES-GCM. You could refactor your encryptPassword utility to leverage window.crypto.subtle instead.

    Also applies to: 40-40

    frontend/package.json (1)

    36-36: Consider using the native Web Crypto API instead of crypto-js.

    Similar to the admin panel, replacing crypto-js with the browser’s native window.crypto.subtle would reduce bundle size and improve security assurance. A small helper around Web Crypto can cover your AES-CBC/GCM needs without external dependencies.

    Also applies to: 58-58

    frontend/app/customers/components/PasswordForm.tsx (1)

    6-6: Consider using absolute imports for better maintainability.

    The relative import path "../../../lib/encryption" is deeply nested. Consider using an absolute import path like "@/lib/encryption" to improve maintainability and readability.

    -import { encryptPassword } from "../../../lib/encryption"; // Adjusted path
    +import { encryptPassword } from "@/lib/encryption";
    admin/.env.example (1)

    4-6: Enhance key generation guidance with specific instructions.

    While the comments explain the requirements for the encryption key, it would be helpful to include specific instructions for generating a secure key. Also, mention that this key must match across frontend, admin, and backend services.

     # This key needs to be a securely generated, preferably 32-byte (256-bit) random string,
     # often represented in base64. For Fernet, it must be a URL-safe base64-encoded 32-byte key.
    +# You can generate a suitable key using:
    +# Python: `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`
    +# Note: This key MUST be the same across frontend, admin, and backend services.
     VITE_APP_SYMMETRIC_ENCRYPTION_KEY="your_strong_symmetric_encryption_key_here"
    frontend/components/actions/register-action.ts (2)

    9-9: Consider using absolute imports for better maintainability.

    The relative import path "../../lib/encryption" could be replaced with an absolute import path like "@/lib/encryption" for better maintainability and readability.

    -import { encryptPassword } from "../../lib/encryption";
    +import { encryptPassword } from "@/lib/encryption";

    31-32: Remove commented-out code.

    Remove the commented-out code on line 31 to keep the codebase clean and maintainable.

      const input = {
        body: {
          email,
    -      // password,
          password: encryptedPassword, // Use encrypted password
          full_name,
        },
      };
    backend/app/core/security.py (3)

    32-46: Robust implementation of password decryption with minor style issue

    The decrypt_password function is well-implemented with proper error handling for different failure scenarios. However, there's a trailing whitespace on line 45 that should be removed.

    Consider adding a check for empty or null passwords to prevent potential errors.

    Apply this fix:

    def decrypt_password(encrypted_password_b64: str) -> str:
        try:
    +       if not encrypted_password_b64:
    +           raise HTTPException(status_code=400, detail="Password cannot be empty.")
            cipher_suite = Fernet(settings.APP_SYMMETRIC_ENCRYPTION_KEY.encode())
            # crypto-js AES output is Base64, Fernet expects bytes.
            # The encrypted_password_b64 itself is a string that needs to be encoded to bytes.
            decrypted_bytes = cipher_suite.decrypt(encrypted_password_b64.encode('utf-8'))
            return decrypted_bytes.decode('utf-8')
        except InvalidToken:
            # This specific exception is raised by Fernet for invalid tokens
            raise HTTPException(status_code=400, detail="Invalid password encryption format or key.")
        except Exception as e: # Catch other potential errors during decryption
            # Log the error e for server-side debugging
            # Consider using a proper logger in a production environment
    -       print(f"Password decryption error: {e}") 
    +       print(f"Password decryption error: {e}")
            raise HTTPException(status_code=400, detail="Could not process password.")
    🧰 Tools
    🪛 Ruff (0.11.9)

    45-45: Trailing whitespace

    Remove trailing whitespace

    (W291)


    32-46: Consider using a logger instead of print statements for production

    Using print() for error logging is not ideal in production environments. Consider using a proper logging solution that allows for different log levels and better control over log output.

    +import logging
    +
    +logger = logging.getLogger(__name__)
    +
     def decrypt_password(encrypted_password_b64: str) -> str:
         try:
             cipher_suite = Fernet(settings.APP_SYMMETRIC_ENCRYPTION_KEY.encode())
             # crypto-js AES output is Base64, Fernet expects bytes.
             # The encrypted_password_b64 itself is a string that needs to be encoded to bytes.
             decrypted_bytes = cipher_suite.decrypt(encrypted_password_b64.encode('utf-8'))
             return decrypted_bytes.decode('utf-8')
         except InvalidToken:
             # This specific exception is raised by Fernet for invalid tokens
             raise HTTPException(status_code=400, detail="Invalid password encryption format or key.")
         except Exception as e: # Catch other potential errors during decryption
             # Log the error e for server-side debugging
             # Consider using a proper logger in a production environment
    -        print(f"Password decryption error: {e}") 
    +        logger.error(f"Password decryption error: {e}", exc_info=True)
             raise HTTPException(status_code=400, detail="Could not process password.")
    🧰 Tools
    🪛 Ruff (0.11.9)

    45-45: Trailing whitespace

    Remove trailing whitespace

    (W291)


    32-46: Add documentation about the expected encryption format

    The function expects passwords encrypted in a specific format (Fernet-compatible encryption). Consider adding docstrings explaining the expected input format to make it clearer for other developers.

     def decrypt_password(encrypted_password_b64: str) -> str:
    +    """
    +    Decrypts a password that was encrypted client-side using AES encryption.
    +    
    +    Args:
    +        encrypted_password_b64: The Base64-encoded encrypted password string
    +        
    +    Returns:
    +        The decrypted plaintext password
    +        
    +    Raises:
    +        HTTPException: If decryption fails due to invalid format, key mismatch,
    +                      or other processing errors
    +    """
         try:
             cipher_suite = Fernet(settings.APP_SYMMETRIC_ENCRYPTION_KEY.encode())
             # crypto-js AES output is Base64, Fernet expects bytes.
    🧰 Tools
    🪛 Ruff (0.11.9)

    45-45: Trailing whitespace

    Remove trailing whitespace

    (W291)

    backend/app/core/config.py (3)

    57-57: Ensure sensitive key is protected in memory

    Adding the symmetric encryption key to the application settings is appropriate, but consider additional memory protection measures for this sensitive value.

    Consider using a secure string handling library or secret management solution that can zero out memory after use to prevent the key from persisting in memory dumps.


    59-71: Improve key validation and error handling

    The validator correctly ensures the key is present and valid for Fernet initialization, but could be improved with more specific exception handling.

    @field_validator("APP_SYMMETRIC_ENCRYPTION_KEY")
    def validate_symmetric_key(cls, v: str) -> str:
        if not v:
            raise ValueError("APP_SYMMETRIC_ENCRYPTION_KEY must be set")
        # Add more specific validation if needed, e.g., for Fernet key format/length
        # For Fernet, it must be a URL-safe base64-encoded 32-byte key.
        # Example basic check (not exhaustive for Fernet format):
        try:
            Fernet(v.encode()) # Attempt to initialize Fernet to check key validity
    -    except Exception as e:
    -        raise ValueError(f"APP_SYMMETRIC_ENCRYPTION_KEY is not a valid Fernet key: {e}")
    +    except ValueError as e:
    +        raise ValueError(f"APP_SYMMETRIC_ENCRYPTION_KEY is not a valid Fernet key: {e}")
    +    except Exception as e:
    +        raise ValueError(f"Unexpected error validating encryption key: {e}")
        return v

    15-19: Consider the impact of dependencies on module loading time

    Adding cryptography imports at the module level increases loading time even when not used.

    Consider moving the Fernet import inside the validator function to delay loading the cryptography module until needed:

    from pydantic import (
        AnyUrl,
        BeforeValidator,
        EmailStr,
        HttpUrl,
        PostgresDsn,
        computed_field,
        model_validator,
        field_validator,
    )
    from pydantic_settings import BaseSettings, SettingsConfigDict
    from typing_extensions import Self
    -from cryptography.fernet import Fernet
    from yarl import URL

    And in the validator function:

    @field_validator("APP_SYMMETRIC_ENCRYPTION_KEY")
    def validate_symmetric_key(cls, v: str) -> str:
        if not v:
            raise ValueError("APP_SYMMETRIC_ENCRYPTION_KEY must be set")
        # Add more specific validation if needed, e.g., for Fernet key format/length
        # For Fernet, it must be a URL-safe base64-encoded 32-byte key.
        try:
            from cryptography.fernet import Fernet
            Fernet(v.encode()) # Attempt to initialize Fernet to check key validity
        except Exception as e:
            raise ValueError(f"APP_SYMMETRIC_ENCRYPTION_KEY is not a valid Fernet key: {e}")
        return v
    frontend/lib/encryption.ts (3)

    4-11: Optimize encryption key retrieval with memoization

    The current implementation retrieves the encryption key from environment variables on every call, which is inefficient.

    -const getEncryptionKey = (): string => {
    +// Memoize the encryption key to avoid repeated environment variable lookups
    +let cachedKey: string | null = null;
    +
    +const getEncryptionKey = (): string => {
    +  if (cachedKey) {
    +    return cachedKey;
    +  }
    +  
       const key = process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY;
       if (!key) {
         console.error("Encryption key is not defined. Please check NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY environment variable.");
         throw new Error("Encryption key is not defined.");
       }
    +  cachedKey = key;
       return key;
     };

    13-22: Add basic validation for the encryption result

    The encryption function should validate that the result is a non-empty string before returning it.

    export const encryptPassword = (plainPassword: string): string => {
      const key = getEncryptionKey();
      try {
        const encrypted = CryptoJS.AES.encrypt(plainPassword, key).toString();
    +    if (!encrypted) {
    +      throw new Error("Encryption produced an empty result");
    +    }
        return encrypted;
      } catch (error) {
    -    console.error("Password encryption failed:", error);
    +    console.error("Password encryption failed:", error instanceof Error ? error.message : error);
        throw new Error("Could not encrypt password.");
      }
    };

    2-2: Consider using a more specific import from crypto-js

    Importing the entire CryptoJS library increases bundle size when only AES is needed.

    -import CryptoJS from 'crypto-js';
    +import AES from 'crypto-js/aes';
    +import Utf8 from 'crypto-js/enc-utf8';

    And then update the encryption function:

      try {
    -    const encrypted = CryptoJS.AES.encrypt(plainPassword, key).toString();
    +    const encrypted = AES.encrypt(plainPassword, key).toString();
         return encrypted;
    admin/src/utils/encryption.test.ts (1)

    4-27: Consider using more robust test environment setup

    The current test modifies import.meta.env directly, which might cause issues in some test environments or if tests run in parallel.

    Consider using a more robust approach like:

    1. Using a testing library's mocking capabilities (like jest.mock or vi.mock)
    2. Creating a separate module that exports the environment config that can be easily mocked
    3. Adding a parameter to the encryptPassword function for dependency injection during tests

    Example approach:

    // In encryption.ts
    export const getEncryptionKey = (env = import.meta.env): string => {
      const key = env.VITE_APP_SYMMETRIC_ENCRYPTION_KEY;
      if (!key) {
        console.error("Encryption key is not defined");
        throw new Error("Encryption key is not defined.");
      }
      return key;
    };
    
    export const encryptPassword = (plainPassword: string, env = import.meta.env): string => {
      const key = getEncryptionKey(env);
      try {
        const encrypted = CryptoJS.AES.encrypt(plainPassword, key).toString();
        return encrypted;
      } catch (error) {
        console.error("Password encryption failed:", error);
        throw new Error("Could not encrypt password.");
      }
    };
    
    // In tests
    it('should encrypt a password successfully', () => {
      const mockEnv = { VITE_APP_SYMMETRIC_ENCRYPTION_KEY: MOCK_KEY };
      const plainPassword = 'myAdminSecretPassword';
      const encrypted = encryptPassword(plainPassword, mockEnv);
      
      // Test assertions
    });
    frontend/lib/encryption.test.ts (2)

    50-59: Consider using undefined assignment instead of delete.

    While the test correctly verifies error handling for missing encryption keys, using the delete operator can impact performance.

    - delete process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY;
    + process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY = undefined;
    🧰 Tools
    🪛 Biome (1.9.4)

    [error] 52-52: Avoid the delete operator which can impact performance.

    Unsafe fix: Use an undefined assignment instead.

    (lint/performance/noDelete)


    1-80: Consider adding tests for special characters and very long passwords.

    While the test coverage is generally good, consider adding tests for:

    1. Passwords with special characters, emojis, and international characters
    2. Very long passwords (e.g., 100+ characters)
    3. Handling of null or undefined inputs

    This would ensure the encryption utility is robust under all expected conditions.

    🧰 Tools
    🪛 Biome (1.9.4)

    [error] 52-52: Avoid the delete operator which can impact performance.

    Unsafe fix: Use an undefined assignment instead.

    (lint/performance/noDelete)

    admin/src/utils/encryption.ts (1)

    1-22: Consider adding detailed documentation for the encryption process.

    While the implementation is solid, adding JSDoc comments would improve maintainability by documenting:

    1. The encryption algorithm used (AES)
    2. The expected input/output formats
    3. The environment variable dependency
    4. Potential error cases

    This would help future developers understand the security implementation details.

    // In admin/src/utils/encryption.ts
    import CryptoJS from 'crypto-js';
    
    +/**
    + * Retrieves the symmetric encryption key from environment variables.
    + * @returns {string} The encryption key
    + * @throws {Error} If the encryption key is not defined in environment variables
    + */
    const getEncryptionKey = (): string => {
      const key = import.meta.env.VITE_APP_SYMMETRIC_ENCRYPTION_KEY;
      if (!key) {
        console.error("Encryption key is not defined. Please check VITE_APP_SYMMETRIC_ENCRYPTION_KEY environment variable.");
        throw new Error("Encryption key is not defined.");
      }
      return key;
    };
    
    +/**
    + * Encrypts a password using AES encryption.
    + * @param {string} plainPassword - The plain text password to encrypt
    + * @returns {string} Base64-encoded encrypted password string
    + * @throws {Error} If encryption fails or if the encryption key is not defined
    + */
    export const encryptPassword = (plainPassword: string): string => {
      const key = getEncryptionKey();
      try {
        const encrypted = CryptoJS.AES.encrypt(plainPassword, key).toString();
        return encrypted;
      } catch (error) {
        console.error("Password encryption failed:", error);
        throw new Error("Could not encrypt password.");
      }
    };
    README.md (2)

    372-394: Clear key management documentation with helpful generation example.

    The key management section:

    1. Properly documents the environment variables for all three components
    2. Emphasizes that all three environments must use the same key
    3. Provides a practical example of generating a compatible key
    4. Includes important notes about key security

    However, there are some inconsistencies in Markdown formatting.

    Consider standardizing the unordered list style to use dashes instead of asterisks for consistency with the rest of the document:

    - *   **Backend (Python/FastAPI):**
    -     *   Variable: `APP_SYMMETRIC_ENCRYPTION_KEY`
    -     *   Location: `backend/.env.example` (and your `.env` file)
    + - **Backend (Python/FastAPI):**
    +   - Variable: `APP_SYMMETRIC_ENCRYPTION_KEY`
    +   - Location: `backend/.env.example` (and your `.env` file)

    Apply similar changes to all bulleted lists in this section.

    🧰 Tools
    🪛 markdownlint-cli2 (0.17.2)

    375-375: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    377-377: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    377-377: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    378-378: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    380-380: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    380-380: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    381-381: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    383-383: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    383-383: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    386-386: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    387-387: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    393-393: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    358-399: Consider adding a warning about symmetric key security risks.

    While the documentation covers the implementation well, it would be beneficial to add a note about the security implications of using symmetric encryption.

    Add a brief note explaining:

    **Security Considerations:**
    - This approach adds an additional security layer but still relies on the secrecy of the symmetric key.
    - If an attacker gains access to both the client-side code and the encryption key, they could potentially decrypt intercepted passwords.
    - Ensure your environment variables are properly secured in production environments.
    - This approach should be considered as complementary to HTTPS, not a replacement.
    🧰 Tools
    🪛 markdownlint-cli2 (0.17.2)

    375-375: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    377-377: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    377-377: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    378-378: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    380-380: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    380-380: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    381-381: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    383-383: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    383-383: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    386-386: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    387-387: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    393-393: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    397-397: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    398-398: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)

    backend/app/tests/core/test_security.py (1)

    1-75: Clean up whitespace in blank lines for consistency.

    Several lines in the test file contain whitespace in otherwise blank lines. While this doesn't affect functionality, it would improve code cleanliness to remove them.

    Remove whitespace from blank lines:

    • Line 24
    • Line 36
    • Line 39
    • Line 51
    • Line 61
    🧰 Tools
    🪛 Ruff (0.11.9)

    21-21: Unused function argument: mock_settings_key

    (ARG001)


    24-24: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    33-33: Unused function argument: mock_settings_key

    (ARG001)


    36-36: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    39-39: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    48-48: Unused function argument: mock_settings_key

    (ARG001)


    51-51: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    61-61: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    65-65: Unused function argument: mock_settings_key

    (ARG001)

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 0d0a02d and fa77c53.

    ⛔ Files ignored due to path filters (3)
    • admin/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
    • backend/poetry.lock is excluded by !**/*.lock
    • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
    📒 Files selected for processing (21)
    • README.md (1 hunks)
    • admin/.env.example (1 hunks)
    • admin/package.json (2 hunks)
    • admin/src/components/UserSettings/ChangePassword.tsx (2 hunks)
    • admin/src/hooks/useAuth.ts (3 hunks)
    • admin/src/utils/encryption.test.ts (1 hunks)
    • admin/src/utils/encryption.ts (1 hunks)
    • backend/.env.example (1 hunks)
    • backend/app/api/routes/users.py (2 hunks)
    • backend/app/core/config.py (2 hunks)
    • backend/app/core/security.py (2 hunks)
    • backend/app/crud.py (3 hunks)
    • backend/app/tests/core/test_security.py (1 hunks)
    • backend/pyproject.toml (1 hunks)
    • frontend/.env.example (1 hunks)
    • frontend/app/customers/components/PasswordForm.tsx (2 hunks)
    • frontend/components/actions/login-action.ts (2 hunks)
    • frontend/components/actions/register-action.ts (2 hunks)
    • frontend/lib/encryption.test.ts (1 hunks)
    • frontend/lib/encryption.ts (1 hunks)
    • frontend/package.json (2 hunks)
    🧰 Additional context used
    🧬 Code Graph Analysis (5)
    frontend/app/customers/components/PasswordForm.tsx (1)
    frontend/lib/encryption.ts (1)
    • encryptPassword (13-22)
    backend/app/api/routes/users.py (1)
    backend/app/core/security.py (3)
    • get_password_hash (28-29)
    • verify_password (24-25)
    • decrypt_password (32-46)
    admin/src/components/UserSettings/ChangePassword.tsx (1)
    admin/src/utils/encryption.ts (1)
    • encryptPassword (13-22)
    frontend/lib/encryption.test.ts (1)
    frontend/lib/encryption.ts (1)
    • encryptPassword (13-22)
    backend/app/tests/core/test_security.py (1)
    backend/app/core/security.py (1)
    • decrypt_password (32-46)
    🪛 Biome (1.9.4)
    frontend/lib/encryption.test.ts

    [error] 52-52: Avoid the delete operator which can impact performance.

    Unsafe fix: Use an undefined assignment instead.

    (lint/performance/noDelete)

    admin/src/utils/encryption.test.ts

    [error] 52-52: Avoid the delete operator which can impact performance.

    Unsafe fix: Use an undefined assignment instead.

    (lint/performance/noDelete)

    🪛 Ruff (0.11.9)
    backend/app/core/security.py

    45-45: Trailing whitespace

    Remove trailing whitespace

    (W291)

    backend/app/tests/core/test_security.py

    21-21: Unused function argument: mock_settings_key

    (ARG001)


    24-24: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    33-33: Unused function argument: mock_settings_key

    (ARG001)


    36-36: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    39-39: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    48-48: Unused function argument: mock_settings_key

    (ARG001)


    51-51: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    61-61: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    65-65: Unused function argument: mock_settings_key

    (ARG001)

    🪛 markdownlint-cli2 (0.17.2)
    README.md

    375-375: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    377-377: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    377-377: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    378-378: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    380-380: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    380-380: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    381-381: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    383-383: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    383-383: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    386-386: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    387-387: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    393-393: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    397-397: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    398-398: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)

    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: merge-playwright-reports
    🔇 Additional comments (29)
    backend/.env.example (1)

    1-3: Environment variable placeholder looks good.

    The new APP_SYMMETRIC_ENCRYPTION_KEY entry is clearly documented and matches the backend’s expected format.

    frontend/.env.example (1)

    8-8: Verify inclusion of DEBUG= is intentional.*

    Adding DEBUG=* in your example will enable all debug namespaces in development—ensure this wildcard pattern is what you want to recommend for users and that it won’t be mistakenly copied into production environment configurations.

    frontend/components/actions/login-action.ts (1)

    9-9: LGTM: Password encryption implementation looks good

    The implementation correctly encrypts the password before sending it to the server. The variable renaming from password to plainPassword improves code clarity, and comments explaining the changes help maintainability.

    Also applies to: 23-25, 30-31

    backend/app/crud.py (2)

    9-9: LGTM: Password decryption in user creation is properly implemented

    The code correctly decrypts the incoming encrypted password before hashing it for storage, which aligns with the client-side encryption strategy.

    Also applies to: 131-132, 134-135


    161-162: LGTM: Authentication flow properly handles encrypted passwords

    The implementation correctly decrypts the password before verification against the stored hash.

    backend/app/api/routes/users.py (2)

    14-14: LGTM: Current password decryption implemented correctly

    The code properly decrypts the current password before verifying it against the stored hash.

    Also applies to: 110-111


    114-116: LGTM: New password decryption and comparison implemented correctly

    The code correctly decrypts the new password and compares it with the decrypted current password before hashing and storing it.

    Also applies to: 119-119

    backend/app/core/security.py (1)

    6-7: LGTM: Required imports for decryption functionality

    The imports for Fernet, InvalidToken, and HTTPException are necessary for the new decryption functionality.

    admin/src/utils/encryption.test.ts (1)

    50-57:

    ❌ Incorrect review comment

    Fix performance issue with delete operator

    Using the delete operator on import.meta.env can impact performance as flagged by static analysis.

    it('should throw an error if encryption key is not defined', () => {
      // @ts-ignore
    -  delete import.meta.env.VITE_APP_SYMMETRIC_ENCRYPTION_KEY;
    +  // Using undefined assignment instead of delete operator
    +  const originalKey = import.meta.env.VITE_APP_SYMMETRIC_ENCRYPTION_KEY;
    +  // @ts-ignore
    +  import.meta.env.VITE_APP_SYMMETRIC_ENCRYPTION_KEY = undefined;
      
      const plainPassword = 'testAdminPassword';
      
      expect(() => encryptPassword(plainPassword)).toThrow('Encryption key is not defined.');
    +  
    +  // Restore the original key
    +  // @ts-ignore
    +  import.meta.env.VITE_APP_SYMMETRIC_ENCRYPTION_KEY = originalKey;
    });

    🏁 Script executed:

    #!/bin/bash
    # Verify if the delete operator has performance implications
    rg -n "delete\s+\w+(\.\w+)+" --type ts

    Length of output: 338


    No refactor needed for delete in test; disable lint rule instead

    The performance cost of delete here is irrelevant—this is test code that runs once, purely to simulate a missing env variable. Rather than rewriting the test, simply disable the lint rule on that line.

    • File: admin/src/utils/encryption.test.ts Line 52

       // @ts-ignore
    -  delete import.meta.env.VITE_APP_SYMMETRIC_ENCRYPTION_KEY;
    +  // eslint-disable-next-line no-delete -- simulate missing env key in test
    +  delete import.meta.env.VITE_APP_SYMMETRIC_ENCRYPTION_KEY;

    Likely an incorrect or invalid review comment.

    🧰 Tools
    🪛 Biome (1.9.4)

    [error] 52-52: Avoid the delete operator which can impact performance.

    Unsafe fix: Use an undefined assignment instead.

    (lint/performance/noDelete)

    frontend/lib/encryption.test.ts (7)

    1-3: LGTM: Good import organization and module dependencies.

    The imports are well-organized, and the test file correctly imports both the function under test and the CryptoJS library needed for verification.


    4-9: Well-structured test suite with clear description.

    The test suite is appropriately named and includes a useful comment about the AES-256 key length. This provides context for the mock key implementation.


    10-23: Good environment variable management in tests.

    The test correctly:

    1. Stores the original environment
    2. Resets modules before each test to avoid cached values
    3. Mocks the encryption key environment variable
    4. Restores the original environment after tests

    This ensures test isolation and prevents test environment leakage.


    25-36: Thorough encryption verification test.

    This test properly verifies both that:

    1. The encryption works (output differs from input)
    2. The encryption is reversible (can be decrypted back to original)

    The end-to-end verification using CryptoJS.AES.decrypt confirms the encryption integrity.


    38-48: Good output format validation.

    The test verifies that the encryption output is a Base64 string, which is important for interoperability with the backend decryption process.


    61-69: Proper validation of different inputs producing different outputs.

    The test correctly verifies that different passwords (even with subtle differences) produce different ciphertexts, which is important for security.


    71-80: Edge case handling for empty strings.

    Good test coverage for edge cases like empty strings, ensuring the encryption utility handles all potential inputs correctly.

    admin/src/utils/encryption.ts (3)

    1-3: Good module structure with clear import.

    The file correctly imports CryptoJS and includes a descriptive comment at the top identifying the file location.


    4-11: Well-implemented encryption key retrieval with proper error handling.

    The getEncryptionKey function:

    1. Correctly retrieves the key from environment variables using Vite's import.meta.env
    2. Validates the key's presence
    3. Provides detailed error logging and throws a user-friendly error message

    This ensures robust key management and clear error messaging for configuration issues.


    13-22: Clean password encryption implementation with good error handling.

    The encryptPassword function:

    1. Retrieves the encryption key
    2. Performs AES encryption with proper error handling
    3. Returns the encrypted value as a string
    4. Logs detailed errors but provides a simplified message to the caller

    This implementation aligns well with the frontend counterpart, ensuring consistent encryption across both client applications.

    README.md (2)

    358-371: Excellent documentation of the enhanced password security mechanism.

    The added section provides clear, comprehensive documentation on the password encryption mechanism, explaining:

    1. The purpose of the enhanced security
    2. The client-side encryption approach
    3. The flow from browser encryption through transmission to server-side decryption
    4. How this complements the existing bcrypt hashing

    This documentation helps users understand the security benefits of the implementation.


    395-399: Good documentation of libraries used.

    The section clearly documents the cryptographic libraries used on both client and server sides, which is important for security audits and developer understanding.

    🧰 Tools
    🪛 markdownlint-cli2 (0.17.2)

    397-397: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    398-398: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)

    backend/app/tests/core/test_security.py (8)

    1-8: Good import organization and dependencies.

    The test file correctly imports all necessary dependencies, including pytest, HTTPException, patch for mocking, and the Fernet encryption class.


    9-14: Well-defined test key generation.

    The test code appropriately:

    1. Generates a fixed Fernet key for predictable test results
    2. Includes clear comments explaining the purpose
    3. Converts the key to both binary and string formats for different use cases

    This ensures test determinism while using realistic encryption.


    15-20: Good use of pytest fixtures for mocking settings.

    The fixture correctly patches the application settings to use the test key, ensuring that tests run in isolation without affecting or depending on actual environment variables.


    21-32: Comprehensive happy path decryption test.

    The test correctly:

    1. Creates a plaintext password
    2. Encrypts it using the test key
    3. Verifies successful decryption back to the original text

    This test confirms that the end-to-end encryption/decryption flow works as expected.

    🧰 Tools
    🪛 Ruff (0.11.9)

    21-21: Unused function argument: mock_settings_key

    (ARG001)


    24-24: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    33-46: Good error handling test for invalid tokens.

    The test verifies that the decrypt function properly handles invalid tokens by:

    1. Attempting to decrypt an obviously invalid string
    2. Checking that an HTTPException with status code 400 is raised
    3. Verifying the appropriate error message

    This ensures robust error handling for malformed inputs.

    🧰 Tools
    🪛 Ruff (0.11.9)

    33-33: Unused function argument: mock_settings_key

    (ARG001)


    36-36: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    39-39: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    48-64: Good security test for key validation.

    This test verifies that the decryption function correctly rejects tokens encrypted with a different key, which is an important security characteristic. It confirms that:

    1. Data encrypted with a different key cannot be decrypted
    2. The correct error status and message are returned

    This helps prevent potential security bypasses.

    🧰 Tools
    🪛 Ruff (0.11.9)

    48-48: Unused function argument: mock_settings_key

    (ARG001)


    51-51: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    61-61: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    65-75: Thorough edge case testing for empty input.

    The test properly handles the edge case of an empty password string, ensuring the function doesn't crash and returns an appropriate error message.

    🧰 Tools
    🪛 Ruff (0.11.9)

    65-65: Unused function argument: mock_settings_key

    (ARG001)


    1-75: The mock_settings_key argument appears unused but is necessary.

    Static analysis flagged the mock_settings_key parameter as unused in multiple test functions, but this is a false positive. The fixture is indirectly used because it patches settings.APP_SYMMETRIC_ENCRYPTION_KEY, which is essential for the tests to function correctly.

    🧰 Tools
    🪛 Ruff (0.11.9)

    21-21: Unused function argument: mock_settings_key

    (ARG001)


    24-24: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    33-33: Unused function argument: mock_settings_key

    (ARG001)


    36-36: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    39-39: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    48-48: Unused function argument: mock_settings_key

    (ARG001)


    51-51: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    61-61: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    65-65: Unused function argument: mock_settings_key

    (ARG001)

    Comment on lines +10 to +12
    # often represented in base64. For Fernet, it must be a URL-safe base64-encoded 32-byte key.
    NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY="your_strong_symmetric_encryption_key_here"
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Remove quotes around the key placeholder to avoid injecting literal quotes.

    Next.js reads env values verbatim, so including quotes will become part of the key string and break decryption. Update to:

    - NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY="your_strong_symmetric_encryption_key_here"
    + NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY=your_strong_symmetric_encryption_key_here
    📝 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 key needs to be a securely generated, preferably 32-byte (256-bit) random string,
    # often represented in base64. For Fernet, it must be a URL-safe base64-encoded 32-byte key.
    NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY="your_strong_symmetric_encryption_key_here"
    # This key needs to be a securely generated, preferably 32-byte (256-bit) random string,
    # often represented in base64. For Fernet, it must be a URL-safe base64-encoded 32-byte key.
    NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY=your_strong_symmetric_encryption_key_here
    🤖 Prompt for AI Agents
    In frontend/.env.example around lines 10 to 12, remove the double quotes around
    the NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY placeholder value to prevent
    literal quotes from being included in the environment variable. This ensures the
    key is read correctly without extra characters that would break decryption.
    

    Comment on lines +24 to +26
    // const { email, password, full_name } = validatedFields.data;
    const { email, password: plainPassword, full_name } = validatedFields.data; // Rename
    const encryptedPassword = encryptPassword(plainPassword);
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Add error handling for password encryption.

    The encryption operation could fail and should be wrapped in a try-catch block to provide better error feedback to the user.

    -  // const { email, password, full_name } = validatedFields.data;
    -  const { email, password: plainPassword, full_name } = validatedFields.data; // Rename
    -  const encryptedPassword = encryptPassword(plainPassword);
    +  const { email, password: plainPassword, full_name } = validatedFields.data;
    +  
    +  let encryptedPassword;
    +  try {
    +    encryptedPassword = encryptPassword(plainPassword);
    +  } catch (error) {
    +    console.error("Password encryption failed:", error);
    +    return {
    +      server_error: "Failed to encrypt password. Please try again.",
    +    };
    +  }
    📝 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 { email, password, full_name } = validatedFields.data;
    const { email, password: plainPassword, full_name } = validatedFields.data; // Rename
    const encryptedPassword = encryptPassword(plainPassword);
    const { email, password: plainPassword, full_name } = validatedFields.data;
    let encryptedPassword;
    try {
    encryptedPassword = encryptPassword(plainPassword);
    } catch (error) {
    console.error("Password encryption failed:", error);
    return {
    server_error: "Failed to encrypt password. Please try again.",
    };
    }
    🤖 Prompt for AI Agents
    In frontend/components/actions/register-action.ts around lines 24 to 26, the
    password encryption step lacks error handling. Wrap the call to
    encryptPassword(plainPassword) in a try-catch block to catch any exceptions
    during encryption. In the catch block, handle the error appropriately by logging
    it and providing user-friendly feedback or error propagation to ensure the
    failure is managed gracefully.
    

    Comment on lines 49 to 57
    mutationFn: (data: UserRegister) => { // data is UserRegister
    const encryptedPassword = encryptPassword(data.password);
    return UsersService.registerUser({
    requestBody: {
    ...data,
    password: encryptedPassword, // Override with encrypted password
    },
    });
    },
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Add error handling for password encryption in signup

    The current implementation doesn't handle potential encryption errors specifically, which could lead to confusing error messages for users.

    mutationFn: (data: UserRegister) => { // data is UserRegister
    -  const encryptedPassword = encryptPassword(data.password);
    -  return UsersService.registerUser({
    -    requestBody: {
    -      ...data,
    -      password: encryptedPassword, // Override with encrypted password
    -    },
    -  });
    +  try {
    +    if (!data.password) {
    +      throw new Error("Password is required");
    +    }
    +    const encryptedPassword = encryptPassword(data.password);
    +    return UsersService.registerUser({
    +      requestBody: {
    +        ...data,
    +        password: encryptedPassword, // Override with encrypted password
    +      },
    +    });
    +  } catch (error) {
    +    console.error("Password encryption failed:", error);
    +    throw new Error("Failed to encrypt password. Please try again.");
    +  }
    },
    📝 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
    mutationFn: (data: UserRegister) => { // data is UserRegister
    const encryptedPassword = encryptPassword(data.password);
    return UsersService.registerUser({
    requestBody: {
    ...data,
    password: encryptedPassword, // Override with encrypted password
    },
    });
    },
    mutationFn: (data: UserRegister) => { // data is UserRegister
    try {
    if (!data.password) {
    throw new Error("Password is required");
    }
    const encryptedPassword = encryptPassword(data.password);
    return UsersService.registerUser({
    requestBody: {
    ...data,
    password: encryptedPassword, // Override with encrypted password
    },
    });
    } catch (error) {
    console.error("Password encryption failed:", error);
    throw new Error("Failed to encrypt password. Please try again.");
    }
    },
    🤖 Prompt for AI Agents
    In admin/src/hooks/useAuth.ts around lines 49 to 57, the password encryption
    step lacks error handling, which may cause unclear errors during signup. Wrap
    the encryption call in a try-catch block to catch any errors thrown by
    encryptPassword, and handle them appropriately by returning or throwing a clear,
    user-friendly error message before proceeding with user registration.
    

    Comment on lines 70 to 79
    const login = async (data: AccessToken) => { // AccessToken is Body_login_login_access_token
    const encryptedPassword = encryptPassword(data.password); // data.password should exist
    const response = await LoginService.loginAccessToken({
    formData: data,
    })
    localStorage.setItem("access_token", response.access_token)
    }
    formData: {
    ...data, // Spread other potential fields like username, grant_type etc.
    password: encryptedPassword, // Override with encrypted password
    },
    });
    localStorage.setItem("access_token", response.access_token);
    };
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    Add error handling for password encryption in login

    Similar to the signup flow, the login function lacks specific error handling for password encryption failures.

    const login = async (data: AccessToken) => { // AccessToken is Body_login_login_access_token
    -  const encryptedPassword = encryptPassword(data.password); // data.password should exist
    -  const response = await LoginService.loginAccessToken({
    -    formData: {
    -      ...data, // Spread other potential fields like username, grant_type etc.
    -      password: encryptedPassword, // Override with encrypted password
    -    },
    -  });
    -  localStorage.setItem("access_token", response.access_token);
    +  try {
    +    if (!data.password) {
    +      throw new Error("Password is required");
    +    }
    +    const encryptedPassword = encryptPassword(data.password);
    +    const response = await LoginService.loginAccessToken({
    +      formData: {
    +        ...data,
    +        password: encryptedPassword,
    +      },
    +    });
    +    localStorage.setItem("access_token", response.access_token);
    +  } catch (error) {
    +    console.error("Login process failed:", error);
    +    if (error instanceof Error && error.message.includes("encrypt")) {
    +      throw new Error("Failed to secure your password. Please try again.");
    +    }
    +    throw error; // Re-throw other errors to be handled by the mutation
    +  }
    };
    📝 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 login = async (data: AccessToken) => { // AccessToken is Body_login_login_access_token
    const encryptedPassword = encryptPassword(data.password); // data.password should exist
    const response = await LoginService.loginAccessToken({
    formData: data,
    })
    localStorage.setItem("access_token", response.access_token)
    }
    formData: {
    ...data, // Spread other potential fields like username, grant_type etc.
    password: encryptedPassword, // Override with encrypted password
    },
    });
    localStorage.setItem("access_token", response.access_token);
    };
    const login = async (data: AccessToken) => { // AccessToken is Body_login_login_access_token
    try {
    if (!data.password) {
    throw new Error("Password is required");
    }
    const encryptedPassword = encryptPassword(data.password);
    const response = await LoginService.loginAccessToken({
    formData: {
    ...data,
    password: encryptedPassword,
    },
    });
    localStorage.setItem("access_token", response.access_token);
    } catch (error) {
    console.error("Login process failed:", error);
    if (error instanceof Error && error.message.includes("encrypt")) {
    throw new Error("Failed to secure your password. Please try again.");
    }
    throw error; // Re-throw other errors to be handled by the mutation
    }
    };
    🤖 Prompt for AI Agents
    In admin/src/hooks/useAuth.ts around lines 70 to 79, the login function does not
    handle errors that may occur during password encryption. Wrap the
    encryptPassword call in a try-catch block to catch any encryption errors, and
    handle them appropriately by either logging the error or returning a failure
    response to prevent the function from proceeding with invalid data.
    

    kubbot and others added 3 commits May 23, 2025 14:37
    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
    Signed-off-by: OpenIM Bot <[email protected]>
    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
    Signed-off-by: OpenIM Bot <[email protected]>
    Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
    Signed-off-by: OpenIM Bot <[email protected]>
    @github-project-automation github-project-automation bot moved this from Backlog to In review in nexus May 23, 2025
    Copy link
    Contributor

    @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

    ♻️ Duplicate comments (1)
    backend/app/tests/core/test_security.py (1)

    43-45: Ensure error message alignment with implementation

    The test now expects only one specific error message, but the actual implementation in backend/app/core/security.py still has two different error messages. This change should be synchronized with an update to the implementation.

    Please make sure the implementation in backend/app/core/security.py has been updated to use a single consistent error message as recommended in the previous review:

    #!/bin/bash
    # Check if the decrypt_password function in security.py still has two different error messages
    
    grep -A 15 "def decrypt_password" backend/app/core/security.py
    🧰 Tools
    🪛 Ruff (0.11.9)

    43-43: SyntaxError: Expected an identifier, but found a keyword 'assert' that cannot be used here


    43-43: SyntaxError: Simple statements must be separated by newlines or semicolons


    45-45: SyntaxError: Expected an identifier, but found a keyword 'assert' that cannot be used here


    45-45: SyntaxError: Simple statements must be separated by newlines or semicolons

    🧹 Nitpick comments (3)
    backend/app/tests/core/test_security.py (3)

    24-24: Clean up whitespace in blank lines

    Please remove the trailing whitespace on lines 24, 50, and 60.

    -    
    +

    Also applies to: 50-50, 60-60

    🧰 Tools
    🪛 Ruff (0.11.9)

    24-24: Blank line contains whitespace

    (W293)


    33-33: Remove unnecessary file comment

    Line 33 contains a comment that seems like a development artifact. This line should be removed as it's redundant given you're already in the file.

    -# In backend/app/tests/core/test_security.py

    72-73: Update error message assertion for consistency

    For consistency with the changes in test_decrypt_password_invalid_token, this test should also be updated to check for the unified error message.

        assert exc_info.value.status_code == 400
    -    assert "Could not process password." in exc_info.value.detail # Fernet will likely fail to decode/decrypt
    +    assert exc_info.value.detail == "Invalid password encryption format or key."
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 2d25b6b and d3eb32d.

    📒 Files selected for processing (2)
    • backend/app/tests/core/test_security.py (1 hunks)
    • frontend/app/customers/components/PasswordForm.tsx (2 hunks)
    🚧 Files skipped from review as they are similar to previous changes (1)
    • frontend/app/customers/components/PasswordForm.tsx
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    backend/app/tests/core/test_security.py (1)
    backend/app/core/security.py (1)
    • decrypt_password (32-46)
    🪛 Ruff (0.11.9)
    backend/app/tests/core/test_security.py

    24-24: Blank line contains whitespace

    (W293)


    43-43: SyntaxError: Expected an identifier, but found a keyword 'assert' that cannot be used here


    43-43: SyntaxError: Simple statements must be separated by newlines or semicolons


    45-45: SyntaxError: Expected an identifier, but found a keyword 'assert' that cannot be used here


    45-45: SyntaxError: Simple statements must be separated by newlines or semicolons


    50-50: Blank line contains whitespace

    (W293)


    60-60: Blank line contains whitespace

    (W293)

    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: test-docker-compose
    🔇 Additional comments (7)
    backend/app/tests/core/test_security.py (7)

    1-8: LGTM! Good test setup.

    The imports and setup for these tests are well organized, with proper imports and a clear structure for testing the password decryption functionality.


    9-14: Good security practice with test keys

    Using a fixed key for testing purposes with a clear comment explaining the test context is a good practice. This ensures predictable test outcomes without compromising security.


    15-20: Well-implemented test fixture

    The fixture correctly patches the application settings to use the test key, which is a clean approach for testing cryptographic functions without modifying actual application settings.


    21-32: Comprehensive positive test case

    This test thoroughly validates the successful decryption path by properly encrypting test data and verifying its decryption matches the original value.

    🧰 Tools
    🪛 Ruff (0.11.9)

    24-24: Blank line contains whitespace

    (W293)


    35-46: Good negative test for invalid tokens

    This test properly verifies that invalid tokens are handled with appropriate HTTP exceptions, which is critical for security-related functions.

    🧰 Tools
    🪛 Ruff (0.11.9)

    43-43: SyntaxError: Expected an identifier, but found a keyword 'assert' that cannot be used here


    43-43: SyntaxError: Simple statements must be separated by newlines or semicolons


    45-45: SyntaxError: Expected an identifier, but found a keyword 'assert' that cannot be used here


    45-45: SyntaxError: Simple statements must be separated by newlines or semicolons


    47-63: Excellent key differentiation test

    Testing decryption with data encrypted using a different key is an important security edge case that's well covered here. This helps ensure the system correctly rejects data encrypted with unauthorized keys.

    🧰 Tools
    🪛 Ruff (0.11.9)

    50-50: Blank line contains whitespace

    (W293)


    60-60: Blank line contains whitespace

    (W293)


    64-74: Good empty input handling test

    Testing with empty input is important for robustness. The detailed comments explaining the expected behavior provide good context for future maintainers.

    Copy link

    gitguardian bot commented May 24, 2025

    ️✅ There are no secrets present in this pull request anymore.

    If these secrets were true positive and are still valid, we highly recommend you to revoke them.
    While these secrets were previously flagged, we no longer have a reference to the
    specific commits where they were detected. Once a secret has been leaked into a git
    repository, you should consider it compromised, even if it was deleted immediately.
    Find here more information about risks.


    🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

    Copy link
    Contributor

    @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

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between d3eb32d and dae88a6.

    ⛔ Files ignored due to path filters (2)
    • admin/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
    • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
    📒 Files selected for processing (6)
    • admin/package.json (2 hunks)
    • backend/app/api/routes/users.py (2 hunks)
    • backend/app/core/config.py (2 hunks)
    • backend/app/crud.py (3 hunks)
    • backend/pyproject.toml (1 hunks)
    • frontend/package.json (2 hunks)
    🚧 Files skipped from review as they are similar to previous changes (5)
    • backend/pyproject.toml
    • admin/package.json
    • backend/app/api/routes/users.py
    • backend/app/crud.py
    • backend/app/core/config.py
    🧰 Additional context used
    🪛 Biome (1.9.4)
    frontend/package.json

    [error] 48-48: The key class-variance-authority was already declared.

    This where a duplicated key was declared again.

    If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

    (lint/suspicious/noDuplicateObjectKeys)

    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: test-docker-compose
    🔇 Additional comments (2)
    frontend/package.json (2)

    49-49: Dependency addition approved: crypto-js

    Adding crypto-js (^4.2.0) aligns with the client-side AES encryption implementation for password security. Ensure that the lockfile is updated accordingly and that the version remains consistent between the frontend and admin applications.


    81-81: Dev dependency addition approved: @types/crypto-js

    Including @types/crypto-js (^4.2.2) provides proper TypeScript typings for the encryption utility. Confirm that it installs without conflicts and enhances IDE/type-checker support.

    @@ -45,6 +45,8 @@
    "@radix-ui/react-toggle": "^1.1.9",
    "@radix-ui/react-toggle-group": "^1.1.10",
    "@radix-ui/react-tooltip": "^1.2.7",
    "class-variance-authority": "^0.7.0",
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Duplicate dependency key detected

    You've declared "class-variance-authority" twice in dependencies (line 48 and line 50). Only the latter definition takes effect, which can lead to confusion and unintended version resolution. Please remove the redundant entry or consolidate the versions into a single declaration.

    🧰 Tools
    🪛 Biome (1.9.4)

    [error] 48-48: The key class-variance-authority was already declared.

    This where a duplicated key was declared again.

    If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

    (lint/suspicious/noDuplicateObjectKeys)

    🤖 Prompt for AI Agents
    In frontend/package.json at line 48, there is a duplicate declaration of the
    "class-variance-authority" dependency. Remove the redundant entry so that
    "class-variance-authority" appears only once in the dependencies section to
    avoid confusion and ensure consistent version resolution.
    

    Copy link
    Contributor

    @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

    ♻️ Duplicate comments (1)
    frontend/package.json (1)

    48-48: Duplicate dependency key detected
    The dependency "class-variance-authority" is declared twice (lines 48 and 50); only the last one takes effect. Please remove the redundant entry to avoid confusion and ensure consistent version resolution.

    🧰 Tools
    🪛 Biome (1.9.4)

    [error] 48-48: The key class-variance-authority was already declared.

    This where a duplicated key was declared again.

    If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

    (lint/suspicious/noDuplicateObjectKeys)

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

    358-369: New security section is clear
    The "Enhanced Password Security" section concisely explains the motivation and high-level flow. As an optional enhancement, you might mention any performance considerations on low-powered devices or older browsers that could require additional polyfills.


    363-368: Clarify encryption details
    The numbered steps are clear. To improve transparency, you may want to briefly note which AES mode (e.g., GCM vs. CBC) is used and how the IV is managed on the client side.


    371-384: Unify list bullet style
    The unordered lists use asterisks and inconsistent indentation, which markdownlint flags (MD004, MD007). Please switch to dashes (-) and use two-space indentation for sub-items to satisfy lint rules and maintain consistency.

    🧰 Tools
    🪛 markdownlint-cli2 (0.17.2)

    375-375: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    377-377: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    377-377: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    378-378: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    380-380: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    380-380: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    381-381: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    383-383: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    383-383: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    386-398: Enhance key management guidance
    Consider linking to the official cryptography docs for key rotation strategies and secure storage best practices, helping users maintain key integrity over time.

    🧰 Tools
    🪛 markdownlint-cli2 (0.17.2)

    386-386: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    387-387: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    393-393: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    397-397: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    398-398: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between dae88a6 and ab1a825.

    ⛔ Files ignored due to path filters (1)
    • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
    📒 Files selected for processing (6)
    • README.md (1 hunks)
    • backend/app/api/routes/users.py (2 hunks)
    • backend/app/core/config.py (2 hunks)
    • backend/app/crud.py (3 hunks)
    • backend/pyproject.toml (1 hunks)
    • frontend/package.json (2 hunks)
    🚧 Files skipped from review as they are similar to previous changes (4)
    • backend/pyproject.toml
    • backend/app/api/routes/users.py
    • backend/app/crud.py
    • backend/app/core/config.py
    🧰 Additional context used
    🪛 markdownlint-cli2 (0.17.2)
    README.md

    375-375: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    376-376: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    377-377: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    377-377: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    378-378: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    379-379: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    380-380: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    380-380: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    381-381: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    382-382: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    383-383: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    383-383: Unordered list indentation
    Expected: 2; Actual: 4

    (MD007, ul-indent)


    386-386: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    387-387: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    393-393: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    397-397: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)


    398-398: Unordered list style
    Expected: dash; Actual: asterisk

    (MD004, ul-style)

    🪛 Biome (1.9.4)
    frontend/package.json

    [error] 48-48: The key class-variance-authority was already declared.

    This where a duplicated key was declared again.

    If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

    (lint/suspicious/noDuplicateObjectKeys)

    ⏰ Context from checks skipped due to timeout of 90000ms (3)
    • GitHub Check: make-all-test
    • GitHub Check: test-backend
    • GitHub Check: test-docker-compose
    🔇 Additional comments (2)
    frontend/package.json (2)

    49-49: Add AES encryption library
    Adding crypto-js (^4.2.0) is correct for client-side AES encryption support. Make sure it’s covered by existing tests and that bundling this dependency doesn’t bloat your production build.


    81-81: Include TypeScript definitions
    Adding @types/crypto-js ensures proper typing for the encryption utility in TypeScript. Good catch!

    Copy link
    Contributor

    @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 (1)
    Makefile (1)

    272-272: Extract HTTP timeout magic number into a Makefile variable
    The inline UV_HTTP_TIMEOUT=120 works, but embedding a literal makes future tweaks error-prone. Consider defining at the top under your settings:

    UV_HTTP_TIMEOUT ?= 120

    Then update the backend-install target:

    -	@cd $(BACKEND_DIR) && UV_HTTP_TIMEOUT=120 $(UV) sync
    +	@cd $(BACKEND_DIR) && UV_HTTP_TIMEOUT=$(UV_HTTP_TIMEOUT) $(UV) sync

    This centralizes the timeout value for easier maintenance.

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between ab1a825 and ae29a54.

    ⛔ Files ignored due to path filters (1)
    • backend/uv.lock is excluded by !**/*.lock
    📒 Files selected for processing (4)
    • .windsurfrules (1 hunks)
    • Makefile (1 hunks)
    • admin/src/components/UserSettings/ChangePassword.tsx (3 hunks)
    • frontend/app/customers/components/PasswordForm.tsx (2 hunks)
    ✅ Files skipped from review due to trivial changes (1)
    • .windsurfrules
    🚧 Files skipped from review as they are similar to previous changes (2)
    • admin/src/components/UserSettings/ChangePassword.tsx
    • frontend/app/customers/components/PasswordForm.tsx
    🧰 Additional context used
    🪛 GitHub Actions: Use Make All Test
    Makefile

    [error] 708-708: Makefile target 'generate-frontend-client' failed with exit code 2 due to configuration validation error.

    ⏰ Context from checks skipped due to timeout of 90000ms (1)
    • GitHub Check: test-docker-compose

    Copy link
    Contributor

    @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: 2

    🧹 Nitpick comments (3)
    backend/app/core/security.py (1)

    30-31: Fix whitespace formatting issues.

    Static analysis detected whitespace issues that should be cleaned up.

    Remove the whitespace from blank lines:

         return str(pwd_context.hash(password))
    -    
    -    
    +
    
    backend/app/tests/core/test_security.py (2)

    1-7: Remove unused import.

    The HTTPException import is not used in the tests and should be removed.

     import pytest
    -from fastapi import HTTPException
     from unittest.mock import patch
     from cryptography.fernet import Fernet
    🧰 Tools
    🪛 Ruff (0.11.9)

    2-2: fastapi.HTTPException imported but unused

    Remove unused import: fastapi.HTTPException

    (F401)


    15-66: Clean up formatting issues.

    Multiple blank lines contain whitespace that should be removed for cleaner code.

     @pytest.fixture
     def mock_settings_key():
         # Patch settings.APP_SYMMETRIC_ENCRYPTION_KEY to use our test key
         with patch.object(settings, 'APP_SYMMETRIC_ENCRYPTION_KEY', TEST_FERNET_KEY_STR) as _fixture:
             yield _fixture
    -    
    +
    
     def test_decrypt_password_success(mock_settings_key):
         """Test successful decryption of a password."""
    -    # 在测试环境中,我们直接返回原始密码,不进行解密
    -    # 因此我们只需要测试函数不会抛出异常
         plain_password = "testpassword123"
    -    
    -    # 在测试环境中,decrypt_password 应该直接返回输入的密码
    +
         result = decrypt_password(plain_password)
         assert result == plain_password

    Note: The unused mock_settings_key arguments will be resolved when test coverage is improved to actually test decryption functionality.

    🧰 Tools
    🪛 Ruff (0.11.9)

    21-21: Unused function argument: mock_settings_key

    (ARG001)


    26-26: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    33-33: Unused function argument: mock_settings_key

    (ARG001)


    37-37: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    42-42: Unused function argument: mock_settings_key

    (ARG001)


    47-47: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    58-58: Unused function argument: mock_settings_key

    (ARG001)


    63-63: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between ae29a54 and fad753b.

    📒 Files selected for processing (3)
    • .env.example (1 hunks)
    • backend/app/core/security.py (2 hunks)
    • backend/app/tests/core/test_security.py (1 hunks)
    ✅ Files skipped from review due to trivial changes (1)
    • .env.example
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    backend/app/tests/core/test_security.py (1)
    backend/app/core/security.py (1)
    • decrypt_password (32-51)
    🪛 Ruff (0.11.9)
    backend/app/core/security.py

    37-37: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    50-50: Trailing whitespace

    Remove trailing whitespace

    (W291)

    backend/app/tests/core/test_security.py

    2-2: fastapi.HTTPException imported but unused

    Remove unused import: fastapi.HTTPException

    (F401)


    21-21: Unused function argument: mock_settings_key

    (ARG001)


    26-26: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    33-33: Unused function argument: mock_settings_key

    (ARG001)


    37-37: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    42-42: Unused function argument: mock_settings_key

    (ARG001)


    47-47: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    58-58: Unused function argument: mock_settings_key

    (ARG001)


    63-63: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)

    ⏰ Context from checks skipped due to timeout of 90000ms (2)
    • GitHub Check: make-all-test
    • GitHub Check: test-docker-compose
    🔇 Additional comments (1)
    backend/app/core/security.py (1)

    6-7: LGTM! Imports are appropriate for the new decryption functionality.

    The imports for Fernet encryption and HTTPException are correctly used in the new decrypt_password function.

    Comment on lines 32 to 51
    def decrypt_password(encrypted_password_b64: str) -> str:
    # 在测试环境中直接返回原始密码,跳过解密过程
    if settings.TESTING or settings.TEST_MODE:
    print("测试模式:跳过密码解密过程,直接返回原始密码")
    return encrypted_password_b64

    try:
    cipher_suite = Fernet(settings.APP_SYMMETRIC_ENCRYPTION_KEY.encode())
    # crypto-js AES output is Base64, Fernet expects bytes.
    # The encrypted_password_b64 itself is a string that needs to be encoded to bytes.
    decrypted_bytes = cipher_suite.decrypt(encrypted_password_b64.encode('utf-8'))
    return decrypted_bytes.decode('utf-8')
    except InvalidToken:
    # This specific exception is raised by Fernet for invalid tokens
    raise HTTPException(status_code=400, detail="Invalid password encryption format or key.")
    except Exception as e: # Catch other potential errors during decryption
    # Log the error e for server-side debugging
    # Consider using a proper logger in a production environment
    print(f"Password decryption error: {e}")
    raise HTTPException(status_code=400, detail="Could not process password.")
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    ⚠️ Potential issue

    Address multiple implementation concerns.

    This function has several issues that need attention:

    1. Language consistency: Chinese comments should be in English for codebase consistency.
    2. Security concern: The testing mode bypass could be risky if TESTING or TEST_MODE flags are accidentally enabled in production.
    3. Logging practices: Using print() instead of proper logging.
    4. Error message consistency: As noted in past reviews, unify the error messages for both exception types.

    Apply these improvements:

    def decrypt_password(encrypted_password_b64: str) -> str:
    -    # 在测试环境中直接返回原始密码,跳过解密过程
    +    # In testing environment, bypass decryption and return the original password
         if settings.TESTING or settings.TEST_MODE:
    -        print("测试模式:跳过密码解密过程,直接返回原始密码")
    +        # Use proper logging instead of print
    +        import logging
    +        logging.info("Testing mode: bypassing password decryption, returning original password")
             return encrypted_password_b64
    -        
    +
         try:
             cipher_suite = Fernet(settings.APP_SYMMETRIC_ENCRYPTION_KEY.encode())
             # crypto-js AES output is Base64, Fernet expects bytes.
             # The encrypted_password_b64 itself is a string that needs to be encoded to bytes.
             decrypted_bytes = cipher_suite.decrypt(encrypted_password_b64.encode('utf-8'))
             return decrypted_bytes.decode('utf-8')
         except InvalidToken:
             # This specific exception is raised by Fernet for invalid tokens
             raise HTTPException(status_code=400, detail="Invalid password encryption format or key.")
         except Exception as e: # Catch other potential errors during decryption
    -        # Log the error e for server-side debugging
    -        # Consider using a proper logger in a production environment
    -        print(f"Password decryption error: {e}") 
    -        raise HTTPException(status_code=400, detail="Could not process password.")
    +        # Use proper logging for server-side debugging
    +        import logging
    +        logging.error(f"Password decryption error: {e}")
    +        # Unify error message as noted in past reviews
    +        raise HTTPException(status_code=400, detail="Invalid password encryption format or key.")
    📝 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
    def decrypt_password(encrypted_password_b64: str) -> str:
    # 在测试环境中直接返回原始密码,跳过解密过程
    if settings.TESTING or settings.TEST_MODE:
    print("测试模式:跳过密码解密过程,直接返回原始密码")
    return encrypted_password_b64
    try:
    cipher_suite = Fernet(settings.APP_SYMMETRIC_ENCRYPTION_KEY.encode())
    # crypto-js AES output is Base64, Fernet expects bytes.
    # The encrypted_password_b64 itself is a string that needs to be encoded to bytes.
    decrypted_bytes = cipher_suite.decrypt(encrypted_password_b64.encode('utf-8'))
    return decrypted_bytes.decode('utf-8')
    except InvalidToken:
    # This specific exception is raised by Fernet for invalid tokens
    raise HTTPException(status_code=400, detail="Invalid password encryption format or key.")
    except Exception as e: # Catch other potential errors during decryption
    # Log the error e for server-side debugging
    # Consider using a proper logger in a production environment
    print(f"Password decryption error: {e}")
    raise HTTPException(status_code=400, detail="Could not process password.")
    def decrypt_password(encrypted_password_b64: str) -> str:
    # In testing environment, bypass decryption and return the original password
    if settings.TESTING or settings.TEST_MODE:
    # Use proper logging instead of print
    import logging
    logging.info("Testing mode: bypassing password decryption, returning original password")
    return encrypted_password_b64
    try:
    cipher_suite = Fernet(settings.APP_SYMMETRIC_ENCRYPTION_KEY.encode())
    # crypto-js AES output is Base64, Fernet expects bytes.
    # The encrypted_password_b64 itself is a string that needs to be encoded to bytes.
    decrypted_bytes = cipher_suite.decrypt(encrypted_password_b64.encode('utf-8'))
    return decrypted_bytes.decode('utf-8')
    except InvalidToken:
    # This specific exception is raised by Fernet for invalid tokens
    raise HTTPException(status_code=400, detail="Invalid password encryption format or key.")
    except Exception as e: # Catch other potential errors during decryption
    # Use proper logging for server-side debugging
    import logging
    logging.error(f"Password decryption error: {e}")
    # Unify error message as noted in past reviews
    raise HTTPException(status_code=400, detail="Invalid password encryption format or key.")
    🧰 Tools
    🪛 Ruff (0.11.9)

    37-37: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    50-50: Trailing whitespace

    Remove trailing whitespace

    (W291)

    🤖 Prompt for AI Agents
    In backend/app/core/security.py around lines 32 to 51, replace all Chinese
    comments with English to maintain language consistency. Remove or refactor the
    testing mode bypass to avoid returning the encrypted password directly, ensuring
    it cannot be accidentally enabled in production. Replace all print statements
    with proper logging calls using the configured logger. Finally, unify the error
    messages in both exception handlers to provide consistent responses for
    decryption failures.
    

    Comment on lines 21 to 66
    def test_decrypt_password_success(mock_settings_key):
    """Test successful decryption of a password."""
    # 在测试环境中,我们直接返回原始密码,不进行解密
    # 因此我们只需要测试函数不会抛出异常
    plain_password = "testpassword123"

    # 在测试环境中,decrypt_password 应该直接返回输入的密码
    result = decrypt_password(plain_password)
    assert result == plain_password

    # In backend/app/tests/core/test_security.py

    def test_decrypt_password_invalid_token(mock_settings_key):
    """Test decryption with an invalid token (malformed base64 or not Fernet encrypted)."""
    # 在测试环境中,decrypt_password 直接返回输入的密码,不会抛出异常
    invalid_encrypted_password = "this is not a valid fernet token"

    # 在测试环境中应该直接返回原始输入
    result = decrypt_password(invalid_encrypted_password)
    assert result == invalid_encrypted_password

    def test_decrypt_password_incorrect_key_type(mock_settings_key):
    """Test decryption with a token encrypted by a different key (simulated)."""
    # 在测试环境中,decrypt_password 直接返回输入的密码,不会抛出异常
    # 即使密码是用不同的密钥加密的
    plain_password = "testpassword123"

    # 使用任意加密密钥加密的密码
    different_key = Fernet.generate_key()
    cipher_suite_different = Fernet(different_key)
    encrypted_password_bytes = cipher_suite_different.encrypt(plain_password.encode('utf-8'))
    encrypted_password_b64 = encrypted_password_bytes.decode('utf-8')

    # 在测试环境中应该直接返回原始输入
    result = decrypt_password(encrypted_password_b64)
    assert result == encrypted_password_b64

    def test_decrypt_empty_password(mock_settings_key):
    """Test decrypting an empty string."""
    # 在测试环境中,decrypt_password 直接返回输入的密码,不会抛出异常
    # 即使输入是空字符串
    empty_password = ""

    # 在测试环境中应该直接返回空字符串
    result = decrypt_password(empty_password)
    assert result == empty_password
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    🛠️ Refactor suggestion

    ⚠️ Potential issue

    Improve test coverage significantly.

    The current tests only verify the testing mode bypass behavior and don't test the actual decryption functionality. This leaves the core business logic untested.

    Issues to address:

    1. Inadequate coverage: Tests only cover the TESTING mode bypass, not real decryption
    2. Language consistency: Chinese comments should be in English
    3. Unused parameters: mock_settings_key fixture is unused (but should be used)
    4. Missing scenarios: No tests for successful decryption, error handling, etc.

    Suggested improvements:

    def test_decrypt_password_success(mock_settings_key):
    -    """Test successful decryption of a password."""
    -    # 在测试环境中,我们直接返回原始密码,不进行解密
    -    # 因此我们只需要测试函数不会抛出异常
    +    """Test that in testing mode, password is returned unchanged."""
         plain_password = "testpassword123"
    -    
    -    # 在测试环境中,decrypt_password 应该直接返回输入的密码
    +
    +    # In testing mode, decrypt_password should return input unchanged
         result = decrypt_password(plain_password)
         assert result == plain_password
    
    +def test_decrypt_password_real_decryption():
    +    """Test actual decryption functionality outside testing mode."""
    +    with patch('app.core.security.settings.TESTING', False), \
    +         patch('app.core.security.settings.TEST_MODE', False), \
    +         patch('app.core.security.settings.APP_SYMMETRIC_ENCRYPTION_KEY', TEST_FERNET_KEY_STR):
    +        
    +        # Encrypt a password using the same key
    +        plain_password = "testpassword123"
    +        cipher_suite = Fernet(TEST_FERNET_KEY)
    +        encrypted_bytes = cipher_suite.encrypt(plain_password.encode('utf-8'))
    +        encrypted_b64 = encrypted_bytes.decode('utf-8')
    +        
    +        # Test decryption
    +        result = decrypt_password(encrypted_b64)
    +        assert result == plain_password
    
    +def test_decrypt_password_invalid_token_production():
    +    """Test error handling for invalid tokens in production mode."""
    +    with patch('app.core.security.settings.TESTING', False), \
    +         patch('app.core.security.settings.TEST_MODE', False), \
    +         patch('app.core.security.settings.APP_SYMMETRIC_ENCRYPTION_KEY', TEST_FERNET_KEY_STR):
    +        
    +        with pytest.raises(HTTPException) as exc_info:
    +            decrypt_password("invalid_token")
    +        assert exc_info.value.status_code == 400
    +        assert "Invalid password encryption format or key." in exc_info.value.detail

    Add the HTTPException import back if you implement the error testing:

     import pytest
    +from fastapi import HTTPException
     from unittest.mock import patch

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

    🧰 Tools
    🪛 Ruff (0.11.9)

    21-21: Unused function argument: mock_settings_key

    (ARG001)


    26-26: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    33-33: Unused function argument: mock_settings_key

    (ARG001)


    37-37: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    42-42: Unused function argument: mock_settings_key

    (ARG001)


    47-47: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)


    58-58: Unused function argument: mock_settings_key

    (ARG001)


    63-63: Blank line contains whitespace

    Remove whitespace from blank line

    (W293)

    🤖 Prompt for AI Agents
    In backend/app/tests/core/test_security.py from lines 21 to 66, the tests only
    verify the bypass behavior in testing mode and do not cover the actual
    decryption logic, leaving core functionality untested. Replace Chinese comments
    with English for consistency, utilize the mock_settings_key fixture properly to
    simulate real key usage, and add new test cases that cover successful decryption
    with a valid token, error handling for invalid tokens raising HTTPException, and
    edge cases like empty input. Also, re-import HTTPException if you add tests that
    expect exceptions to be raised.
    

    Copy link
    Contributor

    @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

    🧹 Nitpick comments (1)
    .env.example (1)

    52-53: Clarify key generation instructions
    The inline # gen: snippet is helpful, but consider moving the detailed key-generation steps into the README (or a dedicated docs section) and keeping .env.example focused on placeholders. This will keep the example concise and avoid duplicating documentation.

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between c2783e5 and 3625ecb.

    📒 Files selected for processing (1)
    • .env.example (1 hunks)
    🧰 Additional context used
    🪛 Gitleaks (8.26.0)
    .env.example

    54-54: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

    (generic-api-key)

    ⏰ Context from checks skipped due to timeout of 90000ms (2)
    • GitHub Check: make-all-test
    • GitHub Check: test-docker-compose

    .env.example Outdated
    # First Superuser Password: Password for the initial administrator account.
    # Choose a strong password.
    # min_length=8
    FIRST_SUPERUSER_PASSWORD='telepace'

    # Used for password encryption and decryption, APP_SYMMETRIC_ENCRYPTION_KEY is not a valid Fernet key: Fernet key must be 32 url-safe base64-encoded bytes.
    # gen: `openssl rand -base64 32 | tr '/+' '_-' | tr -d '='`
    APP_SYMMETRIC_ENCRYPTION_KEY='P6aWHpWD-HurUKwroaqllUW8JGq6uTrTE6A5lURbxV4'
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Remove hardcoded key from example file
    Static analysis flagged this as a potential exposed key. Example files should never include real or production-like secrets. Replace with a clear placeholder (e.g., <YOUR_APP_SYMMETRIC_ENCRYPTION_KEY>) and document the openssl command separately.

    🧰 Tools
    🪛 Gitleaks (8.26.0)

    54-54: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

    (generic-api-key)

    🤖 Prompt for AI Agents
    In the .env.example file at line 54, replace the hardcoded
    APP_SYMMETRIC_ENCRYPTION_KEY value with a clear placeholder such as
    <YOUR_APP_SYMMETRIC_ENCRYPTION_KEY>. Remove any real or production-like secret
    keys from example files to avoid accidental exposure. Additionally, add a
    comment or documentation outside the example file explaining how to generate a
    secure key using the openssl command.
    

    Copy link
    Contributor

    @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

    ♻️ Duplicate comments (1)
    .env.example (1)

    52-54: ⚠️ Potential issue

    Remove hardcoded symmetric encryption key from example file

    Example files must never include real or production-like secrets. Replace the hardcoded key with a placeholder <YOUR_APP_SYMMETRIC_ENCRYPTION_KEY> and move the key-generation instructions to your documentation (e.g., README.md).
    Apply this diff:

    -# 用于密码加密和解密的对称密钥,必须是有效的 Fernet 密钥(32 字节的 URL 安全 base64 编码,末尾必须有 = 符号)
    -# 生成方法: `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`
    -APP_SYMMETRIC_ENCRYPTION_KEY=Buhzb09HgEg-4C7oUsZqykAH_-yfXEONu9sogno3a2s=
    +# 用于密码加密和解密的对称密钥,必须是有效的 Fernet 密钥(32 字节的 URL 安全 base64 编码,末尾必须有 = 符号)
    +# 生成方法: `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`
    +APP_SYMMETRIC_ENCRYPTION_KEY=<YOUR_APP_SYMMETRIC_ENCRYPTION_KEY>
    🧰 Tools
    🪛 Gitleaks (8.26.0)

    54-54: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

    (generic-api-key)

    🧹 Nitpick comments (1)
    .env.example (1)

    52-53: Add English translation for the comment

    Since .env.example is shared across teams, consider prepending an English description above the Chinese comment for broader clarity.
    Apply this diff:

    -# 用于密码加密和解密的对称密钥,必须是有效的 Fernet 密钥(32 字节的 URL 安全 base64 编码,末尾必须有 = 符号)
    +# # Symmetric key for password encryption/decryption; must be a valid Fernet key (32 bytes, URL-safe Base64, ending with “=”)
    +# # 用于密码加密和解密的对称密钥,必须是有效的 Fernet 密钥(32 字节的 URL 安全 base64 编码,末尾必须有 = 符号)
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 3625ecb and a4037bb.

    📒 Files selected for processing (1)
    • .env.example (1 hunks)
    🧰 Additional context used
    🪛 Gitleaks (8.26.0)
    .env.example

    54-54: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

    (generic-api-key)

    ⏰ Context from checks skipped due to timeout of 90000ms (2)
    • GitHub Check: make-all-test
    • GitHub Check: test-docker-compose

    Copy link
    Contributor

    @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 (1)
    admin/tsconfig.build.json (1)

    3-3: Suggestion: Also exclude TSX test files.

    If your admin panel tests include React components (.tsx), consider updating the patterns to cover both .ts and .tsx extensions. For example:

      "exclude": [
        "tests/**/*.ts",
    -   "src/**/*.test.ts",
    -   "src/**/*.spec.ts"
    +   "src/**/*.test.{ts,tsx}",
    +   "src/**/*.spec.{ts,tsx}"
      ]
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 78b18dc and 8b824a2.

    📒 Files selected for processing (1)
    • admin/tsconfig.build.json (1 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms (6)
    • GitHub Check: test-playwright (4, 4)
    • GitHub Check: test-playwright (3, 4)
    • GitHub Check: test-playwright (2, 4)
    • GitHub Check: test-playwright (1, 4)
    • GitHub Check: make-all-test
    • GitHub Check: test-docker-compose
    🔇 Additional comments (1)
    admin/tsconfig.build.json (1)

    3-3: Correctly exclude newly added test files from production builds.

    By extending the exclude array to include "src/**/*.test.ts" and "src/**/*.spec.ts", you ensure that the new encryption utility tests (and any other tests under src) aren’t bundled into the production output.

    Copy link
    Contributor

    @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

    ♻️ Duplicate comments (1)
    frontend/package.json (1)

    48-48: ⚠️ Potential issue

    Remove duplicate dependency declaration.

    The class-variance-authority dependency is declared twice (lines 48 and 52 with versions ^0.7.0 and ^0.7.1 respectively). Only the latter declaration takes effect, making the first one redundant.

    Apply this diff to remove the redundant entry:

    -    "class-variance-authority": "^0.7.0",
    🧰 Tools
    🪛 Biome (1.9.4)

    [error] 48-48: The key class-variance-authority was already declared.

    This where a duplicated key was declared again.

    If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

    (lint/suspicious/noDuplicateObjectKeys)

    🧹 Nitpick comments (1)
    frontend/app/settings/components/PasswordForm.tsx (1)

    84-99: Consider refactoring for improved readability.

    The encryption implementation is secure and handles errors appropriately. However, the use of IIFEs within the JSON object makes the code harder to read and maintain.

    Consider extracting the encryption logic before the request:

            try {
    +         const encryptedCurrentPassword = encryptPassword(formData.current_password);
    +         const encryptedNewPassword = encryptPassword(formData.new_password);
    +
              const response = await fetch(`${apiUrl}/api/v1/users/me/password`, {
                method: "POST",
                headers: {
                  "Content-Type": "application/json",
                  Authorization: `Bearer ${document.cookie.split("accessToken=")[1]?.split(";")[0]}`,
                },
                body: JSON.stringify({
    -             current_password: (() => {
    -               try {
    -                 return encryptPassword(formData.current_password);
    -               } catch (error) {
    -                 console.error("Failed to encrypt current password:", error);
    -                 throw new Error("Password encryption failed. Please try again.");
    -               }
    -             })(),
    -             new_password: (() => {
    -               try {
    -                 return encryptPassword(formData.new_password);
    -               } catch (error) {
    -                 console.error("Failed to encrypt new password:", error);
    -                 throw new Error("Password encryption failed. Please try again.");
    -               }
    -             })(),
    +             current_password: encryptedCurrentPassword,
    +             new_password: encryptedNewPassword,
                }),
              });
            } catch (error) {
    +         if (error instanceof Error && error.message.includes("encryption")) {
    +           console.error("Encryption error:", error);
    +         }
              console.error("Error updating password:", error);

    This approach maintains the same error handling while improving code readability.

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 8b824a2 and 110c8fe.

    ⛔ Files ignored due to path filters (2)
    • backend/uv.lock is excluded by !**/*.lock
    • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
    📒 Files selected for processing (8)
    • .env.example (1 hunks)
    • .github/workflows/playwright.yml (1 hunks)
    • Makefile (1 hunks)
    • backend/app/api/routes/users.py (2 hunks)
    • backend/app/core/config.py (2 hunks)
    • backend/pyproject.toml (1 hunks)
    • frontend/app/settings/components/PasswordForm.tsx (2 hunks)
    • frontend/package.json (2 hunks)
    🚧 Files skipped from review as they are similar to previous changes (6)
    • backend/pyproject.toml
    • .github/workflows/playwright.yml
    • .env.example
    • Makefile
    • backend/app/api/routes/users.py
    • backend/app/core/config.py
    🧰 Additional context used
    🪛 Biome (1.9.4)
    frontend/package.json

    [error] 48-48: The key class-variance-authority was already declared.

    This where a duplicated key was declared again.

    If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.

    (lint/suspicious/noDuplicateObjectKeys)

    🔇 Additional comments (2)
    frontend/package.json (1)

    49-49: LGTM: Appropriate dependencies for password encryption.

    The addition of crypto-js and @types/crypto-js dependencies is well-suited for the client-side AES encryption functionality. The versions are current and stable.

    Also applies to: 89-89

    frontend/app/settings/components/PasswordForm.tsx (1)

    6-6: LGTM: Proper import for encryption utility.

    The import statement correctly references the encryption utility with an appropriate relative path.

    Copy link
    Contributor

    @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: 2

    🧹 Nitpick comments (2)
    frontend/lib/encryption.test.ts (2)

    5-5: Consider using a more realistic mock key length.

    The mock key is 32 bytes which is appropriate for AES-256, but consider adding a comment explaining the key length requirement and potentially testing with different key lengths to ensure robustness.

    - const MOCK_KEY = "testmockkey12345678901234567890"; // 32 bytes for AES-256, though crypto-js AES is flexible
    + const MOCK_KEY = "testmockkey12345678901234567890"; // 32-byte key for AES-256 compatibility

    38-48: Enhance Base64 validation test.

    While the current Base64 regex check is functional, consider making it more robust by actually attempting to decode the Base64 string to ensure it's valid.

      it("should produce a base64 string", () => {
        const plainPassword = "testBase64Output";
        const encrypted = encryptPassword(plainPassword);
    -   // Basic check for Base64: should not contain spaces and should be a string.
    -   // A more robust check might involve a regex or trying to decode it as Base64.
        expect(typeof encrypted).toBe("string");
    -   // CryptoJS AES.encrypt().toString() defaults to Base64.
    -   // A simple regex to check if it looks like Base64 (alphanumeric, +, /, =)
        const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
        expect(base64Regex.test(encrypted)).toBe(true);
    +   
    +   // Verify it's actually valid Base64 by attempting to decode
    +   expect(() => Buffer.from(encrypted, 'base64')).not.toThrow();
      });
    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 110c8fe and ddeec90.

    ⛔ Files ignored due to path filters (2)
    • backend/uv.lock is excluded by !**/*.lock
    • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
    📒 Files selected for processing (13)
    • .github/workflows/release.yml (1 hunks)
    • admin/src/components/UserSettings/ChangePassword.tsx (3 hunks)
    • admin/src/hooks/useAuth.ts (3 hunks)
    • admin/src/utils/encryption.test.ts (1 hunks)
    • admin/src/utils/encryption.ts (1 hunks)
    • backend/app/api/routes/users.py (2 hunks)
    • backend/app/core/config.py (3 hunks)
    • backend/app/core/security.py (2 hunks)
    • backend/app/tests/core/test_security.py (1 hunks)
    • frontend/__tests__/login.test.tsx (3 hunks)
    • frontend/__tests__/register.test.ts (3 hunks)
    • frontend/lib/encryption.test.ts (1 hunks)
    • frontend/lib/encryption.ts (1 hunks)
    🚧 Files skipped from review as they are similar to previous changes (9)
    • admin/src/utils/encryption.test.ts
    • admin/src/components/UserSettings/ChangePassword.tsx
    • admin/src/utils/encryption.ts
    • backend/app/api/routes/users.py
    • backend/app/core/security.py
    • frontend/lib/encryption.ts
    • admin/src/hooks/useAuth.ts
    • backend/app/tests/core/test_security.py
    • backend/app/core/config.py
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    frontend/lib/encryption.test.ts (1)
    frontend/lib/encryption.ts (1)
    • encryptPassword (15-24)
    🪛 Biome (1.9.4)
    frontend/lib/encryption.test.ts

    [error] 52-52: Avoid the delete operator which can impact performance.

    Unsafe fix: Use an undefined assignment instead.

    (lint/performance/noDelete)

    ⏰ Context from checks skipped due to timeout of 90000ms (3)
    • GitHub Check: test-playwright (4, 4)
    • GitHub Check: test-playwright (3, 4)
    • GitHub Check: test-playwright (2, 4)
    🔇 Additional comments (5)
    frontend/lib/encryption.test.ts (1)

    25-36: Excellent cryptographic verification approach.

    The tests properly verify encryption integrity by decrypting the ciphertext and comparing it to the original plaintext. This ensures the encryption/decryption cycle works correctly and validates the cryptographic implementation.

    Also applies to: 73-81

    frontend/__tests__/register.test.ts (2)

    13-16: Well-implemented encryption mocking strategy.

    The mock implementation is clean and predictable, making it easy to verify that encryption is being applied correctly in the register action. The encrypted_ prefix clearly distinguishes encrypted from plain passwords in test assertions.


    29-35: Correctly updated password assertions for encryption.

    The test assertions have been properly updated to expect encrypted password values, ensuring that the tests accurately reflect the new encryption workflow. This maintains test coverage while adapting to the security enhancement.

    Also applies to: 53-59

    frontend/__tests__/login.test.tsx (2)

    18-21: Consistent encryption mocking across test suites.

    The encryption mock implementation matches the approach used in the register tests, providing consistency across the test suite. This makes the codebase easier to maintain and understand.


    38-49: Properly updated login assertions for encrypted passwords.

    The test expectations have been correctly modified to account for the encrypted password format. This ensures that the login functionality is properly tested with the new encryption feature while maintaining comprehensive test coverage.

    Also applies to: 74-85

    @@ -115,7 +115,7 @@ jobs:
    artifacts: "dist/*"
    generateReleaseNotes: true
    bodyFile: "CHANGELOG.md"
    token: ${{ secrets.BOT_GITHUB_TOKEN }}
    token: ${{ secrets.GITHUB_TOKEN }}
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Grant write permissions to GITHUB_TOKEN for release creation
    Switching from BOT_GITHUB_TOKEN to the built-in GITHUB_TOKEN means you must explicitly grant it contents: write rights to create a release. Without this, the release step will fail at runtime.

    Apply this diff at the top of the workflow:

     name: Auto Release
    
    +permissions:
    +  contents: write  # allow GITHUB_TOKEN to create and publish releases
    
     on:
    📝 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
    token: ${{ secrets.GITHUB_TOKEN }}
    name: Auto Release
    permissions:
    contents: write # allow GITHUB_TOKEN to create and publish releases
    on:
    # …rest of your triggers
    🤖 Prompt for AI Agents
    In .github/workflows/release.yml at line 118, the GITHUB_TOKEN is used but lacks
    explicit write permissions needed for release creation. To fix this, add a
    permissions section at the top of the workflow file granting 'contents: write'
    to the GITHUB_TOKEN. This ensures the token has the necessary rights to create
    releases during the workflow execution.
    

    Comment on lines +50 to +61
    it("should throw an error if encryption key is not defined", () => {
    // Temporarily delete the key from our mocked process.env for this test
    delete process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY;

    const plainPassword = "testpassword";

    // We need to re-import or ensure the module re-reads the modified process.env
    // For this structure, the getEncryptionKey function inside encryptPassword will read the updated process.env
    expect(() => encryptPassword(plainPassword)).toThrow(
    "Encryption key is not defined.",
    );
    });
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue

    Fix performance issue with delete operator.

    The static analysis tool correctly identifies a performance issue with using the delete operator. Use undefined assignment instead for better performance.

    - delete process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY;
    + process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY = undefined;
    📝 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
    it("should throw an error if encryption key is not defined", () => {
    // Temporarily delete the key from our mocked process.env for this test
    delete process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY;
    const plainPassword = "testpassword";
    // We need to re-import or ensure the module re-reads the modified process.env
    // For this structure, the getEncryptionKey function inside encryptPassword will read the updated process.env
    expect(() => encryptPassword(plainPassword)).toThrow(
    "Encryption key is not defined.",
    );
    });
    it("should throw an error if encryption key is not defined", () => {
    // Temporarily delete the key from our mocked process.env for this test
    - delete process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY;
    + process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY = undefined;
    const plainPassword = "testpassword";
    // We need to re-import or ensure the module re-reads the modified process.env
    // For this structure, the getEncryptionKey function inside encryptPassword will read the updated process.env
    expect(() => encryptPassword(plainPassword)).toThrow(
    "Encryption key is not defined.",
    );
    });
    🧰 Tools
    🪛 Biome (1.9.4)

    [error] 52-52: Avoid the delete operator which can impact performance.

    Unsafe fix: Use an undefined assignment instead.

    (lint/performance/noDelete)

    🤖 Prompt for AI Agents
    In frontend/lib/encryption.test.ts around lines 50 to 61, replace the use of the
    delete operator on process.env.NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY with
    assigning undefined to it. This avoids the performance penalty of delete and
    achieves the same effect of making the key undefined for the test.
    

    - Updated the APP_SYMMETRIC_ENCRYPTION_KEY in the Playwright workflow for enhanced security.
    - Upgraded various package versions in pnpm-lock.yaml, including '@chakra-ui/react', '@tanstack/react-query', and '@zag-js' components to their latest versions for improved functionality and performance.
    - Adjusted node type definitions and other dependencies to ensure compatibility with the latest updates.
    @cubxxw cubxxw requested a review from Copilot May 28, 2025 03:42
    Copy link
    Contributor

    @Copilot Copilot AI left a comment

    Choose a reason for hiding this comment

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

    Pull Request Overview

    This PR implements client-side AES encryption of passwords and integrates Fernet-based decryption on the backend to add an extra security layer before transmission.

    • Introduces new environment variables for symmetric encryption keys across frontend, admin, and backend.
    • Adds encryptPassword utility in Next.js and admin panel, and decrypt_password in FastAPI.
    • Updates authentication flows and tests to use the new encryption/decryption logic.

    Reviewed Changes

    Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.

    Show a summary per file
    File Description
    frontend/.env.example Added NEXT_PUBLIC_APP_SYMMETRIC_ENCRYPTION_KEY.
    backend/app/core/security.py Implemented decrypt_password with Fernet decryption.
    backend/app/tests/core/test_security.py Added tests for decrypt_password.
    admin/src/utils/encryption.ts Implemented encryptPassword utility.
    admin/src/hooks/useAuth.ts Integrated encryption into login and signup hooks.
    .env.example Added APP_SYMMETRIC_ENCRYPTION_KEY configuration.
    .github/workflows/generate-client.yml Injected symmetric key into CI environment.
    .windsurfrules Added a stray rule regarding reply language.

    export const encryptPassword = (plainPassword: string): string => {
    const key = getEncryptionKey()
    try {
    const encrypted = CryptoJS.AES.encrypt(plainPassword, key).toString()
    Copy link
    Preview

    Copilot AI May 28, 2025

    Choose a reason for hiding this comment

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

    The front-end uses CryptoJS AES encryption (passphrase-based) but the backend expects Fernet tokens. These formats are incompatible, so the backend will fail to decrypt. Consider using a compatible library/protocol on both sides (e.g., Fernet in JS).

    Copilot uses AI. Check for mistakes.

    plain_password = "testpassword123"

    # 在测试环境中,decrypt_password 应该直接返回输入的密码
    result = decrypt_password(plain_password)
    Copy link
    Preview

    Copilot AI May 28, 2025

    Choose a reason for hiding this comment

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

    Tests assume decrypt_password returns the input in test mode, but settings.TESTING or TEST_MODE is never set. The real decryption branch will run and raise an exception. Patch the settings flag or fixture to enable test mode.

    Copilot uses AI. Check for mistakes.

    @@ -0,0 +1 @@
    1. The text of the reply is in Chinese, but the code is in English
    Copy link
    Preview

    Copilot AI May 28, 2025

    Choose a reason for hiding this comment

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

    [nitpick] This rule file appears unintended for the codebase and mixes Chinese/English. Remove or relocate it to avoid confusion.

    Suggested change
    1. The text of the reply is in Chinese, but the code is in English
    [File removed]

    Copilot uses AI. Check for mistakes.

    @@ -60,8 +69,13 @@ const useAuth = () => {
    })

    const login = async (data: AccessToken) => {
    // AccessToken is Body_login_login_access_token
    const encryptedPassword = encryptPassword(data.password) // data.password should exist
    Copy link
    Preview

    Copilot AI May 28, 2025

    Choose a reason for hiding this comment

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

    [nitpick] Encryption is used without a try/catch here; if encryptPassword throws, the error will be unhandled. Consider adding error handling similar to the ChangePassword component.

    Suggested change
    const encryptedPassword = encryptPassword(data.password) // data.password should exist
    let encryptedPassword: string
    try {
    encryptedPassword = encryptPassword(data.password) // data.password should exist
    } catch (error) {
    console.error("Error encrypting password:", error)
    handleError(error)
    throw new Error("Failed to encrypt password")
    }

    Copilot uses AI. Check for mistakes.

    cubxxw and others added 5 commits May 28, 2025 12:05
    - Removed conditional CORS middleware setup for local and production environments.
    - Unified CORS configuration to use settings.all_cors_origins for all environments.
    - Added logging to indicate the configured CORS origins.
    - Commented out all steps in the deploy-staging.yml workflow to temporarily disable the deployment process.
    - This change is intended for maintenance or debugging purposes.
    Co-authored-by: Copilot <[email protected]>
    Signed-off-by: 熊鑫伟 Xinwei Xiong <[email protected]>
    Co-authored-by: Copilot <[email protected]>
    Signed-off-by: 熊鑫伟 Xinwei Xiong <[email protected]>
    Co-authored-by: Copilot <[email protected]>
    Signed-off-by: 熊鑫伟 Xinwei Xiong <[email protected]>
    Copy link
    Contributor

    @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 (2)
    backend/app/core/security.py (2)

    33-33: Replace Chinese comment with English for consistency.

    As noted in previous reviews, maintain language consistency by using English comments throughout the codebase.


    52-52: Replace print statement with proper logging.

    As noted in previous reviews, use the configured logger instead of print statements for production-ready code.

    🧹 Nitpick comments (1)
    .github/workflows/deploy-staging.yml (1)

    1-49: Verify intentional disabling of staging deployment workflow.

    The entire staging deployment workflow has been commented out, which prevents any deployments to the staging environment. This appears to be related to the password encryption security changes being implemented.

    Please confirm:

    1. Is this intentional to prevent deployments during the encryption implementation?
    2. When will this workflow be re-enabled?
    3. Are there alternative deployment processes in place for testing the encryption changes?

    Also, add a newline at the end of the file to resolve the linting issue:

     #           docker compose --project-name $STACK_NAME up -d || (echo "Deployment failed, cleaning up..." && docker compose --project-name $STACK_NAME down --remove-orphans && exit 1)
    +
    🧰 Tools
    🪛 actionlint (1.7.7)

    1-1: workflow is empty

    (syntax-check)

    🪛 YAMLlint (1.37.1)

    [error] 49-49: no new line character at the end of file

    (new-line-at-end-of-file)

    📜 Review details

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

    📥 Commits

    Reviewing files that changed from the base of the PR and between 701bc1d and 51ac12d.

    📒 Files selected for processing (5)
    • .env.example (1 hunks)
    • .github/workflows/deploy-staging.yml (1 hunks)
    • .github/workflows/generate-client.yml (1 hunks)
    • backend/app/core/security.py (2 hunks)
    • backend/app/main.py (1 hunks)
    🚧 Files skipped from review as they are similar to previous changes (2)
    • .github/workflows/generate-client.yml
    • .env.example
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    backend/app/main.py (1)
    backend/app/core/config.py (1)
    • all_cors_origins (84-87)
    🪛 actionlint (1.7.7)
    .github/workflows/deploy-staging.yml

    1-1: workflow is empty

    (syntax-check)

    🪛 YAMLlint (1.37.1)
    .github/workflows/deploy-staging.yml

    [error] 49-49: no new line character at the end of file

    (new-line-at-end-of-file)

    🪛 Ruff (0.11.9)
    backend/app/core/security.py

    35-35: Undefined name logger

    (F821)

    🪛 GitHub Actions: CI/CD Pipeline
    backend/app/core/security.py

    [error] 35-35: Undefined name logger (F821) detected. The variable logger is used but not defined or imported.

    🪛 GitHub Actions: Test Backend
    backend/app/core/security.py

    [error] 35-35: NameError: name 'logger' is not defined in decrypt_password function. This causes multiple test failures in test_users.py and test_security.py.

    ⏰ Context from checks skipped due to timeout of 90000ms (5)
    • GitHub Check: test-playwright (4, 4)
    • GitHub Check: test-playwright (1, 4)
    • GitHub Check: test-playwright (2, 4)
    • GitHub Check: test-playwright (3, 4)
    • GitHub Check: test-docker-compose
    🔇 Additional comments (1)
    backend/app/main.py (1)

    74-81: LGTM! Improved CORS configuration and logging.

    The simplification of CORS configuration by removing environment-based conditional logic reduces complexity and potential security misconfigurations. The added logging statement improves observability by showing the configured origins at startup.

    - Added logging functionality in `backend/app/core/security.py` to improve debugging and monitoring.
    - Updated the OpenAPI specification in `frontend/openapi.json` to include detailed API endpoints for user management, authentication, and item handling, enhancing API documentation and usability.
    @cubxxw cubxxw added this pull request to the merge queue May 28, 2025
    Merged via the queue into main with commit d7f163c May 28, 2025
    13 checks passed
    @cubxxw cubxxw deleted the feat/password-transit-encryption branch May 28, 2025 04:33
    @github-project-automation github-project-automation bot moved this from In review to Done in nexus May 28, 2025
    @coderabbitai coderabbitai bot mentioned this pull request Jun 1, 2025
    4 tasks
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    documentation Improvements or additions to documentation enhancement New feature or request Review effort [1-5]: 4 Tests
    Projects
    Status: Done
    Development

    Successfully merging this pull request may close these issues.

    2 participants