Finalized auth module

This commit is contained in:
Giuseppe Digilio
2018-02-22 15:47:15 +01:00
parent 4488a450c0
commit 2637f1c28e
12 changed files with 143 additions and 114 deletions

View File

@@ -1,15 +0,0 @@
.login-container {
height: 100%;
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}

View File

@@ -25,7 +25,8 @@ export const AuthActionTypes = {
LOG_OUT_SUCCESS: type('dspace/auth/LOG_OUT_SUCCESS'),
REGISTRATION: type('dspace/auth/REGISTRATION'),
REGISTRATION_ERROR: type('dspace/auth/REGISTRATION_ERROR'),
REGISTRATION_SUCCESS: type('dspace/auth/REGISTRATION_SUCCESS')
REGISTRATION_SUCCESS: type('dspace/auth/REGISTRATION_SUCCESS'),
SET_REDIRECT_URL: type('dspace/auth/SET_REDIRECT_URL'),
};
/* tslint:disable:max-classes-per-file */
@@ -251,6 +252,20 @@ export class ResetAuthenticationMessagesAction implements Action {
public type: string = AuthActionTypes.RESET_MESSAGES;
}
/**
* Change the redirect url.
* @class SetRedirectUrlAction
* @implements {Action}
*/
export class SetRedirectUrlAction implements Action {
public type: string = AuthActionTypes.SET_REDIRECT_URL;
payload: string;
constructor(url: string) {
this.payload = url ;
}
}
/* tslint:enable:max-classes-per-file */
/**

View File

@@ -2,7 +2,7 @@
import {
AuthActions, AuthActionTypes, AuthenticatedSuccessAction, AuthenticationErrorAction,
AuthenticationSuccessAction, LogOutErrorAction, RedirectWhenAuthenticationIsRequiredAction,
RedirectWhenTokenExpiredAction
RedirectWhenTokenExpiredAction, SetRedirectUrlAction
} from './auth.actions';
// import models
@@ -29,6 +29,9 @@ export interface AuthState {
// info message
info?: string;
// redirect url after login
redirectUrl?: string;
// the authenticated user
user?: Eperson;
}
@@ -39,7 +42,7 @@ export interface AuthState {
const initialState: AuthState = {
authenticated: false,
loaded: false,
loading: false
loading: false,
};
/**
@@ -137,13 +140,18 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
case AuthActionTypes.RESET_MESSAGES:
return Object.assign({}, state, {
authenticated: false,
authenticated: state.authenticated,
error: undefined,
loaded: false,
loading: false,
loaded: state.loaded,
loading: state.loading,
info: undefined,
});
case AuthActionTypes.SET_REDIRECT_URL:
return Object.assign({}, state, {
redirectUrl: (action as SetRedirectUrlAction).payload,
});
default:
return state;
}
@@ -155,7 +163,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
* @param {State} state
* @returns {boolean}
*/
export const isAuthenticated = (state: AuthState) => state.authenticated;
export const _isAuthenticated = (state: AuthState) => state.authenticated;
/**
* Returns true if the authenticated has loaded.
@@ -163,7 +171,7 @@ export const isAuthenticated = (state: AuthState) => state.authenticated;
* @param {State} state
* @returns {boolean}
*/
export const isAuthenticatedLoaded = (state: AuthState) => state.loaded;
export const _isAuthenticatedLoaded = (state: AuthState) => state.loaded;
/**
* Return the users state
@@ -171,23 +179,23 @@ export const isAuthenticatedLoaded = (state: AuthState) => state.loaded;
* @param {State} state
* @returns {User}
*/
export const getAuthenticatedUser = (state: AuthState) => state.user;
export const _getAuthenticatedUser = (state: AuthState) => state.user;
/**
* Returns the authentication error.
* @function getAuthenticationError
* @param {State} state
* @returns {String}
* @returns {string}
*/
export const getAuthenticationError = (state: AuthState) => state.error;
export const _getAuthenticationError = (state: AuthState) => state.error;
/**
* Returns the authentication info message.
* @function getAuthenticationInfo
* @param {State} state
* @returns {String}
* @returns {string}
*/
export const getAuthenticationInfo = (state: AuthState) => state.info;
export const _getAuthenticationInfo = (state: AuthState) => state.info;
/**
* Returns true if request is in progress.
@@ -195,20 +203,28 @@ export const getAuthenticationInfo = (state: AuthState) => state.info;
* @param {State} state
* @returns {boolean}
*/
export const isLoading = (state: AuthState) => state.loading;
export const _isLoading = (state: AuthState) => state.loading;
/**
* Returns the sign out error.
* @function getLogOutError
* @param {State} state
* @returns {Error}
* @returns {string}
*/
export const getLogOutError = (state: AuthState) => state.error;
export const _getLogOutError = (state: AuthState) => state.error;
/**
* Returns the sign up error.
* @function getRegistrationError
* @param {State} state
* @returns {Error}
* @returns {string}
*/
export const getRegistrationError = (state: AuthState) => state.error;
export const _getRegistrationError = (state: AuthState) => state.error;
/**
* Returns the redirect url.
* @function getRedirectUrl
* @param {State} state
* @returns {string}
*/
export const _getRedirectUrl = (state: AuthState) => state.redirectUrl;

