Skip to content

Commit dd96a5c

Browse files
Functioning Error handeling like in API required
1 parent 7dada40 commit dd96a5c

File tree

6 files changed

+209
-17
lines changed

6 files changed

+209
-17
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package de.tum.aet.devops25.usersvc;
2+
3+
import java.io.IOException;
4+
import java.time.OffsetDateTime;
5+
6+
import org.springframework.http.MediaType;
7+
import org.springframework.security.core.AuthenticationException;
8+
import org.springframework.security.web.AuthenticationEntryPoint;
9+
import org.springframework.stereotype.Component;
10+
11+
import com.fasterxml.jackson.databind.ObjectMapper;
12+
13+
import de.tum.aet.devops25.api.generated.model.ErrorResponse;
14+
import jakarta.servlet.ServletException;
15+
import jakarta.servlet.http.HttpServletRequest;
16+
import jakarta.servlet.http.HttpServletResponse;
17+
18+
@Component
19+
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
20+
21+
private final ObjectMapper objectMapper;
22+
23+
public CustomAuthenticationEntryPoint(ObjectMapper objectMapper) {
24+
this.objectMapper = objectMapper;
25+
}
26+
27+
@Override
28+
public void commence(HttpServletRequest request, HttpServletResponse response,
29+
AuthenticationException authException) throws IOException, ServletException {
30+
31+
String requestURI = request.getRequestURI();
32+
String authHeader = request.getHeader("Authorization");
33+
34+
ErrorResponse error;
35+
if (authHeader == null || authHeader.isEmpty()) {
36+
error = new ErrorResponse()
37+
.error("MISSING_TOKEN")
38+
.message("Authentication token is required")
39+
.path(requestURI)
40+
.status(401)
41+
.timestamp(OffsetDateTime.now());
42+
} else {
43+
error = new ErrorResponse()
44+
.error("INVALID_TOKEN")
45+
.message("Invalid or expired authentication token")
46+
.path(requestURI)
47+
.status(401)
48+
.timestamp(OffsetDateTime.now());
49+
}
50+
51+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
52+
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
53+
response.getWriter().write(objectMapper.writeValueAsString(error));
54+
}
55+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package de.tum.aet.devops25.usersvc;
2+
3+
import java.time.OffsetDateTime;
4+
5+
import org.springframework.http.HttpStatus;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.ControllerAdvice;
8+
import org.springframework.web.bind.annotation.ExceptionHandler;
9+
import org.springframework.web.context.request.WebRequest;
10+
11+
import de.tum.aet.devops25.api.generated.model.ErrorResponse;
12+
13+
@ControllerAdvice
14+
public class GlobalExceptionHandler {
15+
16+
@ExceptionHandler(UserAlreadyExistsException.class)
17+
public ResponseEntity<ErrorResponse> handleUserAlreadyExistsException(UserAlreadyExistsException ex, WebRequest request) {
18+
ErrorResponse error = new ErrorResponse()
19+
.error("USER_ALREADY_EXISTS")
20+
.message(ex.getMessage())
21+
.path(request.getDescription(false).replace("uri=", ""))
22+
.status(409)
23+
.timestamp(OffsetDateTime.now());
24+
25+
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
26+
}
27+
28+
@ExceptionHandler(RuntimeException.class)
29+
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex, WebRequest request) {
30+
ErrorResponse error = new ErrorResponse()
31+
.error("RUNTIME_ERROR")
32+
.message(ex.getMessage())
33+
.path(request.getDescription(false).replace("uri=", ""))
34+
.status(500)
35+
.timestamp(OffsetDateTime.now());
36+
37+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
38+
}
39+
40+
@ExceptionHandler(Exception.class)
41+
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex, WebRequest request) {
42+
ErrorResponse error = new ErrorResponse()
43+
.error("INTERNAL_SERVER_ERROR")
44+
.message("An unexpected error occurred")
45+
.path(request.getDescription(false).replace("uri=", ""))
46+
.status(500)
47+
.timestamp(OffsetDateTime.now());
48+
49+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
50+
}
51+
52+
@ExceptionHandler(IllegalArgumentException.class)
53+
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) {
54+
ErrorResponse error = new ErrorResponse()
55+
.error("VALIDATION_ERROR")
56+
.message(ex.getMessage())
57+
.path(request.getDescription(false).replace("uri=", ""))
58+
.status(400)
59+
.timestamp(OffsetDateTime.now());
60+
61+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
62+
}
63+
}

user-svc/src/main/java/de/tum/aet/devops25/usersvc/JwtAuthenticationFilter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
88
import org.springframework.security.core.context.SecurityContextHolder;
99
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
10+
import org.springframework.stereotype.Component;
1011
import org.springframework.util.StringUtils;
1112
import org.springframework.web.filter.OncePerRequestFilter;
1213

@@ -18,6 +19,7 @@
1819
import jakarta.servlet.http.HttpServletRequest;
1920
import jakarta.servlet.http.HttpServletResponse;
2021

