mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Retrieve the XSRF token first, and set it as both the XSRF header and cookie
This commit is contained in:
@@ -100,14 +100,12 @@ export abstract class AuthRequestService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Factory function to create the request object to send. This needs to be a POST client side and
|
* Factory function to create the request object to send.
|
||||||
* a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow
|
|
||||||
* only the server IP to send a GET to this endpoint.
|
|
||||||
*
|
*
|
||||||
* @param href The href to send the request to
|
* @param href The href to send the request to
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected abstract createShortLivedTokenRequest(href: string): GetRequest | PostRequest;
|
protected abstract createShortLivedTokenRequest(href: string): Observable<PostRequest>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a request to retrieve a short-lived token which provides download access of restricted files
|
* Send a request to retrieve a short-lived token which provides download access of restricted files
|
||||||
@@ -117,7 +115,7 @@ export abstract class AuthRequestService {
|
|||||||
filter((href: string) => isNotEmpty(href)),
|
filter((href: string) => isNotEmpty(href)),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
map((href: string) => new URLCombiner(href, this.shortlivedtokensEndpoint).toString()),
|
map((href: string) => new URLCombiner(href, this.shortlivedtokensEndpoint).toString()),
|
||||||
map((endpointURL: string) => this.createShortLivedTokenRequest(endpointURL)),
|
switchMap((endpointURL: string) => this.createShortLivedTokenRequest(endpointURL)),
|
||||||
tap((request: RestRequest) => this.requestService.send(request)),
|
tap((request: RestRequest) => this.requestService.send(request)),
|
||||||
switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID<ShortLivedToken>(request.uuid)),
|
switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID<ShortLivedToken>(request.uuid)),
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
|
@@ -4,6 +4,7 @@ import { PostRequest } from '../data/request.models';
|
|||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client side version of the service to send authentication requests
|
* Client side version of the service to send authentication requests
|
||||||
@@ -20,15 +21,13 @@ export class BrowserAuthRequestService extends AuthRequestService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create the request object to send. This needs to be a POST client side and
|
* Factory function to create the request object to send.
|
||||||
* a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow
|
|
||||||
* only the server IP to send a GET to this endpoint.
|
|
||||||
*
|
*
|
||||||
* @param href The href to send the request to
|
* @param href The href to send the request to
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected createShortLivedTokenRequest(href: string): PostRequest {
|
protected createShortLivedTokenRequest(href: string): Observable<PostRequest> {
|
||||||
return new PostRequest(this.requestService.generateRequestId(), href);
|
return observableOf(new PostRequest(this.requestService.generateRequestId(), href));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,8 +4,19 @@ import { PostRequest } from '../data/request.models';
|
|||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { HttpHeaders, HttpXsrfTokenExtractor } from '@angular/common/http';
|
import {
|
||||||
import { XSRF_REQUEST_HEADER } from '../xsrf/xsrf.interceptor';
|
HttpHeaders,
|
||||||
|
HttpXsrfTokenExtractor,
|
||||||
|
HttpClient,
|
||||||
|
HttpResponse
|
||||||
|
} from '@angular/common/http';
|
||||||
|
import {
|
||||||
|
XSRF_REQUEST_HEADER,
|
||||||
|
XSRF_RESPONSE_HEADER,
|
||||||
|
DSPACE_XSRF_COOKIE
|
||||||
|
} from '../xsrf/xsrf.interceptor';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server side version of the service to send authentication requests
|
* Server side version of the service to send authentication requests
|
||||||
@@ -17,29 +28,40 @@ export class ServerAuthRequestService extends AuthRequestService {
|
|||||||
halService: HALEndpointService,
|
halService: HALEndpointService,
|
||||||
requestService: RequestService,
|
requestService: RequestService,
|
||||||
rdbService: RemoteDataBuildService,
|
rdbService: RemoteDataBuildService,
|
||||||
protected tokenExtractor: HttpXsrfTokenExtractor,
|
protected httpClient: HttpClient,
|
||||||
) {
|
) {
|
||||||
super(halService, requestService, rdbService);
|
super(halService, requestService, rdbService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create the request object to send. This needs to be a POST client side and
|
* Factory function to create the request object to send.
|
||||||
* a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow
|
|
||||||
* only the server IP to send a GET to this endpoint.
|
|
||||||
*
|
*
|
||||||
* @param href The href to send the request to
|
* @param href The href to send the request to
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected createShortLivedTokenRequest(href: string): PostRequest {
|
protected createShortLivedTokenRequest(href: string): Observable<PostRequest> {
|
||||||
let options = new HttpHeaders();
|
// First do a call to the root endpoint in order to get an XSRF token
|
||||||
options = options.set('Content-Type', 'application/json; charset=utf-8');
|
return this.httpClient.get(this.halService.getRootHref(), { observe: 'response' }).pipe(
|
||||||
options = options.set(XSRF_REQUEST_HEADER, this.tokenExtractor.getToken());
|
// retrieve the XSRF token from the response header
|
||||||
let requestOptions = {
|
map((response: HttpResponse<any>) => response.headers.get(XSRF_RESPONSE_HEADER)),
|
||||||
headers: options,
|
// Use that token to create an HttpHeaders object
|
||||||
};
|
map((xsrfToken: string) => new HttpHeaders()
|
||||||
return Object.assign(new PostRequest(this.requestService.generateRequestId(), href, {}, requestOptions), {
|
.set('Content-Type', 'application/json; charset=utf-8')
|
||||||
responseMsToLive: 2 * 1000 // A short lived token is only valid for 2 seconds.
|
// set the token as the XSRF header
|
||||||
});
|
.set(XSRF_REQUEST_HEADER, xsrfToken)
|
||||||
|
// and as the DSPACE-XSRF-COOKIE
|
||||||
|
.set('Cookie', `${DSPACE_XSRF_COOKIE}=${xsrfToken}`)),
|
||||||
|
map((headers: HttpHeaders) =>
|
||||||
|
// Create a new PostRequest using those headers and the given href
|
||||||
|
new PostRequest(
|
||||||
|
this.requestService.generateRequestId(),
|
||||||
|
href,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: headers,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
16
src/app/core/services/server-xhr.service.ts
Normal file
16
src/app/core/services/server-xhr.service.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { XhrFactory } from '@angular/common';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import * as xhr2 from 'xhr2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the default XhrFactoru server side, to allow us to set cookies in requests to the
|
||||||
|
* backend. This was added to be able to perform a working XSRF request from the node server, as it
|
||||||
|
* needs to set a cookie for the XSRF token
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ServerXhrService implements XhrFactory {
|
||||||
|
build(): XMLHttpRequest {
|
||||||
|
xhr2.prototype._restrictedHeaders.cookie = false;
|
||||||
|
return new xhr2.XMLHttpRequest();
|
||||||
|
}
|
||||||
|
}
|
@@ -19,6 +19,8 @@ export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';
|
|||||||
export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN';
|
export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN';
|
||||||
// Name of cookie where we store the XSRF token
|
// Name of cookie where we store the XSRF token
|
||||||
export const XSRF_COOKIE = 'XSRF-TOKEN';
|
export const XSRF_COOKIE = 'XSRF-TOKEN';
|
||||||
|
// Name of cookie the backend expects the XSRF token to be in
|
||||||
|
export const DSPACE_XSRF_COOKIE = 'DSPACE-XSRF-COOKIE';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Http Interceptor intercepting Http Requests & Responses to
|
* Custom Http Interceptor intercepting Http Requests & Responses to
|
||||||
|
@@ -33,6 +33,8 @@ import { Angulartics2Mock } from '../../app/shared/mocks/angulartics2.service.mo
|
|||||||
import { AuthRequestService } from '../../app/core/auth/auth-request.service';
|
import { AuthRequestService } from '../../app/core/auth/auth-request.service';
|
||||||
import { ServerAuthRequestService } from '../../app/core/auth/server-auth-request.service';
|
import { ServerAuthRequestService } from '../../app/core/auth/server-auth-request.service';
|
||||||
import { ServerInitService } from './server-init.service';
|
import { ServerInitService } from './server-init.service';
|
||||||
|
import { XhrFactory } from '@angular/common';
|
||||||
|
import { ServerXhrService } from '../../app/core/services/server-xhr.service';
|
||||||
|
|
||||||
export function createTranslateLoader(transferState: TransferState) {
|
export function createTranslateLoader(transferState: TransferState) {
|
||||||
return new TranslateServerLoader(transferState, 'dist/server/assets/i18n/', '.json');
|
return new TranslateServerLoader(transferState, 'dist/server/assets/i18n/', '.json');
|
||||||
@@ -104,6 +106,10 @@ export function createTranslateLoader(transferState: TransferState) {
|
|||||||
provide: HardRedirectService,
|
provide: HardRedirectService,
|
||||||
useClass: ServerHardRedirectService,
|
useClass: ServerHardRedirectService,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: XhrFactory,
|
||||||
|
useClass: ServerXhrService,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ServerAppModule {
|
export class ServerAppModule {
|
||||||
|
Reference in New Issue
Block a user