Added CHECK_AUTHENTICATION_TOKEN_COOKIE action

This commit is contained in:
Giuseppe Digilio
2019-10-24 22:46:01 +02:00
parent 24b308ace6
commit 1896b14520
8 changed files with 131 additions and 127 deletions

View File

@@ -8,18 +8,17 @@ import { type } from '../../shared/ngrx/type';
import { EPerson } from '../eperson/models/eperson.model'; import { EPerson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
import { AuthMethodModel } from './models/auth-method.model'; import { AuthMethodModel } from './models/auth-method.model';
import { AuthStatus } from './models/auth-status.model';
export const AuthActionTypes = { export const AuthActionTypes = {
AUTHENTICATE: type('dspace/auth/AUTHENTICATE'), AUTHENTICATE: type('dspace/auth/AUTHENTICATE'),
START_SHIBBOLETH_AUTHENTICATION: type('dspace/auth/START_SHIBBOLETH_AUTHENTICATION'),
GET_JWT_AFTER_SHIBB_LOGIN: type('dspace/auth/GET_JWT_AFTER_SHIBB_LOGIN'),
AUTHENTICATE_ERROR: type('dspace/auth/AUTHENTICATE_ERROR'), AUTHENTICATE_ERROR: type('dspace/auth/AUTHENTICATE_ERROR'),
AUTHENTICATE_SUCCESS: type('dspace/auth/AUTHENTICATE_SUCCESS'), AUTHENTICATE_SUCCESS: type('dspace/auth/AUTHENTICATE_SUCCESS'),
AUTHENTICATED: type('dspace/auth/AUTHENTICATED'), AUTHENTICATED: type('dspace/auth/AUTHENTICATED'),
AUTHENTICATED_ERROR: type('dspace/auth/AUTHENTICATED_ERROR'), AUTHENTICATED_ERROR: type('dspace/auth/AUTHENTICATED_ERROR'),
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_COOKIE: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_COOKIE'),
RETRIEVE_AUTH_METHODS: type('dspace/auth/RETRIEVE_AUTH_METHODS'), RETRIEVE_AUTH_METHODS: type('dspace/auth/RETRIEVE_AUTH_METHODS'),
RETRIEVE_AUTH_METHODS_SUCCESS: type('dspace/auth/RETRIEVE_AUTH_METHODS_SUCCESS'), RETRIEVE_AUTH_METHODS_SUCCESS: type('dspace/auth/RETRIEVE_AUTH_METHODS_SUCCESS'),
RETRIEVE_AUTH_METHODS_ERROR: type('dspace/auth/RETRIEVE_AUTH_METHODS_ERROR'), RETRIEVE_AUTH_METHODS_ERROR: type('dspace/auth/RETRIEVE_AUTH_METHODS_ERROR'),
@@ -58,29 +57,6 @@ export class AuthenticateAction implements Action {
} }
} }
/**
* Authenticate.
* @class StartShibbolethAuthenticationAction
* @implements {Action}
*/
export class StartShibbolethAuthenticationAction implements Action {
public type: string = AuthActionTypes.START_SHIBBOLETH_AUTHENTICATION;
payload: AuthMethodModel;
constructor(authMethodModel: AuthMethodModel) {
this.payload = authMethodModel;
}
}
/**
* GetJWTafterShibbLoginAction.
* @class GetJWTafterShibbLoginAction
* @implements {Action}
*/
export class GetJWTafterShibbLoginAction implements Action {
public type: string = AuthActionTypes.GET_JWT_AFTER_SHIBB_LOGIN;
}
/** /**
* Checks if user is authenticated. * Checks if user is authenticated.
* @class AuthenticatedAction * @class AuthenticatedAction
@@ -166,11 +142,11 @@ export class CheckAuthenticationTokenAction implements Action {
/** /**
* Check Authentication Token Error. * Check Authentication Token Error.
* @class CheckAuthenticationTokenErrorAction * @class CheckAuthenticationTokenCookieAction
* @implements {Action} * @implements {Action}
*/ */
export class CheckAuthenticationTokenErrorAction implements Action { export class CheckAuthenticationTokenCookieAction implements Action {
public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_ERROR; public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE;
} }
/** /**
@@ -349,6 +325,12 @@ export class ResetAuthenticationMessagesAction implements Action {
*/ */
export class RetrieveAuthMethodsAction implements Action { export class RetrieveAuthMethodsAction implements Action {
public type: string = AuthActionTypes.RETRIEVE_AUTH_METHODS; public type: string = AuthActionTypes.RETRIEVE_AUTH_METHODS;
payload: AuthStatus;
constructor(authStatus: AuthStatus) {
this.payload = authStatus;
}
} }
/** /**
@@ -391,14 +373,13 @@ export class SetRedirectUrlAction implements Action {
*/ */
export type AuthActions export type AuthActions
= AuthenticateAction = AuthenticateAction
| GetJWTafterShibbLoginAction
| AuthenticatedAction | AuthenticatedAction
| AuthenticatedErrorAction | AuthenticatedErrorAction
| AuthenticatedSuccessAction | AuthenticatedSuccessAction
| AuthenticationErrorAction | AuthenticationErrorAction
| AuthenticationSuccessAction | AuthenticationSuccessAction
| CheckAuthenticationTokenAction | CheckAuthenticationTokenAction
| CheckAuthenticationTokenErrorAction | CheckAuthenticationTokenCookieAction
| RedirectWhenAuthenticationIsRequiredAction | RedirectWhenAuthenticationIsRequiredAction
| RedirectWhenTokenExpiredAction | RedirectWhenTokenExpiredAction
| RegistrationAction | RegistrationAction

