AuthController.java

package com.ecommerce.identity.interfaces.rest;

import com.ecommerce.identity.application.model.AuthTokens;
import com.ecommerce.identity.application.model.LoginContext;
import com.ecommerce.identity.application.model.UserProfile;
import com.ecommerce.identity.application.service.AuthenticationService;
import com.ecommerce.platform.common.api.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/identity/auth")
public class AuthController {

    private final AuthenticationService authenticationService;

    public AuthController(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    @PostMapping("/register")
    public ResponseEntity<ApiResponse<UserProfile>> register(@Valid @RequestBody RegisterRequest request) {
        UserProfile profile = authenticationService.register(
                request.email(),
                request.password(),
                request.displayName()
        );
        return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(profile));
    }

    @PostMapping("/login")
    public ApiResponse<AuthTokens> login(
            @Valid @RequestBody LoginRequest request,
            HttpServletRequest servletRequest) {
        LoginContext context = new LoginContext(
                servletRequest.getRemoteAddr(),
                truncate(servletRequest.getHeader("User-Agent"), 300)
        );
        return ApiResponse.success(authenticationService.login(
                request.email(),
                request.password(),
                context
        ));
    }

    @PostMapping("/refresh")
    public ApiResponse<AuthTokens> refresh(@Valid @RequestBody RefreshTokenRequest request) {
        return ApiResponse.success(authenticationService.refresh(request.refreshToken()));
    }

    @PostMapping("/logout")
    public ApiResponse<Void> logout(@Valid @RequestBody RefreshTokenRequest request) {
        authenticationService.logout(request.refreshToken());
        return ApiResponse.success(null);
    }

    private String truncate(String value, int maximumLength) {
        if (value == null || value.length() <= maximumLength) {
            return value;
        }
        return value.substring(0, maximumLength);
    }

    public record RegisterRequest(
            @NotBlank @Email @Size(max = 190) String email,
            @NotBlank
            @Size(min = 10, max = 64)
            @Pattern(
                    regexp = "^(?=.*[A-Za-z])(?=.*\\d).+$",
                    message = "password must contain at least one letter and one digit"
            )
            String password,
            @NotBlank @Size(min = 2, max = 50) String displayName
    ) {
    }

    public record LoginRequest(
            @NotBlank @Email @Size(max = 190) String email,
            @NotBlank @Size(max = 128) String password
    ) {
    }

    public record RefreshTokenRequest(
            @NotBlank @Size(max = 512) String refreshToken
    ) {
    }
}