View File

@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { filter, map, withLatestFrom } from 'rxjs/operators';
import { Eperson } from '../eperson/models/eperson.model';
import { AuthRequestService } from './auth-request.service';
@@ -10,10 +11,10 @@ import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
import { isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
import { CookieService } from '../../shared/services/cookie.service';
import { isAuthenticated } from './selectors';
import { getRedirectUrl, isAuthenticated } from './selectors';
import { AppState, routerStateSelector } from '../../app.reducer';
import { Store } from '@ngrx/store';
import { ResetAuthenticationMessagesAction } from './auth.actions';
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
import { RouterReducerState } from '@ngrx/router-store';
import { Router } from '@angular/router';
@@ -30,12 +31,6 @@ export class AuthService {
*/
private _authenticated: boolean;
/**
* The url to redirect after login
* @type string
*/
private _redirectUrl: string;
constructor(private authRequestService: AuthRequestService,
private router: Router,
private storage: CookieService,
@@ -46,16 +41,18 @@ export class AuthService {
// If current route is different from the one setted in authentication guard
// and is not the login route, clear redirect url and messages
this.store.select(routerStateSelector)
.filter((routerState: RouterReducerState) => isNotUndefined(routerState))
.filter((routerState: RouterReducerState) =>
(routerState.state.url !== LOGIN_ROUTE)
&& isNotEmpty(this._redirectUrl)
&& (routerState.state.url !== this._redirectUrl))
.distinctUntilChanged()
const routeUrlObs = this.store.select(routerStateSelector)
.filter((routerState: RouterReducerState) => isNotUndefined(routerState) && isNotUndefined(routerState.state) )
.filter((routerState: RouterReducerState) => (routerState.state.url !== LOGIN_ROUTE))
.map((routerState: RouterReducerState) => routerState.state.url);
const redirectUrlObs = this.getRedirectUrl();
routeUrlObs.pipe(
withLatestFrom(redirectUrlObs),
map(([routeUrl, redirectUrl]) => [routeUrl, redirectUrl])
).filter(([routeUrl, redirectUrl]) => isNotEmpty(redirectUrl) && (routeUrl !== redirectUrl))
.subscribe(() => {
this._redirectUrl = '';
})
this.setRedirectUrl('');
});
}
/**
@@ -135,7 +132,7 @@ export class AuthService {
// Normally you would do an HTTP request to POST the user
// details and then return the new user object
// but, let's just return the new user for this example.
this._authenticated = true;
// this._authenticated = true;
return Observable.of(user);
}
@@ -210,14 +207,18 @@ export class AuthService {
* Redirect to the route navigated before the login
*/
public redirectToPreviousUrl() {
if (isNotEmpty(this._redirectUrl)) {
const url = this._redirectUrl;
this.getRedirectUrl()
.first()
.subscribe((redirectUrl) => {
if (isNotEmpty(redirectUrl)) {
// Clear url
this._redirectUrl = null;
this.router.navigate([url]);
this.setRedirectUrl(undefined);
this.router.navigate([decodeURI(redirectUrl)]);
} else {
this.router.navigate(['/']);
}
})
}
/**
@@ -227,7 +228,7 @@ export class AuthService {
this.store.select(routerStateSelector)
.take(1)
.subscribe((router) => {
// TODO Chack a way to hard refresh the same route
// TODO Check a way to hard refresh the same route
// this.router.navigate([router.state.url], { replaceUrl: true });
this.router.navigate(['/']);
})
@@ -236,14 +237,14 @@ export class AuthService {
/**
* Get redirect url
*/
get redirectUrl(): string {
return this._redirectUrl;
getRedirectUrl(): Observable<string> {
return this.store.select(getRedirectUrl);
}
/**
* Set redirect url
*/
set redirectUrl(value: string) {
this._redirectUrl = value;
setRedirectUrl(value: string) {
this.store.dispatch(new SetRedirectUrlAction(encodeURI(value)));
}
}

View File

@@ -59,9 +59,9 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
// .filter(() => isEmpty(this.router.routerState.snapshot.url) || this.router.routerState.snapshot.url === url)
.take(1)
.subscribe((authenticated) => {
console.log('handleAuth', url, this.router.routerState.snapshot.url);
if (!authenticated) {
this.authService.redirectUrl = url;
console.log('set redirect ', url);
this.authService.setRedirectUrl(url);
this.store.dispatch(new RedirectWhenAuthenticationIsRequiredAction('Login required'));
}
});

View File

@@ -27,7 +27,7 @@ export const getAuthState = (state: any) => state.core.auth;
* @param {any} props
* @return {User}
*/
export const getAuthenticatedUser = createSelector(getAuthState, auth.getAuthenticatedUser);
export const getAuthenticatedUser = createSelector(getAuthState, auth._getAuthenticatedUser);
/**
* Returns the authentication error.
@@ -36,7 +36,7 @@ export const getAuthenticatedUser = createSelector(getAuthState, auth.getAuthent
* @param {any} props
* @return {Error}
*/
export const getAuthenticationError = createSelector(getAuthState, auth.getAuthenticationError);
export const getAuthenticationError = createSelector(getAuthState, auth._getAuthenticationError);
/**
* Returns the authentication info message.
@@ -45,7 +45,7 @@ export const getAuthenticationError = createSelector(getAuthState, auth.getAuthe
* @param {any} props
* @return {string}
*/
export const getAuthenticationInfo = createSelector(getAuthState, auth.getAuthenticationInfo);
export const getAuthenticationInfo = createSelector(getAuthState, auth._getAuthenticationInfo);
/**
* Returns true if the user is authenticated
@@ -54,7 +54,7 @@ export const getAuthenticationInfo = createSelector(getAuthState, auth.getAuthen
* @param {any} props
* @return {boolean}
*/
export const isAuthenticated = createSelector(getAuthState, auth.isAuthenticated);
export const isAuthenticated = createSelector(getAuthState, auth._isAuthenticated);
/**
* Returns true if the user is authenticated
@@ -63,7 +63,7 @@ export const isAuthenticated = createSelector(getAuthState, auth.isAuthenticated
* @param {any} props
* @return {boolean}
*/
export const isAuthenticatedLoaded = createSelector(getAuthState, auth.isAuthenticatedLoaded);
export const isAuthenticatedLoaded = createSelector(getAuthState, auth._isAuthenticatedLoaded);
/**
* Returns true if the authentication request is loading.
@@ -72,7 +72,7 @@ export const isAuthenticatedLoaded = createSelector(getAuthState, auth.isAuthent
* @param {any} props
* @return {boolean}
*/
export const isAuthenticationLoading = createSelector(getAuthState, auth.isLoading);
export const isAuthenticationLoading = createSelector(getAuthState, auth._isLoading);
/**
* Returns the log out error.
@@ -81,7 +81,7 @@ export const isAuthenticationLoading = createSelector(getAuthState, auth.isLoadi
* @param {any} props
* @return {Error}
*/
export const getLogOutError = createSelector(getAuthState, auth.getLogOutError);
export const getLogOutError = createSelector(getAuthState, auth._getLogOutError);
/**
* Returns the registration error.
@@ -90,4 +90,13 @@ export const getLogOutError = createSelector(getAuthState, auth.getLogOutError);
* @param {any} props
* @return {Error}
*/
export const getRegistrationError = createSelector(getAuthState, auth.getRegistrationError);
export const getRegistrationError = createSelector(getAuthState, auth._getRegistrationError);
/**
* Returns the redirect url..
* @function getRedirectUrl
* @param {AuthState} state
* @param {any} props
* @return {string}
*/
export const getRedirectUrl = createSelector(getAuthState, auth._getRedirectUrl);

View File

@@ -1,11 +1,9 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { RouterReducerState } from '@ngrx/router-store';
import { Store } from '@ngrx/store';
import { fadeInOut, fadeOut } from '../animations/fade';
import { CoreState } from '../../core/core.reducers';
import { HostWindowService } from '../host-window.service';
import { AppState, routerStateSelector } from '../../app.reducer';
import { hasValue, isNotUndefined } from '../empty.util';
@@ -32,8 +30,7 @@ export class AuthNavMenuComponent implements OnDestroy, OnInit {
protected subs: Subscription[] = [];
constructor(
private store: Store<AppState>,
constructor(private store: Store<AppState>,
public windowService: HostWindowService) {
}
@@ -44,7 +41,7 @@ export class AuthNavMenuComponent implements OnDestroy, OnInit {
this.user = this.store.select(getAuthenticatedUser);
this.subs.push(this.store.select(routerStateSelector)
.filter((router: RouterReducerState) => isNotUndefined(router))
.filter((router: RouterReducerState) => isNotUndefined(router) && isNotUndefined(router.state))
.subscribe((router: RouterReducerState) => {
this.showAuth = router.state.url !== '/login';
}));

View File

@@ -8,8 +8,7 @@
formControlName="email"
placeholder="{{'login.form.email' | translate}}"
required
type="email"
(input)="resetErrorOrMessage($event)">
type="email">
<label for="inputPassword" class="sr-only">{{"login.form.password" | translate}}</label>
<input id="inputPassword"
autocomplete="off"
@@ -17,9 +16,8 @@
placeholder="{{'login.form.password' | translate}}"
formControlName="password"
required
type="password"
(input)="resetErrorOrMessage($event)">
<div *ngIf="(error | async) && hasError" class="alert alert-danger" role="alert" @fadeOut>{{ error | async }}</div>
type="password">
<div *ngIf="platform.isBrowser && (error | async) && hasError" class="alert alert-danger" role="alert" @fadeOut>{{ error | async }}</div>
<div *ngIf="(message | async) && hasMessage" class="alert alert-info" role="alert" @fadeOut>{{ message | async }}</div>
<button class="btn btn-lg btn-primary btn-block mt-3" type="submit" [disabled]="!form.valid">{{"login.form.submit" | translate}}</button>
<div class="dropdown-divider"></div>

View File

@@ -1,25 +1,25 @@
/* tslint:disable:no-unused-variable */
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from "@angular/core";
import { ComponentFixture, TestBed, async } from "@angular/core/testing";
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { MaterialModule } from "@angular/material";
import { By } from "@angular/platform-browser";
import { Store, StoreModule } from "@ngrx/store";
import { go } from "@ngrx/router-store";
/*import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MaterialModule } from '@angular/material';
import { By } from '@angular/platform-browser';
import { Store, StoreModule } from '@ngrx/store';
import { go } from '@ngrx/router-store';
// reducers
import { reducer } from "../../app.reducers";
import { reducer } from '../../app.reducers';
// models
import { User } from "../../core/models/user";
import { User } from '../../core/models/user';
// services
import { MOCK_USER } from "../../core/services/user.service";
import { MOCK_USER } from '../../core/services/user.service';
// this component to test
import { LogInComponent } from "./log-in.component";
import { LogInComponent } from './log-in.component';
describe("LogInComponent", () => {
describe('LogInComponent', () => {
let component: LogInComponent;
let fixture: ComponentFixture<LogInComponent>;
@@ -65,23 +65,23 @@ describe("LogInComponent", () => {
});
});
it("should create a FormGroup comprised of FormControls", () => {
it('should create a FormGroup comprised of FormControls', () => {
fixture.detectChanges();
expect(component.form instanceof FormGroup).toBe(true);
});
it("should authenticate", () => {
it('should authenticate', () => {
fixture.detectChanges();
// set FormControl values
component.form.controls["email"].setValue(user.email);
component.form.controls["password"].setValue(user.password);
component.form.controls['email'].setValue(user.email);
component.form.controls['password'].setValue(user.password);
// submit form
component.submit();
// verify Store.dispatch() is invoked
expect(page.navigateSpy.calls.any()).toBe(true, "Store.dispatch not invoked");
expect(page.navigateSpy.calls.any()).toBe(true, 'Store.dispatch not invoked');
});
});
@@ -90,6 +90,7 @@ describe("LogInComponent", () => {
*
* @class Page
*/
/*
class Page {
public emailInput: HTMLInputElement;
@@ -102,15 +103,16 @@ class Page {
const store = injector.get(Store);
// add spies
this.navigateSpy = spyOn(store, "dispatch");
this.navigateSpy = spyOn(store, 'dispatch');
}
public addPageElements() {
const emailInputSelector = "input[formcontrolname=\"email\"]";
const emailInputSelector = 'input[formcontrolname=\'email\']';
// console.log(this.fixture.debugElement.query(By.css(emailInputSelector)));
this.emailInput = this.fixture.debugElement.query(By.css(emailInputSelector)).nativeElement;
const passwordInputSelector = "input[formcontrolname=\"password\"]";
const passwordInputSelector = 'input[formcontrolname=\'password\']';
this.passwordInput = this.fixture.debugElement.query(By.css(passwordInputSelector)).nativeElement;
}
}
*/

View File

@@ -23,6 +23,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 { PlatformService } from '../services/platform.service';
/**
* /users/sign-in
@@ -87,6 +88,7 @@ export class LogInComponent implements OnDestroy, OnInit {
constructor(
private authService: AuthService,
private formBuilder: FormBuilder,
private platform: PlatformService,
private store: Store<CoreState>
) { }
@@ -160,6 +162,7 @@ export class LogInComponent implements OnDestroy, OnInit {
* @method submit
*/
public submit() {
this.resetErrorOrMessage();
// get email and password values
const email: string = this.form.get('email').value;
const password: string = this.form.get('password').value;

View File

@@ -1,19 +1,19 @@
/* tslint:disable:no-unused-variable */
import { DebugElement, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { Router } from "@angular/router";
/*import { DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
// import ngrx
import { Store, StoreModule } from "@ngrx/store";
import { Store, StoreModule } from '@ngrx/store';
// reducers
import { reducer } from "../../app.reducers";
import { reducer } from '../../app.reducers';
// test this component
import { SignOutComponent } from "./log-out.component";
import { SignOutComponent } from './log-out.component';
describe("Component: Signout", () => {
describe('Component: Signout', () => {
let component: SignOutComponent;
let fixture: ComponentFixture<SignOutComponent>;
@@ -39,7 +39,8 @@ describe("Component: Signout", () => {
component = fixture.componentInstance;
}));
it("should create an instance", () => {
it('should create an instance', () => {
expect(component).toBeTruthy();
});
});
*/

View File

@@ -3,6 +3,8 @@ export const ROUTES: string[] = [
'items/:id',
'collections/:id',
'communities/:id',
'login',
'logout',
'search',
'**'
];