Fix CVE-2024-46256 and CVE-2024-46257

- Schema validate against bad domain characters
- Integration test for CVE POC examples
- Cypress rewrite of plugins for file upload
This commit is contained in:
Jamie Curnow
2024-10-11 11:21:22 +10:00
parent 7c97516de6
commit c39d5433bc
19 changed files with 358 additions and 180 deletions

View File

@ -1,9 +1,14 @@
const logger = require('./logger');
const restler = require('@jc21/restler');
const axios = require('axios').default;
const BackendApi = function(config, token) {
this.config = config;
this.token = token;
this.axios = axios.create({
baseURL: config.baseUrl,
timeout: 5000,
});
};
/**
@ -14,129 +19,114 @@ BackendApi.prototype.setToken = function(token) {
};
/**
* @param {bool} returnOnError
*/
BackendApi.prototype._prepareOptions = function(returnOnError) {
let options = {
headers: {
Accept: 'application/json'
}
}
if (this.token) {
options.headers.Authorization = 'Bearer ' + this.token;
}
if (returnOnError) {
options.validateStatus = function () {
return true;
}
}
return options;
};
/**
* @param {*} response
* @param {function} resolve
* @param {function} reject
* @param {bool} returnOnError
*/
BackendApi.prototype._handleResponse = function(response, resolve, reject, returnOnError) {
logger('Response data:', response.data);
if (!returnOnError && typeof response.data === 'object' && typeof response.data.error === 'object') {
if (typeof response.data === 'object' && typeof response.data.error === 'object' && typeof response.data.error.message !== 'undefined') {
reject(new Error(response.data.error.code + ': ' + response.data.error.message));
} else {
reject(new Error('Error ' + response.status));
}
} else {
resolve(response.data);
}
};
/**
* @param {*} err
* @param {function} resolve
* @param {function} reject
* @param {bool} returnOnError
*/
BackendApi.prototype._handleError = function(err, resolve, reject, returnOnError) {
logger('Axios Error:', err);
if (returnOnError) {
resolve(typeof err.response.data !== 'undefined' ? err.response.data : err);
} else {
reject(err);
}
};
/**
* @param {string} method
* @param {string} path
* @param {bool} [returnOnError]
* @param {*} [data]
* @returns {Promise<object>}
*/
BackendApi.prototype.get = function(path, returnOnError) {
BackendApi.prototype.request = function (method, path, returnOnError, data) {
logger(method.toUpperCase(), this.config.baseUrl + path);
const options = this._prepareOptions(returnOnError);
return new Promise((resolve, reject) => {
let headers = {
Accept: 'application/json'
};
if (this.token) {
headers.Authorization = 'Bearer ' + this.token;
let opts = {
method: method,
url: path,
...options
}
if (data !== undefined && data !== null) {
opts.data = data;
}
logger('GET ', this.config.baseUrl + path);
restler
.get(this.config.baseUrl + path, {
headers: headers,
this.axios(opts)
.then((response) => {
this._handleResponse(response, resolve, reject, returnOnError);
})
.on('complete', function(data, response) {
logger('Response data:', data);
if (!returnOnError && data instanceof Error) {
reject(data);
} else if (!returnOnError && response.statusCode != 200) {
if (typeof data === 'object' && typeof data.error === 'object' && typeof data.error.message !== 'undefined') {
reject(new Error(data.error.code + ': ' + data.error.message));
} else {
reject(new Error('Error ' + response.statusCode));
}
} else {
resolve(data);
}
.catch((err) => {
this._handleError(err, resolve, reject, returnOnError);
});
});
};
/**
* @param {string} path
* @param {form} form
* @param {bool} [returnOnError]
* @returns {Promise<object>}
*/
BackendApi.prototype.delete = function(path, returnOnError) {
BackendApi.prototype.postForm = function (path, form, returnOnError) {
logger('POST', this.config.baseUrl + path);
const options = this._prepareOptions(returnOnError);
return new Promise((resolve, reject) => {
let headers = {
Accept: 'application/json'
};
if (this.token) {
headers.Authorization = 'Bearer ' + this.token;
const opts = {
...options,
...form.getHeaders(),
}
logger('DELETE ', this.config.baseUrl + path);
restler
.del(this.config.baseUrl + path, {
headers: headers,
this.axios.post(path, form, opts)
.then((response) => {
this._handleResponse(response, resolve, reject, returnOnError);
})
.on('complete', function(data, response) {
logger('Response data:', data);
if (!returnOnError && data instanceof Error) {
reject(data);
} else if (!returnOnError && response.statusCode != 200) {
if (typeof data === 'object' && typeof data.error === 'object' && typeof data.error.message !== 'undefined') {
reject(new Error(data.error.code + ': ' + data.error.message));
} else {
reject(new Error('Error ' + response.statusCode));
}
} else {
resolve(data);
}
.catch((err) => {
this._handleError(err, resolve, reject, returnOnError);
});
});
};
/**
* @param {string} path
* @param {object} data
* @param {bool} [returnOnError]
* @returns {Promise<object>}
*/
BackendApi.prototype.postJson = function(path, data, returnOnError) {
logger('POST ', this.config.baseUrl + path);
return this._putPostJson('postJson', path, data, returnOnError);
};
/**
* @param {string} path
* @param {object} data
* @param {bool} [returnOnError]
* @returns {Promise<object>}
*/
BackendApi.prototype.putJson = function(path, data, returnOnError) {
logger('PUT ', this.config.baseUrl + path);
return this._putPostJson('putJson', path, data, returnOnError);
};
/**
* @param {string} path
* @param {object} data
* @param {bool} [returnOnError]
* @returns {Promise<object>}
*/
BackendApi.prototype._putPostJson = function(fn, path, data, returnOnError) {
return new Promise((resolve, reject) => {
restler[fn](this.config.baseUrl + path, data, {
headers: {
Accept: 'application/json',
Authorization: 'Bearer ' + this.token,
},
}).on('complete', function(data, response) {
logger('Response data:', data);
if (!returnOnError && data instanceof Error) {
reject(data);
} else if (!returnOnError && (response.statusCode < 200 || response.statusCode >= 300)) {
if (typeof data === 'object' && typeof data.error === 'object' && typeof data.error.message !== 'undefined') {
reject(new Error(data.error.code + ': ' + data.error.message));
} else {
reject(new Error('Error ' + response.statusCode));
}
} else {
resolve(data);
}
});
});
};
module.exports = BackendApi;

View File

@ -1,8 +1,9 @@
const fs = require('fs');
const FormData = require('form-data');
const logger = require('./logger');
const Client = require('./client');
module.exports = function (config) {
logger('Client Ready using', config.baseUrl);
return {
@ -17,7 +18,7 @@ module.exports = function (config) {
backendApiGet: (options) => {
const api = new Client(config);
api.setToken(options.token);
return api.get(options.path, options.returnOnError || false);
return api.request('get', options.path, options.returnOnError || false);
},
/**
@ -31,7 +32,26 @@ module.exports = function (config) {
backendApiPost: (options) => {
const api = new Client(config);
api.setToken(options.token);
return api.postJson(options.path, options.data, options.returnOnError || false);
return api.request('post', options.path, options.returnOnError || false, options.data);
},
/**
* @param {object} options
* @param {string} options.token JWT
* @param {string} options.path API path
* @param {object} options.files
* @param {bool} [options.returnOnError] If true, will return instead of throwing errors
* @returns {string}
*/
backendApiPostFiles: (options) => {
const api = new Client(config);
api.setToken(options.token);
const form = new FormData();
for (let [key, value] of Object.entries(options.files)) {
form.append(key, fs.createReadStream(config.fixturesFolder + '/' + value));
}
return api.postForm(options.path, form, options.returnOnError || false);
},
/**
@ -45,7 +65,7 @@ module.exports = function (config) {
backendApiPut: (options) => {
const api = new Client(config);
api.setToken(options.token);
return api.putJson(options.path, options.data, options.returnOnError || false);
return api.request('put', options.path, options.returnOnError || false, options.data);
},
/**
@ -58,7 +78,7 @@ module.exports = function (config) {
backendApiDelete: (options) => {
const api = new Client(config);
api.setToken(options.token);
return api.delete(options.path, options.returnOnError || false);
return api.request('delete', options.path, options.returnOnError || false);
}
};
};
};