From 2f977f2bcb1ab32dce0345747207b054255442d9 Mon Sep 17 00:00:00 2001 From: Steve <54807886+steve-hb@users.noreply.github.com> Date: Sun, 12 Apr 2020 02:34:49 +0200 Subject: [PATCH] =?UTF-8?q?Completed=20register/login/jwt=20process=20incl?= =?UTF-8?q?uding=20user=20micro=20service=20con=E2=80=A6=20(#53)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Completed register/login/jwt process including user micro service connection and renamed "rand" to "nonce" to be more compliant with OWASP * I love checkstyle <3 --- build.gradle | 2 + .../edu/gateway/GatewayApplication.java | 8 +- .../edu/gateway/backend/BackendAPI.java | 9 ++ .../edu/gateway/backend/Service.java | 41 ++++++++++ .../edu/gateway/backend/UserService.java | 71 ++++++++++++++++ .../gateway/backend/user/UserCreateDTO.java | 16 ++++ .../backend/user/UserErrorResponseDTO.java | 12 +++ .../backend/user/UserLoginRequestDTO.java | 13 +++ .../backend/user/UserLoginResponseDTO.java | 12 +++ .../graphql/dto/user/UserRegisterDTO.java | 18 ++++ .../dto/user/UserRegisterResultDTO.java | 23 ++++++ .../mutation/user/AuthenticationResolver.java | 82 ++++++++++++++++--- .../graphql/resolver/util/DeviceId.java | 62 ++++++++++++++ .../graphql/resolver/util/JsonWebToken.java | 8 +- .../graphql/resolver/util/RefreshToken.java | 6 +- .../gateway/service/ReportMicroService.java | 4 - .../edu/gateway/service/TaskMicroService.java | 4 - .../edu/gateway/service/UserMicroService.java | 4 - src/main/resources/log4j2.xml | 1 + src/main/resources/schema.graphqls | 21 +++++ 20 files changed, 384 insertions(+), 33 deletions(-) create mode 100644 src/main/java/de/themorpheus/edu/gateway/backend/BackendAPI.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/backend/Service.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/backend/UserService.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/backend/user/UserCreateDTO.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/backend/user/UserErrorResponseDTO.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/backend/user/UserLoginRequestDTO.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/backend/user/UserLoginResponseDTO.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/UserRegisterDTO.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/UserRegisterResultDTO.java create mode 100644 src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/DeviceId.java delete mode 100644 src/main/java/de/themorpheus/edu/gateway/service/ReportMicroService.java delete mode 100644 src/main/java/de/themorpheus/edu/gateway/service/TaskMicroService.java delete mode 100644 src/main/java/de/themorpheus/edu/gateway/service/UserMicroService.java diff --git a/build.gradle b/build.gradle index b146760..6dfb679 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,8 @@ dependencies { } compile 'io.micrometer:micrometer-core:1.4.0' compile 'io.micrometer:micrometer-registry-influx:1.4.0' + implementation("com.squareup.okhttp3:okhttp:4.5.0") + implementation 'com.google.code.gson:gson:2.8.6' /* GraphQL */ implementation 'com.graphql-java-kickstart:graphql-spring-boot-starter:7.0.0' diff --git a/src/main/java/de/themorpheus/edu/gateway/GatewayApplication.java b/src/main/java/de/themorpheus/edu/gateway/GatewayApplication.java index 7bf3627..919bb51 100644 --- a/src/main/java/de/themorpheus/edu/gateway/GatewayApplication.java +++ b/src/main/java/de/themorpheus/edu/gateway/GatewayApplication.java @@ -1,10 +1,12 @@ package de.themorpheus.edu.gateway; +import de.themorpheus.edu.gateway.backend.BackendAPI; import de.themorpheus.edu.gateway.util.GitInfo; import com.careykevin.graphql.actuator.instrumentation.EnableGraphQLActuator; import com.jcabi.manifests.Manifests; import io.sentry.Sentry; import io.sentry.SentryClient; +import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; @@ -23,9 +25,13 @@ public class GatewayApplication { private static final Logger LOGGER = LoggerFactory.getLogger(GatewayApplication.class.getSimpleName()); - public static void main(String[] args) { + @Getter private static BackendAPI backendAPI; + + public static void main(String[] args) throws IOException { SpringApplication.run(GatewayApplication.class, args); initSentry(); + + backendAPI = new BackendAPI(); } @Bean diff --git a/src/main/java/de/themorpheus/edu/gateway/backend/BackendAPI.java b/src/main/java/de/themorpheus/edu/gateway/backend/BackendAPI.java new file mode 100644 index 0000000..a634d36 --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/backend/BackendAPI.java @@ -0,0 +1,9 @@ +package de.themorpheus.edu.gateway.backend; + +import lombok.Getter; + +public class BackendAPI { + + @Getter private final UserService userService = new UserService(); + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/backend/Service.java b/src/main/java/de/themorpheus/edu/gateway/backend/Service.java new file mode 100644 index 0000000..7a54e7f --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/backend/Service.java @@ -0,0 +1,41 @@ +package de.themorpheus.edu.gateway.backend; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; + +@RequiredArgsConstructor +public abstract class Service { + + protected static final Gson GSON = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + private static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); + + private final String uri; + + @Getter(AccessLevel.PROTECTED) + private final OkHttpClient client = new OkHttpClient(); + + protected Request.Builder request(String route) { + return new Request.Builder() + .url(uri + route); + } + + protected RequestBody body(Object obj) { + return RequestBody.create(GSON.toJson(obj), JSON); + } + + protected T response(Class clazz, String json) { + return GSON.fromJson(json, clazz); + } + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/backend/UserService.java b/src/main/java/de/themorpheus/edu/gateway/backend/UserService.java new file mode 100644 index 0000000..1b7976e --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/backend/UserService.java @@ -0,0 +1,71 @@ +package de.themorpheus.edu.gateway.backend; + +import de.themorpheus.edu.gateway.backend.user.UserCreateDTO; +import de.themorpheus.edu.gateway.backend.user.UserErrorResponseDTO; +import de.themorpheus.edu.gateway.backend.user.UserLoginRequestDTO; +import de.themorpheus.edu.gateway.backend.user.UserLoginResponseDTO; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; +import java.io.IOException; +import okhttp3.Request; +import okhttp3.Response; + +public class UserService extends Service { + + public UserService() { + super("https://user.e-edu.the-morpheus.de"); + } + + public RegisterResponse createUser( + @NotNull String email, + @NotNull String password, + @NotNull String firstName, + @NotNull String lastName, + @Nullable String teacherToken) throws IOException { + + Request request = request("/user") + .post(body(new UserCreateDTO( + email, + password, + firstName, + lastName, + teacherToken + ))) + .build(); + try (Response response = getClient().newCall(request).execute()) { + if (response.code() != 201) return response(RegisterResponse.class, response.body().string()); + return new RegisterResponse(null); + } + } + + public LoginResponse login(String email, String password) throws IOException { + Request request = request("/user/login") + .post(body(new UserLoginRequestDTO(email, password))).build(); + try (Response response = getClient().newCall(request).execute()) { + + if (response.code() != 200) { + UserErrorResponseDTO errorResponseDTO = response(UserErrorResponseDTO.class, response.body().string()); + return new LoginResponse(null, errorResponseDTO.getError()); + } + + UserLoginResponseDTO responseDTO = response(UserLoginResponseDTO.class, response.body().string()); + return new LoginResponse(responseDTO.getSession(), null); + } + } + + @Getter + @RequiredArgsConstructor + public static class LoginResponse { + private final String session; + private final String error; + } + + @Getter + @RequiredArgsConstructor + public static class RegisterResponse { + private final String error; + } + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/backend/user/UserCreateDTO.java b/src/main/java/de/themorpheus/edu/gateway/backend/user/UserCreateDTO.java new file mode 100644 index 0000000..77f135d --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/backend/user/UserCreateDTO.java @@ -0,0 +1,16 @@ +package de.themorpheus.edu.gateway.backend.user; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class UserCreateDTO { + + private final String email; + private final String password; + private final String firstName; + private final String lastName; + private final String teacherToken; + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/backend/user/UserErrorResponseDTO.java b/src/main/java/de/themorpheus/edu/gateway/backend/user/UserErrorResponseDTO.java new file mode 100644 index 0000000..342f9c3 --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/backend/user/UserErrorResponseDTO.java @@ -0,0 +1,12 @@ +package de.themorpheus.edu.gateway.backend.user; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class UserErrorResponseDTO { + + private String error; + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/backend/user/UserLoginRequestDTO.java b/src/main/java/de/themorpheus/edu/gateway/backend/user/UserLoginRequestDTO.java new file mode 100644 index 0000000..cb10ba5 --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/backend/user/UserLoginRequestDTO.java @@ -0,0 +1,13 @@ +package de.themorpheus.edu.gateway.backend.user; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class UserLoginRequestDTO { + + private final String email; + private final String password; + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/backend/user/UserLoginResponseDTO.java b/src/main/java/de/themorpheus/edu/gateway/backend/user/UserLoginResponseDTO.java new file mode 100644 index 0000000..6c13950 --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/backend/user/UserLoginResponseDTO.java @@ -0,0 +1,12 @@ +package de.themorpheus.edu.gateway.backend.user; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class UserLoginResponseDTO { + + private String session; + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/UserRegisterDTO.java b/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/UserRegisterDTO.java new file mode 100644 index 0000000..030341c --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/UserRegisterDTO.java @@ -0,0 +1,18 @@ +package de.themorpheus.edu.gateway.graphql.dto.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import javax.validation.constraints.NotBlank; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserRegisterDTO { + + @NotBlank private String passwordHash; + @NotBlank private String email; + @NotBlank private String firstName; + @NotBlank private String lastName; + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/UserRegisterResultDTO.java b/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/UserRegisterResultDTO.java new file mode 100644 index 0000000..3dcb5c7 --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/graphql/dto/user/UserRegisterResultDTO.java @@ -0,0 +1,23 @@ +package de.themorpheus.edu.gateway.graphql.dto.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import javax.validation.constraints.NotNull; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserRegisterResultDTO { + + @NotNull + private UserAuthResultType result; + + public enum UserAuthResultType { + SUCCESS, + KEY_ALREADY_REGISTERED, + REGISTRATION_FAILED, + MAINTENANCE + } + +} 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 be11844..8eba3d7 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,14 +1,23 @@ package de.themorpheus.edu.gateway.graphql.resolver.mutation.user; +import de.themorpheus.edu.gateway.GatewayApplication; +import de.themorpheus.edu.gateway.backend.UserService; 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.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.servlet.http.Cookie; import javax.validation.Valid; +import de.themorpheus.edu.gateway.graphql.dto.user.UserRegisterDTO; +import de.themorpheus.edu.gateway.graphql.dto.user.UserRegisterResultDTO; +import de.themorpheus.edu.gateway.graphql.resolver.util.DeviceId; 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 com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; import java.time.Duration; import java.util.UUID; import graphql.kickstart.tools.GraphQLMutationResolver; @@ -17,31 +26,78 @@ @Component public class AuthenticationResolver implements GraphQLMutationResolver { - public UserAuthResultDTO authenticate(@Valid UserAuthDTO userAuth, DataFetchingEnvironment environment) { - String deviceId = HeaderUtil.getCookie(environment, HeaderUtil.DEVICE_ID); - - deviceId: { - 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); + private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationResolver.class.getSimpleName()); + + @SuppressWarnings("checkstyle:IllegalCatch") + public UserRegisterResultDTO register(@Valid UserRegisterDTO userRegisterDTO, DataFetchingEnvironment environment) { + UserRegisterResultDTO.UserAuthResultType resultType; + + try { + UserService.RegisterResponse registerResponse = GatewayApplication.getBackendAPI().getUserService().createUser( + userRegisterDTO.getEmail(), + userRegisterDTO.getPasswordHash(), + userRegisterDTO.getFirstName(), + userRegisterDTO.getLastName(), + null + ); + + if (registerResponse.getError() == null) + resultType = UserRegisterResultDTO.UserAuthResultType.SUCCESS; + else if ("user already exist".equals(registerResponse.getError())) + resultType = UserRegisterResultDTO.UserAuthResultType.KEY_ALREADY_REGISTERED; + else + resultType = UserRegisterResultDTO.UserAuthResultType.REGISTRATION_FAILED; + + } catch (Exception ex) { + LOGGER.error("Error while register", ex); + resultType = UserRegisterResultDTO.UserAuthResultType.REGISTRATION_FAILED; } - //TODO: Backend authentication - UserAuthResultDTO.UserAuthResultType resultType = UserAuthResultDTO.UserAuthResultType.SUCCESS; - UUID userId = UUID.randomUUID(); + return new UserRegisterResultDTO(resultType); + } + + @SuppressWarnings("checkstyle:IllegalCatch") + public UserAuthResultDTO authenticate(@Valid UserAuthDTO userAuth, DataFetchingEnvironment environment) { + DecodedJWT requestDeviceId = DeviceId.get(environment); + //TODO: Use for protection + + UserAuthResultDTO.UserAuthResultType resultType; + UUID userId = null; + + backendRequest: try { + UserService.LoginResponse loginResponse = GatewayApplication.getBackendAPI().getUserService().login(userAuth.getKey(), userAuth.getPasswordHash()); + String sessionToken = loginResponse.getSession(); + String error = loginResponse.getError(); + + if (sessionToken == null) { + //TODO: Error handling + + resultType = UserAuthResultDTO.UserAuthResultType.AUTHENTICATION_FAILED; + break backendRequest; + } + + DecodedJWT token = JWT.decode(sessionToken); + userId = UUID.fromString(token.getClaim("uuid").asString()); + + resultType = UserAuthResultDTO.UserAuthResultType.SUCCESS; + } catch (Exception ex) { + LOGGER.error("Error while authentication", ex); + resultType = UserAuthResultDTO.UserAuthResultType.AUTHENTICATION_FAILED; + } if (resultType == UserAuthResultDTO.UserAuthResultType.SUCCESS) { refreshToken: { 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 + if (GatewayApplication.PRODUCTIVE) cookie.setSecure(true); HeaderUtil.addCookie(environment, cookie); //TODO: Same-Site? } + + if (requestDeviceId == null || !requestDeviceId.getClaim("userId").asString().equals(userId.toString())) + DeviceId.set(environment, userId); } return new UserAuthResultDTO(resultType); diff --git a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/DeviceId.java b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/DeviceId.java new file mode 100644 index 0000000..b13dad7 --- /dev/null +++ b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/DeviceId.java @@ -0,0 +1,62 @@ +package de.themorpheus.edu.gateway.graphql.resolver.util; + +import de.themorpheus.edu.gateway.GatewayApplication; +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.interfaces.DecodedJWT; +import javax.servlet.http.Cookie; +import java.security.SecureRandom; +import java.time.Duration; +import java.time.Instant; +import java.util.Base64; +import java.util.Date; +import java.util.UUID; +import graphql.schema.DataFetchingEnvironment; + +public class DeviceId { + + private static final SecureRandom RANDOM = new SecureRandom(); + + private static final int RANDOM_SIZE = 128; + private static final String SECRET = "nrj5m5sui65sdu"; //TODO + private static final Algorithm ALGORITHM = Algorithm.HMAC512(SECRET); + + public static DecodedJWT get(DataFetchingEnvironment environment) { + String cookie = HeaderUtil.getCookie(environment, HeaderUtil.DEVICE_ID); + if (cookie == null) return null; + return JWT.decode(cookie); + } + + public static DecodedJWT getOrSetIfNotExists(DataFetchingEnvironment environment, UUID userId) { + DecodedJWT deviceId = get(environment); + if (deviceId != null) return deviceId; + + return set(environment, userId); + } + + public static DecodedJWT set(DataFetchingEnvironment environment, UUID userId) { + byte[] nonce = new byte[RANDOM_SIZE]; + RANDOM.nextBytes(nonce); + + String deviceId = JWT.create() + .withIssuer("e-edu") + .withSubject("deviceId") + .withClaim("userId", userId.toString()) + .withClaim("userAgent", HeaderUtil.findRequestHeader(environment, "user-agent")) + .withClaim("nonce", Base64.getEncoder().encodeToString(nonce)) + .withIssuedAt(Date.from(Instant.now())) + .withExpiresAt(Date.from(Instant.now().plus(Duration.ofDays(365)))) + .sign(ALGORITHM); + + Cookie cookie = new Cookie(HeaderUtil.DEVICE_ID, deviceId); + cookie.setHttpOnly(true); + cookie.setMaxAge((int) Duration.ofDays(365).toSeconds()); + if (GatewayApplication.PRODUCTIVE) cookie.setSecure(true); + + HeaderUtil.addCookie(environment, cookie); + System.out.println("add device cookie"); + + return JWT.decode(deviceId); + } + +} diff --git a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/JsonWebToken.java b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/JsonWebToken.java index 64350dc..55a67ee 100644 --- a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/JsonWebToken.java +++ b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/JsonWebToken.java @@ -18,18 +18,18 @@ public class JsonWebToken { private static final SecureRandom RANDOM = new SecureRandom(); private static final int RANDOM_SIZE = 128; - private static final String SECRET = "nrj5m5sui65sdu"; + private static final String SECRET = "nrj5m5sui65sdu"; //TODO private static final Algorithm ALGORITHM = Algorithm.HMAC512(SECRET); public static String generate(UUID userId) { - byte[] rand = new byte[RANDOM_SIZE]; - RANDOM.nextBytes(rand); + byte[] nonce = new byte[RANDOM_SIZE]; + RANDOM.nextBytes(nonce); return JWT.create() .withIssuer("e-edu") .withSubject("jwt") .withClaim("userId", userId.toString()) - .withClaim("rand", Base64.getEncoder().encodeToString(rand)) + .withClaim("nonce", Base64.getEncoder().encodeToString(nonce)) .withIssuedAt(Date.from(Instant.now())) .withExpiresAt(Date.from(Instant.now().plus(Duration.ofMinutes(15)))) .sign(ALGORITHM); diff --git a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/RefreshToken.java b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/RefreshToken.java index dc23a7a..7fef501 100644 --- a/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/RefreshToken.java +++ b/src/main/java/de/themorpheus/edu/gateway/graphql/resolver/util/RefreshToken.java @@ -23,8 +23,8 @@ public class RefreshToken { private static final Algorithm ALGORITHM = Algorithm.HMAC512(SECRET); public static String generate(UUID userId) { - byte[] rand = new byte[RANDOM_SIZE]; - RANDOM.nextBytes(rand); + byte[] nonce = new byte[RANDOM_SIZE]; + RANDOM.nextBytes(nonce); Calendar expirationDate = Calendar.getInstance(); expirationDate.setTimeInMillis(System.currentTimeMillis()); @@ -34,7 +34,7 @@ public static String generate(UUID userId) { .withIssuer("e-edu") .withSubject("refresh_token") .withClaim("userId", userId.toString()) - .withClaim("rand", Base64.getEncoder().encodeToString(rand)) + .withClaim("nonce", Base64.getEncoder().encodeToString(nonce)) .withIssuedAt(Date.from(Instant.now())) .withExpiresAt(expirationDate.getTime()) .sign(ALGORITHM); diff --git a/src/main/java/de/themorpheus/edu/gateway/service/ReportMicroService.java b/src/main/java/de/themorpheus/edu/gateway/service/ReportMicroService.java deleted file mode 100644 index 749ba29..0000000 --- a/src/main/java/de/themorpheus/edu/gateway/service/ReportMicroService.java +++ /dev/null @@ -1,4 +0,0 @@ -package de.themorpheus.edu.gateway.service; - -public class ReportMicroService { -} diff --git a/src/main/java/de/themorpheus/edu/gateway/service/TaskMicroService.java b/src/main/java/de/themorpheus/edu/gateway/service/TaskMicroService.java deleted file mode 100644 index ccd5141..0000000 --- a/src/main/java/de/themorpheus/edu/gateway/service/TaskMicroService.java +++ /dev/null @@ -1,4 +0,0 @@ -package de.themorpheus.edu.gateway.service; - -public class TaskMicroService { -} diff --git a/src/main/java/de/themorpheus/edu/gateway/service/UserMicroService.java b/src/main/java/de/themorpheus/edu/gateway/service/UserMicroService.java deleted file mode 100644 index 5ab3378..0000000 --- a/src/main/java/de/themorpheus/edu/gateway/service/UserMicroService.java +++ /dev/null @@ -1,4 +0,0 @@ -package de.themorpheus.edu.gateway.service; - -public class UserMicroService { -} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 27746f3..27cc1f8 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -41,5 +41,6 @@ + diff --git a/src/main/resources/schema.graphqls b/src/main/resources/schema.graphqls index dcc8722..8d8ac87 100644 --- a/src/main/resources/schema.graphqls +++ b/src/main/resources/schema.graphqls @@ -36,6 +36,8 @@ type Mutation { # User + register(userRegisterInfo: UserRegisterInfo): UserRegisterResult + authenticate(userAuth: UserAuth): UserAuthResult jwt: JwtResult @@ -103,6 +105,25 @@ type User { status: Int } +input UserRegisterInfo { + passwordHash: String # PBKDF2 (3k rounds) hashed password + email: String # Email or Username + firstName: String + lastName: String +} + +type UserRegisterResult { + result: UserRegisterResultType +} + +# HTTP Secure Cookie: device_id +enum UserRegisterResultType { + SUCCESS, # refresh_token set via HTTP Secure Cookie + KEY_ALREADY_REGISTERED, + REGISTRATION_FAILED, # Password hash wrong format, banned, too many request... + MAINTENANCE +} + # HTTP Secure Cookie: device_id (optional) # See: https://owasp.org/www-community/Slow_Down_Online_Guessing_Attacks_with_Device_Cookies input UserAuth {