Skip to content

Commit ee224b5

Browse files
authored
Merge pull request #5 from zerodaycode/develop
Develop to Main
2 parents 9f98047 + 74b0472 commit ee224b5

File tree

18 files changed

+606
-18
lines changed

18 files changed

+606
-18
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Handle Deploy Commands in PR Comments
2+
3+
on:
4+
issue_comment:
5+
types:
6+
- created
7+
8+
jobs:
9+
handle-slash-command:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
# Step 1: Check if the command is `/deploy-pre`
14+
- name: Check for `/deploy-pre` Command
15+
id: check_command
16+
uses: peter-evans/slash-command-action@v4
17+
with:
18+
token: ${{ secrets.GITHUB_TOKEN }}
19+
command: /deploy-pre
20+
21+
# Step 2: Extract Branch Reference from PR
22+
- name: Get Pull Request Details
23+
if: steps.check_command.outputs.command == 'true'
24+
id: pr_details
25+
uses: actions/github-script@v6
26+
with:
27+
script: |
28+
const pr = context.payload.issue.pull_request;
29+
if (!pr) {
30+
throw new Error("The /deploy-pre command must be used in a pull request comment.");
31+
}
32+
return pr.head.ref;
33+
result-encoding: string
34+
35+
# Step 3: Trigger Deploy Workflow for the PR Branch
36+
- name: Trigger Deploy Workflow
37+
if: steps.check_command.outputs.command == 'true'
38+
uses: actions/github-script@v6
39+
with:
40+
script: |
41+
github.rest.actions.createWorkflowDispatch({
42+
owner: context.repo.owner,
43+
repo: context.repo.repo,
44+
workflow_id: "deploy-pre.yml", # Replace with your workflow filename
45+
ref: "${{ steps.pr_details.outputs.result }}"
46+
})

.github/workflows/deploy-pre.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Deploy API Gateway to DigitalOcean (PRE)
2+
3+
on:
4+
workflow_dispatch: # Allows manual triggering
5+
6+
jobs:
7+
deploy:
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Checkout Code
12+
uses: actions/checkout@v3
13+
14+
- name: Set up Docker
15+
uses: docker/setup-buildx-action@v2
16+
17+
- name: Build Docker Image
18+
run: |
19+
docker build -t api-gateway:latest .
20+
21+
- name: Save Docker Image
22+
run: |
23+
docker save api-gateway:latest | gzip > api-gateway.tar.gz
24+
25+
- name: Copy Docker Image and Compose File to Droplet
26+
uses: appleboy/[email protected]
27+
with:
28+
host: ${{ secrets.SSH_HOST }}
29+
username: ${{ secrets.SSH_USER }}
30+
key: ${{ secrets.SSH_KEY }}
31+
source: |
32+
./api-gateway.tar.gz
33+
./docker-compose.yml
34+
target: /opt/api-gateway/
35+
36+
- name: Deploy with Docker Compose
37+
uses: appleboy/[email protected]
38+
with:
39+
host: ${{ secrets.SSH_HOST }}
40+
username: ${{ secrets.SSH_USERNAME }}
41+
key: ${{ secrets.SSH_KEY }}
42+
script: |
43+
cd /opt/api-gateway
44+
45+
# Stop and remove old services
46+
docker-compose down || true
47+
48+
# Load the new Docker image
49+
gunzip -c api-gateway.tar.gz | docker load
50+
51+
# Start the service with the new image
52+
docker-compose up -d

docker/Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Stage 1: Build the application
2+
FROM maven:3.9.9-ibm-semeru-23-jammy AS build
3+
WORKDIR /app
4+
5+
COPY ../mvnw ./
6+
COPY ../.mvn .mvn
7+
COPY ../pom.xml ./
8+
COPY ../src/ ./src
9+
10+
RUN chmod +x mvnw && \
11+
# ./mvnw clean install
12+
./mvnw install -DskipTests
13+
# Package the application (this creates the JAR file)
14+
RUN mvn clean package -DskipTests
15+
16+
# Stage 2: Run the application
17+
FROM openjdk:23-jdk-slim
18+
WORKDIR /app
19+
20+
# Copy the built JAR from the first stage
21+
COPY --from=build /app/target/summonerssync.apigateway-0.0.1-SNAPSHOT.jar app.jar
22+
23+
# Expose the port the app runs on
24+
EXPOSE 8080
25+
26+
# Run Spring Boot app
27+
CMD ["java", "-jar", "app.jar"]

docker/docker-compose.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
api-gateway:
3+
container_name: api-gateway
4+
build:
5+
context: ..
6+
dockerfile: docker/Dockerfile
7+
ports:
8+
- "8080:8080"
9+
environment:
10+
SPRING_DATASOURCE_URL: jdbc:postgresql://database:5432/gatewaydb
11+
SPRING_DATASOURCE_USERNAME: admin
12+
SPRING_DATASOURCE_PASSWORD: admin
13+
SPRING_PROFILES_ACTIVE: dev
14+

