Add refresh logic to auth interceptor and service. Misc. prettier.
This commit is contained in:
parent
28222e0655
commit
80752f7513
@ -1,15 +1,48 @@
|
||||
import { HttpInterceptorFn } from '@angular/common/http';
|
||||
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { AuthService } from '../service/auth.service';
|
||||
import { catchError, from, switchMap, throwError } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
export const authInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
const token = authService.accessToken();
|
||||
if (token) {
|
||||
// first we try with the current token
|
||||
return next(
|
||||
req.clone({
|
||||
headers: req.headers.set('Authorization', `Bearer ${token}`),
|
||||
}),
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
// if the request with the current token returned 401,
|
||||
// try refreshing, then retry the request with the new token
|
||||
// for this first scenario, do not retry if we are getting 401 from login or refreshing
|
||||
if (
|
||||
error.status === 401 &&
|
||||
!(error.url?.endsWith('auth/login') || error.url?.endsWith('auth/refresh'))
|
||||
) {
|
||||
return authService.refresh().pipe(
|
||||
switchMap((loginView) => {
|
||||
const newToken = loginView.accessToken;
|
||||
return next(
|
||||
req.clone({
|
||||
headers: req.headers.set('Authorization', `Bearer ${newToken}`),
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
} else if (error.status === 401 && error.url?.endsWith('auth/refresh')) {
|
||||
// our refresh token is expired
|
||||
// redirect to login page
|
||||
return from(router.navigate(['/'])).pipe(
|
||||
switchMap(() => throwError(() => error)),
|
||||
);
|
||||
} else {
|
||||
return throwError(() => error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return next(req);
|
||||
|
||||
@ -2,21 +2,18 @@ import { Component, computed, inject, input, Input } from '@angular/core';
|
||||
import { RecipeView } from '../../model/Recipe.model';
|
||||
import { injectMutation, injectQuery } from '@tanstack/angular-query-experimental';
|
||||
import { ImageService } from '../../service/image.service';
|
||||
import { faStar } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faStar } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { RecipeService } from '../../service/recipe.service';
|
||||
import { AuthService } from '../../service/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-recipe-page-content',
|
||||
imports: [
|
||||
FaIconComponent
|
||||
],
|
||||
imports: [FaIconComponent],
|
||||
templateUrl: './recipe-page-content.html',
|
||||
styleUrl: './recipe-page-content.css',
|
||||
})
|
||||
export class RecipePageContent {
|
||||
|
||||
public recipeView = input.required<RecipeView>();
|
||||
|
||||
private readonly imageService = inject(ImageService);
|
||||
@ -30,7 +27,7 @@ export class RecipePageContent {
|
||||
return {
|
||||
queryKey: ['images', recipe.mainImage.owner.username, recipe.mainImage.filename],
|
||||
queryFn: () => this.imageService.getImage(recipe.mainImage.url),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
protected readonly starMutation = injectMutation(() => ({
|
||||
@ -38,5 +35,4 @@ export class RecipePageContent {
|
||||
}));
|
||||
|
||||
protected readonly faStar = faStar;
|
||||
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { inject, Injectable, Signal, signal } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { LoginView } from '../model/LoginView.model';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { firstValueFrom, Observable, tap } from 'rxjs';
|
||||
import { QueryClient } from '@tanstack/angular-query-experimental';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@ -21,7 +21,13 @@ export class AuthService {
|
||||
|
||||
public async login(username: string, password: string): Promise<LoginView> {
|
||||
const loginView = await firstValueFrom(
|
||||
this.http.post<LoginView>('http://localhost:8080/auth/login', { username, password }),
|
||||
this.http.post<LoginView>(
|
||||
'http://localhost:8080/auth/login',
|
||||
{ username, password },
|
||||
{
|
||||
withCredentials: true,
|
||||
},
|
||||
),
|
||||
);
|
||||
this._accessToken.set(loginView.accessToken);
|
||||
this._username.set(loginView.username);
|
||||
@ -36,4 +42,19 @@ export class AuthService {
|
||||
await this.router.navigate(['/']);
|
||||
await this.queryClient.invalidateQueries();
|
||||
}
|
||||
|
||||
public refresh(): Observable<LoginView> {
|
||||
this._accessToken.set(null);
|
||||
this._username.set(null);
|
||||
return this.http
|
||||
.post<LoginView>('http://localhost:8080/auth/refresh', null, {
|
||||
withCredentials: true,
|
||||
})
|
||||
.pipe(
|
||||
tap((loginView) => {
|
||||
this._username.set(loginView.username);
|
||||
this._accessToken.set(loginView.accessToken);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export class RecipeService {
|
||||
|
||||
public async getRecipeView(username: string, slug: string): Promise<RecipeView> {
|
||||
return firstValueFrom(
|
||||
this.http.get<RecipeView>(`http://localhost:8080/recipes/${username}/${slug}`)
|
||||
this.http.get<RecipeView>(`http://localhost:8080/recipes/${username}/${slug}`),
|
||||
);
|
||||
}
|
||||
|
||||
@ -40,10 +40,9 @@ export class RecipeService {
|
||||
}
|
||||
await this.queryClient.invalidateQueries({
|
||||
queryKey: ['recipe', recipeView.recipe.owner.username, recipeView.recipe.slug],
|
||||
})
|
||||
});
|
||||
} else {
|
||||
throw new Error('Cannot star a recipe when not logged in.')
|
||||
throw new Error('Cannot star a recipe when not logged in.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user