From 61dc4bc74f9dddf8a38a01f4e932601cc6c8afb6 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 08:33:57 +0200 Subject: [PATCH 01/48] add: startPage should not be filtered --- .../org/example/alfs/security/JwtAuthenticationFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/alfs/security/JwtAuthenticationFilter.java b/src/main/java/org/example/alfs/security/JwtAuthenticationFilter.java index 274d89f..a1d8e44 100644 --- a/src/main/java/org/example/alfs/security/JwtAuthenticationFilter.java +++ b/src/main/java/org/example/alfs/security/JwtAuthenticationFilter.java @@ -26,11 +26,11 @@ public JwtAuthenticationFilter(JwtService jwtService, UserRepository userReposit this.userRepository = userRepository; } - // Skips filter for LOGIN & H2 + // Skips filter for LOGIN & H2 & START PAGE @Override protected boolean shouldNotFilter(HttpServletRequest request) { String path = request.getRequestURI(); - return path.startsWith("/auth") || path.startsWith("/h2-console"); + return path.startsWith("/auth") || path.startsWith("/h2-console") || path.startsWith("/startPage"); } From d80add62ee8abd4b37938721e90d7d16b59987c7 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 08:38:29 +0200 Subject: [PATCH 02/48] add: startPage to permitted endpoints --- src/main/java/org/example/alfs/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/example/alfs/config/SecurityConfig.java b/src/main/java/org/example/alfs/config/SecurityConfig.java index 0b61490..36bc5f7 100644 --- a/src/main/java/org/example/alfs/config/SecurityConfig.java +++ b/src/main/java/org/example/alfs/config/SecurityConfig.java @@ -43,6 +43,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/auth/signup").permitAll() .requestMatchers("/auth/hash").permitAll() .requestMatchers("/h2-console/**").permitAll() + .requestMatchers("/startPage").permitAll() //allow access to endpoints during development .requestMatchers("/create", "/tickets/**", "/view/**").permitAll() From a47fe70d2b0cb5db9469f55567a864cedf3a833b Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 08:53:14 +0200 Subject: [PATCH 03/48] Create StartPageController and add initial code --- .../alfs/controllers/StartPageController.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/org/example/alfs/controllers/StartPageController.java diff --git a/src/main/java/org/example/alfs/controllers/StartPageController.java b/src/main/java/org/example/alfs/controllers/StartPageController.java new file mode 100644 index 0000000..3259f25 --- /dev/null +++ b/src/main/java/org/example/alfs/controllers/StartPageController.java @@ -0,0 +1,13 @@ +package org.example.alfs.controllers; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class StartPageController { + + @GetMapping("/") + public String startPage(){ + return "startPage"; + } +} From bfa108e5a8fabb46e79b9d33e6a4dc66a4682db4 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 08:56:45 +0200 Subject: [PATCH 04/48] Correct path to login view --- src/main/jte/layout.jte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/jte/layout.jte b/src/main/jte/layout.jte index 1202e22..fa72458 100644 --- a/src/main/jte/layout.jte +++ b/src/main/jte/layout.jte @@ -29,7 +29,7 @@ From 62d5ff38965032116fd085ee2609a48a11d48d72 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 08:59:55 +0200 Subject: [PATCH 05/48] add : layout to login --- src/main/jte/login.jte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/jte/login.jte b/src/main/jte/login.jte index 08682ae..b297362 100644 --- a/src/main/jte/login.jte +++ b/src/main/jte/login.jte @@ -1,3 +1,5 @@ +@template.layout(title = "Login", content = @` +

Login

@@ -12,4 +14,6 @@ -
\ No newline at end of file + + +`) \ No newline at end of file From 53813f8e6db96102e76b8c0bdd431a00b327efce Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 09:55:45 +0200 Subject: [PATCH 06/48] add : token login and box styling --- src/main/jte/login.jte | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/main/jte/login.jte b/src/main/jte/login.jte index b297362..e0dfa47 100644 --- a/src/main/jte/login.jte +++ b/src/main/jte/login.jte @@ -1,19 +1,38 @@ @template.layout(title = "Login", content = @` +
+

Login

-

Login

+
+ +
- -
-
+
+ -
- - -
+ +
+ +
- - + +
+

Got a token?

+
+
+

Login with token

+ + +
+
+
`) \ No newline at end of file From b7be9cd9babad44c130293794bd649100a355c6d Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 10:44:25 +0200 Subject: [PATCH 07/48] Create startPage --- src/main/jte/startPage.jte | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/main/jte/startPage.jte diff --git a/src/main/jte/startPage.jte b/src/main/jte/startPage.jte new file mode 100644 index 0000000..799efca --- /dev/null +++ b/src/main/jte/startPage.jte @@ -0,0 +1,52 @@ +@template.layout(title = "Start Page", content = @` + +
+ +
+

+ Welcome +

+ +

+ Here you can anonymously and securely report misconduct that concerns the public interest. + We guarantee that your identity is protected in accordance with applicable whistleblowing legislation. +

+
+ +
+ +
+

High security

+

This system is super secure

+
+ +
+

Anonymity

+

You do not need to provide personal information to submit a report.

+
+ +
+

Statutory protection

+

Protection against retaliation under national directives and regulations.

+
+ +
+ + +
+ +`) \ No newline at end of file From 867642184f950e938b346bb0a2fe159525e363a9 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 10:52:40 +0200 Subject: [PATCH 08/48] Permit access to root endpoint --- src/main/java/org/example/alfs/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/config/SecurityConfig.java b/src/main/java/org/example/alfs/config/SecurityConfig.java index 36bc5f7..5cf1248 100644 --- a/src/main/java/org/example/alfs/config/SecurityConfig.java +++ b/src/main/java/org/example/alfs/config/SecurityConfig.java @@ -43,7 +43,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/auth/signup").permitAll() .requestMatchers("/auth/hash").permitAll() .requestMatchers("/h2-console/**").permitAll() - .requestMatchers("/startPage").permitAll() + .requestMatchers("/startPage", "/").permitAll() //allow access to endpoints during development .requestMatchers("/create", "/tickets/**", "/view/**").permitAll() From 9f12a28392faccdf1291d4304572190b2c3acf18 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 10:53:49 +0200 Subject: [PATCH 09/48] Update navigation links and make logo clickable --- src/main/jte/layout.jte | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/jte/layout.jte b/src/main/jte/layout.jte index fa72458..df6b8d4 100644 --- a/src/main/jte/layout.jte +++ b/src/main/jte/layout.jte @@ -21,13 +21,17 @@
-

- ALFS - whistleblower system -

+ +

+ ALFS + whistleblower system +

+
+ + From 2b744740e322b65139d1668d3796d47d38be6c2c Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 14:27:17 +0200 Subject: [PATCH 10/48] Create preview before submitting ticket --- src/main/jte/previewTicket.jte | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/jte/previewTicket.jte diff --git a/src/main/jte/previewTicket.jte b/src/main/jte/previewTicket.jte new file mode 100644 index 0000000..e66b94e --- /dev/null +++ b/src/main/jte/previewTicket.jte @@ -0,0 +1,16 @@ +@param org.example.alfs.dto.ticket.TicketCreateDTO ticket + +@template.layout(title = "Preview", content = @` + +

