diff --git a/backend/src/main/java/com/mailserver/config/SecurityConfig.java b/backend/src/main/java/com/mailserver/config/SecurityConfig.java index f05809b..75d9f82 100644 --- a/backend/src/main/java/com/mailserver/config/SecurityConfig.java +++ b/backend/src/main/java/com/mailserver/config/SecurityConfig.java @@ -2,11 +2,13 @@ package com.mailserver.config; import com.mailserver.security.JwtAuthenticationFilter; import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpStatus; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.SecurityWebFiltersOrder; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsConfigurationSource; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; @@ -42,6 +44,11 @@ public class SecurityConfig { public SecurityWebFilterChain filterChain(ServerHttpSecurity http) { http .csrf(ServerHttpSecurity.CsrfSpec::disable) + // JWT only: without this an API call with no token gets 401 + "WWW-Authenticate: Basic", + // and the browser shows its own login popup where our mailbox password does not work + .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) + .formLogin(ServerHttpSecurity.FormLoginSpec::disable) + .exceptionHandling(e -> e.authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))) .cors(cors -> cors.configurationSource(corsConfigurationSource())) .authorizeExchange(exchange -> exchange .pathMatchers("/api/login", "/api/register", "/actuator/health").permitAll() diff --git a/frontend/src/app/interceptors/auth.interceptor.ts b/frontend/src/app/interceptors/auth.interceptor.ts index c7a2330..3300264 100644 --- a/frontend/src/app/interceptors/auth.interceptor.ts +++ b/frontend/src/app/interceptors/auth.interceptor.ts @@ -1,16 +1,22 @@ -import { HttpInterceptorFn, HttpRequest, HttpHandlerFn } from '@angular/common/http'; +import { HttpErrorResponse, HttpInterceptorFn, HttpRequest, HttpHandlerFn } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { catchError, throwError } from 'rxjs'; export const authInterceptor: HttpInterceptorFn = (req: HttpRequest, next: HttpHandlerFn) => { + const router = inject(Router); const token = localStorage.getItem('token'); - - if (token) { - const cloned = req.clone({ - setHeaders: { - Authorization: `Bearer ${token}` + const request = token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : req; + + return next(request).pipe( + catchError((error: unknown) => { + // not logged in or the token expired → to the login form + if (error instanceof HttpErrorResponse && error.status === 401 && !req.url.endsWith('/login')) { + localStorage.removeItem('token'); + localStorage.removeItem('email'); + router.navigate(['/login']); } - }); - return next(cloned); - } - - return next(req); + return throwError(() => error); + }) + ); };