diff --git a/src/app/interceptor/auth.interceptor.ts b/src/app/interceptor/auth.interceptor.ts
index 87c7a13..c886d10 100644
--- a/src/app/interceptor/auth.interceptor.ts
+++ b/src/app/interceptor/auth.interceptor.ts
@@ -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);
diff --git a/src/app/recipe-page/recipe-page-content/recipe-page-content.html b/src/app/recipe-page/recipe-page-content/recipe-page-content.html
index 4a4ad04..1651f75 100644
--- a/src/app/recipe-page/recipe-page-content/recipe-page-content.html
+++ b/src/app/recipe-page/recipe-page-content/recipe-page-content.html
@@ -4,13 +4,13 @@
{{ recipe.title }}
@if (isLoggedIn()) {
} @else {
-
+
{{ recipe.starCount }}
}
diff --git a/src/app/recipe-page/recipe-page-content/recipe-page-content.ts b/src/app/recipe-page/recipe-page-content/recipe-page-content.ts
index 6c5feec..df8187d 100644
--- a/src/app/recipe-page/recipe-page-content/recipe-page-content.ts
+++ b/src/app/recipe-page/recipe-page-content/recipe-page-content.ts
@@ -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();
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;
-
}
diff --git a/src/app/service/auth.service.ts b/src/app/service/auth.service.ts
index d977b09..b75af77 100644
--- a/src/app/service/auth.service.ts
+++ b/src/app/service/auth.service.ts
@@ -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 {
const loginView = await firstValueFrom(
- this.http.post('http://localhost:8080/auth/login', { username, password }),
+ this.http.post(
+ '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 {
+ this._accessToken.set(null);
+ this._username.set(null);
+ return this.http
+ .post('http://localhost:8080/auth/refresh', null, {
+ withCredentials: true,
+ })
+ .pipe(
+ tap((loginView) => {
+ this._username.set(loginView.username);
+ this._accessToken.set(loginView.accessToken);
+ }),
+ );
+ }
}
diff --git a/src/app/service/recipe.service.ts b/src/app/service/recipe.service.ts
index 096daba..f0fd110 100644
--- a/src/app/service/recipe.service.ts
+++ b/src/app/service/recipe.service.ts
@@ -23,7 +23,7 @@ export class RecipeService {
public async getRecipeView(username: string, slug: string): Promise {
return firstValueFrom(
- this.http.get(`http://localhost:8080/recipes/${username}/${slug}`)
+ this.http.get(`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.');
}
}
-
}