diff --git a/pom.xml b/pom.xml index 6e61d8e..9b710b1 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,11 @@ spring-boot-starter-validation-test test + + org.mapstruct + mapstruct + 1.6.3 + org.springframework.boot spring-boot-starter-webmvc-test @@ -109,6 +114,11 @@ org.projectlombok lombok + + org.mapstruct + mapstruct-processor + 1.5.5.Final + diff --git a/src/main/java/dev/idinaldo/auth_api/adapters/in/controllers/IdentityController.java b/src/main/java/dev/idinaldo/auth_api/adapters/in/controllers/IdentityController.java index b86d396..c1798bd 100644 --- a/src/main/java/dev/idinaldo/auth_api/adapters/in/controllers/IdentityController.java +++ b/src/main/java/dev/idinaldo/auth_api/adapters/in/controllers/IdentityController.java @@ -1,10 +1,11 @@ package dev.idinaldo.auth_api.adapters.in.controllers; -import dev.idinaldo.auth_api.adapters.in.dtos.ClientIdentityRegisterDTO; +import dev.idinaldo.auth_api.adapters.in.dtos.IdentityRequestDTO; import dev.idinaldo.auth_api.application.services.IdentityService; import dev.idinaldo.auth_api.domain.models.Identity; -import dev.idinaldo.auth_api.infrastructure.mappers.IdentityMapper; +import dev.idinaldo.auth_api.infrastructure.mappers.IdentityMapperFacade; import jakarta.validation.Valid; +import org.apache.coyote.BadRequestException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -20,17 +21,22 @@ public class IdentityController { private final IdentityService identityService; - private final IdentityMapper identityMapper; + private final IdentityMapperFacade identityMapper; - public IdentityController(IdentityService identityService, IdentityMapper identityMapper) { + public IdentityController(IdentityService identityService, IdentityMapperFacade identityMapper) { this.identityService = identityService; this.identityMapper = identityMapper; } @PostMapping("/register") - public ResponseEntity registerClient(@RequestBody @Valid ClientIdentityRegisterDTO identityRegisterDTO) { - Identity identity = this.identityMapper.registerDtoToDomain(identityRegisterDTO); + public ResponseEntity registerClient(@RequestBody @Valid IdentityRequestDTO identityRegisterDTO) { + Identity identity = this.identityMapper.requestDtoToDomain(identityRegisterDTO); UUID id = this.identityService.registerClient(identity); return ResponseEntity.status(HttpStatus.CREATED).body("Credentials registered successfully with ID: " + id); } + + @PostMapping("/signin") + public ResponseEntity signIn(@RequestBody @Valid IdentityRequestDTO identityRequestDTO) throws BadRequestException { + return ResponseEntity.ok(this.identityService.signIn(identityRequestDTO)); + } } diff --git a/src/main/java/dev/idinaldo/auth_api/adapters/in/dtos/ClientIdentityRegisterDTO.java b/src/main/java/dev/idinaldo/auth_api/adapters/in/dtos/ClientIdentityRegisterDTO.java deleted file mode 100644 index 98d33fa..0000000 --- a/src/main/java/dev/idinaldo/auth_api/adapters/in/dtos/ClientIdentityRegisterDTO.java +++ /dev/null @@ -1,6 +0,0 @@ -package dev.idinaldo.auth_api.adapters.in.dtos; - -import dev.idinaldo.auth_api.domain.valueObject.Username; - -public record ClientIdentityRegisterDTO(Username username, String password) { -} diff --git a/src/main/java/dev/idinaldo/auth_api/adapters/in/dtos/IdentityRequestDTO.java b/src/main/java/dev/idinaldo/auth_api/adapters/in/dtos/IdentityRequestDTO.java new file mode 100644 index 0000000..2ad87df --- /dev/null +++ b/src/main/java/dev/idinaldo/auth_api/adapters/in/dtos/IdentityRequestDTO.java @@ -0,0 +1,25 @@ +package dev.idinaldo.auth_api.adapters.in.dtos; + + +import dev.idinaldo.auth_api.infrastructure.exceptions.InvalidUsernameException; +import dev.idinaldo.auth_api.infrastructure.exceptions.WeakPasswordException; + +public record IdentityRequestDTO(String username, String password) { + + public IdentityRequestDTO(String username, String password) { + if (this.isUsernameValid(username)) { + if (this.isPasswordValid(password)) { + this.username = username; + this.password = password; + } else throw new WeakPasswordException(); + } else throw new InvalidUsernameException(); + } + + public boolean isUsernameValid(String username) { + return true; + } + + public boolean isPasswordValid(String password) { + return true; + } +} diff --git a/src/main/java/dev/idinaldo/auth_api/adapters/out/JwtGeneratorImpl.java b/src/main/java/dev/idinaldo/auth_api/adapters/out/JwtGeneratorImpl.java new file mode 100644 index 0000000..f358dc4 --- /dev/null +++ b/src/main/java/dev/idinaldo/auth_api/adapters/out/JwtGeneratorImpl.java @@ -0,0 +1,14 @@ +package dev.idinaldo.auth_api.adapters.out; + +import dev.idinaldo.auth_api.domain.models.Identity; +import dev.idinaldo.auth_api.ports.JwtGenerator; +import org.springframework.stereotype.Component; + +@Component +public class JwtGeneratorImpl implements JwtGenerator { + + @Override + public String generateToken(Identity identity) { + return "token"; + } +} diff --git a/src/main/java/dev/idinaldo/auth_api/adapters/out/persistence/IdentityJpaRepository.java b/src/main/java/dev/idinaldo/auth_api/adapters/out/persistence/IdentityJpaRepository.java index 0318ece..0bd1ab5 100644 --- a/src/main/java/dev/idinaldo/auth_api/adapters/out/persistence/IdentityJpaRepository.java +++ b/src/main/java/dev/idinaldo/auth_api/adapters/out/persistence/IdentityJpaRepository.java @@ -1,11 +1,14 @@ package dev.idinaldo.auth_api.adapters.out.persistence; +import dev.idinaldo.auth_api.domain.models.Identity; import dev.idinaldo.auth_api.infrastructure.entities.JpaIdentity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; @Repository public interface IdentityJpaRepository extends JpaRepository { + Optional findByUsername(String username); } diff --git a/src/main/java/dev/idinaldo/auth_api/adapters/out/persistence/IdentityRepositoryImpl.java b/src/main/java/dev/idinaldo/auth_api/adapters/out/persistence/IdentityRepositoryImpl.java index 0e1038a..1a4e316 100644 --- a/src/main/java/dev/idinaldo/auth_api/adapters/out/persistence/IdentityRepositoryImpl.java +++ b/src/main/java/dev/idinaldo/auth_api/adapters/out/persistence/IdentityRepositoryImpl.java @@ -2,19 +2,22 @@ import dev.idinaldo.auth_api.domain.models.Identity; import dev.idinaldo.auth_api.infrastructure.entities.JpaIdentity; -import dev.idinaldo.auth_api.infrastructure.mappers.IdentityMapper; +import dev.idinaldo.auth_api.infrastructure.mappers.IdentityMapperFacade; import dev.idinaldo.auth_api.ports.IdentityRepository; +import org.apache.coyote.BadRequestException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; +import java.util.Optional; import java.util.UUID; @Component public class IdentityRepositoryImpl implements IdentityRepository { private final IdentityJpaRepository identityJpaRepository; - private final IdentityMapper identityMapper; + private final IdentityMapperFacade identityMapper; - public IdentityRepositoryImpl(IdentityJpaRepository identityJpaRepository, IdentityMapper identityMapper) { + public IdentityRepositoryImpl(IdentityJpaRepository identityJpaRepository, IdentityMapperFacade identityMapper) { this.identityJpaRepository = identityJpaRepository; this.identityMapper = identityMapper; } @@ -24,4 +27,10 @@ public UUID save(Identity identity) { JpaIdentity jpaIdentity = this.identityMapper.domainToEntity(identity); return this.identityJpaRepository.save(jpaIdentity).getId(); } + + @Override + public Identity findByUsername(String username) throws BadRequestException { + JpaIdentity jpaIdentity = this.identityJpaRepository.findByUsername(username).orElseThrow(BadRequestException::new); + return this.identityMapper.entityToDomain(jpaIdentity); + } } diff --git a/src/main/java/dev/idinaldo/auth_api/application/services/IdentityService.java b/src/main/java/dev/idinaldo/auth_api/application/services/IdentityService.java index 437ee7d..d770dc3 100644 --- a/src/main/java/dev/idinaldo/auth_api/application/services/IdentityService.java +++ b/src/main/java/dev/idinaldo/auth_api/application/services/IdentityService.java @@ -1,24 +1,44 @@ package dev.idinaldo.auth_api.application.services; -import dev.idinaldo.auth_api.adapters.in.dtos.ClientIdentityRegisterDTO; +import dev.idinaldo.auth_api.adapters.in.dtos.IdentityRequestDTO; import dev.idinaldo.auth_api.application.usecases.ClientRegisterUseCase; +import dev.idinaldo.auth_api.application.usecases.SignInUseCase; import dev.idinaldo.auth_api.domain.models.Identity; import dev.idinaldo.auth_api.ports.IdentityRepository; +import dev.idinaldo.auth_api.ports.JwtGenerator; +import org.apache.coyote.BadRequestException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.UUID; @Service -public class IdentityService implements ClientRegisterUseCase { +public class IdentityService implements ClientRegisterUseCase, SignInUseCase { private final IdentityRepository identityRepository; + private final JwtGenerator jwtGenerator; + private final PasswordEncoder passwordEncoder; + private final String SIGN_IN_ERROR_MESSAGE = "Please verify your request."; + private final Logger logger = LoggerFactory.getLogger(IdentityService.class); - public IdentityService(IdentityRepository identityRepository) { + public IdentityService(IdentityRepository identityRepository, JwtGenerator jwtGenerator, PasswordEncoder passwordEncoder) { this.identityRepository = identityRepository; + this.jwtGenerator = jwtGenerator; + this.passwordEncoder = passwordEncoder; } @Override public UUID registerClient(Identity identity) { return this.identityRepository.save(identity); } + + @Override + public String signIn(IdentityRequestDTO identityRequestDTO) throws BadRequestException { + Identity persistedIdentity = identityRepository.findByUsername(identityRequestDTO.username()); + if (passwordEncoder.matches(identityRequestDTO.password(), persistedIdentity.getPasswordHash())) { + return this.jwtGenerator.generateToken(persistedIdentity); + } else throw new BadRequestException(SIGN_IN_ERROR_MESSAGE); + } } diff --git a/src/main/java/dev/idinaldo/auth_api/application/usecases/ClientRegisterUseCase.java b/src/main/java/dev/idinaldo/auth_api/application/usecases/ClientRegisterUseCase.java index c6d3a09..c926359 100644 --- a/src/main/java/dev/idinaldo/auth_api/application/usecases/ClientRegisterUseCase.java +++ b/src/main/java/dev/idinaldo/auth_api/application/usecases/ClientRegisterUseCase.java @@ -1,6 +1,5 @@ package dev.idinaldo.auth_api.application.usecases; -import dev.idinaldo.auth_api.adapters.in.dtos.ClientIdentityRegisterDTO; import dev.idinaldo.auth_api.domain.models.Identity; import java.util.UUID; diff --git a/src/main/java/dev/idinaldo/auth_api/application/usecases/SignInUseCase.java b/src/main/java/dev/idinaldo/auth_api/application/usecases/SignInUseCase.java new file mode 100644 index 0000000..3dd774a --- /dev/null +++ b/src/main/java/dev/idinaldo/auth_api/application/usecases/SignInUseCase.java @@ -0,0 +1,8 @@ +package dev.idinaldo.auth_api.application.usecases; + +import dev.idinaldo.auth_api.adapters.in.dtos.IdentityRequestDTO; +import org.apache.coyote.BadRequestException; + +public interface SignInUseCase { + String signIn(IdentityRequestDTO identityRequestDTO) throws BadRequestException; +} diff --git a/src/main/java/dev/idinaldo/auth_api/domain/models/Identity.java b/src/main/java/dev/idinaldo/auth_api/domain/models/Identity.java index 6d16389..3960f7b 100644 --- a/src/main/java/dev/idinaldo/auth_api/domain/models/Identity.java +++ b/src/main/java/dev/idinaldo/auth_api/domain/models/Identity.java @@ -1,22 +1,20 @@ package dev.idinaldo.auth_api.domain.models; -import dev.idinaldo.auth_api.domain.valueObject.Username; - import java.util.UUID; public class Identity { private UUID id; - private Username username; + private String username; private String passwordHash; - public Identity(UUID id, Username username, String passwordHash) { + public Identity(UUID id, String username, String passwordHash) { this.id = id; this.username = username; this.passwordHash = passwordHash; } - public Identity(Username username, String passwordHash) { + public Identity(String username, String passwordHash) { this.username = username; this.passwordHash = passwordHash; } @@ -32,11 +30,11 @@ public void setId(UUID id) { this.id = id; } - public Username getUsername() { + public String getUsername() { return username; } - public void setUsername(Username username) { + public void setUsername(String username) { this.username = username; } diff --git a/src/main/java/dev/idinaldo/auth_api/domain/valueObject/Username.java b/src/main/java/dev/idinaldo/auth_api/domain/valueObject/Username.java index ac5138f..6708426 100644 --- a/src/main/java/dev/idinaldo/auth_api/domain/valueObject/Username.java +++ b/src/main/java/dev/idinaldo/auth_api/domain/valueObject/Username.java @@ -4,25 +4,24 @@ import com.fasterxml.jackson.annotation.JsonValue; import dev.idinaldo.auth_api.infrastructure.exceptions.InvalidUsernameException; -public record Username(String username) { +public record Username(String value) { + @JsonCreator - public Username(String username) { - if (this.isValid(username)) { - this.username = username; + public Username(String value) { + if (this.isValid(value)) { + this.value = value; } else { throw new InvalidUsernameException(); } } private boolean isValid(String value) { - System.out.println("[" + value + "]"); - System.out.println(value.matches("\\w{6,16}")); return value.matches("\\w{6,16}"); } @JsonValue public String getValue() { - return this.username; + return this.value; } } diff --git a/src/main/java/dev/idinaldo/auth_api/infrastructure/exceptions/WeakPasswordException.java b/src/main/java/dev/idinaldo/auth_api/infrastructure/exceptions/WeakPasswordException.java new file mode 100644 index 0000000..b885fbd --- /dev/null +++ b/src/main/java/dev/idinaldo/auth_api/infrastructure/exceptions/WeakPasswordException.java @@ -0,0 +1,12 @@ +package dev.idinaldo.auth_api.infrastructure.exceptions; + +public class WeakPasswordException extends RuntimeException { + + public WeakPasswordException() { + super("Please make sure your password is at least 6-characters long and contains letters, numbers and special symbols."); + } + + public WeakPasswordException(String message) { + super(message); + } +} diff --git a/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/IdentityMapper.java b/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/IdentityMapper.java index cba87c0..db1fec7 100644 --- a/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/IdentityMapper.java +++ b/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/IdentityMapper.java @@ -1,8 +1,7 @@ package dev.idinaldo.auth_api.infrastructure.mappers; -import dev.idinaldo.auth_api.adapters.in.dtos.ClientIdentityRegisterDTO; +import dev.idinaldo.auth_api.adapters.in.dtos.IdentityRequestDTO; import dev.idinaldo.auth_api.domain.models.Identity; -import dev.idinaldo.auth_api.infrastructure.entities.JpaIdentity; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -15,21 +14,11 @@ public IdentityMapper(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } - public JpaIdentity domainToEntity(Identity identity) { - JpaIdentity jpaIdentity = new JpaIdentity(); - - jpaIdentity.setUsername(identity.getUsername().getValue()); - jpaIdentity.setPasswordHash(identity.getPasswordHash()); - - return jpaIdentity; + public Identity requestDtoToDomain(IdentityRequestDTO identityRegisterDTO) { + return new Identity( + identityRegisterDTO.username(), + this.passwordEncoder.encode(identityRegisterDTO.password()) + ); } - public Identity registerDtoToDomain(ClientIdentityRegisterDTO identityRegisterDTO) { - Identity identity = new Identity(); - - identity.setUsername(identityRegisterDTO.username()); - identity.setPasswordHash(passwordEncoder.encode(identityRegisterDTO.password())); - - return identity; - } } diff --git a/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/IdentityMapperFacade.java b/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/IdentityMapperFacade.java new file mode 100644 index 0000000..eb9474a --- /dev/null +++ b/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/IdentityMapperFacade.java @@ -0,0 +1,31 @@ +package dev.idinaldo.auth_api.infrastructure.mappers; + +import dev.idinaldo.auth_api.adapters.in.dtos.IdentityRequestDTO; +import dev.idinaldo.auth_api.domain.models.Identity; +import dev.idinaldo.auth_api.infrastructure.entities.JpaIdentity; +import org.springframework.stereotype.Component; + +@Component +public class IdentityMapperFacade { + + private final SimpleIdentityMapper simpleMapper; + private final IdentityMapper complexMapper; + + public IdentityMapperFacade(SimpleIdentityMapper simpleMapper, IdentityMapper complexMapper) { + this.simpleMapper = simpleMapper; + this.complexMapper = complexMapper; + } + + public Identity requestDtoToDomain(IdentityRequestDTO requestDTO) { + return this.complexMapper.requestDtoToDomain(requestDTO); + } + + public Identity entityToDomain(JpaIdentity jpaIdentity) { + return this.simpleMapper.entityToDomain(jpaIdentity); + } + + public JpaIdentity domainToEntity(Identity identity) { + return this.simpleMapper.domainToEntity(identity); + } + +} diff --git a/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/SimpleIdentityMapper.java b/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/SimpleIdentityMapper.java new file mode 100644 index 0000000..1232c20 --- /dev/null +++ b/src/main/java/dev/idinaldo/auth_api/infrastructure/mappers/SimpleIdentityMapper.java @@ -0,0 +1,12 @@ +package dev.idinaldo.auth_api.infrastructure.mappers; + +import dev.idinaldo.auth_api.domain.models.Identity; +import dev.idinaldo.auth_api.infrastructure.entities.JpaIdentity; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface SimpleIdentityMapper { + + Identity entityToDomain(JpaIdentity jpaIdentity); + JpaIdentity domainToEntity(Identity identity); +} diff --git a/src/main/java/dev/idinaldo/auth_api/ports/IdentityRepository.java b/src/main/java/dev/idinaldo/auth_api/ports/IdentityRepository.java index 1b0c375..6552cdd 100644 --- a/src/main/java/dev/idinaldo/auth_api/ports/IdentityRepository.java +++ b/src/main/java/dev/idinaldo/auth_api/ports/IdentityRepository.java @@ -1,11 +1,14 @@ package dev.idinaldo.auth_api.ports; import dev.idinaldo.auth_api.domain.models.Identity; +import org.apache.coyote.BadRequestException; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; @Repository public interface IdentityRepository { UUID save(Identity identity); + Identity findByUsername(String username) throws BadRequestException; } diff --git a/src/main/java/dev/idinaldo/auth_api/ports/JwtGenerator.java b/src/main/java/dev/idinaldo/auth_api/ports/JwtGenerator.java new file mode 100644 index 0000000..74bc28f --- /dev/null +++ b/src/main/java/dev/idinaldo/auth_api/ports/JwtGenerator.java @@ -0,0 +1,9 @@ +package dev.idinaldo.auth_api.ports; + +import dev.idinaldo.auth_api.domain.models.Identity; +import org.springframework.stereotype.Component; + +@Component +public interface JwtGenerator { + String generateToken(Identity identity); +}