From b81fa24565e60e3f97c5add5e4188d5e1b3c162c Mon Sep 17 00:00:00 2001 From: chiahetcho <44137047+chiahetcho@users.noreply.github.com> Date: Thu, 12 Dec 2019 20:40:30 +0100 Subject: [PATCH] Password reset feature (#31) * Add a reset component * Add the resetconfirm component * catching errors --- src/app/app-routing.module.ts | 10 +++ src/app/app.module.ts | 4 ++ src/app/core/auth/auth.service.ts | 16 +++++ src/app/login/login.component.html | 6 +- .../reset-confirm.component.html | 24 +++++++ .../reset-confirm.component.scss | 38 +++++++++++ .../reset-confirm.component.spec.ts | 25 +++++++ .../reset-confirm/reset-confirm.component.ts | 68 +++++++++++++++++++ src/app/reset/reset.component.html | 19 ++++++ src/app/reset/reset.component.scss | 38 +++++++++++ src/app/reset/reset.component.spec.ts | 25 +++++++ src/app/reset/reset.component.ts | 59 ++++++++++++++++ 12 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 src/app/reset-confirm/reset-confirm.component.html create mode 100644 src/app/reset-confirm/reset-confirm.component.scss create mode 100644 src/app/reset-confirm/reset-confirm.component.spec.ts create mode 100644 src/app/reset-confirm/reset-confirm.component.ts create mode 100644 src/app/reset/reset.component.html create mode 100644 src/app/reset/reset.component.scss create mode 100644 src/app/reset/reset.component.spec.ts create mode 100644 src/app/reset/reset.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index ebc3214..a9a0140 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,6 +1,8 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './login/login.component'; +import { ResetComponent } from './reset/reset.component'; +import { ResetConfirmComponent} from './reset-confirm/reset-confirm.component'; import { UiGalleryComponent } from './shared'; import { InternalErrorComponent, NotFoundComponent, AuthGuard, MapsAPIResolver } from './core'; @@ -23,6 +25,14 @@ const routes: Routes = [ path: 'inscription', loadChildren: './signup/signup.module#SignupModule', }, + { + path: 'reinitialiser_mdp', + component: ResetComponent, + }, + { + path: 'rest-auth/password/reset/confirm/:uid/:token', + component: ResetConfirmComponent, + }, { path: '500', component: InternalErrorComponent, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0a79000..8c6fa79 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -34,6 +34,8 @@ import { LoginComponent } from './login/login.component'; // Services import { MessageService } from './core'; +import { ResetComponent } from './reset/reset.component'; +import { ResetConfirmComponent } from './reset-confirm/reset-confirm.component'; registerLocaleData(localeFR); @@ -41,6 +43,8 @@ registerLocaleData(localeFR); declarations: [ AppComponent, LoginComponent, + ResetComponent, + ResetConfirmComponent, ], imports: [ BrowserModule, diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 52cec91..e7c506f 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -19,6 +19,8 @@ class StoredToken extends SimpleStoredItem { key = 'oser-cs-user-token'; } export class AuthService { private loginUrl = environment.apiUrl + 'auth/get-token/'; + private resetUrl = environment.apiUrl + 'rest-auth/password/reset/'; + private resetConfirmUrl = environment.apiUrl + 'rest-auth/password/reset/confirm/'; fromGuard: boolean; redirectUrl: string; @@ -45,6 +47,20 @@ export class AuthService { ); } + reset(email: string): Observable<boolean> { + console.log("reset function of auth service"); + return this.http.post<any>(this.resetUrl, { email }).pipe( + map(() => true), + ); + } + + resetConfirm(uid: string, token: string, new_password1: string, new_password2: string): Observable<boolean> { + console.log("reset confirm function"); + return this.http.post<any>(this.resetConfirmUrl, { uid, token, new_password1, new_password2 }).pipe( + map(() => true), + ); + } + redirectLogin() { this.router.navigate(['/connexion']); } diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html index b670f76..58676ec 100644 --- a/src/app/login/login.component.html +++ b/src/app/login/login.component.html @@ -19,7 +19,9 @@ Se connecter <i *ngIf="loading" class="fa fa-spinner fa-pulse"></i> </button> </div> - + <div class="text-center"> + <br> + <a routerLink="/reinitialiser_mdp">Mot de passe oublié ?</a></div> </form> <messages></messages> @@ -28,4 +30,4 @@ Ou crée ton <a routerLink="/inscription">compte tutoré</a> </p> -</app-form-page> +</app-form-page> \ No newline at end of file diff --git a/src/app/reset-confirm/reset-confirm.component.html b/src/app/reset-confirm/reset-confirm.component.html new file mode 100644 index 0000000..b9d07f9 --- /dev/null +++ b/src/app/reset-confirm/reset-confirm.component.html @@ -0,0 +1,24 @@ +<app-form-page> + <h1>Réinitialiser le mot de passe</h1> + <form [formGroup]="formGroup" (ngSubmit)="resetConfirm()"> + + <!-- Password field --> + <mat-form-field class="block"> + <input matInput type="password" formControlName="new_password1" placeholder="Mot de passe" required autofocus /> + </mat-form-field> + + <!-- Confirm password --> + <mat-form-field class="block"> + <input matInput type="password" formControlName="new_password2" placeholder="Confirmer le mot de passe" required + autofocus /> + </mat-form-field> + + <!-- Submit--> + <div class="text-center"> + <button mat-raised-button color="primary" [disabled]="!formGroup.valid || loading" id="login-btn"> + Confirmer<i *ngIf="loading" class="fa fa-spinner fa-pulse"></i> + </button> + </div> + </form> + +</app-form-page> \ No newline at end of file diff --git a/src/app/reset-confirm/reset-confirm.component.scss b/src/app/reset-confirm/reset-confirm.component.scss new file mode 100644 index 0000000..2e07158 --- /dev/null +++ b/src/app/reset-confirm/reset-confirm.component.scss @@ -0,0 +1,38 @@ +@import '~sass/variables'; +@import '~sass/mixins'; + +.logo { + width: 100%; + max-width: 20em; + height: auto; + display: block; + margin: auto; +} + +.form-group { + display: flex; + flex-flow: column; +} + +.has-error { + input { + @include style-danger; + } +} +#login-btn { + margin-top: 1em; +} + +.form-control { + position: relative; + font-size: 16px; + height: auto; + padding: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.block { + width: 100%; +} diff --git a/src/app/reset-confirm/reset-confirm.component.spec.ts b/src/app/reset-confirm/reset-confirm.component.spec.ts new file mode 100644 index 0000000..4d5f02d --- /dev/null +++ b/src/app/reset-confirm/reset-confirm.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ResetConfirmComponent } from './reset-confirm.component'; + +describe('ResetConfirmComponent', () => { + let component: ResetConfirmComponent; + let fixture: ComponentFixture<ResetConfirmComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ResetConfirmComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ResetConfirmComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/reset-confirm/reset-confirm.component.ts b/src/app/reset-confirm/reset-confirm.component.ts new file mode 100644 index 0000000..cc25023 --- /dev/null +++ b/src/app/reset-confirm/reset-confirm.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { MatSnackBar } from '@angular/material'; +import { AuthService, MessageService } from 'app/core'; +import { of } from 'rxjs'; +import { filter, map, tap, catchError } from 'rxjs/operators'; + +@Component({ + selector: 'app-reset-confirm', + templateUrl: './reset-confirm.component.html', + styleUrls: ['./reset-confirm.component.scss'] +}) +export class ResetConfirmComponent implements OnInit { + + loading: boolean = false; + defaultRedirectUrl: string = '/connexion'; + formGroup: FormGroup; + public uid: string; + public token: string; + + constructor( + private router: Router, + private auth: AuthService, + private messageService: MessageService, + private fb: FormBuilder, + private snackBar: MatSnackBar, + private route: ActivatedRoute + ) { } + + ngOnInit() { + if (this.auth.fromGuard) { + this.messageService.error('Oops ! Vous devez vous connecter pour accéder à cette page.'); + } + if (this.auth.fromUnauthorized) { + this.messageService.error("Oops ! Vous n'avez pas les permissions requises pour accéder à cette page."); + } + + this.createForm(); + this.uid = this.route.snapshot.paramMap.get('uid'); + this.token = this.route.snapshot.paramMap.get('token'); + console.log(this.uid); + console.log(this.token); + } + private createForm() { + this.formGroup = this.fb.group({ + new_password1: '', + new_password2: '' + }); + } + + resetConfirm() { + this.loading = true; + const { new_password1, new_password2 } = this.formGroup.value; + this.messageService.clear(); + this.auth.resetConfirm(this.uid, this.token, new_password1, new_password2).pipe( + catchError(() => { + this.snackBar.open('Erreur lors de la réinitialisation du mot de passe : vérifiez que les mots de passes sont identiques et assez forts (8 caractères). Evitez également les mots de passes courants', 'OK', { duration: 5000 }); + return of(false); + }), + tap(() => this.loading = false), + filter(Boolean), + map(() =>this.auth.redirectUrl ? this.auth.redirectUrl : this.defaultRedirectUrl), + tap(() => this.snackBar.open('Mot de passe réinitialisé', 'OK', { duration: 2000 })), + tap((redirectUrl: string) => this.router.navigate([redirectUrl])), + ).subscribe()} + +} diff --git a/src/app/reset/reset.component.html b/src/app/reset/reset.component.html new file mode 100644 index 0000000..eee1970 --- /dev/null +++ b/src/app/reset/reset.component.html @@ -0,0 +1,19 @@ +<app-form-page> + <h1>Réinitialisation du mot de passe</h1> + + <form [formGroup]="formGroup" (ngSubmit)="reset()"> + + <!-- Email field --> + <mat-form-field class="block"> + <input matInput type="email" formControlName="email" required placeholder="Adresse email" autofocus> + </mat-form-field> + + <!-- Submit --> + <div class="text-center"> + <button mat-raised-button color="primary" [disabled]="!formGroup.valid || loading" id="login-btn"> + Recevoir un mail<i *ngIf="loading" class="fa fa-spinner fa-pulse"></i> + </button> + </div> + </form> + +</app-form-page> \ No newline at end of file diff --git a/src/app/reset/reset.component.scss b/src/app/reset/reset.component.scss new file mode 100644 index 0000000..2e07158 --- /dev/null +++ b/src/app/reset/reset.component.scss @@ -0,0 +1,38 @@ +@import '~sass/variables'; +@import '~sass/mixins'; + +.logo { + width: 100%; + max-width: 20em; + height: auto; + display: block; + margin: auto; +} + +.form-group { + display: flex; + flex-flow: column; +} + +.has-error { + input { + @include style-danger; + } +} +#login-btn { + margin-top: 1em; +} + +.form-control { + position: relative; + font-size: 16px; + height: auto; + padding: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.block { + width: 100%; +} diff --git a/src/app/reset/reset.component.spec.ts b/src/app/reset/reset.component.spec.ts new file mode 100644 index 0000000..15ce951 --- /dev/null +++ b/src/app/reset/reset.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ResetComponent } from './reset.component'; + +describe('ResetComponent', () => { + let component: ResetComponent; + let fixture: ComponentFixture<ResetComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ResetComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ResetComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/reset/reset.component.ts b/src/app/reset/reset.component.ts new file mode 100644 index 0000000..f35be7a --- /dev/null +++ b/src/app/reset/reset.component.ts @@ -0,0 +1,59 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { MatSnackBar } from '@angular/material'; +import { AuthService, MessageService } from 'app/core'; +import { of } from 'rxjs'; +import { filter, map, tap, catchError } from 'rxjs/operators'; + +@Component({ + selector: 'app-reset', + templateUrl: './reset.component.html', + styleUrls: ['./reset.component.scss'] +}) +export class ResetComponent implements OnInit { + + loading: boolean = false; + defaultRedirectUrl: string = '/'; + formGroup: FormGroup; + + constructor( + private router: Router, + private auth: AuthService, + private messageService: MessageService, + private fb: FormBuilder, + private snackBar: MatSnackBar, + ) { } + + ngOnInit() { + if (this.auth.fromGuard) { + this.messageService.error('Oops ! Vous devez vous connecter pour accéder à cette page.'); + } + if (this.auth.fromUnauthorized) { + this.messageService.error("Oops ! Vous n'avez pas les permissions requises pour accéder à cette page."); + } + this.createForm(); + } + + private createForm() { + this.formGroup = this.fb.group({ + email: '', + }); + } + + reset() { + this.loading = true; + const { email } = this.formGroup.value; + this.messageService.clear(); + this.auth.reset(email).pipe( + catchError(() => { + this.snackBar.open('Adresse incorrecte', 'OK', { duration: 5000 }); + return of(false); + }), + tap(() => this.loading = false), + filter(Boolean), + map(() =>this.auth.redirectUrl ? this.auth.redirectUrl : this.defaultRedirectUrl), + tap(() => this.snackBar.open('Mail de réinitialisation envoyé !', 'OK', { duration: 2000 })), + tap((redirectUrl: string) => this.router.navigate([redirectUrl])), + ).subscribe()} +} -- GitLab