Files
kami-parse-server/src/Utils.js
Manuel bee889a329 Add security check (#7247)
* added Parse Server security option

* added SecurityRouter

* added Check class

* added CheckGroup class

* moved parameter validation to Utils

* added CheckRunner class

* added auto-run on server start

* added custom security checks as Parse Server option

* renamed script to check

* reformat log output

* added server config check

* improved contributing guideline

* improved contribution guide

* added check security log

* improved log format

* added checks

* fixed log fomat typo

* added database checks

* fixed database check

* removed database auth check in initial version

* improved contribution guide

* added security check tests

* fixed typo

* improved wording guidelines

* improved wording guidelines
2021-03-10 13:19:28 -06:00

189 lines
5.9 KiB
JavaScript

/**
* utils.js
* @file General purpose utilities
* @description General purpose utilities.
*/
const path = require('path');
const fs = require('fs').promises;
/**
* The general purpose utilities.
*/
class Utils {
/**
* @function getLocalizedPath
* @description Returns a localized file path accoring to the locale.
*
* Localized files are searched in subfolders of a given path, e.g.
*
* root/
* ├── base/ // base path to files
* │ ├── example.html // default file
* │ └── de/ // de language folder
* │ │ └── example.html // de localized file
* │ └── de-AT/ // de-AT locale folder
* │ │ └── example.html // de-AT localized file
*
* Files are matched with the locale in the following order:
* 1. Locale match, e.g. locale `de-AT` matches file in folder `de-AT`.
* 2. Language match, e.g. locale `de-AT` matches file in folder `de`.
* 3. Default; file in base folder is returned.
*
* @param {String} defaultPath The absolute file path, which is also
* the default path returned if localization is not available.
* @param {String} locale The locale.
* @returns {Promise<Object>} The object contains:
* - `path`: The path to the localized file, or the original path if
* localization is not available.
* - `subdir`: The subdirectory of the localized file, or undefined if
* there is no matching localized file.
*/
static async getLocalizedPath(defaultPath, locale) {
// Get file name and paths
const file = path.basename(defaultPath);
const basePath = path.dirname(defaultPath);
// If locale is not set return default file
if (!locale) {
return { path: defaultPath };
}
// Check file for locale exists
const localePath = path.join(basePath, locale, file);
const localeFileExists = await Utils.fileExists(localePath);
// If file for locale exists return file
if (localeFileExists) {
return { path: localePath, subdir: locale };
}
// Check file for language exists
const language = locale.split('-')[0];
const languagePath = path.join(basePath, language, file);
const languageFileExists = await Utils.fileExists(languagePath);
// If file for language exists return file
if (languageFileExists) {
return { path: languagePath, subdir: language };
}
// Return default file
return { path: defaultPath };
}
/**
* @function fileExists
* @description Checks whether a file exists.
* @param {String} path The file path.
* @returns {Promise<Boolean>} Is true if the file can be accessed, false otherwise.
*/
static async fileExists(path) {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
/**
* @function isPath
* @description Evaluates whether a string is a file path (as opposed to a URL for example).
* @param {String} s The string to evaluate.
* @returns {Boolean} Returns true if the evaluated string is a path.
*/
static isPath(s) {
return /(^\/)|(^\.\/)|(^\.\.\/)/.test(s);
}
/**
* Flattens an object and crates new keys with custom delimiters.
* @param {Object} obj The object to flatten.
* @param {String} [delimiter='.'] The delimiter of the newly generated keys.
* @param {Object} result
* @returns {Object} The flattened object.
**/
static flattenObject(obj, parentKey, delimiter = '.', result = {}) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const newKey = parentKey ? parentKey + delimiter + key : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
this.flattenObject(obj[key], newKey, delimiter, result);
} else {
result[newKey] = obj[key];
}
}
}
return result;
}
/**
* Determines whether an object is a Promise.
* @param {any} object The object to validate.
* @returns {Boolean} Returns true if the object is a promise.
*/
static isPromise(object) {
return object instanceof Promise;
}
/**
* Creates an object with all permutations of the original keys.
* @param {Object} object The object to permutate.
* @param {Integer} [index=0] The current key index.
* @param {Object} [current={}] The current result entry being composed.
* @param {Array} [results=[]] The resulting array of permutations.
*/
static getObjectKeyPermutations(object, index = 0, current = {}, results = []) {
const keys = Object.keys(object);
const key = keys[index];
const values = object[key];
for (const value of values) {
current[key] = value;
const nextIndex = index + 1;
if (nextIndex < keys.length) {
this.getObjectKeyPermutations(object, nextIndex, current, results);
} else {
const result = Object.assign({}, current);
results.push(result);
}
}
return results;
}
/**
* Validates parameters and throws if a parameter is invalid.
* Example parameter types syntax:
* ```
* {
* parameterName: {
* t: 'boolean',
* v: isBoolean,
* o: true
* },
* ...
* }
* ```
* @param {Object} params The parameters to validate.
* @param {Array<Object>} types The parameter types used for validation.
* @param {Object} types.t The parameter type; used for error message, not for validation.
* @param {Object} types.v The function to validate the parameter value.
* @param {Boolean} [types.o=false] Is true if the parameter is optional.
*/
static validateParams(params, types) {
for (const key of Object.keys(params)) {
const type = types[key];
const isOptional = !!type.o;
const param = params[key];
if (!(isOptional && param == null) && (!type.v(param))) {
throw `Invalid parameter ${key} must be of type ${type.t} but is ${typeof param}`;
}
}
}
}
module.exports = Utils;