From 65d7d6cd738e85e18e9a3c401c4594786a879ea0 Mon Sep 17 00:00:00 2001
From: FORESTIER Fabien <fabien.forestier@soprasteria.com>
Date: Fri, 21 Sep 2018 11:06:36 +0200
Subject: [PATCH] Add base for authentification with JWT

---
 package-lock.json                             | 11 +++
 package.json                                  |  6 +-
 src/app/app.module.ts                         | 22 +++++-
 .../components/header/header.component.html   | 19 +++--
 .../components/header/header.component.scss   |  5 ++
 .../components/header/header.component.ts     | 14 ++++
 src/app/core/components/index.ts              |  4 +-
 .../components/login/login.component.html     | 50 ++++++++++++++
 .../components/login/login.component.scss     | 23 +++++++
 .../components/login/login.component.spec.ts  | 25 +++++++
 .../core/components/login/login.component.ts  | 55 +++++++++++++++
 src/app/core/core-routing.module.ts           |  9 ++-
 src/app/core/core.module.ts                   |  8 +++
 src/app/core/interceptors/auth-interceptor.ts | 25 +++++++
 src/app/core/models/auth.model.ts             | 18 +++++
 src/app/core/models/index.ts                  |  4 +-
 src/app/core/services/auth.service.ts         | 69 +++++++++++++++++++
 src/app/core/services/index.ts                |  4 +-
 src/app/routes.ts                             |  7 ++
 src/i18n/messages.en.xlf                      | 36 ++++++++++
 src/i18n/messages.fr.xlf                      | 36 ++++++++++
 21 files changed, 435 insertions(+), 15 deletions(-)
 create mode 100644 src/app/core/components/login/login.component.html
 create mode 100644 src/app/core/components/login/login.component.scss
 create mode 100644 src/app/core/components/login/login.component.spec.ts
 create mode 100644 src/app/core/components/login/login.component.ts
 create mode 100644 src/app/core/interceptors/auth-interceptor.ts
 create mode 100644 src/app/core/models/auth.model.ts
 create mode 100644 src/app/core/services/auth.service.ts

diff --git a/package-lock.json b/package-lock.json
index f32b209f..687d84c5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2213,6 +2213,12 @@
         "@types/jasmine": "*"
       }
     },
+    "@types/jwt-decode": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/@types/jwt-decode/-/jwt-decode-2.2.1.tgz",
+      "integrity": "sha512-aWw2YTtAdT7CskFyxEX2K21/zSDStuf/ikI3yBqmwpwJF0pS+/IX5DWv+1UFffZIbruP6cnT9/LAJV1gFwAT1A==",
+      "dev": true
+    },
     "@types/lodash": {
       "version": "4.14.116",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz",
@@ -8322,6 +8328,11 @@
         }
       }
     },
+    "jwt-decode": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz",
+      "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
+    },
     "karma": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.2.tgz",
diff --git a/package.json b/package.json
index fffab5b2..321978f3 100644
--- a/package.json
+++ b/package.json
@@ -29,8 +29,6 @@
     "@angular/router": "6.1.0",
     "@turf/centroid": "^5.1.5",
     "@turf/helpers": "^6.1.4",
-    "@types/lodash.clonedeep": "^4.5.4",
-    "@types/mapbox-gl": "^0.47.0",
     "angulartics2": "^6.2.0",
     "bulma": "^0.7.1",
     "bulma-checkradio": "^2.1.0",
@@ -38,6 +36,7 @@
     "bulma-tooltip": "^2.0.1",
     "core-js": "^2.5.7",
     "font-awesome": "^4.7.0",
+    "jwt-decode": "^2.2.0",
     "lodash": "^4.17.10",
     "lodash.clonedeep": "^4.5.0",
     "mapbox-gl": "^0.47.0",
@@ -54,6 +53,9 @@
     "@angular/language-service": "6.1.0",
     "@types/jasmine": "^2.8.8",
     "@types/jasminewd2": "~2.0.2",
