mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Added check for external auth and page refresh
This commit is contained in:

committed by
Michael W Spalti

parent
ade9533f4c
commit
290a89909e
@@ -17,6 +17,7 @@ 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_COOKIE: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_COOKIE'),
|
CHECK_AUTHENTICATION_TOKEN_COOKIE: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_COOKIE'),
|
||||||
|
SET_AUTH_COOKIE_STATUS: type('dspace/auth/SET_AUTH_COOKIE_STATUS'),
|
||||||
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'),
|
||||||
@@ -150,6 +151,19 @@ export class CheckAuthenticationTokenCookieAction implements Action {
|
|||||||
public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE;
|
public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication cookie status to flag an external authentication response.
|
||||||
|
*/
|
||||||
|
export class SetAuthCookieStatus implements Action {
|
||||||
|
public type: string = AuthActionTypes.SET_AUTH_COOKIE_STATUS;
|
||||||
|
|
||||||
|
payload = false;
|
||||||
|
|
||||||
|
constructor(exists: boolean) {
|
||||||
|
this.payload = exists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign out.
|
* Sign out.
|
||||||
* @class LogOutAction
|
* @class LogOutAction
|
||||||
@@ -425,6 +439,7 @@ export type AuthActions
|
|||||||
| AuthenticationSuccessAction
|
| AuthenticationSuccessAction
|
||||||
| CheckAuthenticationTokenAction
|
| CheckAuthenticationTokenAction
|
||||||
| CheckAuthenticationTokenCookieAction
|
| CheckAuthenticationTokenCookieAction
|
||||||
|
| SetAuthCookieStatus
|
||||||
| RedirectWhenAuthenticationIsRequiredAction
|
| RedirectWhenAuthenticationIsRequiredAction
|
||||||
| RedirectWhenTokenExpiredAction
|
| RedirectWhenTokenExpiredAction
|
||||||
| AddAuthenticationMessageAction
|
| AddAuthenticationMessageAction
|
||||||
|
@@ -214,12 +214,14 @@ describe('AuthEffects', () => {
|
|||||||
authenticated: true
|
authenticated: true
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
spyOn((authEffects as any).authService, 'setExternalAuthStatus');
|
||||||
actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE } });
|
actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE } });
|
||||||
|
|
||||||
const expected = cold('--b-', { b: new RetrieveTokenAction() });
|
const expected = cold('--b-', { b: new RetrieveTokenAction() });
|
||||||
|
|
||||||
expect(authEffects.checkTokenCookie$).toBeObservable(expected);
|
expect(authEffects.checkTokenCookie$).toBeObservable(expected);
|
||||||
authEffects.checkTokenCookie$.subscribe(() => {
|
authEffects.checkTokenCookie$.subscribe(() => {
|
||||||
|
expect(authServiceStub.setExternalAuthStatus).toHaveBeenCalledWith(true);
|
||||||
expect((authEffects as any).authorizationsService.invalidateAuthorizationsRequestCache).toHaveBeenCalled();
|
expect((authEffects as any).authorizationsService.invalidateAuthorizationsRequestCache).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -153,6 +153,7 @@ export class AuthEffects {
|
|||||||
return this.authService.checkAuthenticationCookie().pipe(
|
return this.authService.checkAuthenticationCookie().pipe(
|
||||||
map((response: AuthStatus) => {
|
map((response: AuthStatus) => {
|
||||||
if (response.authenticated) {
|
if (response.authenticated) {
|
||||||
|
this.authService.setExternalAuthStatus(true);
|
||||||
this.authorizationsService.invalidateAuthorizationsRequestCache();
|
this.authorizationsService.invalidateAuthorizationsRequestCache();
|
||||||
return new RetrieveTokenAction();
|
return new RetrieveTokenAction();
|
||||||
} else {
|
} else {
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
AuthenticationErrorAction,
|
AuthenticationErrorAction,
|
||||||
AuthenticationSuccessAction,
|
AuthenticationSuccessAction,
|
||||||
CheckAuthenticationTokenAction,
|
CheckAuthenticationTokenAction,
|
||||||
|
SetAuthCookieStatus,
|
||||||
CheckAuthenticationTokenCookieAction,
|
CheckAuthenticationTokenCookieAction,
|
||||||
LogOutAction,
|
LogOutAction,
|
||||||
LogOutErrorAction,
|
LogOutErrorAction,
|
||||||
@@ -219,6 +220,28 @@ describe('authReducer', () => {
|
|||||||
expect(newState).toEqual(state);
|
expect(newState).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the authentication cookie status in response to a SET_AUTH_COOKIE_STATUS action', () => {
|
||||||
|
initialState = {
|
||||||
|
authenticated: true,
|
||||||
|
loaded: false,
|
||||||
|
blocking: false,
|
||||||
|
loading: true,
|
||||||
|
externalAuth: false,
|
||||||
|
idle: false
|
||||||
|
};
|
||||||
|
const action = new SetAuthCookieStatus(true);
|
||||||
|
const newState = authReducer(initialState, action);
|
||||||
|
state = {
|
||||||
|
authenticated: true,
|
||||||
|
loaded: false,
|
||||||
|
blocking: false,
|
||||||
|
loading: true,
|
||||||
|
externalAuth: true,
|
||||||
|
idle: false
|
||||||
|
};
|
||||||
|
expect(newState).toEqual(state);
|
||||||
|
});
|
||||||
|
|
||||||
it('should properly set the state, in response to a LOG_OUT action', () => {
|
it('should properly set the state, in response to a LOG_OUT action', () => {
|
||||||
initialState = {
|
initialState = {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
RedirectWhenTokenExpiredAction,
|
RedirectWhenTokenExpiredAction,
|
||||||
RefreshTokenSuccessAction,
|
RefreshTokenSuccessAction,
|
||||||
RetrieveAuthenticatedEpersonSuccessAction,
|
RetrieveAuthenticatedEpersonSuccessAction,
|
||||||
RetrieveAuthMethodsSuccessAction,
|
RetrieveAuthMethodsSuccessAction, SetAuthCookieStatus,
|
||||||
SetRedirectUrlAction
|
SetRedirectUrlAction
|
||||||
} from './auth.actions';
|
} from './auth.actions';
|
||||||
// import models
|
// import models
|
||||||
@@ -59,6 +59,8 @@ export interface AuthState {
|
|||||||
// all authentication Methods enabled at the backend
|
// all authentication Methods enabled at the backend
|
||||||
authMethods?: AuthMethod[];
|
authMethods?: AuthMethod[];
|
||||||
|
|
||||||
|
externalAuth?: boolean,
|
||||||
|
|
||||||
// true when the current user is idle
|
// true when the current user is idle
|
||||||
idle: boolean;
|
idle: boolean;
|
||||||
|
|
||||||
@@ -73,6 +75,7 @@ const initialState: AuthState = {
|
|||||||
blocking: true,
|
blocking: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
authMethods: [],
|
authMethods: [],
|
||||||
|
externalAuth: false,
|
||||||
idle: false
|
idle: false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,6 +107,11 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.SET_AUTH_COOKIE_STATUS:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
externalAuth: (action as SetAuthCookieStatus).payload
|
||||||
|
});
|
||||||
|
|
||||||
case AuthActionTypes.AUTHENTICATED_ERROR:
|
case AuthActionTypes.AUTHENTICATED_ERROR:
|
||||||
case AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_ERROR:
|
case AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_ERROR:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
|
@@ -25,7 +25,7 @@ import {
|
|||||||
import { CookieService } from '../services/cookie.service';
|
import { CookieService } from '../services/cookie.service';
|
||||||
import {
|
import {
|
||||||
getAuthenticatedUserId,
|
getAuthenticatedUserId,
|
||||||
getAuthenticationToken,
|
getAuthenticationToken, getExternalAuthCookieStatus,
|
||||||
getRedirectUrl,
|
getRedirectUrl,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isAuthenticatedLoaded,
|
isAuthenticatedLoaded,
|
||||||
@@ -36,7 +36,7 @@ import { AppState } from '../../app.reducer';
|
|||||||
import {
|
import {
|
||||||
CheckAuthenticationTokenAction,
|
CheckAuthenticationTokenAction,
|
||||||
RefreshTokenAction,
|
RefreshTokenAction,
|
||||||
ResetAuthenticationMessagesAction,
|
ResetAuthenticationMessagesAction, SetAuthCookieStatus,
|
||||||
SetRedirectUrlAction,
|
SetRedirectUrlAction,
|
||||||
SetUserAsIdleAction,
|
SetUserAsIdleAction,
|
||||||
UnsetUserAsIdleAction
|
UnsetUserAsIdleAction
|
||||||
@@ -156,6 +156,15 @@ export class AuthService {
|
|||||||
return this.store.pipe(select(isAuthenticatedLoaded));
|
return this.store.pipe(select(isAuthenticatedLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setExternalAuthStatus(external: boolean) {
|
||||||
|
this.store.dispatch(new SetAuthCookieStatus(external));
|
||||||
|
}
|
||||||
|
|
||||||
|
public isExternalAuthentication(): Observable<boolean> {
|
||||||
|
return this.store.pipe(
|
||||||
|
select(getExternalAuthCookieStatus));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the href link to authenticated user
|
* Returns the href link to authenticated user
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
@@ -116,6 +116,8 @@ const _getRedirectUrl = (state: AuthState) => state.redirectUrl;
|
|||||||
|
|
||||||
const _getAuthenticationMethods = (state: AuthState) => state.authMethods;
|
const _getAuthenticationMethods = (state: AuthState) => state.authMethods;
|
||||||
|
|
||||||
|
const _getExternalAuthCookieStatus = (state: AuthState) => state.externalAuth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the user is idle.
|
* Returns true if the user is idle.
|
||||||
* @function _isIdle
|
* @function _isIdle
|
||||||
@@ -178,6 +180,16 @@ export const isAuthenticated = createSelector(getAuthState, _isAuthenticated);
|
|||||||
*/
|
*/
|
||||||
export const isAuthenticatedLoaded = createSelector(getAuthState, _isAuthenticatedLoaded);
|
export const isAuthenticatedLoaded = createSelector(getAuthState, _isAuthenticatedLoaded);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authentication cookie status. Expect to be ture when external authentication
|
||||||
|
* is used.
|
||||||
|
* @function getExternalAuthCookieStatus
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const getExternalAuthCookieStatus = createSelector(getAuthState, _getExternalAuthCookieStatus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the authentication request is loading.
|
* Returns true if the authentication request is loading.
|
||||||
* @function isAuthenticationLoading
|
* @function isAuthenticationLoading
|
||||||
|
@@ -122,6 +122,13 @@ export class AuthServiceStub {
|
|||||||
checkAuthenticationCookie() {
|
checkAuthenticationCookie() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setExternalAuthStatus(externalCookie: boolean) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isExternalAuthentication(): Observable<boolean> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
retrieveAuthMethodsFromAuthStatus(status: AuthStatus) {
|
retrieveAuthMethodsFromAuthStatus(status: AuthStatus) {
|
||||||
return observableOf(authMethodsMock);
|
return observableOf(authMethodsMock);
|
||||||
|
@@ -26,10 +26,11 @@ import { AuthService } from '../../app/core/auth/auth.service';
|
|||||||
import { ThemeService } from '../../app/shared/theme-support/theme.service';
|
import { ThemeService } from '../../app/shared/theme-support/theme.service';
|
||||||
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
|
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
|
||||||
import { coreSelector } from '../../app/core/core.selectors';
|
import { coreSelector } from '../../app/core/core.selectors';
|
||||||
import { find, map } from 'rxjs/operators';
|
import { filter, find, map, switchMap } from 'rxjs/operators';
|
||||||
import { isNotEmpty } from '../../app/shared/empty.util';
|
import { isNotEmpty } from '../../app/shared/empty.util';
|
||||||
import { logStartupMessage } from '../../../startup-message';
|
import { logStartupMessage } from '../../../startup-message';
|
||||||
import { MenuService } from '../../app/shared/menu/menu.service';
|
import { MenuService } from '../../app/shared/menu/menu.service';
|
||||||
|
import { RootDataService } from '../../app/core/data/root-data.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs client-side initialization.
|
* Performs client-side initialization.
|
||||||
@@ -51,6 +52,7 @@ export class BrowserInitService extends InitService {
|
|||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected themeService: ThemeService,
|
protected themeService: ThemeService,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
|
private rootDatatService: RootDataService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
store,
|
store,
|
||||||
@@ -80,6 +82,7 @@ export class BrowserInitService extends InitService {
|
|||||||
return async () => {
|
return async () => {
|
||||||
await this.loadAppState();
|
await this.loadAppState();
|
||||||
this.checkAuthenticationToken();
|
this.checkAuthenticationToken();
|
||||||
|
this.externalAuthCheck();
|
||||||
this.initCorrelationId();
|
this.initCorrelationId();
|
||||||
|
|
||||||
this.checkEnvironment();
|
this.checkEnvironment();
|
||||||
@@ -134,4 +137,24 @@ export class BrowserInitService extends InitService {
|
|||||||
protected initGoogleAnalytics() {
|
protected initGoogleAnalytics() {
|
||||||
this.googleAnalyticsService.addTrackingIdToPage();
|
this.googleAnalyticsService.addTrackingIdToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When authenticated during the external authentication flow invalidate
|
||||||
|
* the cache so the app is rehydrated with fresh data.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private externalAuthCheck() {
|
||||||
|
|
||||||
|
this.authenticationReady$().pipe(
|
||||||
|
switchMap(() => this.authService.isExternalAuthentication().pipe(
|
||||||
|
filter((externalAuth: boolean) => externalAuth)
|
||||||
|
))
|
||||||
|
).subscribe(() => {
|
||||||
|
this.authService.setExternalAuthStatus(false);
|
||||||
|
this.rootDatatService.invalidateRootCache();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user