From c8a1fe086074e13c1086a4e1a26fe7c8e8855fe2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 3 May 2018 19:08:07 +0200 Subject: [PATCH] Removed use of platform service and created different service for SSR and CSR --- src/app/app.component.spec.ts | 26 ++----- src/app/app.component.ts | 27 +++---- src/app/core/auth/auth.effects.ts | 2 +- src/app/core/auth/auth.service.ts | 36 +++++---- src/app/core/auth/server-auth.service.ts | 77 +++++++++++++++++++ src/app/core/core.module.ts | 4 - src/app/shared/log-in/log-in.component.html | 8 +- src/app/shared/log-in/log-in.component.ts | 19 +++-- .../shared/services/client-cookie.service.ts | 25 ++++++ .../shared/services/cookie.service.spec.ts | 4 +- src/app/shared/services/cookie.service.ts | 58 +++++--------- .../shared/services/server-cookie.service.ts | 30 ++++++++ src/modules/app/browser-app.module.ts | 12 +++ src/modules/app/server-app.module.ts | 12 +++ 14 files changed, 234 insertions(+), 106 deletions(-) create mode 100644 src/app/core/auth/server-auth.service.ts create mode 100644 src/app/shared/services/client-cookie.service.ts create mode 100644 src/app/shared/services/server-cookie.service.ts diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index de9b121bff..af58295479 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,22 +1,13 @@ -import { - async, - ComponentFixture, - inject, - TestBed -} from '@angular/core/testing'; +import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'; -import { - CUSTOM_ELEMENTS_SCHEMA, - DebugElement -} from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core'; import { CommonModule } from '@angular/common'; import { By } from '@angular/platform-browser'; -import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { Store, StoreModule } from '@ngrx/store'; - // Load the implementations that should be tested import { AppComponent } from './app.component'; @@ -25,13 +16,11 @@ import { HostWindowResizeAction } from './shared/host-window.actions'; import { MetadataService } from './core/metadata/metadata.service'; -import { GLOBAL_CONFIG, ENV_CONFIG } from '../config'; +import { ENV_CONFIG, GLOBAL_CONFIG } from '../config'; import { NativeWindowRef, NativeWindowService } from './shared/services/window.service'; import { MockTranslateLoader } from './shared/mocks/mock-translate-loader'; import { MockMetadataService } from './shared/mocks/mock-metadata-service'; -import { PlatformServiceStub } from './shared/testing/platform-service-stub'; -import { PlatformService } from './shared/services/platform.service'; let comp: AppComponent; let fixture: ComponentFixture; @@ -55,10 +44,9 @@ describe('App component', () => { ], declarations: [AppComponent], // declare the test component providers: [ - { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, - { provide: NativeWindowService, useValue: new NativeWindowRef() }, - { provide: MetadataService, useValue: new MockMetadataService() }, - { provide: PlatformService, useValue: new PlatformServiceStub() }, + {provide: GLOBAL_CONFIG, useValue: ENV_CONFIG}, + {provide: NativeWindowService, useValue: new NativeWindowRef()}, + {provide: MetadataService, useValue: new MockMetadataService()}, AppComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA] diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4ce1df54b0..7a0dc59d23 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,11 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - HostListener, - Inject, - OnInit, - ViewEncapsulation -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, ViewEncapsulation } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -17,9 +10,8 @@ import { MetadataService } from './core/metadata/metadata.service'; import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowState } from './shared/host-window.reducer'; import { NativeWindowRef, NativeWindowService } from './shared/services/window.service'; -import { CheckAuthenticationTokenAction } from './core/auth/auth.actions'; import { isAuthenticated } from './core/auth/selectors'; -import { PlatformService } from './shared/services/platform.service'; +import { AuthService } from './core/auth/auth.service'; @Component({ selector: 'ds-app', @@ -36,7 +28,7 @@ export class AppComponent implements OnInit { private translate: TranslateService, private store: Store, private metadata: MetadataService, - private platformService: PlatformService + private authService: AuthService ) { // this language will be used as a fallback when a translation isn't found in the current language translate.setDefaultLang('en'); @@ -55,12 +47,13 @@ export class AppComponent implements OnInit { const color: string = this.config.production ? 'red' : 'green'; console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`); this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight); - if (this.platformService.isServer) { - this.store.select(isAuthenticated) - .take(1) - .filter((authenticated) => !authenticated) - .subscribe((authenticated) => this.store.dispatch(new CheckAuthenticationTokenAction())); - } + + // Whether is not authenticathed try to retrieve a possible stored auth token + this.store.select(isAuthenticated) + .take(1) + .filter((authenticated) => !authenticated) + .subscribe((authenticated) => this.authService.checksAuthenticationToken()); + } @HostListener('window:resize', ['$event']) diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index 122975bd96..f3bebebbcd 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -71,7 +71,7 @@ export class AuthEffects { public checkToken: Observable = this.actions$ .ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN) .switchMap(() => { - return this.authService.checkAuthenticationToken() + return this.authService.hasValidAuthenticationToken() .map((token: AuthTokenInfo) => new AuthenticatedAction(token)) .catch((error) => Observable.of(new CheckAuthenticationTokenErrorAction())); }); diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 58e80713e2..cbd532b467 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -15,11 +15,15 @@ import { CookieService } from '../../shared/services/cookie.service'; import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors'; import { AppState, routerStateSelector } from '../../app.reducer'; import { Store } from '@ngrx/store'; -import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions'; +import { + CheckAuthenticationTokenAction, + ResetAuthenticationMessagesAction, + SetRedirectUrlAction +} from './auth.actions'; import { RouterReducerState } from '@ngrx/router-store'; import { CookieAttributes } from 'js-cookie'; import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service'; -import { PlatformService } from '../../shared/services/platform.service'; +import { REQUEST } from '@nguniversal/express-engine/tokens'; export const LOGIN_ROUTE = '/login'; @@ -35,14 +39,14 @@ export class AuthService { * True if authenticated * @type boolean */ - private _authenticated: boolean; + protected _authenticated: boolean; - constructor(@Inject(NativeWindowService) private _window: NativeWindowRef, - private authRequestService: AuthRequestService, - private platform: PlatformService, - private router: Router, - private storage: CookieService, - private store: Store) { + constructor(@Inject(REQUEST) protected req: any, + @Inject(NativeWindowService) protected _window: NativeWindowRef, + protected authRequestService: AuthRequestService, + protected router: Router, + protected storage: CookieService, + protected store: Store) { this.store.select(isAuthenticated) .startWith(false) .subscribe((authenticated: boolean) => this._authenticated = authenticated); @@ -130,10 +134,17 @@ export class AuthService { }); } + /** + * Checks if token is present into browser storage and is valid. (NB Check is done only on SSR) + */ + public checksAuthenticationToken() { + return + } + /** * Checks if token is present into storage and is not expired */ - public checkAuthenticationToken(): Observable { + public hasValidAuthenticationToken(): Observable { return this.store.select(getAuthenticationToken) .take(1) .map((authTokenInfo: AuthTokenInfo) => { @@ -317,10 +328,9 @@ export class AuthService { this.getRedirectUrl() .first() .subscribe((redirectUrl) => { + console.log('Browser'); if (isNotEmpty(redirectUrl)) { - if (this.platform.isBrowser) { - this.clearRedirectUrl(); - } + this.clearRedirectUrl(); // override the route reuse strategy this.router.routeReuseStrategy.shouldReuseRoute = () => { diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts new file mode 100644 index 0000000000..1d240455f3 --- /dev/null +++ b/src/app/core/auth/server-auth.service.ts @@ -0,0 +1,77 @@ +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +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 { AuthTokenInfo } from './models/auth-token-info.model'; +import { CheckAuthenticationTokenAction } from './auth.actions'; + +/** + * The auth service. + */ +@Injectable() +export class ServerAuthService extends AuthService { + + /** + * Authenticate the user + * + * @param {string} user The user name + * @param {string} password The user's password + * @returns {Observable} The authenticated user observable. + */ + public authenticate(user: string, password: string): Observable { + // Attempt authenticating the user using the supplied credentials. + const body = encodeURI(`password=${password}&user=${user}`); + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + + // NB this could be use to avoid the problem with the authentication is case the UI is rendered by Angular Universal. + const clientIp = this.req.connection.remoteAddress; + + headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); + options.headers = headers; + return this.authRequestService.postToEndpoint('login', body, options) + .map((status: AuthStatus) => { + if (status.authenticated) { + return status; + } else { + throw(new Error('Invalid email or password')); + } + }) + + } + + /** + * Checks if token is present into browser storage and is valid. (NB Check is done only on SSR) + */ + public checksAuthenticationToken() { + this.store.dispatch(new CheckAuthenticationTokenAction()) + } + + /** + * Redirect to the route navigated before the login + */ + public redirectToPreviousUrl() { + this.getRedirectUrl() + .first() + .subscribe((redirectUrl) => { + console.log('server side'); + if (isNotEmpty(redirectUrl)) { + // override the route reuse strategy + this.router.routeReuseStrategy.shouldReuseRoute = () => { + return false; + }; + this.router.navigated = false; + const url = decodeURIComponent(redirectUrl); + this.router.navigateByUrl(url); + } else { + this.router.navigate(['/']); + } + }) + + } + +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 9c57026975..068a698565 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -38,13 +38,11 @@ import { SubmissionDefinitionsConfigService } from './config/submission-definiti import { SubmissionFormsConfigService } from './config/submission-forms-config.service'; import { SubmissionSectionsConfigService } from './config/submission-sections-config.service'; import { UUIDService } from './shared/uuid.service'; -import { AuthService } from './auth/auth.service'; import { AuthenticatedGuard } from './auth/authenticated.guard'; import { AuthRequestService } from './auth/auth-request.service'; import { AuthResponseParsingService } from './auth/auth-response-parsing.service'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './auth/auth.interceptor'; -import { CookieService } from '../shared/services/cookie.service'; import { PlatformService } from '../shared/services/platform.service'; const IMPORTS = [ @@ -66,10 +64,8 @@ const PROVIDERS = [ AuthenticatedGuard, AuthRequestService, AuthResponseParsingService, - AuthService, CommunityDataService, CollectionDataService, - CookieService, DSOResponseParsingService, DSpaceRESTv2Service, HostWindowService, diff --git a/src/app/shared/log-in/log-in.component.html b/src/app/shared/log-in/log-in.component.html index bcb6b49c9e..fe9a506e71 100644 --- a/src/app/shared/log-in/log-in.component.html +++ b/src/app/shared/log-in/log-in.component.html @@ -1,5 +1,5 @@ - -