From d9eaee3939bf2e93e5d775d32bce77842201faba Mon Sep 17 00:00:00 2001 From: Josh Morel Date: Fri, 31 Aug 2018 03:18:19 -0400 Subject: add user account email verificiation (#977) * add user account email verificiation includes server and client code to: * enable verificationRequired via custom config * send verification email with registration * ask for verification email * verify via email * prevent login if not verified and required * conditional client links to ask for new verification email * allow login for verified=null these are users created when verification not required should still be able to login when verification is enabled * refactor email verifcation pr * change naming from verified to emailVerified * change naming from askVerifyEmail to askSendVerifyEmail * undo unrelated automatic prettier formatting on api/config * use redirectService for home * remove redundant success notification on email verified * revert test.yaml smpt host --- .../edit-custom-config.component.html | 5 ++ .../edit-custom-config.component.ts | 5 +- client/src/app/+verify-account/index.ts | 2 + .../verify-account-ask-send-email.component.html | 22 ++++++++ .../verify-account-ask-send-email.component.scss | 12 +++++ .../verify-account-ask-send-email.component.ts | 58 ++++++++++++++++++++++ .../verify-account-email.component.html | 15 ++++++ .../verify-account-email.component.ts | 54 ++++++++++++++++++++ .../verify-account-routing.module.ts | 42 ++++++++++++++++ .../app/+verify-account/verify-account.module.ts | 27 ++++++++++ client/src/app/app-routing.module.ts | 4 ++ client/src/app/core/server/server.service.ts | 3 +- client/src/app/login/login.component.html | 4 +- client/src/app/shared/users/user.service.ts | 23 +++++++++ client/src/app/signup/signup.component.ts | 22 ++++++-- 15 files changed, 290 insertions(+), 8 deletions(-) create mode 100644 client/src/app/+verify-account/index.ts create mode 100644 client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html create mode 100644 client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss create mode 100644 client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts create mode 100644 client/src/app/+verify-account/verify-account-email/verify-account-email.component.html create mode 100644 client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts create mode 100644 client/src/app/+verify-account/verify-account-routing.module.ts create mode 100644 client/src/app/+verify-account/verify-account.module.ts (limited to 'client/src/app') diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index ca7890d84..a0f0abd10 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html @@ -91,6 +91,11 @@ i18n-labelText labelText="Signup enabled" > + +
+
+ Request email for account verification +
+ +
+
+ + +
+ {{ formErrors['verify-email-email'] }} +
+
+ +
+ +
This instance does not require email verification.
+
+
diff --git a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss b/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss new file mode 100644 index 000000000..efec6b706 --- /dev/null +++ b/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.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/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts b/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts new file mode 100644 index 000000000..995f42ffc --- /dev/null +++ b/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit } from '@angular/core' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { NotificationsService } from 'angular2-notifications' +import { ServerService } from '@app/core/server' +import { RedirectService } from '@app/core' +import { UserService, FormReactive } from '@app/shared' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' + +@Component({ + selector: 'my-verify-account-ask-send-email', + templateUrl: './verify-account-ask-send-email.component.html', + styleUrls: [ './verify-account-ask-send-email.component.scss' ] +}) + +export class VerifyAccountAskSendEmailComponent extends FormReactive implements OnInit { + + constructor ( + protected formValidatorService: FormValidatorService, + private userValidatorsService: UserValidatorsService, + private userService: UserService, + private serverService: ServerService, + private notificationsService: NotificationsService, + private redirectService: RedirectService, + private i18n: I18n + ) { + super() + } + + get requiresEmailVerification () { + return this.serverService.getConfig().signup.requiresEmailVerification + } + + ngOnInit () { + this.buildForm({ + 'verify-email-email': this.userValidatorsService.USER_EMAIL + }) + } + + askSendVerifyEmail () { + const email = this.form.value['verify-email-email'] + this.userService.askSendVerifyEmail(email) + .subscribe( + () => { + const message = this.i18n( + 'An email with verification link will be sent to {{email}}.', + { email } + ) + this.notificationsService.success(this.i18n('Success'), message) + this.redirectService.redirectToHomepage() + }, + + err => { + this.notificationsService.error(this.i18n('Error'), err.message) + } + ) + } +} diff --git a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html new file mode 100644 index 000000000..30ace5e10 --- /dev/null +++ b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html @@ -0,0 +1,15 @@ +
+
+ Verify account email confirmation +
+ +
+ Your email has been verified and you may now login. Redirecting... +
+ +
+ An error occurred. + Request new verification email. +
+
+
diff --git a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts new file mode 100644 index 000000000..26b3bf4b1 --- /dev/null +++ b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { NotificationsService } from 'angular2-notifications' +import { UserService } from '@app/shared' + +@Component({ + selector: 'my-verify-account-email', + templateUrl: './verify-account-email.component.html' +}) + +export class VerifyAccountEmailComponent implements OnInit { + success = false + + private userId: number + private verificationString: string + + constructor ( + private userService: UserService, + private notificationsService: NotificationsService, + private router: Router, + private route: ActivatedRoute, + private i18n: I18n + ) { + } + + ngOnInit () { + + this.userId = this.route.snapshot.queryParams['userId'] + this.verificationString = this.route.snapshot.queryParams['verificationString'] + + if (!this.userId || !this.verificationString) { + this.notificationsService.error(this.i18n('Error'), this.i18n('Unable to find user id or verification string.')) + } else { + this.verifyEmail() + } + } + + verifyEmail () { + this.userService.verifyEmail(this.userId, this.verificationString) + .subscribe( + () => { + this.success = true + setTimeout(() => { + this.router.navigate([ '/login' ]) + }, 2000) + }, + + err => { + this.notificationsService.error(this.i18n('Error'), err.message) + } + ) + } +} diff --git a/client/src/app/+verify-account/verify-account-routing.module.ts b/client/src/app/+verify-account/verify-account-routing.module.ts new file mode 100644 index 000000000..a038f0336 --- /dev/null +++ b/client/src/app/+verify-account/verify-account-routing.module.ts @@ -0,0 +1,42 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' + +import { MetaGuard } from '@ngx-meta/core' + +import { VerifyAccountEmailComponent } from '@app/+verify-account/verify-account-email/verify-account-email.component' +import { + VerifyAccountAskSendEmailComponent +} from '@app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component' + +const verifyAccountRoutes: Routes = [ + { + path: '', + canActivateChild: [ MetaGuard ], + children: [ + { + path: 'email', + component: VerifyAccountEmailComponent, + data: { + meta: { + title: 'Verify account email' + } + } + }, + { + path: 'ask-send-email', + component: VerifyAccountAskSendEmailComponent, + data: { + meta: { + title: 'Verify account ask send email' + } + } + } + ] + } +] + +@NgModule({ + imports: [ RouterModule.forChild(verifyAccountRoutes) ], + exports: [ RouterModule ] +}) +export class VerifyAccountRoutingModule {} diff --git a/client/src/app/+verify-account/verify-account.module.ts b/client/src/app/+verify-account/verify-account.module.ts new file mode 100644 index 000000000..9092c6b4f --- /dev/null +++ b/client/src/app/+verify-account/verify-account.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core' + +import { VerifyAccountRoutingModule } from '@app/+verify-account/verify-account-routing.module' +import { VerifyAccountEmailComponent } from '@app/+verify-account/verify-account-email/verify-account-email.component' +import { + VerifyAccountAskSendEmailComponent +} from '@app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component' +import { SharedModule } from '@app/shared' + +@NgModule({ + imports: [ + VerifyAccountRoutingModule, + SharedModule + ], + + declarations: [ + VerifyAccountEmailComponent, + VerifyAccountAskSendEmailComponent + ], + + exports: [ + ], + + providers: [ + ] +}) +export class VerifyAccountModule { } diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 30e615b3e..545d6aeda 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -13,6 +13,10 @@ const routes: Routes = [ path: 'my-account', loadChildren: './+my-account/my-account.module#MyAccountModule' }, + { + path: 'verify-account', + loadChildren: './+verify-account/verify-account.module#VerifyAccountModule' + }, { path: 'accounts', loadChildren: './+accounts/accounts.module#AccountsModule' diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index a1ce12069..e7152efa0 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts @@ -40,7 +40,8 @@ export class ServerService { serverVersion: 'Unknown', signup: { allowed: false, - allowedForCurrentIP: false + allowedForCurrentIP: false, + requiresEmailVerification: false }, transcoding: { enabledResolutions: [] diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 3a6d61327..619150ade 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html @@ -3,7 +3,9 @@ Login -
{{ error }}
+
{{ error }} + Request new verification email. +
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index e6dc3dbf8..249c589b7 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts @@ -94,4 +94,27 @@ export class UserService { catchError(res => this.restExtractor.handleError(res)) ) } + + verifyEmail (userId: number, verificationString: string) { + const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email` + const body = { + verificationString + } + + return this.authHttp.post(url, body) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(res => this.restExtractor.handleError(res)) + ) + } + + askSendVerifyEmail (email: string) { + const url = UserService.BASE_USERS_URL + '/ask-send-verify-email' + + return this.authHttp.post(url, { email }) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(err => this.restExtractor.handleError(err)) + ) + } } diff --git a/client/src/app/signup/signup.component.ts b/client/src/app/signup/signup.component.ts index 47f9bc6f4..16e444678 100644 --- a/client/src/app/signup/signup.component.ts +++ b/client/src/app/signup/signup.component.ts @@ -3,7 +3,7 @@ import { Router } from '@angular/router' import { NotificationsService } from 'angular2-notifications' import { UserCreate } from '../../../../shared' import { FormReactive, UserService, UserValidatorsService } from '../shared' -import { RedirectService } from '@app/core' +import { RedirectService, ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' @@ -21,6 +21,7 @@ export class SignupComponent extends FormReactive implements OnInit { private router: Router, private notificationsService: NotificationsService, private userService: UserService, + private serverService: ServerService, private redirectService: RedirectService, private i18n: I18n ) { @@ -31,6 +32,10 @@ export class SignupComponent extends FormReactive implements OnInit { return window.location.host } + get requiresEmailVerification () { + return this.serverService.getConfig().signup.requiresEmailVerification + } + ngOnInit () { this.buildForm({ username: this.userValidatorsService.USER_USERNAME, @@ -47,10 +52,17 @@ export class SignupComponent extends FormReactive implements OnInit { this.userService.signup(userCreate).subscribe( () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('Registration for {{username}} complete.', { username: userCreate.username }) - ) + if (this.requiresEmailVerification) { + this.notificationsService.alert( + this.i18n('Welcome'), + this.i18n('Please check your email to verify your account and complete signup.') + ) + } else { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('Registration for {{username}} complete.', { username: userCreate.username }) + ) + } this.redirectService.redirectToHomepage() }, -- cgit v1.2.3