diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1b9b4ce4370613e680a4cff72000dc8a08de97cf..48ab4603dc4d7ff9c1c29c715ccc4eac434b1b05 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,6 +48,7 @@ import { MatTooltipModule } from '@angular/material'; import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; +import { UserService } from './services/http/user.service'; import { RoomPageComponent } from './components/pages/room-page/room-page.component'; import { RoomCreateComponent } from './components/dialogs/room-create/room-create.component'; import { LoginComponentPageComponent } from './components/pages/login-page/login-page.component'; @@ -70,6 +71,7 @@ import { ContentAnswerService } from './services/http/content-answer.service'; import { RoomDeleteComponent } from './components/dialogs/room-delete/room-delete.component'; import { StatisticsComponent } from './components/fragments/statistics/statistics.component'; import { RoomEditComponent } from './components/dialogs/room-edit/room-edit.component'; +import { UserActivationComponent } from './components/dialogs/user-activation/user-activation.component'; import { ContentChoiceParticipantComponent } from './components/fragments/content-choice-participant/content-choice-participant.component'; import { ContentChoiceCreatorComponent } from './components/fragments/content-choice-creator/content-choice-creator.component'; import { ContentCreatePageComponent } from './components/pages/content-create-page/content-create-page.component'; @@ -119,6 +121,7 @@ import { FooterImprintComponent } from './components/pages/footer-imprint/footer AnswersListComponent, RoomDeleteComponent, RoomEditComponent, + UserActivationComponent, ContentChoiceParticipantComponent, ContentChoiceCreatorComponent, ContentCreatePageComponent, @@ -145,6 +148,7 @@ import { FooterImprintComponent } from './components/pages/footer-imprint/footer RoomCreateComponent, RoomDeleteComponent, RoomEditComponent, + UserActivationComponent, AnswerEditComponent, ContentChoiceCreatorComponent, ContentLikertCreatorComponent, @@ -217,6 +221,7 @@ import { FooterImprintComponent } from './components/pages/footer-imprint/footer CommentService, ContentService, ContentAnswerService, + UserService, { provide: MatDialogRef, useValue: { diff --git a/src/app/components/dialogs/user-activation/user-activation.component.html b/src/app/components/dialogs/user-activation/user-activation.component.html new file mode 100644 index 0000000000000000000000000000000000000000..ae7952cd12b1e6b663036f0e20bafac232074c64 --- /dev/null +++ b/src/app/components/dialogs/user-activation/user-activation.component.html @@ -0,0 +1,9 @@ +<form (ngSubmit)="login(userActivationKey.value)" fxLayout="column" fxLayoutAlign="space-around" + fxLayoutGap="10px"> + <mat-form-field class="input-block"> + <input matInput #userActivationKey type="text" placeholder="{{ 'login.activation-key' | translate }}" + [formControl]="activationKeyFormControl" [errorStateMatcher]="matcher" name="activation-key"/> + <mat-error *ngIf="activationKeyFormControl.hasError('required')">{{ 'login.activation-key-required' | translate }}</mat-error> + </mat-form-field> + <button mat-raised-button color="primary" type="submit">{{ 'login.activate' | translate }}</button> +</form> diff --git a/src/app/components/dialogs/user-activation/user-activation.component.scss b/src/app/components/dialogs/user-activation/user-activation.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/components/dialogs/user-activation/user-activation.component.ts b/src/app/components/dialogs/user-activation/user-activation.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7074043db7683842aee57b876cdf2fa5b24da7e --- /dev/null +++ b/src/app/components/dialogs/user-activation/user-activation.component.ts @@ -0,0 +1,33 @@ +import { Component, Inject, OnInit} from '@angular/core'; +import { NotificationService } from '../../../services/util/notification.service'; +import { UserService } from '../../../services/http/user.service'; +import { FormControl, Validators} from '@angular/forms'; +import { MAT_DIALOG_DATA } from '@angular/material'; + +@Component({ + selector: 'app-user-activation', + templateUrl: './user-activation.component.html', + styleUrls: ['./user-activation.component.scss'] +}) +export class UserActivationComponent implements OnInit { + + activationKeyFormControl = new FormControl('', [Validators.required]); + + constructor( + @Inject(MAT_DIALOG_DATA) public data: any, + public userService: UserService, + public notificationService: NotificationService + ) { + } + + ngOnInit() { + } + + login(activationKey: string): void { + activationKey = activationKey.trim(); + + this.userService.activate(this.data.name.trim(), activationKey).subscribe(ret => { + console.log(ret); + }); + } +} diff --git a/src/app/components/fragments/login/login.component.ts b/src/app/components/fragments/login/login.component.ts index 2eabb04fb7cda3a81ed84e01191f98f620ae8e1a..0207ef26b89b5f9dd3e7989051e21f86a1d7b03a 100644 --- a/src/app/components/fragments/login/login.component.ts +++ b/src/app/components/fragments/login/login.component.ts @@ -2,10 +2,11 @@ import { Component, Input, OnInit } from '@angular/core'; import { AuthenticationService } from '../../../services/http/authentication.service'; import { Router } from '@angular/router'; import { NotificationService } from '../../../services/util/notification.service'; -import { ErrorStateMatcher } from '@angular/material'; +import { ErrorStateMatcher, MatDialog} from '@angular/material'; import { FormControl, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { UserRole } from '../../../models/user-roles.enum'; import { TranslateService } from '@ngx-translate/core'; +import { UserActivationComponent } from '../../dialogs/user-activation/user-activation.component'; export class LoginErrorStateMatcher implements ErrorStateMatcher { isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { @@ -27,16 +28,20 @@ export class LoginComponent implements OnInit { matcher = new LoginErrorStateMatcher(); + name = ''; + constructor(public authenticationService: AuthenticationService, public router: Router, private translationService: TranslateService, - public notificationService: NotificationService) { + public notificationService: NotificationService, + public dialog: MatDialog) { } ngOnInit() { } login(username: string, password: string): void { + this.name = username; username = username.trim(); password = password.trim(); @@ -54,14 +59,21 @@ export class LoginComponent implements OnInit { this.authenticationService.guestLogin(this.role).subscribe(loginSuccessful => this.checkLogin(loginSuccessful)); } - private checkLogin(loginSuccessful: boolean) { - if (loginSuccessful) { + private checkLogin(loginSuccessful: string) { + if (loginSuccessful === 'true') { this.notificationService.show('Login successful!'); if (this.role === UserRole.CREATOR) { this.router.navigate(['creator']); } else { this.router.navigate(['participant']); } + } else if (loginSuccessful === 'activation') { + this.dialog.open(UserActivationComponent, { + width: '350px', + data: { + name: this.name + } + }); } else { this.translationService.get('login.login-data-incorrect').subscribe(message => { this.notificationService.show(message); diff --git a/src/app/services/http/authentication.service.ts b/src/app/services/http/authentication.service.ts index 415c5f80ff46ed564fa4bd8e0a136c53364a6602..6fff4d3850d25769ccfb3a35e067f9a607bf77ac 100644 --- a/src/app/services/http/authentication.service.ts +++ b/src/app/services/http/authentication.service.ts @@ -35,7 +35,13 @@ export class AuthenticationService { } } - login(email: string, password: string, userRole: UserRole): Observable<boolean> { + /* + * Three possible return values: + * - "true": login successful + * - "false": login failed + * - "activation": account exists but needs activation with key + */ + login(email: string, password: string, userRole: UserRole): Observable<string> { const connectionUrl: string = this.apiUrl.base + this.apiUrl.auth + this.apiUrl.login + this.apiUrl.registered; return this.checkLogin(this.http.post<ClientAuthentication>(connectionUrl, { @@ -44,7 +50,7 @@ export class AuthenticationService { }, this.httpOptions), userRole, false); } - guestLogin(userRole: UserRole): Observable<boolean> { + guestLogin(userRole: UserRole): Observable<string> { const connectionUrl: string = this.apiUrl.base + this.apiUrl.auth + this.apiUrl.login + this.apiUrl.guest; return this.checkLogin(this.http.post<ClientAuthentication>(connectionUrl, null, this.httpOptions), userRole, true); @@ -99,7 +105,7 @@ export class AuthenticationService { return this.isLoggedIn() ? this.user.getValue().token : undefined; } - private checkLogin(clientAuthentication: Observable<ClientAuthentication>, userRole: UserRole, isGuest: boolean): Observable<boolean> { + private checkLogin(clientAuthentication: Observable<ClientAuthentication>, userRole: UserRole, isGuest: boolean): Observable<string> { return clientAuthentication.map(result => { if (result) { this.setUser(new User( @@ -109,12 +115,16 @@ export class AuthenticationService { result.token, userRole, isGuest)); - return true; + return 'true'; } else { - return false; + return 'false'; } - }).catch(() => { - return of(false); + }).catch((e) => { + // check if user needs activation + if (e.error.errorType === 'DisabledException') { + return of('activation'); + } + return of('false'); }); } diff --git a/src/app/services/http/user.service.ts b/src/app/services/http/user.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..29b400306fd2a668af60aacaa834d31e0045567f --- /dev/null +++ b/src/app/services/http/user.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { User } from '../../models/user'; +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { ClientAuthentication } from '../../models/client-authentication'; +import { BaseHttpService } from './base-http.service'; + +const httpOptions = { + headers: new HttpHeaders({}) +}; + +@Injectable() +export class UserService extends BaseHttpService { + private apiUrl = { + base: '/api', + user: '/user', + activate: '/activate' + }; + + constructor(private http: HttpClient) { + super(); + } + + activate(name: string, activationKey: string): Observable<string> { + const connectionUrl: string = this.apiUrl.base + this.apiUrl.user + '/~' + encodeURIComponent(name) + this.apiUrl.activate; + + return this.http.post<string>(connectionUrl, { + key: activationKey + }, httpOptions); + } +} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 9a29da1d22b9880540320e56190cce9772dc39a9..8358928461faf2377b915541cb555a488427461c 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -19,7 +19,10 @@ "login": "Anmelden", "login-data-incorrect": "Benutzername oder Passwort nicht korrekt.", "password": "Passwort", - "password-required": "Passwort ist erforderlich" + "password-required": "Passwort ist erforderlich", + "activate": "Aktivieren", + "activation-key": "Aktivierungsschlüssel", + "activation-key-required": "Aktivierungsschlüssel erforderlich" }, "password-reset": { "email": "E-Mail", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 745c2e809b9bb35720b46ac6171a1ae8d8f5df2e..96ac280e89deed8c8f23cd5dbe4a69d3fbf476ba 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -19,7 +19,10 @@ "login": "Login", "login-data-incorrect": "Username or password incorrect.", "password": "Password", - "password-required": "Password is required" + "password-required": "Password is required", + "activate": "Activate", + "activation-key": "Activation key", + "activation-key-required": "Activation key required" }, "password-reset": { "email": "E-Mail",