Skip to content

Hash password updates#23

Open
numbpill3d wants to merge 1 commit intomainfrom
codex/hash-password-before-update-in-user-model
Open

Hash password updates#23
numbpill3d wants to merge 1 commit intomainfrom
codex/hash-password-before-update-in-user-model

Conversation

@numbpill3d
Copy link
Collaborator

@numbpill3d numbpill3d commented Jun 8, 2025

Summary

  • hash passwords when updating a user
  • fix index route tests to use ScrapyardItem model
  • add tests ensuring password update hashes the password

Testing

  • npm test (fails: Missing required Supabase environment variables)

https://chatgpt.com/codex/tasks/task_e_68450196e69c832fbf9dfda55bd6bee1

Summary by Sourcery

Ensure that password updates are securely hashed and update test suite to reflect model changes and verify hashing behavior.

Bug Fixes:

  • Fix index route tests to mock the ScrapyardItem model and include countDocuments and find behaviors.

Enhancements:

  • Hash passwords in User.findByIdAndUpdate when a new password is provided.

Tests:

  • Add unit tests for User.findByIdAndUpdate to verify password hashing and non-hashing scenarios.
  • Enhance index route tests with additional mocking for User and ScrapyardItem models.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jun 8, 2025

Reviewer's Guide

Implements bcrypt-based password hashing in the User.update flow, adds unit tests for both hashing and non-hashing scenarios, and adjusts index route tests to reference the renamed ScrapyardItem model and include document count mocks.

Sequence Diagram for Password Hashing in User Update

sequenceDiagram
    participant C as Caller
    participant UM as User Model
    participant B as bcrypt
    participant DB as Database

    C->>UM: findByIdAndUpdate(id, updateData)
    alt updateData contains password
        UM->>UM: Check for password in updateData
        UM->>B: genSalt(10)
        B-->>UM: salt
        UM->>B: hash(updateData.password, salt)
        B-->>UM: hashedPassword
        UM->>UM: updateData.password = hashedPassword
    end
    UM->>UM: Convert updateData to snake_case for DB
    UM->>DB: Update user record with snake_case_data
    DB-->>UM: Updated user data / Confirmation
    UM-->>C: Result / Updated User
Loading

Updated Class Diagram for User Model

classDiagram
    class User {
        <<Model>>
        +static findByIdAndUpdate(id, updateData) Promise~User~
    }
    class bcrypt {
        <<Library>>
        +static genSalt(rounds) Promise~string~
        +static hash(data, salt) Promise~string~
    }
    User ..> bcrypt : uses
Loading

File-Level Changes

Change Details Files
Implement password hashing in User.findByIdAndUpdate
  • Conditionally hash updateData.password using bcrypt before database update
  • Generate salt with bcrypt.genSalt(10) and compute hashed password
  • Replace plaintext password with its hashed value in update data
server/models/User.js
Add unit tests for password hashing behavior
  • Mock bcrypt.genSalt and bcrypt.hash to return deterministic values
  • Stub Supabase from().update() calls to capture update arguments
  • Verify both hashing when password is provided and skipping when absent
tests/server/models/user.test.js
Refactor index route tests to use ScrapyardItem and include document counts
  • Rename mock from Item to ScrapyardItem and update module path
  • Add countDocuments mocks for both user and item models
  • Introduce find mock on user model to simulate lastActive field
tests/server/routes/index.test.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Comment on lines 203 to 214
*/
static async findByIdAndUpdate(id, updateData) {
try {
// Hash password if it is being updated
if (updateData.password) {
const salt = await bcrypt.genSalt(10);
updateData.password = await bcrypt.hash(updateData.password, salt);
}

// Convert to snake_case for Supabase
const snakeCaseData = {};

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 with User Existence Check

The method findByIdAndUpdate updates user data without verifying if the user with the provided id actually exists in the database. This could lead to errors or unintended behavior if an invalid or non-existent id is used.

Recommendation:

  • Implement a check to verify the user's existence before attempting to update. This could be done by querying the database for the user id and proceeding with the update only if the user is found. This would enhance the reliability and robustness of the method.

Comment on lines 212 to 214
// Convert to snake_case for Supabase
const snakeCaseData = {};

Copy link
Contributor

Choose a reason for hiding this comment

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

Refactoring Suggestion for Key Conversion

The method manually converts keys from camelCase to snake_case using a regex directly within the method. This approach is repeated across different methods, which could lead to code duplication and maintenance challenges.

Recommendation:

  • Consider creating a utility function that handles the conversion of object keys from camelCase to snake_case. Use this utility function across all methods that require such conversion. This would not only reduce code duplication but also centralize changes to the conversion logic, making the codebase easier to maintain and less error-prone.

Comment on lines +31 to +49
describe('User.findByIdAndUpdate', () => {
it('hashes password when provided', async () => {
await User.findByIdAndUpdate('1', { password: 'secret' });

expect(bcrypt.genSalt).toHaveBeenCalledWith(10);
expect(bcrypt.hash).toHaveBeenCalledWith('secret', 'salt');
expect(updateMock).toHaveBeenCalled();
const arg = updateMock.mock.calls[0][0];
expect(arg.password).toBe('hashedPassword');
});

it('does not hash when password not provided', async () => {
await User.findByIdAndUpdate('1', { displayName: 'Test' });

expect(bcrypt.hash).not.toHaveBeenCalled();
const arg = updateMock.mock.calls[0][0];
expect(arg.display_name).toBe('Test');
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

The current tests in User.findByIdAndUpdate are well-implemented for positive scenarios. However, they lack coverage for error handling and edge cases, such as what happens if bcrypt.hash fails or if the database operation returns an error. Recommendation: Add additional test cases to handle exceptions and ensure the function behaves correctly under error conditions. This will improve the robustness and fault tolerance of the application.

]),
countDocuments: jest.fn().mockResolvedValue(2),
find: jest.fn().mockResolvedValue([
{ username: 'testuser1', lastActive: new Date() }
Copy link
Contributor

Choose a reason for hiding this comment

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

Using new Date() directly in the mock (line 15) introduces non-deterministic behavior in tests, as the value changes with each test execution. This can lead to flaky tests. Consider using a fixed timestamp or a mock date library like jest-date-mock to ensure consistency in test outcomes.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @numbpill3d - I've reviewed your changes - here's some feedback:

  • Extract the bcrypt password-hashing logic into a dedicated helper or class method so it can be reused (e.g., in user creation) and keep the model method concise.
  • Replace the hardcoded salt rounds (10) with a configurable constant to make it easier to adjust security parameters in one place.
  • Consider adding a guard in findByIdAndUpdate to skip re-hashing if the incoming password is already hashed to prevent unintended double-hashing.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

static async findByIdAndUpdate(id, updateData) {
try {
// Hash password if it is being updated
if (updateData.password) {
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Falsy password check may skip hashing and store plaintext

Checking if (updateData.password) may miss empty or falsy passwords, resulting in unhashed plaintext storage. Use property existence checks or validate for empty values before hashing.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant