mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'w2p-72699_Hard-redirect-after-log-in' into w2p-72541_User-agreement-and-Privacy-statement
Conflicts: src/app/app-routing.module.ts
This commit is contained in:
@@ -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',
|
||||
})
|
||||
|
@@ -1,7 +1,4 @@
|
||||
<div class="text-center ds-full-screen-loader d-flex align-items-center flex-column justify-content-center" *ngIf="!(hasAuthFinishedLoading$ | async)">
|
||||
<ds-loading [showMessage]="false"></ds-loading>
|
||||
</div>
|
||||
<div class="outer-wrapper" *ngIf="hasAuthFinishedLoading$ | async">
|
||||
<div class="outer-wrapper" *ngIf="isAuthBlocking$ | async; else authLoader">
|
||||
<ds-admin-sidebar></ds-admin-sidebar>
|
||||
<div class="inner-wrapper" [@slideSidebarPadding]="{
|
||||
value: (!(sidebarVisible | async) ? 'hidden' : (slideSidebarOver | async) ? 'shown' : 'expanded'),
|
||||
@@ -26,3 +23,8 @@
|
||||
<ds-footer></ds-footer>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #authLoader>
|
||||
<div class="text-center ds-full-screen-loader d-flex align-items-center flex-column justify-content-center">
|
||||
<ds-loading [showMessage]="false"></ds-loading>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@@ -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<boolean>;
|
||||
isAuthBlocking$: Observable<boolean>;
|
||||
|
||||
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');
|
||||
|
@@ -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<AppState>,) => {
|
||||
return () => store.dispatch(new CheckAuthenticationTokenAction());
|
||||
},
|
||||
deps: [ Store ],
|
||||
multi: true
|
||||
},
|
||||
...DYNAMIC_MATCHER_PROVIDERS,
|
||||
];
|
||||
|
||||
|
62
src/app/core/auth/auth-blocking.guard.spec.ts
Normal file
62
src/app/core/auth/auth-blocking.guard.spec.ts
Normal file
@@ -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<AppState>(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 }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
31
src/app/core/auth/auth-blocking.guard.ts
Normal file
31
src/app/core/auth/auth-blocking.guard.ts
Normal file
@@ -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<AppState>) {
|
||||
}
|
||||
|
||||
canActivate(): Observable<boolean> {
|
||||
return this.store.pipe(select(isAuthenticationBlocking)).pipe(
|
||||
map((isBlocking: boolean) => isBlocking === false),
|
||||
distinctUntilChanged(),
|
||||
filter((finished: boolean) => finished === true),
|
||||
take(1),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -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)]
|
||||
};
|
||||
|
@@ -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:
|
||||
|
@@ -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))
|
||||
|
@@ -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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<ul class="navbar-nav" [ngClass]="{'mr-auto': (isXsOrSm$ | async)}">
|
||||
<li *ngIf="!(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item"
|
||||
(click)="$event.stopPropagation();">
|
||||
<div ngbDropdown display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
|
||||
<div ngbDropdown #loginDrop display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
|
||||
<a href="#" id="dropdownLogin" (click)="$event.preventDefault()" ngbDropdownToggle
|
||||
class="px-1">{{ 'nav.login' | translate }}</a>
|
||||
<div id="loginDropdownMenu" [ngClass]="{'pl-3 pr-3': (loading | async)}" ngbDropdownMenu
|
||||
|
@@ -43,11 +43,13 @@ describe('AuthNavMenuComponent', () => {
|
||||
notAuthState = {
|
||||
authenticated: false,
|
||||
loaded: false,
|
||||
blocking: false,
|
||||
loading: false
|
||||
};
|
||||
authState = {
|
||||
authenticated: true,
|
||||
loaded: true,
|
||||
blocking: false,
|
||||
loading: false,
|
||||
authToken: new AuthTokenInfo('test_token'),
|
||||
userId: EPersonMock.id
|
||||
|
@@ -34,6 +34,7 @@ describe('UserMenuComponent', () => {
|
||||
authState = {
|
||||
authenticated: true,
|
||||
loaded: true,
|
||||
blocking: false,
|
||||
loading: false,
|
||||
authToken: new AuthTokenInfo('test_token'),
|
||||
userId: EPersonMock.id
|
||||
@@ -41,6 +42,7 @@ describe('UserMenuComponent', () => {
|
||||
authStateLoading = {
|
||||
authenticated: true,
|
||||
loaded: true,
|
||||
blocking: false,
|
||||
loading: true,
|
||||
authToken: null,
|
||||
userId: EPersonMock.id
|
||||
|
@@ -26,6 +26,7 @@ describe('ImpersonateNavbarComponent', () => {
|
||||
authState = {
|
||||
authenticated: true,
|
||||
loaded: true,
|
||||
blocking: false,
|
||||
loading: false,
|
||||
authToken: new AuthTokenInfo('test_token'),
|
||||
userId: EPersonMock.id
|
||||
|
@@ -15,6 +15,12 @@ export class LogInContainerComponent implements OnInit {
|
||||
|
||||
@Input() authMethod: AuthMethod;
|
||||
|
||||
/**
|
||||
* A boolean representing if LogInContainerComponent is in a standalone page
|
||||
* @type {boolean}
|
||||
*/
|
||||
@Input() isStandalonePage: boolean;
|
||||
|
||||
/**
|
||||
* Injector to inject a section component with the @Input parameters
|
||||
* @type {Injector}
|
||||
@@ -36,6 +42,7 @@ export class LogInContainerComponent implements OnInit {
|
||||
this.objectInjector = Injector.create({
|
||||
providers: [
|
||||
{ provide: 'authMethodProvider', useFactory: () => (this.authMethod), deps: [] },
|
||||
{ provide: 'isStandalonePage', useFactory: () => (this.isStandalonePage), deps: [] },
|
||||
],
|
||||
parent: this.injector
|
||||
});
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<div *ngIf="i === 1" class="text-center mt-2">
|
||||
<span class="align-middle">{{"login.form.or-divider" | translate}}</span>
|
||||
</div>
|
||||
<ds-log-in-container [authMethod]="authMethod"></ds-log-in-container>
|
||||
<ds-log-in-container [authMethod]="authMethod" [isStandalonePage]="isStandalonePage"></ds-log-in-container>
|
||||
</ng-container>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
@@ -2,9 +2,16 @@ import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { AuthMethod } from '../../core/auth/models/auth.method';
|
||||
import { getAuthenticationMethods, isAuthenticated, isAuthenticationLoading } from '../../core/auth/selectors';
|
||||
import {
|
||||
getAuthenticationError,
|
||||
getAuthenticationMethods,
|
||||
isAuthenticated,
|
||||
isAuthenticationLoading
|
||||
} from '../../core/auth/selectors';
|
||||
import { CoreState } from '../../core/core.reducers';
|
||||
import { getForgotPasswordPath, getRegisterPath } from '../../app-routing.module';
|
||||
import { hasValue } from '../empty.util';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
|
||||
/**
|
||||
* /users/sign-in
|
||||
@@ -41,7 +48,8 @@ export class LogInComponent implements OnInit {
|
||||
*/
|
||||
public loading: Observable<boolean>;
|
||||
|
||||
constructor(private store: Store<CoreState>) {
|
||||
constructor(private store: Store<CoreState>,
|
||||
private authService: AuthService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -55,6 +63,13 @@ export class LogInComponent implements OnInit {
|
||||
|
||||
// set isAuthenticated
|
||||
this.isAuthenticated = this.store.pipe(select(isAuthenticated));
|
||||
|
||||
// Clear the redirect URL if an authentication error occurs and this is not a standalone page
|
||||
this.store.pipe(select(getAuthenticationError)).subscribe((error) => {
|
||||
if (hasValue(error) && !this.isStandalonePage) {
|
||||
this.authService.clearRedirectUrl();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getRegisterPath() {
|
||||
|
@@ -55,6 +55,7 @@ describe('LogInPasswordComponent', () => {
|
||||
providers: [
|
||||
{ provide: AuthService, useClass: AuthServiceStub },
|
||||
{ provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Password) },
|
||||
{ provide: 'isStandalonePage', useValue: true },
|
||||
{ provide: HardRedirectService, useValue: hardRedirectService },
|
||||
],
|
||||
schemas: [
|
||||
|
@@ -68,6 +68,7 @@ export class LogInPasswordComponent implements OnInit {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {AuthMethod} injectedAuthMethodModel
|
||||
* @param {boolean} isStandalonePage
|
||||
* @param {AuthService} authService
|
||||
* @param {HardRedirectService} hardRedirectService
|
||||
* @param {FormBuilder} formBuilder
|
||||
@@ -75,6 +76,7 @@ export class LogInPasswordComponent implements OnInit {
|
||||
*/
|
||||
constructor(
|
||||
@Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod,
|
||||
@Inject('isStandalonePage') public isStandalonePage: boolean,
|
||||
private authService: AuthService,
|
||||
private hardRedirectService: HardRedirectService,
|
||||
private formBuilder: FormBuilder,
|
||||
@@ -140,7 +142,11 @@ export class LogInPasswordComponent implements OnInit {
|
||||
email.trim();
|
||||
password.trim();
|
||||
|
||||
this.authService.setRedirectUrlIfNotSet(this.hardRedirectService.getCurrentRoute());
|
||||
if (!this.isStandalonePage) {
|
||||
this.authService.setRedirectUrl(this.hardRedirectService.getCurrentRoute());
|
||||
} else {
|
||||
this.authService.setRedirectUrlIfNotSet('/');
|
||||
}
|
||||
|
||||
// dispatch AuthenticationAction
|
||||
this.store.dispatch(new AuthenticateAction(email, password));
|
||||
|
@@ -62,6 +62,7 @@ describe('LogInShibbolethComponent', () => {
|
||||
providers: [
|
||||
{ provide: AuthService, useClass: AuthServiceStub },
|
||||
{ provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Shibboleth, location) },
|
||||
{ provide: 'isStandalonePage', useValue: true },
|
||||
{ provide: NativeWindowService, useFactory: NativeWindowMockFactory },
|
||||
{ provide: Router, useValue: new RouterStub() },
|
||||
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub() },
|
||||
|
@@ -51,6 +51,7 @@ export class LogInShibbolethComponent implements OnInit {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {AuthMethod} injectedAuthMethodModel
|
||||
* @param {boolean} isStandalonePage
|
||||
* @param {NativeWindowRef} _window
|
||||
* @param {RouteService} route
|
||||
* @param {AuthService} authService
|
||||
@@ -59,6 +60,7 @@ export class LogInShibbolethComponent implements OnInit {
|
||||
*/
|
||||
constructor(
|
||||
@Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod,
|
||||
@Inject('isStandalonePage') public isStandalonePage: boolean,
|
||||
@Inject(NativeWindowService) protected _window: NativeWindowRef,
|
||||
private route: RouteService,
|
||||
private authService: AuthService,
|
||||
@@ -81,7 +83,11 @@ export class LogInShibbolethComponent implements OnInit {
|
||||
}
|
||||
|
||||
redirectToShibboleth() {
|
||||
this.authService.setRedirectUrlIfNotSet(this.hardRedirectService.getCurrentRoute())
|
||||
if (!this.isStandalonePage) {
|
||||
this.authService.setRedirectUrl(this.hardRedirectService.getCurrentRoute());
|
||||
} else {
|
||||
this.authService.setRedirectUrlIfNotSet('/');
|
||||
}
|
||||
let newLocationUrl = this.location;
|
||||
const currentUrl = this._window.nativeWindow.location.href;
|
||||
const myRegexp = /\?redirectUrl=(.*)/g;
|
||||
|
@@ -162,4 +162,8 @@ export class AuthServiceStub {
|
||||
redirectAfterLoginSuccess() {
|
||||
return;
|
||||
}
|
||||
|
||||
clearRedirectUrl() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user