diff --git a/pom.xml b/pom.xml
index 4c40b62..1e7d895 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,14 @@
io.quarkus
quarkus-reactive-routes
+
+ io.quarkus
+ quarkus-jdbc-postgresql
+
+
+ io.quarkus
+ quarkus-hibernate-orm-panache
+
org.apache.commons
commons-lang3
@@ -61,6 +69,12 @@
4.5.14
compile
+
+ io.smallrye
+ smallrye-jwt
+ 4.6.3
+ compile
+
io.quarkus
quarkus-junit
diff --git a/src/main/java/dev/dinauer/JwtUtils.java b/src/main/java/dev/dinauer/JwtUtils.java
new file mode 100644
index 0000000..f453f41
--- /dev/null
+++ b/src/main/java/dev/dinauer/JwtUtils.java
@@ -0,0 +1,23 @@
+package dev.dinauer;
+
+import io.smallrye.jwt.auth.principal.DefaultJWTParser;
+import io.smallrye.jwt.auth.principal.ParseException;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+
+public class JwtUtils
+{
+ public static ZonedDateTime extractExpiresAt(String token)
+ {
+ try
+ {
+ return Instant.ofEpochSecond(new DefaultJWTParser().parseOnly(token).getExpirationTime()).atZone(ZoneOffset.UTC);
+ }
+ catch (ParseException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/AccessToken.java b/src/main/java/dev/dinauer/oidcproxy/AccessToken.java
new file mode 100644
index 0000000..a53c586
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/AccessToken.java
@@ -0,0 +1,7 @@
+package dev.dinauer.oidcproxy;
+
+import java.time.ZonedDateTime;
+
+public record AccessToken(ZonedDateTime expiresAt, String token)
+{
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/ForwardService.java b/src/main/java/dev/dinauer/oidcproxy/ForwardService.java
deleted file mode 100644
index eee5d94..0000000
--- a/src/main/java/dev/dinauer/oidcproxy/ForwardService.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package dev.dinauer.oidcproxy;
-
-import jakarta.enterprise.context.ApplicationScoped;
-
-@ApplicationScoped
-public class ForwardService
-{
-
-}
diff --git a/src/main/java/dev/dinauer/oidcproxy/ProxyResource.java b/src/main/java/dev/dinauer/oidcproxy/ProxyResource.java
deleted file mode 100644
index 3d303b8..0000000
--- a/src/main/java/dev/dinauer/oidcproxy/ProxyResource.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package dev.dinauer.oidcproxy;
-
-import dev.dinauer.oidcproxy.callback.CallbackService;
-import dev.dinauer.oidcproxy.callback.SessionRepository;
-import dev.dinauer.oidcproxy.startup.ProxyRoute;
-import dev.dinauer.oidcproxy.startup.RouteService;
-import io.quarkus.vertx.web.Route;
-import io.vertx.core.MultiMap;
-import io.vertx.core.buffer.Buffer;
-import io.vertx.core.http.Cookie;
-import io.vertx.core.http.HttpMethod;
-import io.vertx.ext.web.RoutingContext;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.*;
-import jakarta.ws.rs.core.Context;
-import jakarta.ws.rs.core.Link;
-import jakarta.ws.rs.core.MediaType;
-import jakarta.ws.rs.core.Response;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.swing.*;
-import java.io.IOException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.util.*;
-
-@ApplicationScoped
-public class ProxyResource
-{
- private static final Logger LOG = LoggerFactory.getLogger(ProxyResource.class);
-
- @Inject
- RouteService routeService;
-
- @Inject
- SessionRepository sessionRepository;
-
- @Inject
- ForwardService forwardService;
-
- @Inject
- CallbackService callbackService;
-
- @Route(path = "/callback", order = 0)
- public void callback(@Context RoutingContext context)
- {
- callbackService.get(context.response(), context.request());
- }
-
- @Route(path = "/*", order = 1)
- public void proxy(@Context RoutingContext context)
- {
- List requestSegments = Arrays.stream(context.request().path().split("/")).filter(item -> !StringUtils.isBlank(item)).toList();
-
- Optional routeOptional = routeService.match(requestSegments);
- if (routeOptional.isPresent())
- {
- ProxyRoute route = routeOptional.get();
- LOG.info("Matched route with target '{}'", route.target());
- try
- {
- byte[] body = extractBody(context);
- HttpResponse response = forward(context.request().headers(), context.request().method(), body, route.strategy(), extractSession(context.request().cookies()), route.target(), concat(dropRoute(route.segments(), requestSegments)));
- ResponseHandler.success(context, response);
- }
- catch (Exception e)
- {
- LOG.error("Error occurred on upstream.", e);
- ResponseHandler.error(context);
- }
- }
- else
- {
- LOG.error("No route found for request path '{}'", context.request().path());
- ResponseHandler.notFound(context);
- }
- }
-
- private byte[] extractBody(RoutingContext context)
- {
- if (context.body().buffer() != null)
- {
- return context.body().buffer().getBytes();
- }
- return null;
- }
-
- public HttpResponse forward(MultiMap headers, HttpMethod method, byte[] body, String strategy, String auth, String forwardRoot, String forwardPath) throws IOException, InterruptedException
- {
- HttpRequest.Builder builder = HttpRequest.newBuilder().uri(URI.create(forwardRoot + "/" + forwardPath));
- if (body != null)
- {
- builder.method(method.name(), HttpRequest.BodyPublishers.ofByteArray(body));
- }
- else
- {
- builder.method(method.name(), HttpRequest.BodyPublishers.noBody());
- }
- for (Map.Entry entry : headers.entries())
- {
- try
- {
- builder.header(entry.getKey(), entry.getValue());
- }
- catch (Exception e)
- {
- // empty
- }
- }
- if (auth != null && Strings.CI.equals("OIDC", strategy))
- {
- builder.header("Authorization", String.format("Bearer %s", sessionRepository.get(auth)));
- }
- try(HttpClient client = HttpClient.newHttpClient())
- {
- return client.send(builder.build(), HttpResponse.BodyHandlers.ofByteArray());
- }
- }
-
- private List dropRoute(List route, List request)
- {
- List requestSegments = new LinkedList<>(request);
- for (int i = 0; i < route.size(); i++)
- {
- requestSegments.removeFirst();
- }
- return requestSegments;
- }
-
- private String concat(List segments)
- {
- return String.join("/", segments);
- }
-
- private String extractSession(Set cookies)
- {
- for (Cookie cookie : cookies)
- {
- if ("session".equals(cookie.getName()))
- {
- return cookie.getValue();
- }
- }
- return null;
- }
-}
diff --git a/src/main/java/dev/dinauer/oidcproxy/callback/CallbackService.java b/src/main/java/dev/dinauer/oidcproxy/callback/CallbackService.java
index 697ace5..7c236d4 100644
--- a/src/main/java/dev/dinauer/oidcproxy/callback/CallbackService.java
+++ b/src/main/java/dev/dinauer/oidcproxy/callback/CallbackService.java
@@ -1,6 +1,9 @@
package dev.dinauer.oidcproxy.callback;
+import dev.dinauer.JwtUtils;
+import dev.dinauer.oidcproxy.AccessToken;
import dev.dinauer.oidcproxy.callback.model.TokenResponse;
+import dev.dinauer.oidcproxy.session.SessionCache;
import io.vertx.core.http.Cookie;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
@@ -8,6 +11,8 @@ import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
+import java.time.Instant;
+import java.time.ZoneOffset;
import java.time.ZonedDateTime;
@ApplicationScoped
@@ -17,7 +22,7 @@ public class CallbackService
OidcClient client;
@Inject
- SessionRepository sessionRepository;
+ SessionCache sessionCache;
@ConfigProperty(name = "oidc.proxy.client.redirect")
String redirectURI;
@@ -25,9 +30,12 @@ public class CallbackService
public void get(HttpServerResponse response, HttpServerRequest request)
{
String code = request.params().get("code");
- TokenResponse token = client.exchangeAuthorizationCode(code);
- String sessionId = sessionRepository.add(token.accessToken());
- response.addCookie(Cookie.cookie("session", sessionId).setHttpOnly(true).setSecure(true).setPath("/").setMaxAge((int) (token.expiresAt() - ZonedDateTime.now().toEpochSecond())));
+ TokenResponse oidcResponse = client.exchangeAuthorizationCode(code);
+ String sessionId = sessionCache.add(oidcResponse.accessToken(), oidcResponse.refreshToken(), Instant.ofEpochSecond(oidcResponse.expiresAt()).atZone(ZoneOffset.UTC));
+
+ int cookieExpiry = (int) (JwtUtils.extractExpiresAt(oidcResponse.refreshToken()).toEpochSecond() - ZonedDateTime.now().toEpochSecond());
+ response.addCookie(Cookie.cookie("session", sessionId).setHttpOnly(true).setSecure(true).setPath("/").setMaxAge(cookieExpiry));
+ response.addCookie(Cookie.cookie("identity", oidcResponse.idToken()).setSecure(true).setPath("/").setMaxAge(cookieExpiry));
response.setStatusCode(302);
response.putHeader("Location", redirectURI);
response.send();
diff --git a/src/main/java/dev/dinauer/oidcproxy/callback/OidcClient.java b/src/main/java/dev/dinauer/oidcproxy/callback/OidcClient.java
index 96d0fcc..1746a3f 100644
--- a/src/main/java/dev/dinauer/oidcproxy/callback/OidcClient.java
+++ b/src/main/java/dev/dinauer/oidcproxy/callback/OidcClient.java
@@ -41,7 +41,7 @@ public class OidcClient
try
{
HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
- if (response.statusCode() == 200)
+ if (response.statusCode() < 400)
{
return OBJECT_MAPPER.readValue(response.body(), TokenResponse.class);
}
diff --git a/src/main/java/dev/dinauer/oidcproxy/callback/SessionRepository.java b/src/main/java/dev/dinauer/oidcproxy/callback/SessionRepository.java
deleted file mode 100644
index 01bed7f..0000000
--- a/src/main/java/dev/dinauer/oidcproxy/callback/SessionRepository.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package dev.dinauer.oidcproxy.callback;
-
-import jakarta.enterprise.context.ApplicationScoped;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-@ApplicationScoped
-public class SessionRepository
-{
- private final Map tokens = new HashMap<>();
-
- public String add(String token)
- {
- String sessionId = UUID.randomUUID().toString();
- tokens.put(sessionId, token);
- return sessionId;
- }
-
- public String get(String sessionId)
- {
- String token = tokens.get(sessionId);
- if (token != null)
- {
- return token;
- }
- throw new RuntimeException();
- }
-}
diff --git a/src/main/java/dev/dinauer/oidcproxy/callback/model/TokenResponse.java b/src/main/java/dev/dinauer/oidcproxy/callback/model/TokenResponse.java
index b4ed4bb..2af840b 100644
--- a/src/main/java/dev/dinauer/oidcproxy/callback/model/TokenResponse.java
+++ b/src/main/java/dev/dinauer/oidcproxy/callback/model/TokenResponse.java
@@ -2,6 +2,7 @@ package dev.dinauer.oidcproxy.callback.model;
import com.fasterxml.jackson.annotation.JsonProperty;
-public record TokenResponse(@JsonProperty("access_token") String accessToken, @JsonProperty("expires_at") Long expiresAt)
+public record TokenResponse(@JsonProperty("access_token") String accessToken, @JsonProperty("refresh_token") String refreshToken, @JsonProperty("id_token") String idToken, @JsonProperty("expires_at") Long expiresAt)
{
+
}
diff --git a/src/main/java/dev/dinauer/oidcproxy/proxy/ForwardService.java b/src/main/java/dev/dinauer/oidcproxy/proxy/ForwardService.java
new file mode 100644
index 0000000..86c2f85
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/proxy/ForwardService.java
@@ -0,0 +1,68 @@
+package dev.dinauer.oidcproxy.proxy;
+
+import dev.dinauer.oidcproxy.proxy.exception.ProxyHttpException;
+import dev.dinauer.oidcproxy.proxy.exception.TokenNotFoundException;
+import dev.dinauer.oidcproxy.session.SessionCache;
+import dev.dinauer.oidcproxy.proxy.header.HeaderFilter;
+import dev.dinauer.oidcproxy.request.HttpRequestBuilder;
+import io.vertx.core.http.Cookie;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.io.IOException;
+import java.net.http.HttpClient;
+import java.net.http.HttpResponse;
+import java.util.*;
+
+@ApplicationScoped
+public class ForwardService
+{
+ private static final String AUTH_HEADER = "Authorization";
+ private static final HttpClient CLIENT = HttpClient.newHttpClient();
+
+ @Inject
+ SessionCache sessionCache;
+
+ @Inject
+ HeaderFilter headerFilter;
+
+ public HttpResponse send(RoutingContext context, String route, String strategy) throws IOException, InterruptedException, ProxyHttpException, TokenNotFoundException
+ {
+ HttpRequestBuilder builder = HttpRequestBuilder.create();
+ builder.setUri(route);
+ builder.setParams(context.request().params().entries());
+ builder.setMethod(context.request().method().toString());
+ builder.setHeaders(headerFilter.filter(context.request(), strategy));
+ builder.setBody(extractBody(context));
+
+ HttpResponse response = CLIENT.send(builder.build(), HttpResponse.BodyHandlers.ofByteArray());
+ if (response.statusCode() < 400)
+ {
+ return response;
+ }
+ throw new ProxyHttpException(response.statusCode());
+ }
+
+ private List> buildHeaders(List> request, Set cookies, String strategy) throws TokenNotFoundException
+ {
+ List headerNames = request.stream().map(Map.Entry::getKey).toList();
+ if (!headerNames.contains(AUTH_HEADER) && "OIDC".equals(strategy) && cookies.size() == 1)
+ {
+ String session = cookies.iterator().next().getValue();
+ List> headers = new LinkedList<>(request);
+ headers.add(Map.entry(AUTH_HEADER, sessionCache.get(session)));
+ return headers;
+ }
+ return request;
+ }
+
+ private byte[] extractBody(RoutingContext context)
+ {
+ if (context.body().buffer() != null)
+ {
+ return context.body().buffer().getBytes();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/proxy/ProxyResource.java b/src/main/java/dev/dinauer/oidcproxy/proxy/ProxyResource.java
new file mode 100644
index 0000000..086e44e
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/proxy/ProxyResource.java
@@ -0,0 +1,113 @@
+package dev.dinauer.oidcproxy.proxy;
+
+import dev.dinauer.oidcproxy.callback.CallbackService;
+import dev.dinauer.oidcproxy.proxy.exception.ProxyHttpException;
+import dev.dinauer.oidcproxy.proxy.exception.TokenNotFoundException;
+import dev.dinauer.oidcproxy.session.SessionCache;
+import dev.dinauer.oidcproxy.startup.PathConverter;
+import dev.dinauer.oidcproxy.startup.ProxyRoute;
+import dev.dinauer.oidcproxy.startup.RouteService;
+import io.quarkus.vertx.web.Route;
+import io.smallrye.common.annotation.Blocking;
+import io.vertx.core.http.Cookie;
+import io.vertx.core.http.HttpServerRequest;
+import io.vertx.core.http.HttpServerResponse;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.core.Context;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.http.HttpResponse;
+import java.util.*;
+
+@ApplicationScoped
+public class ProxyResource
+{
+ private static final Logger LOG = LoggerFactory.getLogger(ProxyResource.class);
+
+ @Inject
+ RouteService routeService;
+
+ @Inject
+ ForwardService forwardService;
+
+ @Inject
+ CallbackService callbackService;
+
+ @Route(path = "/auth/callback", order = 0)
+ @Blocking
+ public void callback(@Context RoutingContext context)
+ {
+ callbackService.get(context.response(), context.request());
+ }
+
+ @Route(path = "/auth/logout", order = 1)
+ @Blocking
+ public void logout(@Context HttpServerResponse response)
+ {
+ response.addCookie(Cookie.cookie("session", "").setMaxAge(0).setPath("/").setHttpOnly(true).setSecure(true));
+ response.setStatusCode(302);
+ response.putHeader("Location", "http://localhost:3000");
+ response.send();
+ }
+
+ @Route(path = "/*", order = 2)
+ @Blocking
+ public void proxy(@Context RoutingContext context)
+ {
+ HttpServerRequest request = context.request();
+
+ List requestSegments = PathConverter.toSegments(request.path());
+
+ Optional routeOptional = routeService.match(requestSegments);
+ if (routeOptional.isPresent())
+ {
+ ProxyRoute route = routeOptional.get();
+ LOG.info("Matched route with target '{}'", route.target());
+ try
+ {
+ String targetPath = PathConverter.toPath(dropPrefix(route.segments(), requestSegments));
+ String targetURI = route.target() + targetPath;
+ HttpResponse response = forwardService.send(context, targetURI, route.strategy());
+ ResponseHandler.success(context, response);
+ }
+ catch (ProxyHttpException e)
+ {
+ LOG.error("Upstream returned error status {}.", e.getStatusCode(), e);
+ ResponseHandler.error(context, e.getStatusCode());
+ }
+ catch (TokenNotFoundException e)
+ {
+ LOG.error("Token not found.", e);
+ ResponseHandler.error(context, 401);
+ }
+ catch (InterruptedException e)
+ {
+ LOG.error("Proxy request was interrupted, returning 503.", e);
+ Thread.currentThread().interrupt();
+ ResponseHandler.error(context, 503);
+ }
+ catch (Exception e)
+ {
+ LOG.error("Error occurred on upstream.", e);
+ ResponseHandler.error(context, 502);
+ }
+ }
+ else
+ {
+ LOG.error("No route found for request path '{}'", context.request().path());
+ ResponseHandler.notFound(context);
+ }
+ }
+
+ private List dropPrefix(List route, List request)
+ {
+ for (int i = 0; i < route.size(); i++)
+ {
+ request.removeFirst();
+ }
+ return request;
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/ResponseHandler.java b/src/main/java/dev/dinauer/oidcproxy/proxy/ResponseHandler.java
similarity index 85%
rename from src/main/java/dev/dinauer/oidcproxy/ResponseHandler.java
rename to src/main/java/dev/dinauer/oidcproxy/proxy/ResponseHandler.java
index 302cb16..710e7ae 100644
--- a/src/main/java/dev/dinauer/oidcproxy/ResponseHandler.java
+++ b/src/main/java/dev/dinauer/oidcproxy/proxy/ResponseHandler.java
@@ -1,4 +1,4 @@
-package dev.dinauer.oidcproxy;
+package dev.dinauer.oidcproxy.proxy;
import io.vertx.core.buffer.Buffer;
import io.vertx.ext.web.RoutingContext;
@@ -27,9 +27,9 @@ public class ResponseHandler
context.response().send(Buffer.buffer(response.body()));
}
- public static void error(RoutingContext context)
+ public static void error(RoutingContext context, int statusCode)
{
- context.response().setStatusCode(502);
+ context.response().setStatusCode(statusCode);
context.response().send();
}
diff --git a/src/main/java/dev/dinauer/oidcproxy/proxy/exception/ProxyHttpException.java b/src/main/java/dev/dinauer/oidcproxy/proxy/exception/ProxyHttpException.java
new file mode 100644
index 0000000..5bf34a9
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/proxy/exception/ProxyHttpException.java
@@ -0,0 +1,16 @@
+package dev.dinauer.oidcproxy.proxy.exception;
+
+public class ProxyHttpException extends Exception
+{
+ private final int statusCode;
+
+ public ProxyHttpException(int statusCode)
+ {
+ this.statusCode = statusCode;
+ }
+
+ public int getStatusCode()
+ {
+ return statusCode;
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/proxy/exception/TokenNotFoundException.java b/src/main/java/dev/dinauer/oidcproxy/proxy/exception/TokenNotFoundException.java
new file mode 100644
index 0000000..a39cc86
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/proxy/exception/TokenNotFoundException.java
@@ -0,0 +1,9 @@
+package dev.dinauer.oidcproxy.proxy.exception;
+
+public class TokenNotFoundException extends Exception
+{
+ public TokenNotFoundException()
+ {
+ super();
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/proxy/header/HeaderFilter.java b/src/main/java/dev/dinauer/oidcproxy/proxy/header/HeaderFilter.java
new file mode 100644
index 0000000..4799ff3
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/proxy/header/HeaderFilter.java
@@ -0,0 +1,62 @@
+package dev.dinauer.oidcproxy.proxy.header;
+
+import dev.dinauer.oidcproxy.proxy.exception.TokenNotFoundException;
+import dev.dinauer.oidcproxy.proxy.header.strategy.OidcStrategy;
+import dev.dinauer.oidcproxy.session.SessionCache;
+import io.quarkus.security.UnauthorizedException;
+import io.vertx.core.http.Cookie;
+import io.vertx.core.http.HttpServerRequest;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+@ApplicationScoped
+public class HeaderFilter
+{
+ private static final List HOP2HOP = List.of("Keep-Alive", "Transfer-Encoding", "TE", "Connection", "Trailer", "Upgrade", "Proxy-Authenticate", "Proxy-Authorization");
+
+ @Inject
+ SessionCache sessionCache;
+
+ @Inject
+ OidcStrategy oidcStrategy;
+
+ public List> filter(HttpServerRequest request, String strategy) throws TokenNotFoundException
+ {
+ List> headers = filterHop2HopHeaders(request.headers().entries());
+ if ("OIDC".equals(strategy))
+ {
+ headers = oidcStrategy.filter(getAccessToken(request), headers);
+ }
+ return headers;
+ }
+
+ private String getAccessToken(HttpServerRequest request) throws TokenNotFoundException
+ {
+ for (Cookie cookie : request.cookies())
+ {
+ if ("session".equals(cookie.getName()))
+ {
+ String session = cookie.getValue();
+ return sessionCache.get(session);
+ }
+ }
+ throw new UnauthorizedException();
+ }
+
+ private List> filterHop2HopHeaders(List> input)
+ {
+ List> result = new LinkedList<>();
+ for (Map.Entry header : input)
+ {
+ if (!HOP2HOP.contains(header.getKey()))
+ {
+ result.add(header);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/proxy/header/strategy/NoneStrategy.java b/src/main/java/dev/dinauer/oidcproxy/proxy/header/strategy/NoneStrategy.java
new file mode 100644
index 0000000..8a78885
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/proxy/header/strategy/NoneStrategy.java
@@ -0,0 +1,15 @@
+package dev.dinauer.oidcproxy.proxy.header.strategy;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+import java.util.List;
+import java.util.Map;
+
+@ApplicationScoped
+public class NoneStrategy
+{
+ public List> filter(List> input)
+ {
+ return input;
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/proxy/header/strategy/OidcStrategy.java b/src/main/java/dev/dinauer/oidcproxy/proxy/header/strategy/OidcStrategy.java
new file mode 100644
index 0000000..765d5e8
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/proxy/header/strategy/OidcStrategy.java
@@ -0,0 +1,35 @@
+package dev.dinauer.oidcproxy.proxy.header.strategy;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.lang3.Strings;
+
+import java.util.List;
+import java.util.Map;
+
+@ApplicationScoped
+public class OidcStrategy
+{
+ private static final String AUTH_HEADER = "Authorization";
+
+ public List> filter(String jwt, List> input)
+ {
+ if (!hasAuthHeader(input))
+ {
+ input.add(Map.entry(AUTH_HEADER, String.format("Bearer %s", jwt)));
+ }
+ return input;
+ }
+
+ private boolean hasAuthHeader(List> input)
+ {
+ for (Map.Entry header : input)
+ {
+ if (Strings.CI.equals(AUTH_HEADER, header.getKey()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/request/HttpRequestBuilder.java b/src/main/java/dev/dinauer/oidcproxy/request/HttpRequestBuilder.java
new file mode 100644
index 0000000..b940f08
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/request/HttpRequestBuilder.java
@@ -0,0 +1,104 @@
+package dev.dinauer.oidcproxy.request;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.net.http.HttpRequest;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class HttpRequestBuilder
+{
+ private static final Logger LOG = LoggerFactory.getLogger(HttpRequestBuilder.class);
+
+ private String method;
+ private String uri;
+ private List> params;
+ private List> headers;
+ private byte[] body;
+
+ public static HttpRequestBuilder create()
+ {
+ return new HttpRequestBuilder();
+ }
+
+ public void setMethod(String method)
+ {
+ this.method = method;
+ }
+
+ public void setUri(String uri)
+ {
+ this.uri = uri;
+ }
+
+ public void setParams(List> params)
+ {
+ this.params = params;
+ }
+
+ public void setHeaders(List> headers)
+ {
+ this.headers = headers;
+ }
+
+ public void setBody(byte[] body)
+ {
+ this.body = body;
+ }
+
+ public HttpRequest build()
+ {
+ HttpRequest.Builder builder = HttpRequest.newBuilder();
+ builder.uri(buildURI(this.uri, this.params));
+ builder.method(method, buildBody(body));
+
+ if (this.headers != null)
+ {
+ for (Map.Entry element : this.headers)
+ {
+ try
+ {
+ builder.setHeader(element.getKey(), element.getValue());
+ LOG.info("added header " + element.getKey());
+ }
+ catch (Exception e)
+ {
+ LOG.info("Failed to add header.", e);
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ private static URI buildURI(String uri, List> params)
+ {
+ try
+ {
+ if (params != null && !params.isEmpty())
+ {
+ String queryParams = params.stream().map(entry -> String.format("%s=%s", URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8), URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))).collect(Collectors.joining("&"));
+ return new URI(String.format("%s?%s", uri, queryParams));
+ }
+ return new URI(uri);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException();
+ }
+ }
+
+ private static HttpRequest.BodyPublisher buildBody(byte[] body)
+ {
+ if (body != null)
+ {
+ return HttpRequest.BodyPublishers.ofByteArray(body);
+ }
+ return HttpRequest.BodyPublishers.noBody();
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/session/AccessTokenEntity.java b/src/main/java/dev/dinauer/oidcproxy/session/AccessTokenEntity.java
new file mode 100644
index 0000000..5f55398
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/session/AccessTokenEntity.java
@@ -0,0 +1,52 @@
+package dev.dinauer.oidcproxy.session;
+
+import jakarta.persistence.*;
+
+import java.time.ZonedDateTime;
+
+@Entity
+@Table(name = "access_token")
+public class AccessTokenEntity
+{
+ @Id
+ private String id;
+
+ @Column(name = "expires_at")
+ private ZonedDateTime expiresAt;
+
+ @Column(columnDefinition = "text")
+ private String token;
+
+ public String getId()
+ {
+ return id;
+ }
+
+ public AccessTokenEntity setId(String id)
+ {
+ this.id = id;
+ return this;
+ }
+
+ public ZonedDateTime getExpiresAt()
+ {
+ return expiresAt;
+ }
+
+ public AccessTokenEntity setExpiresAt(ZonedDateTime expiresAt)
+ {
+ this.expiresAt = expiresAt;
+ return this;
+ }
+
+ public String getToken()
+ {
+ return token;
+ }
+
+ public AccessTokenEntity setToken(String token)
+ {
+ this.token = token;
+ return this;
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/session/AccessTokenRepository.java b/src/main/java/dev/dinauer/oidcproxy/session/AccessTokenRepository.java
new file mode 100644
index 0000000..418bd33
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/session/AccessTokenRepository.java
@@ -0,0 +1,17 @@
+package dev.dinauer.oidcproxy.session;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+
+@ApplicationScoped
+public class AccessTokenRepository implements PanacheRepositoryBase
+{
+ public List findExpiresBefore(ZonedDateTime timestamp)
+ {
+ return list("expiresAt <= :timestamp", Map.ofEntries(Map.entry("timestamp", timestamp)));
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/session/EncryptUtils.java b/src/main/java/dev/dinauer/oidcproxy/session/EncryptUtils.java
new file mode 100644
index 0000000..fea3510
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/session/EncryptUtils.java
@@ -0,0 +1,71 @@
+package dev.dinauer.oidcproxy.session;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import javax.crypto.*;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Base64;
+
+@ApplicationScoped
+public class EncryptUtils
+{
+ private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding";
+ private static final int IV_LENGTH = 8;
+ private static final int AUTH_TAG_LENGTH = 128;
+
+ private final SecretKey key;
+
+ public EncryptUtils(@ConfigProperty(name = "oidc.proxy.crypto.secret") String secret)
+ {
+ try
+ {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+ KeySpec spec = new PBEKeySpec(secret.toCharArray(), secret.getBytes(), 256_000, 256);
+ this.key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
+ }
+ catch (NoSuchAlgorithmException | InvalidKeySpecException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String encrypt(String input) throws Exception
+ {
+ byte[] iv = new byte[IV_LENGTH];
+ new SecureRandom().nextBytes(iv);
+
+ Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
+ cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(AUTH_TAG_LENGTH, iv));
+
+ byte[] encrypted = cipher.doFinal(input.getBytes((StandardCharsets.UTF_8)));
+
+ byte[] combined = new byte[iv.length + encrypted.length];
+ System.arraycopy(iv, 0, combined, 0, iv.length);
+ System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);
+
+ return Base64.getEncoder().encodeToString(combined);
+ }
+
+ public String decrypt(String ciphertext) throws Exception
+ {
+ byte[] combined = Base64.getDecoder().decode(ciphertext);
+
+ byte[] iv = new byte[IV_LENGTH];
+ byte[] encrypted = new byte[combined.length - IV_LENGTH];
+ System.arraycopy(combined, 0, iv, 0, iv.length);
+ System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length);
+
+ Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
+ cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(AUTH_TAG_LENGTH, iv));
+
+ return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8);
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/session/RefreshTokenEntity.java b/src/main/java/dev/dinauer/oidcproxy/session/RefreshTokenEntity.java
new file mode 100644
index 0000000..be0b618
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/session/RefreshTokenEntity.java
@@ -0,0 +1,52 @@
+package dev.dinauer.oidcproxy.session;
+
+import jakarta.persistence.*;
+
+import java.time.ZonedDateTime;
+
+@Entity
+@Table(name = "refresh_token")
+public class RefreshTokenEntity
+{
+ @Id
+ private String id;
+
+ @Column(name = "expires_at")
+ private ZonedDateTime expiresAt;
+
+ @Column(columnDefinition = "text")
+ private String token;
+
+ public String getId()
+ {
+ return id;
+ }
+
+ public RefreshTokenEntity setId(String id)
+ {
+ this.id = id;
+ return this;
+ }
+
+ public ZonedDateTime getExpiresAt()
+ {
+ return expiresAt;
+ }
+
+ public RefreshTokenEntity setExpiresAt(ZonedDateTime expiresAt)
+ {
+ this.expiresAt = expiresAt;
+ return this;
+ }
+
+ public RefreshTokenEntity setToken(String token)
+ {
+ this.token = token;
+ return this;
+ }
+
+ public String getToken()
+ {
+ return token;
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/session/RefreshTokenRepository.java b/src/main/java/dev/dinauer/oidcproxy/session/RefreshTokenRepository.java
new file mode 100644
index 0000000..03bc808
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/session/RefreshTokenRepository.java
@@ -0,0 +1,17 @@
+package dev.dinauer.oidcproxy.session;
+
+import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
+import jakarta.enterprise.context.ApplicationScoped;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+
+@ApplicationScoped
+public class RefreshTokenRepository implements PanacheRepositoryBase
+{
+ public List findExpiresBefore(ZonedDateTime timestamp)
+ {
+ return list("expiresAt <= :timestamp", Map.ofEntries(Map.entry("timestamp", timestamp)));
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/session/SessionCache.java b/src/main/java/dev/dinauer/oidcproxy/session/SessionCache.java
new file mode 100644
index 0000000..222d6cf
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/session/SessionCache.java
@@ -0,0 +1,104 @@
+package dev.dinauer.oidcproxy.session;
+
+import dev.dinauer.oidcproxy.AccessToken;
+import dev.dinauer.oidcproxy.proxy.exception.TokenNotFoundException;
+import io.quarkus.narayana.jta.QuarkusTransaction;
+import io.quarkus.runtime.Startup;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.context.control.ActivateRequestContext;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import org.jboss.logging.Logger;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.ZonedDateTime;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@ApplicationScoped
+public class SessionCache
+{
+ private final Map tokens = new ConcurrentHashMap<>();
+
+ @Inject
+ Logger LOG;
+
+ @Inject
+ SessionService sessionService;
+
+ @Inject
+ EncryptUtils encryptUtils;
+ @Inject
+ AccessTokenRepository accessTokenRepository;
+
+ @Startup
+ @ActivateRequestContext
+ void housekeeping()
+ {
+ Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> {
+ LOG.info("Running housekeeping...");
+ List sessions = accessTokenRepository.findExpiresBefore(ZonedDateTime.now().plusMinutes(2));
+ for (AccessTokenEntity session : sessions)
+ {
+ QuarkusTransaction.begin();
+ tokens.remove(session.getId());
+ try
+ {
+ accessTokenRepository.delete(session);
+ QuarkusTransaction.commit();
+ }
+ catch (Exception e)
+ {
+ QuarkusTransaction.rollback();
+ }
+ }
+ }, 0, 30, TimeUnit.SECONDS);
+ }
+
+ public String add(String accessToken, String refreshToken, ZonedDateTime expiresAt)
+ {
+ String sessionId = UUID.randomUUID().toString();
+ sessionService.create(sessionId, expiresAt, accessToken, refreshToken);
+ return sessionId;
+ }
+
+ public String get(String sessionId) throws TokenNotFoundException
+ {
+ String session = toHash(sessionId);
+ AccessToken token = tokens.get(session);
+ if (token != null)
+ {
+ return token.token();
+ }
+ AccessTokenEntity accessTokenEntity = accessTokenRepository.findById(session);
+ if (session != null)
+ {
+ try
+ {
+ String accessToken = encryptUtils.decrypt(accessTokenEntity.getToken());
+ tokens.put(session, new AccessToken(accessTokenEntity.getExpiresAt(), accessToken));
+ return accessToken;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ throw new TokenNotFoundException();
+ }
+
+ private String toHash(String sessionId)
+ {
+ try
+ {
+ return Base64.getEncoder().encodeToString( MessageDigest.getInstance("SHA-256").digest(sessionId.getBytes()));
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/session/SessionService.java b/src/main/java/dev/dinauer/oidcproxy/session/SessionService.java
new file mode 100644
index 0000000..673053e
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/session/SessionService.java
@@ -0,0 +1,57 @@
+package dev.dinauer.oidcproxy.session;
+
+import dev.dinauer.oidcproxy.AccessToken;
+import io.smallrye.jwt.auth.principal.DefaultJWTParser;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Base64;
+
+@ApplicationScoped
+public class SessionService
+{
+ @Inject
+ EncryptUtils encryptUtils;
+
+ @Inject
+ AccessTokenRepository accessTokenRepository;
+
+ @Inject
+ RefreshTokenRepository refreshTokenRepository;
+
+ @Transactional
+ public void create(String sessionId, ZonedDateTime sessionExpiresAt, String accessToken, String refreshToken)
+ {
+ String sessionHash = toHash(sessionId);
+ try
+ {
+ JsonWebToken token = new DefaultJWTParser().parseOnly(accessToken);
+ ZonedDateTime accessTokenExpiresAt = Instant.ofEpochSecond(token.getExpirationTime()).atZone(ZoneOffset.UTC);
+ accessTokenRepository.persist(new AccessTokenEntity().setId(sessionHash).setToken(encryptUtils.encrypt(accessToken)).setExpiresAt(accessTokenExpiresAt));
+ refreshTokenRepository.persist(new RefreshTokenEntity().setId(sessionHash).setToken(encryptUtils.encrypt(refreshToken)).setExpiresAt(sessionExpiresAt));
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String toHash(String sessionId)
+ {
+ try
+ {
+ return Base64.getEncoder().encodeToString( MessageDigest.getInstance("SHA-256").digest(sessionId.getBytes()));
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/startup/PathConverter.java b/src/main/java/dev/dinauer/oidcproxy/startup/PathConverter.java
new file mode 100644
index 0000000..62985e9
--- /dev/null
+++ b/src/main/java/dev/dinauer/oidcproxy/startup/PathConverter.java
@@ -0,0 +1,42 @@
+package dev.dinauer.oidcproxy.startup;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
+
+import javax.swing.*;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+public class PathConverter
+{
+ private static final String SEPARATOR = "/";
+
+ public static List toSegments(String path)
+ {
+ if (StringUtils.isBlank(path))
+ {
+ return new LinkedList<>();
+ }
+ return new LinkedList<>(Arrays.stream(path.split(SEPARATOR)).filter(item -> !StringUtils.isBlank(item)).toList());
+ }
+
+ public static String toPath(List segments)
+ {
+ if (isEmpty(segments))
+ {
+ return SEPARATOR;
+ }
+ return SEPARATOR + String.join(SEPARATOR, segments);
+ }
+
+ public static String normalize(String path)
+ {
+ return Strings.CI.removeEnd(path, SEPARATOR);
+ }
+
+ private static boolean isEmpty(List list)
+ {
+ return list == null || list.isEmpty();
+ }
+}
diff --git a/src/main/java/dev/dinauer/oidcproxy/startup/ProxyRoute.java b/src/main/java/dev/dinauer/oidcproxy/startup/ProxyRoute.java
index 3ec3314..0b7312a 100644
--- a/src/main/java/dev/dinauer/oidcproxy/startup/ProxyRoute.java
+++ b/src/main/java/dev/dinauer/oidcproxy/startup/ProxyRoute.java
@@ -1,25 +1,21 @@
package dev.dinauer.oidcproxy.startup;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Strings;
-
import java.util.List;
-import java.util.stream.Stream;
public record ProxyRoute(String path, String target, String strategy)
{
public String path()
{
- return Strings.CI.removeEnd(target, "/");
+ return PathConverter.normalize(this.path);
}
public String target()
{
- return Strings.CI.removeEnd(target, "/");
+ return PathConverter.normalize(this.target);
}
public List segments()
{
- return Stream.of(path.split("/")).filter(item -> !StringUtils.isBlank(item)).toList();
+ return PathConverter.toSegments(this.path);
}
}
diff --git a/src/main/java/dev/dinauer/oidcproxy/startup/RouteService.java b/src/main/java/dev/dinauer/oidcproxy/startup/RouteService.java
index 6875dfd..8f9d6c1 100644
--- a/src/main/java/dev/dinauer/oidcproxy/startup/RouteService.java
+++ b/src/main/java/dev/dinauer/oidcproxy/startup/RouteService.java
@@ -37,6 +37,10 @@ public class RouteService
List result = new LinkedList<>();
for (ConfigRoute route : rules.routes())
{
+ if (StringUtils.isBlank(route.strategy()))
+ {
+ throw new IllegalArgumentException();
+ }
if (StringUtils.isBlank(rules.root()))
{
result.add(new ProxyRoute(route.path(), route.target(), route.strategy()));
diff --git a/src/main/java/dev/dinauer/oidcproxy/startup/model/ConfigRoute.java b/src/main/java/dev/dinauer/oidcproxy/startup/model/ConfigRoute.java
index 6d4735e..67020f6 100644
--- a/src/main/java/dev/dinauer/oidcproxy/startup/model/ConfigRoute.java
+++ b/src/main/java/dev/dinauer/oidcproxy/startup/model/ConfigRoute.java
@@ -1,5 +1,6 @@
package dev.dinauer.oidcproxy.startup.model;
+import dev.dinauer.oidcproxy.startup.PathConverter;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
@@ -8,10 +9,6 @@ public record ConfigRoute(String path, String target, String strategy)
{
public List segments()
{
- if (StringUtils.isBlank(path))
- {
- return List.of();
- }
- return List.of(path.split("/"));
+ return PathConverter.toSegments(this.path);
}
}
diff --git a/src/main/java/dev/dinauer/oidcproxy/startup/model/ConfigRules.java b/src/main/java/dev/dinauer/oidcproxy/startup/model/ConfigRules.java
index 9c15966..61468bc 100644
--- a/src/main/java/dev/dinauer/oidcproxy/startup/model/ConfigRules.java
+++ b/src/main/java/dev/dinauer/oidcproxy/startup/model/ConfigRules.java
@@ -1,6 +1,6 @@
package dev.dinauer.oidcproxy.startup.model;
-import org.apache.commons.lang3.StringUtils;
+import dev.dinauer.oidcproxy.startup.PathConverter;
import java.util.List;
@@ -8,10 +8,6 @@ public record ConfigRules(String root, List routes)
{
public List segments()
{
- if (StringUtils.isBlank(root))
- {
- return List.of();
- }
- return List.of(root.split("/"));
+ return PathConverter.toSegments(this.root);
}
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8cdbca2..61a9aa9 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -3,5 +3,13 @@ oidc.proxy.client.id=backend
oidc.proxy.client.secret=backend
oidc.proxy.client.redirect=http://localhost:3000
-%dev.oidc.proxy.routes.config.location=/home/andreas/Documents/dev/oidc-proxy/src/main/resources/routes.yaml
-%prod.oidc.proxy.routes.config.location=/var/lib/oidc-proxy/routes.yaml
\ No newline at end of file
+%test,dev.oidc.proxy.routes.config.location=/home/andreas/Documents/dev/oidc-proxy/src/main/resources/routes.yaml
+%prod.oidc.proxy.routes.config.location=/var/lib/oidc-proxy/routes.yaml
+
+%test,dev.quarkus.hibernate-orm.schema-management.strategy=drop-and-create
+
+%dev,test.quarkus.datasource.username=postgres
+%dev,test.quarkus.datasource.password=postgres
+%dev,test.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=oidc-proxy
+
+%dev,test.oidc.proxy.crypto.secret=test
\ No newline at end of file
diff --git a/src/main/resources/routes.yaml b/src/main/resources/routes.yaml
index 11de261..34e2cc7 100644
--- a/src/main/resources/routes.yaml
+++ b/src/main/resources/routes.yaml
@@ -2,6 +2,6 @@ routes:
- path: /api
target: http://localhost:8081
strategy: OIDC
- - path: /
+ - path: /example
target: http://example.com
- strategy: NONE
\ No newline at end of file
+ strategy: OIDC
\ No newline at end of file
diff --git a/src/test/java/dev/dinauer/oidcproxy/session/EncryptUtilsTest.java b/src/test/java/dev/dinauer/oidcproxy/session/EncryptUtilsTest.java
new file mode 100644
index 0000000..5b53eb6
--- /dev/null
+++ b/src/test/java/dev/dinauer/oidcproxy/session/EncryptUtilsTest.java
@@ -0,0 +1,28 @@
+package dev.dinauer.oidcproxy.session;
+
+import io.quarkus.test.junit.QuarkusTest;
+import jakarta.inject.Inject;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+@QuarkusTest
+public class EncryptUtilsTest
+{
+ @Inject
+ EncryptUtils encryptUtils;
+
+ @Test
+ void test() throws Exception
+ {
+ String helloWorld = "Hello World!";
+
+ String encrypted = encryptUtils.encrypt(helloWorld);
+ String decrypted = encryptUtils.decrypt(encrypted);
+
+ Assertions.assertEquals(helloWorld, decrypted);
+ Assertions.assertNotEquals(helloWorld, new String(Base64.getDecoder().decode(encrypted), StandardCharsets.UTF_8));
+ }
+}