Improvement for authentication module

This commit is contained in:
Giuseppe Digilio
2018-02-09 09:55:55 +01:00
parent ae584915cf
commit 2f19f32d91
23 changed files with 431 additions and 124 deletions

View File

@@ -12,7 +12,9 @@ import { Store } from '@ngrx/store';
export class HomePageComponent implements OnInit {
public isAuthenticated: Observable<boolean>;
constructor(private store: Store<AppState>) {}
constructor(private store: Store<AppState>) {
}
ngOnInit() {
// set loading
this.isAuthenticated = this.store.select(isAuthenticated);

View File

@@ -1,14 +1,18 @@
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';
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model';
import { NormalizedEpersonModel } from '../eperson/models/NormalizedEperson.model';
export class AuthObjectFactory {
public static getConstructor(type): GenericConstructor<DSpaceObject> {
public static getConstructor(type): GenericConstructor<NormalizedDSpaceObject> {
switch (type) {
case AuthType.Eperson: {
return NormalizedEpersonModel
}
case AuthType.Status: {
return AuthStatus
return NormalizedAuthStatus
}
default: {

View File

@@ -6,7 +6,7 @@ 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 { AuthGetRequest, 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';
@@ -28,16 +28,16 @@ export class AuthRequestService extends HALEndpointService {
super();
}
protected submitRequest(request: RestRequest): Observable<any> {
protected fetchRequest(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`))),
Observable.throw(new Error(response.errorMessage))),
successResponse
.filter((response: AuthSuccessResponse) => isNotEmpty(response))
.map((response: AuthSuccessResponse) => response.authResponse)
.map((response: AuthSuccessResponse) => response.response)
.distinctUntilChanged());
}
@@ -51,8 +51,19 @@ export class AuthRequestService extends HALEndpointService {
.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))
.do((request: PostRequest) => this.requestService.configure(request, true))
.flatMap((request: PostRequest) => this.fetchRequest(request))
.distinctUntilChanged();
}
public getRequest(method: string, options?: HttpOptions): Observable<any> {
return this.getEndpoint()
.filter((href: string) => isNotEmpty(href))
.map((endpointURL) => this.getEndpointByMethod(endpointURL, method))
.distinctUntilChanged()
.map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options))
.do((request: PostRequest) => this.requestService.configure(request, true))
.flatMap((request: PostRequest) => this.fetchRequest(request))
.distinctUntilChanged();
}
}

View File

@@ -3,6 +3,8 @@ import { Inject, Injectable } from '@angular/core';
import { AuthObjectFactory } from './auth-object-factory';
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
import {
AuthErrorResponse,
AuthStatusResponse,
AuthSuccessResponse, ConfigSuccessResponse, ErrorResponse,
RestResponse
} from '../cache/response-cache.models';
@@ -11,10 +13,15 @@ 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 { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseParsingService } from '../data/parsing.service';
import { RestRequest } from '../data/request.models';
import { AuthType } from './auth-type';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { NormalizedAuthStatus } from './models/normalized-auth-status.model';
import { AuthStatus } from './models/auth-status.model';
@Injectable()
export class AuthResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
@@ -29,19 +36,14 @@ export class AuthResponseParsingService extends BaseResponseParsingService imple
}
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));
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && data.statusCode === '200') {
const response = this.process<AuthStatus,AuthType>(data.payload, request.href);
return new AuthStatusResponse(response[Object.keys(response)[0]][0], data.statusCode);
} else if (isEmpty(data.payload) && isNotEmpty(data.headers.get('authorization')) && data.statusCode === '200') {
return new AuthSuccessResponse(new AuthTokenInfo(data.headers.get('authorization')), data.statusCode);
} 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)
return new AuthStatusResponse(data.payload as AuthStatus, data.statusCode);
}
}
}

View File

@@ -0,0 +1,34 @@
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
/**
* The auth service.
*/
@Injectable()
export class AuthStorageService {
constructor(@Inject(PLATFORM_ID) private platformId: string) {}
public get(key: string): any {
let item = null;
if (isPlatformBrowser(this.platformId)) {
item = JSON.parse(localStorage.getItem(key));
}
return item;
}
public store(key: string, item: any) {
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem(key, JSON.stringify(item));
}
return true;
}
public remove(key: string) {
if (isPlatformBrowser(this.platformId)) {
localStorage.removeItem(key);
}
return true;
}
}

View File

@@ -1,3 +1,4 @@
export enum AuthType {
Eperson = 'eperson',
Status = 'status'
}

View File

@@ -6,6 +6,7 @@ import { type } from '../../shared/ngrx/type';
// import models
import { Eperson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
export const AuthActionTypes = {
AUTHENTICATE: type('dspace/auth/AUTHENTICATE'),
@@ -49,9 +50,9 @@ export class AuthenticateAction implements Action {
*/
export class AuthenticatedAction implements Action {
public type: string = AuthActionTypes.AUTHENTICATED;
payload: string;
payload: AuthTokenInfo;
constructor(token: string) {
constructor(token: AuthTokenInfo) {
this.payload = token;
}
}
@@ -108,10 +109,10 @@ export class AuthenticationErrorAction implements Action {
*/
export class AuthenticationSuccessAction implements Action {
public type: string = AuthActionTypes.AUTHENTICATE_SUCCESS;
payload: Eperson;
payload: AuthTokenInfo;
constructor(user: Eperson) {
this.payload = user;
constructor(token: AuthTokenInfo) {
this.payload = token;
}
}

View File

@@ -23,23 +23,7 @@ import {
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
*/
import { AuthStatus } from './models/auth-status.model';
@Injectable()
export class AuthEffects {
@@ -51,18 +35,29 @@ export class AuthEffects {
@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))
.map((response: AuthStatus) => new AuthenticationSuccessAction(response.token))
.catch((error) => Observable.of(new AuthenticationErrorAction(error)));
});
// It means "reacts to this action but don't send another"
@Effect()
public authenticateSuccess: Observable<Action> = this.actions$
.ofType(AuthActionTypes.AUTHENTICATE_SUCCESS)
.do((action: AuthenticationSuccessAction) => this.authService.storeToken(action.payload))
.map((action: AuthenticationSuccessAction) => new AuthenticatedAction(action.payload))
@Effect({dispatch: false})
public logOutSuccess: Observable<Action> = this.actions$
.ofType(AuthActionTypes.LOG_OUT_SUCCESS)
.do((action: LogOutSuccessAction) => this.authService.removeToken());
@Effect()
public authenticated: Observable<Action> = this.actions$
.ofType(AuthActionTypes.AUTHENTICATED)
.switchMap((action: AuthenticatedAction) => {
return this.authService.authenticatedUser()
return this.authService.authenticatedUser(action.payload)
.map((user: Eperson) => new AuthenticatedSuccessAction((user !== null), user))
.catch((error) => Observable.of(new AuthenticatedErrorAction(error)));
});
@@ -70,7 +65,7 @@ export class AuthEffects {
@Effect()
public createUser: Observable<Action> = this.actions$
.ofType(AuthActionTypes.REGISTRATION)
.debounceTime(500)
.debounceTime(500) // to remove when functionality is implemented
.switchMap((action: RegistrationAction) => {
return this.authService.create(action.payload)
.map((user: Eperson) => new RegistrationSuccessAction(user))

View File

@@ -0,0 +1,97 @@
import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import {
HttpClient, HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse,
HttpErrorResponse
} from '@angular/common/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/observable/throw'
import 'rxjs/add/operator/catch';
import { AuthService } from './auth.service';
import { AuthStatus } from './models/auth-status.model';
import { AuthType } from './auth-type';
import { ResourceType } from '../shared/resource-type';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { isNotEmpty } from '../../shared/empty.util';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private inj: Injector, private router: Router) { }
private isUnauthorized(status: number): boolean {
return status === 401 || status === 403;
}
private isLoginResponse(url: string): boolean {
return url.endsWith('/authn/login');
}
private isLogoutResponse(url: string): boolean {
return url.endsWith('/authn/logout');
}
private makeAuthStatusObject(authenticated:boolean, accessToken?: string, error?: string): AuthStatus {
const authStatus = new AuthStatus();
authStatus.id = null;
authStatus.okay = true;
if (authenticated) {
authStatus.authenticated = true;
authStatus.token = new AuthTokenInfo(accessToken);
} else {
authStatus.authenticated = false;
authStatus.error = JSON.parse(error);
}
return authStatus;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authService = this.inj.get(AuthService);
// Get the auth header from the service.
const Authorization = authService.getAuthHeader();
let authReq;
if (isNotEmpty(Authorization)) {
// Clone the request to add the new header.
authReq = req.clone({headers: req.headers.set('authorization', Authorization)});
} else {
authReq = req.clone();
}
// Pass on the cloned request instead of the original request.
return next.handle(authReq)
.map((response) => {
if (response instanceof HttpResponse && response.status === 200 && (this.isLoginResponse(response.url) || this.isLogoutResponse(response.url))) {
let authRes: HttpResponse<any>;
if (this.isLoginResponse(response.url)) {
const token = response.headers.get('authorization');
authRes = response.clone({body: this.makeAuthStatusObject(true, token)});
} else {
authRes = response.clone({body: this.makeAuthStatusObject(false)});
}
return authRes;
} else {
return response;
}
})
.catch((error, caught) => {
// Intercept an unauthorized error response
if (error instanceof HttpErrorResponse && this.isUnauthorized(error.status)) {
// Create a new HttpResponse and return it, so it can be handle properly by AuthService.
const authResponse = new HttpResponse({
body: this.makeAuthStatusObject(false, null, error.error),
headers: error.headers,
status: error.status,
statusText: error.statusText,
url: error.url
});
return Observable.of(authResponse);
} else {
// Return error response as is.
return Observable.throw(error);
}
}) as any;
}
}

View File

@@ -6,6 +6,7 @@ import {
// import models
import { Eperson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
/**
* The auth state.
@@ -25,6 +26,9 @@ export interface AuthState {
// true when loading
loading: boolean;
// access token
token?: AuthTokenInfo;
// the authenticated user
user?: Eperson;
}
@@ -62,8 +66,10 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
case AuthActionTypes.AUTHENTICATED_SUCCESS:
return Object.assign({}, state, {
authenticated: (action as AuthenticatedSuccessAction).payload.authenticated,
authenticated: true,
loaded: true,
error: undefined,
loading: false,
user: (action as AuthenticatedSuccessAction).payload.user
});
@@ -76,26 +82,26 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
});
case AuthActionTypes.AUTHENTICATE_SUCCESS:
case AuthActionTypes.REGISTRATION_SUCCESS:
const user: Eperson = (action as AuthenticationSuccessAction).payload;
const token: AuthTokenInfo = (action as AuthenticationSuccessAction).payload;
// verify user is not null
if (user === null) {
// verify token is not null
if (token === null) {
return state;
}
return Object.assign({}, state, {
authenticated: true,
error: undefined,
loading: false,
user: user
token: token
});
case AuthActionTypes.REGISTRATION_SUCCESS:
return state;
case AuthActionTypes.RESET_ERROR:
return Object.assign({}, state, {
authenticated: null,
error: undefined,
loaded: false,
loading: false
loading: false,
});
case AuthActionTypes.LOG_OUT_ERROR:
@@ -109,7 +115,10 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
return Object.assign({}, state, {
authenticated: false,
error: undefined,
user: undefined
loaded: false,
loading: false,
user: undefined,
token: undefined
});
case AuthActionTypes.REGISTRATION:

View File

@@ -6,6 +6,10 @@ 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';
import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { isNotEmpty, isNotNull } from '../../shared/empty.util';
import { AuthStorageService } from './auth-storage.service';
export const MOCK_USER = new Eperson();
MOCK_USER.id = '92a59227-ccf7-46da-9776-86c3fc64147f';
@@ -30,21 +34,28 @@ MOCK_USER.metadata = [
}
];
export const TOKENITEM = 'ds-token';
export const TOKENITEM = 'dsAuthInfo';
/**
* The user service.
* The auth service.
*/
@Injectable()
export class AuthService {
/**
* True if authenticated
* @type
* @type boolean
*/
private _authenticated = false;
constructor(private authRequestService: AuthRequestService) {}
/**
* The url to redirect after login
* @type string
*/
private _redirectUrl: string;
constructor(private authRequestService: AuthRequestService, private storage: AuthStorageService) {
}
/**
* Authenticate the user
@@ -53,32 +64,28 @@ export class AuthService {
* @param {string} password The user's password
* @returns {Observable<User>} The authenticated user observable.
*/
public authenticate(user: string, password: string): Observable<Eperson> {
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.
// const body = `user=${user}&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 body = encodeURI('password=' + password.toString() + '&user=' + user.toString());
const body = encodeURI(`password=${password}&user=${user}`);
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;
return Observable.of(MOCK_USER);
return this.authRequestService.postToEndpoint('login', body, options)
.map((status: AuthStatus) => {
if (status.authenticated) {
return status;
} else {
throw(new Error('Invalid email or password'));
}
})
return Observable.throw(new Error('Invalid email or password'));
}
/**
@@ -93,11 +100,25 @@ export class AuthService {
* Returns the authenticated user
* @returns {User}
*/
public authenticatedUser(): Observable<Eperson> {
public authenticatedUser(token: AuthTokenInfo): Observable<Eperson> {
// Normally you would do an HTTP request to determine if
// the user has an existing auth session on the server
// but, let's just return the mock user for this example.
return Observable.of(MOCK_USER);
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();
headers = headers.append('Accept', 'application/json');
headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
options.headers = headers;
return this.authRequestService.getRequest('status', options)
.map((status: AuthStatus) => {
if (status.authenticated) {
this._authenticated = true;
return status.eperson[0];
} else {
this._authenticated = false;
throw(new Error('Not authenticated'));
}
});
}
/**
@@ -119,7 +140,47 @@ export class AuthService {
public signout(): Observable<boolean> {
// Normally you would do an HTTP request sign end the session
// but, let's just return an observable of true.
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
const options: HttpOptions = Object.create({headers, responseType: 'text'});
return this.authRequestService.getRequest('logout', options)
.map((status: AuthStatus) => {
if (!status.authenticated) {
this._authenticated = false;
return Observable.of(true);
return true;
} else {
throw(new Error('Invalid email or password'));
}
})
}
public getAuthHeader(): string {
// Retrieve authentication token info
const token = this.storage.get(TOKENITEM);
return (isNotNull(token) && this._authenticated) ? `Bearer ${token.accessToken}` : '';
}
public getToken(): AuthTokenInfo {
// Retrieve authentication token info
return this.storage.get(TOKENITEM);
}
public storeToken(token: AuthTokenInfo) {
// Save authentication token info
return this.storage.store(TOKENITEM, JSON.stringify(token));
}
public removeToken() {
// Remove authentication token info
return this.storage.remove(TOKENITEM);
}
get redirectUrl(): string {
return this._redirectUrl;
}
set redirectUrl(value: string) {
this._redirectUrl = value;
}
}

View File

@@ -7,6 +7,7 @@ import { Store } from '@ngrx/store';
// reducers
import { CoreState } from '../core.reducers';
import { isAuthenticated } from './selectors';
import { AuthService } from './auth.service';
/**
* Prevent unauthorized activating and loading of routes
@@ -18,37 +19,44 @@ export class AuthenticatedGuard implements CanActivate, CanLoad {
/**
* @constructor
*/
constructor(private router: Router, private store: Store<CoreState>) {}
constructor(private authService: AuthService, private router: Router, private store: Store<CoreState>) {}
/**
* True when user is authenticated
* @method canActivate
*/
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
// get observable
const observable = this.store.select(isAuthenticated);
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const url = state.url;
// redirect to sign in page if user is not authenticated
observable.subscribe((authenticated) => {
if (!authenticated) {
this.router.navigate(['/login']);
return this.handleAuth(url);
}
});
return observable;
/**
* True when user is authenticated
* @method canActivateChild
*/
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.canActivate(route, state);
}
/**
* True when user is authenticated
* @method canLoad
*/
canLoad(route: Route): Observable<boolean> | Promise<boolean> | boolean {
canLoad(route: Route): Observable<boolean> {
const url = `/${route.path}`;
return this.handleAuth(url);
}
private handleAuth(url: string): Observable<boolean> {
// get observable
const observable = this.store.select(isAuthenticated);
// redirect to sign in page if user is not authenticated
observable.subscribe((authenticated) => {
if (!authenticated) {
this.authService.redirectUrl = url;
this.router.navigate(['/login']);
}
});

View File

@@ -0,0 +1,7 @@
export interface AuthError {
error: string,
message: string,
path: string,
status: number
timestamp: number
}

View File

@@ -1,5 +0,0 @@
export interface AuthInfo {
access_token?: string,
expires?: number,
expires_in?: number
}

View File

@@ -1,4 +1,7 @@
import { AuthError } from './auth-error.model';
import { AuthTokenInfo } from './auth-token-info.model';
import { DSpaceObject } from '../../shared/dspace-object.model';
import { Eperson } from '../../eperson/models/eperson.model';
export class AuthStatus extends DSpaceObject {
@@ -6,4 +9,9 @@ export class AuthStatus extends DSpaceObject {
authenticated: boolean;
error?: AuthError;
eperson: Eperson[];
token?: AuthTokenInfo
}

View File

@@ -0,0 +1,11 @@
export class AuthTokenInfo {
public accessToken: string;
public expires?: number;
constructor(token: string, expiresIn?: number) {
this.accessToken = token.replace('Bearer ', '');
if (expiresIn) {
this.expires = expiresIn * 1000 + Date.now();
}
}
}

View File

@@ -0,0 +1,26 @@
import { AuthStatus } from './auth-status.model';
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { mapsTo } from '../../cache/builders/build-decorators';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { Eperson } from '../../eperson/models/eperson.model';
@mapsTo(AuthStatus)
@inheritSerialization(NormalizedDSpaceObject)
export class NormalizedAuthStatus extends NormalizedDSpaceObject {
/**
* True if REST API is up and running, should never return false
*/
@autoserialize
okay: boolean;
/**
* True if the token is valid, false if there was no token or the token wasn't valid
*/
@autoserialize
authenticated: boolean;
@autoserializeAs(Eperson)
eperson: Eperson[];
}

View File

@@ -2,12 +2,16 @@ import { RequestError } from '../data/request.models';
import { PageInfo } from '../shared/page-info.model';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { ConfigObject } from '../shared/config/config.model';
import { AuthTokenInfo } from '../auth/models/auth-token-info.model';
import { NormalizedAuthStatus } from '../auth/models/normalized-auth-status.model';
import { AuthStatus } from '../auth/models/auth-status.model';
/* tslint:disable:max-classes-per-file */
export class RestResponse {
public toCache = true;
constructor(
public isSuccessful: boolean,
public statusCode: string
public statusCode: string,
) { }
}
@@ -63,11 +67,31 @@ export class ConfigSuccessResponse extends RestResponse {
}
}
export class AuthSuccessResponse extends RestResponse {
export class AuthStatusResponse extends RestResponse {
public toCache = false;
constructor(
public authResponse: any,
public response: AuthStatus,
public statusCode: string
) {
super(true, statusCode);
}
}
export class AuthSuccessResponse extends RestResponse {
public toCache = false;
constructor(
public response: AuthTokenInfo,
public statusCode: string
) {
super(true, statusCode);
}
}
export class AuthErrorResponse extends RestResponse {
public toCache = false;
constructor(
public response: any,
public statusCode: string,
public pageInfo?: PageInfo
) {
super(true, statusCode);
}

View File

@@ -41,6 +41,10 @@ import { UUIDService } from './shared/uuid.service';
import { AuthService } from './auth/auth.service';
import { AuthenticatedGuard } from './auth/authenticated.guard';
import { AuthRequestService } from './auth/auth-request.service';
import { AuthResponseParsingService } from './auth/auth-response-parsing.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth/auth.interceptor';
import { AuthStorageService } from './auth/auth-storage.service';
const IMPORTS = [
CommonModule,
@@ -60,7 +64,9 @@ const PROVIDERS = [
ApiService,
AuthenticatedGuard,
AuthRequestService,
AuthResponseParsingService,
AuthService,
AuthStorageService,
CommunityDataService,
CollectionDataService,
DSOResponseParsingService,
@@ -83,7 +89,13 @@ const PROVIDERS = [
SubmissionFormsConfigService,
SubmissionSectionsConfigService,
UUIDService,
{ provide: NativeWindowService, useFactory: NativeWindowFactory }
{ provide: NativeWindowService, useFactory: NativeWindowFactory },
// register TokenInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
];
@NgModule({

View File

@@ -193,8 +193,8 @@ export class AuthPostRequest extends PostRequest {
}
export class AuthGetRequest extends GetRequest {
constructor(uuid: string, href: string) {
super(uuid, href);
constructor(uuid: string, href: string, public options?: HttpOptions) {
super(uuid, href, null, options);
}
getResponseParser(): GenericConstructor<ResponseParsingService> {

View File

@@ -17,6 +17,7 @@ import { RequestConfigureAction, RequestExecuteAction } from './request.actions'
import { GetRequest, RestRequest, RestRequestMethod } from './request.models';
import { RequestEntry, RequestState } from './request.reducer';
import { ResponseCacheRemoveAction } from '../cache/response-cache.actions';
@Injectable()
export class RequestService {
@@ -66,9 +67,9 @@ export class RequestService {
.flatMap((uuid: string) => this.getByUUID(uuid));
}
configure<T extends CacheableObject>(request: RestRequest): void {
if (request.method !== RestRequestMethod.Get || !this.isCachedOrPending(request)) {
this.dispatchRequest(request);
configure<T extends CacheableObject>(request: RestRequest, overrideRequest: boolean = false): void {
if (request.method !== RestRequestMethod.Get || !this.isCachedOrPending(request) || overrideRequest) {
this.dispatchRequest(request, overrideRequest);
}
}
@@ -101,10 +102,10 @@ export class RequestService {
return isCached || isPending;
}
private dispatchRequest(request: RestRequest) {
private dispatchRequest(request: RestRequest, overrideRequest: boolean) {
this.store.dispatch(new RequestConfigureAction(request));
this.store.dispatch(new RequestExecuteAction(request.uuid));
if (request.method === RestRequestMethod.Get) {
if (request.method === RestRequestMethod.Get && !overrideRequest) {
this.trackRequestsOnTheirWayToTheStore(request);
}
}

View File

@@ -1,3 +1,5 @@
import { HttpHeaders } from '@angular/common/http';
export interface DSpaceRESTV2Response {
payload: {
[name: string]: any;
@@ -5,5 +7,6 @@ export interface DSpaceRESTV2Response {
_links?: any;
page?: any;
},
headers: HttpHeaders,
statusCode: string
}

View File

@@ -61,21 +61,16 @@ export class DSpaceRESTv2Service {
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));
})*/
requestOptions.headers = Object.assign(new HttpHeaders(), options.headers);
}
if (options && options.responseType) {
// requestOptions.responseType = options.responseType;
requestOptions.responseType = options.responseType;
}
return this.http.request(method, url, requestOptions)
.map((res) => ({ payload: res.body, statusCode: res.statusText }))
.map((res) => {
console.log(res);
return ({ payload: res.body, headers: res.headers, statusCode: res.statusText })
})
.catch((err) => {
console.log('Error: ', err);
return Observable.throw(err);