+    "@types/jwt-decode": "^2.2.1",
+    "@types/lodash.clonedeep": "^4.5.4",
+    "@types/mapbox-gl": "^0.47.0",
     "@types/node": "^6.0.112",
     "codelyzer": "^4.3.0",
     "jasmine-core": "~2.8.0",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 0ab6462b..9b0b3b73 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,6 +1,6 @@
 import { BrowserModule } from '@angular/platform-browser';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { NgModule } from '@angular/core';
+import { NgModule, APP_INITIALIZER } from '@angular/core';
 import { HttpClientModule } from '@angular/common/http';
 
 import { AppComponent } from './app.component';
@@ -9,6 +9,17 @@ import { CoreModule } from './core/core.module';
 import { EditorialisationModule } from './editorialisation/editorialisation.module';
 import { Angulartics2Module } from 'angulartics2';
 import { Angulartics2Piwik } from 'angulartics2/piwik';
+import { AuthService } from './core/services';
+
+// Function used by APP_INITIALIZER before the app start: init user info / statut (expect a promise)
+export function loadUser(authService: AuthService) {
+  return (): Promise<any> => {
+    return new Promise((resolve, reject) => {
+      authService.setUserInfo();
+      resolve();
+    });
+  };
+}
 
 @NgModule({
   declarations: [
@@ -23,7 +34,14 @@ import { Angulartics2Piwik } from 'angulartics2/piwik';
     AppRoutingModule,
     Angulartics2Module.forRoot([Angulartics2Piwik]),
   ],
-  providers: [],
+  providers: [
+    {
+      provide: APP_INITIALIZER,
+      useFactory: loadUser,
+      deps: [AuthService],
+      multi: true,
+    },
+  ],
   bootstrap: [AppComponent],
 })
 export class AppModule { }
diff --git a/src/app/core/components/header/header.component.html b/src/app/core/components/header/header.component.html
index 895bb17b..60141364 100644
--- a/src/app/core/components/header/header.component.html
+++ b/src/app/core/components/header/header.component.html
@@ -15,14 +15,15 @@
     </div>
     <div class="navbar-menu" [ngClass]="{'is-active': burgerActive === true}">
       <div class="navbar-start">
-
         <a class="navbar-item home-link" [routerLink]="['/', AppRoutes.home.uri]" routerLinkActive="active-link" i18n="@@header.home">
           Home
         </a>
-        <a class="navbar-item research-link" [routerLink]="['/', AppRoutes.research.uri]" routerLinkActive="active-link" i18n="@@header.data">
+        <a class="navbar-item research-link" [routerLink]="['/', AppRoutes.research.uri]" routerLinkActive="active-link"
+          i18n="@@header.data">
           Data
         </a>
-        <a class="navbar-item approach-link" [routerLink]="['/', AppRoutes.approach.uri]" routerLinkActive="active-link" i18n="@@header.approach">
+        <a class="navbar-item approach-link" [routerLink]="['/', AppRoutes.approach.uri]" routerLinkActive="active-link"
+          i18n="@@header.approach">
           Approach
         </a>
         <a class="navbar-item approach-link" [routerLink]="['/', AppRoutes.actors.uri]" routerLinkActive="active-link" i18n="@@header.actors">
@@ -31,12 +32,18 @@
       </div>
       <div class="navbar-end">
         <div class="navbar-item">
-          <a href preventDefault class="flag-logo" (click)="changeLanguage('en')"><img src="./assets/img/uk-flag.png" title="English"
-              i18n-title="@@header.logoEnglish" alt="Drapeau français"></a>
+          <a href preventDefault class="flag-logo" (click)="changeLanguage('en')"><img src="./assets/img/uk-flag.png"
+              title="English" i18n-title="@@header.logoEnglish" alt="Drapeau français"></a>
           <a href preventDefault class="flag-logo" (click)="changeLanguage('fr')"><img src="./assets/img/france-flag.png"
               title="French" i18n-title="@@header.logoFrench" alt="Drapeau du Royaume-Uni"></a>
         </div>
