Public Access
Login should work now, tests are missing
This commit is contained in:
1 parent
eecda99d22
commit
132bb6bb64
12 files changed
+377
-16
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>
|
||||
<artifactId>flyway-database-postgresql</artifactId>
|
||||
</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>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-docker-compose</artifactId>
|
||||
@@ -84,15 +97,6 @@
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${basedir}/src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>**/application*.yml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
||||
@@ -5,9 +5,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class AuthServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AuthServiceApplication.class, args);
|
||||
}
|
||||
|
||||
static void main(String[] 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:
|
||||
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:
|
||||
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