Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redirecting user to same page after login. #467

Merged
merged 14 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/app/+login-page/login-page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<div>
<img class="mb-4 login-logo" src="assets/images/dspace-logo.png">
<h1 class="h3 mb-0 font-weight-normal">{{"login.form.header" | translate}}</h1>
<ds-log-in></ds-log-in>
<ds-log-in
[isStandalonePage]="true"></ds-log-in>
</div>
</div>
</div>
44 changes: 40 additions & 4 deletions src/app/core/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ import { AppState } from '../../app.reducer';
import { ClientCookieService } from '../services/client-cookie.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
import { routeServiceStub } from '../../shared/testing/route-service-stub';
import { RouteService } from '../services/route.service';

describe('AuthService test', () => {

let mockStore: Store<AuthState>;
let authService: AuthService;
let routeServiceMock: RouteService;
let authRequest;
let window;
let routerStub;
Expand Down Expand Up @@ -74,6 +77,7 @@ describe('AuthService test', () => {
{ provide: NativeWindowService, useValue: window },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
{ provide: RouteService, useValue: routeServiceStub },
{ provide: ActivatedRoute, useValue: routeStub },
{ provide: Store, useValue: mockStore },
{ provide: RemoteDataBuildService, useValue: rdbService },
Expand Down Expand Up @@ -138,20 +142,21 @@ describe('AuthService test', () => {
{ provide: AuthRequestService, useValue: authRequest },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
{ provide: RouteService, useValue: routeServiceStub },
{ provide: RemoteDataBuildService, useValue: rdbService },
CookieService,
AuthService
]
}).compileComponents();
}));

