diff --git a/.gitignore b/.gitignore index e4e19156c2..e184de3be6 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,6 @@ lib/ # Redis Dump dump.rdb + +# Ignore built TypeScript files +types/**/* diff --git a/package-lock.json b/package-lock.json index 2fa1258792..bf2a3f79f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4521,6 +4521,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true } } }, @@ -4694,6 +4700,14 @@ "ast-module-types": "^2.7.1", "node-source-walk": "^4.2.0", "typescript": "^3.9.7" + }, + "dependencies": { + "typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true + } } }, "dicer": { @@ -5980,6 +5994,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true } } }, @@ -8802,6 +8822,12 @@ "requires": { "has-flag": "^4.0.0" } + }, + "typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true } } }, @@ -10389,6 +10415,14 @@ "ast-module-types": "^2.7.1", "node-source-walk": "^4.2.0", "typescript": "^3.9.7" + }, + "dependencies": { + "typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true + } } }, "ms": { @@ -12197,9 +12231,9 @@ } }, "typescript": { - "version": "3.9.9", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", - "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index b675bef87c..4509b2cf79 100644 --- a/package.json +++ b/package.json @@ -91,13 +91,14 @@ "jsdoc-babel": "0.5.0", "lint-staged": "10.2.3", "madge": "4.0.2", - "mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter", "mock-files-adapter": "file:spec/dependencies/mock-files-adapter", + "mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter", "mongodb-runner": "4.8.1", "mongodb-version-list": "1.0.0", "node-fetch": "2.6.1", "nyc": "15.1.0", "prettier": "2.0.5", + "typescript": "4.2.4", "yaml": "1.10.0" }, "scripts": { @@ -124,7 +125,9 @@ "prettier": "prettier --write '{src,spec}/{**/*,*}.js'", "prepare": "npm run build", "postinstall": "node -p 'require(\"./postinstall.js\")()'", - "madge:circular": "node_modules/.bin/madge ./src --circular" + "madge:circular": "node_modules/.bin/madge ./src --circular", + "pretypes": "npm run build", + "types": "npx typescript" }, "engines": { "node": ">= 8" @@ -155,5 +158,6 @@ "eslint --fix --cache", "git add" ] - } + }, + "types": "./types/index.d.ts" } diff --git a/src/Adapters/Auth/OAuth1Client.js b/src/Adapters/Auth/OAuth1Client.js index f622852e9a..7e24856e89 100644 --- a/src/Adapters/Auth/OAuth1Client.js +++ b/src/Adapters/Auth/OAuth1Client.js @@ -1,231 +1,236 @@ -var https = require('https'), - crypto = require('crypto'); -var Parse = require('parse/node').Parse; - -var OAuth = function (options) { - if (!options) { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'No options passed to OAuth'); +const https = require('https'); +const crypto = require('crypto'); +const Parse = require('parse/node').Parse; + +class OAuth { + constructor(options) { + if (!options) { + throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'No options passed to OAuth'); + } + this.consumer_key = options.consumer_key; + this.consumer_secret = options.consumer_secret; + this.auth_token = options.auth_token; + this.auth_token_secret = options.auth_token_secret; + this.host = options.host; + this.oauth_params = options.oauth_params || {}; + this.signatureMethod = 'HMAC-SHA1'; + this.version = '1.0'; } - this.consumer_key = options.consumer_key; - this.consumer_secret = options.consumer_secret; - this.auth_token = options.auth_token; - this.auth_token_secret = options.auth_token_secret; - this.host = options.host; - this.oauth_params = options.oauth_params || {}; -}; - -OAuth.prototype.send = function (method, path, params, body) { - var request = this.buildRequest(method, path, params, body); - // Encode the body properly, the current Parse Implementation don't do it properly - return new Promise(function (resolve, reject) { - var httpRequest = https - .request(request, function (res) { - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - data = JSON.parse(data); - resolve(data); + + send(method, path, params, body) { + var request = this.buildRequest(method, path, params, body); + // Encode the body properly, the current Parse Implementation don't do it properly + return new Promise(function (resolve, reject) { + var httpRequest = https + .request(request, function (res) { + var data = ''; + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function () { + data = JSON.parse(data); + resolve(data); + }); + }) + .on('error', function () { + reject('Failed to make an OAuth request'); }); - }) - .on('error', function () { - reject('Failed to make an OAuth request'); - }); - if (request.body) { - httpRequest.write(request.body); + if (request.body) { + httpRequest.write(request.body); + } + httpRequest.end(); + }); + } + + buildRequest(method, path, params, body) { + if (path.indexOf('/') != 0) { + path = '/' + path; + } + if (params && Object.keys(params).length > 0) { + path += '?' + OAuth.buildParameterString(params); } - httpRequest.end(); - }); -}; -OAuth.prototype.buildRequest = function (method, path, params, body) { - if (path.indexOf('/') != 0) { - path = '/' + path; - } - if (params && Object.keys(params).length > 0) { - path += '?' + OAuth.buildParameterString(params); - } + var request = { + host: this.host, + path: path, + method: method.toUpperCase(), + }; - var request = { - host: this.host, - path: path, - method: method.toUpperCase(), - }; + var oauth_params = this.oauth_params || {}; + oauth_params.oauth_consumer_key = this.consumer_key; + if (this.auth_token) { + oauth_params['oauth_token'] = this.auth_token; + } - var oauth_params = this.oauth_params || {}; - oauth_params.oauth_consumer_key = this.consumer_key; - if (this.auth_token) { - oauth_params['oauth_token'] = this.auth_token; - } + request = OAuth.signRequest( + request, + oauth_params, + this.consumer_secret, + this.auth_token_secret + ); - request = OAuth.signRequest(request, oauth_params, this.consumer_secret, this.auth_token_secret); + if (body && Object.keys(body).length > 0) { + request.body = OAuth.buildParameterString(body); + } + return request; + } - if (body && Object.keys(body).length > 0) { - request.body = OAuth.buildParameterString(body); + get(path, params) { + return this.send('GET', path, params); } - return request; -}; - -OAuth.prototype.get = function (path, params) { - return this.send('GET', path, params); -}; - -OAuth.prototype.post = function (path, params, body) { - return this.send('POST', path, params, body); -}; - -/* - Proper string %escape encoding -*/ -OAuth.encode = function (str) { - // discuss at: http://phpjs.org/functions/rawurlencode/ - // original by: Brett Zamir (http://brett-zamir.me) - // input by: travc - // input by: Brett Zamir (http://brett-zamir.me) - // input by: Michael Grier - // input by: Ratheous - // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - // bugfixed by: Brett Zamir (http://brett-zamir.me) - // bugfixed by: Joris - // reimplemented by: Brett Zamir (http://brett-zamir.me) - // reimplemented by: Brett Zamir (http://brett-zamir.me) - // note: This reflects PHP 5.3/6.0+ behavior - // note: Please be aware that this function expects to encode into UTF-8 encoded strings, as found on - // note: pages served as UTF-8 - // example 1: rawurlencode('Kevin van Zonneveld!'); - // returns 1: 'Kevin%20van%20Zonneveld%21' - // example 2: rawurlencode('http://kevin.vanzonneveld.net/'); - // returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F' - // example 3: rawurlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a'); - // returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a' - - str = (str + '').toString(); - - // Tilde should be allowed unescaped in future versions of PHP (as reflected below), but if you want to reflect current - // PHP behavior, you would need to add ".replace(/~/g, '%7E');" to the following. - return encodeURIComponent(str) - .replace(/!/g, '%21') - .replace(/'/g, '%27') - .replace(/\(/g, '%28') - .replace(/\)/g, '%29') - .replace(/\*/g, '%2A'); -}; - -OAuth.signatureMethod = 'HMAC-SHA1'; -OAuth.version = '1.0'; - -/* - Generate a nonce -*/ -OAuth.nonce = function () { - var text = ''; - var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - for (var i = 0; i < 30; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); - - return text; -}; - -OAuth.buildParameterString = function (obj) { - // Sort keys and encode values - if (obj) { - var keys = Object.keys(obj).sort(); - - // Map key=value, join them by & - return keys - .map(function (key) { - return key + '=' + OAuth.encode(obj[key]); - }) - .join('&'); + + post(path, params, body) { + return this.send('POST', path, params, body); } - return ''; -}; + /* + Proper string %escape encoding + */ + static encode(str) { + // discuss at: http://phpjs.org/functions/rawurlencode/ + // original by: Brett Zamir (http://brett-zamir.me) + // input by: travc + // input by: Brett Zamir (http://brett-zamir.me) + // input by: Michael Grier + // input by: Ratheous + // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // bugfixed by: Brett Zamir (http://brett-zamir.me) + // bugfixed by: Joris + // reimplemented by: Brett Zamir (http://brett-zamir.me) + // reimplemented by: Brett Zamir (http://brett-zamir.me) + // note: This reflects PHP 5.3/6.0+ behavior + // note: Please be aware that this function expects to encode into UTF-8 encoded strings, as found on + // note: pages served as UTF-8 + // example 1: rawurlencode('Kevin van Zonneveld!'); + // returns 1: 'Kevin%20van%20Zonneveld%21' + // example 2: rawurlencode('http://kevin.vanzonneveld.net/'); + // returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F' + // example 3: rawurlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a'); + // returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a' + + str = (str + '').toString(); + + // Tilde should be allowed unescaped in future versions of PHP (as reflected below), but if you want to reflect current + // PHP behavior, you would need to add ".replace(/~/g, '%7E');" to the following. + return encodeURIComponent(str) + .replace(/!/g, '%21') + .replace(/'/g, '%27') + .replace(/\(/g, '%28') + .replace(/\)/g, '%29') + .replace(/\*/g, '%2A'); + } -/* - Build the signature string from the object -*/ + /* + Generate a nonce + */ + static nonce() { + var text = ''; + var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; -OAuth.buildSignatureString = function (method, url, parameters) { - return [method.toUpperCase(), OAuth.encode(url), OAuth.encode(parameters)].join('&'); -}; + for (var i = 0; i < 30; i++) + text += possible.charAt(Math.floor(Math.random() * possible.length)); -/* - Retuns encoded HMAC-SHA1 from key and text -*/ -OAuth.signature = function (text, key) { - crypto = require('crypto'); - return OAuth.encode(crypto.createHmac('sha1', key).update(text).digest('base64')); -}; + return text; + } -OAuth.signRequest = function (request, oauth_parameters, consumer_secret, auth_token_secret) { - oauth_parameters = oauth_parameters || {}; + static buildParameterString(obj) { + // Sort keys and encode values + if (obj) { + var keys = Object.keys(obj).sort(); + + // Map key=value, join them by & + return keys + .map(function (key) { + return key + '=' + OAuth.encode(obj[key]); + }) + .join('&'); + } - // Set default values - if (!oauth_parameters.oauth_nonce) { - oauth_parameters.oauth_nonce = OAuth.nonce(); - } - if (!oauth_parameters.oauth_timestamp) { - oauth_parameters.oauth_timestamp = Math.floor(new Date().getTime() / 1000); - } - if (!oauth_parameters.oauth_signature_method) { - oauth_parameters.oauth_signature_method = OAuth.signatureMethod; - } - if (!oauth_parameters.oauth_version) { - oauth_parameters.oauth_version = OAuth.version; + return ''; } - if (!auth_token_secret) { - auth_token_secret = ''; + /* + Build the signature string from the object + */ + static buildSignatureString(method, url, parameters) { + return [method.toUpperCase(), OAuth.encode(url), OAuth.encode(parameters)].join('&'); } - // Force GET method if unset - if (!request.method) { - request.method = 'GET'; + + /* + Retuns encoded HMAC-SHA1 from key and text + */ + static signature(text, key) { + return OAuth.encode(crypto.createHmac('sha1', key).update(text).digest('base64')); } - // Collect all the parameters in one signatureParameters object - var signatureParams = {}; - var parametersToMerge = [request.params, request.body, oauth_parameters]; - for (var i in parametersToMerge) { - var parameters = parametersToMerge[i]; - for (var k in parameters) { - signatureParams[k] = parameters[k]; + static signRequest(request, oauth_parameters, consumer_secret, auth_token_secret) { + oauth_parameters = oauth_parameters || {}; + + // Set default values + if (!oauth_parameters.oauth_nonce) { + oauth_parameters.oauth_nonce = OAuth.nonce(); + } + if (!oauth_parameters.oauth_timestamp) { + oauth_parameters.oauth_timestamp = Math.floor(new Date().getTime() / 1000); + } + if (!oauth_parameters.oauth_signature_method) { + oauth_parameters.oauth_signature_method = OAuth.signatureMethod; + } + if (!oauth_parameters.oauth_version) { + oauth_parameters.oauth_version = OAuth.version; + } + + if (!auth_token_secret) { + auth_token_secret = ''; + } + // Force GET method if unset + if (!request.method) { + request.method = 'GET'; } - } - // Create a string based on the parameters - var parameterString = OAuth.buildParameterString(signatureParams); + // Collect all the parameters in one signatureParameters object + var signatureParams = {}; + var parametersToMerge = [request.params, request.body, oauth_parameters]; + for (var i in parametersToMerge) { + var parameters = parametersToMerge[i]; + for (var k in parameters) { + signatureParams[k] = parameters[k]; + } + } - // Build the signature string - var url = 'https://' + request.host + '' + request.path; + // Create a string based on the parameters + var parameterString = OAuth.buildParameterString(signatureParams); - var signatureString = OAuth.buildSignatureString(request.method, url, parameterString); - // Hash the signature string - var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join('&'); + // Build the signature string + var url = 'https://' + request.host + '' + request.path; - var signature = OAuth.signature(signatureString, signatureKey); + var signatureString = OAuth.buildSignatureString(request.method, url, parameterString); + // Hash the signature string + var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join('&'); - // Set the signature in the params - oauth_parameters.oauth_signature = signature; - if (!request.headers) { - request.headers = {}; - } + var signature = OAuth.signature(signatureString, signatureKey); + + // Set the signature in the params + oauth_parameters.oauth_signature = signature; + if (!request.headers) { + request.headers = {}; + } - // Set the authorization header - var authHeader = Object.keys(oauth_parameters) - .sort() - .map(function (key) { - var value = oauth_parameters[key]; - return key + '="' + value + '"'; - }) - .join(', '); - - request.headers.Authorization = 'OAuth ' + authHeader; - - // Set the content type header - request.headers['Content-Type'] = 'application/x-www-form-urlencoded'; - return request; -}; + // Set the authorization header + var authHeader = Object.keys(oauth_parameters) + .sort() + .map(function (key) { + var value = oauth_parameters[key]; + return key + '="' + value + '"'; + }) + .join(', '); + + request.headers.Authorization = 'OAuth ' + authHeader; + + // Set the content type header + request.headers['Content-Type'] = 'application/x-www-form-urlencoded'; + return request; + } +} module.exports = OAuth; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..b04ee88d73 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["lib/**/*"], + "compilerOptions": { + // Read JS files; normally they are ignored as source files + "allowJs": true, + // Generate d.ts files + "declaration": true, + // Only output d.ts files + "emitDeclarationOnly": true, + // Types destination directory; removing this would place the .d.ts files next to the .js files + "outDir": "types", + // Skip node_modules scan + "skipLibCheck": true, + "types": [], + }, + "exclude": [ + "node_modules", + "./node_modules", + "./node_modules/*", + "./node_modules/@types/node/index.d.ts", + ], +} \ No newline at end of file