diff --git a/README.md b/README.md index a56875c3..fe78dc38 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,18 @@ e.g or any other wildfly location +## secret.env + +The project/application needs a secret.env file with the following variables set in order for authentication and tests to work + +``` +SECRET_ADMIN_NAME +SECRET_JWT_HASH +SECRET_DEFAULT_PW +``` + +For convenience the [plugin](https://plugins.jetbrains.com/plugin/7861-envfile) is recommended for reading the secret.env when tests are executed via IntelliJ + ## Build Project and deploy application - *In order for all used relative paths to work diff --git a/gamertrack-IntegrationTest/src/test/java/com/gepardec/rest/impl/AuthResourceImplIT.java b/gamertrack-IntegrationTest/src/test/java/com/gepardec/rest/impl/AuthResourceImplIT.java index a1480d95..323d9dc0 100644 --- a/gamertrack-IntegrationTest/src/test/java/com/gepardec/rest/impl/AuthResourceImplIT.java +++ b/gamertrack-IntegrationTest/src/test/java/com/gepardec/rest/impl/AuthResourceImplIT.java @@ -2,6 +2,7 @@ import com.gepardec.rest.model.command.AuthCredentialCommand; import com.gepardec.rest.model.command.CreateUserCommand; +import com.gepardec.rest.model.command.ValidateTokenCommand; import io.github.cdimascio.dotenv.Dotenv; import io.restassured.RestAssured; import io.restassured.filter.log.LogDetail; @@ -95,4 +96,53 @@ public void createTestUserWithAuthHeader() { .path("token"); usedUserTokens.add(token); } + + @Test + public void ensureValidateTokenForInvalidTokenReturnsUnauthorized() { + ValidateTokenCommand validateTokenCommand = new ValidateTokenCommand("aksldfjalsdfjalskdjfaksdl.asdfasddfasdf.asdfsadff"); + with().when() + .contentType("application/json") + .body(validateTokenCommand) + .post("/auth/validate") + .then() + .statusCode(401); + } + + @Test + public void ensureValidateTokenForNotProvidedOrNullTokenReturnsUnauthorized() { + ValidateTokenCommand validateTokenCommand = new ValidateTokenCommand(null); + with().when() + .contentType("application/json") + .body(validateTokenCommand) + .post("/auth/validate") + .then() + .statusCode(401); + } + + @Test + public void ensureValidateTokenForValidTokenReturns200Ok() { + //Login to get valid token + String authHeader = with().when() + .contentType("application/json") + .body(new AuthCredentialCommand(SECRET_ADMIN_NAME, SECRET_DEFAULT_PW)) + .headers("Content-Type", ContentType.JSON, + "Accept", ContentType.JSON) + .request("POST", "/auth/login") + .then() + .statusCode(200) + .extract() + .header("Authorization"); + + var token = authHeader.replace("Bearer ", ""); + + + //Validate token + ValidateTokenCommand validateTokenCommand = new ValidateTokenCommand(token); + with().when() + .contentType("application/json") + .body(validateTokenCommand) + .post("/auth/validate") + .then() + .statusCode(200); + } } diff --git a/gamertrack-application/src/main/java/com/gepardec/rest/api/AuthResource.java b/gamertrack-application/src/main/java/com/gepardec/rest/api/AuthResource.java index 0499d1a1..0909baa2 100644 --- a/gamertrack-application/src/main/java/com/gepardec/rest/api/AuthResource.java +++ b/gamertrack-application/src/main/java/com/gepardec/rest/api/AuthResource.java @@ -1,6 +1,7 @@ package com.gepardec.rest.api; import com.gepardec.rest.model.command.AuthCredentialCommand; +import com.gepardec.rest.model.command.ValidateTokenCommand; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; @@ -16,4 +17,8 @@ public interface AuthResource { @POST @Path("/login") Response login(AuthCredentialCommand authCredentialCommand); + + @POST + @Path("/validate") + Response validateToken(ValidateTokenCommand token); } diff --git a/gamertrack-application/src/main/java/com/gepardec/rest/impl/AuthResourceImpl.java b/gamertrack-application/src/main/java/com/gepardec/rest/impl/AuthResourceImpl.java index 24353b5a..dce91647 100644 --- a/gamertrack-application/src/main/java/com/gepardec/rest/impl/AuthResourceImpl.java +++ b/gamertrack-application/src/main/java/com/gepardec/rest/impl/AuthResourceImpl.java @@ -3,6 +3,7 @@ import com.gepardec.core.services.AuthService; import com.gepardec.rest.api.AuthResource; import com.gepardec.rest.model.command.AuthCredentialCommand; +import com.gepardec.rest.model.command.ValidateTokenCommand; import com.gepardec.rest.model.mapper.AuthCredentialRestMapper; import com.gepardec.security.JwtUtil; import jakarta.enterprise.context.RequestScoped; @@ -36,4 +37,11 @@ public Response login(AuthCredentialCommand authCredentialCommand) { return Response.status(Response.Status.UNAUTHORIZED).entity("Invalid credentials").build(); } } + + @Override + public Response validateToken(ValidateTokenCommand tokenCmd) { + if (tokenCmd.token() != null && authService.isTokenValid(tokenCmd.token())) return Response.ok().build(); + + return Response.status(Response.Status.UNAUTHORIZED).entity("Invalid token").build(); + } } diff --git a/gamertrack-application/src/main/java/com/gepardec/rest/model/command/ValidateTokenCommand.java b/gamertrack-application/src/main/java/com/gepardec/rest/model/command/ValidateTokenCommand.java new file mode 100644 index 00000000..11904665 --- /dev/null +++ b/gamertrack-application/src/main/java/com/gepardec/rest/model/command/ValidateTokenCommand.java @@ -0,0 +1,5 @@ +package com.gepardec.rest.model.command; + +public record ValidateTokenCommand(String token) { + +} diff --git a/gamertrack-domain/pom.xml b/gamertrack-domain/pom.xml index d1287a6b..ffede518 100644 --- a/gamertrack-domain/pom.xml +++ b/gamertrack-domain/pom.xml @@ -71,6 +71,35 @@ + + org.codehaus.mojo + properties-maven-plugin + 1.2.1 + + + initialize + + read-project-properties + + + true + + ${project.parent.basedir}/secret.env + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + ${SECRET_JWT_HASH} + + + \ No newline at end of file diff --git a/gamertrack-domain/src/main/java/com/gepardec/core/services/AuthService.java b/gamertrack-domain/src/main/java/com/gepardec/core/services/AuthService.java index 806b4947..d28b56e2 100644 --- a/gamertrack-domain/src/main/java/com/gepardec/core/services/AuthService.java +++ b/gamertrack-domain/src/main/java/com/gepardec/core/services/AuthService.java @@ -5,4 +5,5 @@ public interface AuthService { boolean authenticate(AuthCredential credential); boolean createDefaultUserIfNotExists(); + boolean isTokenValid(String token); } diff --git a/gamertrack-domain/src/main/java/com/gepardec/impl/service/AuthServiceImpl.java b/gamertrack-domain/src/main/java/com/gepardec/impl/service/AuthServiceImpl.java index f3f1c290..6b6800d5 100644 --- a/gamertrack-domain/src/main/java/com/gepardec/impl/service/AuthServiceImpl.java +++ b/gamertrack-domain/src/main/java/com/gepardec/impl/service/AuthServiceImpl.java @@ -6,6 +6,8 @@ import com.gepardec.model.AuthCredential; import com.gepardec.security.JwtUtil; import io.github.cdimascio.dotenv.Dotenv; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; import jakarta.ejb.Stateless; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -78,4 +80,16 @@ public boolean createDefaultUserIfNotExists() { return false; //user was not created } } + + @Override + public boolean isTokenValid(String token) { + boolean isValid = false; + try { + Jwts.parser().setSigningKey(jwtUtil.generateKey()).build().parseClaimsJws(token); + isValid = true; + } catch (JwtException e) { + log.error("Token validation failed {}", e.getMessage()); + } + return isValid; + } } diff --git a/gamertrack-domain/src/test/java/com/gepardec/impl/service/AuthServiceImplTest.java b/gamertrack-domain/src/test/java/com/gepardec/impl/service/AuthServiceImplTest.java index e102b4b5..9ec65da3 100644 --- a/gamertrack-domain/src/test/java/com/gepardec/impl/service/AuthServiceImplTest.java +++ b/gamertrack-domain/src/test/java/com/gepardec/impl/service/AuthServiceImplTest.java @@ -13,7 +13,7 @@ import java.util.Optional; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -29,6 +29,7 @@ public class AuthServiceImplTest { @Mock TokenService tokenService; + @Test void ensureCreateDefaultUserIfNotExistsCreatesDefaultUser() { when(authRepository.findByUsername(any())).thenReturn(Optional.empty()); @@ -68,4 +69,22 @@ void ensureAuthenticateReturnsTrueWhenCredentialsCorrect() { assertEquals(authService.authenticate(new AuthCredential("admin", "CorrectPW")), true); } + @Test + void ensureIsTokenValidReturnsFalseIfTokenIsNull() { + assertFalse(authService.isTokenValid(null)); + } + + @Test + void ensureIsTokenValidReturnsFalseIfTokenIsInvalid() { + assertFalse(authService.isTokenValid("invalidToken.shouldNotWork.shouldBeFalse")); + } + + @Test + void ensureIsTokenValidReturnsTrueIfTokenIsValid() { + when(jwtUtil.generateToken(any())).thenCallRealMethod(); + when(jwtUtil.generateKey()).thenCallRealMethod(); + + + assertTrue(authService.isTokenValid(jwtUtil.generateToken("AnyUser"))); + } }