diff --git a/.docker/nginx/default.conf.template b/.docker/nginx/default.conf.template
new file mode 100644
index 0000000..4387733
--- /dev/null
+++ b/.docker/nginx/default.conf.template
@@ -0,0 +1,59 @@
+server {
+ listen 80;
+
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For &proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto &scheme;
+ proxy_set_header Host $http_host;
+
+ proxy_redirect off;
+
+ location / {
+ proxy_pass ${FRONTEND_URI};
+
+ location /login {
+ proxy_pass ${LOGIN_SERVICE_URI};
+ }
+
+ location /logout {
+ proxy_pass ${LOGIN_SERVICE_URI};
+ }
+
+ location /oauth2 {
+ proxy_pass ${LOGIN_SERVICE_URI};
+ }
+
+ location /refresh-token {
+ proxy_pass ${LOGIN_SERVICE_URI};
+ }
+ }
+
+ location /api {
+ rewrite ^/api/(.*) /$1 break;
+
+ proxy_pass ${CORE_API_URI};
+
+ proxy_set_header Authorization "Bearer $cookie_pefi_token";
+ proxy_pass_header Authorization;
+
+ location /api/enums/widget-types {
+ rewrite ^/api/(.*) /$1 break;
+ proxy_pass ${WIDGETS_API_URI};
+ }
+
+ location /api/enums/supported-conversions {
+ rewrite ^/api/(.*) /$1 break;
+ proxy_pass ${STATEMENTS_API_URI};
+ }
+
+ location /api/widgets {
+ rewrite ^/api/(.*) /$1 break;
+ proxy_pass ${WIDGETS_API_URI};
+ }
+
+ location /api/statements {
+ rewrite ^/api/(.*) /$1 break;
+ proxy_pass ${STATEMENTS_API_URI};
+ }
+ }
+}
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 8e8716e..bdf3a64 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -14,6 +14,7 @@
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 3f18e8a..910aea5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,74 +1,87 @@
version: '3.4'
services:
- api-gateway:
- build: ./pefi-api-gateway
+ nginx:
+ image: nginx:stable-alpine
+ volumes:
+ - ./.docker/nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro
ports:
- - '8080:8080'
+ - '8080:80'
environment:
- PROFILE: development
- AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID}
- AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_ID}
- AUTHENTIK_ISSUER_URL: https://auth.mvvasilev.dev/application/o/${AUTHENTIK_APP_NAME}/
- AUTHENTIK_BACK_CHANNEL_LOGOUT_URL: https://auth.mvvasilev.dev/application/o/${AUTHENTIK_APP_NAME}/end-session/
- GATEWAY_URI: http://localhost:8080
CORE_API_URI: http://core-api:8081
STATEMENTS_API_URI: http://statements-api:8081
WIDGETS_API_URI: http://widgets-api:8081
FRONTEND_URI: http://frontend:5173
- REDIS_HOST: redis
- REDIS_PORT: 6379
- SSL_ENABLED: true
- SSL_KEY_STORE_TYPE: PKCS12
- SSL_KEY_STORE: classpath:keystore/local.p12
- SSL_KEY_STORE_PASSWORD: asdf1234
- SSL_KEY_ALIAS: local
+ LOGIN_SERVICE_URI: http://login-service:8081
+ depends_on:
+ - core-api
+ - statements-api
+ - widgets-api
+ - frontend
+ - login-service
frontend:
build: ./pefi-frontend
ports:
- '5173:5173'
+ login-service:
+ build: ./pefi-login-service
+ ports:
+ - '8084:8081'
+ environment:
+ SERVER_PORT: 8081
+ FRONTEND_URI: http://localhost:8080
+ AUTHENTIK_ISSUER_URI: ${AUTHENTIK_ISSUER_URL}
+ AUTHENTIK_BACK_CHANNEL_LOGOUT_URL: ${AUTHENTIK_BACK_CHANNEL_LOGOUT_URL}
+ AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID}
+ AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET}
+
core-api:
build: ./pefi-core-api
ports:
- '8081:8081'
environment:
+ SERVER_PORT: 8081
PROFILE: 'development'
- AUTHENTIK_ISSUER_URL: 'https://auth.mvvasilev.dev/application/o/${AUTHENTIK_APP_NAME}/'
+ AUTHENTIK_ISSUER_URI: ${AUTHENTIK_ISSUER_URL}
DATASOURCE_URL: jdbc:postgresql://database:5432/${POSTGRES_DB}
DATASOURCE_USER: ${POSTGRES_USER}
DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
- KAFKA_SERVERS: 'kafka-broker:9092'
+ KAFKA_SERVERS: localhost:9092
+ depends_on:
+ - kafka1
+ - database
statements-api:
build: ./pefi-statements-api
ports:
- '8082:8081'
environment:
+ SERVER_PORT: 8081
PROFILE: 'development'
- AUTHENTIK_ISSUER_URL: 'https://auth.mvvasilev.dev/application/o/${AUTHENTIK_APP_NAME}/'
+ AUTHENTIK_ISSUER_URI: ${AUTHENTIK_ISSUER_URL}
DATASOURCE_URL: jdbc:postgresql://database:5432/${POSTGRES_DB}
DATASOURCE_USER: ${POSTGRES_USER}
DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
- KAFKA_SERVERS: 'kafka-broker:9092'
+ KAFKA_SERVERS: localhost:9092
+ depends_on:
+ - kafka1
+ - database
widgets-api:
build: ./pefi-widgets-api
ports:
- '8083:8081'
environment:
+ SERVER_PORT: 8081
PROFILE: 'development'
- AUTHENTIK_ISSUER_URL: 'https://auth.mvvasilev.dev/application/o/${AUTHENTIK_APP_NAME}/'
+ AUTHENTIK_ISSUER_URI: ${AUTHENTIK_ISSUER_URL}
DATASOURCE_URL: jdbc:postgresql://database:5432/${POSTGRES_DB}
DATASOURCE_USER: ${POSTGRES_USER}
DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
-
- redis:
- image: redis/redis-stack:latest
- ports:
- - '6379:6379'
- - '6380:8001'
+ depends_on:
+ - database
database:
image: postgres:16.1-alpine
@@ -79,34 +92,38 @@ services:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
- kafka-broker:
- image: confluentinc/cp-kafka:7.5.3
- hostname: broker
- container_name: broker
- depends_on:
- - zookeeper
- ports:
- - "29092:29092"
- - "9092:9092"
- - "9101:9101"
- environment:
- KAFKA_BROKER_ID: 1
- KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
- KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
- KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
- KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
- KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
- KAFKA_JMX_PORT: 9101
- KAFKA_JMX_HOSTNAME: localhost
-
- zookeeper:
- image: confluentinc/cp-zookeeper:7.5.3
- hostname: zookeeper
- container_name: zookeeper
+ zoo1:
+ image: confluentinc/cp-zookeeper:7.3.2
+ hostname: zoo1
+ container_name: zoo1
ports:
- "2181:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
- ZOOKEEPER_TICK_TIME: 2000
\ No newline at end of file
+ ZOOKEEPER_SERVER_ID: 1
+ ZOOKEEPER_SERVERS: zoo1:2888:3888
+
+ kafka1:
+ image: confluentinc/cp-kafka:7.3.2
+ hostname: kafka1
+ container_name: kafka1
+ ports:
+ - "9092:9092"
+ - "29092:29092"
+ - "9999:9999"
+ environment:
+ KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:19092,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092,DOCKER://host.docker.internal:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
+ KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
+ KAFKA_BROKER_ID: 1
+ KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO"
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_JMX_PORT: 9999
+ KAFKA_JMX_HOSTNAME: ${DOCKER_HOST_IP:-127.0.0.1}
+ KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer
+ KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true"
+ depends_on:
+ - zoo1
\ No newline at end of file
diff --git a/pefi-common/build.gradle b/pefi-common/build.gradle
index b2f3404..4fe05af 100644
--- a/pefi-common/build.gradle
+++ b/pefi-common/build.gradle
@@ -10,6 +10,8 @@ repositories {
}
dependencies {
+ compileOnly 'jakarta.servlet:jakarta.servlet-api:6.0.0'
+
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
implementation 'org.springframework:spring-web:6.1.3'
implementation 'org.springframework.data:spring-data-jpa:3.2.0'
diff --git a/pefi-common/src/main/java/dev/mvvasilev/common/configuration/CommonControllerConfiguration.java b/pefi-common/src/main/java/dev/mvvasilev/common/configuration/CommonControllerConfiguration.java
index 1ef63f8..8439439 100644
--- a/pefi-common/src/main/java/dev/mvvasilev/common/configuration/CommonControllerConfiguration.java
+++ b/pefi-common/src/main/java/dev/mvvasilev/common/configuration/CommonControllerConfiguration.java
@@ -6,17 +6,29 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.filter.CommonsRequestLoggingFilter;
import java.util.List;
@Configuration
public class CommonControllerConfiguration {
+ @Bean
+ public CommonsRequestLoggingFilter requestLoggingFilter() {
+ CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
+ loggingFilter.setIncludeClientInfo(true);
+ loggingFilter.setIncludeQueryString(true);
+ loggingFilter.setIncludePayload(true);
+ loggingFilter.setMaxPayloadLength(64000);
+ return loggingFilter;
+ }
+
@RestControllerAdvice(basePackages = {"dev.mvvasilev"})
public static class APIResponseAdvice {
diff --git a/pefi-common/src/main/java/dev/mvvasilev/common/configuration/CommonSecurityConfiguration.java b/pefi-common/src/main/java/dev/mvvasilev/common/configuration/CommonSecurityConfiguration.java
index 304c8bd..3c57cb6 100644
--- a/pefi-common/src/main/java/dev/mvvasilev/common/configuration/CommonSecurityConfiguration.java
+++ b/pefi-common/src/main/java/dev/mvvasilev/common/configuration/CommonSecurityConfiguration.java
@@ -1,59 +1,43 @@
package dev.mvvasilev.common.configuration;
-import org.springframework.beans.factory.annotation.Value;
+import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
-import org.springframework.security.oauth2.jwt.JwtDecoders;
-import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
-import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.transaction.annotation.EnableTransactionManagement;
-import org.springframework.web.filter.CommonsRequestLoggingFilter;
+
+import java.io.IOException;
@Configuration
-@EnableTransactionManagement
public class CommonSecurityConfiguration {
- @Value("${jwt.issuer-url}")
- public String jwtIssuerUrl;
-
private static final String[] WHITELISTED_URLS = {
"/v3/api-docs/**",
- "/swagger-ui/**",
"/v2/api-docs/**",
+ "/swagger-ui/**",
+ "/swagger-ui.html",
"/swagger-resources/**"
};
@Bean
- public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, BearerTokenResolver bearerTokenResolver) throws Exception {
+ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
- .cors(c -> c.disable()) // won't be needing cors, as the API will be completely hidden behind an api gateway
- .csrf(c -> c.disable())
+ .cors(AbstractHttpConfigurer::disable)
+ .csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(c -> {
- c.requestMatchers(WHITELISTED_URLS).permitAll();
+ c.requestMatchers(WHITELISTED_URLS).anonymous();
c.anyRequest().authenticated();
})
- .oauth2ResourceServer(c -> {
- c.jwt(Customizer.withDefaults());
- c.bearerTokenResolver(bearerTokenResolver);
+ .oauth2ResourceServer(oauth2 -> {
+ oauth2.jwt(Customizer.withDefaults());
})
+ .exceptionHandling(e -> e.accessDeniedHandler((req, res, ex) -> res.setStatus(HttpServletResponse.SC_UNAUTHORIZED)))
.build();
}
-
- @Bean
- public JwtDecoder jwtDecoder() {
- return JwtDecoders.fromIssuerLocation(jwtIssuerUrl);
- }
-
- @Bean
- public BearerTokenResolver bearerTokenResolver() {
- DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
- bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.AUTHORIZATION);
- return bearerTokenResolver;
- }
}
\ No newline at end of file
diff --git a/pefi-core-api/src/main/java/dev/mvvasilev/finances/PefiCoreAPI.java b/pefi-core-api/src/main/java/dev/mvvasilev/finances/PefiCoreAPI.java
index 2361621..acf7115 100644
--- a/pefi-core-api/src/main/java/dev/mvvasilev/finances/PefiCoreAPI.java
+++ b/pefi-core-api/src/main/java/dev/mvvasilev/finances/PefiCoreAPI.java
@@ -5,10 +5,15 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-@SpringBootApplication(scanBasePackageClasses = { AuthorizationService.class })
-@EnableJpaRepositories("dev.mvvasilev.finances.*")
+@EnableWebMvc
@EntityScan("dev.mvvasilev.finances.*")
+@EnableJpaRepositories("dev.mvvasilev.finances.*")
+@SpringBootApplication(
+ scanBasePackageClasses = { AuthorizationService.class },
+ scanBasePackages = "dev.mvvasilev.finances.*"
+)
public class PefiCoreAPI {
public static void main(String[] args) {
diff --git a/pefi-core-api/src/main/java/dev/mvvasilev/finances/configuration/KafkaConfiguration.java b/pefi-core-api/src/main/java/dev/mvvasilev/finances/configuration/KafkaConfiguration.java
index fb3535b..4a3dd77 100644
--- a/pefi-core-api/src/main/java/dev/mvvasilev/finances/configuration/KafkaConfiguration.java
+++ b/pefi-core-api/src/main/java/dev/mvvasilev/finances/configuration/KafkaConfiguration.java
@@ -1,12 +1,15 @@
package dev.mvvasilev.finances.configuration;
import dev.mvvasilev.common.dto.KafkaReplaceProcessedTransactionsDTO;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.annotation.EnableKafka;
+import org.springframework.kafka.annotation.EnableKafkaRetryTopic;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
@@ -15,6 +18,7 @@ import org.springframework.kafka.support.serializer.JsonSerializer;
import java.util.Map;
+@EnableKafka
@Configuration
public class KafkaConfiguration {
@@ -25,12 +29,12 @@ public class KafkaConfiguration {
@Bean
public ConsumerFactory replaceTransactionsConsumerFactory() {
- // ...
return new DefaultKafkaConsumerFactory<>(
Map.of(
- ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress,
- ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class,
- ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class
+ ConsumerConfig.GROUP_ID_CONFIG, "core-api",
+ ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress,
+ ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringSerializer.class,
+ ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonSerializer.class
),
new StringDeserializer(),
new JsonDeserializer<>(KafkaReplaceProcessedTransactionsDTO.class)
diff --git a/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/EnumsController.java b/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/EnumsController.java
index 8a0ff65..023fb6e 100644
--- a/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/EnumsController.java
+++ b/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/EnumsController.java
@@ -6,6 +6,7 @@ import dev.mvvasilev.common.web.APIResponseDTO;
import dev.mvvasilev.finances.dtos.CategorizationRuleDTO;
import dev.mvvasilev.finances.dtos.ProcessedTransactionFieldDTO;
import dev.mvvasilev.finances.enums.CategorizationRule;
+import dev.mvvasilev.finances.enums.TimePeriod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -18,6 +19,11 @@ import java.util.Collection;
@RequestMapping("/enums")
public class EnumsController extends AbstractRestController {
+ @GetMapping("/statistics-time-periods")
+ public ResponseEntity> fetchTimePeriods() {
+ return ok(TimePeriod.values());
+ }
+
@GetMapping("/category-rules")
public ResponseEntity>> fetchCategorizationRules() {
return ok(
diff --git a/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java b/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java
index 74ad098..bf7eaf7 100644
--- a/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java
+++ b/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java
@@ -30,11 +30,6 @@ public class StatisticsController extends AbstractRestController {
this.statisticsService = statisticsService;
}
- @GetMapping("/timePeriods")
- public ResponseEntity> fetchTimePeriods() {
- return ok(TimePeriod.values());
- }
-
@GetMapping("/totalSpendingByCategory")
@PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.TransactionCategory))")
public ResponseEntity> fetchSpendingByCategory(
diff --git a/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/TransactionsKafkaListener.java b/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/TransactionsKafkaListener.java
index 83e8c01..f085dca 100644
--- a/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/TransactionsKafkaListener.java
+++ b/pefi-core-api/src/main/java/dev/mvvasilev/finances/controllers/TransactionsKafkaListener.java
@@ -19,7 +19,7 @@ public class TransactionsKafkaListener {
@KafkaListener(
topics = KafkaConfiguration.REPLACE_TRANSACTIONS_TOPIC,
- containerFactory = "replaceTransactionsKafkaListenerContainerFactory"
+ groupId = "core-api"
)
public void replaceTransactionsListener(KafkaReplaceProcessedTransactionsDTO message) {
service.createOrReplaceProcessedTransactions(message.statementId(), message.userId(), message.transactions());
diff --git a/pefi-core-api/src/main/resources/application.properties b/pefi-core-api/src/main/resources/application.properties
index 52bf1f4..316f46b 100644
--- a/pefi-core-api/src/main/resources/application.properties
+++ b/pefi-core-api/src/main/resources/application.properties
@@ -1,10 +1,9 @@
-server.port=8081
-debug=true
-
-logging.level.org.springframework.boot.autoconfigure=ERROR
+server.port=${SERVER_PORT}
spring.profiles.active=${PROFILE}
+spring.security.oauth2.resourceserver.jwt.issuer-uri=${AUTHENTIK_ISSUER_URI}
+
# Database
spring.datasource.url=${DATASOURCE_URL}
spring.datasource.username=${DATASOURCE_USER}
@@ -21,7 +20,4 @@ spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate
spring.flyway.table=core_schema_history
spring.flyway.baseline-version=0.9
-spring.flyway.baseline-on-migrate=true
-
-# Security
-jwt.issuer-url=${AUTHENTIK_ISSUER_URL}
\ No newline at end of file
+spring.flyway.baseline-on-migrate=true
\ No newline at end of file
diff --git a/pefi-frontend/src/components/categories/CategorizationRulesEditor.jsx b/pefi-frontend/src/components/categories/CategorizationRulesEditor.jsx
index 9caba1e..9a65945 100644
--- a/pefi-frontend/src/components/categories/CategorizationRulesEditor.jsx
+++ b/pefi-frontend/src/components/categories/CategorizationRulesEditor.jsx
@@ -24,10 +24,10 @@ export default function CategorizationRulesEditor({selectedCategory, onRuleBehav
toast.promise(
Promise.all([
- utils.performRequest("/api/categories/rules")
+ utils.performRequest("/api/enums/category-rules")
.then(resp => resp.json())
.then(({result}) => setRuleTypes(result)),
- utils.performRequest("/api/processed-transactions/fields")
+ utils.performRequest("/api/enums/processed-transaction-fields")
.then(resp => resp.json())
.then(({result}) => setFields(result))
]),
diff --git a/pefi-frontend/src/components/widgets/WidgetEditModal.jsx b/pefi-frontend/src/components/widgets/WidgetEditModal.jsx
index 5724677..06a1dc9 100644
--- a/pefi-frontend/src/components/widgets/WidgetEditModal.jsx
+++ b/pefi-frontend/src/components/widgets/WidgetEditModal.jsx
@@ -49,7 +49,7 @@ export default function WidgetEditModal(
}, [initialWidget]);
useEffect(() => {
- utils.performRequest("/api/widgets/types")
+ utils.performRequest("/api/enums/widget-types")
.then(resp => resp.json())
.then(resp => setWidgetTypes(resp.result));
@@ -57,7 +57,7 @@ export default function WidgetEditModal(
.then(resp => resp.json())
.then(resp => setCategories(resp.result));
- utils.performRequest("/api/statistics/timePeriods")
+ utils.performRequest("/api/enums/statistics-time-periods")
.then(resp => resp.json())
.then(resp => setTimePeriods(resp.result));
}, []);
diff --git a/pefi-frontend/src/utils.js b/pefi-frontend/src/utils.js
index f7b56c2..8d5a318 100644
--- a/pefi-frontend/src/utils.js
+++ b/pefi-frontend/src/utils.js
@@ -8,25 +8,48 @@ let LEV_FORMAT = new Intl.NumberFormat('bg-BG', {
let utils = {
performRequest: async (url, options) => {
let opts = options ?? { headers: {} };
- return await fetch(url, {
+
+ let result = await fetch(url, {
...opts,
headers: {
...opts.headers,
'X-Requested-With': 'XMLHttpRequest'
}
- }).then(resp => {
- if (resp.status === 401) {
- window.location.replace(`${window.location.origin}/oauth2/authorization/authentik`)
-
- throw "Unauthorized, please login.";
- }
-
- if (!resp.ok) {
- throw resp.status;
- }
-
- return resp;
});
+
+ if (result.ok) {
+ return result;
+ }
+
+ // If we are unauthorized, refresh the token, and try once more.
+ if (result.status === 401) {
+ let tokenResponse = await fetch("/refresh-token", {
+ method: "POST",
+ headers: { 'X-Requested-With': 'XMLHttpRequest' }
+ });
+
+ // If the token refresh failed, redirect to login
+ if (!tokenResponse.ok) {
+ window.location.replace(`${window.location.origin}/oauth2/authorization/authentik`);
+ }
+
+ // Try again
+ let secondAttempt = await fetch(url, {
+ ...opts,
+ headers: {
+ ...opts.headers,
+ 'X-Requested-With': 'XMLHttpRequest'
+ }
+ });
+
+ // If our second attempt failed as well after refresh, redirect to login
+ if (!secondAttempt.ok && result.status === 401) {
+ window.location.replace(`${window.location.origin}/oauth2/authorization/authentik`);
+ }
+ }
+
+ // If the error wasn't unauthorized, just return the response
+ return result;
},
isSpinnerShown: () => {
return localStorage.getItem("SpinnerShowing") === "true";
diff --git a/pefi-login-service/.env.example b/pefi-login-service/.env.example
new file mode 100644
index 0000000..b29247b
--- /dev/null
+++ b/pefi-login-service/.env.example
@@ -0,0 +1,4 @@
+AUTHENTIK_CLIENT_ID= authentik oauth2 client id
+AUTHENTIK_CLIENT_SECRET= authentik oauth2 client secret
+AUTHENTIK_ISSUER_URL= authentik issuer url ( dev: https://auth.mvvasilev.dev/application/o/personal-finances/ )
+AUTHENTIK_BACK_CHANNEL_LOGOUT_URL= authentik back channel logout url ( dev: https://auth.mvvasilev.dev/application/o/personal-finances/end-session/ )
\ No newline at end of file
diff --git a/pefi-login-service/Dockerfile b/pefi-login-service/Dockerfile
new file mode 100644
index 0000000..15ea4d2
--- /dev/null
+++ b/pefi-login-service/Dockerfile
@@ -0,0 +1,7 @@
+FROM eclipse-temurin:21-jdk-alpine
+
+COPY ./build/libs/pefi-login-service-0.0.1-SNAPSHOT.jar app.jar
+
+EXPOSE 8081
+
+ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar $ARGS
\ No newline at end of file
diff --git a/pefi-login-service/build.gradle b/pefi-login-service/build.gradle
new file mode 100644
index 0000000..acad0e1
--- /dev/null
+++ b/pefi-login-service/build.gradle
@@ -0,0 +1,24 @@
+plugins {
+ id 'java'
+ id 'org.springframework.boot' version '3.2.0'
+ id 'io.spring.dependency-management' version '1.1.4'
+}
+
+group = 'dev.mvvasilev'
+version = '0.0.1-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+
+ testImplementation platform('org.junit:junit-bom:5.9.1')
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+}
+
+test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/pefi-login-service/settings.gradle b/pefi-login-service/settings.gradle
new file mode 100644
index 0000000..4a8da55
--- /dev/null
+++ b/pefi-login-service/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'pefi-login-service'
\ No newline at end of file
diff --git a/pefi-login-service/src/main/java/dev/mvvasilev/PefiLoginService.java b/pefi-login-service/src/main/java/dev/mvvasilev/PefiLoginService.java
new file mode 100644
index 0000000..b5bedeb
--- /dev/null
+++ b/pefi-login-service/src/main/java/dev/mvvasilev/PefiLoginService.java
@@ -0,0 +1,11 @@
+package dev.mvvasilev;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class PefiLoginService {
+ public static void main(String[] args) {
+ SpringApplication.run(PefiLoginService.class, args);
+ }
+}
\ No newline at end of file
diff --git a/pefi-login-service/src/main/java/dev/mvvasilev/configuration/SecurityConfiguration.java b/pefi-login-service/src/main/java/dev/mvvasilev/configuration/SecurityConfiguration.java
new file mode 100644
index 0000000..e8f27f3
--- /dev/null
+++ b/pefi-login-service/src/main/java/dev/mvvasilev/configuration/SecurityConfiguration.java
@@ -0,0 +1,58 @@
+package dev.mvvasilev.configuration;
+
+import dev.mvvasilev.service.TokenRefreshService;
+import dev.mvvasilev.utils.CookieUtils;
+import jakarta.servlet.http.Cookie;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfiguration {
+
+ @Value("${auth.success.redirect}")
+ private String redirect;
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http, OAuth2AuthorizedClientRepository repository) throws Exception {
+ return http
+ .authorizeHttpRequests(authorize -> {
+ authorize.requestMatchers(HttpMethod.POST, "/refresh-token").permitAll();
+ authorize.anyRequest().authenticated();
+ })
+ .oauth2Login(l -> l.successHandler((req, res, auth) -> {
+ OAuth2AuthenticationToken oauth = (OAuth2AuthenticationToken) auth;
+
+ OAuth2AuthorizedClient authorizedClient = repository.loadAuthorizedClient(
+ oauth.getAuthorizedClientRegistrationId(),
+ auth,
+ req
+ );
+
+ res.addCookie(
+ CookieUtils.createAccessTokenCookie(authorizedClient.getAccessToken().getTokenValue())
+ );
+
+ if (authorizedClient.getRefreshToken() != null) {
+ res.addCookie(
+ CookieUtils.createRefreshTokenCookie(authorizedClient.getRefreshToken().getTokenValue())
+ );
+ }
+
+ res.setStatus(HttpStatus.TEMPORARY_REDIRECT.value());
+ res.addHeader("Location", redirect);
+
+ }))
+ .build();
+ }
+}
diff --git a/pefi-login-service/src/main/java/dev/mvvasilev/controller/RefreshController.java b/pefi-login-service/src/main/java/dev/mvvasilev/controller/RefreshController.java
new file mode 100644
index 0000000..5ec0916
--- /dev/null
+++ b/pefi-login-service/src/main/java/dev/mvvasilev/controller/RefreshController.java
@@ -0,0 +1,55 @@
+package dev.mvvasilev.controller;
+
+import dev.mvvasilev.configuration.SecurityConfiguration;
+import dev.mvvasilev.dto.TokenDTO;
+import dev.mvvasilev.exception.PefiLoginServiceException;
+import dev.mvvasilev.service.TokenRefreshService;
+import dev.mvvasilev.utils.CookieUtils;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.core.oidc.user.OidcUser;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+
+@RestController
+public class RefreshController {
+
+ private final TokenRefreshService tokenRefreshService;
+
+ public RefreshController(TokenRefreshService tokenRefreshService) {
+ this.tokenRefreshService = tokenRefreshService;
+ }
+
+ @PostMapping("/refresh-token")
+ public ResponseEntity getOidcUserPrincipal(
+ HttpServletResponse response,
+ @CookieValue(CookieUtils.REFRESH_TOKEN_NAME) String refreshToken
+ ) {
+ try {
+ var token = tokenRefreshService.fetchNewTokens(refreshToken);
+
+ response.addCookie(CookieUtils.createAccessTokenCookie(token.accessToken()));
+ response.addCookie(CookieUtils.createRefreshTokenCookie(token.refreshToken()));
+
+ return ResponseEntity.ok().build();
+ } catch (PefiLoginServiceException e) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+ }
+ }
+
+}
diff --git a/pefi-login-service/src/main/java/dev/mvvasilev/dto/TokenDTO.java b/pefi-login-service/src/main/java/dev/mvvasilev/dto/TokenDTO.java
new file mode 100644
index 0000000..d378013
--- /dev/null
+++ b/pefi-login-service/src/main/java/dev/mvvasilev/dto/TokenDTO.java
@@ -0,0 +1,17 @@
+package dev.mvvasilev.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record TokenDTO(
+ @JsonProperty("access_token")
+ String accessToken,
+ @JsonProperty("refresh_token")
+ String refreshToken,
+ @JsonProperty("id_token")
+ String idToken,
+ @JsonProperty("token_type")
+ String tokenType,
+ @JsonProperty("expires_in")
+ int expiredIn
+) {
+}
diff --git a/pefi-login-service/src/main/java/dev/mvvasilev/exception/PefiLoginServiceException.java b/pefi-login-service/src/main/java/dev/mvvasilev/exception/PefiLoginServiceException.java
new file mode 100644
index 0000000..41438e0
--- /dev/null
+++ b/pefi-login-service/src/main/java/dev/mvvasilev/exception/PefiLoginServiceException.java
@@ -0,0 +1,7 @@
+package dev.mvvasilev.exception;
+
+public class PefiLoginServiceException extends RuntimeException {
+ public PefiLoginServiceException(String message) {
+ super(message);
+ }
+}
diff --git a/pefi-login-service/src/main/java/dev/mvvasilev/service/TokenRefreshService.java b/pefi-login-service/src/main/java/dev/mvvasilev/service/TokenRefreshService.java
new file mode 100644
index 0000000..63dd0ef
--- /dev/null
+++ b/pefi-login-service/src/main/java/dev/mvvasilev/service/TokenRefreshService.java
@@ -0,0 +1,51 @@
+package dev.mvvasilev.service;
+
+import dev.mvvasilev.dto.TokenDTO;
+import dev.mvvasilev.exception.PefiLoginServiceException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+
+@Service
+public class TokenRefreshService {
+
+ private final ClientRegistrationRepository clientRegistrationRepository;
+
+ @Autowired
+ public TokenRefreshService(ClientRegistrationRepository clientRegistrationRepository) {
+ this.clientRegistrationRepository = clientRegistrationRepository;
+ }
+
+ public TokenDTO fetchNewTokens(String refreshToken) {
+ var client = clientRegistrationRepository.findByRegistrationId("authentik");
+
+ var template = new RestTemplate();
+
+ var requestBody = new LinkedMultiValueMap();
+ requestBody.put("grant_type", List.of("refresh_token"));
+ requestBody.put("client_id", List.of(client.getClientId()));
+ requestBody.put("client_secret", List.of(client.getClientSecret()));
+ requestBody.put("refresh_token", List.of(refreshToken));
+
+ var requestHeaders = new LinkedMultiValueMap();
+ requestHeaders.put("Content-Type", List.of("application/x-www-form-urlencoded"));
+
+ var request = new HttpEntity<>(requestBody, requestHeaders);
+
+ var tokenResponse = template.postForEntity(client.getProviderDetails().getTokenUri(), request, TokenDTO.class);
+
+ if (!HttpStatus.OK.isSameCodeAs(tokenResponse.getStatusCode())) {
+ throw new PefiLoginServiceException("Token refresh failure");
+ }
+
+ return tokenResponse.getBody();
+ }
+
+}
diff --git a/pefi-login-service/src/main/java/dev/mvvasilev/utils/CookieUtils.java b/pefi-login-service/src/main/java/dev/mvvasilev/utils/CookieUtils.java
new file mode 100644
index 0000000..6fd154d
--- /dev/null
+++ b/pefi-login-service/src/main/java/dev/mvvasilev/utils/CookieUtils.java
@@ -0,0 +1,27 @@
+package dev.mvvasilev.utils;
+
+import jakarta.servlet.http.Cookie;
+
+public class CookieUtils {
+
+ public static final String ACCESS_TOKEN_NAME = "pefi_token";
+
+ public static final String REFRESH_TOKEN_NAME = "pefi_refresh_token";
+
+ public static Cookie createAccessTokenCookie(String value) {
+ var accessTokenCookie = new Cookie(ACCESS_TOKEN_NAME, value);
+ accessTokenCookie.setHttpOnly(true);
+ accessTokenCookie.setPath("/api");
+
+ return accessTokenCookie;
+ }
+
+ public static Cookie createRefreshTokenCookie(String value) {
+ var refreshTokenCookie = new Cookie(REFRESH_TOKEN_NAME, value);
+ refreshTokenCookie.setHttpOnly(true);
+ refreshTokenCookie.setPath("/refresh-token");
+
+ return refreshTokenCookie;
+ }
+
+}
diff --git a/pefi-login-service/src/main/resources/application.properties b/pefi-login-service/src/main/resources/application.properties
new file mode 100644
index 0000000..abf8b96
--- /dev/null
+++ b/pefi-login-service/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+server.port=${SERVER_PORT}
+
+auth.success.redirect=${FRONTEND_URI}
+
+spring.security.oauth2.client.registration.authentik.client-id=${AUTHENTIK_CLIENT_ID}
+spring.security.oauth2.client.registration.authentik.client-secret=${AUTHENTIK_CLIENT_SECRET}
+spring.security.oauth2.client.registration.authentik.authorization-grant-type=authorization_code
+spring.security.oauth2.client.registration.authentik.redirect-uri={baseUrl}/login/oauth2/code/authentik
+spring.security.oauth2.client.registration.authentik.scope=openid
+
+spring.security.oauth2.client.provider.authentik.issuer-uri=${AUTHENTIK_ISSUER_URI}
+spring.security.oauth2.client.provider.authentik.back-channel-logout-url=${AUTHENTIK_BACK_CHANNEL_LOGOUT_URL}
\ No newline at end of file
diff --git a/pefi-statements-api/src/main/java/dev/mvvasilev/statements/PefiStatementsAPI.java b/pefi-statements-api/src/main/java/dev/mvvasilev/statements/PefiStatementsAPI.java
index 39b8f92..88a851d 100644
--- a/pefi-statements-api/src/main/java/dev/mvvasilev/statements/PefiStatementsAPI.java
+++ b/pefi-statements-api/src/main/java/dev/mvvasilev/statements/PefiStatementsAPI.java
@@ -5,10 +5,15 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-@SpringBootApplication(scanBasePackageClasses = { AuthorizationService.class })
-@EnableJpaRepositories("dev.mvvasilev.statements.*")
+@EnableWebMvc
@EntityScan("dev.mvvasilev.statements.*")
+@EnableJpaRepositories("dev.mvvasilev.statements.*")
+@SpringBootApplication(
+ scanBasePackageClasses = { AuthorizationService.class },
+ scanBasePackages = "dev.mvvasilev.statements.*"
+)
public class PefiStatementsAPI {
public static void main(String[] args) {
SpringApplication.run(PefiStatementsAPI.class, args);
diff --git a/pefi-statements-api/src/main/resources/application.properties b/pefi-statements-api/src/main/resources/application.properties
index 79a2ec8..0ea6c46 100644
--- a/pefi-statements-api/src/main/resources/application.properties
+++ b/pefi-statements-api/src/main/resources/application.properties
@@ -1,10 +1,9 @@
-server.port=8081
-debug=true
-
-logging.level.org.springframework.boot.autoconfigure=ERROR
+server.port=${SERVER_PORT}
spring.profiles.active=${PROFILE}
+spring.security.oauth2.resourceserver.jwt.issuer-uri=${AUTHENTIK_ISSUER_URI}
+
# Database
spring.datasource.url=${DATASOURCE_URL}
spring.datasource.username=${DATASOURCE_USER}
@@ -21,7 +20,4 @@ spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate
spring.flyway.table=statements_schema_history
spring.flyway.baseline-version=0.9
-spring.flyway.baseline-on-migrate=true
-
-# Security
-jwt.issuer-url=${AUTHENTIK_ISSUER_URL}
\ No newline at end of file
+spring.flyway.baseline-on-migrate=true
\ No newline at end of file
diff --git a/pefi-widgets-api/.env.example b/pefi-widgets-api/.env.example
new file mode 100644
index 0000000..aaa5e64
--- /dev/null
+++ b/pefi-widgets-api/.env.example
@@ -0,0 +1,9 @@
+PROFILE= production/development
+
+AUTHENTIK_ISSUER_URL= auth server configuration url for fetching JWKs ( dev: https://auth.mvvasilev.dev/application/o/personal-finances/ )
+
+KAFKA_SERVERS= comma-delimited list of kafka servers to connect to
+
+DATASOURCE_URL= database jdbc url ( postgres only, example: jdbc:postgresql://localhost:5432/mydatabase )
+DATASOURCE_USER= database user
+DATASOURCE_PASSWORD= database password
\ No newline at end of file
diff --git a/pefi-widgets-api/build.gradle b/pefi-widgets-api/build.gradle
index f91f8a0..7f0babc 100644
--- a/pefi-widgets-api/build.gradle
+++ b/pefi-widgets-api/build.gradle
@@ -17,6 +17,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
+
implementation 'org.flywaydb:flyway-core'
implementation 'org.apache.commons:commons-lang3:3.14.0'
diff --git a/pefi-widgets-api/src/main/java/dev/mvvasilev/widgets/PefiWidgetsAPI.java b/pefi-widgets-api/src/main/java/dev/mvvasilev/widgets/PefiWidgetsAPI.java
index c397b40..dc64911 100644
--- a/pefi-widgets-api/src/main/java/dev/mvvasilev/widgets/PefiWidgetsAPI.java
+++ b/pefi-widgets-api/src/main/java/dev/mvvasilev/widgets/PefiWidgetsAPI.java
@@ -5,10 +5,15 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-@SpringBootApplication(scanBasePackageClasses = { AuthorizationService.class })
-@EnableJpaRepositories("dev.mvvasilev.widgets.*")
+@EnableWebMvc
@EntityScan("dev.mvvasilev.widgets.*")
+@EnableJpaRepositories("dev.mvvasilev.widgets.*")
+@SpringBootApplication(
+ scanBasePackageClasses = { AuthorizationService.class },
+ scanBasePackages = "dev.mvvasilev.widgets.*"
+)
public class PefiWidgetsAPI {
public static void main(String[] args) {
SpringApplication.run(PefiWidgetsAPI.class, args);
diff --git a/pefi-widgets-api/src/main/java/dev/mvvasilev/widgets/configurations/SecurityConfiguration.java b/pefi-widgets-api/src/main/java/dev/mvvasilev/widgets/configurations/SecurityConfiguration.java
index ecbc1de..b27078f 100644
--- a/pefi-widgets-api/src/main/java/dev/mvvasilev/widgets/configurations/SecurityConfiguration.java
+++ b/pefi-widgets-api/src/main/java/dev/mvvasilev/widgets/configurations/SecurityConfiguration.java
@@ -4,9 +4,11 @@ import dev.mvvasilev.common.configuration.CommonSecurityConfiguration;
import dev.mvvasilev.common.configuration.CommonSwaggerConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@Import(CommonSecurityConfiguration.class)
+@EnableTransactionManagement
public class SecurityConfiguration {
}
diff --git a/pefi-widgets-api/src/main/resources/application.properties b/pefi-widgets-api/src/main/resources/application.properties
index 0a4aa42..bf6bb98 100644
--- a/pefi-widgets-api/src/main/resources/application.properties
+++ b/pefi-widgets-api/src/main/resources/application.properties
@@ -1,10 +1,9 @@
-server.port=8081
-debug=true
-
-logging.level.org.springframework.boot.autoconfigure=ERROR
+server.port=${SERVER_PORT}
spring.profiles.active=${PROFILE}
+spring.security.oauth2.resourceserver.jwt.issuer-uri=${AUTHENTIK_ISSUER_URI}
+
# Database
spring.datasource.url=${DATASOURCE_URL}
spring.datasource.username=${DATASOURCE_USER}
@@ -19,7 +18,4 @@ spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate
spring.flyway.table=widgets_schema_history
spring.flyway.baseline-version=0.9
-spring.flyway.baseline-on-migrate=true
-
-# Security
-jwt.issuer-url=${AUTHENTIK_ISSUER_URL}
\ No newline at end of file
+spring.flyway.baseline-on-migrate=true
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index d6d679b..2d5650f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,4 +6,5 @@ include 'pefi-api-gateway'
include 'pefi-core-api'
include 'pefi-statements-api'
include 'pefi-widgets-api'
+include 'pefi-login-service'