From a1606311613f4ad4f7cf43c73a99ee06bdda74e0 Mon Sep 17 00:00:00 2001
From: Hugo SUBTIL <ext.sopra.husubtil@grandlyon.com>
Date: Mon, 7 Dec 2020 17:15:41 +0100
Subject: [PATCH] fix(auth): add better signin signup logic feat(auth): add
 logout

---
 src/app/header/header.component.html          |  7 +--
 src/app/header/header.component.ts            | 28 ++++++++++--
 src/app/models/user-auth.model.ts             |  5 +++
 src/app/services/auth.service.ts              | 43 +++++++++++++++----
 .../signin-modal/signin-modal.component.html  |  3 ++
 .../signup-modal/signup-modal.component.html  | 13 +++---
 .../signup-modal/signup-modal.component.ts    | 28 +++++++++---
 7 files changed, 100 insertions(+), 27 deletions(-)
 create mode 100644 src/app/models/user-auth.model.ts

diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index 31bc0346c..f933d061e 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -14,14 +14,15 @@
     <a routerLink="/projects" [routerLinkActive]="'active'" i18n>Projets</a> -->
     <a routerLink="/about" [routerLinkActive]="'active'" i18n>Qui sommes-nous ?</a>
     <!-- <a routerLink="/login" [routerLinkActive]="'active'" i18n><span class="clickable ico-mglass purple"></span></a> -->
-    <button (click)="isSignUpOpen = !isSignUpOpen">
+    <button *ngIf="!isLoggedIn" (click)="isPopUpOpen = !isPopUpOpen">
       <span class="ico-profile" fxLayout="column" fxLayoutAlign="center center">
         <span class="head"></span>
         <span class="body"></span>
       </span>
     </button>
+    <button *ngIf="isLoggedIn" (click)="logout()">Logout</button>
   </div>
 </div>
 <app-menu-phone *ngIf="showMenu" (closeEvent)="closeMenu($event)"></app-menu-phone>
-<!-- <app-signup-modal [openned]="isSignUpOpen" (closed)="closeSignUpModal()"></app-signup-modal> -->
-<app-signin-modal [openned]="isSignUpOpen" (closed)="closeSignUpModal()"></app-signin-modal>
+<app-signup-modal *ngIf="displaySignUp" [openned]="isPopUpOpen" (closed)="closeSignUpModal($event)"></app-signup-modal>
+<app-signin-modal *ngIf="!displaySignUp" [openned]="isPopUpOpen" (closed)="closeSignInModal()"></app-signin-modal>
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index 475373180..5b8d6e7c7 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -1,4 +1,5 @@
 import { Component, OnInit } from '@angular/core';