-
+        <div class="navbar-item">
+          <a [routerLink]="['/', AppRoutes.signin.uri]" routerLinkActive="active-link" *ngIf="!userIsSignedIn" i18n="@@header.signIn">
+            Sign In
+          </a>
+          <span class="username" *ngIf="userIsSignedIn">{{ username }}</span>
+          <a href preventDefault (click)="signOut()" *ngIf="userIsSignedIn" i18n="@@header.signOut">Sign Out</a>
+        </div>
       </div>
     </div>
   </nav>
diff --git a/src/app/core/components/header/header.component.scss b/src/app/core/components/header/header.component.scss
index ca9cecf6..cc5e799e 100644
--- a/src/app/core/components/header/header.component.scss
+++ b/src/app/core/components/header/header.component.scss
@@ -20,4 +20,9 @@
 }
 .navbar-menu {
   padding: 0.5rem 2rem;
+}
+
+.username {
+  margin-right: 1rem;
+  font-weight: bold;
 }
\ No newline at end of file
diff --git a/src/app/core/components/header/header.component.ts b/src/app/core/components/header/header.component.ts
index cadcc70b..9f386be0 100644
--- a/src/app/core/components/header/header.component.ts
+++ b/src/app/core/components/header/header.component.ts
@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
 import { environment } from '../../../../environments/environment';
 import { AppRoutes } from '../../../routes';
 import { ActivatedRoute, Router } from '../../../../../node_modules/@angular/router';
