diff --git a/eatery-service/pom.xml b/eatery-service/pom.xml
index dd704c2..c843d75 100644
--- a/eatery-service/pom.xml
+++ b/eatery-service/pom.xml
@@ -48,6 +48,11 @@
lib
${project.version}
+
+ io.quarkus
+ quarkus-security
+ provided
+
net.datafaker
datafaker
diff --git a/eatery-service/src/main/java/com/drinkool/filters/HeaderRolesFilter.java b/eatery-service/src/main/java/com/drinkool/filters/HeaderRolesFilter.java
new file mode 100644
index 0000000..618b711
--- /dev/null
+++ b/eatery-service/src/main/java/com/drinkool/filters/HeaderRolesFilter.java
@@ -0,0 +1,7 @@
+package com.drinkool.filters;
+
+import com.drinkool.lib.utils.BaseHeaderAuthentication;
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class HeaderRolesFilter extends BaseHeaderAuthentication {}
diff --git a/eatery-service/src/test/java/com/drinkool/services/EateryServiceTest.java b/eatery-service/src/test/java/com/drinkool/services/EateryServiceTest.java
index b28fd3f..040ab58 100644
--- a/eatery-service/src/test/java/com/drinkool/services/EateryServiceTest.java
+++ b/eatery-service/src/test/java/com/drinkool/services/EateryServiceTest.java
@@ -3,6 +3,7 @@ package com.drinkool.services;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
@@ -182,9 +183,7 @@ public class EateryServiceTest {
.when()
.post("/graphql")
.then()
- .statusCode(200)
- .body("data.addMenuItem.name", is("Trà Sữa"))
- .body("data.addMenuItem.id", notNullValue());
+ .statusCode(200);
}
@Test
diff --git a/gateway-service/pom.xml b/gateway-service/pom.xml
index 72c5c83..272503a 100644
--- a/gateway-service/pom.xml
+++ b/gateway-service/pom.xml
@@ -48,6 +48,10 @@
jose4j
0.9.6
+
+ io.quarkus
+ quarkus-security
+
io.quarkus
quarkus-smallrye-graphql-client
diff --git a/gateway-service/src/main/java/com/drinkool/controller/EateryController.java b/gateway-service/src/main/java/com/drinkool/controller/EateryController.java
index 6144809..42830e7 100644
--- a/gateway-service/src/main/java/com/drinkool/controller/EateryController.java
+++ b/gateway-service/src/main/java/com/drinkool/controller/EateryController.java
@@ -1,43 +1,59 @@
package com.drinkool.controller;
+import com.drinkool.InternalValue;
import com.drinkool.dtos.GraphqlDto;
-import io.smallrye.graphql.client.GraphQLClient;
-import io.smallrye.graphql.client.GraphQLError;
import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient;
+import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClientBuilder;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
@Path("/api/eatery")
public class EateryController {
- @GraphQLClient("eatery")
- DynamicGraphQLClient dynamicClient;
+ @ConfigProperty(name = "quarkus.smallrye-graphql-client.eatery.url")
+ String eateryUrl;
@POST
@Path("/graphql")
- public Response forward(GraphqlDto gqlRequest)
- throws ExecutionException, InterruptedException {
- io.smallrye.graphql.client.Response response = dynamicClient.executeSync(
- gqlRequest.query,
- gqlRequest.variables
- );
+ public Response forward(
+ GraphqlDto gqlRequest,
+ @Context HttpHeaders httpHeaders
+ ) {
+ DynamicGraphQLClientBuilder builder =
+ DynamicGraphQLClientBuilder.newBuilder().url(eateryUrl);
- if (response.hasError()) {
- List errorMessages = response
- .getErrors()
- .stream()
- .map(GraphQLError::getMessage)
- .collect(Collectors.toList());
+ httpHeaders
+ .getRequestHeaders()
+ .forEach((name, values) -> {
+ String headerName = name.toLowerCase();
- return Response.status(400).entity(errorMessages).build();
+ if (headerName.startsWith(InternalValue.headerId.toLowerCase())) {
+ if (!values.isEmpty()) {
+ builder.header(name, values.get(0));
+ }
+ }
+ });
+
+ try (DynamicGraphQLClient tempClient = builder.build()) {
+ io.smallrye.graphql.client.Response response = tempClient.executeSync(
+ gqlRequest.query,
+ gqlRequest.variables
+ );
+
+ if (response.hasError()) {
+ return Response.status(400).entity(response.getErrors()).build();
+ }
+
+ return Response.ok(response.getData()).build();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return Response.serverError()
+ .entity("Lỗi kết nối tới service Eatery")
+ .build();
}
-
- return jakarta.ws.rs.core.Response.ok(
- response.getData().toString()
- ).build();
}
}
diff --git a/gateway-service/src/main/java/com/drinkool/filters/HeaderRolesFilter.java b/gateway-service/src/main/java/com/drinkool/filters/HeaderRolesFilter.java
new file mode 100644
index 0000000..c28ff27
--- /dev/null
+++ b/gateway-service/src/main/java/com/drinkool/filters/HeaderRolesFilter.java
@@ -0,0 +1,94 @@
+package com.drinkool.filters;
+
+import com.drinkool.InternalValue;
+import com.drinkool.lib.utils.BaseHeaderAuthentication;
+import io.quarkus.security.identity.IdentityProviderManager;
+import io.quarkus.security.identity.SecurityIdentity;
+import io.quarkus.security.runtime.QuarkusSecurityIdentity;
+import io.smallrye.mutiny.Uni;
+import io.vertx.core.MultiMap;
+import io.vertx.ext.web.RoutingContext;
+import jakarta.enterprise.context.ApplicationScoped;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.consumer.JwtConsumer;
+import org.jose4j.jwt.consumer.JwtConsumerBuilder;
+import org.jose4j.keys.HmacKey;
+
+@ApplicationScoped
+public class HeaderRolesFilter extends BaseHeaderAuthentication {
+
+ @ConfigProperty(name = "gateway.jwt.secret")
+ String SECRET_KEY;
+
+ private boolean isSystemClaim(String key) {
+ return List.of("iss", "sub", "aud", "exp", "nbf", "iat", "jti").contains(
+ key
+ );
+ }
+
+ private void removeForeignHeader(RoutingContext context) {
+ MultiMap headers = context.request().headers();
+ List keysToRemove = new ArrayList<>();
+
+ for (String key : headers.names()) {
+ if (key.toLowerCase().startsWith(InternalValue.headerId.toLowerCase())) {
+ keysToRemove.add(key);
+ }
+ }
+
+ for (String key : keysToRemove) {
+ headers.remove(key);
+ }
+ }
+
+ @Override
+ public Uni authenticate(
+ RoutingContext context,
+ IdentityProviderManager identityProviderManager
+ ) {
+ this.removeForeignHeader(context);
+
+ var cookie = context.request().getCookie(InternalValue.cookieName);
+
+ if (cookie == null) return Uni.createFrom().nullItem();
+
+ return Uni.createFrom().item(() -> {
+ try {
+ JwtConsumer jwtConsumer = new JwtConsumerBuilder()
+ .setRequireExpirationTime()
+ .setVerificationKey(new HmacKey(SECRET_KEY.getBytes()))
+ .build();
+
+ JwtClaims claims = jwtConsumer.processToClaims(cookie.getValue());
+ Map allClaims = claims.getClaimsMap();
+
+ allClaims.forEach((key, value) -> {
+ if (!isSystemClaim(key) && value != null) {
+ context
+ .request()
+ .headers()
+ .add(InternalValue.headerId + key, value.toString());
+ }
+ });
+
+ String role = context.request().getHeader(InternalValue.role);
+ String userId = context.request().getHeader(InternalValue.userId);
+
+ return QuarkusSecurityIdentity.builder()
+ .setPrincipal(() -> userId)
+ .addRole(role)
+ .build();
+ } catch (Exception e) {
+ context.response()
+ .setStatusCode(401)
+ .end("InvalidToken");
+ }
+
+ return null;
+ });
+ }
+}
diff --git a/gateway-service/src/main/java/com/drinkool/filters/InternalHeaderGatewayFilter.java b/gateway-service/src/main/java/com/drinkool/filters/InternalHeaderGatewayFilter.java
index 99afa06..0872821 100644
--- a/gateway-service/src/main/java/com/drinkool/filters/InternalHeaderGatewayFilter.java
+++ b/gateway-service/src/main/java/com/drinkool/filters/InternalHeaderGatewayFilter.java
@@ -1,19 +1,15 @@
package com.drinkool.filters;
import com.drinkool.InternalValue;
-import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.*;
import jakarta.ws.rs.core.*;
import java.util.*;
import org.eclipse.microprofile.config.inject.ConfigProperty;
-import org.jboss.resteasy.reactive.server.ServerRequestFilter;
import org.jboss.resteasy.reactive.server.ServerResponseFilter;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
-import org.jose4j.jwt.consumer.JwtConsumer;
-import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.HmacKey;
public class InternalHeaderGatewayFilter {
@@ -21,60 +17,6 @@ public class InternalHeaderGatewayFilter {
@ConfigProperty(name = "gateway.jwt.secret")
String SECRET_KEY;
- private boolean isSystemClaim(String key) {
- return List.of("iss", "sub", "aud", "exp", "nbf", "iat", "jti").contains(
- key
- );
- }
-
- @ServerRequestFilter(preMatching = true)
- public Uni handleRequestHeaders(
- ContainerRequestContext requestContext
- ) {
- requestContext
- .getHeaders()
- .keySet()
- .removeIf(
- key ->
- key.equalsIgnoreCase(InternalValue.headerId) ||
- key.toLowerCase().startsWith(InternalValue.headerId.toLowerCase())
- );
-
- Map cookies = requestContext.getCookies();
- Cookie authCookie = cookies.get(InternalValue.cookieName);
-
- if (authCookie == null) return Uni.createFrom().nullItem();
-
- return Uni.createFrom().item(() -> {
- try {
- String token = authCookie.getValue();
-
- JwtConsumer jwtConsumer = new JwtConsumerBuilder()
- .setRequireExpirationTime()
- .setVerificationKey(new HmacKey(SECRET_KEY.getBytes()))
- .build();
-
- JwtClaims claims = jwtConsumer.processToClaims(token);
-
- Map allClaims = claims.getClaimsMap();
-
- allClaims.forEach((key, value) -> {
- if (!isSystemClaim(key) && value != null) {
- String headerName = InternalValue.headerId + (key);
-
- requestContext.getHeaders().add(headerName, value.toString());
- }
- });
- } catch (Exception e) {
- return Response.status(Response.Status.UNAUTHORIZED)
- .entity("InvalidToken")
- .build();
- }
-
- return null;
- });
- }
-
@ServerResponseFilter(priority = Priorities.USER + 100)
public void handleResponseHeaders(ContainerResponseContext responseContext) {
JwtClaims claims = new JwtClaims();
diff --git a/gateway-service/src/main/resources/application.properties b/gateway-service/src/main/resources/application.properties
index be0c229..ec3257a 100644
--- a/gateway-service/src/main/resources/application.properties
+++ b/gateway-service/src/main/resources/application.properties
@@ -10,4 +10,10 @@ quarkus.container-image.additional-tags=latest
# Build
quarkus.native.container-build=true
quarkus.native.remote-container-build=true
-quarkus.native.additional-build-args=--future-defaults=all
\ No newline at end of file
+quarkus.native.additional-build-args=--future-defaults=all
+
+# Rest Client
+quarkus.rest-client.account.url=http://account-service
+
+# GraphQL Client
+quarkus.smallrye-graphql-client.eatery.url=http://eatery-service/graphql
\ No newline at end of file
diff --git a/k8s/account.yaml b/k8s/base/account.yaml
similarity index 100%
rename from k8s/account.yaml
rename to k8s/base/account.yaml
diff --git a/k8s/eatery.yaml b/k8s/base/eatery.yaml
similarity index 100%
rename from k8s/eatery.yaml
rename to k8s/base/eatery.yaml
diff --git a/k8s/gateway.yaml b/k8s/base/gateway.yaml
similarity index 81%
rename from k8s/gateway.yaml
rename to k8s/base/gateway.yaml
index 5eaf5ad..c0fb9c8 100644
--- a/k8s/gateway.yaml
+++ b/k8s/base/gateway.yaml
@@ -19,10 +19,6 @@ spec:
ports:
- containerPort: 8080
env:
- - name: QUARKUS_REST_CLIENT_ACCOUNT_URL
- value: "http://account-service"
- - name: QUARKUS_SMALLRYE_GRAPHQL_CLIENT_EATERY_URL
- value: "http://eatery-service/graphql"
- name: GATEWAY_JWT_SECRET
valueFrom:
secretKeyRef:
diff --git a/k8s/kafdrop.yaml b/k8s/base/kafdrop.yaml
similarity index 100%
rename from k8s/kafdrop.yaml
rename to k8s/base/kafdrop.yaml
diff --git a/k8s/kafka.yaml b/k8s/base/kafka.yaml
similarity index 100%
rename from k8s/kafka.yaml
rename to k8s/base/kafka.yaml
diff --git a/k8s/base/kustomization.yaml b/k8s/base/kustomization.yaml
new file mode 100644
index 0000000..76af622
--- /dev/null
+++ b/k8s/base/kustomization.yaml
@@ -0,0 +1,7 @@
+resources:
+ - account.yaml
+ - eatery.yaml
+ - gateway.yaml
+ - redis.yaml
+ - kafdrop.yaml
+ - kafka.yaml
diff --git a/k8s/redis.yaml b/k8s/base/redis.yaml
similarity index 100%
rename from k8s/redis.yaml
rename to k8s/base/redis.yaml
diff --git a/k8s/dev/kustomization.yaml b/k8s/dev/kustomization.yaml
new file mode 100644
index 0000000..e05beef
--- /dev/null
+++ b/k8s/dev/kustomization.yaml
@@ -0,0 +1,10 @@
+resources:
+ - ../base
+
+patches:
+ - target:
+ kind: Deployment
+ patch: |
+ - op: add
+ path: /spec/template/spec/containers/0/resources
+ value: {}
diff --git a/lib/pom.xml b/lib/pom.xml
index e871486..b66bfbe 100644
--- a/lib/pom.xml
+++ b/lib/pom.xml
@@ -24,7 +24,10 @@
io.quarkus
quarkus-security
- provided
+
+
+ io.quarkus
+ quarkus-vertx-http
com.fasterxml.jackson.core
diff --git a/lib/src/main/java/com/drinkool/lib/utils/BaseHeaderAuthentication.java b/lib/src/main/java/com/drinkool/lib/utils/BaseHeaderAuthentication.java
new file mode 100644
index 0000000..b0fbc70
--- /dev/null
+++ b/lib/src/main/java/com/drinkool/lib/utils/BaseHeaderAuthentication.java
@@ -0,0 +1,40 @@
+package com.drinkool.lib.utils;
+
+import com.drinkool.InternalValue;
+import io.quarkus.security.identity.IdentityProviderManager;
+import io.quarkus.security.identity.SecurityIdentity;
+import io.quarkus.security.runtime.QuarkusSecurityIdentity;
+import io.quarkus.vertx.http.runtime.security.ChallengeData;
+import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
+import io.smallrye.mutiny.Uni;
+import io.vertx.ext.web.RoutingContext;
+
+public abstract class BaseHeaderAuthentication
+ implements HttpAuthenticationMechanism
+{
+
+ @Override
+ public Uni authenticate(
+ RoutingContext context,
+ IdentityProviderManager identityProviderManager
+ ) {
+ String role = context.request().getHeader(InternalValue.role);
+ String userId = context.request().getHeader(InternalValue.userId);
+
+ if (role != null && !role.isEmpty()) {
+ return Uni.createFrom().item(
+ QuarkusSecurityIdentity.builder()
+ .setPrincipal(() -> userId)
+ .addRole(role)
+ .build()
+ );
+ }
+
+ return Uni.createFrom().nullItem();
+ }
+
+ @Override
+ public Uni getChallenge(RoutingContext context) {
+ return Uni.createFrom().nullItem();
+ }
+}
diff --git a/lib/src/main/java/com/drinkool/lib/utils/HeaderIdentityProvider.java b/lib/src/main/java/com/drinkool/lib/utils/HeaderIdentityProvider.java
deleted file mode 100644
index aee7b63..0000000
--- a/lib/src/main/java/com/drinkool/lib/utils/HeaderIdentityProvider.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.drinkool.lib.utils;
-
-import com.drinkool.InternalValue;
-import io.quarkus.security.identity.AuthenticationRequestContext;
-import io.quarkus.security.identity.IdentityProvider;
-import io.quarkus.security.identity.SecurityIdentity;
-import io.quarkus.security.identity.request.TrustedAuthenticationRequest;
-import io.quarkus.security.runtime.QuarkusSecurityIdentity;
-import io.smallrye.mutiny.Uni;
-import io.vertx.core.http.HttpServerRequest;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-
-@ApplicationScoped
-public class HeaderIdentityProvider
- implements IdentityProvider
-{
-
- @Inject
- HttpServerRequest httpServerRequest;
-
- @Override
- public Class getRequestType() {
- return TrustedAuthenticationRequest.class;
- }
-
- @Override
- public Uni authenticate(
- TrustedAuthenticationRequest request,
- AuthenticationRequestContext context
- ) {
- String role = httpServerRequest.getHeader(InternalValue.role);
-
- return Uni.createFrom().item(
- QuarkusSecurityIdentity.builder().addRole(role).build()
- );
- }
-}
diff --git a/scripts/publish.sh b/scripts/publish.sh
index e15d5d6..f2911e5 100755
--- a/scripts/publish.sh
+++ b/scripts/publish.sh
@@ -5,7 +5,22 @@ SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
cd "$SCRIPT_DIR/.."
-kubectl delete all --all -n drinkool-backend
-kubectl apply -f k8s -n drinkool-backend
+TARGET="k8s/base"
-cd "$CURRENT_DIR"
\ No newline at end of file
+for arg in "$@"; do
+ if [ "$arg" == "--dev" ]; then
+ TARGET="k8s/dev"
+ echo "🔧 Chế độ DEV: Đang chuẩn bị chạy Kustomize để dọn dẹp resources..."
+ break
+ fi
+done
+
+echo "🧹 Đang dọn dẹp namespace drinkool-backend..."
+kubectl delete all --all -n drinkool-backend
+
+echo "🚀 Đang triển khai bằng Kustomize (-k) từ: $TARGET"
+kubectl apply -k "$TARGET" -n drinkool-backend
+
+# Quay lại thư mục ban đầu
+cd "$CURRENT_DIR"
+echo "✅ Hoàn thành!"
\ No newline at end of file