diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityConfig.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityConfig.java index 6d016d8..2e79713 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityConfig.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityConfig.java @@ -3,6 +3,7 @@ package nl.andrewlalis.gymboard_api.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -38,7 +39,22 @@ public class SecurityConfig { .csrf().disable() .cors().and() .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .authorizeHttpRequests().anyRequest().permitAll(); + .authorizeHttpRequests() + .requestMatchers(// Allow the following GET endpoints to be public. + HttpMethod.GET, + "/exercises", + "/leaderboards", + "/gyms/**", + "/submissions/**" + ).permitAll() + .requestMatchers(// Allow the following POST endpoints to be public. + HttpMethod.POST, + "/gyms/submissions", + "/gyms/submissions/upload", + "/auth/token" + ).permitAll() + // Everything else must be authenticated, just to be safe. + .anyRequest().authenticated(); return http.build(); } diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityComponents.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/WebComponents.java similarity index 92% rename from gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityComponents.java rename to gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/WebComponents.java index 5af3e06..6ec6652 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/SecurityComponents.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/config/WebComponents.java @@ -6,7 +6,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration -public class SecurityComponents { +public class WebComponents { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(10); diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/controller/AuthController.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/controller/AuthController.java index 46b8e43..da03fe3 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/controller/AuthController.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/controller/AuthController.java @@ -5,7 +5,7 @@ import nl.andrewlalis.gymboard_api.controller.dto.TokenResponse; import nl.andrewlalis.gymboard_api.controller.dto.UserResponse; import nl.andrewlalis.gymboard_api.model.auth.User; import nl.andrewlalis.gymboard_api.service.auth.TokenService; -import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -20,13 +20,38 @@ public class AuthController { this.tokenService = tokenService; } + /** + * Endpoint for obtaining a new access token for a user to access certain + * parts of the application. This is a public endpoint. If + * a token is successfully obtained, it should be provided to all subsequent + * requests as an Authorization "Bearer" token. + * @param credentials The credentials. + * @return The token the client should use. + */ @PostMapping(path = "/auth/token") public TokenResponse getToken(@RequestBody TokenCredentials credentials) { return tokenService.generateAccessToken(credentials); } + /** + * Endpoint that can be used by an authenticated user to fetch a new access + * token using their current one; useful for staying logged in beyond the + * duration of the initial token's expiration. + * @param auth The current authentication. + * @return The new token the client should use. + */ + @GetMapping(path = "/auth/token") + public TokenResponse getUpdatedToken(Authentication auth) { + return tokenService.regenerateAccessToken(auth); + } + + /** + * Gets information about the user, as determined by the provided access + * token. + * @param user The user that requested information. + * @return The user data. + */ @GetMapping(path = "/auth/me") - @PreAuthorize("isAuthenticated()") public UserResponse getMyUser(@AuthenticationPrincipal User user) { return new UserResponse(user); } diff --git a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/service/auth/TokenService.java b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/service/auth/TokenService.java index 038bc7d..6556d2b 100644 --- a/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/service/auth/TokenService.java +++ b/gymboard-api/src/main/java/nl/andrewlalis/gymboard_api/service/auth/TokenService.java @@ -8,11 +8,13 @@ import nl.andrewlalis.gymboard_api.controller.dto.TokenCredentials; import nl.andrewlalis.gymboard_api.controller.dto.TokenResponse; import nl.andrewlalis.gymboard_api.dao.auth.UserRepository; import nl.andrewlalis.gymboard_api.model.auth.Role; +import nl.andrewlalis.gymboard_api.model.auth.TokenAuthentication; import nl.andrewlalis.gymboard_api.model.auth.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @@ -67,6 +69,22 @@ public class TokenService { if (!passwordEncoder.matches(credentials.password(), user.getPasswordHash())) { throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); } + if (!user.isActivated()) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + } + String token = generateAccessToken(user); + return new TokenResponse(token); + } + + public TokenResponse regenerateAccessToken(Authentication auth) { + if (!(auth instanceof TokenAuthentication tokenAuth)) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + } + User user = userRepository.findByIdWithRoles(tokenAuth.user().getId()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED)); + if (!user.isActivated()) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); + } String token = generateAccessToken(user); return new TokenResponse(token); } diff --git a/gymboard-app/src/pages/TestingPage.vue b/gymboard-app/src/pages/TestingPage.vue index b9eca7f..fe97c9b 100644 --- a/gymboard-app/src/pages/TestingPage.vue +++ b/gymboard-app/src/pages/TestingPage.vue @@ -5,7 +5,7 @@ Use this page to test new functionality, before adding it to the main app. This page should be hidden on production.
-