feat: add auth service

This commit is contained in:
Polianin Nikita 2024-07-02 00:54:21 +03:00
parent 7d78295b9a
commit 86cf636e16
3 changed files with 107 additions and 2 deletions

View File

@ -1,10 +1,12 @@
import {catchError, mergeMap, Observable, retryWhen, tap, timer} from "rxjs"; import {catchError, filter, mergeMap, Observable, retryWhen, switchMap, take, tap, timer} from "rxjs";
import {HttpClient, HttpErrorResponse} from "@angular/common/http"; import {HttpClient, HttpErrorResponse} from "@angular/common/http";
import {NotifyColor, OpenNotifyService} from "@service/open-notify.service"; import {NotifyColor, OpenNotifyService} from "@service/open-notify.service";
import {environment} from "@environment"; import {environment} from "@environment";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {RequestBuilder, RequestData, SetRequestBuilderAfterBuild} from "@api/RequestBuilder"; import {RequestBuilder, RequestData, SetRequestBuilderAfterBuild} from "@api/RequestBuilder";
import {TokenRefreshService} from "@service/token-refresh.service";
import {AuthToken} from "@service/auth.service";
export function retryWithInterval<T>(): (source: Observable<T>) => Observable<T> { export function retryWithInterval<T>(): (source: Observable<T>) => Observable<T> {
return (source: Observable<T>) => return (source: Observable<T>) =>
@ -35,13 +37,14 @@ export enum AvailableVersion {
@Injectable() @Injectable()
export default abstract class ApiService implements SetRequestBuilderAfterBuild { export default abstract class ApiService implements SetRequestBuilderAfterBuild {
constructor(private http: HttpClient, private notify: OpenNotifyService, private router: Router) { constructor(private http: HttpClient, private notify: OpenNotifyService, private router: Router, protected tokenRefreshService: TokenRefreshService) {
} }
private apiUrl = environment.apiUrl; private apiUrl = environment.apiUrl;
protected abstract basePath: string; protected abstract basePath: string;
protected abstract version: AvailableVersion; protected abstract version: AvailableVersion;
private request: RequestData = RequestBuilder.getStandardRequestData(); private request: RequestData = RequestBuilder.getStandardRequestData();
public static readonly tokenKey = 'auth_token';
public setRequestBuilder(request: RequestData): void { public setRequestBuilder(request: RequestData): void {
this.request = request; this.request = request;
@ -128,6 +131,18 @@ export default abstract class ApiService implements SetRequestBuilderAfterBuild
return this.makeHttpRequest<Type>('delete'); return this.makeHttpRequest<Type>('delete');
} }
public addAuth() {
const token = localStorage.getItem(ApiService.tokenKey);
if (!token)
return this;
const authToken = AuthToken.httpHeader((JSON.parse(token) as AuthToken));
authToken.keys().forEach(key => this.request.httpHeaders = this.request.httpHeaders.append(key, authToken.get(key) ?? ''))
return this;
}
private handleError(error: HttpErrorResponse): void { private handleError(error: HttpErrorResponse): void {
// todo: change to Retry-After condition // todo: change to Retry-After condition
if (error.error.toString().includes("setup")) { if (error.error.toString().includes("setup")) {
@ -147,6 +162,7 @@ export default abstract class ApiService implements SetRequestBuilderAfterBuild
message = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.'; message = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.';
break; break;
case 401: case 401:
this.router.navigate(['/login/']).then();
message = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.'; message = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.';
break; break;
case 403: case 403:

View File

@ -4,6 +4,7 @@ import {FooterComponent} from "@component/common/footer/footer.component";
import localeRu from '@angular/common/locales/ru'; import localeRu from '@angular/common/locales/ru';
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import {FocusNextDirective} from "@/directives/focus-next.directive"; import {FocusNextDirective} from "@/directives/focus-next.directive";
import {TokenRefreshService} from "@service/token-refresh.service";
@Component({ @Component({
selector: 'app-root', selector: 'app-root',

View File

@ -0,0 +1,88 @@
import {EventEmitter, Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from "@angular/common/http";
import {catchError, Observable, of, tap} from "rxjs";
import {TokenResponse} from "@api/v1/tokenResponse";
import ApiService from "@api/api.service";
export enum AvailableAuthenticationProvider {
Bearer
}
export class AuthToken {
accessToken: string;
expiresIn: Date;
authProvider: AvailableAuthenticationProvider;
endpoint: string;
constructor(accessToken: string, expiresIn: Date, authProvider: AvailableAuthenticationProvider, refreshEndpoint: string) {
this.accessToken = accessToken;
this.expiresIn = expiresIn;
this.authProvider = authProvider;
this.endpoint = refreshEndpoint;
}
static httpHeader(token: AuthToken): HttpHeaders {
let header = new HttpHeaders();
if (token.authProvider === AvailableAuthenticationProvider.Bearer)
header = header.set('Authorization', `Bearer ${token.accessToken}`);
return header;
}
}
@Injectable({
providedIn: 'root',
})
export class AuthService {
public expireTokenChange = new EventEmitter<Date>();
public tokenChangeError = new EventEmitter();
constructor(private http: HttpClient) {
}
public static setToken(token: TokenResponse, provider: AvailableAuthenticationProvider, refreshEndpoint: string) {
localStorage.setItem(ApiService.tokenKey, JSON.stringify(
new AuthToken(token.accessToken, token.expiresIn, provider, refreshEndpoint)
));
}
public static get tokenExpiresIn(): Date {
const token = localStorage.getItem(ApiService.tokenKey);
if (!token)
return new Date();
const result = new Date((JSON.parse(token) as AuthToken).expiresIn);
return result <= new Date() ? new Date() : result;
}
public refreshToken(): Observable<TokenResponse> {
const token = localStorage.getItem(ApiService.tokenKey);
if (!token)
return of();
const authToken = JSON.parse(token) as AuthToken;
switch (authToken.authProvider) {
case AvailableAuthenticationProvider.Bearer:
return this.http.get<TokenResponse>(authToken.endpoint, {withCredentials: true})
.pipe(
catchError(error => {
this.tokenChangeError.emit();
throw error;
}),
tap(response => {
const newExpireDate = new Date(response.expiresIn);
const oldExpireDate = new Date(authToken.expiresIn);
if (newExpireDate.getTime() !== oldExpireDate.getTime()) {
AuthService.setToken(response, AvailableAuthenticationProvider.Bearer, authToken.endpoint);
this.expireTokenChange.emit(newExpireDate);
}
})
);
}
}
}