Skip to content

feature/92/implement-login-endpoint#247

Merged
Markian-Grybok merged 1 commit into
devfrom
feature/92/implement-login-endpoint
Sep 24, 2025
Merged

feature/92/implement-login-endpoint#247
Markian-Grybok merged 1 commit into
devfrom
feature/92/implement-login-endpoint

Conversation

@Markian-Grybok

@Markian-Grybok Markian-Grybok commented Sep 24, 2025

Copy link
Copy Markdown
Contributor

dev

JIRA

Code reviewers

  • @github_username

Second Level Review

  • @github_username

Summary of issue

ToDo

Summary of change

ToDo

Testing approach

ToDo

CHECK LIST

  • СI passed
  • Сode coverage >=95%
  • PR is reviewed manually again (to make sure you have 100% ready code)
  • All reviewers agreed to merge the PR
  • I've checked new feature as logged in and logged out user if needed
  • PR meets all conventions

Summary by CodeRabbit

  • New Features
    • Added a login endpoint enabling users to sign in and receive an authentication token.
  • Improvements
    • Enhanced input validation for login credentials, ensuring proper email format and required fields.
    • Localized error messages for authentication (e.g., incorrect email or password) for clearer feedback.
  • Chores
    • Updated project resources to support localized authentication messages.

@coderabbitai

coderabbitai Bot commented Sep 24, 2025

Copy link
Copy Markdown

Walkthrough

Adds a MediatR-based login flow: request/handler, validators, Web API endpoint, and localization for auth errors. Removes DTO data annotations in favor of FluentValidation. Updates project resources and introduces unit tests for the handler and validators.

Changes

Cohort / File(s) Summary
DTO validation removal
Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs
Removed [Required] and [MaxLength(20)] from Login and Password properties.
MediatR login flow
Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginCommand.cs, .../LoginHandler.cs
Added LoginCommand carrying UserLoginDTO and LoginHandler that verifies credentials via UserManager, returns Result, generates JWT via IJwtTokenService, and logs errors.
Auth error resources
Streetcode/Streetcode.BLL/Resources/Errors.Auth.resx, .../Errors.Auth.Designer.cs, Streetcode/Streetcode.BLL/Streetcode.BLL.csproj
Introduced auth error resource (IncorrectEmailOrPassword) and designer; updated .csproj to generate and embed resources.
FluentValidation validators
Streetcode/Streetcode.BLL/Validators/Auth/LoginValidator.cs, .../LoginCommandValidator.cs
Added validators for UserLoginDTO and LoginCommand, enforcing email format and non-empty fields; composed via SetValidator.
Web API endpoint
Streetcode/Streetcode.WebApi/Controllers/Auth/AuthController.cs
Added POST /login action using Mediator with LoginCommand; returns Ok on success, BadRequest on failure.
Unit tests — handler
Streetcode/Streetcode.XUnitTest/BLL/MediatR/Auth/Login/LoginHandleTests.cs
Added tests for success, non-existing user, wrong password, and exception paths; mocks UserManager, JWT service, logger.
Unit tests — validation
Streetcode/Streetcode.XUnitTest/BLL/Validators/Auth/LoginCommandValidatorTests.cs, .../LoginValidatorTests.cs
Added tests covering valid/invalid emails, empty/null fields, and edge cases for both validators.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant API as AuthController
  participant M as Mediator
  participant H as LoginHandler
  participant UM as UserManager
  participant JWT as IJwtTokenService
  participant L as ILoggerService

  C->>API: POST /auth/login { UserLoginDTO }
  API->>M: Send(LoginCommand)
  M->>H: Handle(LoginCommand)

  H->>UM: FindByEmailAsync(login)
  alt User not found
    H->>L: Log error
    H-->>M: Result.Fail(IncorrectEmailOrPassword)
  else User found
    H->>UM: CheckPasswordAsync(user, password)
    alt Wrong password
      H->>L: Log error
      H-->>M: Result.Fail(IncorrectEmailOrPassword)
    else Valid password
      H->>JWT: GenerateTokenAsync(user.Id)
      H-->>M: Result.Ok(LoginResultDTO{ token })
    end
  end

  M-->>API: Result
  alt Success
    API-->>C: 200 OK { token }
  else Failure
    API-->>C: 400 BadRequest { errors }
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