22+
@Component
2123
public class JwtAuthenticationFilter extends OncePerRequestFilter {
2224

2325
private static final String JWT_SECRET = "my-super-long-and-secure-secret-key-1234567890!@#$"; // Use env var in production
@@ -27,6 +29,7 @@ protected void doFilterInternal(HttpServletRequest request,
2729
HttpServletResponse response,
2830
FilterChain filterChain) throws ServletException, IOException {
2931
String header = request.getHeader("Authorization");
32+
3033
if (StringUtils.hasText(header) && header.startsWith("Bearer ")) {
3134
String token = header.substring(7);
3235
try {
@@ -46,9 +49,11 @@ protected void doFilterInternal(HttpServletRequest request,
4649
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
4750
SecurityContextHolder.getContext().setAuthentication(authentication);
4851
} catch (Exception e) {
49-
// Invalid token, do nothing (request will be rejected by security)
52+
// Invalid token - let Spring Security handle the error
53+
// The CustomAuthenticationEntryPoint will be called
5054
}
5155
}
56+
5257
filterChain.doFilter(request, response);
5358
}
5459
}
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,43 @@
11
package de.tum.aet.devops25.usersvc;
22

3+
import org.springframework.beans.factory.annotation.Autowired;
34
import org.springframework.context.annotation.Bean;
45
import org.springframework.context.annotation.Configuration;
56
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7+
import org.springframework.security.config.http.SessionCreationPolicy;
68
import org.springframework.security.web.SecurityFilterChain;
79
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
810

911
@Configuration
1012
public class SecurityConfig {
1113

14+
@Autowired
15+
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
16+
17+
@Autowired
18+
private JwtAuthenticationFilter jwtAuthenticationFilter;
19+
1220
@Bean
1321
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
1422
http
1523
.csrf(csrf -> csrf.disable()) // Disable CSRF for APIs
1624
.headers(headers -> headers
1725
.frameOptions(frameOptions -> frameOptions.sameOrigin()) // Allow frames from same origin
1826
)
19-
.formLogin(form -> form.disable()) // <--- Add this line
20-
.httpBasic(httpBasic -> httpBasic.disable()) // <--- And this line
27+
.formLogin(form -> form.disable()) // Disable form login
28+
.httpBasic(httpBasic -> httpBasic.disable()) // Disable HTTP basic auth
29+
.sessionManagement(session -> session
30+
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // Make it stateless
2131
.authorizeHttpRequests(authz -> authz
2232
.requestMatchers("/", "/api/users/register", "/api/users/login", "/health").permitAll()
2333
.anyRequest().authenticated()
2434
)
35+
// Use custom authentication entry point
36+
.exceptionHandling(exceptionHandling -> exceptionHandling
37+
.authenticationEntryPoint(customAuthenticationEntryPoint)
38+
)
2539
// Register your JWT filter here:
26-
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
40+
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
2741
return http.build();
2842
}
2943
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package de.tum.aet.devops25.usersvc;
2+
3+
public class UserAlreadyExistsException extends RuntimeException {
4+
5+
public UserAlreadyExistsException(String message) {
6+
super(message);
7+
}
8+
9+
public UserAlreadyExistsException(String message, Throwable cause) {
10+
super(message, cause);
11+
}
12+
}

user-svc/src/main/java/de/tum/aet/devops25/usersvc/UserController.java

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import javax.crypto.SecretKey;
1111

1212
import org.springframework.beans.factory.annotation.Autowired;
13+
import org.springframework.http.HttpStatus;
1314
import org.springframework.http.ResponseEntity;
1415
import org.springframework.security.core.Authentication;
1516
import org.springframework.security.core.context.SecurityContextHolder;
@@ -22,7 +23,9 @@
2223
import org.springframework.web.bind.annotation.RestController;
2324

2425
import de.tum.aet.devops25.api.generated.controller.UserRegistrationApi;
26+
import de.tum.aet.devops25.api.generated.model.ErrorResponse;
2527
import de.tum.aet.devops25.api.generated.model.RegisterUserRequest;
28+
import de.tum.aet.devops25.api.generated.model.UpdateUserRequest;
2629
import de.tum.aet.devops25.api.generated.model.User;
2730
import io.jsonwebtoken.Jwts;
2831
import io.jsonwebtoken.security.Keys;
@@ -45,7 +48,7 @@ public UserController(UserRepository userRepository) {
4548
public ResponseEntity<User> registerUser(RegisterUserRequest registerUserRequest) {
4649
// Check if user already exists
4750
if (userRepository.findByEmail(registerUserRequest.getEmail()).isPresent()) {
48-
return ResponseEntity.status(409).build(); // Conflict: user already exists
51+
throw new UserAlreadyExistsException("User with this email already exists");
4952
}
5053

5154
// Create and save new user
@@ -108,14 +111,26 @@ public Map<String, Object> getUserServiceHealth() {
108111
}
109112

110113
@PostMapping("/api/users/login")
111-
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest loginRequest) {
114+
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
112115
Optional<UserEntity> userOpt = userRepository.findByEmail(loginRequest.getEmail());
113116
if (userOpt.isEmpty()) {
114-
return ResponseEntity.status(401).build();
117+
ErrorResponse error = new ErrorResponse()
118+
.error("INVALID_CREDENTIALS")
119+
.message("Invalid email or password")
120+
.path("/api/users/login")
121+
.status(401)
122+
.timestamp(OffsetDateTime.now());
123+
return ResponseEntity.status(401).body(error);
115124
}
116125
UserEntity user = userOpt.get();
117126
if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPasswordHash())) {
118-
return ResponseEntity.status(401).build();
127+
ErrorResponse error = new ErrorResponse()
128+
.error("INVALID_CREDENTIALS")
129+
.message("Invalid email or password")
130+
.path("/api/users/login")
131+
.status(401)
132+
.timestamp(OffsetDateTime.now());
133+
return ResponseEntity.status(401).body(error);
119134
}
120135

121136
// Update lastLoginAt
@@ -140,13 +155,19 @@ public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest loginReques
140155
}
141156

142157
@GetMapping("/api/users/profile")
143-
public ResponseEntity<User> getProfile() {
158+
public ResponseEntity<?> getProfile() {
144159
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
145160
String userId = (String) auth.getPrincipal();
146161

147162
Optional<UserEntity> userOpt = userRepository.findById(UUID.fromString(userId));
148163
if (userOpt.isEmpty()) {
149-
return ResponseEntity.status(404).build();
164+
ErrorResponse error = new ErrorResponse()
165+
.error("USER_NOT_FOUND")
166+
.message("User not found")
167+
.path("/api/users/profile")
168+
.status(404)
169+
.timestamp(OffsetDateTime.now());
170+
return ResponseEntity.status(404).body(error);
150171
}
151172

152173
UserEntity userEntity = userOpt.get();
@@ -185,7 +206,13 @@ public ResponseEntity<?> updateProfile(@RequestBody UpdateUserRequest updateRequ
185206

186207
Optional<UserEntity> userOpt = userRepository.findById(UUID.fromString(userId));
187208
if (userOpt.isEmpty()) {
188-
return ResponseEntity.status(404).body("User not found");
209+
ErrorResponse error = new ErrorResponse()
210+
.error("USER_NOT_FOUND")
211+
.message("User not found")
212+
.path("/api/users/profile")
213+
.status(404)
214+
.timestamp(OffsetDateTime.now());
215+
return ResponseEntity.status(404).body(error);
189216
}
190217
UserEntity user = userOpt.get();
191218

@@ -196,8 +223,8 @@ public ResponseEntity<?> updateProfile(@RequestBody UpdateUserRequest updateRequ
196223
if (updateRequest.getLastName() != null) {
197224
user.setLastName(updateRequest.getLastName());
198225
}
199-
if (updateRequest.getEmail() != null) {
200-
user.setEmail(updateRequest.getEmail());
226+
if (updateRequest.getIsActive() != null) {
227+
user.setActive(updateRequest.getIsActive());
201228
}
202229
if (updateRequest.getPreferences() != null) {
203230
// Get existing preferences or create new ones
@@ -229,14 +256,30 @@ public ResponseEntity<?> updateProfile(@RequestBody UpdateUserRequest updateRequ
229256
user.setUpdatedAt(OffsetDateTime.now());
230257

231258
// Add more fields as needed
232-
userRepository.save(user);
259+
UserEntity savedUser = userRepository.save(user);
260+
261+
// Map to API User model and return
262+
User userResponse = new User()
263+
.id(savedUser.getId())
264+
.email(savedUser.getEmail())
265+
.firstName(savedUser.getFirstName())
266+
.lastName(savedUser.getLastName())
267+
.isActive(savedUser.isActive())
268+
.preferences(UserPreferencesMapper.toDto(savedUser.getPreferences()))
269+
.createdAt(savedUser.getCreatedAt())
270+
.updatedAt(savedUser.getUpdatedAt());
271+
272+
// Handle lastLoginAt properly
273+
if (savedUser.getLastLoginAt() != null) {
274+
userResponse.lastLoginAt(savedUser.getLastLoginAt());
275+
}
233276

234-
return ResponseEntity.ok("Profile updated successfully");
277+
return ResponseEntity.ok(userResponse);
235278
}
236279

237280
@PostMapping("/api/users/logout")
238281
public ResponseEntity<?> logout() {
239-
// For stateless JWT, just return 200 OK.
240-
return ResponseEntity.ok("Logged out successfully");
282+
// For stateless JWT, just return 200 OK with a simple message
283+
return ResponseEntity.ok().body("Logged out successfully");
241284
}
242285
}

0 commit comments

Comments
 (0)