浏览代码

dev en cours

rajah 11 月之前
父节点
当前提交
c3c62212af

+ 6 - 0
angular.json

@@ -64,6 +64,12 @@
         },
         "serve": {
           "builder": "@angular-devkit/build-angular:dev-server",
+          "options": {
+            "proxyConfig": "src/proxy.conf.json"
+            "ssl": true,
+            "sslKey": "src/assets/ssl/key.pem",
+            "sslCert": "src/assets/ssl/cert.pem"
+          },
           "configurations": {
             "production": {
               "buildTarget": "demovote-frontend:build:production"

+ 3 - 3
src/app/env.ts

@@ -1,5 +1,5 @@
 
-export const Environnement = 
+export const Environnement =
 {
-	apiUrl: 'http://localhost:8080/demovote-api/v1/'
-};
+	apiUrl: 'https://localhost:8443/demovote-api/v1/'
+};

+ 8 - 1
src/app/interfaces/user.ts

@@ -6,6 +6,13 @@ export class User
   nom: string = "";
   prenom: string = "";
   role: string = "";
-  token: string = "";
+  accessToken: string = "";
+  refreshToken: string = "";
   erreur: string = "";
 }
+
+export class RefreshToken
+{
+  accessToken: string = "";
+  refreshToken: string = "";
+}

+ 22 - 4
src/app/services/account.service.ts

@@ -1,9 +1,10 @@
 import { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
 import { HttpClient } from '@angular/common/http';
 import { BehaviorSubject, Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 import { Environnement } from '../env';
-import { User } from '../interfaces/user';
+import { User, RefreshToken } from '../interfaces/user';
 import { Participant } from '../interfaces/participant';
 
 @Injectable({ providedIn: 'root' })
@@ -19,7 +20,9 @@ export class AccountService
 
   public user: Observable<User | null>;
 
-  constructor(private httpClient: HttpClient)
+  private refreshToken: RefreshToken = new RefreshToken();
+
+  constructor(private router : Router, private httpClient: HttpClient)
   {
     this.userSubject = new BehaviorSubject(JSON.parse(sessionStorage.getItem('user')!));
     this.user = this.userSubject.asObservable();
@@ -28,20 +31,35 @@ export class AccountService
   public isLogged() { if (this.userSubject.value) { return true; } return false; }
   public getUsername() { if (this.userSubject.value) { return this.userSubject.value.username; } return ""; }
   public getRole() { if (this.userSubject.value) { return this.userSubject.value.role; } return ""; }
-  public getToken() { if (this.userSubject.value) { return this.userSubject.value.token; } return ""; }
+  public getAccessToken() { if (this.userSubject.value) { return this.userSubject.value.accessToken; } return ""; }
+  private getRefreshToken() { if (this.userSubject.value) { return this.userSubject.value.refreshToken; } return ""; }
 
   signIn(usr: User): Observable<User>
   {
     return this.httpClient.post<User>(`${this.baseURLsig}/in`, usr).pipe(map(u => { sessionStorage.setItem('user', JSON.stringify(u)); this.userSubject.next(u); return u; }));
   }
+  updateToken()
+  {
+    this.refreshToken.accessToken = "";
+    this.refreshToken.refreshToken = this.getRefreshToken();
+
+    return this.httpClient.post<RefreshToken>(`${this.baseURLsig}/refresh`, this.refreshToken).pipe(map(u => { if ((u != null) && (this.userSubject.value != null)) { this.userSubject.value.accessToken = u.accessToken; } }));
+  }
 
   signOut()
   {
-    this.httpClient.post<User>(`${this.baseURLsig}/signout`, null);
+    this.httpClient.post<User>(`${this.baseURLsig}/out`, null);
 
     sessionStorage.removeItem('user');
     this.userSubject.next(null);
   }
+  silentOut()
+  {
+    sessionStorage.removeItem('user');
+    this.userSubject.next(null);
+
+    if ((this.router.url === '/') || (this.router.url === '/home')) { window.location.reload(); } else { this.router.navigate(['/']); }
+  }
 
   getListArrives(): Observable<User[]> { return this.httpClient.get<User[]>(`${this.baseURLsig}/list`); }
 

+ 36 - 3
src/app/services/auth.interceptor.ts

@@ -1,5 +1,7 @@
+import { catchError, switchMap } from 'rxjs/operators';
+import { throwError } from "rxjs";
 import { Injectable } from '@angular/core';
-import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
 import { Observable } from 'rxjs';
 import { AccountService } from './account.service';
 
@@ -10,15 +12,46 @@ export class AuthInterceptor implements HttpInterceptor
 
   private authToken: string = "";
 
+  private isRefreshing = false;
+
   constructor(private accountService: AccountService) { }
 
   intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>>
   {
-    const authToken = this.accountService.getToken();
+    const authToken = this.accountService.getAccessToken();
 
     const modified = request.clone({ headers: request.headers.append('Authorization', "Bearer " + authToken) });
 
-    return next.handle(modified);
+    return next.handle(modified).pipe(
+      catchError((error) => { if (error instanceof HttpErrorResponse && !request.url.includes('/sign/in') && error.status === 403) { return this.handle401Error(request, next); } return throwError(() => error); })
+      );
+  }
+
+  private handle401Error(request: HttpRequest<any>, next: HttpHandler)
+  {
+    if (!this.isRefreshing)
+    {
+      this.isRefreshing = true;
+
+      if (this.accountService.isLogged())
+      {
+        return this.accountService.updateToken().pipe(
+          switchMap(() => {
+            this.isRefreshing = false;
+            const authToken = this.accountService.getAccessToken();
+            const modified = request.clone({ headers: request.headers.append('Authorization', "Bearer " + authToken) });
+            return next.handle(modified);
+            }),
+          catchError((error) => {
+            this.isRefreshing = false;
+            if (error.status == '403') { this.accountService.silentOut(); }
+            return throwError(() => error);
+            })
+          );
+      }
+    }
+
+    return next.handle(request);
   }
 
 }

+ 34 - 0
src/assets/ssl/cert.pem

@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----

+ 1 - 0
src/assets/ssl/create.txt

@@ -0,0 +1 @@
+openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/C=FR/ST=Auvergne Rhône-Alpes/L=Clérieux/O=Triple A/OU=informatique/CN=localhost"

+ 52 - 0
src/assets/ssl/key.pem

@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+PZLEY/GfYGzxi7KIFs7WiwMXySH3
+-----END PRIVATE KEY-----

+ 6 - 0
src/proxy.conf.json

@@ -0,0 +1,6 @@
+{
+    "/demovote-api/v1/*": {
+        "target": "https://localhost:8443",
+        "secure": true
+    }
+}