Added check for external auth and page refresh

This commit is contained in:
Michael Spalti
2023-04-05 19:44:46 -07:00
committed by Michael W Spalti
parent ade9533f4c
commit 290a89909e
9 changed files with 104 additions and 4 deletions

View File

@@ -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

View File

@@ -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();
}); });
}); });

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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, {

View File

@@ -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}

View File

@@ -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

View File

@@ -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);

View File

@@ -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();
}
);
}
} }