Login should work now, tests are missing
Build, Test & Push Auth Service / quality-check (push) Failing after 37s
Build, Test & Push Auth Service / docker (push) Has been skipped

This commit is contained in:
lobo committed 2026-03-31 19:48:23 +02:00
1 parent 8dd17f84f6
commit 7e8a04d83c
13 files changed
+391 -16

No files matched your search

+71
View File
@@ -0,0 +1,71 @@
name: Build, Test & Push Auth Service
# Trigger the workflow
on:
push:
branches:
- main
jobs:
# ------------------------------------------------------------------
# JOB 1: Code integrity and quality gates (CI)
# ------------------------------------------------------------------
quality-check:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Java 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
# Automatically cache Maven dependencies to speed up builds
cache: 'maven'
- name: Build and Test with Maven
# Run all tests (including Spring Boot context loads) to ensure stability
run: ./mvnw clean verify
# ------------------------------------------------------------------
# JOB 2: Docker Build & Push (CD)
# Runs only if 'quality-check' is successful and we are on branch main
# ------------------------------------------------------------------
docker:
needs: quality-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Prepare tag
id: prep
shell: bash
run: |
# Sanitize branch name for valid Docker tags
BRANCH="${GITHUB_REF_NAME}"
BRANCH="${BRANCH//\//-}"
BRANCH="$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]')"
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
echo "sha=${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: git.andreas-dahm.eu
# Use your Gitea Secrets here
username: ${{ github.repository_owner }}
password: ${{ secrets.TOKEN_GITEA }}
- name: Build & Push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
git.andreas-dahm.eu/lobo_corp/tbp-auth-service:${{ steps.prep.outputs.branch }}
git.andreas-dahm.eu/lobo_corp/tbp-auth-service:${{ steps.prep.outputs.branch }}-${{ steps.prep.outputs.sha }}
git.andreas-dahm.eu/lobo_corp/tbp-auth-service:latest
+35
View File
@@ -0,0 +1,35 @@
# ------------------------------------------------------------------
# STAGE 1: Build the application using Maven Wrapper
# ------------------------------------------------------------------
FROM eclipse-temurin:25-jdk-alpine AS builder
WORKDIR /app
# Copy the Maven wrapper and pom.xml first to cache dependencies
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
# Make the wrapper executable (just in case) and download dependencies
RUN chmod +x mvnw
RUN ./mvnw dependency:go-offline
# Copy the actual source code and build the application (skip tests in Docker,
# as they will be run by the Gitea Action before the build)
COPY src ./src
RUN ./mvnw clean package -DskipTests
# ------------------------------------------------------------------
# STAGE 2: Create the minimal runtime image
# ------------------------------------------------------------------
FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
# Expose the port your Spring Boot app runs on
EXPOSE 8080
# Copy the compiled jar file from the builder stage
COPY --from=builder /app/target/*.jar app.jar
# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]
+13 -9
View File
@@ -40,6 +40,19 @@
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId> <artifactId>flyway-database-postgresql</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId> <artifactId>spring-boot-docker-compose</artifactId>
@@ -84,15 +97,6 @@
</dependencies> </dependencies>
<build> <build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application*.yml</include>
</includes>
</resource>
</resources>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@@ -5,9 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class AuthServiceApplication { public class AuthServiceApplication {
static void main(String[] args) {
public static void main(String[] args) { SpringApplication.run(AuthServiceApplication.class, args);
SpringApplication.run(AuthServiceApplication.class, args); }
}
} }
@@ -0,0 +1,24 @@
package auth_service.configuration;
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration for application observability and metrics.
*/
@Configuration
public class ObservabilityConfig {
/**
* Registers the TimedAspect bean to enable @Timed annotations.
* * @param registry The Micrometer MeterRegistry
*
* @return A new TimedAspect instance
*/
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
@@ -0,0 +1,85 @@
package auth_service.configuration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
/**
* Global security configuration for the Authentication Service.
* Implements whitelist routing and environment-specific CORS.
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
/**
* The Whitelist: Endpoints that do NOT require authentication.
*/
private static final String[] WHITE_LIST_URLS = {
"/api/auth/**", // Allow login, registration, etc.
"/actuator/health", // Allow health checks (e.g., for Docker/Traefik)
"/actuator/prometheus", // Allow Prometheus to scrape metrics
"/error" // Default Spring Boot error page routing
};
/**
* Injects the allowed CORS origins from the active application.yml profile.
*/
@Value("${app.cors.allowed-origins}")
private String[] allowedOrigins;
/**
* Defines the password encoder used for hashing passwords.
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Defines the global CORS configuration.
* Using a dedicated Bean ensures it's properly registered in the filter chain.
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Use OriginPatterns instead of AllowedOrigins to safely support "*" wildcards
configuration.setAllowedOriginPatterns(Arrays.asList(allowedOrigins));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(false); // Set to true later if we use cookies
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// Apply this CORS config to all endpoints
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
http
.csrf(AbstractHttpConfigurer::disable)
// Tell Spring Security to use the CorsConfigurationSource bean defined above
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(WHITE_LIST_URLS).permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
@@ -0,0 +1,46 @@
package auth_service.domain.login.controller;
import auth_service.domain.login.controller.dtos.LoginRequest;
import auth_service.domain.login.controller.dtos.LoginResponse;
import auth_service.domain.login.repository.UserRepository;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Controller handling authentication requests.
*/
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class LoginController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
/**
* Authenticates a user based on email and password.
* * @param request The login credentials
*
* @return HTTP 200 on success, HTTP 401 on failure
*/
@PostMapping("/login")
@Timed(value = "auth.login.requests.time", description = "Time taken to process login requests")
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
return userRepository.findByEmail(request.email())
.filter(user -> passwordEncoder.matches(request.password(), user.getPassword()))
.map(user -> ResponseEntity.ok(
new LoginResponse("Login successful", user.getEmail())
))
.orElseGet(() -> ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
new LoginResponse("Invalid credentials", null)
));
}
}
@@ -0,0 +1,7 @@
package auth_service.domain.login.controller.dtos;
/**
* DTO (Data Transfer Object) representing the incoming login request.
*/
public record LoginRequest(String email, String password) {
}
@@ -0,0 +1,8 @@
package auth_service.domain.login.controller.dtos;
/**
* DTO representing the outgoing login response.
* The email will be null if the login fails.
*/
public record LoginResponse(String message, String email) {
}
@@ -0,0 +1,30 @@
package auth_service.domain.login.model;
import jakarta.persistence.*;
import lombok.*;
/**
* Represents a user entity mapped to the 'users' table in PostgreSQL.
*/
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private boolean enabled;
}
@@ -0,0 +1,22 @@
package auth_service.domain.login.repository;
import auth_service.domain.login.model.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* Repository for database operations regarding the User entity.
*/
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
/**
* Finds a user by their email address.
* * @param email The email to search for
*
* @return An Optional containing the user if found
*/
Optional<UserEntity> findByEmail(String email);
}
+14
View File
@@ -0,0 +1,14 @@
# ─────────────────────────────────────────────
# DEVELOPMENT CONFIGURATION
# ─────────────────────────────────────────────
# Overrides properties from application.yml when the 'dev' profile is active
app:
cors:
# Local development: allow all origins to prevent CORS issues
allowed-origins: "*"
# Optional: Make SQL logs visible for debugging
spring:
jpa:
show-sql: true
+33 -2
View File
@@ -1,5 +1,36 @@
# ─────────────────────────────────────────────
# BASE CONFIGURATION (Production Defaults)
# ─────────────────────────────────────────────
spring: spring:
application: application:
name: auth-service name: tbp-auth-service
profiles:
active: dev # Default profile for local startup
flyway:
# Force Flyway to be enabled
enabled: true
# Create the schema history table if it does not exist
baseline-on-migrate: true
# Explicitly state where to look for scripts
locations: classpath:db/migration
# Actuator & Micrometer Settings
management:
endpoints:
web:
exposure:
# Expose health and metrics to the web
include: health, prometheus, metrics
endpoint:
health:
show-details: always
prometheus:
metrics:
export:
enabled: true
# Custom properties for CORS
app: app:
version: '@project.version@' cors:
# Production default: strictly allow only the frontend domain
allowed-origins: "https://tbp.andreas-dahm.eu"