From e1427c062cca7f85e4ab42e9ec7d5ebad6935d2f Mon Sep 17 00:00:00 2001 From: Liosha84 Date: Thu, 5 Nov 2026 15:33:58 +0100 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=85=D0=BE=D0=B4:=20=D0=B1=D0=B5=D0=B7?= =?UTF-8?q?=20=D0=B2=D1=81=D0=BF=D0=BB=D1=8B=D0=B2=D0=B0=D1=8E=D1=89=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=BE=D0=BA=D0=BD=D0=B0=20=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=83=D0=B7=D0=B5=D1=80=D0=B0=20=E2=80=94=20API=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B2=D0=B5=D1=87=D0=B0=D0=B5=D1=82=20401=20=D0=B1=D0=B5=D0=B7?= =?UTF-8?q?=20Basic,=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9?= =?UTF-8?q?=D1=81=20=D1=83=D0=B2=D0=BE=D0=B4=D0=B8=D1=82=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D1=83=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit α Co-Authored-By: Claude Opus 5.5 --- .../com/mailserver/config/SecurityConfig.java | 7 +++++ .../src/app/interceptors/auth.interceptor.ts | 28 +++++++++++-------- 2 files changed, 24 insertions(+), 11 deletions(-) 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); + }) + ); };