diff --git a/src/app/core/xsrf/browser-xsrf.service.spec.ts b/src/app/core/xsrf/browser-xsrf.service.spec.ts index 378df0e46b..aba3edd330 100644 --- a/src/app/core/xsrf/browser-xsrf.service.spec.ts +++ b/src/app/core/xsrf/browser-xsrf.service.spec.ts @@ -1,8 +1,12 @@ -import { BrowserXSRFService } from './browser-xsrf.service'; import { HttpClient } from '@angular/common/http'; -import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; +import { + HttpClientTestingModule, + HttpTestingController, +} from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; -import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; +import { BrowserXSRFService } from './browser-xsrf.service'; describe(`BrowserXSRFService`, () => { let service: BrowserXSRFService; @@ -14,7 +18,7 @@ describe(`BrowserXSRFService`, () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ HttpClientTestingModule ], - providers: [ BrowserXSRFService ] + providers: [ BrowserXSRFService ], }); httpClient = TestBed.inject(HttpClient); httpTestingController = TestBed.inject(HttpTestingController); @@ -22,20 +26,22 @@ describe(`BrowserXSRFService`, () => { }); describe(`initXSRFToken`, () => { - it(`should perform a POST to the csrf endpoint`, () => { + it(`should perform a GET to the csrf endpoint`, (done: DoneFn) => { service.initXSRFToken(httpClient)(); const req = httpTestingController.expectOne({ url: endpointURL, - method: 'POST' + method: 'GET', }); req.flush({}); httpTestingController.verify(); + expect().nothing(); + done(); }); - describe(`when the POST succeeds`, () => { - it(`should set tokenInitialized$ to true`, () => { + describe(`when the GET succeeds`, () => { + it(`should set tokenInitialized$ to true`, (done: DoneFn) => { service.initXSRFToken(httpClient)(); const req = httpTestingController.expectOne(endpointURL); @@ -44,19 +50,7 @@ describe(`BrowserXSRFService`, () => { httpTestingController.verify(); expect(service.tokenInitialized$.getValue()).toBeTrue(); - }); - }); - - describe(`when the POST fails`, () => { - it(`should set tokenInitialized$ to true`, () => { - service.initXSRFToken(httpClient)(); - - const req = httpTestingController.expectOne(endpointURL); - - req.error(new ErrorEvent('415')); - httpTestingController.verify(); - - expect(service.tokenInitialized$.getValue()).toBeTrue(); + done(); }); }); diff --git a/src/app/core/xsrf/browser-xsrf.service.ts b/src/app/core/xsrf/browser-xsrf.service.ts index 271e8dca47..121defc061 100644 --- a/src/app/core/xsrf/browser-xsrf.service.ts +++ b/src/app/core/xsrf/browser-xsrf.service.ts @@ -1,21 +1,25 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { take } from 'rxjs/operators'; + import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; -import { take, catchError } from 'rxjs/operators'; -import { of as observableOf } from 'rxjs'; import { XSRFService } from './xsrf.service'; +/** + * Browser (CSR) Service to obtain a new CSRF/XSRF token when needed by our RequestService + * to perform a modify request (e.g. POST/PUT/DELETE). + * NOTE: This is primarily necessary before the *first* modifying request, as the CSRF + * token may not yet be initialized. + */ @Injectable() export class BrowserXSRFService extends XSRFService { initXSRFToken(httpClient: HttpClient): () => Promise { - return () => new Promise((resolve) => { - httpClient.post(new RESTURLCombiner('/security/csrf').toString(), undefined).pipe( - // errors are to be expected if the token and the cookie don't match, that's what we're - // trying to fix for future requests, so just emit any observable to end up in the - // subscribe - catchError(() => observableOf(null)), + return () => new Promise((resolve) => { + // Force a new token to be created by calling the CSRF endpoint + httpClient.get(new RESTURLCombiner('/security/csrf').toString(), undefined).pipe( take(1), ).subscribe(() => { + // Once token is returned, set tokenInitialized to true. this.tokenInitialized$.next(true); }); diff --git a/src/app/core/xsrf/server-xsrf.service.spec.ts b/src/app/core/xsrf/server-xsrf.service.spec.ts index b2ace67dd0..05728edb42 100644 --- a/src/app/core/xsrf/server-xsrf.service.spec.ts +++ b/src/app/core/xsrf/server-xsrf.service.spec.ts @@ -1,6 +1,7 @@ -import { ServerXSRFService } from './server-xsrf.service'; import { HttpClient } from '@angular/common/http'; +import { ServerXSRFService } from './server-xsrf.service'; + describe(`ServerXSRFService`, () => { let service: ServerXSRFService; let httpClient: HttpClient; diff --git a/src/app/core/xsrf/server-xsrf.service.ts b/src/app/core/xsrf/server-xsrf.service.ts index 1577893f95..f729aa49a7 100644 --- a/src/app/core/xsrf/server-xsrf.service.ts +++ b/src/app/core/xsrf/server-xsrf.service.ts @@ -1,11 +1,16 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; + import { XSRFService } from './xsrf.service'; +/** + * Server (SSR) Service to obtain a new CSRF/XSRF token. Because SSR only triggers GET + * requests a CSRF token is never needed. + */ @Injectable() export class ServerXSRFService extends XSRFService { initXSRFToken(httpClient: HttpClient): () => Promise { - return () => new Promise((resolve) => { + return () => new Promise((resolve) => { // return immediately, and keep tokenInitialized$ false. The server side can make only GET // requests, since it can never get a valid XSRF cookie resolve(); diff --git a/src/app/core/xsrf/xsrf.service.spec.ts b/src/app/core/xsrf/xsrf.service.spec.ts index a7c5c01cb7..56564a294c 100644 --- a/src/app/core/xsrf/xsrf.service.spec.ts +++ b/src/app/core/xsrf/xsrf.service.spec.ts @@ -1,6 +1,7 @@ -import { XSRFService } from './xsrf.service'; import { HttpClient } from '@angular/common/http'; +import { XSRFService } from './xsrf.service'; + class XSRFServiceImpl extends XSRFService { initXSRFToken(httpClient: HttpClient): () => Promise { return () => null; diff --git a/src/app/core/xsrf/xsrf.service.ts b/src/app/core/xsrf/xsrf.service.ts index fb8dfe74b3..99b27021b6 100644 --- a/src/app/core/xsrf/xsrf.service.ts +++ b/src/app/core/xsrf/xsrf.service.ts @@ -2,6 +2,11 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; +/** + * Abstract CSRF/XSRF Service used to track whether a CSRF token has been received + * from the DSpace REST API. Once it is received, the "tokenInitialized$" flag will + * be set to "true". + */ @Injectable() export abstract class XSRFService { public tokenInitialized$: BehaviorSubject = new BehaviorSubject(false);