Skip to content
This repository has been archived by the owner on Mar 15, 2024. It is now read-only.

Commit

Permalink
Improve authentication flow (#17)
Browse files Browse the repository at this point in the history
* Improve auth flow by parsing more error responses, redo form with reactive forms

* Add error service, move validators to formbuilder instead of in template

* Add loading page
  • Loading branch information
raqbit authored Oct 29, 2018
1 parent 1f344e7 commit 6ec0b21
Show file tree
Hide file tree
Showing 32 changed files with 413 additions and 105 deletions.
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {AuthModule} from './core/auth/auth.module';
import {ImportModule} from './core/import/import.module';
import {CompanyModule} from './core/company/company.module';
import {AgentModule} from './core/agent/agent.module';
import {LoadingPageComponent} from './core/loading/loading-page.component';

@NgModule({
imports: [
Expand All @@ -29,6 +30,7 @@ import {AgentModule} from './core/agent/agent.module';
declarations: [
AppComponent,
ErrorPageComponent,
LoadingPageComponent,
],
providers: [
{
Expand Down
14 changes: 1 addition & 13 deletions src/app/common/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,10 @@ export function getTableauDate(date: moment.Moment): string {
* @param tag - Tag to get the type of
*/
export function getTagType(tag: Tag): string {

if (tag.type !== 'int') {
return tag.type;
}

let type = '';

if (!tag.signed) {
type += 'u';
}

type += tag.type;

type += tag.width;

return type;
return `${!tag.signed ? 'u' : ''}${tag.type}${tag.width}`;
}

/**
Expand Down
12 changes: 10 additions & 2 deletions src/app/core/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@ import {CommonModule} from '@angular/common';
import {LoginComponent} from './login-page/login/login.component';
import {LoginPageComponent} from './login-page/login-page.component';
import {AppThemeModule} from '../theme/app-theme.module';
import {FormsModule} from '@angular/forms';
import {ReactiveFormsModule} from '@angular/forms';
import {AuthGuard} from './auth.guard';
import {AuthService} from './auth.service';
import {LoggedinGuard} from './loggedin.guard';
import {EmailInputComponent} from './login-page/login/email-input/email-input.component';
import {PasswordInputComponent} from './login-page/login/password-input/password-input.component';
import {OtpDialogComponent} from './login-page/login/otp/otp-dialog/otp-dialog.component';
import {OtpInputComponent} from './login-page/login/otp/otp-dialog/otp-input/otp-input.component';

@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
AppThemeModule
],
declarations: [
LoginComponent,
LoginPageComponent,
EmailInputComponent,
PasswordInputComponent,
OtpDialogComponent,
OtpInputComponent,
],
exports: [
LoginPageComponent,
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<ng-container [formGroup]="parentForm">
<mdl-textfield
type="text"
id="emailInput"
name="email"
label="Email"
autocomplete="email"
error-msg="Please enter a valid email"
floating-label
autofocus
[class.is-invalid]="f['email'].errors && (f['email'].dirty || f['email'].touched)"
[formControlName]="'email'">
</mdl-textfield>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Component, Input, OnInit} from '@angular/core';
import {FormGroup} from '@angular/forms';

@Component({
selector: 'ix-email-input',
templateUrl: './email-input.component.html',
styleUrls: ['./email-input.component.css']
})
export class EmailInputComponent implements OnInit {

@Input() parentForm: FormGroup;

get f() {
return this.parentForm.controls;
}

constructor() {
}

ngOnInit() {
}
}
77 changes: 12 additions & 65 deletions src/app/core/auth/login-page/login/login.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<form (ngSubmit)="submitLogin()" #loginForm="ngForm">
<form (ngSubmit)="submitLogin()" [formGroup]="loginForm">
<mdl-card mdl-shadow="2">
<mdl-progress
[indeterminate]="true"
Expand All @@ -8,35 +8,13 @@
<img src="assets/img/logo.png" alt="IXON Logo" class="logo">
</mdl-card-title>
<mdl-card-supporting-text>
<mdl-textfield
type="text"
id="emailInput"
name="email"
label="Email"
autocomplete="email"
error-msg="Please enter a valid email"
floating-label
autofocus
required
email
#emailInput="ngModel"
[disabled]="isLoading"
[class.is-invalid]="emailInput.errors && (emailInput.dirty || emailInput.touched)"
[(ngModel)]="model.email"></mdl-textfield>
<ix-email-input
[parentForm]="loginForm">
</ix-email-input>
<br>
<mdl-textfield
type="password"
id="passwordInput"
name="password"
label="Password"
autocomplete="current-password"
error-msg="Please enter your password"
floating-label
required
#passwordInput="ngModel"
[disabled]="isLoading"
[class.is-invalid]="passwordInput.errors && (passwordInput.dirty || passwordInput.touched)"
[(ngModel)]="model.password"></mdl-textfield>
<ix-password-input
[parentForm]="loginForm">
</ix-password-input>
</mdl-card-supporting-text>
<mdl-card-actions mdl-card-border>
<div>
Expand All @@ -45,46 +23,15 @@
mdl-button
mdl-colored
mdl-ripple
[disabled]="!loginForm.form.valid || isLoading">
[disabled]="!loginForm.valid || isLoading">
Login
</button>
</div>
</mdl-card-actions>
</mdl-card>
</form>
<mdl-dialog
<ix-otp-dialog
#otpDialog
[mdl-dialog-config]="{
styles:{'width': '25rem'},
isModal:true,
animate: false,
enterTransitionDuration: 400,
leaveTransitionDuration: 400}">
<h3 class="mdl-dialog__title">One-time password</h3>
<form (ngSubmit)="submitLogin()" #otpForm="ngForm">
<div class="mdl-dialog__content">
<mdl-textfield
id="otpInput"
name="otp"
label="One-time password"
error-msg="Please enter a valid one-time password"
floating-label
maxlength="6"
pattern="^[0-9]{6}$"
#otpInput="ngModel"
required
autofocus
[class.is-invalid]="otpInput.errors && (otpInput.dirty || otpInput.touched)"
[(ngModel)]="model.otp"></mdl-textfield>
</div>
<div class="mdl-dialog__actions">
<button
type="submit"
mdl-button
mdl-colored
mdl-ripple
[disabled]="!otpForm.form.valid">Submit
</button>
</div>
</form>
</mdl-dialog>
(submit)="submitLogin()"
[formGroup]="loginForm">
</ix-otp-dialog>
77 changes: 57 additions & 20 deletions src/app/core/auth/login-page/login/login.component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {Component, ViewChild} from '@angular/core';
import {LoginDetails} from './logindetails';
import {Component, OnInit, ViewChild} from '@angular/core';
import {AuthService} from '../../auth.service';
import {Router} from '@angular/router';
import {flatMap} from 'rxjs/operators';
import {InitService} from '../../../init/init.service';
import {MdlDialogComponent, MdlSnackbarService} from '@angular-mdl/core';
import {MdlSnackbarService} from '@angular-mdl/core';
import {HttpErrorResponse} from '@angular/common/http';
import {UNAUTHORIZED} from 'http-status-codes';
import {TOO_MANY_REQUESTS, UNAUTHORIZED} from 'http-status-codes';
import {OtpDialogComponent} from './otp/otp-dialog/otp-dialog.component';
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';

/**
* Login dialog
Expand All @@ -16,31 +17,40 @@ import {UNAUTHORIZED} from 'http-status-codes';
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
export class LoginComponent implements OnInit {

/**
* Reference to the OTP token dialog
*/
@ViewChild(MdlDialogComponent) otpDialog: MdlDialogComponent;
@ViewChild(OtpDialogComponent) otpDialog: OtpDialogComponent;

/**
* If currently loading
*/
public isLoading = false;

/**
* Model for form
* Login dialog formgroup
*/
public model = new LoginDetails();
public loginForm: FormGroup;

constructor(
private readonly initService: InitService,
private readonly authService: AuthService,
private readonly router: Router,
private readonly snackbar: MdlSnackbarService
private readonly snackbar: MdlSnackbarService,
private readonly fb: FormBuilder,
) {
}

ngOnInit() {
this.loginForm = this.fb.group({
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required]),
otp: new FormControl('', [Validators.maxLength(6), Validators.pattern(/^[0-9]{6}$/)])
});
}

/**
* Called when login was successfull
*/
Expand All @@ -50,7 +60,9 @@ export class LoginComponent {
tableau.submit();
} else {
// We logged in successfully, redirect to import page
this.router.navigate(['/']);
this.snackbar.showToast('Successfully logged in', 3000);

this.router.navigate(['/import']);
this.isLoading = false;
}
}
Expand All @@ -60,23 +72,41 @@ export class LoginComponent {
* @param error - The error in question
*/
onLoginError(error: HttpErrorResponse) {
this.isLoading = false;
switch (error.status) {
case UNAUTHORIZED: {
// The user is unauthorized because they are missing an otp token, show dialog
if (error.error.data && error.error.data[0].message === 'One-Time Password required') {
this.otpRequired();
if (error.error.data) {
switch (error.error.data[0].message) {
case 'One-Time Password required': {
this.otpRequired();
break;
}
case 'One-Time Password failed': {
this.otpInvalid();
break;
}
default: {
this.wrongCredentials();
break;
}
}
} else {
// The user is not using correct credentials
this.wrongCredentials();
}
break;
}
case TOO_MANY_REQUESTS: {
this.snackbar.showToast('You have tried to login too many times.', 3000);
break;
}
case 0: {
this.snackbar.showToast('Could not connect to IXON.');
this.snackbar.showToast('Could not connect to IXON.', 3000);
break;
}
default: {
this.snackbar.showToast('Something went wrong. Please try again later.');
this.snackbar.showToast('Something went wrong. Please try again later.', 3000);
break;
}
}
Expand All @@ -93,7 +123,7 @@ export class LoginComponent {
this.isLoading = true;

// Create access token
this.authService.createAccessToken(this.model)
this.authService.createAccessToken(this.loginForm.value)
.pipe(
flatMap(() => this.initService.initializeAuthenticated()),
).subscribe(
Expand All @@ -106,17 +136,24 @@ export class LoginComponent {
* Shows snackbar notification and clears the form
*/
private wrongCredentials() {
this.isLoading = false;
this.model.password = '';
this.model.otp = undefined;
this.snackbar.showToast('Incorrect credentials', 3000);
this.loginForm.controls['password'].reset();
this.loginForm.controls['otp'].reset();
this.snackbar.showToast('Incorrect credentials.', 3000);
}

/**
* Opens the OTP dialog
*/
private otpRequired() {
this.isLoading = false;
this.otpDialog.show();
this.snackbar.showToast('Please enter a one-time password.', 3000);
}

private otpInvalid() {
this.snackbar.showToast('The entered one-time password is invalid.', 3000);

this.loginForm.controls['otp'].reset();
this.loginForm.controls['otp'].setErrors({'incorrect': true});
this.otpDialog.show();
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<mdl-dialog
#otpDialog
[mdl-dialog-config]="{
styles:{'width': '25rem'},
isModal:true,
animate: false,
enterTransitionDuration: 400,
leaveTransitionDuration: 400}">
<form (ngSubmit)="submitForm()" [formGroup]="formGroup">
<h3 class="mdl-dialog__title">One-time password</h3>
<div class="mdl-dialog__content">
<ix-otp-input
[parentForm]="formGroup">
</ix-otp-input>
</div>
<div class="mdl-dialog__actions">
<button
type="submit"
mdl-button
mdl-colored
mdl-ripple
[disabled]="!formGroup.valid">
Submit
</button>
</div>
</form>
</mdl-dialog>
Loading

0 comments on commit 6ec0b21

Please sign in to comment.