+import { AuthService } from '../services/auth.service';
 
 @Component({
   selector: 'app-header',
@@ -7,9 +8,10 @@ import { Component, OnInit } from '@angular/core';
 })
 export class HeaderComponent implements OnInit {
   public showMenu = false;
-  public isSignUpOpen = false;
+  public isPopUpOpen = false;
+  public displaySignUp = true;
 
-  constructor() {}
+  constructor(private authService: AuthService) {}
   ngOnInit(): void {}
 
   public openMenu(): void {
@@ -18,7 +20,25 @@ export class HeaderComponent implements OnInit {
   public closeMenu(): void {
     this.showMenu = false;
   }
-  public closeSignUpModal(): void {
-    this.isSignUpOpen = false;
+
+  public get isLoggedIn(): boolean {
+    return this.authService.isLoggedIn();
+  }
+
+  public logout(): void {
+    return this.authService.logout();
+  }
+
+  public closeSignInModal(): void {
+    this.isPopUpOpen = false;
+    this.displaySignUp = true;
+  }
+
+  public closeSignUpModal(value: boolean): void {
+    if (!value) {
+      this.displaySignUp = false;
+    } else {
+      this.isPopUpOpen = false;
+    }
   }
 }
diff --git a/src/app/models/user-auth.model.ts b/src/app/models/user-auth.model.ts
new file mode 100644
index 000000000..4f3a55e5b
--- /dev/null
+++ b/src/app/models/user-auth.model.ts
@@ -0,0 +1,5 @@
+export class UserAuth {
+  username: string;
+  access_token: string;
+  expires_at: string;
+}
diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts
index 0a4e3810b..113cb04be 100644
--- a/src/app/services/auth.service.ts
+++ b/src/app/services/auth.service.ts
@@ -1,24 +1,53 @@
 import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
+import { DateTime } from 'luxon';
 import { BehaviorSubject, Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
+import { UserAuth } from '../models/user-auth.model';
 import { User } from '../models/user.model';
-
 @Injectable({
   providedIn: 'root',
 })
 export class AuthService {
-  private userSubject: BehaviorSubject<User>;
-  public user: Observable<User>;
+  private userSubject: BehaviorSubject<UserAuth>;
+  public user: Observable<UserAuth>;
 
   constructor(private http: HttpClient) {
-    this.userSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('user')));
+    this.userSubject = new BehaviorSubject<UserAuth>(JSON.parse(localStorage.getItem('user')));
     this.user = this.userSubject.asObservable();
   }
 
+  public get userValue(): UserAuth {
+    return this.userSubject.value;
+  }
+
+  public get token(): string {
+    return this.userSubject.value.access_token;
+  }
+
+  public logout(): void {
+    localStorage.removeItem('user');
+    this.userSubject.next(null);
+  }
+
+  public isLoggedIn(): boolean {
+    if (this.userValue) {
+      return new DateTime.local().setZone('Europe/Paris') < this.getExpiration();
+    }
+    return false;
+  }
+
+  public getExpiration(): void {
+    return DateTime.fromISO(this.userValue.expires_at, { zone: 'Europe/Paris' });
+  }
+
+  public register(user: User): Observable<any> {
+    return this.http.post('api/users', user);
+  }
+
   public login(email: string, password: string): Observable<any> {
     return this.http
-      .post<User>('api/auth/login', { email, password })
+      .post<UserAuth>('api/auth/login', { email, password })
       .pipe(
         map((user) => {
           // store user details and jwt token in local storage to keep user logged in between page refreshes
@@ -29,10 +58,6 @@ export class AuthService {
       );
   }
 
-  public register(user: User): Observable<any> {
-    return this.http.post('api/users', user);
-  }
-
   public verifyUser(userId: string, token: string): Observable<any> {
     return this.http.post(`api/users/verify/${userId}`, null, {
       params: { token },
diff --git a/src/app/shared/components/signin-modal/signin-modal.component.html b/src/app/shared/components/signin-modal/signin-modal.component.html
index 93f21867f..c4e3795c3 100644
--- a/src/app/shared/components/signin-modal/signin-modal.component.html
+++ b/src/app/shared/components/signin-modal/signin-modal.component.html
@@ -10,6 +10,7 @@
           <label for="email">Email</label>
           <input
             type="email"
+            autocomplete="on"
             formControlName="email"
             class="form-control"
             [ngClass]="{ 'is-invalid': submitted && f.email.errors }"
@@ -22,6 +23,7 @@
           <label for="password">Mot de passe</label>
           <input
             type="password"
+            autocomplete="on"
             formControlName="password"
             class="form-control"
             [ngClass]="{ 'is-invalid': submitted && f.password.errors }"
@@ -38,6 +40,7 @@
           <label for="confirmPassword">Confirmation du mot de passe</label>
           <input
             type="password"
+            autocomplete="on"
             formControlName="confirmPassword"
             class="form-control"
             [ngClass]="{ 'is-invalid': submitted && f.confirmPassword.errors }"
diff --git a/src/app/shared/components/signup-modal/signup-modal.component.html b/src/app/shared/components/signup-modal/signup-modal.component.html
index b9cd6065f..e12cffd1b 100644
--- a/src/app/shared/components/signup-modal/signup-modal.component.html
+++ b/src/app/shared/components/signup-modal/signup-modal.component.html
@@ -7,35 +7,38 @@
     <div class="card-body">
       <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
         <div class="form-group">
-          <label for="username">Username</label>
+          <label for="username">Identifiant</label>
           <input
             type="text"
             formControlName="username"
+            autocomplete="on"
             class="form-control"
             [ngClass]="{ 'is-invalid': submitted && f.username.errors }"
           />
           <div *ngIf="submitted && f.username.errors" class="invalid-feedback">
-            <div *ngIf="f.username.errors.required">Username is required</div>
+            <div *ngIf="f.username.errors.required">Identifiant requis</div>
           </div>
         </div>
         <div class="form-group">
-          <label for="password">Password</label>
+          <label for="password">Mot de passe</label>
           <input
             type="password"
             formControlName="password"
+            autocomplete="on"
             class="form-control"
             [ngClass]="{ 'is-invalid': submitted && f.password.errors }"
           />
           <div *ngIf="submitted && f.password.errors">
-            <div *ngIf="f.password.errors.required">Password is required</div>
+            <div *ngIf="f.password.errors.required">Mot de passe requis</div>
           </div>
         </div>
+        <div *ngIf="authFailed">Identifiant ou mot de passe invalide</div>
         <div>
           <button [disabled]="loading">
             <span *ngIf="loading"></span>
             Login
           </button>
-          <a routerLink="../register" class="btn btn-link">Register</a>
+          <button (click)="sendSwitchToSignIn()">Inscription</button>
         </div>
       </form>
     </div>
diff --git a/src/app/shared/components/signup-modal/signup-modal.component.ts b/src/app/shared/components/signup-modal/signup-modal.component.ts
index f2704cd5f..1ef87d4d1 100644
--- a/src/app/shared/components/signup-modal/signup-modal.component.ts
+++ b/src/app/shared/components/signup-modal/signup-modal.component.ts
@@ -1,5 +1,6 @@
 import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
 import { FormGroup, FormBuilder, Validators, AbstractControl } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
 import { first } from 'rxjs/operators';
 import { AuthService } from '../../../services/auth.service';
 
@@ -12,17 +13,26 @@ export class SignUpModalComponent implements OnInit {
   public loginForm: FormGroup;
   public loading = false;
   public submitted = false;
+  public authFailed = false;
+  public returnUrl: string;
 
-  constructor(private formBuilder: FormBuilder, private authService: AuthService) {}
+  constructor(
+    private formBuilder: FormBuilder,
+    private route: ActivatedRoute,
+    private router: Router,
+    private authService: AuthService
+  ) {}
 
   @Input() public openned: boolean;
-  @Output() closed = new EventEmitter();
+  @Output() closed = new EventEmitter<boolean>();
 
   ngOnInit(): void {
     this.loginForm = this.formBuilder.group({
       username: ['', Validators.required],
       password: ['', Validators.required],
     });
+    // get return url from route parameters or default to '/'
+    this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
   }
 
   // getter for form fields
@@ -31,7 +41,11 @@ export class SignUpModalComponent implements OnInit {
   }
 
   public closeModal(): void {
-    this.closed.emit();
+    this.closed.emit(true);
+  }
+
+  public sendSwitchToSignIn(): void {
+    this.closed.emit(false);
   }
 
   public onSubmit(): void {
@@ -47,11 +61,13 @@ export class SignUpModalComponent implements OnInit {
       .login(this.f.username.value, this.f.password.value)
       .pipe(first())
       .subscribe(
-        (data) => {
-          //TODO: redirect to ?
+        () => {
+          this.router.navigate([this.returnUrl]);
+          this.closeModal();
         },
-        (error) => {
+        () => {
           this.loading = false;
+          this.authFailed = true;
         }
       );
   }
-- 
GitLab