NewTube/src/interceptors/auth.interceptor.ts

61 lines
2.2 KiB
TypeScript

import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { catchError, switchMap } from 'rxjs/operators';
import { throwError, of } from 'rxjs';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService);
// Attach Authorization header when we have a token
const token = auth.getAccessToken();
let authReq = req;
const isRefreshCall = req.url.includes('/auth/refresh');
const isApiCall = req.url.startsWith('/proxy/api') || req.url.startsWith('/api');
// Ensure cookies are sent for API calls (dev proxy and prod)
if (req.url.startsWith('/proxy/api') || req.url.startsWith('/api')) {
authReq = authReq.clone({ withCredentials: true });
}
if (token && isApiCall) {
authReq = authReq.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
}
return next(authReq).pipe(
catchError((err) => {
if (!isApiCall) {
// Do not attempt refresh for non-API requests (e.g., YouTube/Twitch/Rumble)
return throwError(() => err);
}
if (err instanceof HttpErrorResponse && err.status === 401) {
// If the 401 is for the refresh endpoint itself, do NOT try to refresh again.
if (isRefreshCall) {
return throwError(() => err);
}
// If there is no access token at all, we are not logged in: do not try refresh
const hasToken = !!auth.getAccessToken();
if (!hasToken) {
return throwError(() => err);
}
// Try a single refresh then retry the request once
return auth.refresh().pipe(
switchMap((ok) => {
if (!ok) return throwError(() => err);
const newToken = auth.getAccessToken();
let retry = authReq;
if (newToken) {
retry = retry.clone({ setHeaders: { Authorization: `Bearer ${newToken}` } });
}
if (retry.url.startsWith('/proxy/api')) {
retry = retry.clone({ withCredentials: true });
}
return next(retry);
})
);
}
return throwError(() => err);
})
);
};