pom.xml

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
<groupId>org.springframework.cloud</groupId>
4848
<artifactId>spring-cloud-starter-gateway</artifactId>
4949
</dependency>
50-
5150
<dependency>
5251
<groupId>org.springframework.boot</groupId>
5352
<artifactId>spring-boot-devtools</artifactId>
@@ -59,6 +58,25 @@
5958
<artifactId>lombok</artifactId>
6059
<optional>true</optional>
6160
</dependency>
61+
<dependency>
62+
<groupId>com.auth0</groupId>
63+
<artifactId>java-jwt</artifactId>
64+
<version>4.4.0</version>
65+
</dependency>
66+
<!-- <dependency>-->
67+
<!-- <groupId>io.jsonwebtoken</groupId>-->
68+
<!-- <artifactId>jjwt-api</artifactId>-->
69+
<!-- <version>0.11.5</version>-->
70+
<!-- <scope>runtime</scope>-->
71+
<!-- </dependency>-->
72+
<!-- <dependency>-->
73+
<!-- <groupId>io.jsonwebtoken</groupId>-->
74+
<!-- <artifactId>jjwt-impl</artifactId>-->
75+
<!-- <version>0.11.5</version>-->
76+
<!-- <scope>runtime</scope>-->
77+
<!-- </dependency>-->
78+
79+
<!-- Testing -->
6280
<dependency>
6381
<groupId>org.springframework.boot</groupId>
6482
<artifactId>spring-boot-starter-test</artifactId>
@@ -74,6 +92,17 @@
7492
<artifactId>spring-security-test</artifactId>
7593
<scope>test</scope>
7694
</dependency>
95+
<dependency>
96+
<groupId>org.springframework.cloud</groupId>
97+
<artifactId>spring-cloud-contract-wiremock</artifactId>
98+
<version>4.1.2</version>
99+
<scope>test</scope>
100+
</dependency>
101+
<dependency>
102+
<groupId>jakarta.servlet</groupId>
103+
<artifactId>jakarta.servlet-api</artifactId>
104+
<scope>provided</scope>
105+
</dependency>
77106
</dependencies>
78107
<dependencyManagement>
79108
<dependencies>

src/main/java/com/zerodaycode/summonerssync/apigateway/Application.java renamed to src/main/java/com/zerodaycode/summonerssync/apigateway/SummonersSyncApiGateway.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55

