diff --git a/sportshub-application/src/main/java/org/sportshub/application/security/JwtService.java b/sportshub-application/src/main/java/org/sportshub/application/security/JwtService.java index bd5b178..08aedb3 100644 --- a/sportshub-application/src/main/java/org/sportshub/application/security/JwtService.java +++ b/sportshub-application/src/main/java/org/sportshub/application/security/JwtService.java @@ -17,8 +17,8 @@ public class JwtService { private final int tokenExpirationDelayInMinutes; public JwtService( - @Value("${application.security.secretKey}") String secretKey, - @Value("${application.security.tokenExpirationDelayInMinutes}") int tokenExpirationDelayInMinutes + @Value("${application.security.jwt.secretKey}") String secretKey, + @Value("${application.security.jwt.expirationDelayInMinutes}") int tokenExpirationDelayInMinutes ) { algorithm = Algorithm.HMAC512(secretKey); this.tokenExpirationDelayInMinutes = tokenExpirationDelayInMinutes; diff --git a/sportshub-application/src/main/java/org/sportshub/application/user/UserUseCases.java b/sportshub-application/src/main/java/org/sportshub/application/user/UserUseCases.java index 1cc41ac..e62a21f 100644 --- a/sportshub-application/src/main/java/org/sportshub/application/user/UserUseCases.java +++ b/sportshub-application/src/main/java/org/sportshub/application/user/UserUseCases.java @@ -1,5 +1,6 @@ package org.sportshub.application.user; +import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -8,29 +9,41 @@ import org.sportshub.application.security.AuthenticationFacade; import org.sportshub.application.security.JwtService; import org.sportshub.application.security.annotation.AllowedToAdmins; import org.sportshub.domain.exception.LoginFailureException; +import org.sportshub.domain.exception.RefreshTokenDoesNotExistException; +import org.sportshub.domain.exception.RefreshTokenExpiredException; +import org.sportshub.domain.exception.UserDoesNotExistException; +import org.sportshub.domain.user.model.RefreshToken; import org.sportshub.domain.user.model.User; +import org.sportshub.domain.user.model.UserAuthenticationData; import org.sportshub.domain.user.port.UserPort; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service public class UserUseCases { + private static final String TOKEN_TYPE = "Bearer"; + private final PasswordEncoder passwordEncoder; private final JwtService jwtService; private final UserPort userPort; private final AuthenticationFacade authenticationFacade; + private final int refreshTokenExpirationDelayInDays; public UserUseCases( AuthenticationFacade authenticationFacade, JwtService jwtService, PasswordEncoder passwordEncoder, - UserPort userPort + UserPort userPort, + @Value("${application.security.refreshToken.expirationDelayInDays}") + int refreshTokenExpirationDelayInDays ) { this.passwordEncoder = passwordEncoder; this.jwtService = jwtService; this.userPort = userPort; this.authenticationFacade = authenticationFacade; + this.refreshTokenExpirationDelayInDays = refreshTokenExpirationDelayInDays; } public Optional findById(UUID userId) { @@ -42,11 +55,26 @@ public class UserUseCases { return userPort.findAll(); } - public String authenticate(final UUID id, final String password) { - return userPort.findById(id) - .filter(user -> passwordEncoder.matches(password, user.password())) - .map(jwtService::createJwt) + public UserAuthenticationData authenticate(UUID userId, String password) { + User user = userPort.findById(userId) .orElseThrow(LoginFailureException::new); + + if (!passwordEncoder.matches(password, user.password())) { + throw new LoginFailureException(); + } + + return generateAuthenticationData(user); + } + + public UserAuthenticationData authenticate(UUID refreshTokenValue) { + RefreshToken refreshToken = userPort.findRefreshTokenById(refreshTokenValue) + .filter(RefreshToken::isNotExpired) + .orElseThrow(() -> new RefreshTokenDoesNotExistException(refreshTokenValue)); + + User user = userPort.findById(refreshToken.userId()) + .orElseThrow(() -> new UserDoesNotExistException(refreshToken.userId())); + + return generateAuthenticationData(user); } public Optional getAuthenticatedUser() { @@ -57,4 +85,25 @@ public class UserUseCases { .map(UUID::fromString) .flatMap(userPort::findById); } + + private UserAuthenticationData generateAuthenticationData(final User user) { + String accessToken = jwtService.createJwt(user); + + RefreshToken newRefreshToken = createNewRefreshToken(user); + + return new UserAuthenticationData( + TOKEN_TYPE, + accessToken, + newRefreshToken + ); + } + + private RefreshToken createNewRefreshToken(User user) { + RefreshToken refreshToken = new RefreshToken( + user.id(), + ZonedDateTime.now().plusDays(refreshTokenExpirationDelayInDays) + ); + userPort.save(refreshToken); + return refreshToken; + } } diff --git a/sportshub-domain/src/main/java/org/sportshub/domain/exception/RefreshTokenDoesNotExistException.java b/sportshub-domain/src/main/java/org/sportshub/domain/exception/RefreshTokenDoesNotExistException.java new file mode 100644 index 0000000..b902460 --- /dev/null +++ b/sportshub-domain/src/main/java/org/sportshub/domain/exception/RefreshTokenDoesNotExistException.java @@ -0,0 +1,9 @@ +package org.sportshub.domain.exception; + +import java.util.UUID; + +public class RefreshTokenDoesNotExistException extends FunctionnalException { + public RefreshTokenDoesNotExistException(UUID refreshTokenValue) { + super(String.format("Refresh token \"%s\" does not exist.", refreshTokenValue)); + } +} diff --git a/sportshub-domain/src/main/java/org/sportshub/domain/exception/RefreshTokenExpiredException.java b/sportshub-domain/src/main/java/org/sportshub/domain/exception/RefreshTokenExpiredException.java new file mode 100644 index 0000000..b75ac53 --- /dev/null +++ b/sportshub-domain/src/main/java/org/sportshub/domain/exception/RefreshTokenExpiredException.java @@ -0,0 +1,9 @@ +package org.sportshub.domain.exception; + +import java.util.UUID; + +public class RefreshTokenExpiredException extends FunctionnalException { + public RefreshTokenExpiredException(UUID refreshTokenValue) { + super(String.format("Refresh token \"%s\" is expired.", refreshTokenValue)); + } +} diff --git a/sportshub-domain/src/main/java/org/sportshub/domain/exception/UserDoesNotExistException.java b/sportshub-domain/src/main/java/org/sportshub/domain/exception/UserDoesNotExistException.java new file mode 100644 index 0000000..73b7fbb --- /dev/null +++ b/sportshub-domain/src/main/java/org/sportshub/domain/exception/UserDoesNotExistException.java @@ -0,0 +1,9 @@ +package org.sportshub.domain.exception; + +import java.util.UUID; + +public class UserDoesNotExistException extends FunctionnalException { + public UserDoesNotExistException(UUID userId) { + super(String.format("User \"%s\" does not exist.", userId)); + } +} diff --git a/sportshub-domain/src/main/java/org/sportshub/domain/user/model/RefreshToken.java b/sportshub-domain/src/main/java/org/sportshub/domain/user/model/RefreshToken.java new file mode 100644 index 0000000..24c2999 --- /dev/null +++ b/sportshub-domain/src/main/java/org/sportshub/domain/user/model/RefreshToken.java @@ -0,0 +1,22 @@ +package org.sportshub.domain.user.model; + +import java.time.ZonedDateTime; +import java.util.UUID; + +public record RefreshToken( + UUID userId, + UUID value, + ZonedDateTime expirationDate +) { + public RefreshToken(UUID userId, ZonedDateTime exporationDate) { + this(userId, UUID.randomUUID(), exporationDate); + } + + public boolean isExpired() { + return ZonedDateTime.now().isAfter(expirationDate); + } + + public boolean isNotExpired() { + return !isExpired(); + } +} diff --git a/sportshub-domain/src/main/java/org/sportshub/domain/user/model/UserAuthenticationData.java b/sportshub-domain/src/main/java/org/sportshub/domain/user/model/UserAuthenticationData.java new file mode 100644 index 0000000..cd54dcf --- /dev/null +++ b/sportshub-domain/src/main/java/org/sportshub/domain/user/model/UserAuthenticationData.java @@ -0,0 +1,8 @@ +package org.sportshub.domain.user.model; + +public record UserAuthenticationData( + String tokenType, + String accessToken, + RefreshToken refreshToken +) { +} diff --git a/sportshub-domain/src/main/java/org/sportshub/domain/user/port/UserPort.java b/sportshub-domain/src/main/java/org/sportshub/domain/user/port/UserPort.java index 8f8f84f..3a7a869 100644 --- a/sportshub-domain/src/main/java/org/sportshub/domain/user/port/UserPort.java +++ b/sportshub-domain/src/main/java/org/sportshub/domain/user/port/UserPort.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.sportshub.domain.user.model.RefreshToken; import org.sportshub.domain.user.model.User; public interface UserPort { @@ -12,4 +13,12 @@ public interface UserPort { List findAll(); void save(User user); + + boolean existsById(UUID userId); + + Optional findRefreshTokenByUserId(UUID userId); + + Optional findRefreshTokenById(UUID refreshTokenId); + + void save(RefreshToken refreshToken); } diff --git a/sportshub-exposition/src/main/java/org/sportshub/exposition/configuration/GlobalControllerExceptionHandler.java b/sportshub-exposition/src/main/java/org/sportshub/exposition/configuration/GlobalControllerExceptionHandler.java index f2e33e4..8ef830b 100644 --- a/sportshub-exposition/src/main/java/org/sportshub/exposition/configuration/GlobalControllerExceptionHandler.java +++ b/sportshub-exposition/src/main/java/org/sportshub/exposition/configuration/GlobalControllerExceptionHandler.java @@ -1,7 +1,12 @@ package org.sportshub.exposition.configuration; import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; import org.sportshub.domain.exception.LoginFailureException; +import org.sportshub.domain.exception.RefreshTokenDoesNotExistException; +import org.sportshub.domain.exception.RefreshTokenExpiredException; +import org.sportshub.domain.exception.UserDoesNotExistException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; @@ -14,4 +19,22 @@ public class GlobalControllerExceptionHandler { public void handleLoginFailureException() { // Do nothing. } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(UserDoesNotExistException.class) + public void handleUserDoesNotExistException() { + // Do nothing. + } + + @ResponseStatus(NOT_FOUND) + @ExceptionHandler(RefreshTokenDoesNotExistException.class) + public void handleRefreshTokenDoesNotExistException() { + // Do nothing. + } + + @ResponseStatus(UNAUTHORIZED) + @ExceptionHandler(RefreshTokenExpiredException.class) + public void handleRefreshTokenExpiredException() { + // Do nothing. + } } diff --git a/sportshub-exposition/src/main/java/org/sportshub/exposition/configuration/SecurityConfiguration.java b/sportshub-exposition/src/main/java/org/sportshub/exposition/configuration/SecurityConfiguration.java index b53ebc4..28ac4fd 100644 --- a/sportshub-exposition/src/main/java/org/sportshub/exposition/configuration/SecurityConfiguration.java +++ b/sportshub-exposition/src/main/java/org/sportshub/exposition/configuration/SecurityConfiguration.java @@ -48,7 +48,8 @@ public class SecurityConfiguration { ).permitAll() .requestMatchers( POST, - "/api/users/login" + "/api/users/login", + "/api/users/refresh-token" ).permitAll() .requestMatchers(OPTIONS).permitAll() .anyRequest().authenticated() diff --git a/sportshub-exposition/src/main/java/org/sportshub/exposition/user/UserController.java b/sportshub-exposition/src/main/java/org/sportshub/exposition/user/UserController.java index f17cf22..5538c52 100644 --- a/sportshub-exposition/src/main/java/org/sportshub/exposition/user/UserController.java +++ b/sportshub-exposition/src/main/java/org/sportshub/exposition/user/UserController.java @@ -4,7 +4,10 @@ import java.util.List; import org.sportshub.application.user.UserUseCases; import org.sportshub.domain.user.model.User; +import org.sportshub.domain.user.model.UserAuthenticationData; import org.sportshub.exposition.user.model.LoginRequest; +import org.sportshub.exposition.user.model.LoginResponse; +import org.sportshub.exposition.user.model.RefreshTokenRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -21,12 +24,19 @@ public class UserController { } @PostMapping("/login") - public String login(@RequestBody LoginRequest request) { - return userUseCases.authenticate(request.id(), request.password()); + public LoginResponse login(@RequestBody LoginRequest request) { + UserAuthenticationData userAuthenticationData = userUseCases.authenticate(request.id(), request.password()); + return new LoginResponse(userAuthenticationData); } @GetMapping public List findAll() { return userUseCases.findAll(); } + + @PostMapping("/refresh-token") + public LoginResponse refreshToken(@RequestBody RefreshTokenRequest request) { + UserAuthenticationData userAuthenticationData = userUseCases.authenticate(request.refreshTokenValue()); + return new LoginResponse(userAuthenticationData); + } } diff --git a/sportshub-exposition/src/main/java/org/sportshub/exposition/user/model/LoginResponse.java b/sportshub-exposition/src/main/java/org/sportshub/exposition/user/model/LoginResponse.java new file mode 100644 index 0000000..6dc17f8 --- /dev/null +++ b/sportshub-exposition/src/main/java/org/sportshub/exposition/user/model/LoginResponse.java @@ -0,0 +1,17 @@ +package org.sportshub.exposition.user.model; + +import org.sportshub.domain.user.model.UserAuthenticationData; + +public record LoginResponse( + String tokenType, + String accessToken, + String refreshToken +) { + public LoginResponse(UserAuthenticationData userAuthenticationData) { + this( + userAuthenticationData.tokenType(), + userAuthenticationData.accessToken(), + userAuthenticationData.refreshToken().value().toString() + ); + } +} diff --git a/sportshub-exposition/src/main/java/org/sportshub/exposition/user/model/RefreshTokenRequest.java b/sportshub-exposition/src/main/java/org/sportshub/exposition/user/model/RefreshTokenRequest.java new file mode 100644 index 0000000..6601372 --- /dev/null +++ b/sportshub-exposition/src/main/java/org/sportshub/exposition/user/model/RefreshTokenRequest.java @@ -0,0 +1,8 @@ +package org.sportshub.exposition.user.model; + +import java.util.UUID; + +public record RefreshTokenRequest( + UUID refreshTokenValue +) { +} diff --git a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/adapter/UserInMemoryAdapter.java b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/adapter/UserInMemoryAdapter.java deleted file mode 100644 index f64ad21..0000000 --- a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/adapter/UserInMemoryAdapter.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.sportshub.infrastructure.user.adapter; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.sportshub.domain.user.model.UserRole.ADMIN; -import static org.sportshub.domain.user.model.UserRole.STANDARD; -import org.sportshub.domain.user.model.User; -import org.sportshub.domain.user.port.UserPort; -import org.springframework.stereotype.Component; - -//@Component -public class UserInMemoryAdapter implements UserPort { - private static final List users = List.of( - new User( - UUID.fromString("c1a0805f-c618-47dc-bae7-bee70503644e"), - "$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2", - List.of(STANDARD) - ), - new User( - UUID.fromString("4eff194d-dd8e-463e-974f-034bfd509f84"), - "$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2", - List.of(STANDARD) - ), - new User( - UUID.fromString("c78d7d7c-0386-415d-86dc-98a470591e07"), - "$2a$10$WPuLOKpvaQnMotNo5ijPwegBPwmMF1C04XkTNCBpeBFo4r2YJWy.2", - List.of(STANDARD, ADMIN) - ) - ); - - @Override - public Optional findById(final UUID userId) { - return users.stream() - .filter(user -> userId.equals(user.id())) - .findFirst(); - } - - @Override - public List findAll() { - return users; - } - - @Override - public void save(final User user) { - // Do nothing. - } -} diff --git a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/adapter/UserJpaAdapter.java b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/adapter/UserJpaAdapter.java index 93ed200..3ef1af9 100644 --- a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/adapter/UserJpaAdapter.java +++ b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/adapter/UserJpaAdapter.java @@ -4,43 +4,68 @@ import java.util.List; import java.util.Optional; import java.util.UUID; +import org.sportshub.domain.user.model.RefreshToken; import org.sportshub.domain.user.model.User; import org.sportshub.domain.user.port.UserPort; -import org.sportshub.infrastructure.user.mapper.UserMapper; +import org.sportshub.infrastructure.user.model.RefreshTokenEntity; import org.sportshub.infrastructure.user.model.UserEntity; +import org.sportshub.infrastructure.user.repository.RefreshTokenJpaRepository; import org.sportshub.infrastructure.user.repository.UserJpaRepository; import org.springframework.stereotype.Component; @Component public class UserJpaAdapter implements UserPort { + private final RefreshTokenJpaRepository refreshTokenJpaRepository; private final UserJpaRepository userJpaRepository; - private final UserMapper userMapper; public UserJpaAdapter( - UserJpaRepository userJpaRepository, - UserMapper userMapper + RefreshTokenJpaRepository refreshTokenJpaRepository, + UserJpaRepository userJpaRepository ) { + this.refreshTokenJpaRepository = refreshTokenJpaRepository; this.userJpaRepository = userJpaRepository; - this.userMapper = userMapper; } @Override - public Optional findById(final UUID userId) { + public Optional findById(UUID userId) { return userJpaRepository.findById(userId) - .map(userMapper::mapFrom); + .map(UserEntity::toUser); } @Override public List findAll() { return userJpaRepository.findAll() .stream() - .map(userMapper::mapFrom) + .map(UserEntity::toUser) .toList(); } @Override public void save(User user) { - UserEntity userEntity = userMapper.mapTo(user); + UserEntity userEntity = new UserEntity(user); userJpaRepository.save(userEntity); } + + @Override + public boolean existsById(final UUID userId) { + return userJpaRepository.existsById(userId); + } + + @Override + public Optional findRefreshTokenByUserId(UUID userId) { + return refreshTokenJpaRepository.findByUserId(userId) + .map(RefreshTokenEntity::toRefreshToken); + } + + @Override + public Optional findRefreshTokenById(UUID refreshTokenId) { + return refreshTokenJpaRepository.findByValue(refreshTokenId) + .map(RefreshTokenEntity::toRefreshToken); + } + + @Override + public void save(RefreshToken refreshToken) { + RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(refreshToken); + refreshTokenJpaRepository.save(refreshTokenEntity); + } } diff --git a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/mapper/UserMapper.java b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/mapper/UserMapper.java deleted file mode 100644 index 288b3b2..0000000 --- a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/mapper/UserMapper.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.sportshub.infrastructure.user.mapper; - -import org.sportshub.domain.user.model.User; -import org.sportshub.infrastructure.user.model.UserEntity; -import org.springframework.stereotype.Component; - -@Component -public class UserMapper { - public User mapFrom(UserEntity userEntity) { - return new User(userEntity.getId(), userEntity.getPassword(), userEntity.getRoles()); - } - - public UserEntity mapTo(User user) { - return new UserEntity( - user.id(), - user.password(), - user.roles() - ); - } -} diff --git a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/model/RefreshTokenEntity.java b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/model/RefreshTokenEntity.java new file mode 100644 index 0000000..02fd222 --- /dev/null +++ b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/model/RefreshTokenEntity.java @@ -0,0 +1,40 @@ +package org.sportshub.infrastructure.user.model; + +import java.time.ZonedDateTime; +import java.util.UUID; + +import org.sportshub.domain.user.model.RefreshToken; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "refresh_token") +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class RefreshTokenEntity { + @Id + private UUID userId; + @Column(nullable = false) + private UUID value; + @Column(nullable = false) + private ZonedDateTime expirationDate; + + public RefreshTokenEntity(RefreshToken refreshToken) { + userId = refreshToken.userId(); + value = refreshToken.value(); + expirationDate = refreshToken.expirationDate(); + } + + public RefreshToken toRefreshToken() { + return new RefreshToken(userId, value, expirationDate); + } +} diff --git a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/model/UserEntity.java b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/model/UserEntity.java index 2c94d62..998f68c 100644 --- a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/model/UserEntity.java +++ b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/model/UserEntity.java @@ -3,6 +3,7 @@ package org.sportshub.infrastructure.user.model; import java.util.List; import java.util.UUID; +import org.sportshub.domain.user.model.User; import org.sportshub.domain.user.model.UserRole; import jakarta.persistence.CollectionTable; @@ -37,4 +38,18 @@ public class UserEntity { ) @Column(name = "role") private List roles; + + public UserEntity(User user) { + id = user.id(); + password = user.password(); + roles = user.roles(); + } + + public User toUser() { + return new User( + id, + password, + roles + ); + } } diff --git a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/repository/RefreshTokenJpaRepository.java b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/repository/RefreshTokenJpaRepository.java new file mode 100644 index 0000000..1f33cf2 --- /dev/null +++ b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/repository/RefreshTokenJpaRepository.java @@ -0,0 +1,15 @@ +package org.sportshub.infrastructure.user.repository; + +import java.util.Optional; +import java.util.UUID; + +import org.sportshub.infrastructure.user.model.RefreshTokenEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RefreshTokenJpaRepository extends JpaRepository { + Optional findByUserId(UUID userId); + + Optional findByValue(UUID refreshTokenId); +} diff --git a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/repository/UserJpaRepository.java b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/repository/UserJpaRepository.java index 574236b..cb6d4ae 100644 --- a/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/repository/UserJpaRepository.java +++ b/sportshub-infrastructure/src/main/java/org/sportshub/infrastructure/user/repository/UserJpaRepository.java @@ -8,9 +8,9 @@ import org.sportshub.infrastructure.user.model.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Repository; -@Service +@Repository public interface UserJpaRepository extends JpaRepository { @Query("SELECT u FROM UserEntity u JOIN FETCH u.roles WHERE u.id = :userId") Optional findById(@Param("userId") UUID userId); diff --git a/sportshub-infrastructure/src/main/resources/sql/001-initial-script-tables-creation.sql b/sportshub-infrastructure/src/main/resources/sql/001-initial-script-tables-creation.sql index e722e24..fbe60ba 100644 --- a/sportshub-infrastructure/src/main/resources/sql/001-initial-script-tables-creation.sql +++ b/sportshub-infrastructure/src/main/resources/sql/001-initial-script-tables-creation.sql @@ -10,4 +10,13 @@ CREATE TABLE IF NOT EXISTS user_role ( CONSTRAINT user_role_pk PRIMARY KEY (user_id, role), CONSTRAINT user_role_fk_user_id FOREIGN KEY (user_id) REFERENCES "user" (id) ); -CREATE INDEX user_role_fk_user_id_idx ON user_role (user_id); \ No newline at end of file +CREATE INDEX user_role_fk_user_id_idx ON user_role (user_id); + +CREATE TABLE IF NOT EXISTS refresh_token ( + user_id UUID NOT NULL, + value UUID NOT NULL, + expiration_date TIMESTAMP NOT NULL, + CONSTRAINT refresh_token_pk PRIMARY KEY (user_id), + CONSTRAINT refresh_token_fk_user_id FOREIGN KEY (user_id) REFERENCES "user" (id) +); +CREATE INDEX refresh_token_fk_user_id_idx ON user_role (user_id); \ No newline at end of file diff --git a/sportshub-launcher/src/main/resources/application.yml b/sportshub-launcher/src/main/resources/application.yml index 2b01a2e..c9e48c8 100644 --- a/sportshub-launcher/src/main/resources/application.yml +++ b/sportshub-launcher/src/main/resources/application.yml @@ -1,7 +1,10 @@ application: security: - secretKey: "secret-key" - tokenExpirationDelayInMinutes: 30 + jwt: + secretKey: "secret-key" + expirationDelayInMinutes: 30 + refreshToken: + expirationDelayInDays: 7 logging: level: