aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/app/user/internal-user-service
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline/ClientApp/src/app/user/internal-user-service')
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/errors.ts29
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts21
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts5
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts123
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts155
5 files changed, 0 insertions, 333 deletions
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts b/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts
deleted file mode 100644
index 3358a9d9..00000000
--- a/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-export class BadNetworkError extends Error {
- constructor() {
- super('Network is bad.');
- }
-}
-
-export class AlreadyLoginError extends Error {
- constructor() {
- super('Internal logical error. There is already a token saved. Please call validateUserLoginState first.');
- }
-}
-
-export class BadCredentialsError extends Error {
- constructor() {
- super('Username or password is wrong.');
- }
-}
-
-export class UnknownError extends Error {
- constructor(public internalError?: any) {
- super('Sorry, unknown error occured!');
- }
-}
-
-export class ServerInternalError extends Error {
- constructor(message?: string) {
- super('Wrong server response. ' + message);
- }
-}
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts b/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts
deleted file mode 100644
index f52233c9..00000000
--- a/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { UserCredentials, UserInfo } from '../entities';
-
-export const createTokenUrl = '/api/User/CreateToken';
-export const validateTokenUrl = '/api/User/ValidateToken';
-
-export type CreateTokenRequest = UserCredentials;
-
-export interface CreateTokenResponse {
- success: boolean;
- token?: string;
- userInfo?: UserInfo;
-}
-
-export interface ValidateTokenRequest {
- token: string;
-}
-
-export interface ValidateTokenResponse {
- isValid: boolean;
- userInfo?: UserInfo;
-}
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts
deleted file mode 100644
index f4a85262..00000000
--- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.mock.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { InternalUserService } from './internal-user.service';
-
-export function createMockInternalUserService(): jasmine.SpyObj<InternalUserService> {
- return jasmine.createSpyObj('InternalUserService', ['userRouteNavigate', 'refreshAndGetUserState', 'tryLogin']);
-}
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts
deleted file mode 100644
index 15755382..00000000
--- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import { TestBed, fakeAsync, tick } from '@angular/core/testing';
-import { HttpRequest } from '@angular/common/http';
-import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
-import { Router } from '@angular/router';
-import { MatSnackBar } from '@angular/material';
-
-import { Mock } from 'src/app/test-utilities/mock';
-import { createMockStorage } from 'src/app/test-utilities/storage.mock';
-import { WINDOW } from '../window-inject-token';
-
-import { UserInfo, UserCredentials } from '../entities';
-import {
- createTokenUrl, validateTokenUrl, CreateTokenRequest,
- CreateTokenResponse, ValidateTokenRequest, ValidateTokenResponse
-} from './http-entities';
-import { InternalUserService, SnackBarTextKey, snackBarText, TOKEN_STORAGE_KEY } from './internal-user.service';
-import { repeat } from 'src/app/utilities/language-untilities';
-
-
-describe('InternalUserService', () => {
- let mockLocalStorage: Mock<Storage>;
- let mockSnackBar: jasmine.SpyObj<MatSnackBar>;
-
- beforeEach(() => {
- mockLocalStorage = createMockStorage();
- mockSnackBar = jasmine.createSpyObj('MatSnackBar', ['open']);
- TestBed.configureTestingModule({
- imports: [HttpClientTestingModule],
- providers: [
- { provide: WINDOW, useValue: { localStorage: mockLocalStorage } },
- { provide: Router, useValue: null },
- { provide: MatSnackBar, useValue: mockSnackBar }
- ]
- });
- });
-
- it('should be created', () => {
- const service: InternalUserService = TestBed.get(InternalUserService);
- expect(service).toBeTruthy();
- });
-
- const mockUserInfo: UserInfo = {
- username: 'user',
- roles: ['user', 'other']
- };
-
- const mockToken = 'mock-token';
-
- describe('validate token', () => {
- const validateTokenRequestMatcher = (req: HttpRequest<ValidateTokenRequest>): boolean =>
- req.url === validateTokenUrl && req.body !== null && req.body.token === mockToken;
-
- function createTest(
- expectSnackBarTextKey: SnackBarTextKey,
- setStorageToken: boolean,
- setHttpController?: (controller: HttpTestingController) => void
- ): () => void {
- return fakeAsync(() => {
- if (setStorageToken) {
- mockLocalStorage.setItem(TOKEN_STORAGE_KEY, mockToken);
- }
- TestBed.get(InternalUserService);
- const controller = TestBed.get(HttpTestingController) as HttpTestingController;
- if (setHttpController) {
- setHttpController(controller);
- }
- controller.verify();
- tick();
- expect(mockSnackBar.open).toHaveBeenCalledWith(snackBarText[expectSnackBarTextKey], jasmine.anything(), jasmine.anything());
- });
- }
-
- it('no login should work well', createTest('noLogin', false));
- it('already login should work well', createTest('alreadyLogin', true,
- controller => controller.expectOne(validateTokenRequestMatcher).flush(
- <ValidateTokenResponse>{ isValid: true, userInfo: mockUserInfo })));
- it('invalid login should work well', createTest('invalidLogin', true,
- controller => controller.expectOne(validateTokenRequestMatcher).flush(<ValidateTokenResponse>{ isValid: false })));
- it('check fail should work well', createTest('checkFail', true,
- controller => repeat(4, () => {
- controller.expectOne(validateTokenRequestMatcher).error(new ErrorEvent('Network error', { message: 'simulated network error' }));
- })));
- });
-
- describe('login should work well', () => {
- const mockUserCredentials: UserCredentials = {
- username: 'user',
- password: 'user'
- };
-
- function createTest(rememberMe: boolean) {
- return () => {
- const service: InternalUserService = TestBed.get(InternalUserService);
-
- service.tryLogin({ ...mockUserCredentials, rememberMe: rememberMe }).subscribe(result => {
- expect(result).toEqual(mockUserInfo);
- });
-
- const httpController = TestBed.get(HttpTestingController) as HttpTestingController;
-
- httpController.expectOne((request: HttpRequest<CreateTokenRequest>) =>
- request.url === createTokenUrl && request.body !== null &&
- request.body.username === mockUserCredentials.username &&
- request.body.password === mockUserCredentials.password).flush(<CreateTokenResponse>{
- success: true,
- token: mockToken,
- userInfo: mockUserInfo
- });
-
- expect(service.currentUserInfo).toEqual(mockUserInfo);
-
- httpController.verify();
-
- expect(mockLocalStorage.getItem(TOKEN_STORAGE_KEY)).toBe(rememberMe ? mockToken : null);
- };
- }
-
- it('remember me should work well', createTest(true));
- it('not remember me should work well', createTest(false));
- });
-
- // TODO: test on error situations.
-});
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts
deleted file mode 100644
index 66eafde9..00000000
--- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import { Injectable, Inject } from '@angular/core';
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Router } from '@angular/router';
-
-import { Observable, throwError, BehaviorSubject, of } from 'rxjs';
-import { map, catchError, retry, switchMap, tap, filter } from 'rxjs/operators';
-
-import { AlreadyLoginError, BadCredentialsError, BadNetworkError, UnknownError, ServerInternalError } from './errors';
-import {
- createTokenUrl, validateTokenUrl, CreateTokenRequest,
- CreateTokenResponse, ValidateTokenRequest, ValidateTokenResponse
-} from './http-entities';
-import { UserCredentials, UserInfo } from '../entities';
-import { MatSnackBar } from '@angular/material';
-import { WINDOW } from '../window-inject-token';
-
-export const snackBarText = {
- checkFail: 'Failed to check last login',
- noLogin: 'No login before!',
- alreadyLogin: 'You have login already!',
- invalidLogin: 'Last login is no longer invalid!',
- ok: 'ok'
-};
-
-export type SnackBarTextKey = Exclude<keyof typeof snackBarText, 'ok'>;
-
-export const TOKEN_STORAGE_KEY = 'token';
-
-export interface LoginInfo extends UserCredentials {
- rememberMe: boolean;
-}
-
-/**
- * This service is only used internal in user module.
- */
-@Injectable({
- providedIn: 'root'
-})
-export class InternalUserService {
-
- private token: string | null = null;
- private userInfoSubject = new BehaviorSubject<UserInfo | null | undefined>(undefined);
-
- readonly userInfo$: Observable<UserInfo | null> =
- <Observable<UserInfo | null>>this.userInfoSubject.pipe(filter(value => value !== undefined));
-
- get currentUserInfo(): UserInfo | null | undefined {
- return this.userInfoSubject.value;
- }
-
- private openSnackBar(snackBar: MatSnackBar, textKey: SnackBarTextKey) {
- setTimeout(() => snackBar.open(snackBarText[textKey], snackBarText.ok, { duration: 2000 }), 0);
- }
-
- constructor(@Inject(WINDOW) private window: Window, private httpClient: HttpClient, private router: Router, snackBar: MatSnackBar) {
- const savedToken = this.window.localStorage.getItem(TOKEN_STORAGE_KEY);
- if (savedToken === null) {
- this.openSnackBar(snackBar, 'noLogin');
- this.userInfoSubject.next(null);
- } else {
- this.validateToken(savedToken).subscribe(result => {
- if (result === null) {
- this.window.localStorage.removeItem(TOKEN_STORAGE_KEY);
- this.openSnackBar(snackBar, 'invalidLogin');
- this.userInfoSubject.next(null);
- } else {
- this.token = savedToken;
- this.userInfoSubject.next(result);
- this.openSnackBar(snackBar, 'alreadyLogin');
- }
- }, _ => {
- this.openSnackBar(snackBar, 'checkFail');
- this.userInfoSubject.next(null);
- });
- }
- }
-
- private validateToken(token: string): Observable<UserInfo | null> {
- return this.httpClient.post<ValidateTokenResponse>(validateTokenUrl, <ValidateTokenRequest>{ token: token }).pipe(
- retry(3),
- switchMap(result => {
- if (result.isValid) {
- const { userInfo } = result;
- if (userInfo) {
- return of(userInfo);
- } else {
- return throwError(new ServerInternalError('IsValid is true but UserInfo is null.'));
- }
- } else {
- return of(null);
- }
- }),
- tap({
- error: error => {
- console.error('Failed to validate token.');
- console.error(error);
- }
- }),
- );
- }
-
- userRouteNavigate(commands: any[] | null) {
- this.router.navigate([{
- outlets: {
- user: commands
- }
- }]);
- }
-
- tryLogin(info: LoginInfo): Observable<UserInfo> {
- if (this.token) {
- return throwError(new AlreadyLoginError());
- }
-
- return this.httpClient.post<CreateTokenResponse>(createTokenUrl, <CreateTokenRequest>info).pipe(
- catchError((error: HttpErrorResponse) => {
- if (error.error instanceof ErrorEvent) {
- console.error('An error occurred when login: ' + error.error.message);
- return throwError(new BadNetworkError());
- } else {
- console.error('An unknown error occurred when login: ' + error);
- return throwError(new UnknownError(error));
- }
- }),
- switchMap(result => {
- if (result.success) {
- if (result.token && result.userInfo) {
- this.token = result.token;
- if (info.rememberMe) {
- this.window.localStorage.setItem(TOKEN_STORAGE_KEY, result.token);
- }
- this.userInfoSubject.next(result.userInfo);
- return of(result.userInfo);
- } else {
- console.error('An error occurred when login: server return wrong data.');
- return throwError(new ServerInternalError('Token or userInfo is null.'));
- }
- } else {
- console.error('An error occurred when login: wrong credentials.');
- return throwError(new BadCredentialsError());
- }
- })
- );
- }
-
- logout() {
- if (this.currentUserInfo === null) {
- throw new Error('No login now. You can\'t logout.');
- }
-
- this.window.localStorage.removeItem(TOKEN_STORAGE_KEY);
- this.token = null;
- this.userInfoSubject.next(null);
- }
-}