66
@SpringBootApplication
7-
public class Application {
8-
7+
public class SummonersSyncApiGateway {
98
public static void main(String[] args) {
10-
SpringApplication.run(Application.class, args);
9+
SpringApplication.run(SummonersSyncApiGateway.class, args);
1110
}
12-
1311
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//package com.zerodaycode.summonerssync.apigateway.config;
2+
//
3+
//import org.springframework.cloud.gateway.filter.GatewayFilter;
4+
//import org.springframework.cloud.gateway.filter.GatewayFilterChain;
5+
//import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
6+
//import org.springframework.context.annotation.Configuration;
7+
//import org.springframework.http.HttpHeaders;
8+
//import org.springframework.http.HttpStatus;
9+
//import org.springframework.http.server.reactive.ServerHttpRequest;
10+
//import org.springframework.http.server.reactive.ServerHttpResponse;
11+
//import org.springframework.web.server.ServerWebExchange;
12+
//import reactor.core.publisher.Mono;
13+
//
14+
//@Configuration
15+
//public class AuthFilter implements GatewayFilter {
16+
//
17+
// @Override
18+
// public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
19+
// ServerHttpRequest request = exchange.getRequest();
20+
//// if (routerValidator.isSecured.test(request)) {
21+
//// if (this.isAuthMissing(request))
22+
//// return this.onError(exchange, "Authorization header is missing in request", HttpStatus.UNAUTHORIZED);
23+
//// final String token = this.getAuthHeader(request);
24+
//// if (jwtUtil.isInvalid(token))
25+
//// return this.onError(exchange, "Authorization header is invalid", HttpStatus.UNAUTHORIZED);
26+
//// this.populateRequestWithHeaders(exchange, token);
27+
//// }
28+
// return chain.filter(exchange);
29+
// }
30+
// /*PRIVATE*/
31+
// private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
32+
// ServerHttpResponse response = exchange.getResponse();
33+
// response.setStatusCode(httpStatus);
34+
// return response.setComplete();
35+
// }
36+
// private String getAuthHeader(ServerHttpRequest request) {
37+
// return request.getHeaders().getOrEmpty("Authorization").getFirst();
38+
// }
39+
// private boolean isAuthMissing(ServerHttpRequest request) {
40+
// return !request.getHeaders().containsKey("Authorization");
41+
// }
42+
// private void populateRequestWithHeaders(ServerWebExchange exchange, String token) {
43+
// Claims claims = jwtUtil.getAllClaimsFromToken(token);
44+
// exchange.getRequest().mutate()
45+
// .header("id", String.valueOf(claims.get("id")))
46+
// .header("role", String.valueOf(claims.get("role")))
47+
// .build();
48+
// }
49+
//}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.zerodaycode.summonerssync.apigateway.config;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.http.HttpStatus;
5+
import org.springframework.http.server.reactive.ServerHttpResponse;
6+
import org.springframework.security.core.AuthenticationException;
7+
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
8+
import org.springframework.web.server.ServerWebExchange;
9+
import reactor.core.publisher.Mono;
10+
11+
import java.time.LocalDateTime;
12+
13+
@Configuration
14+
public class CustomAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
15+
16+
@Override
17+
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException exception) {
18+
ServerHttpResponse response = exchange.getResponse();
19+
response.setStatusCode(HttpStatus.UNAUTHORIZED);
20+
21+
// Custom JSON error message
22+
ErrorResponse errorResponse = new ErrorResponse(
23+
HttpStatus.UNAUTHORIZED.value(),
24+
"Unauthorized Access",
25+
LocalDateTime.now()
26+
);
27+
28+
return response.writeWith(Mono.just(response.bufferFactory().wrap(errorResponse.toJson().getBytes())));
29+
}
30+
}
31+
32+
class ErrorResponse {
33+
private final int status;
34+
private final String message;
35+
private final LocalDateTime timestamp;
36+
37+
public ErrorResponse(int status, String message, LocalDateTime timestamp) {
38+
this.status = status;
39+
this.message = message;
40+
this.timestamp = timestamp;
41+
}
42+
43+
public String toJson() {
44+
return String.format("{\"status\":%d,\"message\":\"%s\",\"timestamp\":\"%s\"}", status, message, timestamp);
45+
}
46+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.zerodaycode.summonerssync.apigateway.config;
2+
3+
import com.zerodaycode.summonerssync.apigateway.security.SecurityContextRepository;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
7+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
8+
import org.springframework.security.config.web.server.ServerHttpSecurity;
9+
import org.springframework.security.web.server.SecurityWebFilterChain;
10+
11+
@Configuration
12+
@EnableWebFluxSecurity
13+
@EnableReactiveMethodSecurity
14+
public class SecurityConfig {
15+
private final SecurityContextRepository securityContextRepository;
16+
17+
public SecurityConfig(SecurityContextRepository securityContextRepository) {
18+
this.securityContextRepository = securityContextRepository;
19+
}
20+
21+
@Bean
22+
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
23+
http.csrf(ServerHttpSecurity.CsrfSpec::disable)
24+
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
25+
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable);
26+
27+
http.securityContextRepository(securityContextRepository);
28+
http.authorizeExchange(authorize -> authorize
29+
.pathMatchers("/auth/**")
30+
.permitAll()
31+
.anyExchange()
32+
.authenticated()
33+
);
34+
http.exceptionHandling(exception -> exception.authenticationEntryPoint(new CustomAuthenticationEntryPoint()));
35+
return http.build();
36+
}
37+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.zerodaycode.summonerssync.apigateway.security;
2+
3+
import com.auth0.jwt.interfaces.DecodedJWT;
4+
import org.springframework.security.authentication.ReactiveAuthenticationManager;
5+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
6+
import org.springframework.security.core.Authentication;
7+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
8+
import org.springframework.stereotype.Component;
9+
import reactor.core.publisher.Mono;
10+
11+
import java.util.Collection;
12+
import java.util.List;
13+
14+
@Component
15+
public class AuthenticationManager implements ReactiveAuthenticationManager {
16+
private final JwtUtil jwtUtil;
17+
18+
public AuthenticationManager(JwtUtil jwtUtil) {
19+
this.jwtUtil = jwtUtil;
20+
}
21+
22+
@Override
23+
public Mono<Authentication> authenticate(Authentication authentication) {
24+
25+
return Mono.just(authentication.getCredentials().toString())
26+
.flatMap(this::verifyToken); // we should split this taking the commented code for now
27+
// .flatMap(this::getAuthentication);
28+
}
29+
30+
// private Mono<Authentication> getAuthentication(AuthenticationTokenData appUser) {
31+
// return Mono.just(
32+
// new UsernamePasswordAuthenticationToken(getGrantedAuthorities(), appUser,
33+
// null)
34+
// );
35+
// }
36+
37+
private Collection<SimpleGrantedAuthority> getGrantedAuthorities() {
38+
return List.of(new SimpleGrantedAuthority("ROLE_ADMIN"));
39+
}
40+
41+
private Mono<Authentication> verifyToken(String token) {
42+
return jwtUtil.validateToken(token)
43+
// .filter(decodedJWT -> decodedJWT.getAudience() != null && !decodedJWT.getAudience().isEmpty())
44+
.flatMap(tokenData -> getUser(tokenData, token));
45+
}
46+
47+
private Mono<? extends Authentication> getUser(DecodedJWT tokenData, String token) {
48+
var c = new SimpleGrantedAuthority(tokenData.getClaim("role").toString());
49+
var a = new UsernamePasswordAuthenticationToken(token, null, List.of(c));
50+
return Mono.just(a);
51+
}
52+
}

0 commit comments

Comments
 (0)