Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jc 679 authorization feature #6

Open
wants to merge 64 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
ddba767
Create pagination JC-660
TebyakinaEkaterina Jul 25, 2024
75d1b19
Create table sort JC-660
TebyakinaEkaterina Jul 25, 2024
0b3879b
Create search JC-644
TebyakinaEkaterina Jul 25, 2024
7b129f0
Create filter by type JC-660
TebyakinaEkaterina Jul 25, 2024
cb24b48
Add query params JC-660
TebyakinaEkaterina Jul 25, 2024
fcc162f
Create filters, sort and search JC-660
TebyakinaEkaterina Jul 29, 2024
234859a
Refactoring sort JC-660
TebyakinaEkaterina Jul 29, 2024
8997c70
Create services JC-660
TebyakinaEkaterina Jul 29, 2024
bf77abf
Create observable for params JC-660
TebyakinaEkaterina Jul 30, 2024
a356d61
Realize work with query params service JC-660
TebyakinaEkaterina Jul 30, 2024
f74d6b9
Improving code JC-660
TebyakinaEkaterina Jul 30, 2024
852f2dc
Write documentation JC-660
TebyakinaEkaterina Jul 31, 2024
90190cd
Add navigation JC-679
TebyakinaEkaterina Jul 31, 2024
1cb1ff8
Create login component JC-679
TebyakinaEkaterina Jul 31, 2024
d38c2fe
Create registration component JC-679
TebyakinaEkaterina Jul 31, 2024
dc5e5d9
Create service for form fields compare JC-679
TebyakinaEkaterina Jul 31, 2024
251d141
Add css variables JC-679
TebyakinaEkaterina Jul 31, 2024
a3b942b
Create dto, model and mapper for registration JC-679
TebyakinaEkaterina Jul 31, 2024
f4a6103
Realize registration JC-679
TebyakinaEkaterina Jul 31, 2024
0d62fce
Development login JC-679
TebyakinaEkaterina Aug 1, 2024
7e9371f
Development authorization JC-679
TebyakinaEkaterina Aug 5, 2024
6c7a733
Refactoring authorization JC-679
TebyakinaEkaterina Aug 6, 2024
58d128d
Rename and refactoring dashboard component JC-660
TebyakinaEkaterina Aug 2, 2024
5653ce6
Refactoring api service JC-660
TebyakinaEkaterina Aug 2, 2024
952d1fa
Add util for check enums value JC-660
TebyakinaEkaterina Aug 2, 2024
5df32d4
Rename and refactoring dashboard component JC-660
TebyakinaEkaterina Aug 5, 2024
2e101ee
Refactoring Jc-679
TebyakinaEkaterina Aug 7, 2024
f263a3f
Refactoring JC-679
TebyakinaEkaterina Aug 7, 2024
ad13d50
Fix refresh url JC-679
TebyakinaEkaterina Aug 7, 2024
87a93f2
Fix lint issues JC-679
TebyakinaEkaterina Aug 7, 2024
bf6811d
Fix errors JC-679
TebyakinaEkaterina Aug 7, 2024
14c67d1
Refactoring JC-679
TebyakinaEkaterina Aug 9, 2024
909e0fb
Add services for forms JC-679
TebyakinaEkaterina Aug 12, 2024
302907d
Fix attributes formatting JC-679
TebyakinaEkaterina Aug 12, 2024
395a571
Fix attributes formatting JC-679
TebyakinaEkaterina Aug 13, 2024
3e9cca8
Fix merge issue with anime type mapper JC-679
TebyakinaEkaterina Aug 13, 2024
e75fff9
Fix merge issues JC-679
TebyakinaEkaterina Aug 13, 2024
89b05d1
Fix merge issues JC-679
TebyakinaEkaterina Aug 13, 2024
38e10e0
Fix merge issues JC-679
TebyakinaEkaterina Aug 13, 2024
3f5454d
Code refactoring JC-679
TebyakinaEkaterina Aug 13, 2024
e77acc7
Code refactoring JC-679
TebyakinaEkaterina Aug 13, 2024
fe21172
Move logic of getting an error into a reusable function JC-679
TebyakinaEkaterina Aug 21, 2024
1a3bfcc
Remove remove disable attribute from submit buttons and add a validit…
TebyakinaEkaterina Aug 21, 2024
70a4669
Replace next and error with catchError and tap JC-679
TebyakinaEkaterina Aug 21, 2024
293b06d
Improve refreshAccessToken method JC-679
TebyakinaEkaterina Aug 21, 2024
b7c0896
Add comment to get user method about a throwing an error JC-679
TebyakinaEkaterina Aug 21, 2024
c9bc34b
Fix type in matchFieldsValidator JC-679
TebyakinaEkaterina Aug 21, 2024
9f9b1dd
Rename menu links JC-679
TebyakinaEkaterina Aug 21, 2024
a371cf6
Replace next and error with catchError and tap JC-679
TebyakinaEkaterina Aug 21, 2024
e1bcb20
Fix JSDoc comments JC-679
TebyakinaEkaterina Aug 21, 2024
ed0d531
Improve getErrorMessage method JC-679
TebyakinaEkaterina Aug 23, 2024
fb53cd7
Implement user data storage in the service JC-679
TebyakinaEkaterina Aug 23, 2024
4f8a0b6
Remove unused variable Jc-679
TebyakinaEkaterina Aug 23, 2024
7b236b3
Remove unused import JC-679
TebyakinaEkaterina Aug 23, 2024
76ca386
Fix JSDoc comment JC-679
TebyakinaEkaterina Aug 23, 2024
883b301
Improve confirm password validation JC-726
TebyakinaEkaterina Aug 23, 2024
a1b5aa4
Fix infinite loop of refreshing tokens JC-679
TebyakinaEkaterina Aug 26, 2024
43d2c55
Add JSDoc comments JC-679
TebyakinaEkaterina Aug 26, 2024
adeb549
Create variable for user JC-679
TebyakinaEkaterina Aug 26, 2024
4c71c06
Fix html JC-679
TebyakinaEkaterina Aug 26, 2024
51db6a1
Remove redundant type for error status JC-679
TebyakinaEkaterina Aug 26, 2024
880e59f
Implement local storage service for a a wide range of keys and values…
TebyakinaEkaterina Aug 26, 2024
e1008e3
Create enum for tokens JC-679
TebyakinaEkaterina Aug 28, 2024
189993f
Remove redundant service for local storage JC-679
TebyakinaEkaterina Aug 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/angular/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
>
Anime app
</a>
<camp-authorization-menu-form/>
<camp-authorization-menu/>
</mat-toolbar>
<router-outlet></router-outlet>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<nav class="authorization-menu">
@if (this.usersApiService.user$ | async; as user) {
@if (user$ | async; as user) {
<div class="greeting-message">
Hi,&nbsp;
<span>{{user.firstName}}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { RouterPaths } from '@js-camp/angular/core/model/router-paths';

/** Component with authorization navigation menu. */
@Component({
selector: 'camp-authorization-menu-form',
selector: 'camp-authorization-menu',
standalone: true,
templateUrl: './authorization-menu.component.html',
styleUrl: './authorization-menu.component.css',
Expand All @@ -19,13 +19,15 @@ import { RouterPaths } from '@js-camp/angular/core/model/router-paths';
})
export class AuthorizationMenuComponent {

private readonly authApiService = inject(AuthorizationApiService);

private readonly usersApiService = inject(UserApiService);

/** Enum with paths for link. */
protected readonly routerPaths = RouterPaths;

/** Service for managing current user. */
protected readonly usersApiService = inject(UserApiService);

private readonly authApiService = inject(AuthorizationApiService);
/** Contains information about current user. */
protected readonly user$ = this.usersApiService.user$;

/** Handle click on log out button. */
protected onLogoutClick(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class LoginFormComponent {
return throwError(() => error);
}),
tap(() => {
this.loginFormService.form.reset();
this.router.navigate([this.routerPaths.Main]);
}),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ <h2 class="registration-header">Registration</h2>
type="password"
placeholder="veryStrongPassW0rd"
[formControl]="registrationFormService.form.controls.retypedPassword"
type="password"
>
<mat-error>{{registrationFormService.getErrorMessage('retypedPassword')}}</mat-error>
</mat-form-field>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class RegistrationFormComponent {
return throwError(() => error);
}),
tap(() => {
this.registrationFormService.form.reset();
this.router.navigate([this.routerPaths.Main]);
}),
)
Expand Down
23 changes: 15 additions & 8 deletions apps/angular/src/core/interceptors/authorization.interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';
import { HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpRequest, HttpStatusCode } from '@angular/common/http';
import { inject } from '@angular/core';

import { catchError, Observable, switchMap, throwError } from 'rxjs';

import { ServerErrorStatus } from '@js-camp/core/models/server-error-status';

import { AuthorizationApiService } from '../services/authorization-api.service';
import { LocalStorageService } from '../services/local-storage.service';
import { LocalStorageForAuthorizationService } from '../services/local-storage-for-authorization.service';
import { AppConfig } from '../utils/app-config';
import { UrlConfigService } from '../services/url-config.service';

/**
* Add header Authorization to a request.
Expand All @@ -18,7 +17,11 @@ export function authorizationInterceptor(req: HttpRequest<unknown>, next: HttpHa

const authorizationService = inject(AuthorizationApiService);

const localStorageService = inject(LocalStorageService);
const localStorageService = inject(LocalStorageForAuthorizationService);

const appConfig = inject(AppConfig);

const urlConfigService = inject(UrlConfigService);

return localStorageService.getAccessToken().pipe(
switchMap(accessToken => {
Expand All @@ -30,9 +33,13 @@ export function authorizationInterceptor(req: HttpRequest<unknown>, next: HttpHa
return next(req);
}),
catchError((error: unknown) => {
if (error instanceof HttpErrorResponse && error.status === ServerErrorStatus.Unauthorized) {
if (req.url === `${appConfig.baseApiURL}/${urlConfigService.authorization.refresh}`) {
return next(req);
}
if (error instanceof HttpErrorResponse && error.status === HttpStatusCode.Unauthorized) {
return handleTokenExpired(req);
}
authorizationService.logout();
return throwError(() => error);
}),
);
Expand Down Expand Up @@ -66,7 +73,7 @@ export function authorizationInterceptor(req: HttpRequest<unknown>, next: HttpHa
if (newAccessToken) {
return next(addToken(request, newAccessToken));
}
return next(request);
return throwError(() => new Error('Error handling expired access token'));
}),
catchError((error: unknown) => {
console.error('Error handling expired access token:', error);
Expand Down
10 changes: 7 additions & 3 deletions apps/angular/src/core/services/authorization-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { RegistrationData } from '@js-camp/core/models/registration-data';
import { RegistrationDataMapper } from '@js-camp/core/mappers/registration-data.mapper';
import { AuthorizationTokensDto } from '@js-camp/core/dtos/authorization-tokens.dto';
import { BehaviorSubject, catchError, map, Observable, switchMap, tap, throwError } from 'rxjs';

import { ServerErrorDto } from '@js-camp/core/dtos/server-error.dto';
import { ServerErrorMapper } from '@js-camp/core/mappers/server-error.mapper';
import { LoginData } from '@js-camp/core/models/login-data';
import { LoginDataMapper } from '@js-camp/core/mappers/login-data.mapper';
import { AuthorizationTokens } from '@js-camp/core/models/authorization-tokens';
import { AuthorizationTokensMapper } from '@js-camp/core/mappers/authorization-tokens.mapper';
import { Router } from '@angular/router';

import { UrlConfigService } from './url-config.service';
import { LocalStorageService } from './local-storage.service';
import { LocalStorageForAuthorizationService } from './local-storage-for-authorization.service';

/** Authorization API access service. */
@Injectable({ providedIn: 'root' })
Expand All @@ -28,7 +28,9 @@ export class AuthorizationApiService {

private readonly urlConfigService = inject(UrlConfigService);

private readonly localStorageService = inject(LocalStorageService);
private readonly localStorageService = inject(LocalStorageForAuthorizationService);

private readonly router = inject(Router);

public constructor() {
this.localStorageService.getAccessToken().subscribe(token => {
Expand Down Expand Up @@ -86,6 +88,7 @@ export class AuthorizationApiService {
return this.http.post<AuthorizationTokensDto>(this.urlConfigService.authorization.refresh,
{ refresh: refreshToken });
}
this.logout();
return throwError(() => new Error('Failed to refresh token'));
}),
map(response => AuthorizationTokensMapper.fromDto(response)),
Expand All @@ -98,6 +101,7 @@ export class AuthorizationApiService {
public logout(): void {
this.localStorageService.removeTokens();
this.accessToken$.next(null);
this.router.navigate(['/login']);
}

private handleError(errorResponse: HttpErrorResponse): Observable<never> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { inject, Injectable } from '@angular/core';
import { AuthorizationTokens } from '@js-camp/core/models/authorization-tokens';
import { Observable } from 'rxjs';

import { LocalStorageService } from './local-storage.service';

/** Service for working with local storage. */
@Injectable({ providedIn: 'root' })
export class LocalStorageForAuthorizationService {

private readonly localStorageService = inject(LocalStorageService);

/**
* Save tokens to local storage.
* @param tokens - Access and refresh tokens.
*/
public saveTokens(tokens: AuthorizationTokens): void {
this.localStorageService.setValue('accessToken', tokens.access);
this.localStorageService.setValue('refreshToken', tokens.refresh);
TebyakinaEkaterina marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Remove tokens from local storage.
* @param tokens - Access and refresh tokens.
*/
public removeTokens(): void {
this.localStorageService.removeValue('accessToken');
this.localStorageService.removeValue('refreshToken');
}

/**
* Get access token from local storage.
* @returns Access token.
*/
public getAccessToken(): Observable<string | null> {
return this.localStorageService.getValue('accessToken');
}

/**
* Get refresh token from local storage.
* @returns Refresh token.
*/
public getRefreshToken(): Observable<string | null> {
return this.localStorageService.getValue('refreshToken');
}
TebyakinaEkaterina marked this conversation as resolved.
Show resolved Hide resolved
}
48 changes: 23 additions & 25 deletions apps/angular/src/core/services/local-storage.service.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
import { Injectable } from '@angular/core';
import { AuthorizationTokens } from '@js-camp/core/models/authorization-tokens';
import { Observable, of } from 'rxjs';

/** Service for working with local storage. */
@Injectable({ providedIn: 'root' })
export class LocalStorageService {
TebyakinaEkaterina marked this conversation as resolved.
Show resolved Hide resolved

/**
* Save tokens to local storage.
* @param tokens - Access and refresh tokens.
* Save property to local storage.
* @param name - Property name.
* @param value - Property value.
*/
public saveTokens(tokens: AuthorizationTokens): void {
localStorage.setItem('accessToken', tokens.access);
localStorage.setItem('refreshToken', tokens.refresh);
public setValue<T>(name: string, value: T): void {
TebyakinaEkaterina marked this conversation as resolved.
Show resolved Hide resolved
localStorage.setItem(name, JSON.stringify(value));
}

/**
* Remove tokens from local storage.
* @param tokens - Access and refresh tokens.
* Get property from local storage.
* @param name - Property name.
* @returns Property with specified name.
*/
public removeTokens(): void {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
public getValue<T>(name: string): Observable<T | null> {
const value = localStorage.getItem(name);
if (value) {
try {
return of(JSON.parse(value));
} catch (parseError) {
console.error(parseError);
return of(null);
}
}
return of(null);
}

/**
* Get access token from local storage.
* @returns Access token.
* Remove property from local storage.
* @param name - Property name.
*/
public getAccessToken(): Observable<string | null> {
const accessToken = localStorage.getItem('accessToken');
return of(accessToken);
}

/**
* Get refresh token from local storage.
* @returns Refresh token.
*/
public getRefreshToken(): Observable<string | null> {
const refreshToken = localStorage.getItem('refreshToken');
return of(refreshToken);
public removeValue(name: string): void {
localStorage.removeItem(name);
}
}
1 change: 1 addition & 0 deletions libs/core/dtos/server-error-attribute.dto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** Error's attribute. The form field whose value caused the error. */
export enum ServerErrorAttributeDto {
TebyakinaEkaterina marked this conversation as resolved.
Show resolved Hide resolved
NonField = 'non_field_errors',
Email = 'email',
Expand Down
1 change: 1 addition & 0 deletions libs/core/dtos/user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** Data about user. */
export type UserDto = {
TebyakinaEkaterina marked this conversation as resolved.
Show resolved Hide resolved
readonly email: string;
readonly first_name: string;
Expand Down
1 change: 1 addition & 0 deletions libs/core/mappers/authorization-tokens.mapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AuthorizationTokensDto } from '../dtos/authorization-tokens.dto';
import { AuthorizationTokens } from '../models/authorization-tokens';

/** Map authorization tokens model and dto. */
export namespace AuthorizationTokensMapper {
TebyakinaEkaterina marked this conversation as resolved.
Show resolved Hide resolved

/**
Expand Down
1 change: 1 addition & 0 deletions libs/core/mappers/login-data.mapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LoginDataDto } from '../dtos/login-data.dto';
import { LoginData } from '../models/login-data';

/** Map user data for login model and dto. */
export namespace LoginDataMapper {

/**
Expand Down
1 change: 1 addition & 0 deletions libs/core/mappers/registration-data.mapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RegistrationDataDto } from '../dtos/registration-data.dto';
import { RegistrationData } from '../models/registration-data';

/** Map user data for registration model and dto. */
export namespace RegistrationDataMapper {

/**
Expand Down
1 change: 1 addition & 0 deletions libs/core/mappers/server-error-attribute.mapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ServerErrorAttributeDto } from '../dtos/server-error-attribute.dto';
import { ServerErrorAttribute } from '../models/server-error-attribute';

/** Map server error attribute model and dto. */
export namespace ServerErrorAttributeMapper {
const SERVER_ERROR_ATTRIBUTE_MAP_FROM_DTO: Readonly<Record<ServerErrorAttributeDto, ServerErrorAttribute>> = {
[ServerErrorAttributeDto.NonField]: ServerErrorAttribute.NonField,
Expand Down
1 change: 1 addition & 0 deletions libs/core/mappers/server-error.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { checkIsEnumMember } from '../utils/check-is-enum.util';

import { ServerErrorAttributeMapper } from './server-error-attribute.mapper';

/** Map server error model and dto. */
export namespace ServerErrorMapper {

/**
Expand Down
1 change: 1 addition & 0 deletions libs/core/mappers/user.mapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { UserDto } from '../dtos/user.dto';
import { User } from '../models/user';

/** Map user model and dto. */
export namespace UserMapper {

/**
Expand Down
5 changes: 0 additions & 5 deletions libs/core/models/server-error-status.ts

This file was deleted.

Loading