Commit fcd2b9da authored by Christopher Mark Fullarton's avatar Christopher Mark Fullarton
Browse files

Adds token authentication mechanism

parent 67b1c235
......@@ -376,7 +376,9 @@ export class HomeComponent implements OnInit, OnDestroy {
footerElements.push(this.footerBarService.footerElemLogout);
} else {
footerElements.push(this.footerBarService.footerElemLogin);
if (environment.loginMechanism && environment.loginMechanism.length) {
footerElements.push(this.footerBarService.footerElemLogin);
}
}
this.footerBarService.replaceFooterElements(footerElements);
......
<ng-container *ngIf="!isLoading">
<h4 class="text-center mb-5 mt-sm-5">{{'component.login.title' | translate}}</h4>
<h4 class="text-center mb-5 mt-sm-5">{{'component.login.title' | translate}}</h4>
<div class="input-group input-group-sm">
<div *ngIf="hasUsernamePasswordLogin"
class="input-group input-group-sm">
<input type="text"
<input (keydown.enter)="login()"
[(ngModel)]="username"
[placeholder]="'component.login.username' | translate"
class="form-control"
name="username"
placeholder="Username"
(keypress)="trySubmit($event)"
[(ngModel)]="username"/>
type="text"/>
<input type="password"
<input (keydown.enter)="login()"
[(ngModel)]="password"
[placeholder]="'component.login.password' | translate"
class="form-control my-2 my-sm-0"
name="password"
placeholder="Password"
(keypress)="trySubmit($event)"
[(ngModel)]="password"/>
type="password"/>
<div class="input-group-append">
<button class="btn btn-light"
(click)="login()">{{'component.login.login' | translate}}
<button (click)="login()"
class="btn btn-light">{{'component.login.login' | translate}}
</button>
</div>
</div>
<ng-container *ngIf="hasTokenLogin">
<h6 *ngIf="hasMultipleLoginMethods"
class="mt-5">{{'component.login.login-via-token' | translate}}</h6>
<div class="input-group input-group-sm">
<input (keydown.enter)="login()"
[(ngModel)]="token"
[placeholder]="'component.login.token' | translate"
class="form-control my-2 my-sm-0"
name="token"
type="password"/>
<div class="input-group-append">
<button (click)="login()"
class="btn btn-light">{{'component.login.login' | translate}}
</button>
</div>
</div>
</ng-container>
<h4 *ngIf="authorizationFailed"
class="text-danger text-center mt-5">{{'component.login.authorization_failed' | translate}}
class="text-danger text-center mt-5">{{'component.login.authorization_failed' | translate}}
</h4>
</ng-container>
\ No newline at end of file
</ng-container>
......@@ -70,50 +70,6 @@ describe('LoginComponent', () => {
expect(LoginComponent.TYPE).toEqual('LoginComponent');
});
describe('#trySubmit', () => {
it('should submit the login request if the username and password have been given and the enter key was pressed', () => {
const event = { keyCode: 13 };
const username = 'testuser';
const password = 'testpassword';
spyOn(component, 'login').and.callFake(() => new Promise<void>(resolve => resolve()));
component['username'] = username;
component['password'] = password;
component.trySubmit(event);
expect(component.login).toHaveBeenCalled();
});
it('should not submit the login if the username or password fields are emtpy', () => {
const event = { keyCode: 13 };
const username = '';
const password = '';
spyOn(component, 'login').and.callFake(() => new Promise<void>(resolve => resolve()));
component['username'] = username;
component['password'] = password;
component.trySubmit(event);
expect(component.login).not.toHaveBeenCalled();
});
it('should not submit the login if any key except the enter key is pressed', () => {
const event = { keyCode: 42 };
const username = 'testuser';
const password = 'testpassword';
spyOn(component, 'login').and.callFake(() => new Promise<void>(resolve => resolve()));
component['username'] = username;
component['password'] = password;
component.trySubmit(event);
expect(component.login).not.toHaveBeenCalled();
});
});
describe('#login', () => {
it('should submit the login request if the username and password are valid',
inject([UserService, Router], async (userService: UserService, router: Router) => {
......
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { environment } from '../../../environments/environment';
import { LoginMechanism } from '../../../lib/enums/enums';
import { FooterBarService } from '../../service/footer-bar/footer-bar.service';
import { HeaderLabelService } from '../../service/header-label/header-label.service';
import { UserService } from '../../service/user/user.service';
......@@ -13,6 +15,10 @@ export class LoginComponent implements OnInit {
public static readonly TYPE = 'LoginComponent';
public username = '';
public password = '';
public token = '';
public hasUsernamePasswordLogin: boolean = environment.loginMechanism.includes(LoginMechanism.UsernamePassword);
public hasTokenLogin: boolean = environment.loginMechanism.includes(LoginMechanism.Token);
public hasMultipleLoginMethods: boolean = environment.loginMechanism.length > 1;
private _authorizationFailed = false;
......@@ -53,21 +59,21 @@ export class LoginComponent implements OnInit {
public async login(): Promise<void> {
this._authorizationFailed = false;
if (this.username && this.password) {
const passwordHash = this.userService.hashPassword(this.username, this.password);
const isLoggedIn = await this.userService.authenticateThroughLogin(this.username.toLowerCase(), passwordHash);
let isLoggedIn = false;
if (isLoggedIn) {
this.router.navigateByUrl(this.return);
} else {
this._authorizationFailed = true;
}
if (this.hasTokenLogin && this.token) {
const tokenHash = this.userService.hashToken(this.token);
isLoggedIn = await this.userService.authenticateThroughLoginToken(tokenHash);
} else if (this.hasUsernamePasswordLogin && this.username && this.password) {
const passwordHash = this.userService.hashPassword(this.username, this.password);
isLoggedIn = await this.userService.authenticateThroughLogin(this.username.toLowerCase(), passwordHash);
}
}
public trySubmit(event): void {
if (event.keyCode === 13 && this.username && this.password) {
this.login();
if (isLoggedIn) {
this.router.navigateByUrl(this.return);
} else {
this._authorizationFailed = true;
}
}
}
......@@ -199,10 +199,39 @@ export class UserService {
});
}
public authenticateThroughLoginToken(tokenHash): Promise<boolean> {
return new Promise(async resolve => {
const data = await this.authorizeApiService.postAuthorizationForStaticLogin({
tokenHash,
token: this._staticLoginToken,
}).toPromise().catch(() => resolve(false));
if (!data) {
return;
}
if (data.status === StatusProtocol.Success) {
this._staticLoginToken = data.payload.token;
this._tmpRemoteQuizData = data.payload.quizzes;
this._username = data.payload.username;
this.isLoggedIn = true;
resolve(true);
} else {
this.isLoggedIn = false;
resolve(false);
}
});
}
public hashPassword(username: string, password: string): string {
return this.sha1(`${username}|${password}`);
}
public hashToken(token: string): string {
return this.sha1(token);
}
public isAuthorizedFor(authorization: Array<UserRole>): boolean;
public isAuthorizedFor(authorization: UserRole): boolean;
public isAuthorizedFor(authorization: UserRole | Array<UserRole>): boolean {
......
export const environment = {
import { LoginMechanism } from '../lib/enums/enums';
import { IEnvironment } from '../lib/interfaces/IEnvironment';
export const environment: IEnvironment = {
production: true,
leaderboardAmount: 5,
readingConfirmationEnabled: false,
......@@ -8,6 +11,7 @@ export const environment = {
infoBackendApiEnabled: true,
requireLoginToCreateQuiz: false,
forceQuizTheme: false,
loginMechanism: [LoginMechanism.UsernamePassword],
};
export enum DEVICE_TYPES {
......
......@@ -3,7 +3,10 @@
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
import { LoginMechanism } from '../lib/enums/enums';
import { IEnvironment } from '../lib/interfaces/IEnvironment';
export const environment: IEnvironment = {
production: false,
leaderboardAmount: 5,
readingConfirmationEnabled: false,
......@@ -13,6 +16,7 @@ export const environment = {
infoBackendApiEnabled: false,
requireLoginToCreateQuiz: true,
forceQuizTheme: false,
loginMechanism: [LoginMechanism.UsernamePassword, LoginMechanism.Token],
};
export enum DEVICE_TYPES {
......
export const environment = {
import { LoginMechanism } from '../lib/enums/enums';
import { IEnvironment } from '../lib/interfaces/IEnvironment';
export const environment: IEnvironment = {
production: true,
leaderboardAmount: 5,
readingConfirmationEnabled: false,
......@@ -8,6 +11,7 @@ export const environment = {
infoBackendApiEnabled: false,
requireLoginToCreateQuiz: true,
forceQuizTheme: true,
loginMechanism: [LoginMechanism.UsernamePassword, LoginMechanism.Token],
};
export enum DEVICE_TYPES {
......
export enum LoginMechanism {
UsernamePassword, Token
}
export enum Filter {
None, //
Unused, //
......
import { LoginMechanism } from '../enums/enums';
export interface IEnvironment {
production: boolean;
leaderboardAmount: number;
readingConfirmationEnabled: boolean;
confidenceSliderEnabled: boolean;
infoAboutTabEnabled: boolean;
infoProjectTabEnabled: boolean;
infoBackendApiEnabled: boolean;
requireLoginToCreateQuiz: boolean;
forceQuizTheme: boolean;
loginMechanism: Array<LoginMechanism>;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment