diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index bc0fa787c2..5e02a6cf1b 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -1,5 +1,6 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
+import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
@@ -95,46 +96,49 @@ export function getInfoModulePath() {
@NgModule({
imports: [
RouterModule.forRoot([
- { path: '', redirectTo: '/home', pathMatch: 'full' },
- { path: 'reload/:rnd', component: PageNotFoundComponent, pathMatch: 'full', canActivate: [ReloadGuard] },
- { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule', data: { showBreadcrumbs: false } },
- { path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule' },
- { path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
- { path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
- { path: REGISTER_PATH, loadChildren: './register-page/register-page.module#RegisterPageModule' },
- { path: FORGOT_PASSWORD_PATH, loadChildren: './forgot-password/forgot-password.module#ForgotPasswordModule' },
- { path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' },
- { path: COLLECTION_MODULE_PATH, loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
- { path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule' },
- { path: BITSTREAM_MODULE_PATH, loadChildren: './+bitstream-page/bitstream-page.module#BitstreamPageModule' },
- {
- path: 'mydspace',
- loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule',
- canActivate: [AuthenticatedGuard, EndUserAgreementGuard]
- },
- { path: 'search', loadChildren: './+search-page/search-page-routing.module#SearchPageRoutingModule' },
- { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule'},
- { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard, EndUserAgreementGuard] },
- { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
- { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
- { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
- {
- path: 'workspaceitems',
- loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule'
- },
- {
- path: WORKFLOW_ITEM_MODULE_PATH,
- loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule'
- },
- {
- path: PROFILE_MODULE_PATH,
- loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard, EndUserAgreementGuard]
- },
- { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementGuard] },
- { path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' },
- { path: UNAUTHORIZED_PATH, component: UnauthorizedComponent },
- { path: '**', pathMatch: 'full', component: PageNotFoundComponent },
- ],
+ { path: '', canActivate: [AuthBlockingGuard],
+ children: [
+ { path: '', redirectTo: '/home', pathMatch: 'full' },
+ { path: 'reload/:rnd', component: PageNotFoundComponent, pathMatch: 'full', canActivate: [ReloadGuard] },
+ { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule', data: { showBreadcrumbs: false } },
+ { path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule' },
+ { path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
+ { path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
+ { path: REGISTER_PATH, loadChildren: './register-page/register-page.module#RegisterPageModule' },
+ { path: FORGOT_PASSWORD_PATH, loadChildren: './forgot-password/forgot-password.module#ForgotPasswordModule' },
+ { path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' },
+ { path: COLLECTION_MODULE_PATH, loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
+ { path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule' },
+ { path: BITSTREAM_MODULE_PATH, loadChildren: './+bitstream-page/bitstream-page.module#BitstreamPageModule' },
+ {
+ path: 'mydspace',
+ loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule',
+ canActivate: [AuthenticatedGuard, EndUserAgreementGuard]
+ },
+ { path: 'search', loadChildren: './+search-page/search-page-routing.module#SearchPageRoutingModule' },
+ { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule'},
+ { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard, EndUserAgreementGuard] },
+ { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
+ { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
+ { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
+ {
+ path: 'workspaceitems',
+ loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule'
+ },
+ {
+ path: WORKFLOW_ITEM_MODULE_PATH,
+ loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule'
+ },
+ {
+ path: PROFILE_MODULE_PATH,
+ loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard, EndUserAgreementGuard]
+ },
+ { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementGuard] },
+ { path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' },
+ { path: UNAUTHORIZED_PATH, component: UnauthorizedComponent },
+ { path: '**', pathMatch: 'full', component: PageNotFoundComponent },
+ ]}
+ ],
{
onSameUrlNavigation: 'reload',
})
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 6d6f89ea35..b628424cd4 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,7 +1,4 @@
-
-
-
-
+
+
+
+
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index d5488be610..3f03cd8dbe 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { delay, filter, map, take, distinctUntilChanged } from 'rxjs/operators';
+import { delay, map, distinctUntilChanged } from 'rxjs/operators';
import {
AfterViewInit,
ChangeDetectionStrategy,
@@ -19,7 +19,7 @@ import { MetadataService } from './core/metadata/metadata.service';
import { HostWindowResizeAction } from './shared/host-window.actions';
import { HostWindowState } from './shared/search/host-window.reducer';
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
-import { isAuthenticated, isAuthenticationLoading } from './core/auth/selectors';
+import { isAuthenticationBlocking, isAuthenticationLoading } from './core/auth/selectors';
import { AuthService } from './core/auth/auth.service';
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
import { MenuService } from './shared/menu/menu.service';
@@ -55,7 +55,7 @@ export class AppComponent implements OnInit, AfterViewInit {
/**
* Whether or not the authenticated has finished loading
*/
- hasAuthFinishedLoading$: Observable
;
+ isAuthBlocking$: Observable;
constructor(
@Inject(NativeWindowService) private _window: NativeWindowRef,
@@ -94,8 +94,8 @@ export class AppComponent implements OnInit, AfterViewInit {
}
ngOnInit() {
- this.hasAuthFinishedLoading$ = this.store.pipe(select(isAuthenticationLoading)).pipe(
- map((isLoading: boolean) => isLoading === false),
+ this.isAuthBlocking$ = this.store.pipe(select(isAuthenticationBlocking)).pipe(
+ map((isBlocking: boolean) => isBlocking === false),
distinctUntilChanged()
);
const env: string = environment.production ? 'Production' : 'Development';
@@ -103,11 +103,6 @@ export class AppComponent implements OnInit, AfterViewInit {
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
- // Whether is not authenticathed try to retrieve a possible stored auth token
- this.store.pipe(select(isAuthenticated),
- take(1),
- filter((authenticated) => !authenticated)
- ).subscribe((authenticated) => this.authService.checkAuthenticationToken());
this.sidebarVisible = this.menuService.isMenuVisible(MenuID.ADMIN);
this.collapsedSidebarWidth = this.cssService.getVariable('collapsedSidebarWidth');
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 33454ed6c5..f1cdd5f2e5 100755
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,11 +1,11 @@
import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
-import { NgModule } from '@angular/core';
+import { APP_INITIALIZER, NgModule } from '@angular/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EffectsModule } from '@ngrx/effects';
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
-import { MetaReducer, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store';
+import { MetaReducer, Store, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { DYNAMIC_MATCHER_PROVIDERS } from '@ng-dynamic-forms/core';
import { TranslateModule } from '@ngx-translate/core';
@@ -21,6 +21,7 @@ import { AppComponent } from './app.component';
import { appEffects } from './app.effects';
import { appMetaReducers, debugMetaReducers } from './app.metareducers';
import { appReducers, AppState, storeModuleConfig } from './app.reducer';
+import { CheckAuthenticationTokenAction } from './core/auth/auth.actions';
import { CoreModule } from './core/core.module';
import { ClientCookieService } from './core/services/client-cookie.service';
@@ -91,6 +92,15 @@ const PROVIDERS = [
useClass: DSpaceRouterStateSerializer
},
ClientCookieService,
+ // Check the authentication token when the app initializes
+ {
+ provide: APP_INITIALIZER,
+ useFactory: (store: Store,) => {
+ return () => store.dispatch(new CheckAuthenticationTokenAction());
+ },
+ deps: [ Store ],
+ multi: true
+ },
...DYNAMIC_MATCHER_PROVIDERS,
];
diff --git a/src/app/core/auth/auth-blocking.guard.spec.ts b/src/app/core/auth/auth-blocking.guard.spec.ts
new file mode 100644
index 0000000000..2a89b01a85
--- /dev/null
+++ b/src/app/core/auth/auth-blocking.guard.spec.ts
@@ -0,0 +1,62 @@
+import { Store } from '@ngrx/store';
+import * as ngrx from '@ngrx/store';
+import { cold, getTestScheduler, initTestScheduler, resetTestScheduler } from 'jasmine-marbles/es6';
+import { of as observableOf } from 'rxjs';
+import { AppState } from '../../app.reducer';
+import { AuthBlockingGuard } from './auth-blocking.guard';
+
+describe('AuthBlockingGuard', () => {
+ let guard: AuthBlockingGuard;
+ beforeEach(() => {
+ guard = new AuthBlockingGuard(new Store(undefined, undefined, undefined));
+ initTestScheduler();
+ });
+
+ afterEach(() => {
+ getTestScheduler().flush();
+ resetTestScheduler();
+ });
+
+ describe(`canActivate`, () => {
+
+ describe(`when authState.loading is undefined`, () => {
+ beforeEach(() => {
+ spyOnProperty(ngrx, 'select').and.callFake(() => {
+ return () => {
+ return () => observableOf(undefined);
+ };
+ })
+ });
+ it(`should not emit anything`, () => {
+ expect(guard.canActivate()).toBeObservable(cold('|'));
+ });
+ });
+
+ describe(`when authState.loading is true`, () => {
+ beforeEach(() => {
+ spyOnProperty(ngrx, 'select').and.callFake(() => {
+ return () => {
+ return () => observableOf(true);
+ };
+ })
+ });
+ it(`should not emit anything`, () => {
+ expect(guard.canActivate()).toBeObservable(cold('|'));
+ });
+ });
+
+ describe(`when authState.loading is false`, () => {
+ beforeEach(() => {
+ spyOnProperty(ngrx, 'select').and.callFake(() => {
+ return () => {
+ return () => observableOf(false);
+ };
+ })
+ });
+ it(`should succeed`, () => {
+ expect(guard.canActivate()).toBeObservable(cold('(a|)', { a: true }));
+ });
+ });
+ });
+
+});
diff --git a/src/app/core/auth/auth-blocking.guard.ts b/src/app/core/auth/auth-blocking.guard.ts
new file mode 100644
index 0000000000..7488c0c508
--- /dev/null
+++ b/src/app/core/auth/auth-blocking.guard.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { CanActivate } from '@angular/router';
+import { select, Store } from '@ngrx/store';
+import { Observable } from 'rxjs';
+import { distinctUntilChanged, filter, map, take, tap } from 'rxjs/operators';
+import { AppState } from '../../app.reducer';
+import { isAuthenticationBlocking } from './selectors';
+
+/**
+ * A guard that blocks the loading of any
+ * route until the authentication status has loaded.
+ * To ensure all rest requests get the correct auth header.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class AuthBlockingGuard implements CanActivate {
+
+ constructor(private store: Store) {
+ }
+
+ canActivate(): Observable {
+ return this.store.pipe(select(isAuthenticationBlocking)).pipe(
+ map((isBlocking: boolean) => isBlocking === false),
+ distinctUntilChanged(),
+ filter((finished: boolean) => finished === true),
+ take(1),
+ );
+ }
+
+}
diff --git a/src/app/core/auth/auth.reducer.spec.ts b/src/app/core/auth/auth.reducer.spec.ts
index cf934a7f47..649002903c 100644
--- a/src/app/core/auth/auth.reducer.spec.ts
+++ b/src/app/core/auth/auth.reducer.spec.ts
@@ -42,6 +42,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: true,
loading: false,
};
const action = new AuthenticateAction('user', 'password');
@@ -49,6 +50,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: true,
error: undefined,
loading: true,
info: undefined
@@ -62,6 +64,7 @@ describe('authReducer', () => {
authenticated: false,
loaded: false,
error: undefined,
+ blocking: true,
loading: true,
info: undefined
};
@@ -76,6 +79,7 @@ describe('authReducer', () => {
authenticated: false,
loaded: false,
error: undefined,
+ blocking: true,
loading: true,
info: undefined
};
@@ -84,6 +88,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
info: undefined,
authToken: undefined,
@@ -96,6 +101,7 @@ describe('authReducer', () => {
it('should properly set the state, in response to a AUTHENTICATED action', () => {
initialState = {
authenticated: false,
+ blocking: false,
loaded: false,
error: undefined,
loading: true,
@@ -103,8 +109,15 @@ describe('authReducer', () => {
};
const action = new AuthenticatedAction(mockTokenInfo);
const newState = authReducer(initialState, action);
-
- expect(newState).toEqual(initialState);
+ state = {
+ authenticated: false,
+ blocking: true,
+ loaded: false,
+ error: undefined,
+ loading: true,
+ info: undefined
+ };
+ expect(newState).toEqual(state);
});
it('should properly set the state, in response to a AUTHENTICATED_SUCCESS action', () => {
@@ -112,6 +125,7 @@ describe('authReducer', () => {
authenticated: false,
loaded: false,
error: undefined,
+ blocking: true,
loading: true,
info: undefined
};
@@ -122,6 +136,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: false,
error: undefined,
+ blocking: true,
loading: true,
info: undefined
};
@@ -133,6 +148,7 @@ describe('authReducer', () => {
authenticated: false,
loaded: false,
error: undefined,
+ blocking: true,
loading: true,
info: undefined
};
@@ -143,6 +159,7 @@ describe('authReducer', () => {
authToken: undefined,
error: 'Test error message',
loaded: true,
+ blocking: false,
loading: false,
info: undefined
};
@@ -153,6 +170,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
};
const action = new CheckAuthenticationTokenAction();
@@ -160,6 +178,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: true,
loading: true,
};
expect(newState).toEqual(state);
@@ -169,6 +188,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: true,
};
const action = new CheckAuthenticationTokenCookieAction();
@@ -176,6 +196,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: true,
loading: true,
};
expect(newState).toEqual(state);
@@ -187,6 +208,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id
@@ -204,6 +226,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id
@@ -216,6 +239,7 @@ describe('authReducer', () => {
authToken: undefined,
error: undefined,
loaded: false,
+ blocking: false,
loading: false,
info: undefined,
refreshing: false,
@@ -230,6 +254,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id
@@ -242,6 +267,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: 'Test error message',
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id
@@ -255,6 +281,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: false,
error: undefined,
+ blocking: true,
loading: true,
info: undefined
};
@@ -265,6 +292,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id
@@ -277,6 +305,7 @@ describe('authReducer', () => {
authenticated: false,
loaded: false,
error: undefined,
+ blocking: true,
loading: true,
info: undefined
};
@@ -287,6 +316,7 @@ describe('authReducer', () => {
authToken: undefined,
error: 'Test error message',
loaded: true,
+ blocking: false,
loading: false,
info: undefined
};
@@ -299,6 +329,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id
@@ -311,6 +342,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id,
@@ -325,6 +357,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id,
@@ -338,6 +371,7 @@ describe('authReducer', () => {
authToken: newTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id,
@@ -352,6 +386,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id,
@@ -364,6 +399,7 @@ describe('authReducer', () => {
authToken: undefined,
error: undefined,
loaded: false,
+ blocking: false,
loading: false,
info: undefined,
refreshing: false,
@@ -378,6 +414,7 @@ describe('authReducer', () => {
authToken: mockTokenInfo,
loaded: true,
error: undefined,
+ blocking: false,
loading: false,
info: undefined,
userId: EPersonMock.id
@@ -387,6 +424,7 @@ describe('authReducer', () => {
authenticated: false,
authToken: undefined,
loaded: false,
+ blocking: false,
loading: false,
error: undefined,
info: 'Message',
@@ -410,6 +448,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
};
const action = new AddAuthenticationMessageAction('Message');
@@ -417,6 +456,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
info: 'Message'
};
@@ -427,6 +467,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
error: 'Error',
info: 'Message'
@@ -436,6 +477,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
error: undefined,
info: undefined
@@ -447,6 +489,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false
};
const action = new SetRedirectUrlAction('redirect.url');
@@ -454,6 +497,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
redirectUrl: 'redirect.url'
};
@@ -464,6 +508,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
authMethods: []
};
@@ -472,6 +517,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: true,
loading: true,
authMethods: []
};
@@ -482,6 +528,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: true,
loading: true,
authMethods: []
};
@@ -494,6 +541,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
authMethods: authMethods
};
@@ -504,6 +552,7 @@ describe('authReducer', () => {
initialState = {
authenticated: false,
loaded: false,
+ blocking: true,
loading: true,
authMethods: []
};
@@ -513,6 +562,7 @@ describe('authReducer', () => {
state = {
authenticated: false,
loaded: false,
+ blocking: false,
loading: false,
authMethods: [new AuthMethod(AuthMethodType.Password)]
};
diff --git a/src/app/core/auth/auth.reducer.ts b/src/app/core/auth/auth.reducer.ts
index 0ffd7d0519..9435dd1b1d 100644
--- a/src/app/core/auth/auth.reducer.ts
+++ b/src/app/core/auth/auth.reducer.ts
@@ -39,6 +39,10 @@ export interface AuthState {
// true when loading
loading: boolean;
+ // true when everything else should wait for authorization
+ // to complete
+ blocking: boolean;
+
// info message
info?: string;
@@ -62,7 +66,8 @@ export interface AuthState {
const initialState: AuthState = {
authenticated: false,
loaded: false,
- loading: undefined,
+ blocking: true,
+ loading: false,
authMethods: []
};
@@ -86,7 +91,8 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
case AuthActionTypes.CHECK_AUTHENTICATION_TOKEN:
case AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE:
return Object.assign({}, state, {
- loading: true
+ loading: true,
+ blocking: true
});
case AuthActionTypes.AUTHENTICATED_ERROR:
@@ -96,6 +102,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
authToken: undefined,
error: (action as AuthenticationErrorAction).payload.message,
loaded: true,
+ blocking: false,
loading: false
});
@@ -110,6 +117,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
loaded: true,
error: undefined,
loading: false,
+ blocking: false,
info: undefined,
userId: (action as RetrieveAuthenticatedEpersonSuccessAction).payload
});
@@ -119,6 +127,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
authenticated: false,
authToken: undefined,
error: (action as AuthenticationErrorAction).payload.message,
+ blocking: false,
loading: false
});
@@ -139,6 +148,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
authToken: undefined,
error: undefined,
loaded: false,
+ blocking: false,
loading: false,
info: undefined,
refreshing: false,
@@ -151,6 +161,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
authenticated: false,
authToken: undefined,
loaded: false,
+ blocking: false,
loading: false,
info: (action as RedirectWhenTokenExpiredAction as RedirectWhenAuthenticationIsRequiredAction).payload,
userId: undefined
@@ -181,18 +192,21 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
// next three cases are used by dynamic rendering of login methods
case AuthActionTypes.RETRIEVE_AUTH_METHODS:
return Object.assign({}, state, {
- loading: true
+ loading: true,
+ blocking: true
});
case AuthActionTypes.RETRIEVE_AUTH_METHODS_SUCCESS:
return Object.assign({}, state, {
loading: false,
+ blocking: false,
authMethods: (action as RetrieveAuthMethodsSuccessAction).payload
});
case AuthActionTypes.RETRIEVE_AUTH_METHODS_ERROR:
return Object.assign({}, state, {
loading: false,
+ blocking: false,
authMethods: [new AuthMethod(AuthMethodType.Password)]
});
@@ -204,6 +218,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
case AuthActionTypes.REDIRECT_AFTER_LOGIN_SUCCESS:
return Object.assign({}, state, {
loading: true,
+ blocking: true,
});
default:
diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts
index 150e44296e..3671507cb0 100644
--- a/src/app/core/auth/auth.service.ts
+++ b/src/app/core/auth/auth.service.ts
@@ -436,6 +436,10 @@ export class AuthService {
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : ''));
}
+ /**
+ * Set the redirect url if the current one has not been set yet
+ * @param newRedirectUrl
+ */
setRedirectUrlIfNotSet(newRedirectUrl: string) {
this.getRedirectUrl().pipe(
take(1))
diff --git a/src/app/core/auth/selectors.ts b/src/app/core/auth/selectors.ts
index 173f82e810..c4e95a0fb3 100644
--- a/src/app/core/auth/selectors.ts
+++ b/src/app/core/auth/selectors.ts
@@ -65,6 +65,14 @@ const _getAuthenticationInfo = (state: AuthState) => state.info;
*/
const _isLoading = (state: AuthState) => state.loading;
+/**
+ * Returns true if everything else should wait for authentication.
+ * @function _isBlocking
+ * @param {State} state
+ * @returns {boolean}
+ */
+const _isBlocking = (state: AuthState) => state.blocking;
+
/**
* Returns true if a refresh token request is in progress.
* @function _isRefreshing
@@ -170,6 +178,16 @@ export const isAuthenticatedLoaded = createSelector(getAuthState, _isAuthenticat
*/
export const isAuthenticationLoading = createSelector(getAuthState, _isLoading);
+/**
+ * Returns true if the authentication should block everything else
+ *
+ * @function isAuthenticationBlocking
+ * @param {AuthState} state
+ * @param {any} props
+ * @return {boolean}
+ */
+export const isAuthenticationBlocking = createSelector(getAuthState, _isBlocking);
+
/**
* Returns true if the refresh token request is loading.
* @function isTokenRefreshing
diff --git a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html
index a05381fee8..fa92939e0f 100644
--- a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html
+++ b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html
@@ -1,7 +1,7 @@