From af7b51578b92094883a68a75302a18cdd9aff0d2 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 10 Nov 2025 14:19:01 +0100 Subject: [PATCH] Added session management with periodic keepalive: - **Backend**: Introduced `SessionController` with `/keepalive` endpoint to keep sessions active. - **Frontend**: Enhanced session handling with `startSessionRefresh` and activity tracking in `backend.js`. Integrated session refresh initiation in `main.js`. Adjusted logic in `CalculationAssistant.vue` for input validation. - **Other Changes**: Updated `BulkOperationRepository` cleanup interval to 60 minutes for improved timeout handling. --- src/frontend/src/backend.js | 57 ++++++++++++++++++- src/frontend/src/main.js | 5 ++ .../src/pages/CalculationAssistant.vue | 5 +- .../lcc/controller/SessionController.java | 23 ++++++++ .../bulk/BulkOperationRepository.java | 2 +- 5 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/main/java/de/avatic/lcc/controller/SessionController.java diff --git a/src/frontend/src/backend.js b/src/frontend/src/backend.js index 4a4ba5e..b56bbae 100644 --- a/src/frontend/src/backend.js +++ b/src/frontend/src/backend.js @@ -10,6 +10,55 @@ const getCsrfToken = () => { return null; } +let sessionRefreshInterval = null; +let lastActivity = Date.now(); + + +const refreshSession = async () => { + try { + // Ein simpler authenticated GET request hält die Session am Leben + await fetch('/api/session/keepalive', { + method: 'GET', + credentials: 'include' + }); + console.log('Session refreshed'); + } catch (e) { + console.error('Session refresh failed', e); + } +} + + +const trackActivity = () => { + lastActivity = Date.now(); +} + + +export const startSessionRefresh = () => { + + ['mousedown', 'keypress', 'scroll', 'touchstart'].forEach(event => { + document.addEventListener(event, trackActivity); + }); + + + if (sessionRefreshInterval) { + clearInterval(sessionRefreshInterval); + } + + sessionRefreshInterval = setInterval(() => { + const timeSinceActivity = Date.now() - lastActivity; + + if (timeSinceActivity < (12 * 60 * 60 * 1000)) { + refreshSession(); + } + }, 10 * 60 * 1000); +} + +export const stopSessionRefresh = () => { + if (sessionRefreshInterval) { + clearInterval(sessionRefreshInterval); + } +} + const performRequest = async (requestingStore, method, url, body, expectResponse = true, expectedException = null) => { const params = { @@ -124,6 +173,12 @@ const executeRequest = async (requestingStore, request) => { throw e; }); + if (response.status === 401) { + logger.warn('Session expired, redirecting to login...'); + window.location.href = '/oauth2/authorization/azure'; + return Promise.reject(new Error('Session expired')); + } + let data = null; if (request.expectResponse) { try { @@ -173,4 +228,4 @@ const executeRequest = async (requestingStore, request) => { } export default performRequest; -export {performUpload, performDownload, getCsrfToken}; +export {performUpload, performDownload, getCsrfToken, startSessionRefresh, stopSessionRefresh}; diff --git a/src/frontend/src/main.js b/src/frontend/src/main.js index f1e835c..114056f 100644 --- a/src/frontend/src/main.js +++ b/src/frontend/src/main.js @@ -3,6 +3,7 @@ import router from './router.js'; import { setupErrorBuffer } from './store/error.js' import {createApp} from 'vue' import {createPinia} from 'pinia'; +import {startSessionRefresh} from "@/backend.js"; import App from './App.vue' import { @@ -31,6 +32,9 @@ import { PhCloudX, PhDesktop, PhHardDrives } from "@phosphor-icons/vue"; + + + const app = createApp(App); const pinia = createPinia(); app.use(pinia); @@ -72,5 +76,6 @@ app.use(router); //app.component('base-button', () => import('./components/UI/BasicButton.vue')); //app.component('base-badge', () => import('./components/UI/BasicBadge.vue')); setupErrorBuffer() +startSessionRefresh(); app.mount('#app'); diff --git a/src/frontend/src/pages/CalculationAssistant.vue b/src/frontend/src/pages/CalculationAssistant.vue index 682304a..2f09a3a 100644 --- a/src/frontend/src/pages/CalculationAssistant.vue +++ b/src/frontend/src/pages/CalculationAssistant.vue @@ -150,7 +150,10 @@ export default { }, parsePartNumbers() { this.closeModal('partNumber'); - this.assistantStore.getMaterialsAndSuppliers(this.partNumberField); + + if(this.partNumberField.trim().length !== 0) + this.assistantStore.getMaterialsAndSuppliers(this.partNumberField); + this.partNumberField = ''; }, addCreatedNode(supplier) { diff --git a/src/main/java/de/avatic/lcc/controller/SessionController.java b/src/main/java/de/avatic/lcc/controller/SessionController.java new file mode 100644 index 0000000..392bb42 --- /dev/null +++ b/src/main/java/de/avatic/lcc/controller/SessionController.java @@ -0,0 +1,23 @@ +package de.avatic.lcc.controller; + +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/api/session") +public class SessionController { + + @GetMapping("/keepalive") + public Map keepalive(Authentication authentication) { + + return Map.of( + "status", "ok", + "user", authentication.getName(), + "timestamp", System.currentTimeMillis() + ); + } +} \ No newline at end of file diff --git a/src/main/java/de/avatic/lcc/repositories/bulk/BulkOperationRepository.java b/src/main/java/de/avatic/lcc/repositories/bulk/BulkOperationRepository.java index 8d8b868..898fcac 100644 --- a/src/main/java/de/avatic/lcc/repositories/bulk/BulkOperationRepository.java +++ b/src/main/java/de/avatic/lcc/repositories/bulk/BulkOperationRepository.java @@ -135,7 +135,7 @@ public class BulkOperationRepository { private void cleanupTimeouts(Integer userId) { String sql = """ - UPDATE bulk_operation SET state = 'EXCEPTION' WHERE user_id = ? AND (state = 'PROCESSING' OR state = 'SCHEDULED') AND created_at < NOW() - INTERVAL 30 MINUTE + UPDATE bulk_operation SET state = 'EXCEPTION' WHERE user_id = ? AND (state = 'PROCESSING' OR state = 'SCHEDULED') AND created_at < NOW() - INTERVAL 60 MINUTE """; jdbcTemplate.update(sql, userId);