task

Suggested reviewers

  • denys-pavskyi
  • weazy12
  • Skiper29

Poem

Hop hop! I nudge the gate—login’s on the way,
Commands dispatched, handlers dance, errors kept at bay.
Tokens bloom like clover leaves, green and neatly signed,
Validators sniff the trail—no empty fields behind.
With gentle paws I press “OK,” then bound into the day. 🐇🔐

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description follows the repository template structure but contains only placeholders ("ToDo") and an unresolved JIRA placeholder, leaving "Summary of issue", "Summary of change", and "Testing approach" empty and the checklist unchecked, so it lacks the information required for a meaningful review. Because essential description and testing details are missing, the description is incomplete and insufficient for approval. Populate the template before merge: replace the JIRA placeholder with the actual ticket URL, provide a clear "Summary of issue" and "Summary of change" describing added endpoint, handlers, validators, resources and project file updates, and fill the "Testing approach" with test results and manual verification steps; also update the checklist items (CI, coverage) and assign reviewers, and optionally include example request/response and any migration or configuration notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feature/92/implement-login-endpoint" directly references the primary change (implementing the login endpoint and related handlers/validators/resources) so it accurately reflects the changeset, but it uses a branch-style prefix rather than a concise imperative sentence which slightly reduces readability in changelogs. Overall it is specific and related to the PR contents.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/92/implement-login-endpoint

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@Markian-Grybok Markian-Grybok linked an issue Sep 24, 2025 that may be closed by this pull request
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (10)
Streetcode/Streetcode.WebApi/Controllers/Auth/AuthController.cs (1)

34-37: Standardize error response shape across endpoints

ChangePassword returns BadRequest(result) while Register/Login return BadRequest(result.Errors.Select(...)). Consider unifying the error payload shape for consistency and cleaner clients.

Streetcode/Streetcode.BLL/Validators/Auth/LoginValidator.cs (1)

12-18: Stop-on-first failure and validate emptiness before format

Reorder to avoid dual errors on empty input and stop cascading once NotEmpty fails. Consider built-in EmailAddress for maintainability.

-        RuleFor(x => x.Login)
-                .Matches(@"^(?!.*\.\.)[a-zA-Z0-9_%+-]+(?:\.[a-zA-Z0-9_%+-]+)*@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").WithMessage(Errors_Validation.EmailAddressFormat.FormatWith("Login"))
-                .NotEmpty().WithMessage(Errors_Validation.CannotBeEmpty.FormatWith("Login"));
+        RuleFor(x => x.Login)
+                .Cascade(CascadeMode.Stop)
+                .NotEmpty().WithMessage(Errors_Validation.CannotBeEmpty.FormatWith("Login"))
+                .EmailAddress().WithMessage(Errors_Validation.EmailAddressFormat.FormatWith("Login"));
Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginCommand.cs (1)

7-8: Public API naming: use PascalCase for record property

Avoid camelCase public members. Recommend renaming userLoginDTO to UserLoginDto (or Credentials) for consistency.

-public record LoginCommand(UserLoginDTO userLoginDTO)
+public record LoginCommand(UserLoginDTO UserLoginDto)
     : IRequest<Result<LoginResultDTO>>;

Follow-up: update usages in handler, validators, tests (e.g., request.UserLoginDto, RuleFor(c => c.UserLoginDto), and test expressions).

Streetcode/Streetcode.BLL/Validators/Auth/LoginCommandValidator.cs (1)

8-11: Guard against null DTO before delegating to nested validator

