Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 0 additions & 76 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,84 +9,8 @@ env:
AWS_REGION: us-east-1

jobs:

# =========================================================
# TESTS (JUnit + JaCoCo)
# =========================================================
test:
name: Test Application
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Cache Maven
uses: actions/cache@v4
with:
path: ~/.m2
key: maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
maven-

- name: Run tests with coverage
run: |
cd app
mvn clean verify

# =========================================================
# SONARCLOUD
# =========================================================
sonarqube:
name: SonarCloud
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: zulu

- name: Cache SonarCloud packages
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar

- name: Cache Maven packages
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2

- name: Build and analyze
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
cd app
mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=soat-tech-challenge-2025_lambda-identification-auth

# =========================================================
# DEPLOY LAMBDA (ORIGINAL, só com depends) ⚠️
# =========================================================
deploy:
runs-on: ubuntu-latest
needs: sonarqube
environment: Production
permissions:
id-token: write
Expand Down
92 changes: 43 additions & 49 deletions .github/workflows/destroy.yml
Original file line number Diff line number Diff line change
@@ -1,49 +1,43 @@
name: Destroy Lambda

on:
workflow_dispatch: # Dispara manualmente

env:
AWS_REGION: us-east-1

jobs:
destroy-lambda:
runs-on: ubuntu-latest
environment: Production

steps:
# 1️⃣ Checkout do repositório
- uses: actions/checkout@v3

# 2️⃣ Configurar credenciais AWS
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
aws-region: ${{ env.AWS_REGION }}

# 3️⃣ Verificar se a Lambda existe
- name: Check if Lambda exists
id: check_lambda
run: |
if aws lambda get-function --function-name lambda-identification-auth --region $AWS_REGION > /dev/null 2>&1; then
echo "Lambda exists"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "Lambda does not exist"
echo "exists=false" >> $GITHUB_OUTPUT
fi

# 4️⃣ Deletar Lambda se existir
- name: Delete Lambda
if: steps.check_lambda.outputs.exists == 'true'
run: |
echo "Deleting Lambda..."
aws lambda delete-function --function-name lambda-identification-auth --region $AWS_REGION
echo "✅ Lambda deleted successfully"

# 5️⃣ Mensagem caso a Lambda não exista
- name: Lambda not found
if: steps.check_lambda.outputs.exists == 'false'
run: echo "⚠️ Lambda not found, nothing to delete"
yaml
name: Destroy Infrastructure

on:
workflow_dispatch:

env:
AWS_REGION: us-east-1
TF_DIR: .

jobs:
destroy:
runs-on: ubuntu-latest
environment: Production

steps:
- uses: actions/checkout@v3

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
aws-region: ${{ env.AWS_REGION }}

- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: '1.5.7'

- name: Terraform Init
working-directory: ${{ env.TF_DIR }}
run: terraform init -input=false

- name: Terraform Plan Destroy (preview)
working-directory: ${{ env.TF_DIR }}
run: terraform plan -destroy -input=false -out=tfplan_destroy

- name: Terraform Destroy (apply)
working-directory: ${{ env.TF_DIR }}
run: terraform apply -input=false -auto-approve tfplan_destroy
env:
AWS_REGION: ${{ env.AWS_REGION }}
10 changes: 8 additions & 2 deletions api_gateway.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ resource "aws_apigatewayv2_integration" "lambda_backend" {
payload_format_version = "2.0"
}