+import { AuthService } from '../../services';
 
 @Component({
   selector: 'app-header',
@@ -15,6 +16,7 @@ export class HeaderComponent implements OnInit {
 
   constructor(
     private _router: Router,
+    private _authService: AuthService,
   ) { }
 
   ngOnInit() {
@@ -25,4 +27,16 @@ export class HeaderComponent implements OnInit {
     window.location.href = environment.angularAppHost[lang] + this._router.url;
   }
 
+  signOut() {
+    this._authService.signOut();
+  }
+
+  get userIsSignedIn() {
+    return this._authService.userIsSignedIn();
+  }
+
+  get username() {
+    return `${this._authService.user.firstname} ${this._authService.user.lastname}`;
+  }
+
 }
diff --git a/src/app/core/components/index.ts b/src/app/core/components/index.ts
index 82c8d51d..dea54885 100644
--- a/src/app/core/components/index.ts
+++ b/src/app/core/components/index.ts
@@ -3,8 +3,9 @@ import { MainComponent } from './main/main.component';
 import { FooterComponent } from './footer/footer.component';
 import { NotificationsComponent } from './notifications/notifications.component';
 import { ErrorComponent } from './error/error.component';
+import { LoginComponent } from './login/login.component';
 
-export { HeaderComponent, MainComponent, FooterComponent, NotificationsComponent, ErrorComponent };
+export { HeaderComponent, MainComponent, FooterComponent, NotificationsComponent, ErrorComponent, LoginComponent };
 
 // tslint:disable-next-line:variable-name
 export const CoreComponents = [
@@ -13,4 +14,5 @@ export const CoreComponents = [
   FooterComponent,
   NotificationsComponent,
   ErrorComponent,
+  LoginComponent,
 ];
diff --git a/src/app/core/components/login/login.component.html b/src/app/core/components/login/login.component.html
new file mode 100644
index 00000000..20c16afe
--- /dev/null
+++ b/src/app/core/components/login/login.component.html
@@ -0,0 +1,50 @@
+<div class="columns">
+  <form [formGroup]="form" class="column is-8-touch is-offset-2-touch is-6 is-offset-3">
+    <h1 class="has-text-centered" i18n="@@login.signIn">Sign In</h1>
+
+    <div class="notification is-danger" [ngStyle]="{'visibility': (errorLogin === true ? 'visible' : 'hidden')}">
+      <button class="delete" (click)="closeErrorMessage()"></button>
+      <span i18n="@@login.incorrectCredentials">Your credentials are not correct.</span>
+    </div>
+
+    <div class="field">
+      <label class="label">Email:</label>
+      <div class="field">
+        <p class="control has-icons-left">
+          <input class="input" type="email" formControlName="email">
+          <span class="icon is-small is-left">
+            <i class="fas fa-envelope"></i>
+          </span>
+        </p>
+        <div *ngIf="email.invalid && email.touched" class="field-error">
+          <div *ngIf="email.errors['required']" i18n="@@login.emailRequired">
+            Email is required.
+          </div>
+          <div *ngIf="email.errors['email']" i18n="@@login.incorrectEmail">
+            You need to provide a valid email.
+            </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="field">
+      <label class="label" i18n="@@login.password">Password:</label>
+      <div class="field">
+        <p class="control has-icons-left">
+          <input class="input" type="password" formControlName="password">
+          <span class="icon is-small is-left">
+            <i class="fas fa-lock"></i>
+          </span>
+        </p>
+        <div *ngIf="password.invalid && password.touched" class="field-error">
+            <div *ngIf="password.errors['required']" i18n="@@login.passwordRequired">
+              Password is required.
+            </div>
+          </div>
+      </div>
+    </div>
+    <div class="has-text-centered button-wrapper">
+      <button class="button is-success" (click)="login()" [disabled]="form.invalid" i18n="@@login.signInBtn">Sign In</button>
+    </div>
+  </form>
+</div>
\ No newline at end of file
diff --git a/src/app/core/components/login/login.component.scss b/src/app/core/components/login/login.component.scss
new file mode 100644
index 00000000..9b3550ac
--- /dev/null
+++ b/src/app/core/components/login/login.component.scss
@@ -0,0 +1,23 @@
+@import '../../../../scss/variables.scss';
+
+.notification {
+  padding: 0.5rem 1.75rem 0.5rem 0.75rem;
+}
+
+.field-error {
+  color: $red;
+}
+
+.ng-touched {
+  &.ng-valid:not(form) {
+    border-right: 5px solid $green; /* green */
+  }
+
+  &.ng-invalid:not(form)  {
+    border-right: 5px solid $red; /* red */
+  }
+}
+
+.button-wrapper {
+  margin-top: 1rem;
+}
\ No newline at end of file
diff --git a/src/app/core/components/login/login.component.spec.ts b/src/app/core/components/login/login.component.spec.ts
new file mode 100644
index 00000000..d6d85a84
--- /dev/null
+++ b/src/app/core/components/login/login.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoginComponent } from './login.component';
+
+describe('LoginComponent', () => {
+  let component: LoginComponent;
+  let fixture: ComponentFixture<LoginComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ LoginComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(LoginComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/app/core/components/login/login.component.ts b/src/app/core/components/login/login.component.ts
new file mode 100644
index 00000000..1e5ac0d9
--- /dev/null
+++ b/src/app/core/components/login/login.component.ts
@@ -0,0 +1,55 @@
+import { Component, OnInit } from '@angular/core';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { AuthService } from '../../services';
+import { Router } from '@angular/router';
+
+@Component({
+  selector: 'app-login',
+  templateUrl: './login.component.html',
+  styleUrls: ['./login.component.scss'],
+})
+export class LoginComponent {
+
+  form: FormGroup;
+  errorLogin = false;
+
+  constructor(
+    private _fb: FormBuilder,
+    private _authService: AuthService,
+    private _router: Router,
+  ) {
+
+    this.form = this._fb.group({
+      email: ['', [Validators.required, Validators.email]],
+      password: ['', Validators.required],
+    });
+  }
+
+  login() {
+    const val = this.form.value;
+
+    if (val.email && val.password) {
+      this._authService.login(val.email, val.password).subscribe(
+        (res) => {
+          if (res) {
+            this.errorLogin = false;
+            this._router.navigateByUrl('/');
+          } else {
+            this.errorLogin = true;
+          }
+        },
+        (err) => {
+          this.errorLogin = true;
+        },
+      );
+    }
+  }
+
+  closeErrorMessage() {
+    this.errorLogin = false;
+  }
+
+  get email() { return this.form.get('email'); }
+  get password() { return this.form.get('password'); }
+
+}
diff --git a/src/app/core/core-routing.module.ts b/src/app/core/core-routing.module.ts
index 5f7c4038..319b8225 100644
--- a/src/app/core/core-routing.module.ts
+++ b/src/app/core/core-routing.module.ts
@@ -1,7 +1,7 @@
 import { NgModule } from '@angular/core';
 import { Routes, RouterModule } from '@angular/router';
 import { AppRoutes } from '../routes';
-import { ErrorComponent } from './components';
+import { ErrorComponent, LoginComponent } from './components';
 
 export const routes: Routes = [
   {
@@ -9,6 +9,13 @@ export const routes: Routes = [
     redirectTo: AppRoutes.home.uri,
     pathMatch: 'full',
   },
+  {
+    path: AppRoutes.signin.uri,
+    component: LoginComponent,
+    data: {
+      title: AppRoutes.signin.title,
+    },
+  },
   {
     path: AppRoutes.error.uri,
     component: ErrorComponent,
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 4a5b5aa6..57da56fb 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -9,12 +9,15 @@ import { HttpErrorResponseInterceptor } from './interceptors/http-error-response
 import { HTTP_INTERCEPTORS } from '@angular/common/http';
 import { CoreServices } from './services';
 import { RouterModule } from '../../../node_modules/@angular/router';
+import { ReactiveFormsModule } from '@angular/forms';
+import { AuthInterceptor } from './interceptors/auth-interceptor';
 
 @NgModule({
   imports: [
     CommonModule,
     CoreRoutingModule,
     SharedModule,
+    ReactiveFormsModule,
   ],
   declarations: [CoreComponents],
   providers: [
@@ -28,6 +31,11 @@ import { RouterModule } from '../../../node_modules/@angular/router';
       useClass: HttpErrorResponseInterceptor,
       multi: true,
     },
+    {
+      provide: HTTP_INTERCEPTORS,
+      useClass: AuthInterceptor,
+      multi: true,
+    },
   ],
   exports: [MainComponent, NotificationsComponent],
 })
diff --git a/src/app/core/interceptors/auth-interceptor.ts b/src/app/core/interceptors/auth-interceptor.ts
new file mode 100644
index 00000000..0551ba9d
--- /dev/null
+++ b/src/app/core/interceptors/auth-interceptor.ts
@@ -0,0 +1,25 @@
+import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { environment } from '../../../environments/environment.prod';
+
+@Injectable()
+export class AuthInterceptor implements HttpInterceptor {
+
+  intercept(
+    req: HttpRequest<any>,
+    next: HttpHandler,
+  ): Observable<HttpEvent<any>> {
+    const token = localStorage.getItem('token');
+    const request = req;
+
+    // Can also filter by url
+    // && req.url.includes(environment.elasticsearchUrl.full)
+    // if (token) {
+    //   request = req.clone({
+    //     headers: req.headers.set('Authorization', 'Bearer ' + token),
+    //   });
+    // }
+    return next.handle(request);
+  }
+}
diff --git a/src/app/core/models/auth.model.ts b/src/app/core/models/auth.model.ts
new file mode 100644
index 00000000..6de44da6
--- /dev/null
+++ b/src/app/core/models/auth.model.ts
@@ -0,0 +1,18 @@
+export class User {
+  id: number;
+  firstname: string;
+  lastname: string;
+
+  //  payload is the decrypted payload of the JWT token
+  constructor(payload) {
+    if (payload) {
+      this.id = payload.id;
+      this.firstname = payload.firstname;
+      this.lastname = payload.lastname;
+    }
+  }
+}
+
+export interface ILoginResponse {
+  token: string;
+}
diff --git a/src/app/core/models/index.ts b/src/app/core/models/index.ts
index 804ec59d..019c0524 100644
--- a/src/app/core/models/index.ts
+++ b/src/app/core/models/index.ts
@@ -1,5 +1,5 @@
 import { Notification, INotification } from './notification.model';
 import { IMatomoResponse } from './matomo.model';
+import { User, ILoginResponse } from './auth.model';
 
-export { Notification, INotification };
-export { IMatomoResponse };
+export { Notification, INotification, User, IMatomoResponse, ILoginResponse };
diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts
new file mode 100644
index 00000000..128004fe
--- /dev/null
+++ b/src/app/core/services/auth.service.ts
@@ -0,0 +1,69 @@
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import * as JwtDecode from 'jwt-decode';
+import { map } from 'rxjs/operators';
+import { Observable } from 'rxjs';
+import { ILoginResponse, User } from '../models';
+
+@Injectable()
+export class AuthService {
+
+  private _user: User = null;
+
+  constructor(
+    private _http: HttpClient,
+  ) { }
+
+  login(email: string, password: string): Observable<boolean> {
+    return this._http.post<ILoginResponse>('http://localhost:3000/api/login', { email, password }).pipe(
+      map(
+        (res) => {
+          return this.setSession(res);
+        },
+        (err) => {
+          return false;
+        },
+      ),
+    );
+  }
+
+  logout() {
+    localStorage.removeItem('token');
+  }
+
+  private setSession(authResult): boolean {
+    let success = false;
+    if (authResult && authResult.token) {
+      localStorage.setItem('token', authResult.token);
+      this.setUserInfo();
+      success = true;
+    } else {
+      this.signOut();
+    }
+
+    return success;
+  }
+
+  setUserInfo() {
+    const token = localStorage.getItem('token');
+    try {
+      const payload = JwtDecode(token);
+      this._user = new User(payload);
+    } catch (error) {
+    }
+  }
+
+  signOut() {
+    localStorage.removeItem('token');
+    this._user = null;
+  }
+
+  get user() {
+    return this._user;
+  }
+
+  userIsSignedIn() {
+    return this._user != null;
+  }
+}
diff --git a/src/app/core/services/index.ts b/src/app/core/services/index.ts
index ed90907e..30fdaef5 100644
--- a/src/app/core/services/index.ts
+++ b/src/app/core/services/index.ts
@@ -3,8 +3,9 @@ import { NotificationService } from './notification.service';
 import { MatomoService } from './matomo.service';
 import { NavigationHistoryService } from './navigation-history.service';
 import { StorageService } from './storage.service';
+import { AuthService } from './auth.service';
 
-export { ErrorService, NotificationService, MatomoService, NavigationHistoryService };
+export { ErrorService, NotificationService, MatomoService, NavigationHistoryService, AuthService };
 
 // tslint:disable-next-line:variable-name
 export const CoreServices = [
@@ -13,4 +14,5 @@ export const CoreServices = [
   MatomoService,
   NavigationHistoryService,
   StorageService,
+  AuthService,
 ];
diff --git a/src/app/routes.ts b/src/app/routes.ts
index 18c900f3..c727cc8e 100644
--- a/src/app/routes.ts
+++ b/src/app/routes.ts
@@ -1,5 +1,12 @@
 // tslint:disable-next-line:variable-name
 export const AppRoutes = {
+  signin: {
+    uri: 'connexion',
+    title: {
+      fr: 'Connexion',
+      en: 'Sign In',
+    },
+  },
   home: {
     uri: 'accueil',
     title: {
diff --git a/src/i18n/messages.en.xlf b/src/i18n/messages.en.xlf
index 5fd3e499..f40531a5 100644
--- a/src/i18n/messages.en.xlf
+++ b/src/i18n/messages.en.xlf
@@ -18,6 +18,14 @@
         <source>Actors</source>
         <target>Actors</target>
       </trans-unit>
+      <trans-unit id="header.signIn" datatype="html">
+         <source>Sign In</source>
+         <target>Sign In</target>
+      </trans-unit>
+      <trans-unit id="header.signOut" datatype="html">
+         <source>Sign Out</source>
+         <target>Sign Out</target>
+      </trans-unit>
       <trans-unit id="footer.rss" datatype="html">
         <source>RSS Feed (new window)</source>
         <target>RSS Feed (new window)</target>
@@ -275,6 +283,34 @@
          <source>Go back to home page</source>
          <target>Go back to home page</target>
       </trans-unit>
+       <trans-unit id="login.signIn" datatype="html">
+         <source>Sign In</source>
+         <target>Sign In</target>
+      </trans-unit>
+      <trans-unit id="login.incorrectCredentials" datatype="html">
+         <source>Your credentials are not correct.</source>
+         <target>Your credentials are not correct.</target>
+      </trans-unit>
+      <trans-unit id="login.emailRequired" datatype="html">
+         <source>Email is required.</source>
+         <target>Email is required.</target>
+      </trans-unit>
+      <trans-unit id="login.incorrectEmail" datatype="html">
+         <source>You need to provide a valid email.</source>
+         <target>You need to provide a valid email.</target>
+      </trans-unit>
+      <trans-unit id="login.password" datatype="html">
+         <source>Password:</source>
+         <target>Password:</target>
+      </trans-unit>
+      <trans-unit id="login.passwordRequired" datatype="html">
+         <source>Password is required.</source>
+         <target>Password is required.</target>
+      </trans-unit>
+      <trans-unit id="login.signInBtn" datatype="html">
+         <source>Sign In</source>
+         <target>Sign In</target>
+      </trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/src/i18n/messages.fr.xlf b/src/i18n/messages.fr.xlf
index 15430968..96473e96 100644
--- a/src/i18n/messages.fr.xlf
+++ b/src/i18n/messages.fr.xlf
@@ -18,6 +18,14 @@
         <source>Actors</source>
         <target>Les acteurs</target>
       </trans-unit>
+      <trans-unit id="header.signIn" datatype="html">
+         <source>Sign In</source>
+         <target>Connexion</target>
+      </trans-unit>
+      <trans-unit id="header.signOut" datatype="html">
+         <source>Sign Out</source>
+         <target>Déconnexion</target>
+      </trans-unit>
       <trans-unit id="footer.rss" datatype="html">
         <source>RSS Feed (new window)</source>
         <target>Flux RSS (nouvelle fenêtre)</target>
@@ -283,6 +291,34 @@
          <source>Go back to home page</source>
          <target>Retour à la page d'accueil</target>
       </trans-unit>
+      <trans-unit id="login.signIn" datatype="html">
+         <source>Sign In</source>
+         <target>Connexion</target>
+      </trans-unit>
+      <trans-unit id="login.incorrectCredentials" datatype="html">
+         <source>Your credentials are not correct.</source>
+         <target>Vos identifiants ne sont pas corrects.</target>
+      </trans-unit>
+      <trans-unit id="login.emailRequired" datatype="html">
+         <source>Email is required.</source>
+         <target>Veuillez renseigner votre email.</target>
+      </trans-unit>
+      <trans-unit id="login.incorrectEmail" datatype="html">
+         <source>You need to provide a valid email.</source>
+         <target>Veuillez renseigner un email valide.</target>
+      </trans-unit>
+      <trans-unit id="login.password" datatype="html">
+         <source>Password:</source>
+         <target>Mot de passe:</target>
+      </trans-unit>
+      <trans-unit id="login.passwordRequired" datatype="html">
+         <source>Password is required.</source>
+         <target>Veuillez renseigner votre mot de passe.</target>
+      </trans-unit>
+      <trans-unit id="login.signInBtn" datatype="html">
+         <source>Sign In</source>
+         <target>Connexion</target>
+      </trans-unit>
     </body>
   </file>
 </xliff>
-- 
GitLab