feat(client): auth interceptor

This commit is contained in:
eneller
2026-07-23 13:02:58 +02:00
parent 4f734fff30
commit c876a45ef8
7 changed files with 57 additions and 9 deletions

View File

@@ -3,6 +3,8 @@ import { provideRouter } from '@angular/router';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { DATE_PIPE_DEFAULT_OPTIONS } from '@angular/common'; import { DATE_PIPE_DEFAULT_OPTIONS } from '@angular/common';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './services/auth-interceptor';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
@@ -10,5 +12,6 @@ export const appConfig: ApplicationConfig = {
provideRouter(routes), provideRouter(routes),
{provide: DEFAULT_CURRENCY_CODE, useValue: ''}, {provide: DEFAULT_CURRENCY_CODE, useValue: ''},
{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}}, {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}},
provideHttpClient(withInterceptors([authInterceptor])),
] ]
}; };

View File

@@ -57,15 +57,15 @@
<!-- Error Alert --> <!-- Error Alert -->
@if (error) {
<ngb-alert <ngb-alert
*ngIf="error"
type="danger" type="danger"
(closed)="error = null" (closed)="error = null"
[dismissible]="true" [dismissible]="true"
> >
{{ error }} {{ error }}
</ngb-alert> </ngb-alert>
}
</form> </form>
</div> </div>
</div> </div>

View File

@@ -4,6 +4,7 @@ import { Validators, FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, F
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { APIService } from '../../services/api'; import { APIService } from '../../services/api';
import { GenericMessage } from '@message/Message';
@Component({ @Component({
selector: 'app-screen-login', selector: 'app-screen-login',
@@ -40,9 +41,10 @@ export class ScreenLogin {
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
this.router.navigateByUrl(returnUrl); this.router.navigateByUrl(returnUrl);
}, },
error: (err) => { error: (resp) => {
//FIXME error message displaying delayed, display message from server response //FIXME error message displaying delayed, display message from server response
this.error = err.error?.message || 'Login failed. Please try again.'; let msg: GenericMessage = resp.error;
this.error = msg.message || 'Login failed. Please try again.';
this.loading = false; this.loading = false;
} }
}); });

View File

@@ -36,7 +36,7 @@ export class ScreenProfile implements OnInit{
logOut(){ logOut(){
this.api.logout().subscribe({ this.api.logout().subscribe({
next: () => { next: () => {
this.router.navigate(['login']) this.router.navigate(['/'])
}, },
error: (err) => { error: (err) => {
console.error('Error logging out:', err) console.error('Error logging out:', err)

View File

@@ -24,15 +24,22 @@ export class APIService {
); );
} }
logout(): Observable<any>{ logout(): Observable<any>{
return this.http.post(`${this.apiUrl}/auth/logout`, {}); return this.http.post(`${this.apiUrl}/auth/logout`, {}).pipe(
tap({
next: () => this.isAuthenticatedSubject.next(false),
error: () => this.isAuthenticatedSubject.next(false),
})
);
}
clearAuthState() {
this.isAuthenticatedSubject.next(false);
} }
checkAuthStatus(): Observable<boolean> { checkAuthStatus(): Observable<boolean> {
return this.http.get(`${this.apiUrl}/auth/status`).pipe( return this.http.get(`${this.apiUrl}/auth/status`).pipe(
map(() => true), map(() => true),
catchError(() => of(false)), catchError(() => of(false)),
tap({ tap( authenticated => {
next: () => this.isAuthenticatedSubject.next(true), this.isAuthenticatedSubject.next(authenticated);
error: () => this.isAuthenticatedSubject.next(false),
}) })
); );
} }

View File

@@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { HttpInterceptorFn } from '@angular/common/http';
import { authInterceptor } from './auth-interceptor';
describe('authInterceptor', () => {
const interceptor: HttpInterceptorFn = (req, next) =>
TestBed.runInInjectionContext(() => authInterceptor(req, next));
beforeEach(() => {
TestBed.configureTestingModule({});
});
it('should be created', () => {
expect(interceptor).toBeTruthy();
});
});

View File

@@ -0,0 +1,19 @@
import { HttpErrorResponse, HttpEventType, HttpInterceptorFn } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
import { APIService } from './api';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const api = inject(APIService);
const router = inject(Router)
return next(req).pipe(
catchError((err: HttpErrorResponse) => {
if (err.status === 401){
api.clearAuthState();
router.createUrlTree(['/login']);
}
return throwError(() => err);
})
)
};