fix 401 loop hell
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AuthService } from '../../shared/services/auth.service';
|
||||
|
||||
@Injectable({
|
||||
@@ -14,19 +14,19 @@ export class AuthGuard implements CanActivate {
|
||||
) { }
|
||||
|
||||
canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
_route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<boolean> | Promise<boolean> | boolean {
|
||||
// Check if user is authenticated
|
||||
if (this.authService.isAuthenticated()) {
|
||||
return true;
|
||||
}
|
||||
return this.authService.whenSessionReady().pipe(
|
||||
map(() => {
|
||||
if (this.authService.isAuthenticated()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Store the attempted URL for redirecting after login
|
||||
this.authService.setRedirectUrl(state.url);
|
||||
|
||||
// Redirect to login page
|
||||
this.router.navigate(['/login']);
|
||||
return false;
|
||||
this.authService.setRedirectUrl(state.url);
|
||||
this.router.navigate(['/login']);
|
||||
return false;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, ActivatedRouteSnapshot, Router } from '@angular/router';
|
||||
import { AuthService } from '../../shared/services/auth.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RoleGuard implements CanActivate {
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot): boolean {
|
||||
const requiredRoles = route.data['roles'] as string[] | undefined;
|
||||
if (!requiredRoles?.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const user = this.authService.getCurrentUser();
|
||||
const allowed = user?.roles?.some(r => requiredRoles.includes(r)) ?? false;
|
||||
|
||||
if (!allowed) {
|
||||
this.router.navigate(['/user-portal/dashboard']);
|
||||
}
|
||||
|
||||
return allowed;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,75 @@
|
||||
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
|
||||
import { HttpInterceptorFn, HttpErrorResponse, HttpRequest } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { catchError, throwError } from 'rxjs';
|
||||
import { catchError, switchMap, throwError } from 'rxjs';
|
||||
import { AuthService } from '../../shared/services/auth.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ApiConfigService } from '../services/api-config.service';
|
||||
|
||||
export const authInterceptor: HttpInterceptorFn = (request, next) => {
|
||||
/**
|
||||
* Attaches Bearer tokens to ROLAC API calls and silently refreshes on 401.
|
||||
*/
|
||||
export const authInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const authService = inject(AuthService);
|
||||
const apiConfigService = inject(ApiConfigService);
|
||||
const router = inject(Router);
|
||||
const apiConfig = inject(ApiConfigService);
|
||||
const router = inject(Router);
|
||||
|
||||
// Get the current user and token
|
||||
const token = authService.getToken();
|
||||
const request = attachToken(req, authService, apiConfig);
|
||||
|
||||
// Clone the request and add the Authorization header if token exists
|
||||
if (token && shouldAddToken(apiConfigService, request)) {
|
||||
request = request.clone({
|
||||
setHeaders: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle the request and catch 401 errors
|
||||
return next(request).pipe(
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
if (
|
||||
error.status === 401 &&
|
||||
!req.headers.has('X-Retry') &&
|
||||
!isPublicAuthRequest(req, apiConfig)
|
||||
) {
|
||||
return authService.refresh().pipe(
|
||||
switchMap(success => {
|
||||
if (success) {
|
||||
const retryReq = attachToken(
|
||||
req.clone({ setHeaders: { 'X-Retry': 'true' } }),
|
||||
authService,
|
||||
apiConfig
|
||||
);
|
||||
return next(retryReq);
|
||||
}
|
||||
authService.logout();
|
||||
router.navigate(['/login']);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (error.status === 401) {
|
||||
// Token is invalid or expired, logout user
|
||||
authService.logout();
|
||||
router.navigate(['/login']);
|
||||
}
|
||||
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if the token should be added to this request
|
||||
* Skip adding token to login requests and other public endpoints
|
||||
*/
|
||||
function shouldAddToken(apiConfigService: ApiConfigService, request: any): boolean {
|
||||
// Don't add token to outbound requests to other domains
|
||||
if (!request.url.startsWith(apiConfigService.getBaseUrl())) {
|
||||
return false;
|
||||
function attachToken(
|
||||
req: HttpRequest<unknown>,
|
||||
authService: AuthService,
|
||||
apiConfig: ApiConfigService
|
||||
): HttpRequest<unknown> {
|
||||
const token = authService.getToken();
|
||||
if (!token || !shouldAddToken(apiConfig, req)) {
|
||||
return req;
|
||||
}
|
||||
|
||||
// Don't add token to login requests
|
||||
if (request.url.includes('/Auth/login') || request.url.includes('/Token/Create')) {
|
||||
return false;
|
||||
}
|
||||
// Don't add token to public endpoints (you can customize this list)
|
||||
const publicEndpoints = [
|
||||
'/Auth/register',
|
||||
'/Auth/forgot-password',
|
||||
'/Auth/reset-password'
|
||||
];
|
||||
|
||||
return !publicEndpoints.some(endpoint => request.url.includes(endpoint));
|
||||
return req.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
|
||||
}
|
||||
|
||||
const PUBLIC_AUTH_PATHS = ['/auth/login', '/auth/refresh', '/auth/logout'];
|
||||
|
||||
function isPublicAuthRequest(req: HttpRequest<unknown>, apiConfig: ApiConfigService): boolean {
|
||||
if (!req.url.startsWith(apiConfig.getBaseUrl())) {
|
||||
return false;
|
||||
}
|
||||
return PUBLIC_AUTH_PATHS.some(path => req.url.toLowerCase().includes(path));
|
||||
}
|
||||
|
||||
function shouldAddToken(apiConfig: ApiConfigService, req: HttpRequest<unknown>): boolean {
|
||||
return !isPublicAuthRequest(req, apiConfig);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export class ApiConfigService {
|
||||
* Get specific API endpoints
|
||||
*/
|
||||
get authUrl(): string {
|
||||
return this.getApiUrl('Auth');
|
||||
return this.getApiUrl('auth');
|
||||
}
|
||||
|
||||
get tokenUrl(): string {
|
||||
|
||||
Reference in New Issue
Block a user