Skip to content

feat: use walletAddress as the primary public ID#140

Merged
Villarley merged 2 commits intoStarShopCr:Developfrom
diegoveme:use_walletAddress_as_primary_id_and_convert_id_to_uuid
Aug 20, 2025
Merged

feat: use walletAddress as the primary public ID#140
Villarley merged 2 commits intoStarShopCr:Developfrom
diegoveme:use_walletAddress_as_primary_id_and_convert_id_to_uuid

Conversation

@diegoveme
Copy link
Contributor

@diegoveme diegoveme commented Aug 12, 2025

🚀 StarShop Pull Request

Mark with an x all the checkboxes that apply (like [x])

  • Closes #
  • Added tests (if necessary)
  • Run tests
  • Run formatting
  • Evidence attached
  • Commented the code

📌 Type of Change

  • Documentation (updates to README, docs, or comments)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

📝 Changes description

Security Enhancement: UUID Migration and walletAddress as Primary Identifier

This PR implements a comprehensive security upgrade by migrating from numeric IDs to UUIDs and using walletAddress as the primary identifier for all public API interactions.

Key Changes:

  1. Database Schema Migration:

    • Created migration 1751199237000-MigrateUserIdToUUID.ts to convert user ID from SERIAL to UUID
    • Created migration 1751199238000-UpdateForeignKeysToUUID.ts to update all foreign key relationships
    • User table now uses UUID primary key with auto-generation
    • Added unique index on walletAddress for performance
  2. Entity Updates:

    • Updated User entity to use @PrimaryGeneratedColumn('uuid')
    • Updated UserRole, BuyerRequest, and Review entities to use UUID for userId
    • Maintained existing Cart and Order entities (already UUID compatible)
  3. Service Layer Refactoring:

    • UserService: Now uses walletAddress as primary identifier for public operations
    • AuthService: Updated to work with walletAddress and handle UUID internally
    • RoleService: Updated to use string UUIDs instead of numeric IDs
    • BuyerRequestsService and ReviewService: Updated to use string UUIDs
  4. Controller Updates:

    • UserController: All endpoints now use walletAddress parameter instead of numeric ID
    • RoleController: Updated to use walletAddress for role assignment
    • API routes changed from /users/:id to /users/:walletAddress
  5. Authentication & JWT Updates:

    • JWT strategy updated to handle both walletAddress and id for backward compatibility
    • Request objects now use walletAddress for user identification
    • Internal UUIDs are never exposed to clients
  6. DTO Updates:

    • Updated response DTOs to use string UUIDs
    • Removed id field from public API responses
    • Maintained walletAddress as the primary identifier
  7. Comprehensive Testing:

    • Added user-update-api.spec.ts with complete test coverage
    • Tests verify user updates, profile access, authorization, and UUID handling
    • Ensures id field is never exposed in API responses
  8. Documentation:

    • Created comprehensive docs/UUID-MIGRATION.md guide
    • Includes security benefits, implementation details, and rollback procedures

Security Benefits:

  • Eliminates ID enumeration attacks (UUIDs are not sequential)
  • Prevents scraping vulnerabilities (internal IDs never exposed)
  • Uses blockchain-native identifiers (walletAddress)
  • Maintains backward compatibility during migration

API Changes:

  • Before: PUT /users/update/:id, GET /users/:id
  • After: PUT /users/update/:walletAddress, GET /users/:walletAddress

📸 Evidence (A photo is required as evidence)

  1. Database Migrations Created:

    • src/migrations/1751199237000-MigrateUserIdToUUID.ts
    • src/migrations/1751199238000-UpdateForeignKeysToUUID.ts
  2. Entity Files Updated:

    • src/modules/users/entities/user.entity.ts - UUID primary key
    • src/modules/auth/entities/user-role.entity.ts - UUID userId
    • src/modules/buyer-requests/entities/buyer-request.entity.ts - UUID userId
    • src/modules/reviews/entities/review.entity.ts - UUID userId
  3. Service Files Refactored:

    • src/modules/users/services/user.service.ts - walletAddress methods
    • src/modules/auth/services/auth.service.ts - walletAddress support
    • src/modules/auth/services/role.service.ts - UUID handling
    • src/modules/buyer-requests/services/buyer-requests.service.ts - UUID support
    • src/modules/reviews/services/review.service.ts - UUID support
  4. Controller Files Updated:

    • src/modules/users/controllers/user.controller.ts - walletAddress endpoints
    • src/modules/auth/controllers/role.controller.ts - walletAddress role assignment
    • src/modules/reviews/controllers/review.controller.ts - UUID handling
  5. Test Files Added:

    • src/modules/users/tests/user-update-api.spec.ts - Comprehensive testing
  6. Documentation Created:

    • docs/UUID-MIGRATION.md - Complete migration guide

⏰ Time spent breakdown

  • Database Migration Design: 1 hour
  • Entity Updates: 2 hours
  • Service Layer Refactoring: 3 hours
  • Controller Updates: 2 hours
  • Authentication Flow Updates: 2 hours
  • DTO and Type Updates: 1 hour
  • Comprehensive Testing: 2 hours
  • Documentation: 1 hour
  • Code Review and Refinement: 1 hour

🌌 Comments

Technical Implementation Notes:

  1. Backward Compatibility: The implementation maintains backward compatibility by:

    • JWT strategy handles both walletAddress and id fields
    • Existing tokens continue to work during migration
    • Internal services can still use getUserById() method
  2. Performance Considerations:

    • Added unique index on walletAddress for fast lookups
    • Foreign key relationships optimized for UUID operations
    • Query performance maintained through proper indexing
  3. Security Enhancements:

    • UUIDs prevent ID enumeration attacks
    • Internal database IDs never exposed to clients
    • All public endpoints use walletAddress identifier
    • Role-based access control maintained
  4. Migration Strategy:

    • Two-phase migration approach for safe database updates
    • Data integrity maintained throughout the process
    • Rollback procedures documented for emergency scenarios
  5. Testing Coverage:

    • Unit tests cover all CRUD operations
    • Authorization and authentication flows tested
    • UUID exposure prevention verified
    • Edge cases and error scenarios covered

