Feature: Added Login / Register Feature #3

Merged
igorpropisnov merged 26 commits from feature/register-view into main 2024-05-21 21:24:11 +02:00
5 changed files with 106 additions and 25 deletions
Showing only changes of commit e308a7bace - Show all commits

View File

@ -1,11 +1,15 @@
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AuthService } from './shared/service';
@Component({
selector: 'app-root',
standalone: true,
providers: [],
providers: [AuthService],
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
export class AppComponent {}
export class AppComponent {
private readonly authService: AuthService = inject(AuthService);
}

View File

@ -1,11 +1,14 @@
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { ApplicationConfig } from '@angular/core';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';
import { AuthInterceptor } from './shared/interceptors/auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([AuthInterceptor])),
provideRouter(routes, withComponentInputBinding()),
provideAnimations(),
],

View File

@ -0,0 +1,51 @@
import {
HttpErrorResponse,
HttpEvent,
HttpHandlerFn,
HttpInterceptorFn,
HttpRequest,
} from '@angular/common/http';
import { inject } from '@angular/core';
import { Observable, catchError, switchMap, throwError } from 'rxjs';
import { AuthService } from '../service';
import { Tokens } from '../types';
export const AuthInterceptor: HttpInterceptorFn = (
request: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
const authService: AuthService = inject(AuthService);
const accessToken: string | null = authService.access_token;
if (accessToken) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${accessToken}`,
},
});
}
return next(request).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
return authService.refreshToken().pipe(
switchMap((tokens: Tokens) => {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${tokens.access_token}`,
},
});
return next(request);
}),
catchError((refreshError) => {
authService.signout();
return throwError(() => new Error(refreshError));
})
);
}
return throwError(() => new Error());
})
);
};

View File

@ -1,7 +1,7 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, Observable, tap } from 'rxjs';
import { environment } from '../../../environments/environment';
import { LoginCredentials, Tokens } from '../types';
@ -13,21 +13,27 @@ import { SessionStorageService } from './session-storage.service';
providedIn: 'root',
})
export class AuthService {
private readonly path: string = '/api/auth';
private access_token: string | null = null;
private refresh_token: string | null = null;
private isAuthenticated$: BehaviorSubject<boolean> =
private readonly _path: string = '/api/auth';
private _access_token: string | null = null;
private _refresh_token: string | null = null;
private _isAuthenticated$: BehaviorSubject<boolean> =
new BehaviorSubject<boolean>(false);
public get access_token(): string | null {
return this._access_token;
}
public constructor(
private readonly httpClient: HttpClient,
private readonly localStorageService: LocalStorageService,
private readonly sessionStorageService: SessionStorageService
) {}
) {
this.autoLogin();
}
public signin(credentials: LoginCredentials): void {
this.httpClient
.post<Tokens>(environment.api.base + `${this.path}/signin`, credentials)
.post<Tokens>(environment.api.base + `${this._path}/signin`, credentials)
.subscribe((response: Tokens) => {
this.handleSuccess(response);
});
@ -35,43 +41,60 @@ export class AuthService {
public signup(credentials: LoginCredentials): void {
this.httpClient
.post<Tokens>(environment.api.base + `${this.path}/signup`, credentials)
.post<Tokens>(environment.api.base + `${this._path}/signup`, credentials)
.subscribe((response: Tokens) => {
// The checked accept terms should be saved with a timestamp in the db
//TODO The checked accept terms should be saved with a timestamp in the db
this.handleSuccess(response);
});
}
public signout(): void {
this.access_token = null;
this.refresh_token = null;
this._access_token = null;
this._refresh_token = null;
this.localStorageService.removeItem('access_token');
this.sessionStorageService.removeItem('refresh_token');
this.isAuthenticated$.next(false);
this._isAuthenticated$.next(false);
}
public refreshToken(): void {
public autoLogin(): void {
const storedAccessToken: string | null =
this.localStorageService.getItem('access_token');
const storedRefreshToken: string | null =
this.sessionStorageService.getItem('refresh_token');
if (storedAccessToken && storedRefreshToken) {
this._refresh_token = storedRefreshToken;
this._isAuthenticated$.next(true);
//TODO Validate tokens with backend or decode JWT to check expiration
} else {
this.signout();
}
}
public refreshToken(): Observable<Tokens> {
const headers = new HttpHeaders().set(
'Authorization',
'Bearer ' + this.refresh_token
'Bearer ' + this._refresh_token
);
this.httpClient
return this.httpClient
.post<Tokens>(
environment.api.base + `${this.path}/refresh`,
environment.api.base + `${this._path}/refresh`,
{},
{ headers: headers }
)
.subscribe((response: Tokens) => {
this.handleSuccess(response);
});
.pipe(
tap((response: Tokens) => {
this.handleSuccess(response);
})
);
}
private handleSuccess(tokens: Tokens): void {
this.access_token = tokens.access_token;
this.refresh_token = tokens.refresh_token;
this._access_token = tokens.access_token;
this._refresh_token = tokens.refresh_token;
this.localStorageService.setItem('access_token', tokens.access_token);
this.sessionStorageService.setItem('refresh_token', tokens.refresh_token);
this.isAuthenticated$.next(true);
this._isAuthenticated$.next(true);
}
}