feat: implement authentication module using clean architecture with Login and Register use cases - #1730
Conversation
…ogin and Register use cases
📝 WalkthroughWalkthroughThis change adds a hexagonal authentication module. It separates domain entities, application use cases, ports, infrastructure adapters, and HTTP presentation. Registration and login now use the new controller and dependency wiring. ChangesHexagonal authentication
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthController
participant RegisterUser
participant UserRepositoryMongoImpl
participant EmailServiceImpl
Client->>AuthController: POST /register
AuthController->>RegisterUser: execute({name, email, password, frontendUrl})
RegisterUser->>UserRepositoryMongoImpl: findByEmail(email)
UserRepositoryMongoImpl-->>RegisterUser: UserEntity or no user
RegisterUser->>UserRepositoryMongoImpl: save(UserEntity)
RegisterUser->>EmailServiceImpl: sendVerificationEmail(to, verificationUrl)
AuthController-->>Client: 201 registration response
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/src/auth/application/useCases/RegisterUser.js`:
- Around line 34-39: Update both email-delivery catch blocks in RegisterUser.js
at lines 34-39 and 71-76: do not suppress sendVerificationEmail failures after
persisting a verification token. Either enqueue durable retry work for the
replacement and initial verification emails, or propagate a retriable error so
registration is not reported as complete without a usable delivery path.
- Around line 23-24: Update the registration flow around RegisterUser to
eliminate the check-then-save race: retain the lookup only as an early
optimization, but catch the User model’s duplicate-email constraint error from
the save operation and return the existing generic registration response;
alternatively, use an atomic repository operation that handles lookup and
creation together.
In `@backend/src/auth/domain/entities/UserEntity.js`:
- Around line 49-62: Update the profileDetails initialization in the UserEntity
constructor so the top-level profileDetails values are spread before assigning
socials. Then construct socials by merging the default social fields with
profileDetails.socials, preserving defaults when persisted data provides only
partial socials.
In `@backend/src/auth/infrastructure/database/UserRepositoryMongoImpl.js`:
- Around line 44-46: Update the existing-user branch in UserRepositoryMongoImpl
to remove upsert from findByIdAndUpdate options, then detect a null update
result and throw the repository’s established not-found or concurrency error
before returning. Preserve the normal updated-document flow for existing users
found successfully.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 9a61f931-25ba-41ad-b4ee-bcb0ceca548f
📒 Files selected for processing (15)
backend/routes/authRoutes.jsbackend/src/auth/application/ports/IEmailService.jsbackend/src/auth/application/ports/IUserRepository.jsbackend/src/auth/application/useCases/LoginUser.jsbackend/src/auth/application/useCases/LoginUser.unit.test.jsbackend/src/auth/application/useCases/RegisterUser.jsbackend/src/auth/application/useCases/RegisterUser.unit.test.jsbackend/src/auth/domain/entities/UserEntity.jsbackend/src/auth/index.jsbackend/src/auth/infrastructure/database/UserRepositoryMongoImpl.jsbackend/src/auth/infrastructure/services/EmailServiceImpl.jsbackend/src/auth/infrastructure/services/TokenServiceImpl.jsbackend/src/auth/presentation/controllers/AuthController.jsbackend/src/auth/presentation/routes/authRoutesHex.jsbackend/src/shared/domain/BaseError.js
|
@TanCodeX address the coderabbit suggestions |
… UserEntity profile initialization logic
@KaranUnique Done, please review. |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/src/auth/application/useCases/RegisterUser.js (1)
27-35: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftPrevent concurrent verification refreshes from invalidating tokens.
When two requests find the same unverified user, each request saves a different token hash and sends its matching raw token. The later save can complete before the earlier email send. The stored hash can then differ from the token in the delivered email, so the user cannot verify the account.
Serialize token refresh and delivery, or use an outbox/versioned operation that never sends a superseded token.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/src/auth/application/useCases/RegisterUser.js` around lines 27 - 35, Update the unverified-user branch in RegisterUser so concurrent registration requests cannot overwrite a verification token while another request is delivering its email. Serialize token generation, persistence, and sendVerificationEmail for each user, or implement an outbox/version check that prevents delivery of superseded tokens while preserving matching stored hashes and raw tokens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/src/auth/application/useCases/RegisterUser.js`:
- Around line 68-73: Update the ConflictError handling in RegisterUser so it
returns alreadyRegistered only when the underlying duplicate-key conflict
targets the email field. Inspect the error’s duplicate-key details or preserved
field metadata, and rethrow conflicts involving prepPilotId or any other field.
---
Outside diff comments:
In `@backend/src/auth/application/useCases/RegisterUser.js`:
- Around line 27-35: Update the unverified-user branch in RegisterUser so
concurrent registration requests cannot overwrite a verification token while
another request is delivering its email. Serialize token generation,
persistence, and sendVerificationEmail for each user, or implement an
outbox/version check that prevents delivery of superseded tokens while
preserving matching stored hashes and raw tokens.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 00cabd39-64b9-4f6a-ae56-2790a66ef781
📒 Files selected for processing (3)
backend/src/auth/application/useCases/RegisterUser.jsbackend/src/auth/domain/entities/UserEntity.jsbackend/src/auth/infrastructure/database/UserRepositoryMongoImpl.js
🚧 Files skipped from review as they are similar to previous changes (2)
- backend/src/auth/infrastructure/database/UserRepositoryMongoImpl.js
- backend/src/auth/domain/entities/UserEntity.js
… granularity with field metadata
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/src/auth/application/useCases/RegisterUser.js`:
- Around line 33-40: Make token freshness and verification-email delivery atomic
in the registration flow around the user repository save and
emailService.sendVerificationEmail call. Replace the separate findByEmail check
with a repository/email-port operation that conditionally delivers only when the
saved token version remains current, or preserve every unexpired verification
token until expiry; add a concurrency test covering a token replacement between
the freshness check and delivery.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 66f6a3ce-f189-4eb2-90b3-d7c813617952
📒 Files selected for processing (3)
backend/src/auth/application/useCases/RegisterUser.jsbackend/src/auth/infrastructure/database/UserRepositoryMongoImpl.jsbackend/src/shared/domain/BaseError.js
🚧 Files skipped from review as they are similar to previous changes (2)
- backend/src/shared/domain/BaseError.js
- backend/src/auth/infrastructure/database/UserRepositoryMongoImpl.js
| await this.userRepository.save(userExists); | ||
|
|
||
| // Outbox/version check to prevent delivery of superseded tokens | ||
| const latestUser = await this.userRepository.findByEmail(cleanEmail); | ||
| if (latestUser && latestUser.emailVerificationToken === hashedToken) { | ||
| const verificationUrl = `${frontendUrl}/verify-email?token=${rawToken}`; | ||
| await this.emailService.sendVerificationEmail(userExists.email, verificationUrl); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift
Make token freshness atomic with email delivery.
The save at Line 33, re-read at Line 36, and email send at Line 39 are separate operations. A concurrent request can replace the token after Line 36 returns and before Line 39 runs. The first request can then send a token that verification no longer accepts.
Use a repository/email port operation that couples token versioning to delivery, or keep each unexpired verification token valid until expiry. Add a concurrency test for this interleaving.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/src/auth/application/useCases/RegisterUser.js` around lines 33 - 40,
Make token freshness and verification-email delivery atomic in the registration
flow around the user repository save and emailService.sendVerificationEmail
call. Replace the separate findByEmail check with a repository/email-port
operation that conditionally delivers only when the saved token version remains
current, or preserve every unexpired verification token until expiry; add a
concurrency test covering a token replacement between the freshness check and
delivery.
📝 Pull Request Description
Related Issue
Closes #1608
Summary
Migrates the authentication domain toward a Hexagonal Architecture (Ports and Adapters) by separating business logic from Express and Mongoose dependencies.
This first phase introduces a clean domain/application/infrastructure/presentation structure under
backend/src/, while keeping the existing authentication routes backward compatible.Key changes include:
RegisterUserandLoginUseruse cases.UserEntitywithout Mongoose dependencies./registerand/loginroutes to use the new hexagonal implementation.This establishes a reusable blueprint for migrating additional authentication endpoints and future domains to Hexagonal Architecture.
Type of Change
How Has This Been Tested?
Describe the testing steps performed.
RegisterUserandLoginUserusing Vitest./registerand/loginendpoints continue to work through the existing authentication router.Screenshots (if applicable)
Not applicable — this PR contains backend architecture and testing changes.
Checklist
Looks good to me. Ready to merge.