View File

@@ -14,7 +14,7 @@ import {
AuthenticatedSuccessAction, AuthenticatedSuccessAction,
AuthenticationErrorAction, AuthenticationErrorAction,
AuthenticationSuccessAction, AuthenticationSuccessAction,
CheckAuthenticationTokenErrorAction, CheckAuthenticationTokenCookieAction,
LogOutErrorAction, LogOutErrorAction,
LogOutSuccessAction, LogOutSuccessAction,
RefreshTokenErrorAction, RefreshTokenErrorAction,
@@ -146,7 +146,7 @@ describe('AuthEffects', () => {
actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token}}); actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token}});
const expected = cold('--b-', {b: new CheckAuthenticationTokenErrorAction()}); const expected = cold('--b-', {b: new CheckAuthenticationTokenCookieAction()});
expect(authEffects.checkToken$).toBeObservable(expected); expect(authEffects.checkToken$).toBeObservable(expected);
}); });

View File

@@ -1,12 +1,10 @@
import { of as observableOf, Observable } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { filter, debounceTime, switchMap, take, tap, catchError, map } from 'rxjs/operators'; import { catchError, debounceTime, filter, map, switchMap, take, tap } from 'rxjs/operators';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
// import @ngrx // import @ngrx
import { Actions, Effect, ofType } from '@ngrx/effects'; import { Actions, Effect, ofType } from '@ngrx/effects';
import { Action, select, Store } from '@ngrx/store'; import { Action, select, Store } from '@ngrx/store';
// import services // import services
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
// import actions // import actions
@@ -18,7 +16,7 @@ import {
AuthenticatedSuccessAction, AuthenticatedSuccessAction,
AuthenticationErrorAction, AuthenticationErrorAction,
AuthenticationSuccessAction, AuthenticationSuccessAction,
CheckAuthenticationTokenErrorAction, CheckAuthenticationTokenCookieAction,
LogOutErrorAction, LogOutErrorAction,
LogOutSuccessAction, LogOutSuccessAction,
RefreshTokenAction, RefreshTokenAction,
@@ -28,8 +26,8 @@ import {
RegistrationErrorAction, RegistrationErrorAction,
RegistrationSuccessAction, RegistrationSuccessAction,
RetrieveAuthMethodsAction, RetrieveAuthMethodsAction,
RetrieveAuthMethodsSuccessAction, RetrieveAuthMethodsErrorAction,
GetJWTafterShibbLoginAction, StartShibbolethAuthenticationAction, RetrieveAuthMethodsErrorAction RetrieveAuthMethodsSuccessAction
} from './auth.actions'; } from './auth.actions';
import { EPerson } from '../eperson/models/eperson.model'; import { EPerson } from '../eperson/models/eperson.model';
import { AuthStatus } from './models/auth-status.model'; import { AuthStatus } from './models/auth-status.model';
@@ -37,6 +35,7 @@ import { AuthTokenInfo } from './models/auth-token-info.model';
import { AppState } from '../../app.reducer'; import { AppState } from '../../app.reducer';
import { isAuthenticated } from './selectors'; import { isAuthenticated } from './selectors';
import { StoreActionTypes } from '../../store.actions'; import { StoreActionTypes } from '../../store.actions';
import { AuthMethodModel } from './models/auth-method.model';
@Injectable() @Injectable()
export class AuthEffects { export class AuthEffects {
@@ -57,22 +56,6 @@ export class AuthEffects {
}) })
); );
/**
* Shib Login.
* @method shibLogin
*/
@Effect()
public shibbLogin$: Observable<Action> = this.actions$.pipe(
ofType(AuthActionTypes.GET_JWT_AFTER_SHIBB_LOGIN),
switchMap((action: GetJWTafterShibbLoginAction) => {
return this.authService.startShibbAuth().pipe(
take(1),
map((response: AuthStatus) => new AuthenticationSuccessAction(response.token)),
catchError((error) => observableOf(new AuthenticationErrorAction(error)))
);
})
);
@Effect() @Effect()
public authenticateSuccess$: Observable<Action> = this.actions$.pipe( public authenticateSuccess$: Observable<Action> = this.actions$.pipe(
ofType(AuthActionTypes.AUTHENTICATE_SUCCESS), ofType(AuthActionTypes.AUTHENTICATE_SUCCESS),
@@ -102,17 +85,27 @@ export class AuthEffects {
switchMap(() => { switchMap(() => {
return this.authService.hasValidAuthenticationToken().pipe( return this.authService.hasValidAuthenticationToken().pipe(
map((token: AuthTokenInfo) => new AuthenticatedAction(token)), map((token: AuthTokenInfo) => new AuthenticatedAction(token)),
catchError((error) => observableOf(new CheckAuthenticationTokenErrorAction())) catchError((error) => observableOf(new CheckAuthenticationTokenCookieAction()))
); );
}) })
); );
@Effect() @Effect()
public checkTokenError$: Observable<Action> = this.actions$ public checkTokenError$: Observable<Action> = this.actions$.pipe(
.pipe( ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE),
ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_ERROR), switchMap(() => {
map(() => new RetrieveAuthMethodsAction()) return this.authService.checkAuthenticationCookie().pipe(
) map((response: AuthStatus) => {
if (response.authenticated) {
return new AuthenticatedAction(response.token);
} else {
return new RetrieveAuthMethodsAction(response);
}
}),
catchError((error) => observableOf(new AuthenticatedErrorAction(error)))
);
})
);
@Effect() @Effect()
public createUser$: Observable<Action> = this.actions$.pipe( public createUser$: Observable<Action> = this.actions$.pipe(
@@ -199,14 +192,14 @@ export class AuthEffects {
public retrieveMethods$: Observable<Action> = this.actions$ public retrieveMethods$: Observable<Action> = this.actions$
.pipe( .pipe(
ofType(AuthActionTypes.RETRIEVE_AUTH_METHODS), ofType(AuthActionTypes.RETRIEVE_AUTH_METHODS),
switchMap(() => { switchMap((action: RetrieveAuthMethodsAction) => {
return this.authService.retrieveAuthMethods() return this.authService.retrieveAuthMethods(action.payload)
.pipe( .pipe(
map((location: any) => new RetrieveAuthMethodsSuccessAction(location)), map((authMethodModels: AuthMethodModel[]) => new RetrieveAuthMethodsSuccessAction(authMethodModels)),
catchError((error) => observableOf(new RetrieveAuthMethodsErrorAction())) catchError((error) => observableOf(new RetrieveAuthMethodsErrorAction()))
) )
}) })
) );
/** /**
* @constructor * @constructor

View File

@@ -123,6 +123,7 @@ export class AuthInterceptor implements HttpInterceptor {
} else { } else {
authMethodModels.push(new AuthMethodModel(AuthMethodType.Password)); authMethodModels.push(new AuthMethodModel(AuthMethodType.Password));
} }
authMethodModels.push(new AuthMethodModel(AuthMethodType.Shibboleth, 'location'));
return authMethodModels; return authMethodModels;
} }
@@ -176,7 +177,7 @@ export class AuthInterceptor implements HttpInterceptor {
// Get the auth header from the service. // Get the auth header from the service.
const Authorization = authService.buildAuthHeader(token); const Authorization = authService.buildAuthHeader(token);
// Clone the request to add the new header. // Clone the request to add the new header.
newReq = req.clone({headers: req.headers.set('authorization', Authorization), withCredentials: true}); newReq = req.clone({headers: req.headers.set('authorization', Authorization)});
} else { } else {
newReq = req.clone({withCredentials: true}); newReq = req.clone({withCredentials: true});
} }

View File

@@ -8,7 +8,7 @@ import {
AuthenticationErrorAction, AuthenticationErrorAction,
AuthenticationSuccessAction, AuthenticationSuccessAction,
CheckAuthenticationTokenAction, CheckAuthenticationTokenAction,
CheckAuthenticationTokenErrorAction, CheckAuthenticationTokenCookieAction,
LogOutAction, LogOutAction,
LogOutErrorAction, LogOutErrorAction,
LogOutSuccessAction, LogOutSuccessAction,
@@ -18,10 +18,16 @@ import {
RefreshTokenErrorAction, RefreshTokenErrorAction,
RefreshTokenSuccessAction, RefreshTokenSuccessAction,
ResetAuthenticationMessagesAction, ResetAuthenticationMessagesAction,
RetrieveAuthMethodsAction,
RetrieveAuthMethodsErrorAction,
RetrieveAuthMethodsSuccessAction,
SetRedirectUrlAction SetRedirectUrlAction
} from './auth.actions'; } from './auth.actions';
import { AuthTokenInfo } from './models/auth-token-info.model'; import { AuthTokenInfo } from './models/auth-token-info.model';
import { EPersonMock } from '../../shared/testing/eperson-mock'; import { EPersonMock } from '../../shared/testing/eperson-mock';
import { AuthStatus } from './models/auth-status.model';
import { AuthMethodModel } from './models/auth-method.model';
import { AuthMethodType } from '../../shared/log-in/methods/authMethods-type';
describe('authReducer', () => { describe('authReducer', () => {
@@ -158,18 +164,18 @@ describe('authReducer', () => {
expect(newState).toEqual(state); expect(newState).toEqual(state);
}); });
it('should properly set the state, in response to a CHECK_AUTHENTICATION_TOKEN_ERROR action', () => { it('should properly set the state, in response to a CHECK_AUTHENTICATION_TOKEN_COOKIE action', () => {
initialState = { initialState = {
authenticated: false, authenticated: false,
loaded: false, loaded: false,
loading: true, loading: true,
}; };
const action = new CheckAuthenticationTokenErrorAction(); const action = new CheckAuthenticationTokenCookieAction();
const newState = authReducer(initialState, action); const newState = authReducer(initialState, action);
state = { state = {
authenticated: false, authenticated: false,
loaded: false, loaded: false,
loading: false, loading: true,
}; };
expect(newState).toEqual(state); expect(newState).toEqual(state);
}); });
@@ -408,4 +414,63 @@ describe('authReducer', () => {
}; };
expect(newState).toEqual(state); expect(newState).toEqual(state);
}); });
it('should properly set the state, in response to a RETRIEVE_AUTH_METHODS action', () => {
initialState = {
authenticated: false,
loaded: false,
loading: false,
authMethods: []
};
const action = new RetrieveAuthMethodsAction(new AuthStatus());
const newState = authReducer(initialState, action);
state = {
authenticated: false,
loaded: false,
loading: true,
authMethods: []
};
expect(newState).toEqual(state);
});
it('should properly set the state, in response to a RETRIEVE_AUTH_METHODS_SUCCESS action', () => {
initialState = {
authenticated: false,
loaded: false,
loading: true,
authMethods: []
};
const authMethods = [
new AuthMethodModel(AuthMethodType.Password),
new AuthMethodModel(AuthMethodType.Shibboleth, 'location')
];
const action = new RetrieveAuthMethodsSuccessAction(authMethods);
const newState = authReducer(initialState, action);
state = {
authenticated: false,
loaded: false,
loading: false,
authMethods: authMethods
};
expect(newState).toEqual(state);
});
it('should properly set the state, in response to a RETRIEVE_AUTH_METHODS_ERROR action', () => {
initialState = {
authenticated: false,
loaded: false,
loading: true,
authMethods: []
};
const action = new RetrieveAuthMethodsErrorAction();
const newState = authReducer(initialState, action);
state = {
authenticated: false,
loaded: false,
loading: false,
authMethods: []
};
expect(newState).toEqual(state);
});
}); });

View File

@@ -62,7 +62,7 @@ const initialState: AuthState = {
authenticated: false, authenticated: false,
loaded: false, loaded: false,
loading: false, loading: false,
authMethods: new Array<AuthMethodModel>() authMethods: []
}; };
/** /**
@@ -81,21 +81,9 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
info: undefined info: undefined
}); });
case AuthActionTypes.START_SHIBBOLETH_AUTHENTICATION:
return Object.assign({}, state, {
error: undefined,
loading: true,
info: undefined
});
case AuthActionTypes.GET_JWT_AFTER_SHIBB_LOGIN:
return Object.assign({}, state, {
error: undefined,
loading: true,
info: undefined
});
case AuthActionTypes.AUTHENTICATED: case AuthActionTypes.AUTHENTICATED:
case AuthActionTypes.CHECK_AUTHENTICATION_TOKEN:
case AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE:
return Object.assign({}, state, { return Object.assign({}, state, {
loading: true loading: true
}); });
@@ -129,21 +117,10 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
loading: false loading: false
}); });
case AuthActionTypes.AUTHENTICATED:
case AuthActionTypes.AUTHENTICATE_SUCCESS: case AuthActionTypes.AUTHENTICATE_SUCCESS:
case AuthActionTypes.LOG_OUT: case AuthActionTypes.LOG_OUT:
return state; return state;
case AuthActionTypes.CHECK_AUTHENTICATION_TOKEN:
return Object.assign({}, state, {
loading: true
});
case AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_ERROR:
return Object.assign({}, state, {
loading: false
});
case AuthActionTypes.LOG_OUT_ERROR: case AuthActionTypes.LOG_OUT_ERROR:
return Object.assign({}, state, { return Object.assign({}, state, {
authenticated: true, authenticated: true,

View File

@@ -18,7 +18,11 @@ import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/emp
import { CookieService } from '../services/cookie.service'; import { CookieService } from '../services/cookie.service';
import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors'; import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors';
import { AppState, routerStateSelector } from '../../app.reducer'; import { AppState, routerStateSelector } from '../../app.reducer';
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions'; import {
CheckAuthenticationTokenAction,
ResetAuthenticationMessagesAction,
SetRedirectUrlAction
} from './auth.actions';
import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util'; import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -115,17 +119,11 @@ export class AuthService {
} }
public startShibbAuth(): Observable<AuthStatus> { /**
* Checks if token is present into the request cookie
return this.authRequestService.postToEndpoint('login').pipe( */
map((status: AuthStatus) => { public checkAuthenticationCookie(): Observable<AuthStatus> {
if (status.authenticated) { return this.authRequestService.postToEndpoint('login');
return status;
} else {
throw(new Error('Shibboleth login failed'));
}
}))
} }
/** /**
@@ -162,7 +160,7 @@ export class AuthService {
* Checks if token is present into browser storage and is valid. (NB Check is done only on SSR) * Checks if token is present into browser storage and is valid. (NB Check is done only on SSR)
*/ */
public checkAuthenticationToken() { public checkAuthenticationToken() {
return this.store.dispatch(new CheckAuthenticationTokenAction());
} }
/** /**
@@ -215,16 +213,12 @@ export class AuthService {
* Retrieve authentication methods available * Retrieve authentication methods available
* @returns {User} * @returns {User}
*/ */
public retrieveAuthMethods(): Observable<AuthMethodModel[]> { public retrieveAuthMethods(status: AuthStatus): Observable<AuthMethodModel[]> {
return this.authRequestService.postToEndpoint('login', {}).pipe( let authMethods: AuthMethodModel[] = [];
map((status: AuthStatus) => {
let authMethods: AuthMethodModel[];
if (isNotEmpty(status.authMethods)) { if (isNotEmpty(status.authMethods)) {
authMethods = status.authMethods; authMethods = status.authMethods;
} }
return authMethods; return observableOf(authMethods);
})
)
} }
/** /**

View File

@@ -45,13 +45,6 @@ export class ServerAuthService extends AuthService {
})); }));
} }
/**
* Checks if token is present into browser storage and is valid. (NB Check is done only on SSR)
*/
public checkAuthenticationToken() {
this.store.dispatch(new CheckAuthenticationTokenAction());
}
/** /**
* Redirect to the route navigated before the login * Redirect to the route navigated before the login
*/ */