If userLoginDTO is null, the nested validator won’t run and no error will be produced. Add a NotNull guard.

-        RuleFor(dto => dto.userLoginDTO).SetValidator(loginValidator);
+        RuleFor(c => c.userLoginDTO)
+            .NotNull()
+            .SetValidator(loginValidator);

If you apply the property rename in LoginCommand, update to c => c.UserLoginDto.

Streetcode/Streetcode.XUnitTest/BLL/Validators/Auth/LoginCommandValidatorTests.cs (1)

104-119: Add a test for null DTO to cover the NotNull guard

Covers the case where the command is constructed with a null payload.

     [Fact]
     public void Validate_ComplexValidEmail_ShouldPass()
     {
         // Arrange
         var command = new LoginCommand(new UserLoginDTO
         {
             Login = "user.name+test@subdomain.example.co.uk",
             Password = "ComplexPass123!"
         });

         // Act
         var result = _validator.TestValidate(command);

         // Assert
         result.ShouldNotHaveAnyValidationErrors();
     }
+
+    [Fact]
+    public void Validate_NullDto_ShouldFail()
+    {
+        // Arrange
+        var command = new LoginCommand(null);
+
+        // Act
+        var result = _validator.TestValidate(command);
+
+        // Assert
+        result.ShouldHaveValidationErrorFor(x => x.userLoginDTO);
+    }
Streetcode/Streetcode.BLL/Streetcode.BLL.csproj (1)

130-133: PublicResXFileCodeGenerator choice

Using PublicResXFileCodeGenerator is appropriate if consumers outside BLL need these strings. If not, consider internal to reduce surface area.

Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginHandler.cs (1)

29-45: Consider SignInManager for lockout/2FA/email‑confirm flows

Using UserManager for password checks bypasses Identity features (lockout on failure, 2FA, email confirmation). Prefer SignInManager.CheckPasswordSignInAsync(user, password, lockoutOnFailure: true) and handle its result.

Streetcode/Streetcode.XUnitTest/BLL/Validators/Auth/LoginValidatorTests.cs (2)

137-152: Add whitespace cases and trimming behavior

Current validator doesn’t trim inputs; whitespace-only values may pass Matches. Add tests (and optionally Trim in the validator).

Apply this diff to extend tests:

+    [Fact]
+    public void Validate_LoginWithWhitespaceOnly_ShouldHaveError()
+    {
+        var dto = new UserLoginDTO { Login = "   ", Password = "validPassword123" };
+        var result = _validator.TestValidate(dto);
+        result.ShouldHaveValidationErrorFor(x => x.Login);
+    }
+
+    [Fact]
+    public void Validate_PasswordWithWhitespaceOnly_ShouldHaveError()
+    {
+        var dto = new UserLoginDTO { Login = "test@example.com", Password = "   " };
+        var result = _validator.TestValidate(dto);
+        result.ShouldHaveValidationErrorFor(x => x.Password);
+    }

Optionally update LoginValidator to normalize input:

-        RuleFor(x => x.Login)
+        RuleFor(x => x.Login)
+                .Transform(x => x?.Trim())
                 .Matches(@"^(?!.*\.\.)[a-zA-Z0-9_%+-]+(?:\.[a-zA-Z0-9_%+-]+)*@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").WithMessage(Errors_Validation.EmailAddressFormat.FormatWith("Login"))
                 .NotEmpty().WithMessage(Errors_Validation.CannotBeEmpty.FormatWith("Login"));
-        RuleFor(x => x.Password)
+        RuleFor(x => x.Password)
+                .Transform(x => x?.Trim())
                 .NotEmpty().WithMessage(Errors_Validation.CannotBeEmpty.FormatWith("Password"));

8-170: Reduce duplication with [Theory]/InlineData

Multiple tests differ only by inputs. Consider parameterizing to speed up maintenance.

Streetcode/Streetcode.XUnitTest/BLL/MediatR/Auth/Login/LoginHandleTests.cs (1)

