mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Added first release of the authentication module
This commit is contained in:
@@ -47,7 +47,8 @@
|
|||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"login": "Log In"
|
"login": "Log In",
|
||||||
|
"logout": "Log Out"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"results-per-page": "Results Per Page",
|
"results-per-page": "Results Per Page",
|
||||||
@@ -150,10 +151,19 @@
|
|||||||
"login": {
|
"login": {
|
||||||
"title": "Login",
|
"title": "Login",
|
||||||
"form": {
|
"form": {
|
||||||
"header": "Please log in",
|
"header": "Please log in to DSpace",
|
||||||
"email": "Email address",
|
"email": "Email address",
|
||||||
|
"forgot-password": "Have you forgotten your password?",
|
||||||
|
"new-user": "New user? Click here to register.",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"submit": "Log in"
|
"submit": "Log in"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"logout": {
|
||||||
|
"title": "Logout",
|
||||||
|
"form": {
|
||||||
|
"header": "Log out from DSpace",
|
||||||
|
"submit": "Log out"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
<ds-home-news></ds-home-news>
|
<ds-home-news></ds-home-news>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<p *ngIf="(isAuthenticated | async)">Loggato</p>
|
||||||
<ds-search-form></ds-search-form>
|
<ds-search-form></ds-search-form>
|
||||||
<ds-top-level-community-list></ds-top-level-community-list>
|
<ds-top-level-community-list></ds-top-level-community-list>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,10 +1,21 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { isAuthenticated } from '../core/auth/selectors';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { AppState } from '../app.reducer';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-home-page',
|
selector: 'ds-home-page',
|
||||||
styleUrls: ['./home-page.component.scss'],
|
styleUrls: ['./home-page.component.scss'],
|
||||||
templateUrl: './home-page.component.html'
|
templateUrl: './home-page.component.html'
|
||||||
})
|
})
|
||||||
export class HomePageComponent {
|
export class HomePageComponent implements OnInit {
|
||||||
|
public isAuthenticated: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(private store: Store<AppState>) {}
|
||||||
|
ngOnInit() {
|
||||||
|
// set loading
|
||||||
|
this.isAuthenticated = this.store.select(isAuthenticated);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div class="text-center mt-5">
|
<div class="text-center mt-5">
|
||||||
<img class="mb-4" src="assets/images/dspace-logo.png" alt="" width="72" height="72">
|
<img class="mb-4" src="assets/images/dspace-logo.png" alt="" width="72" height="72">
|
||||||
<h1 class="h3 mb-0 font-weight-normal">{{"login.form.header" | translate}}</h1>
|
<h1 class="h3 mb-0 font-weight-normal">{{"login.form.header" | translate}}</h1>
|
||||||
<ds-login-form></ds-login-form>
|
<ds-log-in></ds-log-in>
|
||||||
</div>
|
</div>
|
||||||
|
19
src/app/+logout-page/logout-page-routing.module.ts
Normal file
19
src/app/+logout-page/logout-page-routing.module.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { LogoutPageComponent } from './logout-page.component';
|
||||||
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{
|
||||||
|
canActivate: [AuthenticatedGuard],
|
||||||
|
path: '',
|
||||||
|
component: LogoutPageComponent,
|
||||||
|
data: { title: 'logout.title' }
|
||||||
|
}
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class LogoutPageRoutingModule { }
|
5
src/app/+logout-page/logout-page.component.html
Normal file
5
src/app/+logout-page/logout-page.component.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="text-center mt-5">
|
||||||
|
<img class="mb-4" src="assets/images/dspace-logo.png" alt="" width="72" height="72">
|
||||||
|
<h1 class="h3 mb-0 font-weight-normal">{{"logout.form.header" | translate}}</h1>
|
||||||
|
<ds-log-out></ds-log-out>
|
||||||
|
</div>
|
15
src/app/+logout-page/logout-page.component.scss
Normal file
15
src/app/+logout-page/logout-page.component.scss
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.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;
|
||||||
|
}
|
10
src/app/+logout-page/logout-page.component.ts
Normal file
10
src/app/+logout-page/logout-page.component.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-logout-page',
|
||||||
|
styleUrls: ['./logout-page.component.scss'],
|
||||||
|
templateUrl: './logout-page.component.html'
|
||||||
|
})
|
||||||
|
export class LogoutPageComponent {
|
||||||
|
|
||||||
|
}
|
19
src/app/+logout-page/logout-page.module.ts
Normal file
19
src/app/+logout-page/logout-page.module.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { LogoutPageComponent } from './logout-page.component';
|
||||||
|
import { LogoutPageRoutingModule } from './logout-page-routing.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
LogoutPageRoutingModule,
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
LogoutPageComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class LogoutPageModule {
|
||||||
|
|
||||||
|
}
|
@@ -13,6 +13,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
|||||||
{ path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' },
|
{ path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' },
|
||||||
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
|
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
|
||||||
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
|
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
|
||||||
|
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
|
||||||
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
|
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import { HeaderEffects } from './header/header.effects';
|
import { HeaderEffects } from './header/header.effects';
|
||||||
import { StoreEffects } from './store.effects';
|
import { StoreEffects } from './store.effects';
|
||||||
|
import { AuthEffects } from './core/auth/auth.effects';
|
||||||
|
|
||||||
export const appEffects = [
|
export const appEffects = [
|
||||||
StoreEffects,
|
StoreEffects,
|
||||||
|
@@ -1,29 +1,30 @@
|
|||||||
import { ActionReducerMap } from '@ngrx/store';
|
import { ActionReducerMap } from '@ngrx/store';
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
|
||||||
import { headerReducer, HeaderState } from './header/header.reducer';
|
import { headerReducer, HeaderState } from './header/header.reducer';
|
||||||
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
||||||
import {
|
import {
|
||||||
SearchSidebarState,
|
SearchSidebarState,
|
||||||
sidebarReducer
|
sidebarReducer
|
||||||
} from './+search-page/search-sidebar/search-sidebar.reducer';
|
} from './+search-page/search-sidebar/search-sidebar.reducer';
|
||||||
import {
|
import {
|
||||||
filterReducer,
|
filterReducer,
|
||||||
SearchFiltersState
|
SearchFiltersState
|
||||||
} from './+search-page/search-filters/search-filter/search-filter.reducer';
|
} from './+search-page/search-filters/search-filter/search-filter.reducer';
|
||||||
|
import { authReducer, AuthState } from './core/auth/auth.reducers';
|
||||||
export interface AppState {
|
|
||||||
router: fromRouter.RouterReducerState;
|
export interface AppState {
|
||||||
hostWindow: HostWindowState;
|
router: fromRouter.RouterReducerState;
|
||||||
header: HeaderState;
|
hostWindow: HostWindowState;
|
||||||
searchSidebar: SearchSidebarState;
|
header: HeaderState;
|
||||||
searchFilter: SearchFiltersState;
|
searchSidebar: SearchSidebarState;
|
||||||
}
|
searchFilter: SearchFiltersState;
|
||||||
|
}
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
|
||||||
router: fromRouter.routerReducer,
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
hostWindow: hostWindowReducer,
|
router: fromRouter.routerReducer,
|
||||||
header: headerReducer,
|
hostWindow: hostWindowReducer,
|
||||||
searchSidebar: sidebarReducer,
|
header: headerReducer,
|
||||||
searchFilter: filterReducer
|
searchSidebar: sidebarReducer,
|
||||||
};
|
searchFilter: filterReducer
|
||||||
|
};
|
||||||
|
19
src/app/core/auth/auth-object-factory.ts
Normal file
19
src/app/core/auth/auth-object-factory.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import { AuthType } from './auth-type';
|
||||||
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
|
||||||
|
export class AuthObjectFactory {
|
||||||
|
public static getConstructor(type): GenericConstructor<DSpaceObject> {
|
||||||
|
switch (type) {
|
||||||
|
case AuthType.Status: {
|
||||||
|
return AuthStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
src/app/core/auth/auth-request.service.ts
Normal file
58
src/app/core/auth/auth-request.service.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { AuthPostRequest, PostRequest, RestRequest } from '../data/request.models';
|
||||||
|
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||||
|
import { AuthSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
|
||||||
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthRequestService extends HALEndpointService {
|
||||||
|
protected linkName = 'authn';
|
||||||
|
protected browseEndpoint = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if authenticated
|
||||||
|
* @type
|
||||||
|
*/
|
||||||
|
private _authenticated = false;
|
||||||
|
|
||||||
|
constructor(protected responseCache: ResponseCacheService,
|
||||||
|
protected requestService: RequestService,
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected submitRequest(request: RestRequest): Observable<any> {
|
||||||
|
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
||||||
|
.map((entry: ResponseCacheEntry) => entry.response)
|
||||||
|
.partition((response: RestResponse) => response.isSuccessful);
|
||||||
|
return Observable.merge(
|
||||||
|
errorResponse.flatMap((response: ErrorResponse) =>
|
||||||
|
Observable.throw(new Error(`Couldn't send data to server`))),
|
||||||
|
successResponse
|
||||||
|
.filter((response: AuthSuccessResponse) => isNotEmpty(response))
|
||||||
|
.map((response: AuthSuccessResponse) => response.authResponse)
|
||||||
|
.distinctUntilChanged());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getEndpointByMethod(endpoint: string, method: string): string {
|
||||||
|
return isNotEmpty(method) ? `${endpoint}/${method}` : `${endpoint}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<any> {
|
||||||
|
return this.getEndpoint()
|
||||||
|
.filter((href: string) => isNotEmpty(href))
|
||||||
|
.map((endpointURL) => this.getEndpointByMethod(endpointURL, method))
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options))
|
||||||
|
.do((request: PostRequest) => this.requestService.configure(request))
|
||||||
|
.flatMap((request: PostRequest) => this.submitRequest(request))
|
||||||
|
.distinctUntilChanged();
|
||||||
|
}
|
||||||
|
}
|
47
src/app/core/auth/auth-response-parsing.service.ts
Normal file
47
src/app/core/auth/auth-response-parsing.service.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { AuthObjectFactory } from './auth-object-factory';
|
||||||
|
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
|
||||||
|
import {
|
||||||
|
AuthSuccessResponse, ConfigSuccessResponse, ErrorResponse,
|
||||||
|
RestResponse
|
||||||
|
} from '../cache/response-cache.models';
|
||||||
|
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
|
import { ConfigObject } from '../shared/config/config.model';
|
||||||
|
import { ConfigType } from '../shared/config/config-type';
|
||||||
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { ResponseParsingService } from '../data/parsing.service';
|
||||||
|
import { RestRequest } from '../data/request.models';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||||
|
|
||||||
|
protected objectFactory = AuthObjectFactory;
|
||||||
|
protected toCache = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
) { super();
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||||
|
/*if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && data.statusCode === '200') {
|
||||||
|
const configDefinition = this.process<ConfigObject,ConfigType>(data.payload, request.href);
|
||||||
|
return new ConfigSuccessResponse(configDefinition[Object.keys(configDefinition)[0]], data.statusCode, this.processPageInfo(data.payload.page));
|
||||||
|
} else {
|
||||||
|
return new ErrorResponse(
|
||||||
|
Object.assign(
|
||||||
|
new Error('Unexpected response from config endpoint'),
|
||||||
|
{statusText: data.statusCode}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
console.log(data);
|
||||||
|
return new AuthSuccessResponse(data.payload, data.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
src/app/core/auth/auth-type.ts
Normal file
3
src/app/core/auth/auth-type.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export enum AuthType {
|
||||||
|
Status = 'status'
|
||||||
|
}
|
219
src/app/core/auth/auth.actions.ts
Normal file
219
src/app/core/auth/auth.actions.ts
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
// 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';
|
||||||
|
|
||||||
|
export const AuthActionTypes = {
|
||||||
|
AUTHENTICATE: type('dspace/auth/AUTHENTICATE'),
|
||||||
|
AUTHENTICATE_ERROR: type('dspace/auth/AUTHENTICATE_ERROR'),
|
||||||
|
AUTHENTICATE_SUCCESS: type('dspace/auth/AUTHENTICATE_SUCCESS'),
|
||||||
|
AUTHENTICATED: type('dspace/auth/AUTHENTICATED'),
|
||||||
|
AUTHENTICATED_ERROR: type('dspace/auth/AUTHENTICATED_ERROR'),
|
||||||
|
AUTHENTICATED_SUCCESS: type('dspace/auth/AUTHENTICATED_SUCCESS'),
|
||||||
|
RESET_ERROR: type('dspace/auth/RESET_ERROR'),
|
||||||
|
LOG_OUT: type('dspace/auth/LOG_OUT'),
|
||||||
|
LOG_OUT_ERROR: type('dspace/auth/LOG_OUT_ERROR'),
|
||||||
|
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')
|
||||||
|
};
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate.
|
||||||
|
* @class AuthenticateAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class AuthenticateAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.AUTHENTICATE;
|
||||||
|
payload: {
|
||||||
|
email: string;
|
||||||
|
password: string
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(email: string, password: string) {
|
||||||
|
this.payload = { email, password };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if user is authenticated.
|
||||||
|
* @class AuthenticatedAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class AuthenticatedAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.AUTHENTICATED;
|
||||||
|
payload: string;
|
||||||
|
|
||||||
|
constructor(token: string) {
|
||||||
|
this.payload = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticated check success.
|
||||||
|
* @class AuthenticatedSuccessAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class AuthenticatedSuccessAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.AUTHENTICATED_SUCCESS;
|
||||||
|
payload: {
|
||||||
|
authenticated: boolean;
|
||||||
|
user: Eperson
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(authenticated: boolean, user: Eperson) {
|
||||||
|
this.payload = { authenticated, user };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticated check error.
|
||||||
|
* @class AuthenticatedErrorAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class AuthenticatedErrorAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.AUTHENTICATED_ERROR;
|
||||||
|
payload: Error;
|
||||||
|
|
||||||
|
constructor(payload: Error) {
|
||||||
|
this.payload = payload ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication error.
|
||||||
|
* @class AuthenticationErrorAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class AuthenticationErrorAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.AUTHENTICATE_ERROR;
|
||||||
|
payload: Error;
|
||||||
|
|
||||||
|
constructor(payload: Error) {
|
||||||
|
this.payload = payload ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication success.
|
||||||
|
* @class AuthenticationSuccessAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class AuthenticationSuccessAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.AUTHENTICATE_SUCCESS;
|
||||||
|
payload: Eperson;
|
||||||
|
|
||||||
|
constructor(user: Eperson) {
|
||||||
|
this.payload = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset error.
|
||||||
|
* @class ResetAuthenticationErrorAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class ResetAuthenticationErrorAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.RESET_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign out.
|
||||||
|
* @class LogOutAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class LogOutAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.LOG_OUT;
|
||||||
|
constructor(public payload?: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign out error.
|
||||||
|
* @class LogOutErrorAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class LogOutErrorAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.LOG_OUT_ERROR;
|
||||||
|
payload: Error;
|
||||||
|
|
||||||
|
constructor(payload: Error) {
|
||||||
|
this.payload = payload ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign out success.
|
||||||
|
* @class LogOutSuccessAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class LogOutSuccessAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.LOG_OUT_SUCCESS;
|
||||||
|
constructor(public payload?: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign up.
|
||||||
|
* @class RegistrationAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class RegistrationAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.REGISTRATION;
|
||||||
|
payload: Eperson;
|
||||||
|
|
||||||
|
constructor(user: Eperson) {
|
||||||
|
this.payload = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign up error.
|
||||||
|
* @class RegistrationErrorAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class RegistrationErrorAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.REGISTRATION_ERROR;
|
||||||
|
payload: Error;
|
||||||
|
|
||||||
|
constructor(payload: Error) {
|
||||||
|
this.payload = payload ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign up success.
|
||||||
|
* @class RegistrationSuccessAction
|
||||||
|
* @implements {Action}
|
||||||
|
*/
|
||||||
|
export class RegistrationSuccessAction implements Action {
|
||||||
|
public type: string = AuthActionTypes.REGISTRATION_SUCCESS;
|
||||||
|
payload: Eperson;
|
||||||
|
|
||||||
|
constructor(user: Eperson) {
|
||||||
|
this.payload = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions type.
|
||||||
|
* @type {AuthActions}
|
||||||
|
*/
|
||||||
|
export type AuthActions
|
||||||
|
=
|
||||||
|
AuthenticateAction
|
||||||
|
| AuthenticatedAction
|
||||||
|
| AuthenticatedErrorAction
|
||||||
|
| AuthenticatedSuccessAction
|
||||||
|
| AuthenticationErrorAction
|
||||||
|
| AuthenticationSuccessAction
|
||||||
|
| RegistrationAction
|
||||||
|
| RegistrationErrorAction
|
||||||
|
| RegistrationSuccessAction;
|
97
src/app/core/auth/auth.effects.ts
Normal file
97
src/app/core/auth/auth.effects.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
// import @ngrx
|
||||||
|
import { Effect, Actions } from '@ngrx/effects';
|
||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
// import rxjs
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
// import services
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
|
// import actions
|
||||||
|
import {
|
||||||
|
AuthActionTypes, AuthenticateAction, AuthenticatedAction,
|
||||||
|
AuthenticatedErrorAction,
|
||||||
|
AuthenticatedSuccessAction,
|
||||||
|
AuthenticationErrorAction,
|
||||||
|
AuthenticationSuccessAction, LogOutAction,
|
||||||
|
LogOutErrorAction,
|
||||||
|
LogOutSuccessAction, RegistrationAction,
|
||||||
|
RegistrationErrorAction,
|
||||||
|
RegistrationSuccessAction
|
||||||
|
} from './auth.actions';
|
||||||
|
import { Eperson } from '../eperson/models/eperson.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effects offer a way to isolate and easily test side-effects within your
|
||||||
|
* application.
|
||||||
|
* The `toPayload` helper function returns just
|
||||||
|
* the payload of the currently dispatched action, useful in
|
||||||
|
* instances where the current state is not necessary.
|
||||||
|
*
|
||||||
|
* Documentation on `toPayload` can be found here:
|
||||||
|
* https://github.com/ngrx/effects/blob/master/docs/api.md#topayload
|
||||||
|
*
|
||||||
|
* If you are unfamiliar with the operators being used in these examples, please
|
||||||
|
* check out the sources below:
|
||||||
|
*
|
||||||
|
* Official Docs: http://reactivex.io/rxjs/manual/overview.html#categories-of-operators
|
||||||
|
* RxJS 5 Operators By Example: https://gist.github.com/btroncone/d6cf141d6f2c00dc6b35
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthEffects {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate user.
|
||||||
|
* @method authenticate
|
||||||
|
*/
|
||||||
|
@Effect()
|
||||||
|
public authenticate: Observable<Action> = this.actions$
|
||||||
|
.ofType(AuthActionTypes.AUTHENTICATE)
|
||||||
|
.debounceTime(500)
|
||||||
|
.switchMap((action: AuthenticateAction) => {
|
||||||
|
return this.authService.authenticate(action.payload.email, action.payload.password)
|
||||||
|
.map((user: Eperson) => new AuthenticationSuccessAction(user))
|
||||||
|
.catch((error) => Observable.of(new AuthenticationErrorAction(error)));
|
||||||
|
});
|
||||||
|
|
||||||
|
@Effect()
|
||||||
|
public authenticated: Observable<Action> = this.actions$
|
||||||
|
.ofType(AuthActionTypes.AUTHENTICATED)
|
||||||
|
.switchMap((action: AuthenticatedAction) => {
|
||||||
|
return this.authService.authenticatedUser()
|
||||||
|
.map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), user))
|
||||||
|
.catch((error) => Observable.of(new AuthenticatedErrorAction(error)));
|
||||||
|
});
|
||||||
|
|
||||||
|
@Effect()
|
||||||
|
public createUser: Observable<Action> = this.actions$
|
||||||
|
.ofType(AuthActionTypes.REGISTRATION)
|
||||||
|
.debounceTime(500)
|
||||||
|
.switchMap((action: RegistrationAction) => {
|
||||||
|
return this.authService.create(action.payload)
|
||||||
|
.map((user: Eperson) => new RegistrationSuccessAction(user))
|
||||||
|
.catch((error) => Observable.of(new RegistrationErrorAction(error)));
|
||||||
|
});
|
||||||
|
|
||||||
|
@Effect()
|
||||||
|
public signOut: Observable<Action> = this.actions$
|
||||||
|
.ofType(AuthActionTypes.LOG_OUT)
|
||||||
|
.switchMap((action: LogOutAction) => {
|
||||||
|
return this.authService.signout()
|
||||||
|
.map((value) => new LogOutSuccessAction())
|
||||||
|
.catch((error) => Observable.of(new LogOutErrorAction(error)));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {Actions} actions$
|
||||||
|
* @param {AuthService} authService
|
||||||
|
*/
|
||||||
|
constructor(private actions$: Actions,
|
||||||
|
private authService: AuthService) {
|
||||||
|
}
|
||||||
|
}
|
181
src/app/core/auth/auth.reducers.ts
Normal file
181
src/app/core/auth/auth.reducers.ts
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
// import actions
|
||||||
|
import {
|
||||||
|
AuthActions, AuthActionTypes, AuthenticatedSuccessAction, AuthenticationErrorAction,
|
||||||
|
AuthenticationSuccessAction, LogOutErrorAction
|
||||||
|
} from './auth.actions';
|
||||||
|
|
||||||
|
// import models
|
||||||
|
import { Eperson } from '../eperson/models/eperson.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The auth state.
|
||||||
|
* @interface State
|
||||||
|
*/
|
||||||
|
export interface AuthState {
|
||||||
|
|
||||||
|
// boolean if user is authenticated
|
||||||
|
authenticated: boolean;
|
||||||
|
|
||||||
|
// error message
|
||||||
|
error?: string;
|
||||||
|
|
||||||
|
// true if we have attempted existing auth session
|
||||||
|
loaded: boolean;
|
||||||
|
|
||||||
|
// true when loading
|
||||||
|
loading: boolean;
|
||||||
|
|
||||||
|
// the authenticated user
|
||||||
|
user?: Eperson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial state.
|
||||||
|
*/
|
||||||
|
const initialState: AuthState = {
|
||||||
|
authenticated: null,
|
||||||
|
loaded: false,
|
||||||
|
loading: false
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reducer function.
|
||||||
|
* @function reducer
|
||||||
|
* @param {State} state Current state
|
||||||
|
* @param {AuthActions} action Incoming action
|
||||||
|
*/
|
||||||
|
export function authReducer(state: any = initialState, action: AuthActions): AuthState {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case AuthActionTypes.AUTHENTICATE:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
error: undefined,
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.AUTHENTICATED_ERROR:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: false,
|
||||||
|
error: (action as AuthenticationErrorAction).payload.message,
|
||||||
|
loaded: true
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.AUTHENTICATED_SUCCESS:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: (action as AuthenticatedSuccessAction).payload.authenticated,
|
||||||
|
loaded: true,
|
||||||
|
user: (action as AuthenticatedSuccessAction).payload.user
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.AUTHENTICATE_ERROR:
|
||||||
|
case AuthActionTypes.REGISTRATION_ERROR:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: false,
|
||||||
|
error: (action as AuthenticationErrorAction).payload.message,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.AUTHENTICATE_SUCCESS:
|
||||||
|
case AuthActionTypes.REGISTRATION_SUCCESS:
|
||||||
|
const user: Eperson = (action as AuthenticationSuccessAction).payload;
|
||||||
|
|
||||||
|
// verify user is not null
|
||||||
|
if (user === null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: true,
|
||||||
|
error: undefined,
|
||||||
|
loading: false,
|
||||||
|
user: user
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.RESET_ERROR:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: null,
|
||||||
|
loaded: false,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.LOG_OUT_ERROR:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: true,
|
||||||
|
error: (action as LogOutErrorAction).payload.message,
|
||||||
|
user: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.LOG_OUT_SUCCESS:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: false,
|
||||||
|
error: undefined,
|
||||||
|
user: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.REGISTRATION:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
authenticated: false,
|
||||||
|
error: undefined,
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the user is authenticated.
|
||||||
|
* @function isAuthenticated
|
||||||
|
* @param {State} state
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isAuthenticated = (state: AuthState) => state.authenticated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the authenticated has loaded.
|
||||||
|
* @function isAuthenticatedLoaded
|
||||||
|
* @param {State} state
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isAuthenticatedLoaded = (state: AuthState) => state.loaded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the users state
|
||||||
|
* @function getAuthenticatedUser
|
||||||
|
* @param {State} state
|
||||||
|
* @returns {User}
|
||||||
|
*/
|
||||||
|
export const getAuthenticatedUser = (state: AuthState) => state.user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authentication error.
|
||||||
|
* @function getAuthenticationError
|
||||||
|
* @param {State} state
|
||||||
|
* @returns {Error}
|
||||||
|
*/
|
||||||
|
export const getAuthenticationError = (state: AuthState) => state.error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if request is in progress.
|
||||||
|
* @function isLoading
|
||||||
|
* @param {State} state
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isLoading = (state: AuthState) => state.loading;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sign out error.
|
||||||
|
* @function getLogOutError
|
||||||
|
* @param {State} state
|
||||||
|
* @returns {Error}
|
||||||
|
*/
|
||||||
|
export const getLogOutError = (state: AuthState) => state.error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sign up error.
|
||||||
|
* @function getRegistrationError
|
||||||
|
* @param {State} state
|
||||||
|
* @returns {Error}
|
||||||
|
*/
|
||||||
|
export const getRegistrationError = (state: AuthState) => state.error;
|
@@ -3,6 +3,9 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { Eperson } from '../eperson/models/eperson.model';
|
import { Eperson } from '../eperson/models/eperson.model';
|
||||||
|
import { AuthRequestService } from './auth-request.service';
|
||||||
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
|
|
||||||
export const MOCK_USER = new Eperson();
|
export const MOCK_USER = new Eperson();
|
||||||
MOCK_USER.id = '92a59227-ccf7-46da-9776-86c3fc64147f';
|
MOCK_USER.id = '92a59227-ccf7-46da-9776-86c3fc64147f';
|
||||||
@@ -27,6 +30,8 @@ MOCK_USER.metadata = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const TOKENITEM = 'ds-token';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user service.
|
* The user service.
|
||||||
*/
|
*/
|
||||||
@@ -39,18 +44,36 @@ export class AuthService {
|
|||||||
*/
|
*/
|
||||||
private _authenticated = false;
|
private _authenticated = false;
|
||||||
|
|
||||||
|
constructor(private authRequestService: AuthRequestService) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticate the user
|
* Authenticate the user
|
||||||
*
|
*
|
||||||
* @param {string} email The user's email address
|
* @param {string} user The user name
|
||||||
* @param {string} password The user's password
|
* @param {string} password The user's password
|
||||||
* @returns {Observable<User>} The authenticated user observable.
|
* @returns {Observable<User>} The authenticated user observable.
|
||||||
*/
|
*/
|
||||||
public authenticate(email: string, password: string): Observable<Eperson> {
|
public authenticate(user: string, password: string): Observable<Eperson> {
|
||||||
// Normally you would do an HTTP request to determine to
|
// 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}`;
|
||||||
if (email === MOCK_USER.email && password === 'password') {
|
// const body = encodeURI('password=test&user=vera.aloe@mailinator.com');
|
||||||
|
// const body = [{user}, {password}];
|
||||||
|
const formData: FormData = new FormData();
|
||||||
|
formData.append('user', user);
|
||||||
|
formData.append('password', password);
|
||||||
|
const body = 'password=' + password.toString() + '&user=' + user.toString();
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
headers = headers.append('Accept', 'application/json');
|
||||||
|
options.headers = headers;
|
||||||
|
options.responseType = 'text';
|
||||||
|
this.authRequestService.postToEndpoint('login', body, options)
|
||||||
|
.subscribe((r) => {
|
||||||
|
console.log(r);
|
||||||
|
})
|
||||||
|
if (user === 'test' && password === 'password') {
|
||||||
this._authenticated = true;
|
this._authenticated = true;
|
||||||
return Observable.of(MOCK_USER);
|
return Observable.of(MOCK_USER);
|
||||||
}
|
}
|
||||||
|
@@ -5,10 +5,8 @@ import { Observable } from 'rxjs/Observable';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
// reducers
|
// reducers
|
||||||
import {
|
import { CoreState } from '../core.reducers';
|
||||||
isAuthenticated,
|
import { isAuthenticated } from './selectors';
|
||||||
State
|
|
||||||
} from '../app.reducers';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent unauthorized activating and loading of routes
|
* Prevent unauthorized activating and loading of routes
|
||||||
@@ -20,7 +18,7 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
|
|||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
constructor(private router: Router, private store: Store<State>) {}
|
constructor(private router: Router, private store: Store<CoreState>) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True when user is authenticated
|
* True when user is authenticated
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
|
||||||
|
|
||||||
export class AuthenticationStatus extends DSpaceObject {
|
|
||||||
|
|
||||||
okay: boolean;
|
|
||||||
|
|
||||||
authenticated: boolean;
|
|
||||||
|
|
||||||
}
|
|
5
src/app/core/auth/models/auth-info.model.ts
Normal file
5
src/app/core/auth/models/auth-info.model.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface AuthInfo {
|
||||||
|
access_token?: string,
|
||||||
|
expires?: number,
|
||||||
|
expires_in?: number
|
||||||
|
}
|
9
src/app/core/auth/models/auth-status.model.ts
Normal file
9
src/app/core/auth/models/auth-status.model.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||||
|
|
||||||
|
export class AuthStatus extends DSpaceObject {
|
||||||
|
|
||||||
|
okay: boolean;
|
||||||
|
|
||||||
|
authenticated: boolean;
|
||||||
|
|
||||||
|
}
|
84
src/app/core/auth/selectors.ts
Normal file
84
src/app/core/auth/selectors.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { createSelector } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { AuthState } from './auth.reducers';
|
||||||
|
import { coreSelector, CoreState } from '../core.reducers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every reducer module's default export is the reducer function itself. In
|
||||||
|
* addition, each module should export a type or interface that describes
|
||||||
|
* the state of the reducer plus any selector functions. The `* as`
|
||||||
|
* notation packages up all of the exports into a single object.
|
||||||
|
*/
|
||||||
|
import * as auth from './auth.reducers';
|
||||||
|
import { AppState } from '../../app.reducer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user state.
|
||||||
|
* @function getUserState
|
||||||
|
* @param {AppState} state Top level state.
|
||||||
|
* @return {AuthState}
|
||||||
|
*/
|
||||||
|
export const getAuthState = (state: any) => state.core.auth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authenticated user
|
||||||
|
* @function getAuthenticatedUser
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {User}
|
||||||
|
*/
|
||||||
|
export const getAuthenticatedUser = createSelector(getAuthState, auth.getAuthenticatedUser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authentication error.
|
||||||
|
* @function getAuthenticationError
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {Error}
|
||||||
|
*/
|
||||||
|
export const getAuthenticationError = createSelector(getAuthState, auth.getAuthenticationError);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the user is authenticated
|
||||||
|
* @function isAuthenticated
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const isAuthenticated = createSelector(getAuthState, auth.isAuthenticated);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the user is authenticated
|
||||||
|
* @function isAuthenticated
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const isAuthenticatedLoaded = createSelector(getAuthState, auth.isAuthenticatedLoaded);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the authentication request is loading.
|
||||||
|
* @function isAuthenticationLoading
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const isAuthenticationLoading = createSelector(getAuthState, auth.isLoading);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the log out error.
|
||||||
|
* @function getLogOutError
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {Error}
|
||||||
|
*/
|
||||||
|
export const getLogOutError = createSelector(getAuthState, auth.getLogOutError);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the registration error.
|
||||||
|
* @function getRegistrationError
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {Error}
|
||||||
|
*/
|
||||||
|
export const getRegistrationError = createSelector(getAuthState, auth.getRegistrationError);
|
10
src/app/core/cache/response-cache.models.ts
vendored
10
src/app/core/cache/response-cache.models.ts
vendored
@@ -62,4 +62,14 @@ export class ConfigSuccessResponse extends RestResponse {
|
|||||||
super(true, statusCode);
|
super(true, statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AuthSuccessResponse extends RestResponse {
|
||||||
|
constructor(
|
||||||
|
public authResponse: any,
|
||||||
|
public statusCode: string,
|
||||||
|
public pageInfo?: PageInfo
|
||||||
|
) {
|
||||||
|
super(true, statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
@@ -3,10 +3,12 @@ import { ObjectCacheEffects } from './cache/object-cache.effects';
|
|||||||
import { ResponseCacheEffects } from './cache/response-cache.effects';
|
import { ResponseCacheEffects } from './cache/response-cache.effects';
|
||||||
import { UUIDIndexEffects } from './index/index.effects';
|
import { UUIDIndexEffects } from './index/index.effects';
|
||||||
import { RequestEffects } from './data/request.effects';
|
import { RequestEffects } from './data/request.effects';
|
||||||
|
import { AuthEffects } from './auth/auth.effects';
|
||||||
|
|
||||||
export const coreEffects = [
|
export const coreEffects = [
|
||||||
ResponseCacheEffects,
|
ResponseCacheEffects,
|
||||||
RequestEffects,
|
RequestEffects,
|
||||||
ObjectCacheEffects,
|
ObjectCacheEffects,
|
||||||
UUIDIndexEffects,
|
UUIDIndexEffects,
|
||||||
|
AuthEffects,
|
||||||
];
|
];
|
||||||
|
@@ -38,6 +38,9 @@ import { SubmissionDefinitionsConfigService } from './config/submission-definiti
|
|||||||
import { SubmissionFormsConfigService } from './config/submission-forms-config.service';
|
import { SubmissionFormsConfigService } from './config/submission-forms-config.service';
|
||||||
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
|
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
|
||||||
import { UUIDService } from './shared/uuid.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';
|
||||||
|
|
||||||
const IMPORTS = [
|
const IMPORTS = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -55,6 +58,9 @@ const EXPORTS = [
|
|||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
ApiService,
|
ApiService,
|
||||||
|
AuthenticatedGuard,
|
||||||
|
AuthRequestService,
|
||||||
|
AuthService,
|
||||||
CommunityDataService,
|
CommunityDataService,
|
||||||
CollectionDataService,
|
CollectionDataService,
|
||||||
DSOResponseParsingService,
|
DSOResponseParsingService,
|
||||||
|
@@ -4,19 +4,22 @@ import { responseCacheReducer, ResponseCacheState } from './cache/response-cache
|
|||||||
import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
|
import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
|
||||||
import { indexReducer, IndexState } from './index/index.reducer';
|
import { indexReducer, IndexState } from './index/index.reducer';
|
||||||
import { requestReducer, RequestState } from './data/request.reducer';
|
import { requestReducer, RequestState } from './data/request.reducer';
|
||||||
|
import { authReducer, AuthState } from './auth/auth.reducers';
|
||||||
|
|
||||||
export interface CoreState {
|
export interface CoreState {
|
||||||
'data/object': ObjectCacheState,
|
'data/object': ObjectCacheState,
|
||||||
'data/response': ResponseCacheState,
|
'data/response': ResponseCacheState,
|
||||||
'data/request': RequestState,
|
'data/request': RequestState,
|
||||||
'index': IndexState
|
'index': IndexState,
|
||||||
|
'auth': AuthState,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const coreReducers: ActionReducerMap<CoreState> = {
|
export const coreReducers: ActionReducerMap<CoreState> = {
|
||||||
'data/object': objectCacheReducer,
|
'data/object': objectCacheReducer,
|
||||||
'data/response': responseCacheReducer,
|
'data/response': responseCacheReducer,
|
||||||
'data/request': requestReducer,
|
'data/request': requestReducer,
|
||||||
'index': indexReducer
|
'index': indexReducer,
|
||||||
|
'auth': authReducer
|
||||||
};
|
};
|
||||||
|
|
||||||
export const coreSelector = createFeatureSelector<CoreState>('core');
|
export const coreSelector = createFeatureSelector<CoreState>('core');
|
||||||
|
@@ -33,9 +33,9 @@ export class RequestEffects {
|
|||||||
let body;
|
let body;
|
||||||
if (isNotEmpty(request.body)) {
|
if (isNotEmpty(request.body)) {
|
||||||
const serializer = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(request.body.type));
|
const serializer = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(request.body.type));
|
||||||
body = JSON.stringify(serializer.serialize(request.body));
|
body = serializer.serialize(request.body);
|
||||||
}
|
}
|
||||||
return this.restApi.request(request.method, request.href, body)
|
return this.restApi.request(request.method, request.href, body, request.options)
|
||||||
.map((data: DSpaceRESTV2Response) =>
|
.map((data: DSpaceRESTV2Response) =>
|
||||||
this.injector.get(request.getResponseParser()).parse(request, data))
|
this.injector.get(request.getResponseParser()).parse(request, data))
|
||||||
.do((response: RestResponse) => this.responseCache.add(request.href, response, this.EnvConfig.cache.msToLive))
|
.do((response: RestResponse) => this.responseCache.add(request.href, response, this.EnvConfig.cache.msToLive))
|
||||||
|
@@ -7,6 +7,9 @@ import { ResponseParsingService } from './parsing.service';
|
|||||||
import { RootResponseParsingService } from './root-response-parsing.service';
|
import { RootResponseParsingService } from './root-response-parsing.service';
|
||||||
import { BrowseResponseParsingService } from './browse-response-parsing.service';
|
import { BrowseResponseParsingService } from './browse-response-parsing.service';
|
||||||
import { ConfigResponseParsingService } from './config-response-parsing.service';
|
import { ConfigResponseParsingService } from './config-response-parsing.service';
|
||||||
|
import { AuthResponseParsingService } from '../auth/auth-response-parsing.service';
|
||||||
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
|
||||||
@@ -34,7 +37,8 @@ export abstract class RestRequest {
|
|||||||
public uuid: string,
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public method: RestRequestMethod = RestRequestMethod.Get,
|
public method: RestRequestMethod = RestRequestMethod.Get,
|
||||||
public body?: any
|
public body?: any,
|
||||||
|
public options?: HttpOptions
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +51,8 @@ export class GetRequest extends RestRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public uuid: string,
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public body?: any
|
public body?: any,
|
||||||
|
public options?: HttpOptions
|
||||||
) {
|
) {
|
||||||
super(uuid, href, RestRequestMethod.Get, body)
|
super(uuid, href, RestRequestMethod.Get, body)
|
||||||
}
|
}
|
||||||
@@ -57,7 +62,8 @@ export class PostRequest extends RestRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public uuid: string,
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public body?: any
|
public body?: any,
|
||||||
|
public options?: HttpOptions
|
||||||
) {
|
) {
|
||||||
super(uuid, href, RestRequestMethod.Post, body)
|
super(uuid, href, RestRequestMethod.Post, body)
|
||||||
}
|
}
|
||||||
@@ -67,7 +73,8 @@ export class PutRequest extends RestRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public uuid: string,
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public body?: any
|
public body?: any,
|
||||||
|
public options?: HttpOptions
|
||||||
) {
|
) {
|
||||||
super(uuid, href, RestRequestMethod.Put, body)
|
super(uuid, href, RestRequestMethod.Put, body)
|
||||||
}
|
}
|
||||||
@@ -77,7 +84,8 @@ export class DeleteRequest extends RestRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public uuid: string,
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public body?: any
|
public body?: any,
|
||||||
|
public options?: HttpOptions
|
||||||
) {
|
) {
|
||||||
super(uuid, href, RestRequestMethod.Delete, body)
|
super(uuid, href, RestRequestMethod.Delete, body)
|
||||||
}
|
}
|
||||||
@@ -87,7 +95,8 @@ export class OptionsRequest extends RestRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public uuid: string,
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public body?: any
|
public body?: any,
|
||||||
|
public options?: HttpOptions
|
||||||
) {
|
) {
|
||||||
super(uuid, href, RestRequestMethod.Options, body)
|
super(uuid, href, RestRequestMethod.Options, body)
|
||||||
}
|
}
|
||||||
@@ -97,7 +106,8 @@ export class HeadRequest extends RestRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public uuid: string,
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public body?: any
|
public body?: any,
|
||||||
|
public options?: HttpOptions
|
||||||
) {
|
) {
|
||||||
super(uuid, href, RestRequestMethod.Head, body)
|
super(uuid, href, RestRequestMethod.Head, body)
|
||||||
}
|
}
|
||||||
@@ -107,7 +117,8 @@ export class PatchRequest extends RestRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public uuid: string,
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public body?: any
|
public body?: any,
|
||||||
|
public options?: HttpOptions
|
||||||
) {
|
) {
|
||||||
super(uuid, href, RestRequestMethod.Patch, body)
|
super(uuid, href, RestRequestMethod.Patch, body)
|
||||||
}
|
}
|
||||||
@@ -134,7 +145,7 @@ export class FindAllRequest extends GetRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
uuid: string,
|
uuid: string,
|
||||||
href: string,
|
href: string,
|
||||||
public options?: FindAllOptions,
|
public body?: FindAllOptions,
|
||||||
) {
|
) {
|
||||||
super(uuid, href);
|
super(uuid, href);
|
||||||
}
|
}
|
||||||
@@ -171,6 +182,26 @@ export class ConfigRequest extends GetRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AuthPostRequest extends PostRequest {
|
||||||
|
constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
|
||||||
|
super(uuid, href, body, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
|
return AuthResponseParsingService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthGetRequest extends GetRequest {
|
||||||
|
constructor(uuid: string, href: string) {
|
||||||
|
super(uuid, href);
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
|
return AuthResponseParsingService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class RequestError extends Error {
|
export class RequestError extends Error {
|
||||||
statusText: string;
|
statusText: string;
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,21 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Request } from '@angular/http';
|
import { Request } from '@angular/http';
|
||||||
import { HttpClient, HttpResponse } from '@angular/common/http'
|
import { HttpClient, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http'
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { RestRequestMethod } from '../data/request.models';
|
import { RestRequestMethod } from '../data/request.models';
|
||||||
|
|
||||||
import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model';
|
import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model';
|
||||||
|
import { HttpObserve } from '@angular/common/http/src/client';
|
||||||
|
|
||||||
|
export interface HttpOptions {
|
||||||
|
body?: any;
|
||||||
|
headers?: HttpHeaders;
|
||||||
|
params?: HttpParams;
|
||||||
|
observe?: HttpObserve;
|
||||||
|
reportProgress?: boolean;
|
||||||
|
responseType?: 'arraybuffer' | 'blob' | 'json' | 'text';
|
||||||
|
withCredentials?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to access DSpace's REST API
|
* Service to access DSpace's REST API
|
||||||
@@ -45,8 +56,25 @@ export class DSpaceRESTv2Service {
|
|||||||
* @return {Observable<string>}
|
* @return {Observable<string>}
|
||||||
* An Observable<string> containing the response from the server
|
* An Observable<string> containing the response from the server
|
||||||
*/
|
*/
|
||||||
request(method: RestRequestMethod, url: string, body?: any): Observable<DSpaceRESTV2Response> {
|
request(method: RestRequestMethod, url: string, body?: any, options?: HttpOptions): Observable<DSpaceRESTV2Response> {
|
||||||
return this.http.request(method, url, { body, observe: 'response' })
|
const requestOptions: HttpOptions = {};
|
||||||
|
requestOptions.body = body;
|
||||||
|
requestOptions.observe = 'response';
|
||||||
|
if (options && options.headers) {
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Accept', 'application/json');
|
||||||
|
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
// requestOptions.headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
requestOptions.headers = headers;
|
||||||
|
/* const keys = options.headers.getAll('');
|
||||||
|
keys.forEach((key) => {
|
||||||
|
requestOptions.headers.append(key, options.headers.get(key));
|
||||||
|
})*/
|
||||||
|
}
|
||||||
|
if (options && options.responseType) {
|
||||||
|
// requestOptions.responseType = options.responseType;
|
||||||
|
}
|
||||||
|
return this.http.request(method, url, requestOptions)
|
||||||
.map((res) => ({ payload: res.body, statusCode: res.statusText }))
|
.map((res) => ({ payload: res.body, statusCode: res.statusText }))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log('Error: ', err);
|
console.log('Error: ', err);
|
||||||
|
@@ -23,9 +23,10 @@ export abstract class HALEndpointService {
|
|||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEndpoint(): Observable<string> {
|
public getEndpoint(linkName?: string): Observable<string> {
|
||||||
|
const mapLinkName = isNotEmpty(linkName) ? linkName : this.linkName;
|
||||||
return this.getEndpointMap()
|
return this.getEndpointMap()
|
||||||
.map((map: EndpointMap) => map[this.linkName])
|
.map((map: EndpointMap) => map[mapLinkName])
|
||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,19 +12,7 @@
|
|||||||
<a class="nav-link" routerLink="/home" routerLinkActive="active"><i class="fa fa-home fa-fw" aria-hidden="true"></i> {{ 'nav.home' | translate }}<span class="sr-only">(current)</span></a>
|
<a class="nav-link" routerLink="/home" routerLinkActive="active"><i class="fa fa-home fa-fw" aria-hidden="true"></i> {{ 'nav.home' | translate }}<span class="sr-only">(current)</span></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav" [ngClass]="{'mr-auto': (windowService.isMobileView() | async)}">
|
<ds-auth-nav-menu></ds-auth-nav-menu>
|
||||||
<li *ngIf="!(windowService.isMobileView() | async)" class="nav-item dropdown" (click)="$event.stopPropagation();">
|
|
||||||
<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>
|
|
||||||
<div ngbDropdownMenu aria-labelledby="dropdownLogin">
|
|
||||||
<ds-login-form></ds-login-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li *ngIf="(windowService.isMobileView() | async)" class="nav-item">
|
|
||||||
<a class="nav-link" routerLink="/login" routerLinkActive="active"><i class="fa fa-sign-in fa-fw" aria-hidden="true"></i> {{ 'nav.login' | translate }}<span class="sr-only">(current)</span></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { createSelector, Store } from '@ngrx/store';
|
import { createSelector, Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { RouterReducerState } from '@ngrx/router-store';
|
||||||
|
|
||||||
import { HeaderState } from './header.reducer';
|
import { HeaderState } from './header.reducer';
|
||||||
import { HeaderToggleAction } from './header.actions';
|
import { HeaderToggleAction } from './header.actions';
|
||||||
import { AppState } from '../app.reducer';
|
import { AppState } from '../app.reducer';
|
||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { fadeInOut } from '../shared/animations/fade';
|
|
||||||
|
|
||||||
const headerStateSelector = (state: AppState) => state.header;
|
const headerStateSelector = (state: AppState) => state.header;
|
||||||
const navCollapsedSelector = createSelector(headerStateSelector, (header: HeaderState) => header.navCollapsed);
|
const navCollapsedSelector = createSelector(headerStateSelector, (header: HeaderState) => header.navCollapsed);
|
||||||
@@ -15,12 +15,15 @@ const navCollapsedSelector = createSelector(headerStateSelector, (header: Header
|
|||||||
selector: 'ds-header',
|
selector: 'ds-header',
|
||||||
styleUrls: ['header.component.scss'],
|
styleUrls: ['header.component.scss'],
|
||||||
templateUrl: 'header.component.html',
|
templateUrl: 'header.component.html',
|
||||||
animations: [
|
|
||||||
fadeInOut
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
export class HeaderComponent implements OnInit {
|
export class HeaderComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* Whether user is authenticated.
|
||||||
|
* @type {Observable<string>}
|
||||||
|
*/
|
||||||
|
public isAuthenticated: Observable<boolean>;
|
||||||
public isNavBarCollapsed: Observable<boolean>;
|
public isNavBarCollapsed: Observable<boolean>;
|
||||||
|
public showAuth = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<AppState>,
|
private store: Store<AppState>,
|
||||||
@@ -29,6 +32,7 @@ export class HeaderComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// set loading
|
||||||
this.isNavBarCollapsed = this.store.select(navCollapsedSelector);
|
this.isNavBarCollapsed = this.store.select(navCollapsedSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
src/app/shared/auth-nav-menu/auth-nav-menu.component.html
Normal file
26
src/app/shared/auth-nav-menu/auth-nav-menu.component.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<ul class="navbar-nav" [ngClass]="{'mr-auto': (windowService.isMobileView() | async)}">
|
||||||
|
<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>
|
||||||
|
<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">
|
||||||
|
<ds-log-in></ds-log-in>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li *ngIf="!(isAuthenticated | async) && (windowService.isMobileView() | async)" class="nav-item">
|
||||||
|
<a class="nav-link" routerLink="/login" routerLinkActive="active"><i class="fa fa-sign-in fa-fw" aria-hidden="true"></i> {{ 'nav.login' | translate }}<span class="sr-only">(current)</span></a>
|
||||||
|
</li>
|
||||||
|
<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>
|
||||||
|
<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">
|
||||||
|
<ds-log-out></ds-log-out>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li *ngIf="(isAuthenticated | async) && (windowService.isMobileView() | async)" class="nav-item">
|
||||||
|
<a class="nav-link" routerLink="/logout" routerLinkActive="active"><i class="fa fa-sign-out fa-fw" aria-hidden="true"></i> {{ 'nav.logout' | translate }}<span class="sr-only">(current)</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
61
src/app/shared/auth-nav-menu/auth-nav-menu.component.ts
Normal file
61
src/app/shared/auth-nav-menu/auth-nav-menu.component.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
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 } from '../../app.reducer';
|
||||||
|
import { hasValue, isNotUndefined } from '../empty.util';
|
||||||
|
import { getAuthenticatedUser, isAuthenticated } from '../../core/auth/selectors';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
import { Eperson } from '../../core/eperson/models/eperson.model';
|
||||||
|
|
||||||
|
const routerStateSelector = (state: AppState) => state.router;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-auth-nav-menu',
|
||||||
|
templateUrl: './auth-nav-menu.component.html',
|
||||||
|
styleUrls: ['./auth-nav-menu.component.scss'],
|
||||||
|
animations: [fadeInOut, fadeOut]
|
||||||
|
})
|
||||||
|
export class AuthNavMenuComponent implements OnDestroy, OnInit {
|
||||||
|
/**
|
||||||
|
* Whether user is authenticated.
|
||||||
|
* @type {Observable<string>}
|
||||||
|
*/
|
||||||
|
public isAuthenticated: Observable<boolean>;
|
||||||
|
|
||||||
|
public showAuth = false;
|
||||||
|
|
||||||
|
public user: Observable<Eperson>;
|
||||||
|
|
||||||
|
protected subs: Subscription[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private appStore: Store<AppState>,
|
||||||
|
private coreStore: Store<CoreState>,
|
||||||
|
private windowService: HostWindowService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// set loading
|
||||||
|
this.isAuthenticated = this.coreStore.select(isAuthenticated);
|
||||||
|
|
||||||
|
this.user = this.appStore.select(getAuthenticatedUser);
|
||||||
|
|
||||||
|
this.subs.push(this.appStore.select(routerStateSelector)
|
||||||
|
.filter((router: RouterReducerState) => isNotUndefined(router))
|
||||||
|
.subscribe((router: RouterReducerState) => {
|
||||||
|
this.showAuth = router.state.url !== '/login';
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.subs
|
||||||
|
.filter((sub) => hasValue(sub))
|
||||||
|
.forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
29
src/app/shared/log-in/log-in.component.html
Normal file
29
src/app/shared/log-in/log-in.component.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<ds-loading *ngIf="(loading | async)"></ds-loading>
|
||||||
|
<form *ngIf="!(loading | async)" class="form-login px-4 py-3" (ngSubmit)="submit()" [formGroup]="form" novalidate>
|
||||||
|
<label for="inputEmail" class="sr-only">{{"login.form.email" | translate}}</label>
|
||||||
|
<input id="inputEmail"
|
||||||
|
autocomplete="off"
|
||||||
|
autofocus
|
||||||
|
class="form-control"
|
||||||
|
formControlName="email"
|
||||||
|
placeholder="{{'login.form.email' | translate}}"
|
||||||
|
required
|
||||||
|
type="email"
|
||||||
|
(input)="resetError($event)">
|
||||||
|
<label for="inputPassword" class="sr-only">{{"login.form.password" | translate}}</label>
|
||||||
|
<input id="inputPassword"
|
||||||
|
autocomplete="off"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="{{'login.form.password' | translate}}"
|
||||||
|
formControlName="password"
|
||||||
|
required
|
||||||
|
type="password"
|
||||||
|
(input)="resetError($event)">
|
||||||
|
<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" type="submit" [disabled]="!form.valid">{{"login.form.submit" | translate}}</button>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="#">{{"login.form.new-user" | translate}}</a>
|
||||||
|
<a class="dropdown-item" href="#">{{"login.form.forgot-password" | translate}}</a>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
116
src/app/shared/log-in/log-in.component.spec.ts
Normal file
116
src/app/shared/log-in/log-in.component.spec.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/* 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";
|
||||||
|
|
||||||
|
// reducers
|
||||||
|
import { reducer } from "../../app.reducers";
|
||||||
|
|
||||||
|
// models
|
||||||
|
import { User } from "../../core/models/user";
|
||||||
|
|
||||||
|
// services
|
||||||
|
import { MOCK_USER } from "../../core/services/user.service";
|
||||||
|
|
||||||
|
// this component to test
|
||||||
|
import { LogInComponent } from "./log-in.component";
|
||||||
|
|
||||||
|
describe("LogInComponent", () => {
|
||||||
|
|
||||||
|
let component: LogInComponent;
|
||||||
|
let fixture: ComponentFixture<LogInComponent>;
|
||||||
|
let page: Page;
|
||||||
|
let user: User = new User();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
user = MOCK_USER;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
// refine the test module by declaring the test component
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
MaterialModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
StoreModule.provideStore(reducer)
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
LogInComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
// create component and test fixture
|
||||||
|
fixture = TestBed.createComponent(LogInComponent);
|
||||||
|
|
||||||
|
// get test component from the fixture
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// create page
|
||||||
|
page = new Page(component, fixture);
|
||||||
|
|
||||||
|
// verify the fixture is stable (no pending tasks)
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
page.addPageElements();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a FormGroup comprised of FormControls", () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(component.form instanceof FormGroup).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should authenticate", () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
// set FormControl values
|
||||||
|
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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I represent the DOM elements and attach spies.
|
||||||
|
*
|
||||||
|
* @class Page
|
||||||
|
*/
|
||||||
|
class Page {
|
||||||
|
|
||||||
|
public emailInput: HTMLInputElement;
|
||||||
|
public navigateSpy: jasmine.Spy;
|
||||||
|
public passwordInput: HTMLInputElement;
|
||||||
|
|
||||||
|
constructor(private component: LogInComponent, private fixture: ComponentFixture<LogInComponent>) {
|
||||||
|
// use injector to get services
|
||||||
|
const injector = fixture.debugElement.injector;
|
||||||
|
const store = injector.get(Store);
|
||||||
|
|
||||||
|
// add spies
|
||||||
|
this.navigateSpy = spyOn(store, "dispatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
public addPageElements() {
|
||||||
|
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\"]";
|
||||||
|
this.passwordInput = this.fixture.debugElement.query(By.css(passwordInputSelector)).nativeElement;
|
||||||
|
}
|
||||||
|
}
|
163
src/app/shared/log-in/log-in.component.ts
Normal file
163
src/app/shared/log-in/log-in.component.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
|
||||||
|
// @ngrx
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
|
// rxjs
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/operator/filter';
|
||||||
|
import 'rxjs/add/operator/takeWhile';
|
||||||
|
|
||||||
|
// actions
|
||||||
|
import { AuthenticateAction, ResetAuthenticationErrorAction } from '../../core/auth/auth.actions';
|
||||||
|
|
||||||
|
// reducers
|
||||||
|
import {
|
||||||
|
getAuthenticationError,
|
||||||
|
isAuthenticated,
|
||||||
|
isAuthenticationLoading,
|
||||||
|
} from '../../core/auth/selectors';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { CoreState } from '../../core/core.reducers';
|
||||||
|
|
||||||
|
import { isNotEmpty, isNotNull } from '../empty.util';
|
||||||
|
import { fadeOut } from '../animations/fade';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /users/sign-in
|
||||||
|
* @class LogInComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-log-in',
|
||||||
|
templateUrl: './log-in.component.html',
|
||||||
|
styleUrls: ['./log-in.component.scss'],
|
||||||
|
animations: [fadeOut]
|
||||||
|
})
|
||||||
|
export class LogInComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The error if authentication fails.
|
||||||
|
* @type {Observable<string>}
|
||||||
|
*/
|
||||||
|
public error: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has authentication error.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public hasError = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the authentication is loading.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public loading: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authentication form.
|
||||||
|
* @type {FormGroup}
|
||||||
|
*/
|
||||||
|
public form: FormGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component state.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
private alive = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {FormBuilder} formBuilder
|
||||||
|
* @param {Store<State>} store
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private router: Router,
|
||||||
|
private store: Store<CoreState>
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook that is called after data-bound properties of a directive are initialized.
|
||||||
|
* @method ngOnInit
|
||||||
|
*/
|
||||||
|
public ngOnInit() {
|
||||||
|
// set formGroup
|
||||||
|
this.form = this.formBuilder.group({
|
||||||
|
email: ['', Validators.required],
|
||||||
|
password: ['', Validators.required]
|
||||||
|
});
|
||||||
|
|
||||||
|
// set error
|
||||||
|
this.error = this.store.select(getAuthenticationError)
|
||||||
|
.map((error) => {
|
||||||
|
this.hasError = (isNotEmpty(error));
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
|
||||||
|
// set loading
|
||||||
|
this.loading = this.store.select(isAuthenticationLoading);
|
||||||
|
|
||||||
|
// subscribe to success
|
||||||
|
this.store.select(isAuthenticated)
|
||||||
|
.takeWhile(() => this.alive)
|
||||||
|
.filter((authenticated) => authenticated)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook that is called when a directive, pipe or service is destroyed.
|
||||||
|
* @method ngOnDestroy
|
||||||
|
*/
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.alive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the home page.
|
||||||
|
* @method home
|
||||||
|
*/
|
||||||
|
public home() {
|
||||||
|
this.router.navigate(['/home']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset error.
|
||||||
|
*/
|
||||||
|
public resetError() {
|
||||||
|
if (this.hasError) {
|
||||||
|
this.store.dispatch(new ResetAuthenticationErrorAction());
|
||||||
|
this.hasError = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To to the registration page.
|
||||||
|
* @method register
|
||||||
|
*/
|
||||||
|
public register() {
|
||||||
|
this.router.navigate(['/register']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the authentication form.
|
||||||
|
* @method submit
|
||||||
|
*/
|
||||||
|
public submit() {
|
||||||
|
// get email and password values
|
||||||
|
const email: string = this.form.get('email').value;
|
||||||
|
const password: string = this.form.get('password').value;
|
||||||
|
|
||||||
|
// trim values
|
||||||
|
email.trim();
|
||||||
|
password.trim();
|
||||||
|
|
||||||
|
// dispatch AuthenticationAction
|
||||||
|
this.store.dispatch(new AuthenticateAction(email, password));
|
||||||
|
|
||||||
|
// clear form
|
||||||
|
this.form.reset();
|
||||||
|
}
|
||||||
|
}
|
9
src/app/shared/log-out/log-out.component.html
Normal file
9
src/app/shared/log-out/log-out.component.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<ds-loading *ngIf="(loading | async)"></ds-loading>
|
||||||
|
<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>
|
||||||
|
<button class="btn btn-lg btn-primary btn-block mt-3" (click)="logOut()">{{"logout.form.submit" | translate}}</button>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="#">{{"login.form.new-user" | translate}}</a>
|
||||||
|
<a class="dropdown-item" href="#">{{"login.form.forgot-password" | translate}}</a>
|
||||||
|
</div>
|
1
src/app/shared/log-out/log-out.component.scss
Normal file
1
src/app/shared/log-out/log-out.component.scss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import '../log-in/log-in.component.scss';
|
45
src/app/shared/log-out/log-out.component.spec.ts
Normal file
45
src/app/shared/log-out/log-out.component.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/* 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 ngrx
|
||||||
|
import { Store, StoreModule } from "@ngrx/store";
|
||||||
|
|
||||||
|
// reducers
|
||||||
|
import { reducer } from "../../app.reducers";
|
||||||
|
|
||||||
|
// test this component
|
||||||
|
import { SignOutComponent } from "./log-out.component";
|
||||||
|
|
||||||
|
describe("Component: Signout", () => {
|
||||||
|
let component: SignOutComponent;
|
||||||
|
let fixture: ComponentFixture<SignOutComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
// refine the test module by declaring the test component
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
StoreModule.provideStore(reducer)
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SignOutComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
// create component and test fixture
|
||||||
|
fixture = TestBed.createComponent(SignOutComponent);
|
||||||
|
|
||||||
|
// get test component from the fixture
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should create an instance", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
89
src/app/shared/log-out/log-out.component.ts
Normal file
89
src/app/shared/log-out/log-out.component.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
// @ngrx
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
|
// actions
|
||||||
|
import { LogOutAction } from '../../core/auth/auth.actions';
|
||||||
|
|
||||||
|
// reducers
|
||||||
|
import {
|
||||||
|
getLogOutError,
|
||||||
|
isAuthenticated,
|
||||||
|
isAuthenticationLoading,
|
||||||
|
} from '../../core/auth/selectors';
|
||||||
|
|
||||||
|
import { AppState } from '../../app.reducer';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { fadeOut } from '../animations/fade';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-log-out',
|
||||||
|
templateUrl: './log-out.component.html',
|
||||||
|
styleUrls: ['./log-out.component.scss'],
|
||||||
|
animations: [fadeOut]
|
||||||
|
})
|
||||||
|
export class LogOutComponent implements OnDestroy, OnInit {
|
||||||
|
/**
|
||||||
|
* The error if authentication fails.
|
||||||
|
* @type {Observable<string>}
|
||||||
|
*/
|
||||||
|
public error: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the logout is loading.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
public loading: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component state.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
private alive = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {Store<State>} store
|
||||||
|
*/
|
||||||
|
constructor(private router: Router,
|
||||||
|
private store: Store<AppState>) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook that is called when a directive, pipe or service is destroyed.
|
||||||
|
*/
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.alive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook that is called after data-bound properties of a directive are initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
// set error
|
||||||
|
this.error = this.store.select(getLogOutError);
|
||||||
|
|
||||||
|
// set loading
|
||||||
|
this.loading = this.store.select(isAuthenticationLoading);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the home page.
|
||||||
|
*/
|
||||||
|
public home() {
|
||||||
|
this.router.navigate(['/home']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To to the log in page.
|
||||||
|
*/
|
||||||
|
public logIn() {
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public logOut() {
|
||||||
|
this.store.dispatch(new LogOutAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,11 +0,0 @@
|
|||||||
<form class="form-login px-4 py-3">
|
|
||||||
<label for="inputEmail" class="sr-only">{{"login.form.email" | translate}}</label>
|
|
||||||
<input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
|
|
||||||
<label for="inputPassword" class="sr-only">{{"login.form.password" | translate}}</label>
|
|
||||||
<input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
|
|
||||||
<button class="btn btn-lg btn-primary btn-block mt-3" type="submit">{{"login.form.submit" | translate}}</button>
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<a class="dropdown-item" href="#">New around here? Sign up</a>
|
|
||||||
<a class="dropdown-item" href="#">Forgot password?</a>
|
|
||||||
</form>
|
|
||||||
|
|
@@ -1,10 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-login-form',
|
|
||||||
styleUrls: ['./login-form.component.scss'],
|
|
||||||
templateUrl: './login-form.component.html'
|
|
||||||
})
|
|
||||||
export class LoginFormComponent {
|
|
||||||
|
|
||||||
}
|
|
@@ -41,7 +41,9 @@ import { SearchResultGridElementComponent } from './object-grid/search-result-gr
|
|||||||
import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component';
|
import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component';
|
||||||
import { GridThumbnailComponent } from './object-grid/grid-thumbnail/grid-thumbnail.component';
|
import { GridThumbnailComponent } from './object-grid/grid-thumbnail/grid-thumbnail.component';
|
||||||
import { VarDirective } from './utils/var.directive';
|
import { VarDirective } from './utils/var.directive';
|
||||||
import { LoginFormComponent } from './login-form/login-form.component';
|
import { LogInComponent } from './log-in/log-in.component';
|
||||||
|
import { AuthNavMenuComponent } from './auth-nav-menu/auth-nav-menu.component';
|
||||||
|
import { LogOutComponent } from './log-out/log-out.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -64,12 +66,14 @@ const PIPES = [
|
|||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
// put shared components here
|
// put shared components here
|
||||||
|
AuthNavMenuComponent,
|
||||||
ComcolPageContentComponent,
|
ComcolPageContentComponent,
|
||||||
ComcolPageHeaderComponent,
|
ComcolPageHeaderComponent,
|
||||||
ComcolPageLogoComponent,
|
ComcolPageLogoComponent,
|
||||||
ErrorComponent,
|
ErrorComponent,
|
||||||
LoadingComponent,
|
LoadingComponent,
|
||||||
LoginFormComponent,
|
LogInComponent,
|
||||||
|
LogOutComponent,
|
||||||
ObjectListComponent,
|
ObjectListComponent,
|
||||||
AbstractListableElementComponent,
|
AbstractListableElementComponent,
|
||||||
WrapperListElementComponent,
|
WrapperListElementComponent,
|
||||||
|
Reference in New Issue
Block a user