Preview

+ +
+

${ticket.getTitle()}

+

${ticket.getDescription()}

+
+ + + + + +`) \ No newline at end of file From 77b05ebb8bad67777592d94c57e5c29f28909b24 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 14:36:59 +0200 Subject: [PATCH 11/48] add: ticket preview method --- .../example/alfs/controllers/TicketController.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/alfs/controllers/TicketController.java b/src/main/java/org/example/alfs/controllers/TicketController.java index 7ff0a32..ed2be9e 100644 --- a/src/main/java/org/example/alfs/controllers/TicketController.java +++ b/src/main/java/org/example/alfs/controllers/TicketController.java @@ -27,14 +27,14 @@ public TicketController(TicketService ticketService) { } //create ticket - @PreAuthorize("hasRole('REPORTER')") // should change later for anonymous access + //@PreAuthorize("hasRole('REPORTER')") // should change later for anonymous access @GetMapping("/create") public String createNewTicketForm(Model model) { model.addAttribute("ticket", new TicketCreateDTO()); return "create"; } - @PreAuthorize("hasRole('REPORTER')") // should change later for anonymous access + //@PreAuthorize("hasRole('REPORTER')") // should change later for anonymous access @PostMapping("/create") public String createNewTicket(@ModelAttribute("ticket") @Valid TicketCreateDTO ticketCreateDTO, BindingResult bindingResult, Model model) { if (bindingResult.hasErrors()) { @@ -47,6 +47,13 @@ public String createNewTicket(@ModelAttribute("ticket") @Valid TicketCreateDTO t return "redirect:/tickets/" + ticket.getId(); } + //Preview ticket + @PostMapping("/previewTicket") + public String previewTicket(@ModelAttribute("ticket") TicketCreateDTO dto, Model model) { + + model.addAttribute("ticket", dto); + return "previewTicket"; + } //view ticket by token From 0948b8912c8f11e24dd241dadbacc48767274bbc Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 14:38:18 +0200 Subject: [PATCH 12/48] update endpoint to preview --- src/main/jte/create.jte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/jte/create.jte b/src/main/jte/create.jte index 02fc472..6ebea9a 100644 --- a/src/main/jte/create.jte +++ b/src/main/jte/create.jte @@ -3,7 +3,7 @@ @template.layout(title = "Create Ticket", content = @`

Create Ticket

