(core) mitigate csrf by requiring custom header for unsafe methods

Summary:
For methods other than `GET`, `HEAD`, and `OPTIONS`, allow cookie-based authentication only if a certain custom header is present.

Specifically, we check that `X-Requested-With` is set to `XMLHttpRequest`. This is somewhat arbitrary, but allows us to use https://expressjs.com/en/api.html#req.xhr.

A request send from a browser that sets a custom header will prompt a preflight check, giving us a chance to check if the origin is trusted.

This diff deals with getting the header in place. There will be more work to do after this:
 * Make sure that all important endpoints are checking origin.  Skimming code, /api endpoint check origin, and some but not all others.
 * Add tests spot-testing origin checks.
 * Check on cases that authenticate differently.
    - Check the websocket endpoint - it can be connected to from an arbitrary site; there is per-doc access control but probably better to lock it down more.
    - There may be old endpoints that authenticate based on knowledge of a client id rather than cookies.

Test Plan: added a test

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2631
This commit is contained in:
Paul Fitzpatrick
2020-10-08 09:28:39 -04:00
parent 8dbcbba6b5
commit bd6a54e901
8 changed files with 96 additions and 28 deletions

View File

@@ -51,6 +51,7 @@ export class BaseAPI {
this._logger = options.logger || console;
this._headers = {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
...options.headers
};
this._extraParameters = options.extraParameters;
@@ -61,6 +62,16 @@ export class BaseAPI {
return this.request(url, init);
}
public defaultHeaders() {
return this._headers;
}
public defaultHeadersWithoutContentType() {
const headers = {...this.defaultHeaders()};
delete headers['Content-Type'];
return headers;
}
// Similar to request, but uses the axios library, and supports progress indicator.
@BaseAPI.countRequest
protected async requestAxios(url: string, config: AxiosRequestConfig): Promise<AxiosResponse> {