resource "aws_apigatewayv2_route" "auth_token_route" {
resource "aws_apigatewayv2_route" "login" {
api_id = data.aws_apigatewayv2_api.tc_api.id
route_key = "POST /auth/token"
route_key = "POST /login"
target = "integrations/${aws_apigatewayv2_integration.lambda_backend.id}"
}

resource "aws_apigatewayv2_route" "me" {
api_id = data.aws_apigatewayv2_api.tc_api.id
route_key = "GET /me"
target = "integrations/${aws_apigatewayv2_integration.lambda_backend.id}"
}
100 changes: 83 additions & 17 deletions app/src/main/java/tech/buildrun/lambda/Handler.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package tech.buildrun.lambda;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.nio.charset.StandardCharsets;
Expand All @@ -25,39 +28,79 @@ public class Handler implements RequestHandler<APIGatewayProxyRequestEvent, APIG
public APIGatewayProxyResponseEvent handleRequest(
APIGatewayProxyRequestEvent request,
Context context) {
try {
if (request.getBody() == null || request.getBody().isBlank()) {
return response(400, Map.of("message", "Body obrigatório"));
}

Map<String, String> body =
mapper.readValue(request.getBody(), Map.class);

String username = body.get("user");
try {
String path = request.getPath();
String method = request.getHttpMethod();

if (username == null) {
return response(400, Map.of("message", "user obrigatórios"));
if ("/login".equals(path) && "POST".equalsIgnoreCase(method)) {
return login(request);
}

String token = gerarToken(username);
if ("/me".equals(path) && "GET".equalsIgnoreCase(method)) {
return me(request);
}

return response(200, Map.of("token", token));
return response(404, Map.of("message", "Rota não encontrada"));

} catch (Exception e) {
try {
return response(500, Map.of("message", "Erro ao gerar token"));
return response(500, Map.of("message", "Erro interno"));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}

private String gerarToken(String username) {
/* ================= LOGIN ================= */

if (JWT_SECRET == null || JWT_SECRET.length() < 32) {
throw new IllegalStateException("JWT_SECRET deve ter no mínimo 32 caracteres");
private APIGatewayProxyResponseEvent login(APIGatewayProxyRequestEvent request) throws Exception {

if (request.getBody() == null || request.getBody().isBlank()) {
return response(400, Map.of("message", "Body obrigatório"));
}

Map<String, String> body =
mapper.readValue(request.getBody(), Map.class);

String username = body.get("user");

if (username == null || username.isBlank()) {
return response(400, Map.of("message", "user é obrigatório"));
}

String token = gerarToken(username);

return response(200, Map.of("token", token));
}

/* ================= ME ================= */

private APIGatewayProxyResponseEvent me(APIGatewayProxyRequestEvent request)
throws Exception {

String authHeader = request.getHeaders().get("authorization");

if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return response(401, Map.of("message", "Token não informado"));
}

String token = authHeader.replace("Bearer ", "");

Claims claims = validarToken(token);

return response(200, Map.of(
"user", claims.getSubject(),
"role", claims.get("role")
));
}

/* ================= JWT ================= */

private String gerarToken(String username) {

validarSecret();

SecretKey key = Keys.hmacShaKeyFor(
JWT_SECRET.getBytes(StandardCharsets.UTF_8)
);
Expand All @@ -71,10 +114,33 @@ private String gerarToken(String username) {
.compact();
}

private Claims validarToken(String token) {

validarSecret();

SecretKey key = Keys.hmacShaKeyFor(
JWT_SECRET.getBytes(StandardCharsets.UTF_8)
);

return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
}

private void validarSecret() {
if (JWT_SECRET == null || JWT_SECRET.length() < 32) {
throw new IllegalStateException("JWT_SECRET deve ter no mínimo 32 caracteres");
}
}

/* ================= RESPONSE ================= */

private APIGatewayProxyResponseEvent response(int status, Object body) throws Exception {
return new APIGatewayProxyResponseEvent()
.withStatusCode(status)
.withHeaders(Map.of("Content-Type", "application/json"))
.withBody(mapper.writeValueAsString(body));
}
}
}
17 changes: 14 additions & 3 deletions lambda.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@ data "aws_iam_role" "lambda_exec_role" {
name = "tc-infra-id-lambda-exec-role"
}

data "aws_security_group" "id_lambda" {
name = "tc-id-lambda-sg"
resource "aws_security_group" "id_lambda" {
name = "tc-id-lambda-sg"
description = "Security group for Lambda ID function"
vpc_id = data.aws_vpc.tc_lambda_vpc.id

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = var.tags
}

resource "aws_lambda_function" "id_lambda" {
Expand All @@ -29,7 +40,7 @@ resource "aws_lambda_function" "id_lambda" {
}
vpc_config {
subnet_ids = data.aws_subnets.tc_lambda_subnets.ids
security_group_ids = [data.aws_security_group.id_lambda.id]
security_group_ids = [aws_security_group.id_lambda.id]
}

tags = var.tags
Expand Down