initial codebase
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
import { MockAuthenticationService } from './authentication.service.mock';
|
||||
import { AuthenticationGuard } from './authentication.guard';
|
||||
|
||||
describe('AuthenticationGuard', () => {
|
||||
let authenticationGuard: AuthenticationGuard;
|
||||
let authenticationService: MockAuthenticationService;
|
||||
let mockRouter: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRouter = {
|
||||
navigate: jasmine.createSpy('navigate')
|
||||
};
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
AuthenticationGuard,
|
||||
{ provide: AuthenticationService, useClass: MockAuthenticationService },
|
||||
{ provide: Router, useValue: mockRouter },
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(inject([
|
||||
AuthenticationGuard,
|
||||
AuthenticationService
|
||||
], (_authenticationGuard: AuthenticationGuard,
|
||||
_authenticationService: MockAuthenticationService) => {
|
||||
|
||||
authenticationGuard = _authenticationGuard;
|
||||
authenticationService = _authenticationService;
|
||||
}));
|
||||
|
||||
it('should have a canActivate method', () => {
|
||||
expect(typeof authenticationGuard.canActivate).toBe('function');
|
||||
});
|
||||
|
||||
it('should return true if user is authenticated', () => {
|
||||
expect(authenticationGuard.canActivate()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false and redirect to login if user is not authenticated', () => {
|
||||
// Arrange
|
||||
authenticationService.credentials = null;
|
||||
|
||||
// Act
|
||||
const result = authenticationGuard.canActivate();
|
||||
|
||||
// Assert
|
||||
expect(mockRouter.navigate).toHaveBeenCalledWith(['/login'], {replaceUrl: true});
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router, CanActivate } from '@angular/router';
|
||||
|
||||
import { Logger } from '../logger.service';
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
|
||||
const log = new Logger('AuthenticationGuard');
|
||||
|
||||
@Injectable()
|
||||
export class AuthenticationGuard implements CanActivate {
|
||||
|
||||
constructor(private router: Router,
|
||||
private authenticationService: AuthenticationService) { }
|
||||
|
||||
canActivate(): boolean {
|
||||
if (this.authenticationService.isAuthenticated()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
log.debug('Not authenticated, redirecting...');
|
||||
this.router.navigate(['/login'], { replaceUrl: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
import { Credentials, LoginContext } from './authentication.service';
|
||||
|
||||
export class MockAuthenticationService {
|
||||
|
||||
credentials: Credentials | null = {
|
||||
username: 'test',
|
||||
token: '123'
|
||||
};
|
||||
|
||||
login(context: LoginContext): Observable<Credentials> {
|
||||
return of({
|
||||
username: context.username,
|
||||
token: '123456'
|
||||
});
|
||||
}
|
||||
|
||||
logout(): Observable<boolean> {
|
||||
this.credentials = null;
|
||||
return of(true);
|
||||
}
|
||||
|
||||
isAuthenticated(): boolean {
|
||||
return !!this.credentials;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import { TestBed, inject, fakeAsync, tick } from '@angular/core/testing';
|
||||
|
||||
import { AuthenticationService, Credentials} from './authentication.service';
|
||||
|
||||
const credentialsKey = 'credentials';
|
||||
|
||||
describe('AuthenticationService', () => {
|
||||
let authenticationService: AuthenticationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [AuthenticationService]
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(inject([
|
||||
AuthenticationService
|
||||
], (_authenticationService: AuthenticationService) => {
|
||||
authenticationService = _authenticationService;
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
// Cleanup
|
||||
localStorage.removeItem(credentialsKey);
|
||||
sessionStorage.removeItem(credentialsKey);
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('should return credentials', fakeAsync(() => {
|
||||
// Act
|
||||
const request = authenticationService.login({
|
||||
username: 'toto',
|
||||
password: '123'
|
||||
});
|
||||
tick();
|
||||
|
||||
// Assert
|
||||
request.subscribe(credentials => {
|
||||
expect(credentials).toBeDefined();
|
||||
expect(credentials.token).toBeDefined();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should authenticate user', fakeAsync(() => {
|
||||
expect(authenticationService.isAuthenticated()).toBe(false);
|
||||
|
||||
// Act
|
||||
const request = authenticationService.login({
|
||||
username: 'toto',
|
||||
password: '123'
|
||||
});
|
||||
tick();
|
||||
|
||||
// Assert
|
||||
request.subscribe(() => {
|
||||
expect(authenticationService.isAuthenticated()).toBe(true);
|
||||
expect(authenticationService.credentials).toBeDefined();
|
||||
expect(authenticationService.credentials).not.toBeNull();
|
||||
expect((<Credentials>authenticationService.credentials).token).toBeDefined();
|
||||
expect((<Credentials>authenticationService.credentials).token).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should persist credentials for the session', fakeAsync(() => {
|
||||
// Act
|
||||
const request = authenticationService.login({
|
||||
username: 'toto',
|
||||
password: '123'
|
||||
});
|
||||
tick();
|
||||
|
||||
// Assert
|
||||
request.subscribe(() => {
|
||||
expect(sessionStorage.getItem(credentialsKey)).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should persist credentials across sessions', fakeAsync(() => {
|
||||
// Act
|
||||
const request = authenticationService.login({
|
||||
username: 'toto',
|
||||
password: '123',
|
||||
remember: true
|
||||
});
|
||||
tick();
|
||||
|
||||
// Assert
|
||||
request.subscribe(() => {
|
||||
expect(localStorage.getItem(credentialsKey)).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('logout', () => {
|
||||
it('should clear user authentication', fakeAsync(() => {
|
||||
// Arrange
|
||||
const loginRequest = authenticationService.login({
|
||||
username: 'toto',
|
||||
password: '123'
|
||||
});
|
||||
tick();
|
||||
|
||||
// Assert
|
||||
loginRequest.subscribe(() => {
|
||||
expect(authenticationService.isAuthenticated()).toBe(true);
|
||||
|
||||
const request = authenticationService.logout();
|
||||
tick();
|
||||
|
||||
request.subscribe(() => {
|
||||
expect(authenticationService.isAuthenticated()).toBe(false);
|
||||
expect(authenticationService.credentials).toBeNull();
|
||||
expect(sessionStorage.getItem(credentialsKey)).toBeNull();
|
||||
expect(localStorage.getItem(credentialsKey)).toBeNull();
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should clear persisted user authentication', fakeAsync(() => {
|
||||
// Arrange
|
||||
const loginRequest = authenticationService.login({
|
||||
username: 'toto',
|
||||
password: '123',
|
||||
remember: true
|
||||
});
|
||||
tick();
|
||||
|
||||
// Assert
|
||||
loginRequest.subscribe(() => {
|
||||
expect(authenticationService.isAuthenticated()).toBe(true);
|
||||
|
||||
const request = authenticationService.logout();
|
||||
tick();
|
||||
|
||||
request.subscribe(() => {
|
||||
expect(authenticationService.isAuthenticated()).toBe(false);
|
||||
expect(authenticationService.credentials).toBeNull();
|
||||
expect(sessionStorage.getItem(credentialsKey)).toBeNull();
|
||||
expect(localStorage.getItem(credentialsKey)).toBeNull();
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
export interface Credentials {
|
||||
// Customize received credentials here
|
||||
username: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface LoginContext {
|
||||
username: string;
|
||||
password: string;
|
||||
remember?: boolean;
|
||||
}
|
||||
|
||||
const credentialsKey = 'credentials';
|
||||
|
||||
/**
|
||||
* Provides a base for authentication workflow.
|
||||
* The Credentials interface as well as login/logout methods should be replaced with proper implementation.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AuthenticationService {
|
||||
|
||||
private _credentials: Credentials | null;
|
||||
|
||||
constructor() {
|
||||
const savedCredentials = sessionStorage.getItem(credentialsKey) || localStorage.getItem(credentialsKey);
|
||||
if (savedCredentials) {
|
||||
this._credentials = JSON.parse(savedCredentials);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the user.
|
||||
* @param {LoginContext} context The login parameters.
|
||||
* @return {Observable<Credentials>} The user credentials.
|
||||
*/
|
||||
login(context: LoginContext): Observable<Credentials> {
|
||||
// Replace by proper authentication call
|
||||
const data = {
|
||||
username: context.username,
|
||||
token: '123456'
|
||||
};
|
||||
this.setCredentials(data, context.remember);
|
||||
return of(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs out the user and clear credentials.
|
||||
* @return {Observable<boolean>} True if the user was logged out successfully.
|
||||
*/
|
||||
logout(): Observable<boolean> {
|
||||
// Customize credentials invalidation here
|
||||
this.setCredentials();
|
||||
return of(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks is the user is authenticated.
|
||||
* @return {boolean} True if the user is authenticated.
|
||||
*/
|
||||
isAuthenticated(): boolean {
|
||||
return !!this.credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user credentials.
|
||||
* @return {Credentials} The user credentials or null if the user is not authenticated.
|
||||
*/
|
||||
get credentials(): Credentials | null {
|
||||
return this._credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user credentials.
|
||||
* The credentials may be persisted across sessions by setting the `remember` parameter to true.
|
||||
* Otherwise, the credentials are only persisted for the current session.
|
||||
* @param {Credentials=} credentials The user credentials.
|
||||
* @param {boolean=} remember True to remember credentials across sessions.
|
||||
*/
|
||||
private setCredentials(credentials?: Credentials, remember?: boolean) {
|
||||
this._credentials = credentials || null;
|
||||
|
||||
if (credentials) {
|
||||
const storage = remember ? localStorage : sessionStorage;
|
||||
storage.setItem(credentialsKey, JSON.stringify(credentials));
|
||||
} else {
|
||||
sessionStorage.removeItem(credentialsKey);
|
||||
localStorage.removeItem(credentialsKey);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user