Future Considerations:

  • Support for multi-chain wallet formats
  • Enhanced wallet signature verification
  • Rate limiting per wallet address
  • Cross-chain user identification

This implementation significantly enhances the security posture of StarShop while maintaining all existing functionality and providing a clear migration path for production deployment.


Thank you for contributing to StarShop, we are glad that you have chosen us as your project of choice and we hope that you continue to contribute to this great project, so that together we can make our mark at the top!

Summary by CodeRabbit

  • New Features
    • User and auth endpoints now use walletAddress as the identifier.
    • Role assignment/removal operates by walletAddress.
  • Refactor
    • Switched user IDs to UUIDs across the platform.
    • Public API responses no longer include numeric id fields.
  • Documentation
    • Added a UUID migration guide outlining schema and API changes.
  • Tests
    • Added tests for user update and retrieval via walletAddress, including authorization scenarios and response validation.

@coderabbitai
Copy link

coderabbitai bot commented Aug 12, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Migrates User.id from integer to UUID across DB, entities, services, and DTOs; shifts public APIs to use walletAddress as the primary identifier. Adds TypeORM migrations for users and related foreign keys, updates JWT validation, adjusts controllers/services for users, auth, reviews, and buyer-requests, adds tests, and documents the migration.

Changes

Cohort / File(s) Summary
Documentation
docs/UUID-MIGRATION.md
Adds a comprehensive migration spec detailing schema, API, service, auth, controller changes, steps, and rollback.
DB Migrations
src/migrations/1751199237000-MigrateUserIdToUUID.ts, src/migrations/1751199238000-UpdateForeignKeysToUUID.ts
Converts users.id to UUID, adds unique index on walletAddress; updates FK columns (user_roles, buyer_requests, reviews; links carts, orders) to reference users.id (UUID). Down migrations revert.
Auth Layer
src/modules/auth/controllers/role.controller.ts, src/modules/auth/services/auth.service.ts, src/modules/auth/services/role.service.ts, src/modules/auth/strategies/jwt.strategy.ts, src/types/auth-request.type.ts
Switches role assignment/removal to walletAddress; adds getUserByWalletAddress; removes numeric coercions; JWT validate prioritizes walletAddress, falls back to id; request user.id now string (UUID).
Users Module
src/modules/users/controllers/user.controller.ts, src/modules/users/entities/user.entity.ts, src/modules/users/services/user.service.ts
User.id becomes UUID; walletAddress indexed; controllers/routes use walletAddress instead of id; responses omit id; services add getUserByWalletAddress and update/get by walletAddress.
Buyer Requests
src/modules/buyer-requests/entities/buyer-request.entity.ts, src/modules/buyer-requests/services/buyer-requests.service.ts, src/modules/buyer-requests/dto/buyer-request-response.dto.ts
userId fields/types changed to UUID string; service method signatures accept string userId; DTO types updated.
Reviews
src/modules/reviews/entities/review.entity.ts, src/modules/reviews/services/review.service.ts, src/modules/reviews/dto/review.dto.ts, src/modules/reviews/controllers/review.controller.ts
Review.userId becomes UUID string; service methods accept string userId; DTO updated; controller stops numeric coercion.
Tests
src/modules/users/tests/user-update-api.spec.ts
Adds tests for users update/get by walletAddress, authorization cases, and ensures UUID id is not exposed.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API as UsersController
  participant USvc as UserService
  participant DB

  Client->>API: PUT /users/update/:walletAddress
  API->>USvc: updateUser(walletAddress, dto)
  USvc->>USvc: getUserByWalletAddress(walletAddress)
  USvc->>DB: SELECT user WHERE walletAddress=...
  DB-->>USvc: User(id: UUID)
  USvc->>DB: UPDATE user SET ...
  DB-->>USvc: Updated User
  USvc-->>API: Updated User
  API-->>Client: { walletAddress, name, email, role, updatedAt } (no id)
Loading
sequenceDiagram
  participant Client
  participant Auth as JwtStrategy.validate
  participant ASvc as AuthService
  participant DB

  Client->>Auth: JWT payload
  alt payload.walletAddress
    Auth->>ASvc: getUserByWalletAddress(walletAddress)
  else payload.id
    Auth->>ASvc: getUserById(id: UUID)
  end
  ASvc->>DB: SELECT user + roles
  DB-->>ASvc: User
  ASvc-->>Auth: User
  Auth-->>Client: Authorized (user.id UUID, walletAddress)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Villarley

Poem

I hopped through tables, keys in tow,
Swapped numbers for UUIDs—onward we go!
Wallets now guide the warren’s way,
Secrets of ids kept tucked away.
Tests nibble green, migrations sing,
A safer burrow for every spring. 🐇✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🔭 Outside diff range comments (6)
src/modules/auth/controllers/role.controller.ts (1)

22-22: Migrate getMyRoles endpoint to use walletAddress
The controller is still passing req.user.id into getUserRoles, and the service signature only supports a numeric ID. To align with the public-API strategy, switch both controller and service to use the user’s walletAddress.

• In src/modules/auth/controllers/role.controller.ts (line 22):

  • Replace
    const roles = await this.roleService.getUserRoles(req.user.id);
  • With
    const roles = await this.roleService.getUserRoles(req.user.walletAddress);
  • Ensure req.user.walletAddress is populated and typed on the request user object.

• In src/modules/auth/services/role.service.ts:

  • Update the getUserRoles signature to
    async getUserRoles(walletAddress: string): Promise<Role[]> {  }
  • Change the repository query to filter by wallet address (e.g. join on user → walletAddress), or introduce a new method getUserRolesByWalletAddress.
  • Apply the same parameter and query changes to hasRole and hasAnyRole.
