diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 222214c76f..8773b1a9fb 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -1,7 +1,6 @@ import { Observable, of as observableOf, throwError as observableThrowError } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; -import { EPersonDataService } from '../eperson/eperson-data.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestService } from '../data/request.service'; import { GLOBAL_CONFIG } from '../../../config'; diff --git a/src/app/core/auth/auth.actions.ts b/src/app/core/auth/auth.actions.ts index d0969d38d4..2681ed39a2 100644 --- a/src/app/core/auth/auth.actions.ts +++ b/src/app/core/auth/auth.actions.ts @@ -1,9 +1,7 @@ // import @ngrx import { Action } from '@ngrx/store'; - // import type function import { type } from '../../shared/ngrx/type'; - // import models import { EPerson } from '../eperson/models/eperson.model'; import { AuthTokenInfo } from './models/auth-token-info.model'; @@ -31,6 +29,9 @@ export const AuthActionTypes = { REGISTRATION_ERROR: type('dspace/auth/REGISTRATION_ERROR'), REGISTRATION_SUCCESS: type('dspace/auth/REGISTRATION_SUCCESS'), SET_REDIRECT_URL: type('dspace/auth/SET_REDIRECT_URL'), + RETRIEVE_AUTHENTICATED_EPERSON: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON'), + RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS'), + RETRIEVE_AUTHENTICATED_EPERSON_ERROR: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_ERROR'), }; /* tslint:disable:max-classes-per-file */ @@ -76,11 +77,11 @@ export class AuthenticatedSuccessAction implements Action { payload: { authenticated: boolean; authToken: AuthTokenInfo; - user: EPerson + userHref: string }; - constructor(authenticated: boolean, authToken: AuthTokenInfo, user: EPerson) { - this.payload = { authenticated, authToken, user }; + constructor(authenticated: boolean, authToken: AuthTokenInfo, userHref: string) { + this.payload = { authenticated, authToken, userHref }; } } @@ -322,6 +323,47 @@ export class SetRedirectUrlAction implements Action { } } +/** + * Retrieve the authenticated eperson. + * @class RetrieveAuthenticatedEpersonAction + * @implements {Action} + */ +export class RetrieveAuthenticatedEpersonAction implements Action { + public type: string = AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON; + payload: string; + + constructor(user: string) { + this.payload = user ; + } +} + +/** + * Set the authenticated eperson in the state. + * @class RetrieveAuthenticatedEpersonSuccessAction + * @implements {Action} + */ +export class RetrieveAuthenticatedEpersonSuccessAction implements Action { + public type: string = AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS; + payload: EPerson; + + constructor(user: EPerson) { + this.payload = user ; + } +} + +/** + * Set the authenticated eperson in the state. + * @class RetrieveAuthenticatedEpersonSuccessAction + * @implements {Action} + */ +export class RetrieveAuthenticatedEpersonErrorAction implements Action { + public type: string = AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_ERROR; + payload: Error; + + constructor(payload: Error) { + this.payload = payload ; + } +} /* tslint:enable:max-classes-per-file */ /** @@ -343,4 +385,8 @@ export type AuthActions | RegistrationErrorAction | RegistrationSuccessAction | AddAuthenticationMessageAction - | ResetAuthenticationMessagesAction; + | ResetAuthenticationMessagesAction + | RetrieveAuthenticatedEpersonAction + | RetrieveAuthenticatedEpersonErrorAction + | RetrieveAuthenticatedEpersonSuccessAction + | SetRedirectUrlAction; diff --git a/src/app/core/auth/auth.effects.spec.ts b/src/app/core/auth/auth.effects.spec.ts index 8c2b4026e0..34b900fe7e 100644 --- a/src/app/core/auth/auth.effects.spec.ts +++ b/src/app/core/auth/auth.effects.spec.ts @@ -18,12 +18,14 @@ import { LogOutErrorAction, LogOutSuccessAction, RefreshTokenErrorAction, - RefreshTokenSuccessAction + RefreshTokenSuccessAction, + RetrieveAuthenticatedEpersonAction, + RetrieveAuthenticatedEpersonErrorAction, + RetrieveAuthenticatedEpersonSuccessAction } from './auth.actions'; import { AuthServiceStub } from '../../shared/testing/auth-service-stub'; import { AuthService } from './auth.service'; import { AuthState } from './auth.reducer'; - import { EPersonMock } from '../../shared/testing/eperson-mock'; describe('AuthEffects', () => { @@ -42,13 +44,14 @@ describe('AuthEffects', () => { authServiceStub = new AuthServiceStub(); token = authServiceStub.getToken(); } + beforeEach(() => { init(); TestBed.configureTestingModule({ providers: [ AuthEffects, - {provide: AuthService, useValue: authServiceStub}, - {provide: Store, useValue: store}, + { provide: AuthService, useValue: authServiceStub }, + { provide: Store, useValue: store }, provideMockActions(() => actions), // other providers ], @@ -63,11 +66,11 @@ describe('AuthEffects', () => { actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATE, - payload: {email: 'user', password: 'password'} + payload: { email: 'user', password: 'password' } } }); - const expected = cold('--b-', {b: new AuthenticationSuccessAction(token)}); + const expected = cold('--b-', { b: new AuthenticationSuccessAction(token) }); expect(authEffects.authenticate$).toBeObservable(expected); }); @@ -80,11 +83,11 @@ describe('AuthEffects', () => { actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATE, - payload: {email: 'user', password: 'wrongpassword'} + payload: { email: 'user', password: 'wrongpassword' } } }); - const expected = cold('--b-', {b: new AuthenticationErrorAction(new Error('Message Error test'))}); + const expected = cold('--b-', { b: new AuthenticationErrorAction(new Error('Message Error test')) }); expect(authEffects.authenticate$).toBeObservable(expected); }); @@ -94,9 +97,9 @@ describe('AuthEffects', () => { describe('authenticateSuccess$', () => { it('should return a AUTHENTICATED action in response to a AUTHENTICATE_SUCCESS action', () => { - actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATE_SUCCESS, payload: token}}); + actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATE_SUCCESS, payload: token } }); - const expected = cold('--b-', {b: new AuthenticatedAction(token)}); + const expected = cold('--b-', { b: new AuthenticatedAction(token) }); expect(authEffects.authenticateSuccess$).toBeObservable(expected); }); @@ -106,9 +109,9 @@ describe('AuthEffects', () => { describe('when token is valid', () => { it('should return a AUTHENTICATED_SUCCESS action in response to a AUTHENTICATED action', () => { - actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}}); + actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATED, payload: token } }); - const expected = cold('--b-', {b: new AuthenticatedSuccessAction(true, token, EPersonMock)}); + const expected = cold('--b-', { b: new AuthenticatedSuccessAction(true, token, EPersonMock._links.self.href) }); expect(authEffects.authenticated$).toBeObservable(expected); }); @@ -118,23 +121,42 @@ describe('AuthEffects', () => { it('should return a AUTHENTICATED_ERROR action in response to a AUTHENTICATED action', () => { spyOn((authEffects as any).authService, 'authenticatedUser').and.returnValue(observableThrow(new Error('Message Error test'))); - actions = hot('--a-', {a: {type: AuthActionTypes.AUTHENTICATED, payload: token}}); + actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATED, payload: token } }); - const expected = cold('--b-', {b: new AuthenticatedErrorAction(new Error('Message Error test'))}); + const expected = cold('--b-', { b: new AuthenticatedErrorAction(new Error('Message Error test')) }); expect(authEffects.authenticated$).toBeObservable(expected); }); }); }); + describe('authenticatedSuccess$', () => { + + it('should return a RETRIEVE_AUTHENTICATED_EPERSON action in response to a AUTHENTICATED_SUCCESS action', () => { + actions = hot('--a-', { + a: { + type: AuthActionTypes.AUTHENTICATED_SUCCESS, payload: { + authenticated: true, + authToken: token, + userHref: EPersonMock._links.self.href + } + } + }); + + const expected = cold('--b-', { b: new RetrieveAuthenticatedEpersonAction(EPersonMock._links.self.href) }); + + expect(authEffects.authenticatedSuccess$).toBeObservable(expected); + }); + }); + describe('checkToken$', () => { describe('when check token succeeded', () => { it('should return a AUTHENTICATED action in response to a CHECK_AUTHENTICATION_TOKEN action', () => { - actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN}}); + actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN } }); - const expected = cold('--b-', {b: new AuthenticatedAction(token)}); + const expected = cold('--b-', { b: new AuthenticatedAction(token) }); expect(authEffects.checkToken$).toBeObservable(expected); }); @@ -144,23 +166,53 @@ describe('AuthEffects', () => { it('should return a CHECK_AUTHENTICATION_TOKEN_ERROR action in response to a CHECK_AUTHENTICATION_TOKEN action', () => { spyOn((authEffects as any).authService, 'hasValidAuthenticationToken').and.returnValue(observableThrow('')); - actions = hot('--a-', {a: {type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token}}); + actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN, payload: token } }); - const expected = cold('--b-', {b: new CheckAuthenticationTokenErrorAction()}); + const expected = cold('--b-', { b: new CheckAuthenticationTokenErrorAction() }); expect(authEffects.checkToken$).toBeObservable(expected); }); }) }); + describe('retrieveAuthenticatedEperson$', () => { + + describe('when request is successful', () => { + it('should return a RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS action in response to a RETRIEVE_AUTHENTICATED_EPERSON action', () => { + actions = hot('--a-', { + a: { + type: AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON, + payload: EPersonMock._links.self.href + } + }); + + const expected = cold('--b-', { b: new RetrieveAuthenticatedEpersonSuccessAction(EPersonMock) }); + + expect(authEffects.retrieveAuthenticatedEperson$).toBeObservable(expected); + }); + }); + + describe('when request is not successful', () => { + it('should return a RETRIEVE_AUTHENTICATED_EPERSON_ERROR action in response to a RETRIEVE_AUTHENTICATED_EPERSON action', () => { + spyOn((authEffects as any).authService, 'retrieveAuthenticatedUserByHref').and.returnValue(observableThrow(new Error('Message Error test'))); + + actions = hot('--a-', { a: { type: AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON, payload: token } }); + + const expected = cold('--b-', { b: new RetrieveAuthenticatedEpersonErrorAction(new Error('Message Error test')) }); + + expect(authEffects.retrieveAuthenticatedEperson$).toBeObservable(expected); + }); + }); + }); + describe('refreshToken$', () => { describe('when refresh token succeeded', () => { it('should return a REFRESH_TOKEN_SUCCESS action in response to a REFRESH_TOKEN action', () => { - actions = hot('--a-', {a: {type: AuthActionTypes.REFRESH_TOKEN}}); + actions = hot('--a-', { a: { type: AuthActionTypes.REFRESH_TOKEN } }); - const expected = cold('--b-', {b: new RefreshTokenSuccessAction(token)}); + const expected = cold('--b-', { b: new RefreshTokenSuccessAction(token) }); expect(authEffects.refreshToken$).toBeObservable(expected); }); @@ -170,9 +222,9 @@ describe('AuthEffects', () => { it('should return a REFRESH_TOKEN_ERROR action in response to a REFRESH_TOKEN action', () => { spyOn((authEffects as any).authService, 'refreshAuthenticationToken').and.returnValue(observableThrow('')); - actions = hot('--a-', {a: {type: AuthActionTypes.REFRESH_TOKEN, payload: token}}); + actions = hot('--a-', { a: { type: AuthActionTypes.REFRESH_TOKEN, payload: token } }); - const expected = cold('--b-', {b: new RefreshTokenErrorAction()}); + const expected = cold('--b-', { b: new RefreshTokenErrorAction() }); expect(authEffects.refreshToken$).toBeObservable(expected); }); @@ -184,9 +236,9 @@ describe('AuthEffects', () => { describe('when refresh token succeeded', () => { it('should return a LOG_OUT_SUCCESS action in response to a LOG_OUT action', () => { - actions = hot('--a-', {a: {type: AuthActionTypes.LOG_OUT}}); + actions = hot('--a-', { a: { type: AuthActionTypes.LOG_OUT } }); - const expected = cold('--b-', {b: new LogOutSuccessAction()}); + const expected = cold('--b-', { b: new LogOutSuccessAction() }); expect(authEffects.logOut$).toBeObservable(expected); }); @@ -196,9 +248,9 @@ describe('AuthEffects', () => { it('should return a REFRESH_TOKEN_ERROR action in response to a LOG_OUT action', () => { spyOn((authEffects as any).authService, 'logout').and.returnValue(observableThrow(new Error('Message Error test'))); - actions = hot('--a-', {a: {type: AuthActionTypes.LOG_OUT, payload: token}}); + actions = hot('--a-', { a: { type: AuthActionTypes.LOG_OUT, payload: token } }); - const expected = cold('--b-', {b: new LogOutErrorAction(new Error('Message Error test'))}); + const expected = cold('--b-', { b: new LogOutErrorAction(new Error('Message Error test')) }); expect(authEffects.logOut$).toBeObservable(expected); }); diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index 1e68802af8..5ee63ccd92 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -26,7 +26,10 @@ import { RefreshTokenSuccessAction, RegistrationAction, RegistrationErrorAction, - RegistrationSuccessAction + RegistrationSuccessAction, + RetrieveAuthenticatedEpersonAction, + RetrieveAuthenticatedEpersonErrorAction, + RetrieveAuthenticatedEpersonSuccessAction } from './auth.actions'; import { EPerson } from '../eperson/models/eperson.model'; import { AuthStatus } from './models/auth-status.model'; @@ -66,11 +69,17 @@ export class AuthEffects { ofType(AuthActionTypes.AUTHENTICATED), switchMap((action: AuthenticatedAction) => { return this.authService.authenticatedUser(action.payload).pipe( - map((user: EPerson) => new AuthenticatedSuccessAction((user !== null), action.payload, user)), + map((userHref: string) => new AuthenticatedSuccessAction((userHref !== null), action.payload, userHref)), catchError((error) => observableOf(new AuthenticatedErrorAction(error))),); }) ); + @Effect() + public authenticatedSuccess$: Observable = this.actions$.pipe( + ofType(AuthActionTypes.AUTHENTICATED_SUCCESS), + map((action: AuthenticatedSuccessAction) => new RetrieveAuthenticatedEpersonAction(action.payload.userHref)) + ); + // It means "reacts to this action but don't send another" @Effect({ dispatch: false }) public authenticatedError$: Observable = this.actions$.pipe( @@ -78,6 +87,16 @@ export class AuthEffects { tap((action: LogOutSuccessAction) => this.authService.removeToken()) ); + @Effect() + public retrieveAuthenticatedEperson$: Observable = this.actions$.pipe( + ofType(AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON), + switchMap((action: RetrieveAuthenticatedEpersonAction) => { + return this.authService.retrieveAuthenticatedUserByHref(action.payload).pipe( + map((user: EPerson) => new RetrieveAuthenticatedEpersonSuccessAction(user)), + catchError((error) => observableOf(new RetrieveAuthenticatedEpersonErrorAction(error)))); + }) + ); + @Effect() public checkToken$: Observable = this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN), switchMap(() => { diff --git a/src/app/core/auth/auth.reducer.spec.ts b/src/app/core/auth/auth.reducer.spec.ts index ca2ba00036..f299696007 100644 --- a/src/app/core/auth/auth.reducer.spec.ts +++ b/src/app/core/auth/auth.reducer.spec.ts @@ -17,7 +17,7 @@ import { RefreshTokenAction, RefreshTokenErrorAction, RefreshTokenSuccessAction, - ResetAuthenticationMessagesAction, + ResetAuthenticationMessagesAction, RetrieveAuthenticatedEpersonErrorAction, RetrieveAuthenticatedEpersonSuccessAction, SetRedirectUrlAction } from './auth.actions'; import { AuthTokenInfo } from './models/auth-token-info.model'; @@ -107,16 +107,15 @@ describe('authReducer', () => { loading: true, info: undefined }; - const action = new AuthenticatedSuccessAction(true, mockTokenInfo, EPersonMock); + const action = new AuthenticatedSuccessAction(true, mockTokenInfo, EPersonMock._links.self.href); const newState = authReducer(initialState, action); state = { authenticated: true, authToken: mockTokenInfo, - loaded: true, + loaded: false, error: undefined, - loading: false, - info: undefined, - user: EPersonMock + loading: true, + info: undefined }; expect(newState).toEqual(state); }); @@ -242,6 +241,50 @@ describe('authReducer', () => { expect(newState).toEqual(state); }); + it('should properly set the state, in response to a RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS action', () => { + initialState = { + authenticated: true, + authToken: mockTokenInfo, + loaded: false, + error: undefined, + loading: true, + info: undefined + }; + const action = new RetrieveAuthenticatedEpersonSuccessAction(EPersonMock); + const newState = authReducer(initialState, action); + state = { + authenticated: true, + authToken: mockTokenInfo, + loaded: true, + error: undefined, + loading: false, + info: undefined, + user: EPersonMock + }; + expect(newState).toEqual(state); + }); + + it('should properly set the state, in response to a RETRIEVE_AUTHENTICATED_EPERSON_ERROR action', () => { + initialState = { + authenticated: false, + loaded: false, + error: undefined, + loading: true, + info: undefined + }; + const action = new RetrieveAuthenticatedEpersonErrorAction(mockError); + const newState = authReducer(initialState, action); + state = { + authenticated: false, + authToken: undefined, + error: 'Test error message', + loaded: true, + loading: false, + info: undefined + }; + expect(newState).toEqual(state); + }); + it('should properly set the state, in response to a REFRESH_TOKEN action', () => { initialState = { authenticated: true, diff --git a/src/app/core/auth/auth.reducer.ts b/src/app/core/auth/auth.reducer.ts index 98827d842e..7d5e50c432 100644 --- a/src/app/core/auth/auth.reducer.ts +++ b/src/app/core/auth/auth.reducer.ts @@ -8,7 +8,7 @@ import { LogOutErrorAction, RedirectWhenAuthenticationIsRequiredAction, RedirectWhenTokenExpiredAction, - RefreshTokenSuccessAction, + RefreshTokenSuccessAction, RetrieveAuthenticatedEpersonSuccessAction, SetRedirectUrlAction } from './auth.actions'; // import models @@ -80,6 +80,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut }); case AuthActionTypes.AUTHENTICATED_ERROR: + case AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_ERROR: return Object.assign({}, state, { authenticated: false, authToken: undefined, @@ -91,12 +92,16 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut case AuthActionTypes.AUTHENTICATED_SUCCESS: return Object.assign({}, state, { authenticated: true, - authToken: (action as AuthenticatedSuccessAction).payload.authToken, + authToken: (action as AuthenticatedSuccessAction).payload.authToken + }); + + case AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS: + return Object.assign({}, state, { loaded: true, error: undefined, loading: false, info: undefined, - user: (action as AuthenticatedSuccessAction).payload.user + user: (action as RetrieveAuthenticatedEpersonSuccessAction).payload }); case AuthActionTypes.AUTHENTICATE_ERROR: diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts index 0928afcb19..31649abe32 100644 --- a/src/app/core/auth/auth.service.spec.ts +++ b/src/app/core/auth/auth.service.spec.ts @@ -5,14 +5,12 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Store, StoreModule } from '@ngrx/store'; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { of as observableOf } from 'rxjs'; -import { LinkService } from '../cache/builders/link.service'; import { authReducer, AuthState } from './auth.reducer'; import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { AuthService } from './auth.service'; import { RouterStub } from '../../shared/testing/router-stub'; import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; - import { CookieService } from '../services/cookie.service'; import { AuthRequestServiceStub } from '../../shared/testing/auth-request-service-stub'; import { AuthRequestService } from './auth-request.service'; @@ -23,12 +21,21 @@ import { EPersonMock } from '../../shared/testing/eperson-mock'; 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'; +import { Observable } from 'rxjs/internal/Observable'; +import { RemoteData } from '../data/remote-data'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; +import { EPersonDataService } from '../eperson/eperson-data.service'; describe('AuthService test', () => { + const mockEpersonDataService: any = { + findByHref(href: string): Observable> { + return createSuccessfulRemoteDataObject$(EPersonMock); + } + }; + let mockStore: Store; let authService: AuthService; let routeServiceMock: RouteService; @@ -62,7 +69,7 @@ describe('AuthService test', () => { linkService = { resolveLinks: {} }; - spyOn(linkService, 'resolveLinks').and.returnValue({authenticated: true, eperson: observableOf({payload: {}})}); + spyOn(linkService, 'resolveLinks').and.returnValue({ authenticated: true, eperson: observableOf({ payload: {} }) }); } @@ -83,7 +90,7 @@ describe('AuthService test', () => { { provide: RouteService, useValue: routeServiceStub }, { provide: ActivatedRoute, useValue: routeStub }, { provide: Store, useValue: mockStore }, - { provide: LinkService, useValue: linkService }, + { provide: EPersonDataService, useValue: mockEpersonDataService }, CookieService, AuthService ], @@ -101,8 +108,14 @@ describe('AuthService test', () => { expect(authService.authenticate.bind(null, 'user', 'passwordwrong')).toThrow(); }); - it('should return the authenticated user object when user token is valid', () => { - authService.authenticatedUser(new AuthTokenInfo('test_token')).subscribe((user: EPerson) => { + it('should return the authenticated user href when user token is valid', () => { + authService.authenticatedUser(new AuthTokenInfo('test_token')).subscribe((userHref: string) => { + expect(userHref).toBeDefined(); + }); + }); + + it('should return the authenticated user', () => { + authService.retrieveAuthenticatedUserByHref(EPersonMock._links.self.href).subscribe((user: EPerson) => { expect(user).toBeDefined(); }); }); @@ -159,7 +172,7 @@ describe('AuthService test', () => { (state as any).core = Object.create({}); (state as any).core.auth = authenticatedState; }); - authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, linkService); + authService = new AuthService({}, window, undefined, authReqService, mockEpersonDataService, router, routeService, cookieService, store); })); it('should return true when user is logged in', () => { @@ -221,7 +234,7 @@ describe('AuthService test', () => { (state as any).core = Object.create({}); (state as any).core.auth = authenticatedState; }); - authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, linkService); + authService = new AuthService({}, window, undefined, authReqService, mockEpersonDataService, router, routeService, cookieService, store); storage = (authService as any).storage; routeServiceMock = TestBed.get(RouteService); routerStub = TestBed.get(Router); @@ -250,7 +263,7 @@ describe('AuthService test', () => { expect(storage.remove).toHaveBeenCalled(); }); - it ('should set redirect url to previous page', () => { + it('should set redirect url to previous page', () => { spyOn(routeServiceMock, 'getHistory').and.callThrough(); spyOn(routerStub, 'navigateByUrl'); authService.redirectAfterLoginSuccess(true); @@ -258,7 +271,7 @@ describe('AuthService test', () => { expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/collection/123'); }); - it ('should set redirect url to current page', () => { + it('should set redirect url to current page', () => { spyOn(routeServiceMock, 'getHistory').and.callThrough(); spyOn(routerStub, 'navigateByUrl'); authService.redirectAfterLoginSuccess(false); @@ -266,7 +279,7 @@ describe('AuthService test', () => { expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/home'); }); - it ('should redirect to / and not to /login', () => { + it('should redirect to / and not to /login', () => { spyOn(routeServiceMock, 'getHistory').and.returnValue(observableOf(['/login', '/login'])); spyOn(routerStub, 'navigateByUrl'); authService.redirectAfterLoginSuccess(true); @@ -274,7 +287,7 @@ describe('AuthService test', () => { expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/'); }); - it ('should redirect to / when no redirect url is found', () => { + it('should redirect to / when no redirect url is found', () => { spyOn(routeServiceMock, 'getHistory').and.returnValue(observableOf([''])); spyOn(routerStub, 'navigateByUrl'); authService.redirectAfterLoginSuccess(true); diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 1da9f63b27..fd5c98f789 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -4,12 +4,10 @@ import { HttpHeaders } from '@angular/common/http'; import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import { Observable, of as observableOf } from 'rxjs'; -import { distinctUntilChanged, filter, map, startWith, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, startWith, take, withLatestFrom } from 'rxjs/operators'; import { RouterReducerState } from '@ngrx/router-store'; import { select, Store } from '@ngrx/store'; import { CookieAttributes } from 'js-cookie'; -import { followLink } from '../../shared/utils/follow-link-config.model'; -import { LinkService } from '../cache/builders/link.service'; import { EPerson } from '../eperson/models/eperson.model'; import { AuthRequestService } from './auth-request.service'; @@ -24,6 +22,8 @@ import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth. import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util'; import { RouteService } from '../services/route.service'; +import { EPersonDataService } from '../eperson/eperson-data.service'; +import { getFirstSucceededRemoteDataPayload } from '../shared/operators'; export const LOGIN_ROUTE = '/login'; export const LOGOUT_ROUTE = '/logout'; @@ -44,13 +44,13 @@ export class AuthService { constructor(@Inject(REQUEST) protected req: any, @Inject(NativeWindowService) protected _window: NativeWindowRef, - protected authRequestService: AuthRequestService, @Optional() @Inject(RESPONSE) private response: any, + protected authRequestService: AuthRequestService, + protected epersonService: EPersonDataService, protected router: Router, protected routeService: RouteService, protected storage: CookieService, - protected store: Store, - protected linkService: LinkService + protected store: Store ) { this.store.pipe( select(isAuthenticated), @@ -123,10 +123,10 @@ export class AuthService { } /** - * Returns the authenticated user - * @returns {User} + * Returns the href link to authenticated user + * @returns {string} */ - public authenticatedUser(token: AuthTokenInfo): Observable { + public authenticatedUser(token: AuthTokenInfo): Observable { // Determine if the user has an existing auth session on the server const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); @@ -134,16 +134,25 @@ export class AuthService { headers = headers.append('Authorization', `Bearer ${token.accessToken}`); options.headers = headers; return this.authRequestService.getRequest('status', options).pipe( - map((status) => this.linkService.resolveLinks(status, followLink('eperson'))), - switchMap((status: AuthStatus) => { + map((status: AuthStatus) => { if (status.authenticated) { - return status.eperson.pipe(map((eperson) => eperson.payload)); + return status._links.eperson.href; } else { throw(new Error('Not authenticated')); } })) } + /** + * Returns the authenticated user + * @returns {User} + */ + public retrieveAuthenticatedUserByHref(userHref: string): Observable { + return this.epersonService.findByHref(userHref).pipe( + getFirstSucceededRemoteDataPayload() + ) + } + /** * Checks if token is present into browser storage and is valid. (NB Check is done only on SSR) */ diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts index eea2d83867..c8cba0206b 100644 --- a/src/app/core/auth/server-auth.service.ts +++ b/src/app/core/auth/server-auth.service.ts @@ -2,11 +2,9 @@ import { HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { filter, map, switchMap, take } from 'rxjs/operators'; +import { filter, map, take } from 'rxjs/operators'; import { isNotEmpty } from '../../shared/empty.util'; -import { followLink } from '../../shared/utils/follow-link-config.model'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; -import { EPerson } from '../eperson/models/eperson.model'; import { CheckAuthenticationTokenAction } from './auth.actions'; import { AuthService } from './auth.service'; import { AuthStatus } from './models/auth-status.model'; @@ -22,7 +20,7 @@ export class ServerAuthService extends AuthService { * Returns the authenticated user * @returns {User} */ - public authenticatedUser(token: AuthTokenInfo): Observable { + public authenticatedUser(token: AuthTokenInfo): Observable { // Determine if the user has an existing auth session on the server const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); @@ -35,10 +33,9 @@ export class ServerAuthService extends AuthService { options.headers = headers; return this.authRequestService.getRequest('status', options).pipe( - map((status) => this.linkService.resolveLinks(status, followLink('eperson'))), - switchMap((status: AuthStatus) => { + map((status: AuthStatus) => { if (status.authenticated) { - return status.eperson.pipe(map((eperson) => eperson.payload)); + return status._links.eperson.href; } else { throw(new Error('Not authenticated')); } diff --git a/src/app/shared/testing/auth-request-service-stub.ts b/src/app/shared/testing/auth-request-service-stub.ts index 82ce682a9b..b32b5395ba 100644 --- a/src/app/shared/testing/auth-request-service-stub.ts +++ b/src/app/shared/testing/auth-request-service-stub.ts @@ -5,8 +5,6 @@ import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { isNotEmpty } from '../empty.util'; import { EPersonMock } from './eperson-mock'; -import { RemoteData } from '../../core/data/remote-data'; -import { createSuccessfulRemoteDataObject$ } from './utils'; export class AuthRequestServiceStub { protected mockUser: EPerson = EPersonMock; @@ -28,7 +26,14 @@ export class AuthRequestServiceStub { if (this.validateToken(token)) { authStatusStub.authenticated = true; authStatusStub.token = this.mockTokenInfo; - authStatusStub.eperson = createSuccessfulRemoteDataObject$(this.mockUser); + authStatusStub._links = { + self: { + href: 'dspace.org/api/status', + }, + eperson: { + href: this.mockUser._links.self.href + } + }; } else { authStatusStub.authenticated = false; } @@ -47,7 +52,14 @@ export class AuthRequestServiceStub { if (this.validateToken(token)) { authStatusStub.authenticated = true; authStatusStub.token = this.mockTokenInfo; - authStatusStub.eperson = createSuccessfulRemoteDataObject$(this.mockUser); + authStatusStub._links = { + self: { + href: 'dspace.org/api/status', + }, + eperson: { + href: this.mockUser._links.self.href + } + }; } else { authStatusStub.authenticated = false; } diff --git a/src/app/shared/testing/auth-service-stub.ts b/src/app/shared/testing/auth-service-stub.ts index a6d24d5c8b..a3c6351ccd 100644 --- a/src/app/shared/testing/auth-service-stub.ts +++ b/src/app/shared/testing/auth-service-stub.ts @@ -3,7 +3,6 @@ import { AuthStatus } from '../../core/auth/models/auth-status.model'; import { AuthTokenInfo } from '../../core/auth/models/auth-token-info.model'; import { EPersonMock } from './eperson-mock'; import { EPerson } from '../../core/eperson/models/eperson.model'; -import { RemoteData } from '../../core/data/remote-data'; import { createSuccessfulRemoteDataObject$ } from './utils'; export class AuthServiceStub { @@ -30,14 +29,18 @@ export class AuthServiceStub { } } - public authenticatedUser(token: AuthTokenInfo): Observable { + public authenticatedUser(token: AuthTokenInfo): Observable { if (token.accessToken === 'token_test') { - return observableOf(EPersonMock); + return observableOf(EPersonMock._links.self.href); } else { throw(new Error('Message Error test')); } } + public retrieveAuthenticatedUserByHref(href: string): Observable { + return observableOf(EPersonMock); + } + public buildAuthHeader(token?: AuthTokenInfo): string { return `Bearer ${token.accessToken}`; }