mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Auth module improvement
This commit is contained in:
@@ -28,3 +28,5 @@ export const appReducers: ActionReducerMap<AppState> = {
|
|||||||
searchSidebar: sidebarReducer,
|
searchSidebar: sidebarReducer,
|
||||||
searchFilter: filterReducer
|
searchFilter: filterReducer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const routerStateSelector = (state: AppState) => state.router;
|
||||||
|
@@ -17,6 +17,7 @@ export const AuthActionTypes = {
|
|||||||
AUTHENTICATED_SUCCESS: type('dspace/auth/AUTHENTICATED_SUCCESS'),
|
AUTHENTICATED_SUCCESS: type('dspace/auth/AUTHENTICATED_SUCCESS'),
|
||||||
CHECK_AUTHENTICATION_TOKEN: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN'),
|
CHECK_AUTHENTICATION_TOKEN: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN'),
|
||||||
CHECK_AUTHENTICATION_TOKEN_ERROR: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_ERROR'),
|
CHECK_AUTHENTICATION_TOKEN_ERROR: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_ERROR'),
|
||||||
|
REDIRECT: type('dspace/auth/REDIRECT'),
|
||||||
RESET_ERROR: type('dspace/auth/RESET_ERROR'),
|
RESET_ERROR: type('dspace/auth/RESET_ERROR'),
|
||||||
LOG_OUT: type('dspace/auth/LOG_OUT'),
|
LOG_OUT: type('dspace/auth/LOG_OUT'),
|
||||||
LOG_OUT_ERROR: type('dspace/auth/LOG_OUT_ERROR'),
|
LOG_OUT_ERROR: type('dspace/auth/LOG_OUT_ERROR'),
|
||||||
@@ -136,15 +137,6 @@ export class CheckAuthenticationTokenErrorAction implements Action {
|
|||||||
public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_ERROR;
|
public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset error.
|
|
||||||
* @class ResetAuthenticationErrorAction
|
|
||||||
* @implements {Action}
|
|
||||||
*/
|
|
||||||
export class ResetAuthenticationErrorAction implements Action {
|
|
||||||
public type: string = AuthActionTypes.RESET_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign out.
|
* Sign out.
|
||||||
* @class LogOutAction
|
* @class LogOutAction
|
||||||
@@ -179,6 +171,20 @@ export class LogOutSuccessAction implements Action {
|
|||||||
constructor(public payload?: any) {}
|
constructor(public payload?: any) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to login page when token is expired.
|
||||||
|
* @class RedirectWhenTokenExpiredAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class RedirectWhenTokenExpiredAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.REDIRECT;
|
||||||
|
payload: string;
|
||||||
|
|
||||||
|
constructor(message: string) {
|
||||||
|
this.payload = message ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign up.
|
* Sign up.
|
||||||
* @class RegistrationAction
|
* @class RegistrationAction
|
||||||
@@ -221,6 +227,15 @@ export class RegistrationSuccessAction implements Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset error.
|
||||||
|
* @class ResetAuthenticationErrorAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class ResetAuthenticationErrorAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.RESET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -237,6 +252,7 @@ export type AuthActions
|
|||||||
| AuthenticationSuccessAction
|
| AuthenticationSuccessAction
|
||||||
| CheckAuthenticationTokenAction
|
| CheckAuthenticationTokenAction
|
||||||
| CheckAuthenticationTokenErrorAction
|
| CheckAuthenticationTokenErrorAction
|
||||||
|
| RedirectWhenTokenExpiredAction
|
||||||
| RegistrationAction
|
| RegistrationAction
|
||||||
| RegistrationErrorAction
|
| RegistrationErrorAction
|
||||||
| RegistrationSuccessAction;
|
| RegistrationSuccessAction;
|
||||||
|
@@ -16,7 +16,7 @@ import {
|
|||||||
AuthenticatedErrorAction,
|
AuthenticatedErrorAction,
|
||||||
AuthenticatedSuccessAction,
|
AuthenticatedSuccessAction,
|
||||||
AuthenticationErrorAction,
|
AuthenticationErrorAction,
|
||||||
AuthenticationSuccessAction, CheckAuthenticationTokenAction, CheckAuthenticationTokenErrorAction, LogOutAction,
|
AuthenticationSuccessAction, CheckAuthenticationTokenErrorAction,
|
||||||
LogOutErrorAction,
|
LogOutErrorAction,
|
||||||
LogOutSuccessAction, RegistrationAction,
|
LogOutSuccessAction, RegistrationAction,
|
||||||
RegistrationErrorAction,
|
RegistrationErrorAction,
|
||||||
@@ -87,7 +87,7 @@ export class AuthEffects {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* When the store is rehydrated in the browser,
|
* When the store is rehydrated in the browser,
|
||||||
* clear a possible invalid token
|
* clear a possible invalid token or authentication errors
|
||||||
*/
|
*/
|
||||||
@Effect({dispatch: false})
|
@Effect({dispatch: false})
|
||||||
public clearInvalidTokenOnRehydrate = this.actions$
|
public clearInvalidTokenOnRehydrate = this.actions$
|
||||||
@@ -96,7 +96,8 @@ export class AuthEffects {
|
|||||||
return this.store.select(isAuthenticated)
|
return this.store.select(isAuthenticated)
|
||||||
.take(1)
|
.take(1)
|
||||||
.filter((authenticated) => !authenticated)
|
.filter((authenticated) => !authenticated)
|
||||||
.do(() => this.authService.removeToken());
|
.do(() => this.authService.removeToken())
|
||||||
|
.do(() => this.authService.resetAuthenticationError());
|
||||||
});
|
});
|
||||||
|
|
||||||
@Effect()
|
@Effect()
|
||||||
@@ -113,10 +114,16 @@ export class AuthEffects {
|
|||||||
.ofType(AuthActionTypes.LOG_OUT_SUCCESS)
|
.ofType(AuthActionTypes.LOG_OUT_SUCCESS)
|
||||||
.do((action: LogOutSuccessAction) => this.authService.removeToken());
|
.do((action: LogOutSuccessAction) => this.authService.removeToken());
|
||||||
|
|
||||||
|
@Effect({dispatch: false})
|
||||||
|
public redirectToLogin: Observable<Action> = this.actions$
|
||||||
|
.ofType(AuthActionTypes.REDIRECT)
|
||||||
|
.do(() => this.authService.redirectToLogin());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {Actions} actions$
|
* @param {Actions} actions$
|
||||||
* @param {AuthService} authService
|
* @param {AuthService} authService
|
||||||
|
* @param {Store} store
|
||||||
*/
|
*/
|
||||||
constructor(private actions$: Actions,
|
constructor(private actions$: Actions,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
@@ -14,10 +14,13 @@ import { AuthType } from './auth-type';
|
|||||||
import { ResourceType } from '../shared/resource-type';
|
import { ResourceType } from '../shared/resource-type';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { AppState } from '../../app.reducer';
|
||||||
|
import { RedirectWhenTokenExpiredAction } from './auth.actions';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthInterceptor implements HttpInterceptor {
|
export class AuthInterceptor implements HttpInterceptor {
|
||||||
constructor(private inj: Injector, private router: Router) { }
|
constructor(private inj: Injector, private store: Store<AppState>) { }
|
||||||
|
|
||||||
private isUnauthorized(status: number): boolean {
|
private isUnauthorized(status: number): boolean {
|
||||||
return status === 401 || status === 403;
|
return status === 401 || status === 403;
|
||||||
@@ -84,15 +87,21 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
.catch((error, caught) => {
|
.catch((error, caught) => {
|
||||||
// Intercept an unauthorized error response
|
// Intercept an unauthorized error response
|
||||||
if (error instanceof HttpErrorResponse && this.isUnauthorized(error.status)) {
|
if (error instanceof HttpErrorResponse && this.isUnauthorized(error.status)) {
|
||||||
// Create a new HttpResponse and return it, so it can be handle properly by AuthService.
|
// Checks if is a response from a request to an authentication endpoint
|
||||||
const authResponse = new HttpResponse({
|
if (this.isAuthRequest(error.url)) {
|
||||||
body: this.makeAuthStatusObject(false, null, error.error),
|
// Create a new HttpResponse and return it, so it can be handle properly by AuthService.
|
||||||
headers: error.headers,
|
const authResponse = new HttpResponse({
|
||||||
status: error.status,
|
body: this.makeAuthStatusObject(false, null, error.error),
|
||||||
statusText: error.statusText,
|
headers: error.headers,
|
||||||
url: error.url
|
status: error.status,
|
||||||
});
|
statusText: error.statusText,
|
||||||
return Observable.of(authResponse);
|
url: error.url
|
||||||
|
});
|
||||||
|
return Observable.of(authResponse);
|
||||||
|
} else {
|
||||||
|
// Redirect to the login route
|
||||||
|
this.store.dispatch(new RedirectWhenTokenExpiredAction('Your session has expired. Please log in again.'));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Return error response as is.
|
// Return error response as is.
|
||||||
return Observable.throw(error);
|
return Observable.throw(error);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// import actions
|
// import actions
|
||||||
import {
|
import {
|
||||||
AuthActions, AuthActionTypes, AuthenticatedSuccessAction, AuthenticationErrorAction,
|
AuthActions, AuthActionTypes, AuthenticatedSuccessAction, AuthenticationErrorAction,
|
||||||
AuthenticationSuccessAction, LogOutErrorAction
|
AuthenticationSuccessAction, LogOutErrorAction, RedirectWhenTokenExpiredAction
|
||||||
} from './auth.actions';
|
} from './auth.actions';
|
||||||
|
|
||||||
// import models
|
// import models
|
||||||
@@ -25,6 +25,9 @@ export interface AuthState {
|
|||||||
// true when loading
|
// true when loading
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
|
||||||
|
// info message
|
||||||
|
message?: string;
|
||||||
|
|
||||||
// the authenticated user
|
// the authenticated user
|
||||||
user?: Eperson;
|
user?: Eperson;
|
||||||
}
|
}
|
||||||
@@ -33,7 +36,7 @@ export interface AuthState {
|
|||||||
* The initial state.
|
* The initial state.
|
||||||
*/
|
*/
|
||||||
const initialState: AuthState = {
|
const initialState: AuthState = {
|
||||||
authenticated: null,
|
authenticated: false,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
loading: false
|
loading: false
|
||||||
};
|
};
|
||||||
@@ -50,7 +53,8 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
case AuthActionTypes.AUTHENTICATE:
|
case AuthActionTypes.AUTHENTICATE:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
error: undefined,
|
error: undefined,
|
||||||
loading: true
|
loading: true,
|
||||||
|
message: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
case AuthActionTypes.AUTHENTICATED_ERROR:
|
case AuthActionTypes.AUTHENTICATED_ERROR:
|
||||||
@@ -67,6 +71,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
loaded: true,
|
loaded: true,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
message: undefined,
|
||||||
user: (action as AuthenticatedSuccessAction).payload.user
|
user: (action as AuthenticatedSuccessAction).payload.user
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,10 +101,11 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
|
|
||||||
case AuthActionTypes.RESET_ERROR:
|
case AuthActionTypes.RESET_ERROR:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
authenticated: null,
|
authenticated: false,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
message: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
case AuthActionTypes.LOG_OUT_ERROR:
|
case AuthActionTypes.LOG_OUT_ERROR:
|
||||||
@@ -115,6 +121,16 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
message: undefined,
|
||||||
|
user: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.REDIRECT:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: false,
|
||||||
|
loaded: false,
|
||||||
|
loading: false,
|
||||||
|
message: (action as RedirectWhenTokenExpiredAction).payload,
|
||||||
user: undefined
|
user: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -122,7 +138,8 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
loading: true
|
loading: true,
|
||||||
|
message: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -158,10 +175,18 @@ export const getAuthenticatedUser = (state: AuthState) => state.user;
|
|||||||
* Returns the authentication error.
|
* Returns the authentication error.
|
||||||
* @function getAuthenticationError
|
* @function getAuthenticationError
|
||||||
* @param {State} state
|
* @param {State} state
|
||||||
* @returns {Error}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
export const getAuthenticationError = (state: AuthState) => state.error;
|
export const getAuthenticationError = (state: AuthState) => state.error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authentication info message.
|
||||||
|
* @function getAuthenticationError
|
||||||
|
* @param {State} state
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
export const getAuthenticationMessage = (state: AuthState) => state.message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if request is in progress.
|
* Returns true if request is in progress.
|
||||||
* @function isLoading
|
* @function isLoading
|
||||||
|
@@ -8,9 +8,16 @@ import { HttpHeaders } from '@angular/common/http';
|
|||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
|
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
|
||||||
import { isNotEmpty, isNotNull } from '../../shared/empty.util';
|
import { isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
|
||||||
import { CookieService } from '../../shared/services/cookie.service';
|
import { CookieService } from '../../shared/services/cookie.service';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { isAuthenticated } from './selectors';
|
||||||
|
import { AppState, routerStateSelector } from '../../app.reducer';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { ResetAuthenticationErrorAction } from './auth.actions';
|
||||||
|
import { RouterReducerState } from '@ngrx/router-store';
|
||||||
|
|
||||||
|
export const LOGIN_ROUTE = '/login';
|
||||||
/**
|
/**
|
||||||
* The auth service.
|
* The auth service.
|
||||||
*/
|
*/
|
||||||
@@ -21,7 +28,7 @@ export class AuthService {
|
|||||||
* True if authenticated
|
* True if authenticated
|
||||||
* @type boolean
|
* @type boolean
|
||||||
*/
|
*/
|
||||||
private _authenticated = false;
|
private _authenticated: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The url to redirect after login
|
* The url to redirect after login
|
||||||
@@ -29,7 +36,27 @@ export class AuthService {
|
|||||||
*/
|
*/
|
||||||
private _redirectUrl: string;
|
private _redirectUrl: string;
|
||||||
|
|
||||||
constructor(private authRequestService: AuthRequestService, private storage: CookieService) {
|
constructor(private route: ActivatedRoute,
|
||||||
|
private authRequestService: AuthRequestService,
|
||||||
|
private router: Router,
|
||||||
|
private storage: CookieService,
|
||||||
|
private store: Store<AppState>) {
|
||||||
|
this.store.select(isAuthenticated)
|
||||||
|
.startWith(false)
|
||||||
|
.subscribe((authenticated: boolean) => this._authenticated = authenticated);
|
||||||
|
|
||||||
|
// If current route is different from the one setted in authentication guard
|
||||||
|
// and is not the login route, clear it
|
||||||
|
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()
|
||||||
|
.subscribe((routerState: RouterReducerState) => {
|
||||||
|
this._redirectUrl = '';
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,18 +67,12 @@ export class AuthService {
|
|||||||
* @returns {Observable<User>} The authenticated user observable.
|
* @returns {Observable<User>} The authenticated user observable.
|
||||||
*/
|
*/
|
||||||
public authenticate(user: string, password: string): Observable<AuthStatus> {
|
public authenticate(user: string, password: string): Observable<AuthStatus> {
|
||||||
// Normally you would do an HTTP request to determine to
|
// Attempt authenticating the user using the supplied credentials.
|
||||||
// attempt authenticating the user using the supplied credentials.
|
|
||||||
// const body = `user=${user}&password=${password}`;
|
|
||||||
// const body = encodeURI('password=test&user=vera.aloe@mailinator.com');
|
|
||||||
// const body = [{user}, {password}];
|
|
||||||
// const body = encodeURI('password=' + password.toString() + '&user=' + user.toString());
|
|
||||||
const body = encodeURI(`password=${password}&user=${user}`);
|
const body = encodeURI(`password=${password}&user=${user}`);
|
||||||
const options: HttpOptions = Object.create({});
|
const options: HttpOptions = Object.create({});
|
||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
// options.responseType = 'text';
|
|
||||||
return this.authRequestService.postToEndpoint('login', body, options)
|
return this.authRequestService.postToEndpoint('login', body, options)
|
||||||
.map((status: AuthStatus) => {
|
.map((status: AuthStatus) => {
|
||||||
if (status.authenticated) {
|
if (status.authenticated) {
|
||||||
@@ -67,8 +88,8 @@ export class AuthService {
|
|||||||
* Determines if the user is authenticated
|
* Determines if the user is authenticated
|
||||||
* @returns {Observable<boolean>}
|
* @returns {Observable<boolean>}
|
||||||
*/
|
*/
|
||||||
public authenticated(): Observable<boolean> {
|
public isAuthenticated(): Observable<boolean> {
|
||||||
return Observable.of(this._authenticated);
|
return this.store.select(isAuthenticated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,10 +106,8 @@ export class AuthService {
|
|||||||
return this.authRequestService.getRequest('status', options)
|
return this.authRequestService.getRequest('status', options)
|
||||||
.map((status: AuthStatus) => {
|
.map((status: AuthStatus) => {
|
||||||
if (status.authenticated) {
|
if (status.authenticated) {
|
||||||
this._authenticated = true;
|
|
||||||
return status.eperson[0];
|
return status.eperson[0];
|
||||||
} else {
|
} else {
|
||||||
this._authenticated = false;
|
|
||||||
throw(new Error('Not authenticated'));
|
throw(new Error('Not authenticated'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -102,6 +121,13 @@ export class AuthService {
|
|||||||
return isNotEmpty(token) ? Observable.of(token) : Observable.throw(false);
|
return isNotEmpty(token) ? Observable.of(token) : Observable.throw(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear authentication errors
|
||||||
|
*/
|
||||||
|
public resetAuthenticationError(): void {
|
||||||
|
this.store.dispatch(new ResetAuthenticationErrorAction());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new user
|
* Create a new user
|
||||||
* @returns {User}
|
* @returns {User}
|
||||||
@@ -119,15 +145,13 @@ export class AuthService {
|
|||||||
* @returns {Observable<boolean>}
|
* @returns {Observable<boolean>}
|
||||||
*/
|
*/
|
||||||
public logout(): Observable<boolean> {
|
public logout(): Observable<boolean> {
|
||||||
// Normally you would do an HTTP request sign end the session
|
// Send a request that sign end the session
|
||||||
// but, let's just return an observable of true.
|
|
||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
const options: HttpOptions = Object.create({headers, responseType: 'text'});
|
const options: HttpOptions = Object.create({headers, responseType: 'text'});
|
||||||
return this.authRequestService.getRequest('logout', options)
|
return this.authRequestService.getRequest('logout', options)
|
||||||
.map((status: AuthStatus) => {
|
.map((status: AuthStatus) => {
|
||||||
if (!status.authenticated) {
|
if (!status.authenticated) {
|
||||||
this._authenticated = false;
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw(new Error('Invalid email or password'));
|
throw(new Error('Invalid email or password'));
|
||||||
@@ -136,32 +160,77 @@ export class AuthService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve authentication token info and make authorization header
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
public getAuthHeader(): string {
|
public getAuthHeader(): string {
|
||||||
// Retrieve authentication token info
|
const token = this.getToken();
|
||||||
const token = this.storage.get(TOKENITEM);
|
return (this._authenticated && isNotNull(token)) ? `Bearer ${token.accessToken}` : '';
|
||||||
return (isNotNull(token) && this._authenticated) ? `Bearer ${token.accessToken}` : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get authentication token info
|
||||||
|
* @returns {AuthTokenInfo}
|
||||||
|
*/
|
||||||
public getToken(): AuthTokenInfo {
|
public getToken(): AuthTokenInfo {
|
||||||
// Retrieve authentication token info
|
// Retrieve authentication token info and check if is valid
|
||||||
return this.storage.get(TOKENITEM);
|
const token = this.storage.get(TOKENITEM);
|
||||||
|
if (isNotEmpty(token) && token.hasOwnProperty('accessToken') && isNotEmpty(token.accessToken)) {
|
||||||
|
return token;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save authentication token info
|
||||||
|
*
|
||||||
|
* @param {AuthTokenInfo} token The token to save
|
||||||
|
* @returns {AuthTokenInfo}
|
||||||
|
*/
|
||||||
public storeToken(token: AuthTokenInfo) {
|
public storeToken(token: AuthTokenInfo) {
|
||||||
// Save authentication token info
|
|
||||||
return this.storage.set(TOKENITEM, token);
|
return this.storage.set(TOKENITEM, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove authentication token info
|
||||||
|
*/
|
||||||
public removeToken() {
|
public removeToken() {
|
||||||
// Remove authentication token info
|
|
||||||
console.log('REMOVE!!!!');
|
|
||||||
return this.storage.remove(TOKENITEM);
|
return this.storage.remove(TOKENITEM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the login route
|
||||||
|
*/
|
||||||
|
public redirectToLogin() {
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the route navigated before the login
|
||||||
|
*/
|
||||||
|
public redirectToPreviousUrl() {
|
||||||
|
if (isNotEmpty(this._redirectUrl)) {
|
||||||
|
const url = this._redirectUrl;
|
||||||
|
// Clear url
|
||||||
|
this._redirectUrl = null;
|
||||||
|
this.router.navigate([url]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get redirect url
|
||||||
|
*/
|
||||||
get redirectUrl(): string {
|
get redirectUrl(): string {
|
||||||
return this._redirectUrl;
|
return this._redirectUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set redirect url
|
||||||
|
*/
|
||||||
set redirectUrl(value: string) {
|
set redirectUrl(value: string) {
|
||||||
this._redirectUrl = value;
|
this._redirectUrl = value;
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { Store } from '@ngrx/store';
|
|||||||
|
|
||||||
// reducers
|
// reducers
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { isAuthenticated } from './selectors';
|
import { isAuthenticated, isAuthenticationLoading } from './selectors';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +50,7 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleAuth(url: string): Observable<boolean> {
|
private handleAuth(url: string): Observable<boolean> {
|
||||||
|
console.log('handleAuth', url)
|
||||||
// get observable
|
// get observable
|
||||||
const observable = this.store.select(isAuthenticated);
|
const observable = this.store.select(isAuthenticated);
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
|
|||||||
observable.subscribe((authenticated) => {
|
observable.subscribe((authenticated) => {
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
this.authService.redirectUrl = url;
|
this.authService.redirectUrl = url;
|
||||||
this.router.navigate(['/login']);
|
this.authService.redirectToLogin();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -38,6 +38,15 @@ export const getAuthenticatedUser = createSelector(getAuthState, auth.getAuthent
|
|||||||
*/
|
*/
|
||||||
export const getAuthenticationError = createSelector(getAuthState, auth.getAuthenticationError);
|
export const getAuthenticationError = createSelector(getAuthState, auth.getAuthenticationError);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authentication info message.
|
||||||
|
* @function getAuthenticationError
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {Error}
|
||||||
|
*/
|
||||||
|
export const getAuthenticationMessage = createSelector(getAuthState, auth.getAuthenticationMessage);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the user is authenticated
|
* Returns true if the user is authenticated
|
||||||
* @function isAuthenticated
|
* @function isAuthenticated
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<li *ngIf="!(isAuthenticated | async) && !(windowService.isMobileView() | async) && showAuth" class="nav-item dropdown" (click)="$event.stopPropagation();">
|
<li *ngIf="!(isAuthenticated | async) && !(windowService.isMobileView() | async) && showAuth" class="nav-item dropdown" (click)="$event.stopPropagation();">
|
||||||
<div ngbDropdown placement="bottom-right" class="d-inline-block float-right" @fadeInOut>
|
<div ngbDropdown placement="bottom-right" class="d-inline-block float-right" @fadeInOut>
|
||||||
<a href="#" id="dropdownLogin" class="nav-link" (click)="$event.preventDefault()" ngbDropdownToggle><i class="fa fa-sign-in fa-fw" aria-hidden="true"></i> {{ 'nav.login' | translate }}<span class="caret"></span></a>
|
<a href="#" id="dropdownLogin" class="nav-link" (click)="$event.preventDefault()" ngbDropdownToggle><i class="fa fa-sign-in fa-fw" aria-hidden="true"></i> {{ 'nav.login' | translate }}<span class="caret"></span></a>
|
||||||
<div ngbDropdownMenu aria-labelledby="dropdownLogin">
|
<div id="loginDropdownMenu" ngbDropdownMenu aria-labelledby="dropdownLogin">
|
||||||
<ds-log-in></ds-log-in>
|
<ds-log-in></ds-log-in>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<li *ngIf="(isAuthenticated | async) && !(windowService.isMobileView() | async)" class="nav-item">
|
<li *ngIf="(isAuthenticated | async) && !(windowService.isMobileView() | async)" class="nav-item">
|
||||||
<div ngbDropdown placement="bottom-right" class="d-inline-block" [ngClass]="{'float-right': !(windowService.isMobileView() | async)}" @fadeInOut>
|
<div ngbDropdown placement="bottom-right" class="d-inline-block" [ngClass]="{'float-right': !(windowService.isMobileView() | async)}" @fadeInOut>
|
||||||
<a href="#" id="dropdownUser" class="nav-link" (click)="$event.preventDefault()" ngbDropdownToggle><i class="fa fa-user fa-fw" aria-hidden="true"></i>Hello {{(user | async).name}}<span class="caret"></span></a>
|
<a href="#" id="dropdownUser" class="nav-link" (click)="$event.preventDefault()" ngbDropdownToggle><i class="fa fa-user fa-fw" aria-hidden="true"></i>Hello {{(user | async).name}}<span class="caret"></span></a>
|
||||||
<div ngbDropdownMenu aria-labelledby="dropdownUser">
|
<div id="logoutDropdownMenu" ngbDropdownMenu aria-labelledby="dropdownUser">
|
||||||
<ds-log-out></ds-log-out>
|
<ds-log-out></ds-log-out>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
#loginDropdownMenu, #logoutDropdownMenu {
|
||||||
|
min-width: 330px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loginDropdownMenu {
|
||||||
|
min-height: 260px;
|
||||||
|
}
|
||||||
|
@@ -7,14 +7,12 @@ import { Store } from '@ngrx/store';
|
|||||||
import { fadeInOut, fadeOut } from '../animations/fade';
|
import { fadeInOut, fadeOut } from '../animations/fade';
|
||||||
import { CoreState } from '../../core/core.reducers';
|
import { CoreState } from '../../core/core.reducers';
|
||||||
import { HostWindowService } from '../host-window.service';
|
import { HostWindowService } from '../host-window.service';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState, routerStateSelector } from '../../app.reducer';
|
||||||
import { hasValue, isNotUndefined } from '../empty.util';
|
import { hasValue, isNotUndefined } from '../empty.util';
|
||||||
import { getAuthenticatedUser, isAuthenticated } from '../../core/auth/selectors';
|
import { getAuthenticatedUser, isAuthenticated } from '../../core/auth/selectors';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { Eperson } from '../../core/eperson/models/eperson.model';
|
import { Eperson } from '../../core/eperson/models/eperson.model';
|
||||||
|
|
||||||
const routerStateSelector = (state: AppState) => state.router;
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-auth-nav-menu',
|
selector: 'ds-auth-nav-menu',
|
||||||
templateUrl: './auth-nav-menu.component.html',
|
templateUrl: './auth-nav-menu.component.html',
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
placeholder="{{'login.form.email' | translate}}"
|
placeholder="{{'login.form.email' | translate}}"
|
||||||
required
|
required
|
||||||
type="email"
|
type="email"
|
||||||
(input)="resetError($event)">
|
(input)="resetErrorOrMessage($event)">
|
||||||
<label for="inputPassword" class="sr-only">{{"login.form.password" | translate}}</label>
|
<label for="inputPassword" class="sr-only">{{"login.form.password" | translate}}</label>
|
||||||
<input id="inputPassword"
|
<input id="inputPassword"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@@ -18,8 +18,9 @@
|
|||||||
formControlName="password"
|
formControlName="password"
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
(input)="resetError($event)">
|
(input)="resetErrorOrMessage($event)">
|
||||||
<div *ngIf="(error | async) && hasError" class="alert alert-danger" role="alert" @fadeOut>{{ error | async }}</div>
|
<div *ngIf="(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>
|
<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>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item" href="#">{{"login.form.new-user" | translate}}</a>
|
<a class="dropdown-item" href="#">{{"login.form.new-user" | translate}}</a>
|
||||||
|
@@ -14,15 +14,15 @@ import { AuthenticateAction, ResetAuthenticationErrorAction } from '../../core/a
|
|||||||
|
|
||||||
// reducers
|
// reducers
|
||||||
import {
|
import {
|
||||||
getAuthenticationError,
|
getAuthenticationError, getAuthenticationMessage,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isAuthenticationLoading,
|
isAuthenticationLoading,
|
||||||
} from '../../core/auth/selectors';
|
} from '../../core/auth/selectors';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { CoreState } from '../../core/core.reducers';
|
import { CoreState } from '../../core/core.reducers';
|
||||||
|
|
||||||
import { isNotEmpty, isNotNull } from '../empty.util';
|
import { isNotEmpty } from '../empty.util';
|
||||||
import { fadeOut } from '../animations/fade';
|
import { fadeOut } from '../animations/fade';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* /users/sign-in
|
* /users/sign-in
|
||||||
@@ -48,6 +48,18 @@ export class LogInComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
public hasError = false;
|
public hasError = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authentication info message.
|
||||||
|
* @type {Observable<string>}
|
||||||
|
*/
|
||||||
|
public message: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has authentication message.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public hasMessage = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the authentication is loading.
|
* True if the authentication is loading.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -68,12 +80,13 @@ export class LogInComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
|
* @param {AuthService} authService
|
||||||
* @param {FormBuilder} formBuilder
|
* @param {FormBuilder} formBuilder
|
||||||
* @param {Store<State>} store
|
* @param {Store<State>} store
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private router: Router,
|
|
||||||
private store: Store<CoreState>
|
private store: Store<CoreState>
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -95,6 +108,13 @@ export class LogInComponent implements OnDestroy, OnInit {
|
|||||||
return error;
|
return error;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set error
|
||||||
|
this.message = this.store.select(getAuthenticationMessage)
|
||||||
|
.map((message) => {
|
||||||
|
this.hasMessage = (isNotEmpty(message));
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
|
||||||
// set loading
|
// set loading
|
||||||
this.loading = this.store.select(isAuthenticationLoading);
|
this.loading = this.store.select(isAuthenticationLoading);
|
||||||
|
|
||||||
@@ -103,7 +123,7 @@ export class LogInComponent implements OnDestroy, OnInit {
|
|||||||
.takeWhile(() => this.alive)
|
.takeWhile(() => this.alive)
|
||||||
.filter((authenticated) => authenticated)
|
.filter((authenticated) => authenticated)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.router.navigate(['/']);
|
this.authService.redirectToPreviousUrl();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,20 +136,13 @@ export class LogInComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to the home page.
|
* Reset error or message.
|
||||||
* @method home
|
|
||||||
*/
|
*/
|
||||||
public home() {
|
public resetErrorOrMessage() {
|
||||||
this.router.navigate(['/home']);
|
if (this.hasError || this.hasMessage) {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset error.
|
|
||||||
*/
|
|
||||||
public resetError() {
|
|
||||||
if (this.hasError) {
|
|
||||||
this.store.dispatch(new ResetAuthenticationErrorAction());
|
this.store.dispatch(new ResetAuthenticationErrorAction());
|
||||||
this.hasError = false;
|
this.hasError = false;
|
||||||
|
this.hasMessage = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +151,8 @@ export class LogInComponent implements OnDestroy, OnInit {
|
|||||||
* @method register
|
* @method register
|
||||||
*/
|
*/
|
||||||
public register() {
|
public register() {
|
||||||
this.router.navigate(['/register']);
|
// TODO enable after registration process is done
|
||||||
|
// this.router.navigate(['/register']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -2,8 +2,6 @@
|
|||||||
<div *ngIf="!(loading | async)" class="form-login px-4 py-3">
|
<div *ngIf="!(loading | async)" class="form-login px-4 py-3">
|
||||||
|
|
||||||
<div *ngIf="(error | async) && hasError" class="alert alert-danger" role="alert" @fadeOut>{{ error | async }}</div>
|
<div *ngIf="(error | async) && hasError" class="alert alert-danger" role="alert" @fadeOut>{{ error | async }}</div>
|
||||||
<button class="btn btn-lg btn-primary btn-block mt-3" (click)="logOut()">{{"logout.form.submit" | translate}}</button>
|
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item" href="#">{{"login.form.new-user" | translate}}</a>
|
<button class="btn btn-lg btn-primary btn-block mt-3" (click)="logOut()">{{"logout.form.submit" | translate}}</button>
|
||||||
<a class="dropdown-item" href="#">{{"login.form.forgot-password" | translate}}</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -75,13 +75,6 @@ export class LogOutComponent implements OnDestroy, OnInit {
|
|||||||
this.router.navigate(['/home']);
|
this.router.navigate(['/home']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* To to the log in page.
|
|
||||||
*/
|
|
||||||
public logIn() {
|
|
||||||
this.router.navigate(['/login']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public logOut() {
|
public logOut() {
|
||||||
this.store.dispatch(new LogOutAction());
|
this.store.dispatch(new LogOutAction());
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user