From ecb4e35f4e6c7304cb274593c13cb47fd5078b75 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 30 Jan 2018 13:27:07 +0100 Subject: Add ability to reset our password --- client/src/app/app.module.ts | 14 ++-- client/src/app/login/login.component.html | 44 ++++++++++-- client/src/app/login/login.component.scss | 10 +++ client/src/app/login/login.component.ts | 37 +++++++++- client/src/app/reset-password/index.ts | 3 + .../reset-password-routing.module.ts | 25 +++++++ .../reset-password/reset-password.component.html | 33 +++++++++ .../reset-password/reset-password.component.scss | 12 ++++ .../app/reset-password/reset-password.component.ts | 79 ++++++++++++++++++++++ .../app/reset-password/reset-password.module.ts | 24 +++++++ client/src/app/shared/users/user.service.ts | 21 +++++- client/src/polyfills.ts | 34 ++++++---- client/src/sass/application.scss | 2 +- 13 files changed, 309 insertions(+), 29 deletions(-) create mode 100644 client/src/app/reset-password/index.ts create mode 100644 client/src/app/reset-password/reset-password-routing.module.ts create mode 100644 client/src/app/reset-password/reset-password.component.html create mode 100644 client/src/app/reset-password/reset-password.component.scss create mode 100644 client/src/app/reset-password/reset-password.component.ts create mode 100644 client/src/app/reset-password/reset-password.module.ts (limited to 'client') diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index e69edbc4b..ddcaf3f48 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -1,19 +1,20 @@ import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' +import { ResetPasswordModule } from '@app/reset-password' -import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' +import { MetaLoader, MetaModule, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' + +import { AccountModule } from './account' import { AppRoutingModule } from './app-routing.module' import { AppComponent } from './app.component' - -import { AccountModule } from './account' import { CoreModule } from './core' +import { HeaderComponent } from './header' import { LoginModule } from './login' -import { SignupModule } from './signup' +import { MenuComponent } from './menu' import { SharedModule } from './shared' +import { SignupModule } from './signup' import { VideosModule } from './videos' -import { MenuComponent } from './menu' -import { HeaderComponent } from './header' export function metaFactory (): MetaLoader { return new MetaStaticLoader({ @@ -46,6 +47,7 @@ export function metaFactory (): MetaLoader { AccountModule, CoreModule, LoginModule, + ResetPasswordModule, SignupModule, SharedModule, VideosModule, diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index b61b66ec7..660a08280 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html @@ -19,10 +19,13 @@
- +
+ +
I forgot my password
+
{{ formErrors.password }}
@@ -31,3 +34,36 @@
+ + diff --git a/client/src/app/login/login.component.scss b/client/src/app/login/login.component.scss index efec6b706..2cf6991ce 100644 --- a/client/src/app/login/login.component.scss +++ b/client/src/app/login/login.component.scss @@ -10,3 +10,13 @@ input[type=submit] { @include peertube-button; @include orange-button; } + +input[type=password] { + display: inline-block; + margin-right: 5px; +} + +.forgot-password-button { + display: inline-block; + cursor: pointer; +} diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index e7c9c7226..22e8c77dd 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts @@ -1,7 +1,9 @@ -import { Component, OnInit } from '@angular/core' +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core' import { FormBuilder, FormGroup, Validators } from '@angular/forms' import { Router } from '@angular/router' - +import { UserService } from '@app/shared' +import { NotificationsService } from 'angular2-notifications' +import { ModalDirective } from 'ngx-bootstrap/modal' import { AuthService } from '../core' import { FormReactive } from '../shared' @@ -12,6 +14,9 @@ import { FormReactive } from '../shared' }) export class LoginComponent extends FormReactive implements OnInit { + @ViewChild('forgotPasswordModal') forgotPasswordModal: ModalDirective + @ViewChild('forgotPasswordEmailInput') forgotPasswordEmailInput: ElementRef + error: string = null form: FormGroup @@ -27,9 +32,12 @@ export class LoginComponent extends FormReactive implements OnInit { 'required': 'Password is required.' } } + forgotPasswordEmail = '' constructor ( private authService: AuthService, + private userService: UserService, + private notificationsService: NotificationsService, private formBuilder: FormBuilder, private router: Router ) { @@ -60,4 +68,29 @@ export class LoginComponent extends FormReactive implements OnInit { err => this.error = err.message ) } + + askResetPassword () { + this.userService.askResetPassword(this.forgotPasswordEmail) + .subscribe( + res => { + const message = `An email with the reset password instructions will be sent to ${this.forgotPasswordEmail}.` + this.notificationsService.success('Success', message) + this.hideForgotPasswordModal() + }, + + err => this.notificationsService.error('Error', err.message) + ) + } + + onForgotPasswordModalShown () { + this.forgotPasswordEmailInput.nativeElement.focus() + } + + openForgotPasswordModal () { + this.forgotPasswordModal.show() + } + + hideForgotPasswordModal () { + this.forgotPasswordModal.hide() + } } diff --git a/client/src/app/reset-password/index.ts b/client/src/app/reset-password/index.ts new file mode 100644 index 000000000..438dc576a --- /dev/null +++ b/client/src/app/reset-password/index.ts @@ -0,0 +1,3 @@ +export * from './reset-password-routing.module' +export * from './reset-password.component' +export * from './reset-password.module' diff --git a/client/src/app/reset-password/reset-password-routing.module.ts b/client/src/app/reset-password/reset-password-routing.module.ts new file mode 100644 index 000000000..b41069568 --- /dev/null +++ b/client/src/app/reset-password/reset-password-routing.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' + +import { MetaGuard } from '@ngx-meta/core' + +import { ResetPasswordComponent } from './reset-password.component' + +const resetPasswordRoutes: Routes = [ + { + path: 'reset-password', + component: ResetPasswordComponent, + canActivate: [ MetaGuard ], + data: { + meta: { + title: 'Reset password' + } + } + } +] + +@NgModule({ + imports: [ RouterModule.forChild(resetPasswordRoutes) ], + exports: [ RouterModule ] +}) +export class ResetPasswordRoutingModule {} diff --git a/client/src/app/reset-password/reset-password.component.html b/client/src/app/reset-password/reset-password.component.html new file mode 100644 index 000000000..d142c523f --- /dev/null +++ b/client/src/app/reset-password/reset-password.component.html @@ -0,0 +1,33 @@ +
+
+ Reset my password +
+ +
{{ error }}
+ +
+
+ + +
+ {{ formErrors.password }} +
+
+ +
+ + +
+ {{ formErrors['password-confirm'] }} +
+
+ + +
+
diff --git a/client/src/app/reset-password/reset-password.component.scss b/client/src/app/reset-password/reset-password.component.scss new file mode 100644 index 000000000..efec6b706 --- /dev/null +++ b/client/src/app/reset-password/reset-password.component.scss @@ -0,0 +1,12 @@ +@import '_variables'; +@import '_mixins'; + +input:not([type=submit]) { + @include peertube-input-text(340px); + display: block; +} + +input[type=submit] { + @include peertube-button; + @include orange-button; +} diff --git a/client/src/app/reset-password/reset-password.component.ts b/client/src/app/reset-password/reset-password.component.ts new file mode 100644 index 000000000..408374779 --- /dev/null +++ b/client/src/app/reset-password/reset-password.component.ts @@ -0,0 +1,79 @@ +import { Component, OnInit } from '@angular/core' +import { FormBuilder, FormGroup, Validators } from '@angular/forms' +import { ActivatedRoute, Router } from '@angular/router' +import { USER_PASSWORD, UserService } from '@app/shared' +import { NotificationsService } from 'angular2-notifications' +import { AuthService } from '../core' +import { FormReactive } from '../shared' + +@Component({ + selector: 'my-login', + templateUrl: './reset-password.component.html', + styleUrls: [ './reset-password.component.scss' ] +}) + +export class ResetPasswordComponent extends FormReactive implements OnInit { + form: FormGroup + formErrors = { + 'password': '', + 'password-confirm': '' + } + validationMessages = { + 'password': USER_PASSWORD.MESSAGES, + 'password-confirm': { + 'required': 'Confirmation of the password is required.' + } + } + + private userId: number + private verificationString: string + + constructor ( + private authService: AuthService, + private userService: UserService, + private notificationsService: NotificationsService, + private formBuilder: FormBuilder, + private router: Router, + private route: ActivatedRoute + ) { + super() + } + + buildForm () { + this.form = this.formBuilder.group({ + password: [ '', USER_PASSWORD.VALIDATORS ], + 'password-confirm': [ '', Validators.required ] + }) + + this.form.valueChanges.subscribe(data => this.onValueChanged(data)) + } + + ngOnInit () { + this.buildForm() + + this.userId = this.route.snapshot.queryParams['userId'] + this.verificationString = this.route.snapshot.queryParams['verificationString'] + + if (!this.userId || !this.verificationString) { + this.notificationsService.error('Error', 'Unable to find user id or verification string.') + this.router.navigate([ '/' ]) + } + } + + resetPassword () { + this.userService.resetPassword(this.userId, this.verificationString, this.form.value.password) + .subscribe( + () => { + this.notificationsService.success('Success', 'Your password has been successfully reset!') + this.router.navigate([ '/login' ]) + }, + + err => this.notificationsService.error('Error', err.message) + ) + } + + isConfirmedPasswordValid () { + const values = this.form.value + return values.password === values['password-confirm'] + } +} diff --git a/client/src/app/reset-password/reset-password.module.ts b/client/src/app/reset-password/reset-password.module.ts new file mode 100644 index 000000000..c2711981a --- /dev/null +++ b/client/src/app/reset-password/reset-password.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core' + +import { ResetPasswordRoutingModule } from './reset-password-routing.module' +import { ResetPasswordComponent } from './reset-password.component' +import { SharedModule } from '../shared' + +@NgModule({ + imports: [ + ResetPasswordRoutingModule, + SharedModule + ], + + declarations: [ + ResetPasswordComponent + ], + + exports: [ + ResetPasswordComponent + ], + + providers: [ + ] +}) +export class ResetPasswordModule { } diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index 742fb0728..da7b583f4 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts @@ -5,7 +5,6 @@ import 'rxjs/add/operator/map' import { UserCreate, UserUpdateMe } from '../../../../../shared' import { environment } from '../../../environments/environment' import { RestExtractor } from '../rest' -import { User } from './user.model' @Injectable() export class UserService { @@ -54,4 +53,24 @@ export class UserService { return this.authHttp.get(url) .catch(res => this.restExtractor.handleError(res)) } + + askResetPassword (email: string) { + const url = UserService.BASE_USERS_URL + '/ask-reset-password' + + return this.authHttp.post(url, { email }) + .map(this.restExtractor.extractDataBool) + .catch(res => this.restExtractor.handleError(res)) + } + + resetPassword (userId: number, verificationString: string, password: string) { + const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password` + const body = { + verificationString, + password + } + + return this.authHttp.post(url, body) + .map(this.restExtractor.extractDataBool) + .catch(res => this.restExtractor.handleError(res)) + } } diff --git a/client/src/polyfills.ts b/client/src/polyfills.ts index c2d7f1d6e..fbe104aa0 100644 --- a/client/src/polyfills.ts +++ b/client/src/polyfills.ts @@ -19,26 +19,30 @@ */ /** IE9, IE10 and IE11 requires all of the following polyfills. **/ -// import 'core-js/es6/symbol'; -// import 'core-js/es6/object'; -// import 'core-js/es6/function'; -// import 'core-js/es6/parse-int'; -// import 'core-js/es6/parse-float'; -// import 'core-js/es6/number'; -// import 'core-js/es6/math'; -// import 'core-js/es6/string'; -// import 'core-js/es6/date'; -// import 'core-js/es6/array'; -// import 'core-js/es6/regexp'; -// import 'core-js/es6/map'; -// import 'core-js/es6/weak-map'; -// import 'core-js/es6/set'; + +// For Google Bot +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/weak-map'; +import 'core-js/es6/set'; /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** IE10 and IE11 requires the following for the Reflect API. */ -// import 'core-js/es6/reflect'; + +// For Google Bot +import 'core-js/es6/reflect'; /** Evergreen browsers require these. **/ diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 253bb1b3c..33d7ce0a5 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -19,7 +19,7 @@ $FontPathSourceSansPro: '../../node_modules/npm-font-source-sans-pro/fonts'; } body { - font-family: 'Source Sans Pro'; + font-family: 'Source Sans Pro', sans-serif; font-weight: $font-regular; color: #000; } -- cgit v1.2.3