beforeEach(inject([CookieService, AuthRequestService, Store, Router], (cookieService: CookieService, authReqService: AuthRequestService, store: Store<AppState>, router: Router) => {
beforeEach(inject([CookieService, AuthRequestService, Store, Router, RouteService], (cookieService: CookieService, authReqService: AuthRequestService, store: Store<AppState>, router: Router, routeService: RouteService) => {
store
.subscribe((state) => {
(state as any).core = Object.create({});
(state as any).core.auth = authenticatedState;
});
authService = new AuthService({}, window, undefined, authReqService, router, cookieService, store, rdbService);
authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService);
}));

it('should return true when user is logged in', () => {
Expand Down Expand Up @@ -189,6 +194,7 @@ describe('AuthService test', () => {
{ provide: AuthRequestService, useValue: authRequest },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
{ provide: RouteService, useValue: routeServiceStub },
{ provide: RemoteDataBuildService, useValue: rdbService },
ClientCookieService,
CookieService,
Expand All @@ -197,7 +203,7 @@ describe('AuthService test', () => {
}).compileComponents();
}));

beforeEach(inject([ClientCookieService, AuthRequestService, Store, Router], (cookieService: ClientCookieService, authReqService: AuthRequestService, store: Store<AppState>, router: Router) => {
beforeEach(inject([ClientCookieService, AuthRequestService, Store, Router, RouteService], (cookieService: ClientCookieService, authReqService: AuthRequestService, store: Store<AppState>, router: Router, routeService: RouteService) => {
const expiredToken: AuthTokenInfo = new AuthTokenInfo('test_token');
expiredToken.expires = Date.now() - (1000 * 60 * 60);
authenticatedState = {
Expand All @@ -212,11 +218,14 @@ describe('AuthService test', () => {
(state as any).core = Object.create({});
(state as any).core.auth = authenticatedState;
});
authService = new AuthService({}, window, undefined, authReqService, router, cookieService, store, rdbService);
authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService);
storage = (authService as any).storage;
routeServiceMock = TestBed.get(RouteService);
routerStub = TestBed.get(Router);
spyOn(storage, 'get');
spyOn(storage, 'remove');
spyOn(storage, 'set');

}));

it('should throw false when token is not valid', () => {
Expand All @@ -238,5 +247,32 @@ describe('AuthService test', () => {
expect(storage.remove).toHaveBeenCalled();
});

it ('should set redirect url to previous page', () => {
spyOn(routeServiceMock, 'getHistory').and.callThrough();
authService.redirectAfterLoginSuccess(true);
expect(routeServiceMock.getHistory).toHaveBeenCalled();
expect(routerStub.navigate).toHaveBeenCalledWith(['/collection/123']);
});

it ('should set redirect url to current page', () => {
spyOn(routeServiceMock, 'getHistory').and.callThrough();
authService.redirectAfterLoginSuccess(false);
expect(routeServiceMock.getHistory).toHaveBeenCalled();
expect(routerStub.navigate).toHaveBeenCalledWith(['/home']);
});

it ('should redirect to / and not to /login', () => {
spyOn(routeServiceMock, 'getHistory').and.returnValue(observableOf(['/login', '/login']));
authService.redirectAfterLoginSuccess(true);
expect(routeServiceMock.getHistory).toHaveBeenCalled();
expect(routerStub.navigate).toHaveBeenCalledWith(['/']);
});

it ('should redirect to / when no redirect url is found', () => {
spyOn(routeServiceMock, 'getHistory').and.returnValue(observableOf(['']));
authService.redirectAfterLoginSuccess(true);
expect(routeServiceMock.getHistory).toHaveBeenCalled();
expect(routerStub.navigate).toHaveBeenCalledWith(['/']);
});
});
});
40 changes: 32 additions & 8 deletions src/app/core/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import {RouteService} from '../services/route.service';

export const LOGIN_ROUTE = '/login';
export const LOGOUT_ROUTE = '/logout';
Expand All @@ -45,6 +46,7 @@ export class AuthService {
protected authRequestService: AuthRequestService,
@Optional() @Inject(RESPONSE) private response: any,
protected router: Router,
protected routeService: RouteService,
protected storage: CookieService,
protected store: Store<AppState>,
protected rdbService: RemoteDataBuildService
Expand Down Expand Up @@ -337,7 +339,7 @@ export class AuthService {
/**
* Redirect to the route navigated before the login
*/
public redirectToPreviousUrl() {
public redirectAfterLoginSuccess(isStandalonePage: boolean) {
this.getRedirectUrl().pipe(
take(1))
.subscribe((redirectUrl) => {
Expand All @@ -346,18 +348,39 @@ export class AuthService {
this.clearRedirectUrl();
this.router.onSameUrlNavigation = 'reload';
const url = decodeURIComponent(redirectUrl);
this.router.navigateByUrl(url);
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
// this._window.nativeWindow.location.href = url;
this.navigateToRedirectUrl(url);
} else {
this.router.navigate(['/']);
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
// this._window.nativeWindow.location.href = '/';
// If redirectUrl is empty use history.
this.routeService.getHistory().pipe(
take(1)
).subscribe((history) => {
let redirUrl;
if (isStandalonePage) {
// For standalone login pages, use the previous route.
redirUrl = history[history.length - 2] || '';
} else {
redirUrl = history[history.length - 1] || '';
}
this.navigateToRedirectUrl(redirUrl);
});
}
})
});

}

protected navigateToRedirectUrl(url: string) {
// in case the user navigates directly to /login (via bookmark, etc), or the route history is not found.
if (isEmpty(url) || url.startsWith(LOGIN_ROUTE)) {
this.router.navigate(['/']);
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
// this._window.nativeWindow.location.href = '/';
} else {
/* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
// this._window.nativeWindow.location.href = url;
this.router.navigate([url]);
}
}

/**
* Refresh route navigated
*/
Expand Down Expand Up @@ -400,4 +423,5 @@ export class AuthService {
this.store.dispatch(new SetRedirectUrlAction(''));
this.storage.remove(REDIRECT_COOKIE);
}

}
17 changes: 11 additions & 6 deletions src/app/core/auth/server-auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { map, switchMap, take } from 'rxjs/operators';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { Injectable } from '@angular/core';

import { Observable } from 'rxjs';
import { HttpHeaders } from '@angular/common/http';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from './models/auth-status.model';
import { isNotEmpty } from '../../shared/empty.util';
import { AuthService } from './auth.service';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { AuthService, LOGIN_ROUTE } from './auth.service';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { CheckAuthenticationTokenAction } from './auth.actions';
import { EPerson } from '../eperson/models/eperson.model';
Expand Down Expand Up @@ -54,7 +54,7 @@ export class ServerAuthService extends AuthService {
/**
* Redirect to the route navigated before the login
*/
public redirectToPreviousUrl() {
public redirectAfterLoginSuccess(isStandalonePage: boolean) {
this.getRedirectUrl().pipe(
take(1))
.subscribe((redirectUrl) => {
Expand All @@ -67,10 +67,15 @@ export class ServerAuthService extends AuthService {
const url = decodeURIComponent(redirectUrl);
this.router.navigateByUrl(url);
} else {
this.router.navigate(['/']);
// If redirectUrl is empty use history. For ssr the history array should contain the requested url.
this.routeService.getHistory().pipe(
filter((history) => history.length > 0),
take(1)
).subscribe((history) => {
this.navigateToRedirectUrl(history[history.length - 1] || '');
});
}
})

}

}
3 changes: 2 additions & 1 deletion src/app/shared/auth-nav-menu/auth-nav-menu.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<div ngbDropdown 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 aria-labelledby="dropdownLogin">
<ds-log-in></ds-log-in>
<ds-log-in
[isStandalonePage]="false"></ds-log-in>
</div>
</div>
</li>
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/log-in/log-in.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ describe('LogInComponent', () => {
// verify Store.dispatch() is invoked
expect(page.navigateSpy.calls.any()).toBe(true, 'Store.dispatch not invoked');
});

});

/**
Expand Down
9 changes: 7 additions & 2 deletions src/app/shared/log-in/log-in.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { filter, map, takeWhile } from 'rxjs/operators';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { select, Store } from '@ngrx/store';
Expand All @@ -20,6 +20,7 @@ import { CoreState } from '../../core/core.reducers';
import { isNotEmpty } from '../empty.util';
import { fadeOut } from '../animations/fade';
import { AuthService } from '../../core/auth/auth.service';
import { Router } from '@angular/router';

/**
* /users/sign-in
Expand Down Expand Up @@ -81,10 +82,13 @@ export class LogInComponent implements OnDestroy, OnInit {
*/
private alive = true;

@Input() isStandalonePage: boolean;

/**
* @constructor
* @param {AuthService} authService
* @param {FormBuilder} formBuilder
* @param {Router} router
* @param {Store<State>} store
*/
constructor(
Expand Down Expand Up @@ -135,7 +139,7 @@ export class LogInComponent implements OnDestroy, OnInit {
takeWhile(() => this.alive),
filter((authenticated) => authenticated))
.subscribe(() => {
this.authService.redirectToPreviousUrl();
this.authService.redirectAfterLoginSuccess(this.isStandalonePage);
}
);
}
Expand Down Expand Up @@ -188,4 +192,5 @@ export class LogInComponent implements OnDestroy, OnInit {
// clear form
this.form.reset();
}

}
7 changes: 6 additions & 1 deletion src/app/shared/testing/auth-service-stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class AuthServiceStub {

token: AuthTokenInfo = new AuthTokenInfo('token_test');
private _tokenExpired = false;
private redirectUrl;

constructor() {
this.token.expires = Date.now() + (1000 * 60 * 60);
Expand Down Expand Up @@ -88,7 +89,11 @@ export class AuthServiceStub {
}

setRedirectUrl(url: string) {
return;
this.redirectUrl = url;
}

getRedirectUrl() {
return observableOf(this.redirectUrl);
}

public storeToken(token: AuthTokenInfo) {
Expand Down
3 changes: 3 additions & 0 deletions src/app/shared/testing/route-service-stub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export const routeServiceStub: any = {
},
getRouteDataValue: (param) => {
return observableOf({})
},
getHistory: () => {
return observableOf(['/home','/collection/123','/home'])
}
/* tslint:enable:no-empty */
};
2 changes: 1 addition & 1 deletion src/modules/app/server-app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function createTranslateLoader() {
{
provide: SubmissionService,
useClass: ServerSubmissionService
},
}
]
})
export class ServerAppModule {
Expand Down