118-125: Align exception-path assertions with secure handler behavior

If the handler stops returning raw exception messages (recommended), assert a generic, localized message and still verify logging captured the exception.

Apply this diff:

-        result.IsFailed.Should().BeTrue();
-        result.Errors.Should().HaveCount(1);
-        result.Errors.First().Message.Should().Be(exception.Message);
-        _mockLogger.Verify(x => x.LogError(command, exception.Message), Times.Once);
+        result.IsFailed.Should().BeTrue();
+        result.Errors.Should().HaveCount(1);
+        var genericMsg = Errors_Auth.IncorrectEmailOrPassword.FormatWith("Login", "******");
+        result.Errors.First().Message.Should().Be(genericMsg);
+        _mockLogger.Verify(x => x.LogError(command, It.Is<string>(m => m.Contains(exception.Message))), Times.Once);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3271a88 and cbed357.

📒 Files selected for processing (12)
  • Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs (0 hunks)
  • Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginCommand.cs (1 hunks)
  • Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginHandler.cs (1 hunks)
  • Streetcode/Streetcode.BLL/Resources/Errors.Auth.Designer.cs (1 hunks)
  • Streetcode/Streetcode.BLL/Resources/Errors.Auth.resx (1 hunks)
  • Streetcode/Streetcode.BLL/Streetcode.BLL.csproj (3 hunks)
  • Streetcode/Streetcode.BLL/Validators/Auth/LoginCommandValidator.cs (1 hunks)
  • Streetcode/Streetcode.BLL/Validators/Auth/LoginValidator.cs (1 hunks)
  • Streetcode/Streetcode.WebApi/Controllers/Auth/AuthController.cs (2 hunks)
  • Streetcode/Streetcode.XUnitTest/BLL/MediatR/Auth/Login/LoginHandleTests.cs (1 hunks)
  • Streetcode/Streetcode.XUnitTest/BLL/Validators/Auth/LoginCommandValidatorTests.cs (1 hunks)
  • Streetcode/Streetcode.XUnitTest/BLL/Validators/Auth/LoginValidatorTests.cs (1 hunks)
💤 Files with no reviewable changes (1)
  • Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs
🧰 Additional context used
🧬 Code graph analysis (8)
Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginCommand.cs (2)
Streetcode/Streetcode.XUnitTest/BLL/MediatR/Auth/Login/LoginHandleTests.cs (2)
  • UserLoginDTO (140-147)
  • LoginResultDTO (127-138)
Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs (1)
  • UserLoginDTO (5-9)
Streetcode/Streetcode.XUnitTest/BLL/Validators/Auth/LoginCommandValidatorTests.cs (3)
Streetcode/Streetcode.BLL/Validators/Auth/LoginCommandValidator.cs (2)
  • LoginCommandValidator (6-12)
  • LoginCommandValidator (8-11)
Streetcode/Streetcode.BLL/Validators/Auth/LoginValidator.cs (2)
  • LoginValidator (8-19)
  • LoginValidator (10-18)
Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs (1)
  • UserLoginDTO (5-9)
Streetcode/Streetcode.BLL/Validators/Auth/LoginValidator.cs (3)
Streetcode/Streetcode.XUnitTest/BLL/MediatR/Auth/Login/LoginHandleTests.cs (1)
  • UserLoginDTO (140-147)
Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs (1)
  • UserLoginDTO (5-9)
Streetcode/Streetcode.BLL/Util/Extensions/ResourceExtensions.cs (1)
  • FormatWith (5-8)
Streetcode/Streetcode.WebApi/Controllers/Auth/AuthController.cs (2)
Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginHandler.cs (1)
  • Task (29-51)
Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs (1)
  • UserLoginDTO (5-9)
Streetcode/Streetcode.XUnitTest/BLL/MediatR/Auth/Login/LoginHandleTests.cs (5)
Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginHandler.cs (3)
  • LoginHandler (13-52)
  • LoginHandler (19-27)
  • Task (29-51)