-
+ Date: Wed, 15 Apr 2026 14:39:17 +0200 Subject: [PATCH 13/48] permit access to ticket create and preview endpoints --- src/main/java/org/example/alfs/config/SecurityConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/config/SecurityConfig.java b/src/main/java/org/example/alfs/config/SecurityConfig.java index 5cf1248..3273067 100644 --- a/src/main/java/org/example/alfs/config/SecurityConfig.java +++ b/src/main/java/org/example/alfs/config/SecurityConfig.java @@ -44,9 +44,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/auth/hash").permitAll() .requestMatchers("/h2-console/**").permitAll() .requestMatchers("/startPage", "/").permitAll() + .requestMatchers("/tickets/create").permitAll() + .requestMatchers("/tickets/previewTicket").permitAll() //allow access to endpoints during development - .requestMatchers("/create", "/tickets/**", "/view/**").permitAll() + .requestMatchers("/tickets/**").permitAll() .requestMatchers("/css/**", "/js/**", "/images/**", "/static/**").permitAll() .requestMatchers("/login", "/login-form").permitAll() .requestMatchers("/signup", "/signup-form").permitAll() From 5e4912a8d97f2a4a2aaadc4249c39733406d1cbd Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 17:00:10 +0200 Subject: [PATCH 14/48] feat: add anonymous and authenticated ticket submission endpoints --- .../alfs/controllers/TicketController.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/org/example/alfs/controllers/TicketController.java b/src/main/java/org/example/alfs/controllers/TicketController.java index ed2be9e..d7b526c 100644 --- a/src/main/java/org/example/alfs/controllers/TicketController.java +++ b/src/main/java/org/example/alfs/controllers/TicketController.java @@ -55,6 +55,26 @@ public String previewTicket(@ModelAttribute("ticket") TicketCreateDTO dto, Model return "previewTicket"; } + //Submit ticket anonymous + @PostMapping("/submit-anonymous") + public String submitAnonymous(@ModelAttribute TicketCreateDTO dto) { + + TicketViewDTO ticket = ticketService.createAnonymousTicket(dto); + + return "redirect:/tickets/token/" + ticket.getToken(); + } + + //Submit ticket as logged in user + @PostMapping("/submit-authenticated") + public String submitAuthenticated( + @ModelAttribute TicketCreateDTO dto + ) { + + TicketViewDTO ticket = ticketService.createNewTicket(dto); + + return "redirect:/tickets/" + ticket.getId(); + } + //view ticket by token @GetMapping("/token/{token}") From 1512997aa49e430eb76c40c49a92fa658b386038 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 17:00:39 +0200 Subject: [PATCH 15/48] feat: add preview page actions for anonymous and authenticated submission --- src/main/jte/previewTicket.jte | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/jte/previewTicket.jte b/src/main/jte/previewTicket.jte index e66b94e..7620e90 100644 --- a/src/main/jte/previewTicket.jte +++ b/src/main/jte/previewTicket.jte @@ -10,6 +10,28 @@
+
+ + + + + + + + + +
+ + + + +
+ +
From b9c3cf1c78303a875b82d638d5542fad88404d41 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 17:01:24 +0200 Subject: [PATCH 16/48] refactor: move token generation from entity to service --- src/main/java/org/example/alfs/entities/Ticket.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/example/alfs/entities/Ticket.java b/src/main/java/org/example/alfs/entities/Ticket.java index b159aaa..6aef19e 100644 --- a/src/main/java/org/example/alfs/entities/Ticket.java +++ b/src/main/java/org/example/alfs/entities/Ticket.java @@ -47,8 +47,6 @@ public class Ticket { public void prePersist() { createdAt = LocalDateTime.now(); if (status == null) status = TicketStatus.OPEN; - if (reporterToken == null || reporterToken.isBlank()) - reporterToken = UUID.randomUUID().toString(); // Skapa token för anonyma anmälare } @PreUpdate From 6e34d83608c64c63d2465d82190bfcb57a9a8e90 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 17:02:16 +0200 Subject: [PATCH 17/48] add: method for anonymous ticket --- .../example/alfs/services/TicketService.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index 42c87ab..ce91642 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -53,6 +53,29 @@ public TicketViewDTO createNewTicket(TicketCreateDTO ticketCreateDTO) { return ticketMapper.entityToViewDTO(savedTicket); } + //Create anonymous ticket + // This token is currently stored in plain text for simplicity. + //todo hash token + public TicketViewDTO createAnonymousTicket(TicketCreateDTO dto) { + + Ticket ticket = new Ticket(); + + ticket.setTitle(dto.getTitle()); + ticket.setDescription(dto.getDescription()); + + String rawToken = java.util.UUID.randomUUID().toString(); + + ticket.setReporterToken(rawToken); + + Ticket saved = ticketRepository.save(ticket); + + TicketViewDTO view = ticketMapper.entityToViewDTO(saved); + + view.setToken(rawToken); + + return view; + } + // View by token public TicketViewDTO getTicketByToken(String token) { Ticket ticket = ticketRepository.findByReporterToken(token) From 419a420a1027b2b5c7cbad5680a067b4c919a8e3 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 15 Apr 2026 17:03:19 +0200 Subject: [PATCH 18/48] add: token field --- src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java b/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java index 2b1108e..3e99ed0 100644 --- a/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java +++ b/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java @@ -17,6 +17,7 @@ public class TicketViewDTO { private Long id; + private String token; private String title; private String description; private TicketStatus status; From 6b7eed6df8dbe48d5e9004cce97d713679f435df Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:05:38 +0200 Subject: [PATCH 19/48] feat(auth): add user feedback for login, logout and token validation --- .../alfs/controllers/AuthViewController.java | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/example/alfs/controllers/AuthViewController.java b/src/main/java/org/example/alfs/controllers/AuthViewController.java index 990cea0..5713b82 100644 --- a/src/main/java/org/example/alfs/controllers/AuthViewController.java +++ b/src/main/java/org/example/alfs/controllers/AuthViewController.java @@ -1,6 +1,6 @@ package org.example.alfs.controllers; -import jakarta.servlet.http.Cookie; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import org.example.alfs.dto.auth.SignupRequestDTO; @@ -8,11 +8,15 @@ import org.example.alfs.security.JwtService; import org.example.alfs.services.AuthService; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import java.time.Duration; + /** * Handles login for the browser (UI). * @@ -32,8 +36,10 @@ public AuthViewController(AuthService authService, JwtService jwtService) { } @GetMapping("/login") - public String loginPage() { - return "login"; // login.jte + public String loginPage(@RequestParam(required = false) String error, @RequestParam(required = false) String tokenError, Model model) { + model.addAttribute("error", error); + model.addAttribute("tokenError", tokenError); + return "login"; } @GetMapping("/signup") @@ -61,21 +67,25 @@ public String signupForm( public String loginForm( @RequestParam String username, @RequestParam String password, - HttpServletResponse response + HttpServletResponse response, + RedirectAttributes redirectAttributes ) { try { User user = authService.login(username, password); String token = jwtService.generateToken(user); - Cookie cookie = new Cookie("JWT", token); - cookie.setHttpOnly(true); - cookie.setPath("/"); - cookie.setMaxAge(60 * 60 * 24); + ResponseCookie cookie = ResponseCookie.from("JWT", token) + .httpOnly(true) + .path("/") + .maxAge(Duration.ofDays(1)) + .sameSite("Lax") + .build(); - response.addCookie(cookie); + response.addHeader("Set-Cookie", cookie.toString()); - return "redirect:/api/hello"; // should change later + redirectAttributes.addFlashAttribute("success", "You are signed in!"); + return "redirect:/"; } catch (ResponseStatusException ex) { @@ -89,16 +99,18 @@ public String loginForm( } } - @PostMapping("/logout") - public String logout(HttpServletResponse response) { - - Cookie cookie = new Cookie("JWT", null); - cookie.setHttpOnly(true); - cookie.setPath("/"); - cookie.setMaxAge(0); + @PostMapping("/auth/logout") + public String logout(HttpServletResponse response, RedirectAttributes redirectAttributes) { - response.addCookie(cookie); + ResponseCookie cookie = ResponseCookie.from("JWT", "") + .httpOnly(true) + .path("/") + .maxAge(Duration.ZERO) + .sameSite("Lax") + .build(); - return "redirect:/login"; + response.addHeader("Set-Cookie", cookie.toString()); + redirectAttributes.addFlashAttribute("success", "Successfully signed out"); + return "redirect:/"; } } \ No newline at end of file From 304ea313ad77c64c43499d9455d4812149358299 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:06:26 +0200 Subject: [PATCH 20/48] adds success message if ticket created successfully --- src/main/jte/create.jte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/jte/create.jte b/src/main/jte/create.jte index 6ebea9a..c52fd31 100644 --- a/src/main/jte/create.jte +++ b/src/main/jte/create.jte @@ -1,9 +1,9 @@ @param org.example.alfs.dto.ticket.TicketCreateDTO ticket - +@param String success @template.layout(title = "Create Ticket", content = @`

Create Ticket

-
+
-`) \ No newline at end of file +`, success = success) \ No newline at end of file From a412f14424cf5d5be3c071f7e40b1b5fda244fb7 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:07:10 +0200 Subject: [PATCH 21/48] feat(global): add isLoggedIn to all views via ControllerAdvice --- .../alfs/config/GlobalModelAttributes.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/org/example/alfs/config/GlobalModelAttributes.java diff --git a/src/main/java/org/example/alfs/config/GlobalModelAttributes.java b/src/main/java/org/example/alfs/config/GlobalModelAttributes.java new file mode 100644 index 0000000..0c4b7a6 --- /dev/null +++ b/src/main/java/org/example/alfs/config/GlobalModelAttributes.java @@ -0,0 +1,28 @@ +package org.example.alfs.config; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.example.alfs.security.SecurityUtils; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ModelAttribute; + +@ControllerAdvice +public class GlobalModelAttributes { + + private final SecurityUtils securityUtils; + + public GlobalModelAttributes(SecurityUtils securityUtils) { + this.securityUtils = securityUtils; + } + + @ModelAttribute + public void addGlobalAttributes(Model model) { + + boolean isLoggedIn = false; + + try { + securityUtils.getCurrentUser(); + isLoggedIn = true; + } catch (Exception ignored) {} + + model.addAttribute("isLoggedIn", isLoggedIn); + } +} \ No newline at end of file From 817804a5f21060acb3812830a0931afd5d9ba837 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:10:46 +0200 Subject: [PATCH 22/48] feat(layout): add conditional navbar and success toast --- src/main/jte/layout.jte | 76 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/src/main/jte/layout.jte b/src/main/jte/layout.jte index df6b8d4..8731da5 100644 --- a/src/main/jte/layout.jte +++ b/src/main/jte/layout.jte @@ -1,5 +1,7 @@ +@import org.springframework.security.core.context.SecurityContextHolder @param String title @param gg.jte.Content content +@param String success @@ -31,9 +33,29 @@
@@ -52,5 +74,55 @@ + + @if(success != null) +
+ +
+ + +
+ + +
+ + +
+ ${success} +
+ + + +
+ + +
+
+
+@endif + + + + \ No newline at end of file From bf89d3b73528c610ea4094b7a7bb3f1d95a22fc4 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:12:29 +0200 Subject: [PATCH 23/48] feat(login): add token form and error handling --- src/main/jte/login.jte | 46 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/main/jte/login.jte b/src/main/jte/login.jte index e0dfa47..7844cca 100644 --- a/src/main/jte/login.jte +++ b/src/main/jte/login.jte @@ -1,38 +1,68 @@ +@param String error +@param String tokenError +@param String success @template.layout(title = "Login", content = @`

Login

- + @if(error != null) +
+ Invalid username or password +
+ @endif
- - + + +
- +
+ + +

+ Don’t have an account? + + Create account here + +

Got a token?

-
-

Login with token

+ @if(tokenError != null) +
+ Invalid token +
+ @endif + + +

View report with token

+ + placeholder="Enter your token" + class="w-full px-3 py-2 rounded bg-white text-black" /> + +
-`) \ No newline at end of file +`, success = success) \ No newline at end of file From 27ef41968269681b9747c181d88304b6b0a69b12 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:15:44 +0200 Subject: [PATCH 24/48] feat(tickets): add my tickets view --- src/main/jte/my-tickets.jte | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/main/jte/my-tickets.jte diff --git a/src/main/jte/my-tickets.jte b/src/main/jte/my-tickets.jte new file mode 100644 index 0000000..6717366 --- /dev/null +++ b/src/main/jte/my-tickets.jte @@ -0,0 +1,38 @@ +@param java.util.List tickets +@param String success + +@template.layout(title = "My tickets", content = @` + +

My tickets

+ +@if(tickets.isEmpty()) +

No tickets yet.

+@else +
+ @for (var ticket : tickets) +
+ +

+ ${ticket.getTitle()} +

+ +

+ ${ticket.getDescription()} +

+ +

+ Created: ${ticket.getFormattedCreatedAt()} +

+

Status: ${ticket.getStatus()}

+ + + View ticket → + + +
+ @endfor +
+@endif + +`, success = success) \ No newline at end of file From c1a85b7c2ae021fd3d249465c5c177deb5050fc7 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:17:19 +0200 Subject: [PATCH 25/48] feat(tickets): pass success message to preview page --- src/main/jte/previewTicket.jte | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/jte/previewTicket.jte b/src/main/jte/previewTicket.jte index 7620e90..ba03a11 100644 --- a/src/main/jte/previewTicket.jte +++ b/src/main/jte/previewTicket.jte @@ -1,4 +1,5 @@ @param org.example.alfs.dto.ticket.TicketCreateDTO ticket +@param String success @template.layout(title = "Preview", content = @` @@ -13,21 +14,12 @@
-
+ -
- -
- - - -
@@ -35,4 +27,4 @@ -`) \ No newline at end of file +`, success = success) \ No newline at end of file From c7eb1b769ba1f73bcb760c21cb9c24f442cb8c96 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:18:38 +0200 Subject: [PATCH 26/48] fix(security): allow access to logout endpoint --- src/main/java/org/example/alfs/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/example/alfs/config/SecurityConfig.java b/src/main/java/org/example/alfs/config/SecurityConfig.java index 3273067..e2565e7 100644 --- a/src/main/java/org/example/alfs/config/SecurityConfig.java +++ b/src/main/java/org/example/alfs/config/SecurityConfig.java @@ -41,6 +41,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/login").permitAll() .requestMatchers("/auth/signup").permitAll() + .requestMatchers("/auth/logout").permitAll() .requestMatchers("/auth/hash").permitAll() .requestMatchers("/h2-console/**").permitAll() .requestMatchers("/startPage", "/").permitAll() From 777313cfacb3b511340ff4f4c3081c6a247246aa Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:19:56 +0200 Subject: [PATCH 27/48] style(auth): improve signup page layout and styling --- src/main/jte/signup.jte | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/jte/signup.jte b/src/main/jte/signup.jte index 8e441f9..cf43aa4 100644 --- a/src/main/jte/signup.jte +++ b/src/main/jte/signup.jte @@ -1,17 +1,37 @@ -

Sign up

+@param org.example.alfs.dto.auth.SignupRequestDTO request +@param String success + +@template.layout(title = "Sign up", content = @` + +

Sign up

+ +
-
- - + +
- - + +
- + + +

+ Already have an account? + + Login + +

+
-Already have an account? Login \ No newline at end of file +`, success = success) \ No newline at end of file From 264bb624c50f96b9f03989567404b7a1916d5354 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:21:47 +0200 Subject: [PATCH 28/48] feat(home): enable success toast on start page --- src/main/jte/startPage.jte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/jte/startPage.jte b/src/main/jte/startPage.jte index 799efca..f37fafa 100644 --- a/src/main/jte/startPage.jte +++ b/src/main/jte/startPage.jte @@ -1,5 +1,7 @@ +@param String success @template.layout(title = "Start Page", content = @` +
@@ -49,4 +51,4 @@
-`) \ No newline at end of file +`, success = success) \ No newline at end of file From 6f3734024ccee12386fd87ebf358806e600cf39a Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:22:33 +0200 Subject: [PATCH 29/48] Add Model parameter to start page handler --- .../java/org/example/alfs/controllers/StartPageController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/controllers/StartPageController.java b/src/main/java/org/example/alfs/controllers/StartPageController.java index 3259f25..a41a840 100644 --- a/src/main/java/org/example/alfs/controllers/StartPageController.java +++ b/src/main/java/org/example/alfs/controllers/StartPageController.java @@ -1,13 +1,14 @@ package org.example.alfs.controllers; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @Controller public class StartPageController { @GetMapping("/") - public String startPage(){ + public String startPage(Model model){ return "startPage"; } } From bac8b3d8216ca217fa629cca73bfee10894ac748 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:22:54 +0200 Subject: [PATCH 30/48] Adds styling --- src/main/resources/static/css/style.css | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index 93091c2..98da827 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -1,11 +1,11 @@ - +@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap'); :root { --state-blue: #1a2b49; --state-gold: #b39359; --state-gray: #f4f4f4; } body { - font-family: Georgia, serif; + font-family: "Roboto", sans-serif; background-color: var(--state-gray); color: #333; } @@ -25,3 +25,11 @@ body { border: 1px solid #ddd; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); } +@keyframes toastProgress { + from { width: 100%; } + to { width: 0%; } +} + +.animate-toast { + animation: toastProgress 3s linear forwards; +} \ No newline at end of file From 0d970155eaa93a5163445140d22ec646b3bd5036 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:23:16 +0200 Subject: [PATCH 31/48] Change reportToken to have nullable true --- src/main/java/org/example/alfs/entities/Ticket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/entities/Ticket.java b/src/main/java/org/example/alfs/entities/Ticket.java index 6aef19e..6db2207 100644 --- a/src/main/java/org/example/alfs/entities/Ticket.java +++ b/src/main/java/org/example/alfs/entities/Ticket.java @@ -36,7 +36,7 @@ public class Ticket { @Column(nullable = false, length = 32) private TicketStatus status; - @Column(nullable = false, unique = true, length = 128, updatable = false) + @Column(nullable = true, unique = true, length = 128, updatable = false) private String reporterToken; private LocalDateTime createdAt; From b4dfd490ec9bf098d9125a4455397268c4fbd871 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:24:44 +0200 Subject: [PATCH 32/48] feat(tickets): add ticket-created page with token display --- src/main/jte/ticket-created.jte | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/jte/ticket-created.jte diff --git a/src/main/jte/ticket-created.jte b/src/main/jte/ticket-created.jte new file mode 100644 index 0000000..9b5a453 --- /dev/null +++ b/src/main/jte/ticket-created.jte @@ -0,0 +1,24 @@ +@param String token +@param String success + +@template.layout(title = "Ticket Created", content = @` + +

Ditt ärende är skapat!

+ +
+ +

+ Spara denna kod: + ${token} +

+ +

+ + Gå till ditt ärende + +

+ +
+ +`, success = success) \ No newline at end of file From 01ae94606b08f8bb200fd5e6de32aa62f810ba0b Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:26:08 +0200 Subject: [PATCH 33/48] feat(tickets): improve ticket creation flow with success feedback and token handling --- .../alfs/controllers/TicketController.java | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/example/alfs/controllers/TicketController.java b/src/main/java/org/example/alfs/controllers/TicketController.java index d7b526c..0701b52 100644 --- a/src/main/java/org/example/alfs/controllers/TicketController.java +++ b/src/main/java/org/example/alfs/controllers/TicketController.java @@ -1,5 +1,7 @@ package org.example.alfs.controllers; - +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; import jakarta.validation.Valid; import org.example.alfs.dto.ticket.TicketAssignDTO; import org.example.alfs.dto.ticket.TicketCreateDTO; @@ -36,54 +38,47 @@ public String createNewTicketForm(Model model) { //@PreAuthorize("hasRole('REPORTER')") // should change later for anonymous access @PostMapping("/create") - public String createNewTicket(@ModelAttribute("ticket") @Valid TicketCreateDTO ticketCreateDTO, BindingResult bindingResult, Model model) { + public String createNewTicket( + @ModelAttribute("ticket") @Valid TicketCreateDTO dto, + BindingResult bindingResult, + Model model, + RedirectAttributes redirectAttributes + ) { if (bindingResult.hasErrors()) { - model.addAttribute("ticket", ticketCreateDTO); + model.addAttribute("ticket", dto); return "create"; } - TicketViewDTO ticket = ticketService.createNewTicket(ticketCreateDTO); - - return "redirect:/tickets/" + ticket.getId(); + TicketViewDTO ticket = ticketService.createNewTicket(dto); + redirectAttributes.addFlashAttribute("success", "Ticket created successfully"); - } - //Preview ticket - @PostMapping("/previewTicket") - public String previewTicket(@ModelAttribute("ticket") TicketCreateDTO dto, Model model) { + if (ticket.getToken() != null) { + return "redirect:/tickets/ticket-created?token=" + ticket.getToken(); + } - model.addAttribute("ticket", dto); - return "previewTicket"; + return "redirect:/tickets/" + ticket.getId(); } - //Submit ticket anonymous - @PostMapping("/submit-anonymous") - public String submitAnonymous(@ModelAttribute TicketCreateDTO dto) { - - TicketViewDTO ticket = ticketService.createAnonymousTicket(dto); - - return "redirect:/tickets/token/" + ticket.getToken(); - } - //Submit ticket as logged in user - @PostMapping("/submit-authenticated") - public String submitAuthenticated( - @ModelAttribute TicketCreateDTO dto - ) { - TicketViewDTO ticket = ticketService.createNewTicket(dto); - - return "redirect:/tickets/" + ticket.getId(); - } //view ticket by token - @GetMapping("/token/{token}") public String viewTicketByToken(@PathVariable String token, Model model) { - TicketViewDTO ticket = ticketService.getTicketByToken(token); - model.addAttribute("ticket", ticket); + try { + TicketViewDTO ticket = ticketService.getTicketByToken(token); + model.addAttribute("ticket", ticket); + return "view"; - return "view"; + } catch (ResponseStatusException ex) { + + if (ex.getStatusCode() == HttpStatus.NOT_FOUND) { + return "redirect:/login?tokenError=true"; + } + + throw ex; + } } //view ticket by id @@ -135,6 +130,13 @@ public String myAssignedTickets(Model model) { return "assigned-tickets"; } + + @GetMapping("/ticket-created") + public String ticketCreated(@RequestParam String token, Model model) { + model.addAttribute("token", token); + return "ticket-created"; + } + //create comment //View comment //upload attachment From 44ce2962790341ab63ba787d0dc6ca2c8ef59a83 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:27:01 +0200 Subject: [PATCH 34/48] feat(tickets): support anonymous ticket creation with reporter token --- .../example/alfs/services/TicketService.java | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index ce91642..ac49a1e 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -38,44 +38,42 @@ public TicketService(TicketRepository ticketRepository, } //createNewTicket - public TicketViewDTO createNewTicket(TicketCreateDTO ticketCreateDTO) { - - Ticket ticket = new Ticket(); - - ticket.setTitle(ticketCreateDTO.getTitle()); - ticket.setDescription(ticketCreateDTO.getDescription()); - - User user = requireCurrentUser(); - ticket.setReporter(user); - - Ticket savedTicket = ticketRepository.save(ticket); - - return ticketMapper.entityToViewDTO(savedTicket); - } - - //Create anonymous ticket - // This token is currently stored in plain text for simplicity. - //todo hash token - public TicketViewDTO createAnonymousTicket(TicketCreateDTO dto) { + public TicketViewDTO createNewTicket(TicketCreateDTO dto) { Ticket ticket = new Ticket(); ticket.setTitle(dto.getTitle()); ticket.setDescription(dto.getDescription()); - String rawToken = java.util.UUID.randomUUID().toString(); + User user = null; + + try { + user = requireCurrentUser(); + } catch (ResponseStatusException ex) { + user = null; + } - ticket.setReporterToken(rawToken); + String token = null; + + if (user != null) { + ticket.setReporter(user); + } else { + token = java.util.UUID.randomUUID().toString(); + ticket.setReporterToken(token); + } Ticket saved = ticketRepository.save(ticket); TicketViewDTO view = ticketMapper.entityToViewDTO(saved); - view.setToken(rawToken); + if (token != null) { + view.setToken(token); + } return view; } + // View by token public TicketViewDTO getTicketByToken(String token) { Ticket ticket = ticketRepository.findByReporterToken(token) From 0b784ee76895d0f998738601d8a882841e974858 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:27:25 +0200 Subject: [PATCH 35/48] feat(tickets): add formatted createdAt helper in TicketViewDTO --- .../java/org/example/alfs/dto/ticket/TicketViewDTO.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java b/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java index 3e99ed0..d84539f 100644 --- a/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java +++ b/src/main/java/org/example/alfs/dto/ticket/TicketViewDTO.java @@ -2,7 +2,7 @@ import lombok.Data; import org.example.alfs.enums.TicketStatus; - +import java.time.format.DateTimeFormatter; import java.time.LocalDateTime; /* @@ -24,4 +24,10 @@ public class TicketViewDTO { private LocalDateTime createdAt; private Long assignedInvestigatorId; + + public String getFormattedCreatedAt() { + if (createdAt == null) return ""; + + return createdAt.format(DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm")); + } } From fa034bd45f86760aaf1a41e2b63d49b7ba837da1 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:27:47 +0200 Subject: [PATCH 36/48] add success toast support to ticket view page --- src/main/jte/view.jte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/jte/view.jte b/src/main/jte/view.jte index 60b2417..06964a5 100644 --- a/src/main/jte/view.jte +++ b/src/main/jte/view.jte @@ -1,4 +1,5 @@ @param org.example.alfs.dto.ticket.TicketViewDTO ticket +@param String success @template.layout(title = "View Ticket", content = @`

Ticket

@@ -20,4 +21,4 @@ Status: ${ticket.getStatus()}
-`) \ No newline at end of file +`, success = success) \ No newline at end of file From c55e12a1277830e8e145e63f0afe60fe8a2f51f3 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 11:33:52 +0200 Subject: [PATCH 37/48] feat: improve create ticket UX based on login state --- .../example/alfs/config/GlobalModelAttributes.java | 5 ++++- src/main/jte/create.jte | 14 +++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/alfs/config/GlobalModelAttributes.java b/src/main/java/org/example/alfs/config/GlobalModelAttributes.java index 0c4b7a6..5174c6b 100644 --- a/src/main/java/org/example/alfs/config/GlobalModelAttributes.java +++ b/src/main/java/org/example/alfs/config/GlobalModelAttributes.java @@ -17,12 +17,15 @@ public GlobalModelAttributes(SecurityUtils securityUtils) { public void addGlobalAttributes(Model model) { boolean isLoggedIn = false; + String username = null; try { - securityUtils.getCurrentUser(); + var user = securityUtils.getCurrentUser(); isLoggedIn = true; + username = user.getUsername(); } catch (Exception ignored) {} model.addAttribute("isLoggedIn", isLoggedIn); + model.addAttribute("username", username); } } \ No newline at end of file diff --git a/src/main/jte/create.jte b/src/main/jte/create.jte index c52fd31..0441912 100644 --- a/src/main/jte/create.jte +++ b/src/main/jte/create.jte @@ -1,8 +1,20 @@ @param org.example.alfs.dto.ticket.TicketCreateDTO ticket @param String success +@param Boolean isLoggedIn +@param String username @template.layout(title = "Create Ticket", content = @`

Create Ticket

- + @if(!isLoggedIn) +
+ This ticket will be created anonymously. + Sign in to attach it to your account. +
+ @else +
+ This ticket will be created as ${username}. + To create anonymously, please sign out first. +
+ @endif
Date: Thu, 16 Apr 2026 11:42:11 +0200 Subject: [PATCH 38/48] fix(tickets): handle anonymous user safely in ticket creation - replace exception-based user lookup with getCurrentUserOrNull - prevent NullPointerException in createNewTicket - fix failing tests for unauthenticated and missing users --- .../java/org/example/alfs/security/SecurityUtils.java | 8 ++++++++ .../java/org/example/alfs/services/TicketService.java | 8 +------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/example/alfs/security/SecurityUtils.java b/src/main/java/org/example/alfs/security/SecurityUtils.java index b2ed239..f5c1e44 100644 --- a/src/main/java/org/example/alfs/security/SecurityUtils.java +++ b/src/main/java/org/example/alfs/security/SecurityUtils.java @@ -27,4 +27,12 @@ public User getCurrentUser() { return userRepository.findByUsername(username) .orElseThrow(() -> new RuntimeException("Authenticated user not found in database")); } + + public User getCurrentUserOrNull() { + try { + return getCurrentUser(); + } catch (RuntimeException ex) { + return null; + } + } } \ No newline at end of file diff --git a/src/main/java/org/example/alfs/services/TicketService.java b/src/main/java/org/example/alfs/services/TicketService.java index ac49a1e..aa5349c 100644 --- a/src/main/java/org/example/alfs/services/TicketService.java +++ b/src/main/java/org/example/alfs/services/TicketService.java @@ -45,13 +45,7 @@ public TicketViewDTO createNewTicket(TicketCreateDTO dto) { ticket.setTitle(dto.getTitle()); ticket.setDescription(dto.getDescription()); - User user = null; - - try { - user = requireCurrentUser(); - } catch (ResponseStatusException ex) { - user = null; - } + User user = securityUtils.getCurrentUserOrNull(); String token = null; From 4486ddfb912b3848f515059bd80947a9711b7f0e Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 16 Apr 2026 14:18:07 +0200 Subject: [PATCH 39/48] Refactor `TicketServiceTest` to use nested class for `createNewTicket` tests and add test for anonymous reporter scenario --- .../alfs/services/TicketServiceTest.java | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/src/test/java/org/example/alfs/services/TicketServiceTest.java b/src/test/java/org/example/alfs/services/TicketServiceTest.java index a85eae1..534b5a6 100644 --- a/src/test/java/org/example/alfs/services/TicketServiceTest.java +++ b/src/test/java/org/example/alfs/services/TicketServiceTest.java @@ -71,28 +71,60 @@ private User reporterUser() { return u; } - @Test - @DisplayName("createNewTicket should set reporter as user") - void createNewTicket_shouldSetReporterAsUser() { - // Arrange - TicketCreateDTO dto = new TicketCreateDTO(); - dto.setTitle("Test Ticket"); - dto.setDescription("This is a test ticket"); - User reporter = reporterUser(); - - when(securityUtils.getCurrentUser()).thenReturn(reporter); - when(ticketRepository.save(any())).thenAnswer(i -> i.getArgument(0)); - when(ticketMapper.entityToViewDTO(any())).thenReturn(new TicketViewDTO()); - - // Act - ticketService.createNewTicket(dto); - - // Assert - verify(ticketRepository).save(argThat(ticket -> - ticket.getReporter().equals(reporter) && - ticket.getTitle().equals("Test Ticket") && - ticket.getDescription().equals("This is a test ticket") - )); + @Nested + @DisplayName("createNewTicket tests") + class CreateNewTicketTests { + + @Test + @DisplayName("createNewTicket should set reporter as user") + void createNewTicket_shouldSetReporterAsUser() { + // Arrange + TicketCreateDTO dto = new TicketCreateDTO(); + dto.setTitle("Test Ticket"); + dto.setDescription("This is a test ticket"); + User reporter = reporterUser(); + + TicketViewDTO viewDTO = new TicketViewDTO(); + + when(securityUtils.getCurrentUserOrNull()).thenReturn(reporter); + when(ticketRepository.save(any())).thenAnswer(i -> i.getArgument(0)); + when(ticketMapper.entityToViewDTO(any())).thenReturn(viewDTO); + + // Act + ticketService.createNewTicket(dto); + + // Assert + verify(ticketRepository).save(argThat(ticket -> + reporter.equals(ticket.getReporter()) && + "Test Ticket".equals(ticket.getTitle()) && + "This is a test ticket".equals(ticket.getDescription()) + )); + } + + @Test + @DisplayName("createNewTicket should set reporter token when user is not authenticated") + void createNewTicket_shouldSetReporterToken_whenAnonymous() { + // Arrange + TicketCreateDTO dto = new TicketCreateDTO(); + dto.setTitle("Anonymous Ticket"); + dto.setDescription("Filed anonymously"); + + TicketViewDTO viewDTO = new TicketViewDTO(); + + when(securityUtils.getCurrentUserOrNull()).thenReturn(null); + when(ticketRepository.save(any())).thenAnswer(i -> i.getArgument(0)); + when(ticketMapper.entityToViewDTO(any())).thenReturn(viewDTO); + + // Act + TicketViewDTO result = ticketService.createNewTicket(dto); + + // Assert + verify(ticketRepository).save(argThat(ticket -> + ticket.getReporter() == null && + ticket.getReporterToken() != null + )); + assertNotNull(result.getToken()); + } } @Nested From 0230126ecc3a775b83608fd5ba57c516cea23b62 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 16 Apr 2026 14:31:01 +0200 Subject: [PATCH 40/48] Add verification that `getMyTickets` does not call repository for unauthorized users --- .../java/org/example/alfs/services/TicketServiceTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/example/alfs/services/TicketServiceTest.java b/src/test/java/org/example/alfs/services/TicketServiceTest.java index 534b5a6..cf13d05 100644 --- a/src/test/java/org/example/alfs/services/TicketServiceTest.java +++ b/src/test/java/org/example/alfs/services/TicketServiceTest.java @@ -506,10 +506,11 @@ void noAuthenticatedUser_shouldThrowUnauthorized() { // Act ResponseStatusException ex = assertThrows(ResponseStatusException.class, () -> - ticketService.createNewTicket(new TicketCreateDTO())); + ticketService.getMyTickets()); // Assert assertEquals(HttpStatus.UNAUTHORIZED, ex.getStatusCode()); + verify(ticketRepository, never()).findByReporterId(any()); } @Test @@ -521,10 +522,11 @@ void userNotFoundInDatabase_shouldThrowUnauthorized() { // Act ResponseStatusException ex = assertThrows(ResponseStatusException.class, () -> - ticketService.createNewTicket(new TicketCreateDTO())); + ticketService.getMyTickets()); // Assert assertEquals(HttpStatus.UNAUTHORIZED, ex.getStatusCode()); + verify(ticketRepository, never()).findByReporterId(any()); } } From cc4e5c96e17cdbf933e5b51a80163ae60dc61b91 Mon Sep 17 00:00:00 2001 From: Simon Forsberg Date: Thu, 16 Apr 2026 14:59:34 +0200 Subject: [PATCH 41/48] Enhance `createNewTicket` tests in `TicketServiceTest` by adding assertions for reporter token and using `ArgumentCaptor` for better validation of saved entity --- .../example/alfs/services/TicketServiceTest.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/example/alfs/services/TicketServiceTest.java b/src/test/java/org/example/alfs/services/TicketServiceTest.java index cf13d05..2a48ef1 100644 --- a/src/test/java/org/example/alfs/services/TicketServiceTest.java +++ b/src/test/java/org/example/alfs/services/TicketServiceTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -91,14 +92,16 @@ void createNewTicket_shouldSetReporterAsUser() { when(ticketMapper.entityToViewDTO(any())).thenReturn(viewDTO); // Act - ticketService.createNewTicket(dto); + TicketViewDTO result = ticketService.createNewTicket(dto); // Assert verify(ticketRepository).save(argThat(ticket -> reporter.equals(ticket.getReporter()) && + ticket.getReporterToken() == null && "Test Ticket".equals(ticket.getTitle()) && "This is a test ticket".equals(ticket.getDescription()) )); + assertNull(result.getToken()); } @Test @@ -116,14 +119,15 @@ void createNewTicket_shouldSetReporterToken_whenAnonymous() { when(ticketMapper.entityToViewDTO(any())).thenReturn(viewDTO); // Act + ArgumentCaptor ticketCaptor = ArgumentCaptor.forClass(Ticket.class); TicketViewDTO result = ticketService.createNewTicket(dto); // Assert - verify(ticketRepository).save(argThat(ticket -> - ticket.getReporter() == null && - ticket.getReporterToken() != null - )); - assertNotNull(result.getToken()); + verify(ticketRepository).save(ticketCaptor.capture()); + Ticket saved = ticketCaptor.getValue(); + assertNull(saved.getReporter()); + assertNotNull(saved.getReporterToken()); + assertEquals(saved.getReporterToken(), result.getToken()); } } From 33cfc79056998b52b85260c0aade87141094fa8d Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 15:37:37 +0200 Subject: [PATCH 42/48] fix: narrow exception handling in GlobalModelAttributes --- .../org/example/alfs/config/GlobalModelAttributes.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/alfs/config/GlobalModelAttributes.java b/src/main/java/org/example/alfs/config/GlobalModelAttributes.java index 5174c6b..cf9cf9d 100644 --- a/src/main/java/org/example/alfs/config/GlobalModelAttributes.java +++ b/src/main/java/org/example/alfs/config/GlobalModelAttributes.java @@ -1,4 +1,6 @@ package org.example.alfs.config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.example.alfs.security.SecurityUtils; import org.springframework.ui.Model; @@ -6,7 +8,7 @@ @ControllerAdvice public class GlobalModelAttributes { - + private static final Logger log = LoggerFactory.getLogger(GlobalModelAttributes.class); private final SecurityUtils securityUtils; public GlobalModelAttributes(SecurityUtils securityUtils) { @@ -23,7 +25,9 @@ public void addGlobalAttributes(Model model) { var user = securityUtils.getCurrentUser(); isLoggedIn = true; username = user.getUsername(); - } catch (Exception ignored) {} + } catch (RuntimeException ex) { + log.debug("Could not resolve current user for global model attributes", ex); + } model.addAttribute("isLoggedIn", isLoggedIn); model.addAttribute("username", username); From 9c0b3d0e509d52b0ba9536581036b5716a9de41c Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 15:37:45 +0200 Subject: [PATCH 43/48] fix: only return null for auth failures in getCurrentUserOrNull --- .../org/example/alfs/security/SecurityUtils.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/alfs/security/SecurityUtils.java b/src/main/java/org/example/alfs/security/SecurityUtils.java index f5c1e44..9d76be1 100644 --- a/src/main/java/org/example/alfs/security/SecurityUtils.java +++ b/src/main/java/org/example/alfs/security/SecurityUtils.java @@ -32,7 +32,18 @@ public User getCurrentUserOrNull() { try { return getCurrentUser(); } catch (RuntimeException ex) { - return null; + + String message = ex.getMessage(); + + boolean authFailure = + "No authenticated user in security context".equals(message) || + "Authenticated user not found in database".equals(message); + + if (authFailure) { + return null; + } + + throw ex; } } } \ No newline at end of file From 2ee1dc1545f75cdca8231ec729be357292445ba1 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 15:37:52 +0200 Subject: [PATCH 44/48] fix: encode token in URL to prevent invalid routing --- src/main/jte/login.jte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/jte/login.jte b/src/main/jte/login.jte index 7844cca..ba43e02 100644 --- a/src/main/jte/login.jte +++ b/src/main/jte/login.jte @@ -47,7 +47,7 @@ @endif

View report with token

From 2ba8839277e7a54ee720938504c1e222225d7763 Mon Sep 17 00:00:00 2001 From: Adam Ottosson Date: Thu, 16 Apr 2026 15:37:59 +0200 Subject: [PATCH 45/48] fix: remove duplicate JS toast animation and rely on CSS --- src/main/jte/layout.jte | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/jte/layout.jte b/src/main/jte/layout.jte index 8731da5..78407ed 100644 --- a/src/main/jte/layout.jte +++ b/src/main/jte/layout.jte @@ -107,15 +107,8 @@