diff --git a/src/main/java/de/themorpheus/edu/gateway/GatewayApplication.java b/src/main/java/de/themorpheus/edu/gateway/GatewayApplication.java index 616f905..a2a5ce7 100644 --- a/src/main/java/de/themorpheus/edu/gateway/GatewayApplication.java +++ b/src/main/java/de/themorpheus/edu/gateway/GatewayApplication.java @@ -1,6 +1,5 @@ package de.themorpheus.edu.gateway; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @@ -10,9 +9,6 @@ @SpringBootApplication public class GatewayApplication { - @Value("${graphql.url:graphql}") - private static final String GRAPHQL_URL = "/graphql"; - public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } @@ -22,10 +18,12 @@ public WebMvcConfigurer webConfiguration() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping(GRAPHQL_URL).allowedOrigins( - "http://localhost:3000", - "api.e-edu.the-morpheus.de" - ).allowedMethods("GET", "OPTION", "POST", "PATCH", "PUT", "DELETE"); + registry + .addMapping("/graphql") + .allowedOrigins( + "*" //TODO: Not in production + ) + .allowedMethods("GET", "OPTION", "POST", "PATCH", "PUT", "DELETE"); } }; } diff --git a/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/JwtRequestDTO.java b/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/JwtRequestDTO.java deleted file mode 100644 index 1286add..0000000 --- a/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/JwtRequestDTO.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.themorpheus.edu.gateway.graphql.dto.user; - -public class JwtRequestDTO { - -} diff --git a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/mutation/user/AuthenticationResolver.java b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/mutation/user/AuthenticationResolver.java index 8407e37..be11844 100644 --- a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/mutation/user/AuthenticationResolver.java +++ b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/mutation/user/AuthenticationResolver.java @@ -1,16 +1,15 @@ package de.themorpheus.edu.gateway.graphql.resolver.mutation.user; -import de.themorpheus.edu.gateway.graphql.dto.user.JwtRequestDTO; import de.themorpheus.edu.gateway.graphql.dto.user.JwtResultDTO; import de.themorpheus.edu.gateway.graphql.dto.user.UserAuthDTO; import de.themorpheus.edu.gateway.graphql.dto.user.UserAuthResultDTO; import org.springframework.stereotype.Component; +import javax.servlet.http.Cookie; import javax.validation.Valid; import de.themorpheus.edu.gateway.graphql.resolver.util.HeaderUtil; import de.themorpheus.edu.gateway.graphql.resolver.util.JsonWebToken; import de.themorpheus.edu.gateway.graphql.resolver.util.RefreshToken; -import java.util.Map; -import java.util.Optional; +import java.time.Duration; import java.util.UUID; import graphql.kickstart.tools.GraphQLMutationResolver; import graphql.schema.DataFetchingEnvironment; @@ -19,19 +18,14 @@ public class AuthenticationResolver implements GraphQLMutationResolver { public UserAuthResultDTO authenticate(@Valid UserAuthDTO userAuth, DataFetchingEnvironment environment) { - Optional optionalDeviceId = HeaderUtil.findHeader(environment, HeaderUtil.DEVICE_ID); + String deviceId = HeaderUtil.getCookie(environment, HeaderUtil.DEVICE_ID); deviceId: { - HeaderUtil.setCookie( - environment, - Map.of( - HeaderUtil.DEVICE_ID, optionalDeviceId.orElse("EXAMPLE_DEVICE_COOKIE"), - //HeaderUtil.CookieOption.SAME_SITE.getValue(), "Strict" //TODO: Activate - HeaderUtil.CookieOption.SAME_SITE.getValue(), "None" - ), - HeaderUtil.CookieOption.SECURE, - HeaderUtil.CookieOption.HTTP_ONLY - ); + Cookie cookie = new Cookie(HeaderUtil.DEVICE_ID, deviceId == null ? "EXAMPLE_DEVICE_COOKIE" : deviceId); + cookie.setHttpOnly(true); + cookie.setMaxAge((int) Duration.ofDays(365).toSeconds()); + //cookie.setSecure(true); //TODO: Only in productive + HeaderUtil.addCookie(environment, cookie); } //TODO: Backend authentication @@ -40,22 +34,20 @@ public UserAuthResultDTO authenticate(@Valid UserAuthDTO userAuth, DataFetchingE if (resultType == UserAuthResultDTO.UserAuthResultType.SUCCESS) { refreshToken: { - HeaderUtil.setCookie( - environment, - Map.of( - HeaderUtil.REFRESH_TOKEN, RefreshToken.generate(userId), - HeaderUtil.CookieOption.SAME_SITE.getValue(), "None" //TODO: Strict - ), - HeaderUtil.CookieOption.SECURE, - HeaderUtil.CookieOption.HTTP_ONLY - ); + Cookie cookie = new Cookie(HeaderUtil.REFRESH_TOKEN, RefreshToken.generate(userId)); + cookie.setHttpOnly(true); + cookie.setMaxAge((int) Duration.ofDays(365).toSeconds()); + //cookie.setSecure(true); //TODO: Only in productive + + HeaderUtil.addCookie(environment, cookie); + //TODO: Same-Site? } } return new UserAuthResultDTO(resultType); } - public JwtResultDTO jwt(@Valid JwtRequestDTO requestDTO, DataFetchingEnvironment environment) { + public JwtResultDTO jwt(DataFetchingEnvironment environment) { // Get cookie String refreshTokenCookie = HeaderUtil.getCookie(environment, HeaderUtil.REFRESH_TOKEN); if (refreshTokenCookie == null) return new JwtResultDTO(null, JwtResultDTO.JwtStatus.INVALID); //TODO: 403 Forbidden diff --git a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/HeaderUtil.java b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/HeaderUtil.java index 54f1369..c0a325e 100644 --- a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/HeaderUtil.java +++ b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/HeaderUtil.java @@ -2,11 +2,12 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; import java.util.Enumeration; -import java.util.Map; -import java.util.Optional; +import java.util.List; import graphql.schema.DataFetchingEnvironment; import graphql.servlet.context.DefaultGraphQLServletContext; @@ -15,19 +16,20 @@ public class HeaderUtil { public static final String DEVICE_ID = "device_id"; public static final String REFRESH_TOKEN = "refresh_token"; - public static final String SET_COOKIE = "set-cookie"; public static final String COOKIE = "cookie"; - public static Optional findHeader(DataFetchingEnvironment environment, String header) { + public static List findRequestHeader(DataFetchingEnvironment environment, String header) { DefaultGraphQLServletContext context = environment.getContext(); HttpServletRequest request = context.getHttpServletRequest(); - Enumeration headerNames = request.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String currentHeader = headerNames.nextElement(); - if (currentHeader.equalsIgnoreCase(header)) return Optional.of(request.getHeader(currentHeader)); + Enumeration headers = request.getHeaders(header); + + List result = new ArrayList<>(); + while (headers.hasMoreElements()) { + String currentHeader = headers.nextElement(); + result.add(currentHeader); } - return Optional.empty(); + return result; } public static void setHeader(DataFetchingEnvironment environment, String header, String value) { @@ -36,35 +38,24 @@ public static void setHeader(DataFetchingEnvironment environment, String header, response.setHeader(header, value); } - public static void setCookie(DataFetchingEnvironment environment, Map cookies, CookieOption... options) { + public static void addCookie(DataFetchingEnvironment environment, Cookie cookie) { DefaultGraphQLServletContext context = environment.getContext(); HttpServletResponse response = context.getHttpServletResponse(); - StringBuilder value = new StringBuilder(); - cookies.forEach((cookieKey, cookieValue) -> { - value.append(cookieKey); - value.append('='); - value.append(cookieValue); - value.append(';'); - }); - - for (CookieOption option : options) { - if (option.isValueRequired()) throw new IllegalArgumentException("Consider passing this option via 'cookies': " + option); - value.append(option.getValue()); - value.append(';'); - } - - response.setHeader(SET_COOKIE, value.toString()); + response.addCookie(cookie); } public static String getCookie(DataFetchingEnvironment environment, String key) { - Optional cookieOptional = HeaderUtil.findHeader(environment, COOKIE); - if (!cookieOptional.isPresent()) return null; + List cookiesList = HeaderUtil.findRequestHeader(environment, COOKIE); + if (cookiesList.isEmpty()) return null; - String[] parts = cookieOptional.get().split(";"); + for (String cookies : cookiesList) { + for (String c : cookies.split(";")) { + String cookie = c.trim(); - for (String part : parts) - if (part.startsWith(key) && part.contains("=")) - return part.split("=")[1]; + if (cookie.startsWith(key) && cookie.contains("=")) + return cookie.split("=")[1]; + } + } return null; } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f6348fc..d090f14 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,3 @@ -security.basic.enable:false +security.basic.enable=false server.port=80 +graphql.playground.settings.request.credentials=Same-Origin diff --git a/src/main/resources/schema.graphqls b/src/main/resources/schema.graphqls index ba88de5..dcc8722 100644 --- a/src/main/resources/schema.graphqls +++ b/src/main/resources/schema.graphqls @@ -38,7 +38,7 @@ type Mutation { authenticate(userAuth: UserAuth): UserAuthResult - jwt(jwtRequest: JwtRequest): JwtResult + jwt: JwtResult } @@ -126,7 +126,6 @@ enum UserAuthResultType { # HTTP Secure Cookie: refresh_token # HTTP Secure Cookie: device_id -input JwtRequest type JwtResult { token: String # JWT; ONLY stored in the frontend state manager!