From 95a17a1724da3757a4544248d525c14232d8d2c8 Mon Sep 17 00:00:00 2001 From: Viktor Lindell Date: Mon, 27 Apr 2026 11:10:25 +0200 Subject: [PATCH 1/3] fix: preserve SavedRequest in auth success handler and fix deleteFile condition --- .../untitled/config/SecurityConfig.java | 20 +-------- .../CustomAuthenticationSuccessHandler.java | 43 +++++++++++++++++++ src/main/resources/static/js/script.js | 2 +- 3 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/example/untitled/security/CustomAuthenticationSuccessHandler.java diff --git a/src/main/java/org/example/untitled/config/SecurityConfig.java b/src/main/java/org/example/untitled/config/SecurityConfig.java index dcab62e..e01df28 100644 --- a/src/main/java/org/example/untitled/config/SecurityConfig.java +++ b/src/main/java/org/example/untitled/config/SecurityConfig.java @@ -1,6 +1,6 @@ package org.example.untitled.config; -import org.example.untitled.user.Role; +import org.example.untitled.security.CustomAuthenticationSuccessHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -16,8 +16,6 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import java.util.Optional; - @Configuration @EnableWebSecurity @EnableMethodSecurity @@ -67,21 +65,7 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c @Bean public AuthenticationSuccessHandler customAuthenticationSuccessHandler() { - return (request, response, authentication) -> { - Role role = authentication.getAuthorities().stream() - .map(a -> Role.fromAuthority(a.getAuthority())) - .flatMap(Optional::stream) - .findFirst() - .orElse(Role.USER); - - if (role == Role.ADMIN) { - response.sendRedirect("/admin"); - } else if (role == Role.HANDLER || role == Role.SUPERVISOR) { - response.sendRedirect("/handler"); - } else { - response.sendRedirect("/user"); - } - }; + return new CustomAuthenticationSuccessHandler(); } @Bean diff --git a/src/main/java/org/example/untitled/security/CustomAuthenticationSuccessHandler.java b/src/main/java/org/example/untitled/security/CustomAuthenticationSuccessHandler.java new file mode 100644 index 0000000..fc5c2dc --- /dev/null +++ b/src/main/java/org/example/untitled/security/CustomAuthenticationSuccessHandler.java @@ -0,0 +1,43 @@ +package org.example.untitled.security; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.example.untitled.user.Role; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; + +import java.io.IOException; +import java.util.Optional; + +public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { + + private final HttpSessionRequestCache requestCache = new HttpSessionRequestCache(); + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + SavedRequest savedRequest = requestCache.getRequest(request, response); + if (savedRequest != null) { + super.onAuthenticationSuccess(request, response, authentication); + return; + } + + Role role = authentication.getAuthorities().stream() + .map(a -> Role.fromAuthority(a.getAuthority())) + .flatMap(Optional::stream) + .findFirst() + .orElse(Role.USER); + + String targetUrl = switch (role) { + case ADMIN -> "/admin"; + case HANDLER, SUPERVISOR -> "/handler"; + default -> "/user"; + }; + + clearAuthenticationAttributes(request); + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } +} \ No newline at end of file diff --git a/src/main/resources/static/js/script.js b/src/main/resources/static/js/script.js index fc0ce20..0a1be64 100644 --- a/src/main/resources/static/js/script.js +++ b/src/main/resources/static/js/script.js @@ -62,7 +62,7 @@ async function uploadNewFile(){ } async function deleteFile(fileName){ const status = document.getElementById('status'); - if (!window.confirm(fileName + " will be deleted! Are you sure?")) { + if (window.confirm(fileName + " will be deleted! Are you sure?")) { const res = await apiReq(`/upload/api/files/delete-url?fileName=${encodeURIComponent(fileName)}`, {method: 'DELETE'}); if(res.ok){ await fetchAFile(); From e93db1d71abc00d5dc65dda61ba7a93254d57d47 Mon Sep 17 00:00:00 2001 From: Viktor Lindell Date: Mon, 27 Apr 2026 13:36:18 +0200 Subject: [PATCH 2/3] merge main and resolve script.js conflict --- src/main/resources/static/js/script.js | 34 ++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/main/resources/static/js/script.js b/src/main/resources/static/js/script.js index 0a1be64..e04fc45 100644 --- a/src/main/resources/static/js/script.js +++ b/src/main/resources/static/js/script.js @@ -16,25 +16,30 @@ async function fetchAFile(){ } async function downloadFile(fileName) { - const res = await apiReq(`/upload/files/download-url?fileName=${encodeURIComponent(fileName)}`); + const res = await apiReq(`/tickets/upload/api/files/download-url?fileName=${encodeURIComponent(fileName)}`); if (!res) return; const {url} = await res.json(); window.open(url, '_blank'); } async function uploadNewFile(){ + const submitBtn = document.getElementById('submitBtn'); + submitBtn.disabled = true; const input = document.getElementById('fileInput'); const status = document.getElementById('status'); - if (input.files.length === 0) return; + if (input.files.length === 0){ + document.querySelector('form').submit(); + return; + } const filesToUpload = Array.from(input.files); input.value = null; - + submitBtn.innerText = "Uploading..."; for (let i = 0; i < filesToUpload.length; i++) { const file = filesToUpload[i]; try { status.innerText = `Processing file ${i + 1} of ${filesToUpload.length}: ${file.name}`; - const res = await apiReq(`/upload/files/upload-url?fileName=${encodeURIComponent(file.name)}&contentType=${encodeURIComponent(file.type)}`); + const res = await apiReq(`/tickets/upload/api/files/upload-url?fileName=${encodeURIComponent(file.name)}&contentType=${encodeURIComponent(file.type)}`); if (!res) continue; const { url, fileName: uploadedFileName } = await res.json(); status.innerText = `Uploading ${file.name}...`; @@ -44,26 +49,40 @@ async function uploadNewFile(){ headers: { 'Content-Type': file.type } }); if (putRes.ok) { - await apiReq(`/upload/files/callback?fileName=${encodeURIComponent(uploadedFileName)}`, { + await apiReq(`/tickets/upload/api/files/callback?fileName=${encodeURIComponent(uploadedFileName)}`, { method: 'POST' }); status.innerText = `Successfully uploaded ${file.name}`; - fetchAFile(); + + const hiddenContainer = document.getElementById("hidden-file-inputs"); + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'fileNames'; + input.value = uploadedFileName; + hiddenContainer.appendChild(input); } else { status.innerText = `Failed to upload ${file.name}. Status: ${putRes.status}`; + submitBtn.disabled = false; + submitBtn.innerText = "Create Ticket"; } } catch (error) { console.error(error); status.innerText = `Error uploading ${file.name}: ${error.message}`; + submitBtn.disabled = false; + submitBtn.innerText = "Create Ticket"; } } + submitBtn.disabled = false; + submitBtn.innerText = "Create Ticket"; status.innerText = "All uploads completed."; + document.querySelector('form').submit(); } + async function deleteFile(fileName){ const status = document.getElementById('status'); if (window.confirm(fileName + " will be deleted! Are you sure?")) { - const res = await apiReq(`/upload/api/files/delete-url?fileName=${encodeURIComponent(fileName)}`, {method: 'DELETE'}); + const res = await apiReq(`/tickets/upload/api/files/delete-url?fileName=${encodeURIComponent(fileName)}`, {method: 'DELETE'}); if(res.ok){ await fetchAFile(); } else { @@ -71,6 +90,7 @@ async function deleteFile(fileName){ } } } + async function apiReq(url, options = {}) { options.credentials = options.credentials || 'same-origin'; options.headers = options.headers || {}; From 430a55681fbb93bd397dfcd620423624c72baecc Mon Sep 17 00:00:00 2001 From: Viktor Lindell Date: Mon, 27 Apr 2026 13:50:17 +0200 Subject: [PATCH 3/3] fix: correct S3 API URL prefix and add null guard in deleteFile --- src/main/resources/static/js/script.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/resources/static/js/script.js b/src/main/resources/static/js/script.js index e04fc45..7dedde3 100644 --- a/src/main/resources/static/js/script.js +++ b/src/main/resources/static/js/script.js @@ -16,7 +16,7 @@ async function fetchAFile(){ } async function downloadFile(fileName) { - const res = await apiReq(`/tickets/upload/api/files/download-url?fileName=${encodeURIComponent(fileName)}`); + const res = await apiReq(`/upload/api/files/download-url?fileName=${encodeURIComponent(fileName)}`); if (!res) return; const {url} = await res.json(); window.open(url, '_blank'); @@ -39,7 +39,7 @@ async function uploadNewFile(){ try { status.innerText = `Processing file ${i + 1} of ${filesToUpload.length}: ${file.name}`; - const res = await apiReq(`/tickets/upload/api/files/upload-url?fileName=${encodeURIComponent(file.name)}&contentType=${encodeURIComponent(file.type)}`); + const res = await apiReq(`/upload/api/files/upload-url?fileName=${encodeURIComponent(file.name)}&contentType=${encodeURIComponent(file.type)}`); if (!res) continue; const { url, fileName: uploadedFileName } = await res.json(); status.innerText = `Uploading ${file.name}...`; @@ -49,7 +49,7 @@ async function uploadNewFile(){ headers: { 'Content-Type': file.type } }); if (putRes.ok) { - await apiReq(`/tickets/upload/api/files/callback?fileName=${encodeURIComponent(uploadedFileName)}`, { + await apiReq(`/upload/api/files/callback?fileName=${encodeURIComponent(uploadedFileName)}`, { method: 'POST' }); @@ -82,8 +82,9 @@ async function uploadNewFile(){ async function deleteFile(fileName){ const status = document.getElementById('status'); if (window.confirm(fileName + " will be deleted! Are you sure?")) { - const res = await apiReq(`/tickets/upload/api/files/delete-url?fileName=${encodeURIComponent(fileName)}`, {method: 'DELETE'}); - if(res.ok){ + const res = await apiReq(`/upload/api/files/delete-url?fileName=${encodeURIComponent(fileName)}`, {method: 'DELETE'}); + if (!res) return; + if (res.ok) { await fetchAFile(); } else { status.innerText= 'Error: ' + res.status;