Public Access
Login should work now, tests are missing
This commit is contained in:
1 parent
eecda99d22
commit
132bb6bb64
12 files changed
+378
-17
No files matched your search
@@ -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
@@ -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"]
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
Reference in new issue
Block a user