Skip to content
This repository has been archived by the owner on May 22, 2021. It is now read-only.

Commit

Permalink
Merge experimental into master
Browse files Browse the repository at this point in the history
  • Loading branch information
steve-hb authored Apr 12, 2020
1 parent 9e8cded commit 4eff218
Show file tree
Hide file tree
Showing 20 changed files with 384 additions and 33 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package de.themorpheus.edu.gateway.backend;

import lombok.Getter;

public class BackendAPI {

@Getter private final UserService userService = new UserService();

}
41 changes: 41 additions & 0 deletions src/main/java/de/themorpheus/edu/gateway/backend/Service.java
Original file line number Diff line number Diff line change
@@ -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> T response(Class<T> clazz, String json) {
return GSON.fromJson(json, clazz);
}

}
71 changes: 71 additions & 0 deletions src/main/java/de/themorpheus/edu/gateway/backend/UserService.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 4eff218

Please sign in to comment.