aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2019-03-16 20:57:31 +0800
committercrupest <crupest@outlook.com>2019-03-16 20:57:31 +0800
commit1158c85071e7a8132b42df9481141c107eeee657 (patch)
treeb9ae4f45fe8a23ebc7bc705af97f0377b73f8063
parent1c9edc5914869a3bbde20742c483182636ee4d43 (diff)
downloadtimeline-1158c85071e7a8132b42df9481141c107eeee657.tar.gz
timeline-1158c85071e7a8132b42df9481141c107eeee657.tar.bz2
timeline-1158c85071e7a8132b42df9481141c107eeee657.zip
Add remember me.
-rw-r--r--Timeline/ClientApp/package.json2
-rw-r--r--Timeline/ClientApp/src/app/test-utilities/activated-route.mock.ts10
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts81
-rw-r--r--Timeline/ClientApp/src/app/user/user.module.ts4
-rw-r--r--Timeline/ClientApp/src/app/utilities/language-untilities.ts4
-rw-r--r--Timeline/ClientApp/tsconfig.json4
6 files changed, 66 insertions, 39 deletions
diff --git a/Timeline/ClientApp/package.json b/Timeline/ClientApp/package.json
index f725a0f5..7c6d28f0 100644
--- a/Timeline/ClientApp/package.json
+++ b/Timeline/ClientApp/package.json
@@ -4,7 +4,7 @@
"scripts": {
"ng": "ng",
"start": "ng serve",
- "start-dotnet": "dotnet run --project ..",
+ "start-dotnet": "dotnet run --project ../Timeline.csproj",
"build": "ng build",
"build:ssr": "ng run Timeline:server:dev",
"test": "ng test",
diff --git a/Timeline/ClientApp/src/app/test-utilities/activated-route.mock.ts b/Timeline/ClientApp/src/app/test-utilities/activated-route.mock.ts
index 1743e615..72707c5e 100644
--- a/Timeline/ClientApp/src/app/test-utilities/activated-route.mock.ts
+++ b/Timeline/ClientApp/src/app/test-utilities/activated-route.mock.ts
@@ -1,11 +1,15 @@
-import { ParamMap } from '@angular/router';
+import { ParamMap, ActivatedRouteSnapshot, ActivatedRoute } from '@angular/router';
import { Observable, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
+export type PartialMock<T> = {
+ [P in keyof T]?: T[P] | PartialMock<T[P]>;
+};
+
export interface ParamMapCreator { [name: string]: string | string[]; }
-export class MockActivatedRouteSnapshot {
+export class MockActivatedRouteSnapshot implements PartialMock<ActivatedRouteSnapshot> {
private paramMapInternal: ParamMap;
@@ -44,7 +48,7 @@ export class MockActivatedRouteSnapshot {
}
}
-export class MockActivatedRoute {
+export class MockActivatedRoute implements PartialMock<ActivatedRoute> {
snapshot$ = new BehaviorSubject<MockActivatedRouteSnapshot>(new MockActivatedRouteSnapshot());
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
index 4767bd16..604393f4 100644
--- 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
@@ -2,10 +2,8 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';
-import { Observable, of, throwError, BehaviorSubject } from 'rxjs';
-import { map, catchError, retry } from 'rxjs/operators';
-
-import { nullIfUndefined } from '../../utilities/language-untilities';
+import { Observable, throwError, BehaviorSubject, of } from 'rxjs';
+import { map, catchError, retry, switchMap, tap } from 'rxjs/operators';
import { AlreadyLoginError, BadCredentialsError, BadNetworkError, UnknownError } from './errors';
import {
@@ -13,10 +11,9 @@ import {
CreateTokenResponse, ValidateTokenRequest, ValidateTokenResponse
} from './http-entities';
import { UserCredentials, UserInfo } from '../entities';
+import { MatSnackBar } from '@angular/material';
-export type UserLoginState = 'nologin' | 'invalidlogin' | 'success';
-
/**
* This service is only used internal in user module.
*/
@@ -36,41 +33,60 @@ export class InternalUserService {
return this.userInfoSubject;
}
- constructor(private httpClient: HttpClient, private router: Router) { }
+ constructor(private httpClient: HttpClient, private router: Router, private snackBar: MatSnackBar) {
+ const savedToken = window.localStorage.getItem('token');
+ if (savedToken === null) {
+ setTimeout(() => snackBar.open('No login before!', 'ok', { duration: 2000 }), 0);
+ } else {
+ this.validateToken(savedToken).subscribe(result => {
+ if (result === null) {
+ window.localStorage.removeItem('token');
+ setTimeout(() => snackBar.open('Last login is no longer invalid!', 'ok', { duration: 2000 }), 0);
+ } else {
+ this.token = savedToken;
+ this.userInfoSubject.next(result);
+ setTimeout(() => snackBar.open('You have login already!', 'ok', { duration: 2000 }), 0);
+ }
+ }, _ => {
+ setTimeout(() => snackBar.open('Failed to check last login', 'ok', { duration: 2000 }), 0);
+ });
+ }
- userRouteNavigate(commands: any[] | null) {
- this.router.navigate([{
- outlets: {
- user: commands
- }
- }]);
}
- refreshAndGetUserState(): Observable<UserLoginState> {
- if (this.token === undefined || this.token === null) {
- return of(<UserLoginState>'nologin');
- }
-
- return this.httpClient.post<ValidateTokenResponse>(validateTokenUrl, <ValidateTokenRequest>{ token: this.token }).pipe(
+ private validateToken(token: string): Observable<UserInfo | null> {
+ return this.httpClient.post<ValidateTokenResponse>(validateTokenUrl, <ValidateTokenRequest>{ token: token }).pipe(
retry(3),
- catchError(error => {
- console.error('Failed to validate token.');
- return throwError(error);
- }),
- map(result => {
+ switchMap(result => {
if (result.isValid) {
- this.userInfoSubject.next(nullIfUndefined(result.userInfo));
- return <UserLoginState>'success';
+ const { userInfo } = result;
+ if (userInfo) {
+ return of(userInfo);
+ } else {
+ return throwError(new Error('Wrong server response. IsValid is true but UserInfo is null.'));
+ }
} else {
- this.token = null;
- this.userInfoSubject.next(null);
- return <UserLoginState>'invalidlogin';
+ return of(null);
}
- })
+ }),
+ tap({
+ error: error => {
+ console.error('Failed to validate token.');
+ console.error(error);
+ }
+ }),
);
}
- tryLogin(credentials: UserCredentials): Observable<UserInfo> {
+ userRouteNavigate(commands: any[] | null) {
+ this.router.navigate([{
+ outlets: {
+ user: commands
+ }
+ }]);
+ }
+
+ tryLogin(credentials: UserCredentials, options: { remember: boolean } = { remember: true }): Observable<UserInfo> {
if (this.token) {
return throwError(new AlreadyLoginError());
}
@@ -90,6 +106,9 @@ export class InternalUserService {
}),
map(result => {
this.token = result.token;
+ if (options.remember) {
+ window.localStorage.setItem('token', result.token);
+ }
this.userInfoSubject.next(result.userInfo);
return result.userInfo;
})
diff --git a/Timeline/ClientApp/src/app/user/user.module.ts b/Timeline/ClientApp/src/app/user/user.module.ts
index 8f3b9a9c..7645d61d 100644
--- a/Timeline/ClientApp/src/app/user/user.module.ts
+++ b/Timeline/ClientApp/src/app/user/user.module.ts
@@ -6,7 +6,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
import {
MatFormFieldModule, MatProgressSpinnerModule,
- MatDialogModule, MatInputModule, MatButtonModule
+ MatDialogModule, MatInputModule, MatButtonModule, MatSnackBarModule
} from '@angular/material';
import { RequireNoLoginGuard, RequireLoginGuard } from './auth.guard';
@@ -25,7 +25,7 @@ import { UtilityModule } from '../utilities/utility.module';
{ path: '**', component: RedirectComponent, outlet: 'user' }
]),
CommonModule, HttpClientModule, ReactiveFormsModule, BrowserAnimationsModule,
- MatFormFieldModule, MatProgressSpinnerModule, MatDialogModule, MatInputModule, MatButtonModule,
+ MatFormFieldModule, MatProgressSpinnerModule, MatDialogModule, MatInputModule, MatButtonModule, MatSnackBarModule,
UtilityModule
],
exports: [RouterModule],
diff --git a/Timeline/ClientApp/src/app/utilities/language-untilities.ts b/Timeline/ClientApp/src/app/utilities/language-untilities.ts
index be9df2dc..7f38f3e4 100644
--- a/Timeline/ClientApp/src/app/utilities/language-untilities.ts
+++ b/Timeline/ClientApp/src/app/utilities/language-untilities.ts
@@ -3,9 +3,9 @@ export function nullIfUndefined<T>(value: T | undefined): T | null {
}
export function throwIfNullOrUndefined<T>(value: T | null | undefined,
- lazyMessage: () => string = () => 'Value mustn\'t be falsy'): T | never {
+ message: string | (() => string) = 'Value mustn\'t be null or undefined'): T | never {
if (value === null || value === undefined) {
- throw new Error(lazyMessage());
+ throw new Error(typeof message === 'string' ? message : message());
} else {
return value;
}
diff --git a/Timeline/ClientApp/tsconfig.json b/Timeline/ClientApp/tsconfig.json
index 437067d6..86c42495 100644
--- a/Timeline/ClientApp/tsconfig.json
+++ b/Timeline/ClientApp/tsconfig.json
@@ -17,5 +17,9 @@
"dom"
],
"strict": true
+ },
+ "angularCompilerOptions": {
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true
}
}