Streetcode/Streetcode.BLL/Util/Extensions/ResourceExtensions.cs (1)
  • FormatWith (5-8)
Streetcode/Streetcode.BLL/Services/Logging/LoggerService.cs (1)
  • LogError (35-40)
Streetcode/Streetcode.BLL/DTO/Users/UserDTO.cs (1)
  • UserDTO (6-26)
Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs (1)
  • UserLoginDTO (5-9)
Streetcode/Streetcode.BLL/Validators/Auth/LoginCommandValidator.cs (1)
Streetcode/Streetcode.BLL/Validators/Auth/LoginValidator.cs (2)
  • LoginValidator (8-19)
  • LoginValidator (10-18)
Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginHandler.cs (3)
Streetcode/Streetcode.XUnitTest/BLL/MediatR/Auth/Login/LoginHandleTests.cs (2)
  • LoginResultDTO (127-138)
  • User (149-159)
Streetcode/Streetcode.BLL/Util/Extensions/ResourceExtensions.cs (1)
  • FormatWith (5-8)
Streetcode/Streetcode.BLL/Services/Logging/LoggerService.cs (1)
  • LogError (35-40)
Streetcode/Streetcode.XUnitTest/BLL/Validators/Auth/LoginValidatorTests.cs (2)
Streetcode/Streetcode.BLL/Validators/Auth/LoginValidator.cs (2)
  • LoginValidator (8-19)
  • LoginValidator (10-18)
Streetcode/Streetcode.BLL/DTO/Users/UserLoginDTO.cs (1)
  • UserLoginDTO (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (5)
Streetcode/Streetcode.BLL/Resources/Errors.Auth.Designer.cs (1)

39-47: Resource wrapper looks fine; confirm public exposure is intended

This auto-generated wrapper and base name alignment look correct. If these resources aren’t meant to be consumed outside BLL, consider switching the resx generator to internal to avoid leaking surface area.

Streetcode/Streetcode.BLL/Streetcode.BLL.csproj (2)

73-77: Resource wiring LGTM

Errors.Auth resource/designer linkage is correctly configured.


170-172: Res.resx generator choice

ResXFileCodeGenerator (internal) is fine for internal-only resources. No action required.

Streetcode/Streetcode.WebApi/Controllers/Auth/AuthController.cs (1)

14-17: Confirmed: BaseApiController routes by action (api/[controller]/[action]) — no ambiguous [HttpPost] matches

BaseApiController has [Route("api/[controller]/[action]")] (Streetcode/Streetcode.WebApi/Controllers/BaseApiController.cs:8-9), so [HttpPost] actions without explicit templates are disambiguated by action name; only duplicate action names would collide.

Streetcode/Streetcode.XUnitTest/BLL/MediatR/Auth/Login/LoginHandleTests.cs (1)

45-48: Mock return type consistency if service signature changes

If you decide to keep IJwtTokenService returning Result, update the mock accordingly.

Apply this diff (only if the service returns Result):

-        _mockJwtTokenService.Setup(i => i.GenerateTokenAsync(user.Id)).ReturnsAsync(loginUserRes);
+        _mockJwtTokenService.Setup(i => i.GenerateTokenAsync(user.Id)).ReturnsAsync(Result.Ok(loginUserRes));

Comment thread Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginHandler.cs
Comment thread Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginHandler.cs
Comment thread Streetcode/Streetcode.BLL/MediatR/Auth/Login/LoginHandler.cs
Comment thread Streetcode/Streetcode.BLL/Resources/Errors.Auth.resx
Comment thread Streetcode/Streetcode.WebApi/Controllers/Auth/AuthController.cs
@Markian-Grybok Markian-Grybok merged commit 4b20d0d into dev Sep 24, 2025
5 checks passed

@Skiper29 Skiper29 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Бачу вже пізно)

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.

[Task][User/Authentication] Implement login endpoint

4 participants