--- a/src/modules/auth/controllers/role.controller.ts
+++ b/src/modules/auth/controllers/role.controller.ts
@@ -22,7 +22,7 @@ export class RoleController {
-   const roles = await this.roleService.getUserRoles(req.user.id);
+   const roles = await this.roleService.getUserRoles(req.user.walletAddress);
src/modules/reviews/services/review.service.ts (2)

56-60: Handle unique-constraint race for duplicate reviews.

There’s a TOCTOU between the existence check and save. Under concurrency, the DB unique constraint (@Unique(['userId','productId'])) will be the source of truth. Map Postgres unique violations (23505) to a friendly error.

Apply this diff:

-    try {
-      return await this.repository.save(review);
-    } catch (error) {
-      throw new BadRequestError(`Failed to create review: ${error.message}`);
-    }
+    try {
+      return await this.repository.save(review);
+    } catch (error: any) {
+      // Postgres unique_violation
+      if (error?.code === '23505') {
+        throw new BadRequestError('You have already reviewed this product');
+      }
+      throw new BadRequestError(`Failed to create review: ${error.message || 'Unknown error'}`);
+    }

15-18: Unify AppDataSource import in ReviewService
ReviewService is currently pulling in the default export from config/ormconfig.ts, while most other services (e.g. UserService, RoleService, JwtAuthMiddleware) use the named export from config/database.ts. This mismatch spins up two different DataSource instances and can lead to config drift.

Please update:

• File: src/modules/reviews/services/review.service.ts
• Change the import on line 3:

- import AppDataSource from '../../../config/ormconfig';
+ import { AppDataSource } from '../../../config/database';

After this change, all modules will reference the same AppDataSource definition.

src/modules/auth/services/auth.service.ts (3)

179-192: Avoid duplicating update logic; delegate to UserService.updateUser to retain email-uniqueness validation.

Currently this path updates without the uniqueness check present in UserService.updateUser, risking duplicate emails. Delegate the update here.

Apply this diff:

-  async updateUser(walletAddress: string, updateData: { name?: string; email?: string }): Promise<User> {
-    const user = await this.userRepository.findOne({ where: { walletAddress } });
-
-    if (!user) {
-      throw new BadRequestError('User not found');
-    }
-
-    // Update user data
-    Object.assign(user, updateData);
-    await this.userRepository.save(user);
-
-    return this.getUserByWalletAddress(walletAddress);
-  }
+  async updateUser(walletAddress: string, updateData: { name?: string; email?: string }): Promise<User> {
+    // Reuse centralized validation (email uniqueness, etc.)
+    return this.userService.updateUser(walletAddress, updateData);
+  }

45-55: Dev-only signature bypass is risky; gate via explicit feature flag.

A hardcoded bypass token in development increases the risk of accidental enablement. Gate this behind a dedicated config flag (e.g., AUTH_BYPASS_SIGNATURE=true) and log a clear warning, or restrict it to test environments only.

Follow-up (outside selected lines):

if (process.env.AUTH_BYPASS_SIGNATURE === 'true' && signature === process.env.TEST_SIGNATURE) {
  console.warn('AUTH_BYPASS_SIGNATURE is enabled; skipping signature verification');
  return true;
}

151-158: Consolidate JWT signing via JwtService

This service currently mixes direct jsonwebtoken.sign calls with this.jwtService.sign. For consistency and easier testing, replace all direct sign(…) invocations with this.jwtService.sign(…) and remove the import { sign } from 'jsonwebtoken'.

Occurrences to update:

  • src/modules/auth/services/auth.service.ts
    • Lines 91–93 (after updating a user)
    • Lines 124–126 (after saving a new user)
    • Lines 153–155 (on login)

Example replacement:

- const token = sign({ id: user.id, walletAddress: user.walletAddress, role }, config.jwtSecret, {
-   expiresIn: '1h',
- });
+ const token = this.jwtService.sign(
+   { id: user.id, walletAddress: user.walletAddress, role },
+   { expiresIn: '1h' }
+ );

Also ensure your JwtModule (or equivalent) is configured with the secret and default expiration so you only need to pass payload and optional overrides here. Finally, remove the unused import { sign } from 'jsonwebtoken'.

🧹 Nitpick comments (19)
src/migrations/1751199237000-MigrateUserIdToUUID.ts (2)

22-22: Use IF NOT EXISTS is unnecessary for UNIQUE INDEX

The CREATE UNIQUE INDEX statement uses IF NOT EXISTS but this is a new migration adding the index for the first time.

Simplify the statement:

-  await queryRunner.query(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_users_walletAddress" ON "users" ("walletAddress")`);
+  await queryRunner.query(`CREATE UNIQUE INDEX "IDX_users_walletAddress" ON "users" ("walletAddress")`);

24-26: Missing foreign key updates in this migration

The comment indicates that related tables will be updated separately, but this creates a risk of running migrations out of order.

Consider combining this migration with the foreign key updates in a single transaction, or add explicit checks to ensure migrations run in the correct order. You could also use TypeORM's migration transaction support to ensure atomicity.

src/modules/auth/strategies/jwt.strategy.ts (1)

34-35: Consider adding deprecation logging for id-based authentication

Since the fallback to payload.id is for backward compatibility during migration, consider adding a deprecation warning to track usage and plan for its eventual removal.

 } else if (payload.id) {
   // Fallback to id for backward compatibility during migration
+  console.warn('JWT validation using legacy id field. This will be deprecated in future versions.');
   user = await this.authService.getUserById(payload.id);
src/modules/reviews/entities/review.entity.ts (1)

19-20: UUID FK change for userId looks good; consider aligning cascade behavior.

The switch to type: 'uuid' on userId is correct and consistent with the migration. To avoid mismatches with DB constraints and to ensure cleanup of dependent rows, consider adding onDelete: 'CASCADE' to the relation.

Follow-up change (outside selected lines):

// around Lines 22-24
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user: User;

Please verify that the corresponding FK in the migration for reviews.userId also uses ON DELETE CASCADE so entity metadata and DB constraints stay in sync.

src/modules/reviews/services/review.service.ts (1)

35-39: User existence verification aligns with new ID semantics.

Catching and rethrowing a NotFound here keeps the outward contract clean.

You can avoid the try/catch by having userService.getUserById itself throw a NotFoundError and letting it bubble up unchanged, keeping error mapping centralized.

src/modules/auth/services/role.service.ts (2)

67-69: hasRole signature alignment and logic look good.

Small follow-up: ensure Role.name is constrained to RoleName at the entity/type level to avoid widening to arbitrary strings.


72-75: hasAnyRole signature alignment looks good.

No issues; consider normalizing case if you ever introduce case-insensitive role names.

src/modules/users/services/user.service.ts (1)

46-55: getUserByWalletAddress aligns with wallet-first design; consider 404 semantics.

Implementation is correct and loads roles. Consider throwing a 404-specific error type (e.g., NotFoundError) for absent users to match REST semantics; several other modules use NotFoundError.

src/modules/users/tests/user-update-api.spec.ts (3)

79-95: Response shape assertion is good; ensure controller strips internal IDs.

Test covers walletAddress as the primary identifier and role derivation. Consider also asserting that the response omits id here (as you do later for GET) to enforce consistency across endpoints.


170-195: GET profile test is aligned with wallet-first approach and hides internal id.

Good assertion set. Consider also verifying that roles are derived from userRoles consistently.


260-271: Brittle test: introspecting method source to assert parameter names.

Using toString() on functions to check parameter names is not robust and may break under minification or transpilation. Prefer validating:

  • Route metadata (path template includes ':walletAddress'), or
  • Controller behavior by calling the method with a walletAddress and asserting service calls (already covered).

Recommendation: remove this test or replace it with metadata inspection using Nest’s Reflector or by spinning up the module and checking the route definitions.

src/modules/auth/services/auth.service.ts (3)

193-202: Deduplicate getUserByWalletAddress with UserService.

This method duplicates UserService.getUserByWalletAddress. Prefer delegating to reduce drift and keep relations consistent.

Follow-up change (outside selected lines):

// Remove this method and replace callers with:
return this.userService.getUserByWalletAddress(walletAddress);

247-271: assignRole by walletAddress is correct and consistent with UUID FKs.

  • Good: Lookup user by walletAddress, delete prior roles, assign new one, and return via wallet-based lookup.
  • Optional: Wrap delete+insert in a transaction to avoid transient gaps if another read happens in-between.

273-282: removeRole by walletAddress is correct.

Consider a transaction if other related operations will be added later, but current usage is fine.

src/modules/users/controllers/user.controller.ts (5)

96-98: Route pattern inconsistency with REST conventions

The update route pattern /update/:walletAddress deviates from standard RESTful conventions. Consider using PUT /users/:walletAddress instead.

-  * PUT /users/update/:walletAddress
+  * PUT /users/:walletAddress
   */
-  @Put('update/:walletAddress')
+  @Put(':walletAddress')

139-145: Code duplication: Extract authorization logic

The authorization check logic is duplicated between updateUser and getUserByWalletAddress. Consider extracting this into a private method to follow DRY principles.

Add a private method to handle authorization:

private checkUserAuthorization(
  walletAddress: string,
  currentUserWalletAddress: string | undefined,
  currentUserRole: string | undefined,
  errorMessage: string
): void {
  if (!currentUserWalletAddress) {
    throw new UnauthorizedError('User not authenticated');
  }
  if (walletAddress.toLowerCase() !== currentUserWalletAddress.toLowerCase() && currentUserRole !== 'admin') {
    throw new UnauthorizedError(errorMessage);
  }
}

Then use it in both methods:

-   const currentUserWalletAddress = req.user?.walletAddress;
-   const currentUserRole = req.user?.role?.[0];
-
-   // Check if user is accessing their own profile or is admin
-   if (walletAddress !== currentUserWalletAddress && currentUserRole !== 'admin') {
-     throw new UnauthorizedError('Access denied');
-   }
+   this.checkUserAuthorization(
+     walletAddress,
+     req.user?.walletAddress,
+     req.user?.role?.[0],
+     'Access denied'
+   );

129-131: Update documentation to reflect actual endpoint behavior

The comment states "admin only or own profile" but the endpoint actually allows users to access their own profile OR admins to access any profile. The current wording could be misleading.

   /**
-   * Get user by wallet address (admin only or own profile)
+   * Get user by wallet address (own profile or admin access to any profile)
    * GET /users/:walletAddress
    */

87-87: Consider extracting role resolution logic

The pattern user.userRoles?.[0]?.role?.name || 'buyer' is repeated multiple times throughout the controller. Consider extracting this into a utility method to improve maintainability.

Add a private method:

private getUserRole(user: any): string {
  return user.userRoles?.[0]?.role?.name || 'buyer';
}

Then use it throughout:

-         role: result.user.userRoles?.[0]?.role?.name || 'buyer',
+         role: this.getUserRole(result.user),

Also applies to: 122-122, 155-155, 179-179


24-31: Consider adding JSDoc comments to interfaces

The interfaces would benefit from documentation to clarify their purpose and field meanings, especially for the optional fields.

+/**
+ * Response structure for user data
+ */
 interface UserResponse {
+  /** User's wallet address (primary identifier) */
   walletAddress: string;
+  /** User's display name */
   name: string;
+  /** User's email address */
   email: string;
+  /** User's role in the system */
   role: string;
+  /** Timestamp of user creation */
   createdAt?: Date;
+  /** Timestamp of last update */
   updatedAt?: Date;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 73832ca and 72685c9.

📒 Files selected for processing (20)
  • docs/UUID-MIGRATION.md (1 hunks)
  • src/migrations/1751199237000-MigrateUserIdToUUID.ts (1 hunks)
  • src/migrations/1751199238000-UpdateForeignKeysToUUID.ts (1 hunks)
  • src/modules/auth/controllers/role.controller.ts (1 hunks)
  • src/modules/auth/entities/user-role.entity.ts (1 hunks)
  • src/modules/auth/services/auth.service.ts (6 hunks)
  • src/modules/auth/services/role.service.ts (1 hunks)
  • src/modules/auth/strategies/jwt.strategy.ts (1 hunks)
  • src/modules/buyer-requests/dto/buyer-request-response.dto.ts (1 hunks)
  • src/modules/buyer-requests/entities/buyer-request.entity.ts (1 hunks)
  • src/modules/buyer-requests/services/buyer-requests.service.ts (4 hunks)
  • src/modules/reviews/controllers/review.controller.ts (2 hunks)
  • src/modules/reviews/dto/review.dto.ts (1 hunks)
  • src/modules/reviews/entities/review.entity.ts (1 hunks)
  • src/modules/reviews/services/review.service.ts (3 hunks)
  • src/modules/users/controllers/user.controller.ts (2 hunks)
  • src/modules/users/entities/user.entity.ts (3 hunks)
  • src/modules/users/services/user.service.ts (4 hunks)
  • src/modules/users/tests/user-update-api.spec.ts (1 hunks)
  • src/types/auth-request.type.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/modules/buyer-requests/services/buyer-requests.service.ts (1)
src/modules/buyer-requests/dto/buyer-request-response.dto.ts (1)
  • BuyerRequestResponseDto (3-22)
src/modules/users/services/user.service.ts (1)
src/utils/errors.ts (1)
  • BadRequestError (12-16)
src/modules/auth/services/auth.service.ts (2)
src/utils/errors.ts (1)
  • BadRequestError (12-16)
src/modules/auth/types/role.types.ts (1)
  • RoleName (1-1)
src/modules/auth/services/role.service.ts (1)
src/modules/auth/types/role.types.ts (1)
  • RoleName (1-1)
src/modules/users/controllers/user.controller.ts (2)
src/modules/auth/dto/auth.dto.ts (1)
  • UpdateUserDto (54-70)
src/utils/errors.ts (1)
  • UnauthorizedError (18-22)
🪛 markdownlint-cli2 (0.17.2)
docs/UUID-MIGRATION.md

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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)

🔇 Additional comments (32)
src/modules/reviews/dto/review.dto.ts (1)

10-10: Type change aligns with UUID migration

The change from number to string for userId correctly aligns with the system-wide UUID migration for user IDs.

src/modules/buyer-requests/entities/buyer-request.entity.ts (1)

54-55: UUID column type correctly applied

The change from numeric to UUID type for userId is properly implemented with the correct TypeORM column decorator.

src/modules/buyer-requests/dto/buyer-request-response.dto.ts (1)

11-11: DTO types correctly updated for UUID migration

Both userId and nested user.id fields are properly updated to string type, maintaining consistency with the entity changes.

Also applies to: 16-16

src/types/auth-request.type.ts (1)

5-5: Auth types properly aligned with UUID migration

The type changes from string | number to string with UUID comments properly document the migration and ensure type safety throughout the authentication flow.

Also applies to: 16-16

src/modules/users/entities/user.entity.ts (2)

17-18: UUID primary key correctly implemented

The change from numeric auto-increment to UUID generation is properly implemented using TypeORM's built-in UUID generator.


26-28: Index on walletAddress improves query performance

Adding an index on walletAddress is a good optimization since it's now being used as the primary public identifier for API operations.

src/migrations/1751199238000-UpdateForeignKeysToUUID.ts (2)

19-47: Foreign key constraints properly configured

The foreign key constraints with CASCADE DELETE are correctly added to maintain referential integrity.


8-14: Migration order verified—no further action required
The 1751199237000-MigrateUserIdToUUID runs before 1751199238000-UpdateForeignKeysToUUID as expected. PostgreSQL will fail the ::uuid casts if users.id isn’t already a UUID, so an extra pre-validation query isn’t necessary. Proceed as-is.

src/modules/buyer-requests/services/buyer-requests.service.ts (5)

27-27: API signature changes look good for UUID migration

The updated method signatures correctly accept userId as string, aligning with the UUID migration. The type changes are consistent across all affected methods.

Also applies to: 138-138, 189-189, 289-289


149-151: String comparison for userId is appropriate

The ownership check correctly uses string comparison (!==) which is appropriate for UUID strings.


199-201: Consistent ownership validation

The ownership check maintains consistency with other methods, using string comparison for UUID validation.


300-302: Ownership check properly handles UUID strings

The string comparison for ownership validation is correctly implemented for the UUID-based userId.


269-270: Verify userId field presence in response DTO

The response includes userId: buyerRequest.userId which correctly passes the UUID string to the DTO.

src/modules/reviews/controllers/review.controller.ts (3)

16-16: Direct assignment of userId maintains type consistency

Removing the numeric coercion and directly assigning req.user.id preserves the UUID string type, which aligns with the UUID migration. This ensures type consistency throughout the authentication flow.


81-81: Consistent UUID handling in deleteReview

The removal of numeric coercion maintains type consistency with the UUID migration across both create and delete operations.


32-32: Service calls correctly pass UUID string userId

Both service method calls now receive the userId as a string type, matching the updated service signatures that expect UUID strings.

Also applies to: 91-91

src/modules/auth/entities/user-role.entity.ts (2)

10-11: UUID column type correctly configured

The userId field is properly configured with type: 'uuid' and typed as string, aligning with the UUID migration strategy.


16-18: Foreign key relationship maintained correctly

The @JoinColumn decorator correctly references 'userId' which will properly map to the UUID column in the database after migration.

src/modules/auth/strategies/jwt.strategy.ts (2)

27-36: Well-structured dual authentication path for migration

The implementation provides a clean migration path by:

  1. Preferring walletAddress-based authentication (the new primary identifier)
  2. Falling back to UUID-based authentication for backward compatibility
  3. Properly validating the token payload structure

This approach ensures zero-downtime migration while existing tokens remain valid.


42-46: Clear separation of internal and public identifiers

The returned user object properly maintains both identifiers with clear documentation about their purposes - UUID for internal use and walletAddress for public APIs.

src/modules/auth/controllers/role.controller.ts (1)

12-16: API correctly updated to use walletAddress as primary identifier

The endpoint now accepts walletAddress instead of userId, aligning with the architectural shift to use wallet addresses as the primary public identifier. The removal of toString() conversions is appropriate since both parameters are already strings.

src/modules/reviews/services/review.service.ts (1)

95-108: deleteReview signature update to string UUID is correct.

The ownership check remains valid with string IDs.

src/modules/auth/services/role.service.ts (2)

55-57: removeRoleFromUser userId: string change is correct.

Query shape now matches the updated UUID FK type.


61-63: Query by string userId is consistent with UUID migration.

Looks good.

src/modules/users/services/user.service.ts (4)

57-61: getUserById switch to string id is correct.

The repository query now matches UUIDs without numeric coercion.


72-88: updateUser via walletAddress is correct; email uniqueness guard is solid.

  • Good: prevent email collisions by checking existingUser.id !== user.id.
  • Ensure a unique index exists on email at the DB level to back-stop this logic (optional).

You can check for a unique index via migration files or DB schema. If not present, I can propose a migration.


90-93: getUserOrders by walletAddress is aligned.

Loads orders via relation; consistent with wallet-first.


103-106: getUserWishlist by walletAddress is aligned.

Loads wishlist via relation; consistent with wallet-first.

src/modules/users/tests/user-update-api.spec.ts (3)

114-128: Unauthorized update test is sound; confirm controller throws the same error type.

You’re asserting UnauthorizedError (custom). Verify the controller uses this class rather than Nest’s UnauthorizedException to avoid brittle test coupling.

I can scan the controller to confirm the error type used and adjust the test accordingly.


130-155: Partial update and email-only tests add valuable coverage.

These assert field-level updates and retention of other fields correctly.


214-228: Unauthorized GET test is correct; ensure controller/guides enforce wallet-based ownership.

This protects against cross-account reads. No additional issues.

src/modules/auth/services/auth.service.ts (1)

165-167: getUserById: switch to string UUID query is correct.

This aligns with the UUID migration.

Comment on lines +32 to +35
```
PUT /users/update/:id
GET /users/:id
```
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add language identifiers to fenced code blocks (markdownlint MD040).

Specify a language for the HTTP route examples to satisfy markdownlint and improve rendering.

Apply this diff:

-```
+```http
 PUT /users/update/:id
 GET /users/:id

- +http
PUT /users/update/:walletAddress
GET /users/:walletAddress

Run markdownlint to verify:

Also applies to: 38-41


🏁 Script executed:

#!/bin/bash
npx --yes markdownlint-cli2 "docs/**/*.md" || true
# Or quick grep for missing-language fences:
rg -n "^```$" docs || true

Length of output: 18567


🏁 Script executed:

#!/bin/bash
set -e
echo "Listing all fenced code blocks in docs/UUID-MIGRATION.md with line numbers:"
rg -n "```" docs/UUID-MIGRATION.md
echo
echo "Displaying lines 30 to 50 of docs/UUID-MIGRATION.md for context:"
sed -n '30,50p' docs/UUID-MIGRATION.md

Length of output: 286


🏁 Script executed:

#!/bin/bash
set -e
echo "Fenced code blocks in docs/UUID-MIGRATION.md (line numbers):"
rg -n "```" docs/UUID-MIGRATION.md

echo
echo "Showing lines 31–42 for context:"
sed -n '31,42p' docs/UUID-MIGRATION.md

Length of output: 256


Add HTTP language identifiers to code fences in docs/UUID-MIGRATION.md

To satisfy markdownlint MD040 and improve readability, the unnamed fenced blocks under “Before (using numeric ID)” and “After (using walletAddress)” need a language identifier.

• docs/UUID-MIGRATION.md, lines 32–35 (Before)
• docs/UUID-MIGRATION.md, lines 38–41 (After)

Apply this diff:

-``` 
+```http
 PUT /users/update/:id
 GET /users/:id
-``` 
+```

-``` 
+```http
 PUT /users/update/:walletAddress
 GET /users/:walletAddress
-``` 
+```
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

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

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In docs/UUID-MIGRATION.md around lines 32–35 and 38–41, the fenced code blocks
under “Before (using numeric ID)” and “After (using walletAddress)” are missing
a language identifier and the second block should show walletAddress; update
both fences to start with ```http and end with ``` and change the routes in the
“After” block from /users/update/:id and /users/:id to
/users/update/:walletAddress and /users/:walletAddress respectively so the
examples are language-tagged and correct.

Comment on lines +8 to +11
await queryRunner.query(`ALTER TABLE "users" ADD COLUMN "id_new" UUID DEFAULT gen_random_uuid()`);

// Update existing records to have unique UUIDs
await queryRunner.query(`UPDATE "users" SET "id_new" = gen_random_uuid() WHERE "id_new" IS NULL`);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Redundant UUID generation in UPDATE statement

Line 11 attempts to update records where id_new IS NULL, but line 8 already sets a default value of gen_random_uuid() for the column. This UPDATE will never find any NULL values.

Remove the redundant UPDATE or modify the logic:

   // First, add a new UUID column
-  await queryRunner.query(`ALTER TABLE "users" ADD COLUMN "id_new" UUID DEFAULT gen_random_uuid()`);
+  await queryRunner.query(`ALTER TABLE "users" ADD COLUMN "id_new" UUID`);
   
   // Update existing records to have unique UUIDs
-  await queryRunner.query(`UPDATE "users" SET "id_new" = gen_random_uuid() WHERE "id_new" IS NULL`);
+  await queryRunner.query(`UPDATE "users" SET "id_new" = gen_random_uuid()`);
📝 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
await queryRunner.query(`ALTER TABLE "users" ADD COLUMN "id_new" UUID DEFAULT gen_random_uuid()`);
// Update existing records to have unique UUIDs
await queryRunner.query(`UPDATE "users" SET "id_new" = gen_random_uuid() WHERE "id_new" IS NULL`);
// First, add a new UUID column
await queryRunner.query(`ALTER TABLE "users" ADD COLUMN "id_new" UUID`);
// Update existing records to have unique UUIDs
await queryRunner.query(`UPDATE "users" SET "id_new" = gen_random_uuid()`);
🤖 Prompt for AI Agents
In src/migrations/1751199237000-MigrateUserIdToUUID.ts around lines 8–11, the
migration adds id_new with DEFAULT gen_random_uuid(), so the subsequent UPDATE
... WHERE "id_new" IS NULL is redundant (it will never match); remove the UPDATE
statement (or replace it with a simple UPDATE "users" SET "id_new" =
gen_random_uuid() without the WHERE if you intentionally want to force new UUIDs
for all existing rows), and keep only the ADD COLUMN with the default (and later
steps to backfill/replace the old id if needed).

Comment on lines +29 to +44
public async down(queryRunner: QueryRunner): Promise<void> {
// Revert to SERIAL id
await queryRunner.query(`ALTER TABLE "users" ADD COLUMN "id_old" SERIAL`);

// Drop the UUID primary key constraint
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433"`);

// Rename columns
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "id"`);
await queryRunner.query(`ALTER TABLE "users" RENAME COLUMN "id_old" TO "id"`);

// Restore the SERIAL primary key
await queryRunner.query(`ALTER TABLE "users" ADD CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id")`);

// Drop the walletAddress index
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_users_walletAddress"`);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Down migration is destructive and will fail

The down migration attempts to convert UUIDs back to SERIAL integers, which will fail because:

  1. UUIDs cannot be cast to integers
  2. Even if it worked, all relationships would be broken
  3. The SERIAL sequence wouldn't know what value to continue from

This migration should be marked as irreversible or implement a proper rollback strategy:

  public async down(queryRunner: QueryRunner): Promise<void> {
+    throw new Error('This migration cannot be reversed. UUID to integer conversion would result in data loss.');
-    // Revert to SERIAL id
-    await queryRunner.query(`ALTER TABLE "users" ADD COLUMN "id_old" SERIAL`);
-    // ... rest of the down migration
  }
📝 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
public async down(queryRunner: QueryRunner): Promise<void> {
// Revert to SERIAL id
await queryRunner.query(`ALTER TABLE "users" ADD COLUMN "id_old" SERIAL`);
// Drop the UUID primary key constraint
await queryRunner.query(`ALTER TABLE "users" DROP CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433"`);
// Rename columns
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "id"`);
await queryRunner.query(`ALTER TABLE "users" RENAME COLUMN "id_old" TO "id"`);
// Restore the SERIAL primary key
await queryRunner.query(`ALTER TABLE "users" ADD CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id")`);
// Drop the walletAddress index
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_users_walletAddress"`);
public async down(queryRunner: QueryRunner): Promise<void> {
throw new Error('This migration cannot be reversed. UUID to integer conversion would result in data loss.');
}

Comment on lines +59 to +61
await queryRunner.query(`ALTER TABLE "user_roles" ALTER COLUMN "userId" TYPE INTEGER USING "userId"::integer`);
await queryRunner.query(`ALTER TABLE "buyer_requests" ALTER COLUMN "userId" TYPE INTEGER USING "userId"::integer`);
await queryRunner.query(`ALTER TABLE "reviews" ALTER COLUMN "userId" TYPE INTEGER USING "userId"::integer`);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Down migration will lose UUID data

Converting UUIDs back to integers in the down migration will result in data loss since UUIDs cannot be meaningfully converted to integers. This makes the migration effectively irreversible in production.

Consider documenting this as a one-way migration or implementing a mapping table approach:

   // Revert column types to integer (this will require data migration in a real scenario)
+  // WARNING: This is a destructive operation that will lose the UUID values
+  // Consider implementing a mapping table if rollback capability is needed
   await queryRunner.query(`ALTER TABLE "user_roles" ALTER COLUMN "userId" TYPE INTEGER USING "userId"::integer`);

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

🤖 Prompt for AI Agents
In src/migrations/1751199238000-UpdateForeignKeysToUUID.ts around lines 59 to
61, the down migration attempts to cast UUID foreign-key values back to INTEGER
which will lose data; either treat this migration as irreversible and make
down() throw or log a clear one-way migration notice, or implement a reversible
strategy: create and populate mapping tables before the up() change that map old
integer IDs to new UUIDs (and persist those maps), then in down() use those
mapping tables to restore integer IDs and update FK references and constraints;
update migration comments and tests accordingly to reflect the chosen approach.

Comment on lines +50 to 52
userId,
roleId: role.id,
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Saving string userId to UserRole is correct; add type-safety and dedup protection.

  • Use the shared RoleName type instead of local string for assignRoleToUser to prevent drift.
  • Consider preventing duplicate role rows (unique constraint on [userId, roleId] and/or idempotent assign).

Follow-ups (outside selected lines):

  • Replace the local alias with an import:
// top of file
- type RoleName = 'buyer' | 'seller' | 'admin';
+ import type { RoleName } from '../types/role.types';
  • Strongly type the parameter:
- async assignRoleToUser(userId: string, roleName: string): Promise<void> {
+ async assignRoleToUser(userId: string, roleName: RoleName): Promise<void> {
  • Optionally, enforce idempotency:
const existing = await this.userRoleRepository.findOne({ where: { userId, roleId: role.id } });
if (existing) return;
🤖 Prompt for AI Agents
In src/modules/auth/services/role.service.ts around lines 50 to 52, the method
currently accepts a plain string roleName and writes userId/roleId rows without
guarding duplicates; update the file to import and use the shared RoleName type
for the assignRoleToUser parameter to ensure type-safety (replace local alias
with import at the top of the file and change the method signature to accept
RoleName), and add idempotency/dedup protection before inserting (check for an
existing UserRole row with the same userId and roleId and return early if found)
or ensure a unique constraint on [userId, roleId] so duplicate rows are
prevented.

Comment on lines +21 to 24
userId: string,
productId: number,
rating: number,
comment?: string
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Signature update to userId: string is correct and consistent.

The public method now aligns with UUID-based user IDs.

Ensure all controller and DTO entry points now pass a string UUID for userId. Run:


🏁 Script executed:

#!/bin/bash
# Find any parseInt/Number conversions for userId that may remain
rg -n "parseInt\\(\\s*userId\\s*\\)|Number\\(\\s*userId\\s*\\)|: number\\s*[),]" -A 2

Length of output: 23595


Remove remaining numeric conversions of userId

Several parseInt(userId) and Number(userId) usages remain in your services and controllers, which will break once userId is treated exclusively as a UUID string. Please update these to accept and propagate the string UUID (or convert at a single, well-defined boundary).

• src/modules/shared/services/role.service.ts
– line 49: save({ userId: parseInt(userId), … })
– line 58: where: { userId: parseInt(userId) }

• src/modules/wishlist/services/wishlist.service.ts
– lines 28–30, 48–50, 60–62: calls to this.toNumber(userId)

• src/modules/products/controllers/product.controller.ts
– line 78: sellerId: Number(userId)

Please remove or centralize these conversions so that all entry points consistently accept a string userId UUID.

🤖 Prompt for AI Agents
In src/modules/reviews/services/review.service.ts around lines 21–24, and
related services/controllers noted in the review, several places still convert
userId to a number (parseInt/Number/this.toNumber); remove those numeric
conversions and treat userId as a UUID string throughout the service layer:
update calls to save({ userId, … }) and where: { userId } to pass the string
UUID, remove usages of this.toNumber(userId) and Number(userId) in the listed
files, and if numeric conversion is absolutely required keep it only at a
single, well-defined boundary (e.g., an adapter or controller entry point) and
change downstream types/signatures to accept string userId.

Comment on lines +106 to 112
const currentUserWalletAddress = req.user?.walletAddress;
const currentUserRole = req.user?.role?.[0];

// Check if user is updating their own profile or is admin
if (userId !== currentUserId && currentUserRole !== 'admin') {
if (walletAddress !== currentUserWalletAddress && currentUserRole !== 'admin') {
throw new UnauthorizedError('You can only update your own profile');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential security issue: Missing null checks for user authentication

The code accesses req.user?.walletAddress and req.user?.role?.[0] but doesn't explicitly verify that req.user exists before the authorization check. While the JwtAuthGuard should ensure authentication, adding explicit null checks would make the code more defensive.

  async updateUser(
    @Param('walletAddress') walletAddress: string,
    @Body() updateDto: UpdateUserDto,
    @Req() req: Request
  ): Promise<UserUpdateResponse> {
+    if (!req.user) {
+      throw new UnauthorizedError('User not authenticated');
+    }
    const currentUserWalletAddress = req.user?.walletAddress;
    const currentUserRole = req.user?.role?.[0];
📝 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 currentUserWalletAddress = req.user?.walletAddress;
const currentUserRole = req.user?.role?.[0];
// Check if user is updating their own profile or is admin
if (userId !== currentUserId && currentUserRole !== 'admin') {
if (walletAddress !== currentUserWalletAddress && currentUserRole !== 'admin') {
throw new UnauthorizedError('You can only update your own profile');
}
async updateUser(
@Param('walletAddress') walletAddress: string,
@Body() updateDto: UpdateUserDto,
@Req() req: Request
): Promise<UserUpdateResponse> {
if (!req.user) {
throw new UnauthorizedError('User not authenticated');
}
const currentUserWalletAddress = req.user?.walletAddress;
const currentUserRole = req.user?.role?.[0];
// Check if user is updating their own profile or is admin
if (walletAddress !== currentUserWalletAddress && currentUserRole !== 'admin') {
throw new UnauthorizedError('You can only update your own profile');
}
// ...rest of the update logic...
}
🤖 Prompt for AI Agents
In src/modules/users/controllers/user.controller.ts around lines 106 to 112, the
code uses req.user?.walletAddress and req.user?.role?.[0] but doesn't explicitly
verify req.user exists before performing the auth check; add a defensive check
that throws UnauthorizedError (or same auth error) if req.user is
null/undefined, and ensure req.user.role is an array (or treat missing role as
non-admin) before comparing role[0] to 'admin' so the authorization condition
reliably handles unauthenticated requests and missing role data.

Comment on lines +110 to 112
if (walletAddress !== currentUserWalletAddress && currentUserRole !== 'admin') {
throw new UnauthorizedError('You can only update your own profile');
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider case-insensitive wallet address comparison

Ethereum wallet addresses are case-insensitive in their canonical form, but may be provided in different cases (checksummed vs lowercase). Consider normalizing the addresses before comparison to avoid false negatives.

    // Check if user is updating their own profile or is admin
-   if (walletAddress !== currentUserWalletAddress && currentUserRole !== 'admin') {
+   if (walletAddress.toLowerCase() !== currentUserWalletAddress?.toLowerCase() && currentUserRole !== 'admin') {
      throw new UnauthorizedError('You can only update your own profile');
    }
📝 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
if (walletAddress !== currentUserWalletAddress && currentUserRole !== 'admin') {
throw new UnauthorizedError('You can only update your own profile');
}
// Check if user is updating their own profile or is admin
if (
walletAddress.toLowerCase() !== currentUserWalletAddress?.toLowerCase() &&
currentUserRole !== 'admin'
) {
throw new UnauthorizedError('You can only update your own profile');
}
🤖 Prompt for AI Agents
In src/modules/users/controllers/user.controller.ts around lines 110 to 112, the
wallet address equality check is case-sensitive which can cause false negatives
for checksummed vs lowercase addresses; normalize both addresses before
comparing (e.g., trim and convert both walletAddress and
currentUserWalletAddress to the same case such as .toLowerCase(), or use a
reliable library method like ethers.utils.getAddress to canonicalize them) and
then perform the comparison, ensuring you also handle null/undefined values
safely before normalization.

@Villarley Villarley changed the base branch from main to Develop August 19, 2025 23:46
Copy link
Contributor

@Villarley Villarley left a comment

Choose a reason for hiding this comment

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

LGTM

@Villarley Villarley merged commit 4db15d8 into StarShopCr:Develop Aug 20, 2025
1 check passed
This was referenced Aug 20, 2025
@coderabbitai coderabbitai bot mentioned this pull request Aug 31, 2025
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments