Fixed authentication module

This commit is contained in:
Giuseppe Digilio
2018-02-13 12:56:07 +01:00
parent 8df514f39c
commit 4488a450c0
9 changed files with 104 additions and 59 deletions

View File

@@ -1,10 +1,21 @@
import { Component } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '../app.reducer';
import { ResetAuthenticationMessagesAction } from '../core/auth/auth.actions';
@Component({ @Component({
selector: 'ds-login-page', selector: 'ds-login-page',
styleUrls: ['./login-page.component.scss'], styleUrls: ['./login-page.component.scss'],
templateUrl: './login-page.component.html' templateUrl: './login-page.component.html'
}) })
export class LoginPageComponent { export class LoginPageComponent implements OnDestroy {
constructor(private store: Store<AppState>) {}
ngOnDestroy() {
// Clear all authentication messages when leaving login page
this.store.dispatch(new ResetAuthenticationMessagesAction());
}
} }

View File

@@ -17,8 +17,9 @@ export const AuthActionTypes = {
AUTHENTICATED_SUCCESS: type('dspace/auth/AUTHENTICATED_SUCCESS'), AUTHENTICATED_SUCCESS: type('dspace/auth/AUTHENTICATED_SUCCESS'),
CHECK_AUTHENTICATION_TOKEN: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN'), CHECK_AUTHENTICATION_TOKEN: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN'),
CHECK_AUTHENTICATION_TOKEN_ERROR: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_ERROR'), CHECK_AUTHENTICATION_TOKEN_ERROR: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_ERROR'),
REDIRECT: type('dspace/auth/REDIRECT'), REDIRECT_TOKEN_EXPIRED: type('dspace/auth/REDIRECT_TOKEN_EXPIRED'),
RESET_ERROR: type('dspace/auth/RESET_ERROR'), REDIRECT_AUTHENTICATION_REQUIRED: type('dspace/auth/REDIRECT_AUTHENTICATION_REQUIRED'),
RESET_MESSAGES: type('dspace/auth/RESET_MESSAGES'),
LOG_OUT: type('dspace/auth/LOG_OUT'), LOG_OUT: type('dspace/auth/LOG_OUT'),
LOG_OUT_ERROR: type('dspace/auth/LOG_OUT_ERROR'), LOG_OUT_ERROR: type('dspace/auth/LOG_OUT_ERROR'),
LOG_OUT_SUCCESS: type('dspace/auth/LOG_OUT_SUCCESS'), LOG_OUT_SUCCESS: type('dspace/auth/LOG_OUT_SUCCESS'),
@@ -171,13 +172,27 @@ export class LogOutSuccessAction implements Action {
constructor(public payload?: any) {} constructor(public payload?: any) {}
} }
/**
* Redirect to login page when authentication is required.
* @class RedirectWhenAuthenticationIsRequiredAction
* @implements {Action}
*/
export class RedirectWhenAuthenticationIsRequiredAction implements Action {
public type: string = AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED;
payload: string;
constructor(message: string) {
this.payload = message ;
}
}
/** /**
* Redirect to login page when token is expired. * Redirect to login page when token is expired.
* @class RedirectWhenTokenExpiredAction * @class RedirectWhenTokenExpiredAction
* @implements {Action} * @implements {Action}
*/ */
export class RedirectWhenTokenExpiredAction implements Action { export class RedirectWhenTokenExpiredAction implements Action {
public type: string = AuthActionTypes.REDIRECT; public type: string = AuthActionTypes.REDIRECT_TOKEN_EXPIRED;
payload: string; payload: string;
constructor(message: string) { constructor(message: string) {
@@ -229,11 +244,11 @@ export class RegistrationSuccessAction implements Action {
/** /**
* Reset error. * Reset error.
* @class ResetAuthenticationErrorAction * @class ResetAuthenticationMessagesAction
* @implements {Action} * @implements {Action}
*/ */
export class ResetAuthenticationErrorAction implements Action { export class ResetAuthenticationMessagesAction implements Action {
public type: string = AuthActionTypes.RESET_ERROR; public type: string = AuthActionTypes.RESET_MESSAGES;
} }
/* tslint:enable:max-classes-per-file */ /* tslint:enable:max-classes-per-file */
@@ -252,6 +267,7 @@ export type AuthActions
| AuthenticationSuccessAction | AuthenticationSuccessAction
| CheckAuthenticationTokenAction | CheckAuthenticationTokenAction
| CheckAuthenticationTokenErrorAction | CheckAuthenticationTokenErrorAction
| RedirectWhenAuthenticationIsRequiredAction
| RedirectWhenTokenExpiredAction | RedirectWhenTokenExpiredAction
| RegistrationAction | RegistrationAction
| RegistrationErrorAction | RegistrationErrorAction

View File

@@ -112,11 +112,12 @@ export class AuthEffects {
@Effect({dispatch: false}) @Effect({dispatch: false})
public logOutSuccess: Observable<Action> = this.actions$ public logOutSuccess: Observable<Action> = this.actions$
.ofType(AuthActionTypes.LOG_OUT_SUCCESS) .ofType(AuthActionTypes.LOG_OUT_SUCCESS)
.do((action: LogOutSuccessAction) => this.authService.removeToken()); .do(() => this.authService.removeToken())
.do(() => this.authService.refreshPage());
@Effect({dispatch: false}) @Effect({dispatch: false})
public redirectToLogin: Observable<Action> = this.actions$ public redirectToLogin: Observable<Action> = this.actions$
.ofType(AuthActionTypes.REDIRECT) .ofType(AuthActionTypes.REDIRECT_TOKEN_EXPIRED, AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED)
.do(() => this.authService.redirectToLogin()); .do(() => this.authService.redirectToLogin());
/** /**

View File

@@ -1,20 +1,19 @@
import { Injectable, Injector } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { import {
HttpClient, HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse, HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse,
HttpErrorResponse HttpErrorResponse
} from '@angular/common/http'; } from '@angular/common/http';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import 'rxjs/add/observable/throw' import 'rxjs/add/observable/throw'
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch';
import { AppState } from '../../app.reducer';
import { AuthError } from './models/auth-error.model';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AuthStatus } from './models/auth-status.model'; import { AuthStatus } from './models/auth-status.model';
import { AuthType } from './auth-type';
import { ResourceType } from '../shared/resource-type';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
import { AppState } from '../../app.reducer';
import { RedirectWhenTokenExpiredAction } from './auth.actions'; import { RedirectWhenTokenExpiredAction } from './auth.actions';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@@ -47,7 +46,7 @@ export class AuthInterceptor implements HttpInterceptor {
authStatus.token = new AuthTokenInfo(accessToken); authStatus.token = new AuthTokenInfo(accessToken);
} else { } else {
authStatus.authenticated = false; authStatus.authenticated = false;
authStatus.error = isNotEmpty(error) ? JSON.parse(error) : null; authStatus.error = isNotEmpty(error) ? ((typeof error === 'string') ? JSON.parse(error) : error) : null;
} }
return authStatus; return authStatus;
} }

View File

@@ -1,7 +1,8 @@
// import actions // import actions
import { import {
AuthActions, AuthActionTypes, AuthenticatedSuccessAction, AuthenticationErrorAction, AuthActions, AuthActionTypes, AuthenticatedSuccessAction, AuthenticationErrorAction,
AuthenticationSuccessAction, LogOutErrorAction, RedirectWhenTokenExpiredAction AuthenticationSuccessAction, LogOutErrorAction, RedirectWhenAuthenticationIsRequiredAction,
RedirectWhenTokenExpiredAction
} from './auth.actions'; } from './auth.actions';
// import models // import models
@@ -26,7 +27,7 @@ export interface AuthState {
loading: boolean; loading: boolean;
// info message // info message
message?: string; info?: string;
// the authenticated user // the authenticated user
user?: Eperson; user?: Eperson;
@@ -54,7 +55,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
return Object.assign({}, state, { return Object.assign({}, state, {
error: undefined, error: undefined,
loading: true, loading: true,
message: undefined info: undefined
}); });
case AuthActionTypes.AUTHENTICATED_ERROR: case AuthActionTypes.AUTHENTICATED_ERROR:
@@ -71,7 +72,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
loaded: true, loaded: true,
error: undefined, error: undefined,
loading: false, loading: false,
message: undefined, info: undefined,
user: (action as AuthenticatedSuccessAction).payload.user user: (action as AuthenticatedSuccessAction).payload.user
}); });
@@ -96,18 +97,6 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
loading: false loading: false
}); });
case AuthActionTypes.REGISTRATION_SUCCESS:
return state;
case AuthActionTypes.RESET_ERROR:
return Object.assign({}, state, {
authenticated: false,
error: undefined,
loaded: false,
loading: false,
message: undefined,
});
case AuthActionTypes.LOG_OUT_ERROR: case AuthActionTypes.LOG_OUT_ERROR:
return Object.assign({}, state, { return Object.assign({}, state, {
authenticated: true, authenticated: true,
@@ -121,16 +110,17 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
error: undefined, error: undefined,
loaded: false, loaded: false,
loading: false, loading: false,
message: undefined, info: undefined,
user: undefined user: undefined
}); });
case AuthActionTypes.REDIRECT: case AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED:
case AuthActionTypes.REDIRECT_TOKEN_EXPIRED:
return Object.assign({}, state, { return Object.assign({}, state, {
authenticated: false, authenticated: false,
loaded: false, loaded: false,
loading: false, loading: false,
message: (action as RedirectWhenTokenExpiredAction).payload, info: (action as RedirectWhenTokenExpiredAction as RedirectWhenAuthenticationIsRequiredAction).payload,
user: undefined user: undefined
}); });
@@ -139,7 +129,19 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
authenticated: false, authenticated: false,
error: undefined, error: undefined,
loading: true, loading: true,
message: undefined info: undefined
});
case AuthActionTypes.REGISTRATION_SUCCESS:
return state;
case AuthActionTypes.RESET_MESSAGES:
return Object.assign({}, state, {
authenticated: false,
error: undefined,
loaded: false,
loading: false,
info: undefined,
}); });
default: default:
@@ -181,11 +183,11 @@ export const getAuthenticationError = (state: AuthState) => state.error;
/** /**
* Returns the authentication info message. * Returns the authentication info message.
* @function getAuthenticationError * @function getAuthenticationInfo
* @param {State} state * @param {State} state
* @returns {String} * @returns {String}
*/ */
export const getAuthenticationMessage = (state: AuthState) => state.message; export const getAuthenticationInfo = (state: AuthState) => state.info;
/** /**
* Returns true if request is in progress. * Returns true if request is in progress.

View File

@@ -10,12 +10,12 @@ import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model'; import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
import { isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util'; import { isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
import { CookieService } from '../../shared/services/cookie.service'; import { CookieService } from '../../shared/services/cookie.service';
import { ActivatedRoute, Router } from '@angular/router';
import { isAuthenticated } from './selectors'; import { isAuthenticated } from './selectors';
import { AppState, routerStateSelector } from '../../app.reducer'; import { AppState, routerStateSelector } from '../../app.reducer';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { ResetAuthenticationErrorAction } from './auth.actions'; import { ResetAuthenticationMessagesAction } from './auth.actions';
import { RouterReducerState } from '@ngrx/router-store'; import { RouterReducerState } from '@ngrx/router-store';
import { Router } from '@angular/router';
export const LOGIN_ROUTE = '/login'; export const LOGIN_ROUTE = '/login';
/** /**
@@ -36,8 +36,7 @@ export class AuthService {
*/ */
private _redirectUrl: string; private _redirectUrl: string;
constructor(private route: ActivatedRoute, constructor(private authRequestService: AuthRequestService,
private authRequestService: AuthRequestService,
private router: Router, private router: Router,
private storage: CookieService, private storage: CookieService,
private store: Store<AppState>) { private store: Store<AppState>) {
@@ -46,7 +45,7 @@ export class AuthService {
.subscribe((authenticated: boolean) => this._authenticated = authenticated); .subscribe((authenticated: boolean) => this._authenticated = authenticated);
// If current route is different from the one setted in authentication guard // If current route is different from the one setted in authentication guard
// and is not the login route, clear it // and is not the login route, clear redirect url and messages
this.store.select(routerStateSelector) this.store.select(routerStateSelector)
.filter((routerState: RouterReducerState) => isNotUndefined(routerState)) .filter((routerState: RouterReducerState) => isNotUndefined(routerState))
.filter((routerState: RouterReducerState) => .filter((routerState: RouterReducerState) =>
@@ -54,7 +53,7 @@ export class AuthService {
&& isNotEmpty(this._redirectUrl) && isNotEmpty(this._redirectUrl)
&& (routerState.state.url !== this._redirectUrl)) && (routerState.state.url !== this._redirectUrl))
.distinctUntilChanged() .distinctUntilChanged()
.subscribe((routerState: RouterReducerState) => { .subscribe(() => {
this._redirectUrl = ''; this._redirectUrl = '';
}) })
} }
@@ -125,7 +124,7 @@ export class AuthService {
* Clear authentication errors * Clear authentication errors
*/ */
public resetAuthenticationError(): void { public resetAuthenticationError(): void {
this.store.dispatch(new ResetAuthenticationErrorAction()); this.store.dispatch(new ResetAuthenticationMessagesAction());
} }
/** /**
@@ -221,6 +220,19 @@ export class AuthService {
} }
} }
/**
* Refresh route navigated
*/
public refreshPage() {
this.store.select(routerStateSelector)
.take(1)
.subscribe((router) => {
// TODO Chack a way to hard refresh the same route
// this.router.navigate([router.state.url], { replaceUrl: true });
this.router.navigate(['/']);
})
}
/** /**
* Get redirect url * Get redirect url
*/ */

View File

@@ -8,6 +8,8 @@ import { Store } from '@ngrx/store';
import { CoreState } from '../core.reducers'; import { CoreState } from '../core.reducers';
import { isAuthenticated, isAuthenticationLoading } from './selectors'; import { isAuthenticated, isAuthenticationLoading } from './selectors';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { RedirectWhenAuthenticationIsRequiredAction } from './auth.actions';
import { isEmpty } from '../../shared/empty.util';
/** /**
* Prevent unauthorized activating and loading of routes * Prevent unauthorized activating and loading of routes
@@ -27,7 +29,6 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
*/ */
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const url = state.url; const url = state.url;
return this.handleAuth(url); return this.handleAuth(url);
} }
@@ -50,16 +51,19 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
} }
private handleAuth(url: string): Observable<boolean> { private handleAuth(url: string): Observable<boolean> {
console.log('handleAuth', url)
// get observable // get observable
const observable = this.store.select(isAuthenticated); const observable = this.store.select(isAuthenticated);
// redirect to sign in page if user is not authenticated // redirect to sign in page if user is not authenticated
observable.subscribe((authenticated) => { observable
if (!authenticated) { // .filter(() => isEmpty(this.router.routerState.snapshot.url) || this.router.routerState.snapshot.url === url)
this.authService.redirectUrl = url; .take(1)
this.authService.redirectToLogin(); .subscribe((authenticated) => {
} console.log('handleAuth', url, this.router.routerState.snapshot.url);
if (!authenticated) {
this.authService.redirectUrl = url;
this.store.dispatch(new RedirectWhenAuthenticationIsRequiredAction('Login required'));
}
}); });
return observable; return observable;

View File

@@ -40,12 +40,12 @@ export const getAuthenticationError = createSelector(getAuthState, auth.getAuthe
/** /**
* Returns the authentication info message. * Returns the authentication info message.
* @function getAuthenticationError * @function getAuthenticationInfo
* @param {AuthState} state * @param {AuthState} state
* @param {any} props * @param {any} props
* @return {Error} * @return {string}
*/ */
export const getAuthenticationMessage = createSelector(getAuthState, auth.getAuthenticationMessage); export const getAuthenticationInfo = createSelector(getAuthState, auth.getAuthenticationInfo);
/** /**
* Returns true if the user is authenticated * Returns true if the user is authenticated

View File

@@ -10,11 +10,11 @@ import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/takeWhile'; import 'rxjs/add/operator/takeWhile';
// actions // actions
import { AuthenticateAction, ResetAuthenticationErrorAction } from '../../core/auth/auth.actions'; import { AuthenticateAction, ResetAuthenticationMessagesAction } from '../../core/auth/auth.actions';
// reducers // reducers
import { import {
getAuthenticationError, getAuthenticationMessage, getAuthenticationError, getAuthenticationInfo,
isAuthenticated, isAuthenticated,
isAuthenticationLoading, isAuthenticationLoading,
} from '../../core/auth/selectors'; } from '../../core/auth/selectors';
@@ -109,7 +109,7 @@ export class LogInComponent implements OnDestroy, OnInit {
}); });
// set error // set error
this.message = this.store.select(getAuthenticationMessage) this.message = this.store.select(getAuthenticationInfo)
.map((message) => { .map((message) => {
this.hasMessage = (isNotEmpty(message)); this.hasMessage = (isNotEmpty(message));
return message; return message;
@@ -140,7 +140,7 @@ export class LogInComponent implements OnDestroy, OnInit {
*/ */
public resetErrorOrMessage() { public resetErrorOrMessage() {
if (this.hasError || this.hasMessage) { if (this.hasError || this.hasMessage) {
this.store.dispatch(new ResetAuthenticationErrorAction()); this.store.dispatch(new ResetAuthenticationMessagesAction());
this.hasError = false; this.hasError = false;
this.hasMessage = false; this.hasMessage = false;
} }