diff --git a/src/app/interceptors/auth-token.interceptor.ts b/src/app/interceptors/auth-token.interceptor.ts
index 79ddf82..731d6e1 100644
--- a/src/app/interceptors/auth-token.interceptor.ts
+++ b/src/app/interceptors/auth-token.interceptor.ts
@@ -1,27 +1,61 @@
-import { HttpInterceptorFn } from '@angular/common/http';
+import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { Authorization } from '@services/authorization/authorization';
+import { catchError, throwError } from 'rxjs';
+
+let lastSessionExpiredNotificationAt = 0;
+let lastServiceErrorNotificationAt = 0;
export const authTokenInterceptor: HttpInterceptorFn = (req, next) => {
const token = getToken();
- if (!token) return next(req);
-
const requestPath = req.url.split('?')[0];
const isAuthEndpoint =
requestPath.endsWith('/auth/login') ||
requestPath.endsWith('/auth/register');
- if (isAuthEndpoint) {
- return next(req);
+ const hasToken = !!token;
+
+ if (token && !isAuthEndpoint && isTokenExpired(token)) {
+ clearAuthStorage();
+ notifySessionExpired();
+ redirectToLogin();
+ return throwError(() =>
+ new HttpErrorResponse({
+ status: 401,
+ statusText: 'Session expired',
+ error: { message: 'Session expired' }
+ })
+ );
}
- if (req.headers.has('Authorization')) {
- return next(req);
- }
-
- return next(
- req.clone({
+ let requestToSend = req;
+ if (token && !isAuthEndpoint && !req.headers.has('Authorization')) {
+ requestToSend = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
+ });
+ }
+
+ return next(requestToSend).pipe(
+ catchError((error: unknown) => {
+ if (
+ hasToken &&
+ !isAuthEndpoint &&
+ error instanceof HttpErrorResponse &&
+ error.status === 401
+ ) {
+ clearAuthStorage();
+ notifySessionExpired();
+ redirectToLogin();
+ }
+
+ if (
+ error instanceof HttpErrorResponse &&
+ (error.status === 0 || error.status >= 500)
+ ) {
+ notifyServiceContactError();
+ }
+
+ return throwError(() => error);
})
);
};
@@ -40,3 +74,45 @@ function getToken(): string | null {
return null;
}
}
+
+function isTokenExpired(token: string): boolean {
+ try {
+ const payloadSegment = token.split('.')[1];
+ if (!payloadSegment) return false;
+
+ const normalized = payloadSegment.replace(/-/g, '+').replace(/_/g, '/');
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
+ const decoded = atob(padded);
+ const payload = JSON.parse(decoded) as { exp?: unknown };
+ if (typeof payload.exp !== 'number') return false;
+
+ const nowSeconds = Math.floor(Date.now() / 1000);
+ return payload.exp <= nowSeconds;
+ } catch {
+ return false;
+ }
+}
+
+function clearAuthStorage() {
+ localStorage.removeItem(Authorization.USER_STORAGE_KEY);
+ localStorage.removeItem(Authorization.TOKEN_STORAGE_KEY);
+}
+
+function notifySessionExpired() {
+ const now = Date.now();
+ if (now - lastSessionExpiredNotificationAt < 1200) return;
+ lastSessionExpiredNotificationAt = now;
+ window.alert('Sessione scaduta. Effettua di nuovo il login.');
+}
+
+function notifyServiceContactError() {
+ const now = Date.now();
+ if (now - lastServiceErrorNotificationAt < 1200) return;
+ lastServiceErrorNotificationAt = now;
+ window.alert('Error contacting service, please retry later.');
+}
+
+function redirectToLogin() {
+ if (window.location.pathname === '/login') return;
+ window.location.assign('/login');
+}
diff --git a/src/app/pages/auth/login/login.html b/src/app/pages/auth/login/login.html
index 05a13b6..cf2a60c 100644
--- a/src/app/pages/auth/login/login.html
+++ b/src/app/pages/auth/login/login.html
@@ -54,9 +54,14 @@
-
+ [disabled]="loginFormModel().password.length === 0">
+
+
@@ -75,4 +80,4 @@
-
\ No newline at end of file
+
diff --git a/src/app/pages/auth/signup/signup.html b/src/app/pages/auth/signup/signup.html
index 3d31e28..7dfd2b7 100644
--- a/src/app/pages/auth/signup/signup.html
+++ b/src/app/pages/auth/signup/signup.html
@@ -47,7 +47,12 @@
placeholder="********" />
+ [disabled]="signupModel().password.length === 0">
+
+
@if (isInvalid(signupForm.password())) {
{{ signupForm.password().errors()[0].message }}
}
@@ -62,8 +67,12 @@
[field]="signupForm.confirmPassword" placeholder="********" />
+ [disabled]="signupModel().confirmPassword.length === 0">
+
+
@if (!isInvalid(signupForm.password()) && isInvalid(signupForm.confirmPassword())) {
{{
signupForm.confirmPassword().errors()[0].message}}
@@ -101,4 +110,4 @@
-
\ No newline at end of file
+
diff --git a/src/app/services/authorization/authorization-call.fake.ts b/src/app/services/authorization/authorization-call.fake.ts
index a70b63f..93f3f7c 100644
--- a/src/app/services/authorization/authorization-call.fake.ts
+++ b/src/app/services/authorization/authorization-call.fake.ts
@@ -13,7 +13,7 @@ export class AuthorizationCallFakeService extends AuthorizationCallServiceBase {
return new Observable((observer) => {
const user = this.users.find(u => u.username === username);
if (!user) {
- observer.error(new Error('Invalid username'));
+ observer.error(new Error('User not found'));
return;
}
if (password !== user.password) {
@@ -45,4 +45,4 @@ export class AuthorizationCallFakeService extends AuthorizationCallServiceBase {
-}
\ No newline at end of file
+}
diff --git a/src/app/services/authorization/authorization-call.ts b/src/app/services/authorization/authorization-call.ts
index 597ac74..bc2bb08 100644
--- a/src/app/services/authorization/authorization-call.ts
+++ b/src/app/services/authorization/authorization-call.ts
@@ -1,7 +1,7 @@
import { AuthorizationCallServiceBase } from "./authorization-call.base";
import { User, UserRegistration } from "@models/user";
-import { map, Observable } from "rxjs";
-import { HttpClient } from "@angular/common/http";
+import { catchError, map, Observable, throwError } from "rxjs";
+import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { inject } from "@angular/core";
import { environment } from "@environment";
@@ -25,6 +25,15 @@ export class AuthorizationCallService extends AuthorizationCallServiceBase {
email: String(userSource["email"] ?? ''),
token: typeof token === "string" && token.length > 0 ? token : undefined
} satisfies User;
+ }),
+ catchError((error: unknown) => {
+ if (error instanceof HttpErrorResponse && error.status === 404) {
+ return throwError(() => new Error('User not found'));
+ }
+ if (error instanceof HttpErrorResponse && error.status === 401) {
+ return throwError(() => new Error('Invalid password'));
+ }
+ return throwError(() => error);
})
);
}