diff --git a/spec/support/CurrentSpecReporter.js b/spec/support/CurrentSpecReporter.js index 4f4968fdcb..8d7f301561 100755 --- a/spec/support/CurrentSpecReporter.js +++ b/spec/support/CurrentSpecReporter.js @@ -14,6 +14,8 @@ const flakyTests = [ "ParseLiveQuery handle invalid websocket payload length", // Unhandled promise rejection: TypeError: message.split is not a function "rest query query internal field", + // Timeout + "ParseLiveQuery can handle afterEvent sendEvent to false", ]; /** The minimum execution time in seconds for a test to be considered slow. */ diff --git a/src/AccountLockout.js b/src/AccountLockout.js index 13d655e6b7..13c8101812 100644 --- a/src/AccountLockout.js +++ b/src/AccountLockout.js @@ -1,5 +1,6 @@ // This class handles the Account Lockout Policy settings. -import Parse from 'parse/node'; +import ParseError from './ParseError'; +import { encodeDate } from './Utils'; export class AccountLockout { constructor(user, config) { @@ -81,7 +82,7 @@ export class AccountLockout { const now = new Date(); const updateFields = { - _account_lockout_expires_at: Parse._encode( + _account_lockout_expires_at: encodeDate( new Date(now.getTime() + this._config.accountLockout.duration * 60 * 1000) ), }; @@ -91,7 +92,7 @@ export class AccountLockout { err && err.code && err.message && - err.code === Parse.Error.OBJECT_NOT_FOUND && + err.code === ParseError.OBJECT_NOT_FOUND && err.message === 'Object not found.' ) { return; // nothing to update so we are good @@ -110,14 +111,14 @@ export class AccountLockout { _notLocked() { const query = { username: this._user.username, - _account_lockout_expires_at: { $gt: Parse._encode(new Date()) }, + _account_lockout_expires_at: { $gt: encodeDate(new Date()) }, _failed_login_count: { $gte: this._config.accountLockout.threshold }, }; return this._config.database.find('_User', query).then(users => { if (Array.isArray(users) && users.length > 0) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Your account is locked due to multiple failed login attempts. Please try again after ' + this._config.accountLockout.duration + ' minute(s)' diff --git a/src/Adapters/Auth/BaseCodeAuthAdapter.js b/src/Adapters/Auth/BaseCodeAuthAdapter.js index 696e4ee71b..39ef590455 100644 --- a/src/Adapters/Auth/BaseCodeAuthAdapter.js +++ b/src/Adapters/Auth/BaseCodeAuthAdapter.js @@ -1,3 +1,5 @@ +import ParseError from '../../ParseError'; + // abstract class for auth code adapters import AuthAdapter from './AuthAdapter'; export default class BaseAuthCodeAdapter extends AuthAdapter { @@ -31,27 +33,27 @@ export default class BaseAuthCodeAdapter extends AuthAdapter { async beforeFind(authData) { if (this.enableInsecureAuth && !authData?.code) { if (!authData?.access_token) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${this.adapterName} auth is invalid for this user.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `${this.adapterName} auth is invalid for this user.`); } const user = await this.getUserFromAccessToken(authData.access_token, authData); if (user.id !== authData.id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${this.adapterName} auth is invalid for this user.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `${this.adapterName} auth is invalid for this user.`); } return; } if (!authData?.code) { - throw new Parse.Error(Parse.Error.VALIDATION_ERROR, `${this.adapterName} code is required.`); + throw new ParseError(ParseError.VALIDATION_ERROR, `${this.adapterName} code is required.`); } const access_token = await this.getAccessTokenFromCode(authData); const user = await this.getUserFromAccessToken(access_token, authData); if (authData.id && user.id !== authData.id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${this.adapterName} auth is invalid for this user.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `${this.adapterName} auth is invalid for this user.`); } authData.access_token = access_token; @@ -104,7 +106,7 @@ export default class BaseAuthCodeAdapter extends AuthAdapter { const startPos = data.indexOf('('); const endPos = data.indexOf(')'); if (startPos === -1 || endPos === -1) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${this.adapterName} auth is invalid for this user.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `${this.adapterName} auth is invalid for this user.`); } const jsonData = data.substring(startPos + 1, endPos); return JSON.parse(jsonData); diff --git a/src/Adapters/Auth/OAuth1Client.js b/src/Adapters/Auth/OAuth1Client.js index fec508ba8b..0aaa9e6ad8 100644 --- a/src/Adapters/Auth/OAuth1Client.js +++ b/src/Adapters/Auth/OAuth1Client.js @@ -1,10 +1,11 @@ var https = require('https'), crypto = require('crypto'); -var Parse = require('parse/node').Parse; + +import ParseError from '../../ParseError'; var OAuth = function (options) { if (!options) { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'No options passed to OAuth'); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'No options passed to OAuth'); } this.consumer_key = options.consumer_key; this.consumer_secret = options.consumer_secret; diff --git a/src/Adapters/Auth/apple.js b/src/Adapters/Auth/apple.js index 24502f4a55..c8c8de9e73 100644 --- a/src/Adapters/Auth/apple.js +++ b/src/Adapters/Auth/apple.js @@ -45,7 +45,7 @@ // Apple SignIn Auth // https://developer.apple.com/documentation/signinwithapplerestapi -const Parse = require('parse/node').Parse; +import ParseError from '../../ParseError'; const jwksClient = require('jwks-rsa'); const jwt = require('jsonwebtoken'); const authUtils = require('./utils'); @@ -64,8 +64,8 @@ const getAppleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => { try { key = await authUtils.getSigningKey(client, keyId); } catch (error) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, `Unable to find matching key for Key ID: ${keyId}` ); } @@ -74,7 +74,7 @@ const getAppleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => { const verifyIdToken = async ({ token, id }, { clientId, cacheMaxEntries, cacheMaxAge }) => { if (!token) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token is invalid for this user.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `id token is invalid for this user.`); } const { kid: keyId, alg: algorithm } = authUtils.getHeaderFromToken(token); @@ -96,18 +96,18 @@ const verifyIdToken = async ({ token, id }, { clientId, cacheMaxEntries, cacheMa } catch (exception) { const message = exception.message; - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${message}`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `${message}`); } if (jwtClaims.iss !== TOKEN_ISSUER) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, `id token not issued by correct OpenID provider - expected: ${TOKEN_ISSUER} | from: ${jwtClaims.iss}` ); } if (jwtClaims.sub !== id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `auth data is invalid for this user.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `auth data is invalid for this user.`); } return jwtClaims; }; diff --git a/src/Adapters/Auth/facebook.js b/src/Adapters/Auth/facebook.js index 273004ad62..5ce40ac507 100644 --- a/src/Adapters/Auth/facebook.js +++ b/src/Adapters/Auth/facebook.js @@ -59,7 +59,7 @@ */ // Helper functions for accessing the Facebook Graph API. -const Parse = require('parse/node').Parse; +import ParseError from '../../ParseError'; const crypto = require('crypto'); const jwksClient = require('jwks-rsa'); const jwt = require('jsonwebtoken'); @@ -88,7 +88,7 @@ function validateGraphToken(authData, options) { if ((data && data.id == authData.id) || (process.env.TESTING && authData.id === 'test')) { return; } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); }); } @@ -98,16 +98,16 @@ async function validateGraphAppId(appIds, authData, options) { return; } if (!Array.isArray(appIds)) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'appIds must be an array.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'appIds must be an array.'); } if (!appIds.length) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is not configured.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Facebook auth is not configured.'); } const data = await graphRequest( `app?access_token=${access_token}${getAppSecretPath(authData, options)}` ); if (!data || !appIds.includes(data.id)) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.'); } } @@ -123,8 +123,8 @@ const getFacebookKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => { try { key = await authUtils.getSigningKey(client, keyId); } catch (error) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, `Unable to find matching key for Key ID: ${keyId}` ); } @@ -133,7 +133,7 @@ const getFacebookKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => { const verifyIdToken = async ({ token, id }, { clientId, cacheMaxEntries, cacheMaxAge }) => { if (!token) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'id token is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'id token is invalid for this user.'); } const { kid: keyId, alg: algorithm } = authUtils.getHeaderFromToken(token); @@ -155,18 +155,18 @@ const verifyIdToken = async ({ token, id }, { clientId, cacheMaxEntries, cacheMa } catch (exception) { const message = exception.message; - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${message}`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `${message}`); } if (jwtClaims.iss !== TOKEN_ISSUER) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, `id token not issued by correct OpenID provider - expected: ${TOKEN_ISSUER} | from: ${jwtClaims.iss}` ); } if (jwtClaims.sub !== id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'auth data is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'auth data is invalid for this user.'); } return jwtClaims; }; diff --git a/src/Adapters/Auth/gcenter.js b/src/Adapters/Auth/gcenter.js index d53643df8b..c4af31de5d 100644 --- a/src/Adapters/Auth/gcenter.js +++ b/src/Adapters/Auth/gcenter.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for Apple Game Center. * @@ -120,7 +121,7 @@ class GameCenterAuth extends AuthAdapter { !headers.get('content-length') || parseInt(headers.get('content-length'), 10) > 10000 ) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid rootCertificateURL.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Invalid rootCertificateURL.'); } this.ca.cert = pki.certificateFromPem(certificate); @@ -160,7 +161,7 @@ class GameCenterAuth extends AuthAdapter { async getAppleCertificate(publicKeyUrl) { if (!this.verifyPublicKeyUrl(publicKeyUrl)) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `Invalid publicKeyUrl: ${publicKeyUrl}`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `Invalid publicKeyUrl: ${publicKeyUrl}`); } if (this.cache[publicKeyUrl]) { @@ -185,14 +186,14 @@ class GameCenterAuth extends AuthAdapter { const publicKeyCert = pki.certificateFromPem(cert); if (!this.ca.cert) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Root certificate is invalid or missing.' ); } if (!this.ca.cert.verify(publicKeyCert)) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `Invalid publicKeyUrl: ${publicKeyUrl}`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `Invalid publicKeyUrl: ${publicKeyUrl}`); } } @@ -206,7 +207,7 @@ class GameCenterAuth extends AuthAdapter { verifier.update(Buffer.from(authData.salt, 'base64')); if (!verifier.verify(publicKey, authData.signature, 'base64')) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid signature.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Invalid signature.'); } } @@ -219,7 +220,7 @@ class GameCenterAuth extends AuthAdapter { for (const key of requiredKeys) { if (!authData[key]) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `AuthData ${key} is missing.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `AuthData ${key} is missing.`); } } diff --git a/src/Adapters/Auth/github.js b/src/Adapters/Auth/github.js index 7aa842f03b..06ae89f6ba 100644 --- a/src/Adapters/Auth/github.js +++ b/src/Adapters/Auth/github.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for GitHub. * @class GitHubAdapter @@ -88,12 +89,12 @@ class GitHubAdapter extends BaseCodeAuthAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.VALIDATION_ERROR, `Failed to exchange code for token: ${response.statusText}`); + throw new ParseError(ParseError.VALIDATION_ERROR, `Failed to exchange code for token: ${response.statusText}`); } const data = await response.json(); if (data.error) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, data.error_description || data.error); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, data.error_description || data.error); } return data.access_token; @@ -110,12 +111,12 @@ class GitHubAdapter extends BaseCodeAuthAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.VALIDATION_ERROR, `Failed to fetch GitHub user: ${response.statusText}`); + throw new ParseError(ParseError.VALIDATION_ERROR, `Failed to fetch GitHub user: ${response.statusText}`); } const userData = await response.json(); if (!userData.id || !userData.login) { - throw new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Invalid GitHub user data received.'); + throw new ParseError(ParseError.VALIDATION_ERROR, 'Invalid GitHub user data received.'); } return userData; diff --git a/src/Adapters/Auth/google.js b/src/Adapters/Auth/google.js index d7f90956d9..f22d9b53a0 100644 --- a/src/Adapters/Auth/google.js +++ b/src/Adapters/Auth/google.js @@ -45,8 +45,7 @@ 'use strict'; // Helper functions for accessing the google API. -var Parse = require('parse/node').Parse; - +import ParseError from '../../ParseError'; const https = require('https'); const jwt = require('jsonwebtoken'); const authUtils = require('./utils'); @@ -98,7 +97,7 @@ function getGoogleKeyByKeyId(keyId) { async function verifyIdToken({ id_token: token, id }, { clientId }) { if (!token) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token is invalid for this user.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `id token is invalid for this user.`); } const { kid: keyId, alg: algorithm } = authUtils.getHeaderFromToken(token); @@ -112,23 +111,23 @@ async function verifyIdToken({ id_token: token, id }, { clientId }) { }); } catch (exception) { const message = exception.message; - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `${message}`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `${message}`); } if (jwtClaims.iss !== TOKEN_ISSUER && jwtClaims.iss !== HTTPS_TOKEN_ISSUER) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, `id token not issued by correct provider - expected: ${TOKEN_ISSUER} or ${HTTPS_TOKEN_ISSUER} | from: ${jwtClaims.iss}` ); } if (jwtClaims.sub !== id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `auth data is invalid for this user.`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `auth data is invalid for this user.`); } if (clientId && jwtClaims.aud !== clientId) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, `id token not authorized for this clientId.` ); } diff --git a/src/Adapters/Auth/gpgames.js b/src/Adapters/Auth/gpgames.js index 01b1cec7cf..0aa6e6297a 100644 --- a/src/Adapters/Auth/gpgames.js +++ b/src/Adapters/Auth/gpgames.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for Google Play Games Services. * @@ -87,16 +88,16 @@ class GooglePlayGamesServicesAdapter extends BaseCodeAuthAdapter { }); if (!response.ok) { - throw new Parse.Error( - Parse.Error.VALIDATION_ERROR, + throw new ParseError( + ParseError.VALIDATION_ERROR, `Failed to exchange code for token: ${response.statusText}` ); } const data = await response.json(); if (data.error) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, data.error_description || data.error ); } @@ -115,16 +116,16 @@ class GooglePlayGamesServicesAdapter extends BaseCodeAuthAdapter { }); if (!response.ok) { - throw new Parse.Error( - Parse.Error.VALIDATION_ERROR, + throw new ParseError( + ParseError.VALIDATION_ERROR, `Failed to fetch Google Play Games Services user: ${response.statusText}` ); } const userData = await response.json(); if (!userData.playerId || userData.playerId !== authData.id) { - throw new Parse.Error( - Parse.Error.VALIDATION_ERROR, + throw new ParseError( + ParseError.VALIDATION_ERROR, 'Invalid Google Play Games Services user data received.' ); } diff --git a/src/Adapters/Auth/index.js b/src/Adapters/Auth/index.js index 7f5581da49..ede4a92f6a 100755 --- a/src/Adapters/Auth/index.js +++ b/src/Adapters/Auth/index.js @@ -1,5 +1,5 @@ import loadAdapter from '../AdapterLoader'; -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; import AuthAdapter from './AuthAdapter'; const apple = require('./apple'); @@ -83,8 +83,8 @@ function authDataValidator(provider, adapter, appIds, options) { !authAdapterPolicies[adapter.policy] && typeof adapter.policy !== 'function' ) { - throw new Parse.Error( - Parse.Error.OTHER_CAUSE, + throw new ParseError( + ParseError.OTHER_CAUSE, 'AuthAdapter policy is not configured correctly. The value must be either "solo", "additional", "default" or undefined (will be handled as "default")' ); } @@ -96,8 +96,8 @@ function authDataValidator(provider, adapter, appIds, options) { typeof adapter.validateLogin !== 'function' || typeof adapter.validateUpdate !== 'function' ) { - throw new Parse.Error( - Parse.Error.OTHER_CAUSE, + throw new ParseError( + ParseError.OTHER_CAUSE, 'Adapter is not configured. Implement either validateAuthData or all of the following: validateSetUp, validateLogin and validateUpdate' ); } diff --git a/src/Adapters/Auth/instagram.js b/src/Adapters/Auth/instagram.js index 55cb357f6a..83405c8928 100644 --- a/src/Adapters/Auth/instagram.js +++ b/src/Adapters/Auth/instagram.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for Instagram. * @@ -84,12 +85,12 @@ class InstagramAdapter extends BaseAuthCodeAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Instagram API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Instagram API request failed.'); } const data = await response.json(); if (data.error) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, data.error_description || data.error); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, data.error_description || data.error); } return data.access_token; @@ -103,12 +104,12 @@ class InstagramAdapter extends BaseAuthCodeAdapter { const response = await fetch(path); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Instagram API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Instagram API request failed.'); } const user = await response.json(); if (user?.id !== authData.id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Instagram auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Instagram auth is invalid for this user.'); } return { diff --git a/src/Adapters/Auth/janraincapture.js b/src/Adapters/Auth/janraincapture.js index ca55df7da8..759030a70a 100644 --- a/src/Adapters/Auth/janraincapture.js +++ b/src/Adapters/Auth/janraincapture.js @@ -44,7 +44,7 @@ // Helper functions for accessing the Janrain Capture API. -var Parse = require('parse/node').Parse; +import ParseError from '../../ParseError'; var querystring = require('querystring'); const httpsRequest = require('./httpsRequest'); @@ -56,8 +56,8 @@ function validateAuthData(authData, options) { if (data && data.stat == 'ok' && data.result == authData.id) { return; } - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Janrain capture auth is invalid for this user.' ); }); diff --git a/src/Adapters/Auth/janrainengage.js b/src/Adapters/Auth/janrainengage.js index 782cbb121a..0c58fd0da1 100644 --- a/src/Adapters/Auth/janrainengage.js +++ b/src/Adapters/Auth/janrainengage.js @@ -1,6 +1,7 @@ // Helper functions for accessing the Janrain Engage API. var httpsRequest = require('./httpsRequest'); -var Parse = require('parse/node').Parse; +import Parse from 'parse/node'; +import ParseError from '../../ParseError'; var querystring = require('querystring'); import Config from '../../Config'; import Deprecator from '../../Deprecator/Deprecator'; @@ -11,7 +12,7 @@ function validateAuthData(authData, options) { Deprecator.logRuntimeDeprecation({ usage: 'janrainengage adapter' }); if (!config?.auth?.janrainengage?.enableInsecureAuth || !config.enableInsecureAuthAdapters) { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'janrainengage adapter only works with enableInsecureAuth: true'); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'janrainengage adapter only works with enableInsecureAuth: true'); } return apiRequest(options.api_key, authData.auth_token).then(data => { @@ -20,8 +21,8 @@ function validateAuthData(authData, options) { if (data && data.stat == 'ok' && data.profile.identifier == authData.id) { return; } - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Janrain engage auth is invalid for this user.' ); }); diff --git a/src/Adapters/Auth/keycloak.js b/src/Adapters/Auth/keycloak.js index 457faeeaed..0e3fd4dc87 100644 --- a/src/Adapters/Auth/keycloak.js +++ b/src/Adapters/Auth/keycloak.js @@ -65,8 +65,7 @@ * - [Securing Apps Documentation](https://www.keycloak.org/docs/latest/securing_apps/) * - [Server Administration Documentation](https://www.keycloak.org/docs/latest/server_admin/) */ - -const { Parse } = require('parse/node'); +import ParseError from '../../ParseError'; const httpsRequest = require('./httpsRequest'); const arraysEqual = (_arr1, _arr2) => { @@ -84,10 +83,10 @@ const arraysEqual = (_arr1, _arr2) => { const handleAuth = async ({ access_token, id, roles, groups } = {}, { config } = {}) => { if (!(access_token && id)) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Missing access token and/or User id'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Missing access token and/or User id'); } if (!config || !(config['auth-server-url'] && config['realm'])) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Missing keycloak configuration'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Missing keycloak configuration'); } try { const response = await httpsRequest.get({ @@ -106,17 +105,17 @@ const handleAuth = async ({ access_token, id, roles, groups } = {}, { config } = ) { return; } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid authentication'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Invalid authentication'); } catch (e) { - if (e instanceof Parse.Error) { + if (e instanceof ParseError) { throw e; } const error = JSON.parse(e.text); if (error.error_description) { - throw new Parse.Error(Parse.Error.HOSTING_ERROR, error.error_description); + throw new ParseError(ParseError.HOSTING_ERROR, error.error_description); } else { - throw new Parse.Error( - Parse.Error.HOSTING_ERROR, + throw new ParseError( + ParseError.HOSTING_ERROR, 'Could not connect to the authentication server' ); } diff --git a/src/Adapters/Auth/ldap.js b/src/Adapters/Auth/ldap.js index 5f6a88a7b5..8167f688df 100644 --- a/src/Adapters/Auth/ldap.js +++ b/src/Adapters/Auth/ldap.js @@ -74,12 +74,12 @@ const ldapjs = require('ldapjs'); -const Parse = require('parse/node').Parse; +import ParseError from '../../ParseError'; function validateAuthData(authData, options) { if (!optionsAreValid(options)) { return new Promise((_, reject) => { - reject(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP auth configuration missing')); + reject(new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'LDAP auth configuration missing')); }); } const clientOptions = options.url.startsWith('ldaps://') @@ -99,17 +99,17 @@ function validateAuthData(authData, options) { let error; switch (ldapError.code) { case 49: - error = new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + error = new ParseError( + ParseError.OBJECT_NOT_FOUND, 'LDAP: Wrong username or password' ); break; case 'DEPTH_ZERO_SELF_SIGNED_CERT': - error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LDAPS: Certificate mismatch'); + error = new ParseError(ParseError.OBJECT_NOT_FOUND, 'LDAPS: Certificate mismatch'); break; default: - error = new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + error = new ParseError( + ParseError.OBJECT_NOT_FOUND, 'LDAP: Somthing went wrong (' + ldapError.code + ')' ); } @@ -150,7 +150,7 @@ function searchForGroup(client, options, id, resolve, reject) { if (searchError) { client.unbind(); client.destroy(); - return reject(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP group search failed')); + return reject(new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'LDAP group search failed')); } res.on('searchEntry', entry => { if (entry.pojo.attributes.find(obj => obj.type === 'cn').values.includes(options.groupCn)) { @@ -165,14 +165,14 @@ function searchForGroup(client, options, id, resolve, reject) { client.unbind(); client.destroy(); return reject( - new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP: User not in group') + new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'LDAP: User not in group') ); } }); res.on('error', () => { client.unbind(); client.destroy(); - return reject(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP group search failed')); + return reject(new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'LDAP group search failed')); }); }); } diff --git a/src/Adapters/Auth/line.js b/src/Adapters/Auth/line.js index 7551db817d..241821926d 100644 --- a/src/Adapters/Auth/line.js +++ b/src/Adapters/Auth/line.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for Line. * @@ -73,8 +74,8 @@ class LineAdapter extends BaseCodeAuthAdapter { async getAccessTokenFromCode(authData) { if (!authData.code) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Line auth is invalid for this user.' ); } @@ -95,16 +96,16 @@ class LineAdapter extends BaseCodeAuthAdapter { }); if (!response.ok) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, `Failed to exchange code for token: ${response.statusText}` ); } const data = await response.json(); if (data.error) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, data.error_description || data.error ); } @@ -122,16 +123,16 @@ class LineAdapter extends BaseCodeAuthAdapter { }); if (!response.ok) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, `Failed to fetch Line user: ${response.statusText}` ); } const userData = await response.json(); if (!userData?.userId) { - throw new Parse.Error( - Parse.Error.VALIDATION_ERROR, + throw new ParseError( + ParseError.VALIDATION_ERROR, 'Invalid Line user data received.' ); } diff --git a/src/Adapters/Auth/linkedin.js b/src/Adapters/Auth/linkedin.js index 2d74166783..2cf5fbf5a2 100644 --- a/src/Adapters/Auth/linkedin.js +++ b/src/Adapters/Auth/linkedin.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for LinkedIn. * @@ -82,7 +83,7 @@ class LinkedInAdapter extends BaseAuthCodeAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LinkedIn API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'LinkedIn API request failed.'); } return response.json(); @@ -104,7 +105,7 @@ class LinkedInAdapter extends BaseAuthCodeAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LinkedIn API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'LinkedIn API request failed.'); } const json = await response.json(); diff --git a/src/Adapters/Auth/meetup.js b/src/Adapters/Auth/meetup.js index 33ec63d36e..65fab3e944 100644 --- a/src/Adapters/Auth/meetup.js +++ b/src/Adapters/Auth/meetup.js @@ -1,5 +1,6 @@ // Helper functions for accessing the meetup API. -var Parse = require('parse/node').Parse; +import Parse from 'parse/node'; +import ParseError from '../../ParseError'; const httpsRequest = require('./httpsRequest'); import Config from '../../Config'; import Deprecator from '../../Deprecator/Deprecator'; @@ -12,12 +13,12 @@ async function validateAuthData(authData) { Deprecator.logRuntimeDeprecation({ usage: 'meetup adapter' }); if (!meetupConfig?.enableInsecureAuth) { - throw new Parse.Error('Meetup only works with enableInsecureAuth: true'); + throw new ParseError('Meetup only works with enableInsecureAuth: true'); } const data = await request('member/self', authData.access_token); if (data?.id !== authData.id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Meetup auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Meetup auth is invalid for this user.'); } } diff --git a/src/Adapters/Auth/microsoft.js b/src/Adapters/Auth/microsoft.js index a2e17ef4a5..d154e3bff8 100644 --- a/src/Adapters/Auth/microsoft.js +++ b/src/Adapters/Auth/microsoft.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for Microsoft. * @@ -76,7 +77,7 @@ class MicrosoftAdapter extends BaseAuthCodeAdapter { }); if (!userResponse.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Microsoft API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Microsoft API request failed.'); } return userResponse.json(); @@ -98,7 +99,7 @@ class MicrosoftAdapter extends BaseAuthCodeAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Microsoft API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Microsoft API request failed.'); } const json = await response.json(); diff --git a/src/Adapters/Auth/oauth2.js b/src/Adapters/Auth/oauth2.js index 1498f8bf4e..c6cde55fb7 100644 --- a/src/Adapters/Auth/oauth2.js +++ b/src/Adapters/Auth/oauth2.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for OAuth2 Token Introspection. * @@ -59,10 +60,10 @@ class OAuth2Adapter extends AuthAdapter { super.validateOptions(options); if (!options.tokenIntrospectionEndpointUrl) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'OAuth2 token introspection endpoint URL is missing.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'OAuth2 token introspection endpoint URL is missing.'); } if (options.appidField && !options.appIds?.length) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'OAuth2 configuration is missing app IDs.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'OAuth2 configuration is missing app IDs.'); } this.tokenIntrospectionEndpointUrl = options.tokenIntrospectionEndpointUrl; @@ -85,7 +86,7 @@ class OAuth2Adapter extends AuthAdapter { : this.appIds.includes(appIdFieldValue); if (!isValidAppId) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'OAuth2: Invalid app ID.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'OAuth2: Invalid app ID.'); } } @@ -93,7 +94,7 @@ class OAuth2Adapter extends AuthAdapter { const response = await this.requestTokenInfo(authData.access_token); if (!response.active || (this.useridField && authData.id !== response[this.useridField])) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'OAuth2 access token is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'OAuth2 access token is invalid for this user.'); } return {}; @@ -110,7 +111,7 @@ class OAuth2Adapter extends AuthAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'OAuth2 token introspection request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'OAuth2 token introspection request failed.'); } return response.json(); diff --git a/src/Adapters/Auth/phantauth.js b/src/Adapters/Auth/phantauth.js index d9145c84ca..94267b6daf 100644 --- a/src/Adapters/Auth/phantauth.js +++ b/src/Adapters/Auth/phantauth.js @@ -5,7 +5,8 @@ * To learn more, please go to: https://www.phantauth.net */ -const { Parse } = require('parse/node'); +import Parse from 'parse/node'; +import ParseError from '../../ParseError'; const httpsRequest = require('./httpsRequest'); import Config from '../../Config'; import Deprecator from '../../Deprecator/Deprecator'; @@ -18,12 +19,12 @@ async function validateAuthData(authData) { const phantauthConfig = config.auth.phantauth; if (!phantauthConfig?.enableInsecureAuth) { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'PhantAuth only works with enableInsecureAuth: true'); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'PhantAuth only works with enableInsecureAuth: true'); } const data = await request('auth/userinfo', authData.access_token); if (data?.sub !== authData.id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'PhantAuth auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'PhantAuth auth is invalid for this user.'); } } diff --git a/src/Adapters/Auth/qq.js b/src/Adapters/Auth/qq.js index 873e9071b8..a05a33361e 100644 --- a/src/Adapters/Auth/qq.js +++ b/src/Adapters/Auth/qq.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for QQ. * @@ -77,7 +78,7 @@ class QqAdapter extends BaseAuthCodeAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'qq API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'qq API request failed.'); } const data = await response.text(); @@ -100,7 +101,7 @@ class QqAdapter extends BaseAuthCodeAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'qq API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'qq API request failed.'); } const text = await response.text(); diff --git a/src/Adapters/Auth/spotify.js b/src/Adapters/Auth/spotify.js index c3304c6348..51593f38b2 100644 --- a/src/Adapters/Auth/spotify.js +++ b/src/Adapters/Auth/spotify.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for Spotify. * @@ -76,7 +77,7 @@ class SpotifyAdapter extends BaseAuthCodeAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Spotify API request failed.'); } const user = await response.json(); @@ -87,8 +88,8 @@ class SpotifyAdapter extends BaseAuthCodeAdapter { async getAccessTokenFromCode(authData) { if (!authData.code || !authData.redirect_uri || !authData.code_verifier) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Spotify auth configuration authData.code and/or authData.redirect_uri and/or authData.code_verifier.' ); } @@ -108,7 +109,7 @@ class SpotifyAdapter extends BaseAuthCodeAdapter { }); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify API request failed.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Spotify API request failed.'); } return response.json(); diff --git a/src/Adapters/Auth/twitter.js b/src/Adapters/Auth/twitter.js index 9a6881bd24..c788815e0f 100644 --- a/src/Adapters/Auth/twitter.js +++ b/src/Adapters/Auth/twitter.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for Twitter. * @@ -91,8 +92,8 @@ class TwitterAuthAdapter extends AuthAdapter { } if (!options.consumer_key || !options.consumer_secret) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Twitter auth configuration missing consumer_key and/or consumer_secret.' ); } @@ -105,16 +106,16 @@ class TwitterAuthAdapter extends AuthAdapter { return; } - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.' ); } async validateInsecureAuth(authData, options) { if (!authData.oauth_token || !authData.oauth_token_secret) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Twitter insecure auth requires oauth_token and oauth_token_secret.' ); } @@ -128,8 +129,8 @@ class TwitterAuthAdapter extends AuthAdapter { return; } - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.' ); } @@ -159,8 +160,8 @@ class TwitterAuthAdapter extends AuthAdapter { const consumer_key = authData.consumer_key; if (!consumer_key) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.' ); } @@ -168,8 +169,8 @@ class TwitterAuthAdapter extends AuthAdapter { options = options.filter(option => option.consumer_key === consumer_key); if (options.length === 0) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.' ); } @@ -209,20 +210,20 @@ class TwitterAuthAdapter extends AuthAdapter { async beforeFind(authData) { if (this.enableInsecureAuth && !authData?.code) { if (!authData?.access_token) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); } const user = await this.getUserFromAccessToken(authData.access_token, authData); if (user.id !== authData.id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.'); } return; } if (!authData?.code) { - throw new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Twitter code is required.'); + throw new ParseError(ParseError.VALIDATION_ERROR, 'Twitter code is required.'); } const access_token = await this.exchangeAccessToken(authData); diff --git a/src/Adapters/Auth/utils.js b/src/Adapters/Auth/utils.js index 0d4d7cd8a2..28371f041a 100644 --- a/src/Adapters/Auth/utils.js +++ b/src/Adapters/Auth/utils.js @@ -1,10 +1,11 @@ const jwt = require('jsonwebtoken'); const util = require('util'); -const Parse = require('parse/node').Parse; +import ParseError from '../../ParseError'; + const getHeaderFromToken = token => { const decodedToken = jwt.decode(token, { complete: true }); if (!decodedToken) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `provided token does not decode as JWT`); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, `provided token does not decode as JWT`); } return decodedToken.header; diff --git a/src/Adapters/Auth/vkontakte.js b/src/Adapters/Auth/vkontakte.js index 3b5b7a9bac..956dd2cb01 100644 --- a/src/Adapters/Auth/vkontakte.js +++ b/src/Adapters/Auth/vkontakte.js @@ -3,7 +3,8 @@ // Helper functions for accessing the vkontakte API. const httpsRequest = require('./httpsRequest'); -var Parse = require('parse/node').Parse; +import Parse from 'parse/node'; +import ParseError from '../../ParseError'; import Config from '../../Config'; import Deprecator from '../../Deprecator/Deprecator'; @@ -14,12 +15,12 @@ async function validateAuthData(authData, params) { const vkConfig = config.auth.vkontakte; if (!vkConfig?.enableInsecureAuth || !config.enableInsecureAuthAdapters) { - throw new Parse.Error('Vk only works with enableInsecureAuth: true'); + throw new ParseError('Vk only works with enableInsecureAuth: true'); } const response = await vkOAuth2Request(params); if (!response?.access_token) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk appIds or appSecret is incorrect.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Vk appIds or appSecret is incorrect.'); } const vkUser = await request( @@ -28,7 +29,7 @@ async function validateAuthData(authData, params) { ); if (!vkUser?.response?.length || vkUser.response[0].id !== authData.id) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Vk auth is invalid for this user.'); } } @@ -41,8 +42,8 @@ function vkOAuth2Request(params) { !params.appSecret || !params.appSecret.length ) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Vk auth is not configured. Missing appIds or appSecret.' ); } diff --git a/src/Adapters/Auth/wechat.js b/src/Adapters/Auth/wechat.js index d9c196f5a4..d48adbe402 100644 --- a/src/Adapters/Auth/wechat.js +++ b/src/Adapters/Auth/wechat.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for WeChat. * @@ -86,7 +87,7 @@ class WeChatAdapter extends BaseAuthCodeAdapter { const data = await response.json(); if (!response.ok || data.errcode !== 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'WeChat auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'WeChat auth is invalid for this user.'); } return data; @@ -94,7 +95,7 @@ class WeChatAdapter extends BaseAuthCodeAdapter { async getAccessTokenFromCode(authData) { if (!authData.code) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'WeChat auth requires a code to be sent.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'WeChat auth requires a code to be sent.'); } const appId = this.clientId; @@ -108,7 +109,7 @@ class WeChatAdapter extends BaseAuthCodeAdapter { const data = await response.json(); if (!response.ok || data.errcode) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'WeChat auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'WeChat auth is invalid for this user.'); } authData.id = data.openid; diff --git a/src/Adapters/Auth/weibo.js b/src/Adapters/Auth/weibo.js index 86a761c653..d04938aa97 100644 --- a/src/Adapters/Auth/weibo.js +++ b/src/Adapters/Auth/weibo.js @@ -1,3 +1,4 @@ +import ParseError from '../../ParseError'; /** * Parse Server authentication adapter for Weibo. * @@ -104,7 +105,7 @@ class WeiboAdapter extends BaseAuthCodeAdapter { const data = await response.json(); if (!response.ok) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Weibo auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Weibo auth is invalid for this user.'); } return { @@ -114,8 +115,8 @@ class WeiboAdapter extends BaseAuthCodeAdapter { async getAccessTokenFromCode(authData) { if (!authData?.code || !authData?.redirect_uri) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Weibo auth requires code and redirect_uri to be sent.' ); } @@ -139,7 +140,7 @@ class WeiboAdapter extends BaseAuthCodeAdapter { const data = await response.json(); if (!response.ok || data.errcode) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Weibo auth is invalid for this user.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Weibo auth is invalid for this user.'); } return data.access_token; diff --git a/src/Adapters/Files/FilesAdapter.js b/src/Adapters/Files/FilesAdapter.js index f06c52df89..6cf32b4c80 100644 --- a/src/Adapters/Files/FilesAdapter.js +++ b/src/Adapters/Files/FilesAdapter.js @@ -17,7 +17,7 @@ // database adapter. import type { Config } from '../../Config'; -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; /** * @interface * @memberof module:Adapters @@ -67,9 +67,9 @@ export class FilesAdapter { * * @param {string} filename * - * @returns {null|Parse.Error} null if there are no errors + * @returns {null|ParseError} null if there are no errors */ - // validateFilename(filename: string): ?Parse.Error {} + // validateFilename(filename: string): ?ParseError {} /** Handles Byte-Range Requests for Streaming * @@ -95,16 +95,16 @@ export class FilesAdapter { * Simple filename validation * * @param filename - * @returns {null|Parse.Error} + * @returns {null|ParseError} */ -export function validateFilename(filename): ?Parse.Error { +export function validateFilename(filename): ?ParseError { if (filename.length > 128) { - return new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.'); + return new ParseError(ParseError.INVALID_FILE_NAME, 'Filename too long.'); } const regx = /^[_a-zA-Z0-9][a-zA-Z0-9@. ~_-]*$/; if (!filename.match(regx)) { - return new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename contains invalid characters.'); + return new ParseError(ParseError.INVALID_FILE_NAME, 'Filename contains invalid characters.'); } return null; } diff --git a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js index 45b27f7516..c4caae22c7 100644 --- a/src/Adapters/Storage/Mongo/MongoSchemaCollection.js +++ b/src/Adapters/Storage/Mongo/MongoSchemaCollection.js @@ -1,5 +1,5 @@ import MongoCollection from './MongoCollection'; -import Parse from 'parse/node'; +import ParseError from '../../../ParseError'; function mongoFieldToParseSchemaField(type) { if (type[0] === '*') { @@ -187,7 +187,7 @@ class MongoSchemaCollection { .catch(error => { if (error.code === 11000) { //Mongo's duplicate key error - throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.'); + throw new ParseError(ParseError.DUPLICATE_VALUE, 'Class already exists.'); } else { throw error; } @@ -229,8 +229,8 @@ class MongoSchemaCollection { existingField => schema.fields[existingField].type === 'GeoPoint' ) ) { - throw new Parse.Error( - Parse.Error.INCORRECT_TYPE, + throw new ParseError( + ParseError.INCORRECT_TYPE, 'MongoDB only supports one GeoPoint field in a class.' ); } diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index fc6c5556a4..54431c9557 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -13,7 +13,7 @@ import { transformPointerString, } from './MongoTransform'; // @flow-disable-next -import Parse from 'parse/node'; +import ParseError from '../../../ParseError'; // @flow-disable-next import _ from 'lodash'; import defaults from '../../../defaults'; @@ -120,7 +120,7 @@ function validateExplainValue(explain) { true, ]; if (!explainAllowedValues.includes(explain)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Invalid value for explain'); + throw new ParseError(ParseError.INVALID_QUERY, 'Invalid value for explain'); } } } @@ -198,7 +198,7 @@ export class MongoStorageAdapter implements StorageAdapter { return this.connectionPromise; } - handleError(error: ?(Error | Parse.Error)): Promise { + handleError(error: ?(Error | ParseError)): Promise { if (error && error.code === 13) { // Unauthorized error delete this.client; @@ -274,11 +274,11 @@ export class MongoStorageAdapter implements StorageAdapter { Object.keys(submittedIndexes).forEach(name => { const field = submittedIndexes[name]; if (existingIndexes[name] && field.__op !== 'Delete') { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Index ${name} exists, cannot update.`); + throw new ParseError(ParseError.INVALID_QUERY, `Index ${name} exists, cannot update.`); } if (!existingIndexes[name] && field.__op === 'Delete') { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, `Index ${name} does not exist, cannot delete.` ); } @@ -294,8 +294,8 @@ export class MongoStorageAdapter implements StorageAdapter { key.indexOf('_p_') === 0 ? key.replace('_p_', '') : key ) ) { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, `Field ${key} does not exist, cannot add index.` ); } @@ -485,8 +485,8 @@ export class MongoStorageAdapter implements StorageAdapter { .catch(error => { if (error.code === 11000) { // Duplicate value - const err = new Parse.Error( - Parse.Error.DUPLICATE_VALUE, + const err = new ParseError( + ParseError.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided' ); err.underlyingError = error; @@ -522,12 +522,12 @@ export class MongoStorageAdapter implements StorageAdapter { .then( ({ deletedCount }) => { if (deletedCount === 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } return Promise.resolve(); }, () => { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error'); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'Database adapter error'); } ); } @@ -570,8 +570,8 @@ export class MongoStorageAdapter implements StorageAdapter { .then(result => mongoObjectToParseObject(className, result, schema)) .catch(error => { if (error.code === 11000) { - throw new Parse.Error( - Parse.Error.DUPLICATE_VALUE, + throw new ParseError( + ParseError.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided' ); } @@ -717,8 +717,8 @@ export class MongoStorageAdapter implements StorageAdapter { .then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest)) .catch(error => { if (error.code === 11000) { - throw new Parse.Error( - Parse.Error.DUPLICATE_VALUE, + throw new ParseError( + ParseError.DUPLICATE_VALUE, 'Tried to ensure field uniqueness for a class that already has duplicates.' ); } @@ -1004,7 +1004,7 @@ export class MongoStorageAdapter implements StorageAdapter { case '': break; default: - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Not supported read preference.'); + throw new ParseError(ParseError.INVALID_QUERY, 'Not supported read preference.'); } return readPreference; } diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 336d9affc9..c15866aaef 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -1,8 +1,8 @@ import log from '../../../logger'; import _ from 'lodash'; var mongodb = require('mongodb'); -var Parse = require('parse/node').Parse; -const Utils = require('../../../Utils'); +import ParseError from '../../../ParseError'; +const { encodeDate, relativeTimeToDate, validateGeoPoint } = require('../../../Utils'); const transformKey = (className, fieldName, schema) => { // Check if the schema is known since it's a built-in field. @@ -180,8 +180,8 @@ const transformInteriorValue = restValue => { typeof restValue === 'object' && Object.keys(restValue).some(key => key.includes('$') || key.includes('.')) ) { - throw new Parse.Error( - Parse.Error.INVALID_NESTED_KEY, + throw new ParseError( + ParseError.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters" ); } @@ -349,8 +349,8 @@ function transformQueryKeyValue(className, key, value, schema, count = false) { if (transformRes !== CannotTransform) { return { key, value: transformRes }; } else { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `You cannot use ${value} as a query parameter.` ); } @@ -412,7 +412,7 @@ const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) => default: // Auth data should have been transformed already if (restKey.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'can only query on ' + restKey); + throw new ParseError(ParseError.INVALID_KEY_NAME, 'can only query on ' + restKey); } // Trust that the auth data has been transformed and save it directly if (restKey.match(/^_auth_data_[a-zA-Z0-9_]+$/)) { @@ -451,8 +451,8 @@ const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) => // Handle normal objects by recursing if (Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))) { - throw new Parse.Error( - Parse.Error.INVALID_NESTED_KEY, + throw new ParseError( + ParseError.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters" ); } @@ -572,7 +572,7 @@ const transformInteriorAtom = atom => { objectId: atom.objectId, }; } else if (typeof atom === 'function' || typeof atom === 'symbol') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `cannot transform value: ${atom}`); + throw new ParseError(ParseError.INVALID_JSON, `cannot transform value: ${atom}`); } else if (DateCoder.isValidJSON(atom)) { return DateCoder.JSONToDatabase(atom); } else if (BytesCoder.isValidJSON(atom)) { @@ -604,7 +604,7 @@ function transformTopLevelAtom(atom, field) { return atom; case 'symbol': case 'function': - throw new Parse.Error(Parse.Error.INVALID_JSON, `cannot transform value: ${atom}`); + throw new ParseError(ParseError.INVALID_JSON, `cannot transform value: ${atom}`); case 'object': if (atom instanceof Date) { // Technically dates are not rest format, but, it seems pretty @@ -639,8 +639,8 @@ function transformTopLevelAtom(atom, field) { default: // I don't think typeof can ever let us get here - throw new Parse.Error( - Parse.Error.INTERNAL_SERVER_ERROR, + throw new ParseError( + ParseError.INTERNAL_SERVER_ERROR, `really did not expect value: ${atom}` ); } @@ -660,7 +660,7 @@ function transformConstraint(constraint, field, count = false) { const transformer = atom => { const result = transformFunction(atom, field); if (result === CannotTransform) { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad atom: ${JSON.stringify(atom)}`); + throw new ParseError(ParseError.INVALID_JSON, `bad atom: ${JSON.stringify(atom)}`); } return result; }; @@ -682,8 +682,8 @@ function transformConstraint(constraint, field, count = false) { const val = constraint[key]; if (val && typeof val === 'object' && val.$relativeTime) { if (field && field.type !== 'Date') { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, '$relativeTime can only be used with Date field' ); } @@ -692,21 +692,21 @@ function transformConstraint(constraint, field, count = false) { case '$exists': case '$ne': case '$eq': - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, '$relativeTime can only be used with the $lt, $lte, $gt, and $gte operators' ); } - const parserResult = Utils.relativeTimeToDate(val.$relativeTime); + const parserResult = relativeTimeToDate(val.$relativeTime); if (parserResult.status === 'success') { answer[key] = parserResult.result; break; } log.info('Error while parsing relative date', parserResult); - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `bad $relativeTime (${key}) value. ${parserResult.info}` ); } @@ -719,7 +719,7 @@ function transformConstraint(constraint, field, count = false) { case '$nin': { const arr = constraint[key]; if (!(arr instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value'); + throw new ParseError(ParseError.INVALID_JSON, 'bad ' + key + ' value'); } answer[key] = _.flatMap(arr, value => { return (atom => { @@ -735,14 +735,14 @@ function transformConstraint(constraint, field, count = false) { case '$all': { const arr = constraint[key]; if (!(arr instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value'); + throw new ParseError(ParseError.INVALID_JSON, 'bad ' + key + ' value'); } answer[key] = arr.map(transformInteriorAtom); const values = answer[key]; if (isAnyValueRegex(values) && !isAllValuesRegexOrNone(values)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'All $all values must be of regex type or none: ' + values ); } @@ -752,7 +752,7 @@ function transformConstraint(constraint, field, count = false) { case '$regex': var s = constraint[key]; if (typeof s !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad regex: ' + s); + throw new ParseError(ParseError.INVALID_JSON, 'bad regex: ' + s); } answer[key] = s; break; @@ -760,7 +760,7 @@ function transformConstraint(constraint, field, count = false) { case '$containedBy': { const arr = constraint[key]; if (!(arr instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $containedBy: should be an array`); + throw new ParseError(ParseError.INVALID_JSON, `bad $containedBy: should be an array`); } answer.$elemMatch = { $nin: arr.map(transformer), @@ -774,31 +774,31 @@ function transformConstraint(constraint, field, count = false) { case '$text': { const search = constraint[key].$search; if (typeof search !== 'object') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $search, should be object`); + throw new ParseError(ParseError.INVALID_JSON, `bad $text: $search, should be object`); } if (!search.$term || typeof search.$term !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $term, should be string`); + throw new ParseError(ParseError.INVALID_JSON, `bad $text: $term, should be string`); } else { answer[key] = { $search: search.$term, }; } if (search.$language && typeof search.$language !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $language, should be string`); + throw new ParseError(ParseError.INVALID_JSON, `bad $text: $language, should be string`); } else if (search.$language) { answer[key].$language = search.$language; } if (search.$caseSensitive && typeof search.$caseSensitive !== 'boolean') { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `bad $text: $caseSensitive, should be boolean` ); } else if (search.$caseSensitive) { answer[key].$caseSensitive = search.$caseSensitive; } if (search.$diacriticSensitive && typeof search.$diacriticSensitive !== 'boolean') { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `bad $text: $diacriticSensitive, should be boolean` ); } else if (search.$diacriticSensitive) { @@ -838,15 +838,15 @@ function transformConstraint(constraint, field, count = false) { case '$select': case '$dontSelect': - throw new Parse.Error( - Parse.Error.COMMAND_UNAVAILABLE, + throw new ParseError( + ParseError.COMMAND_UNAVAILABLE, 'the ' + key + ' constraint is not supported yet' ); case '$within': var box = constraint[key]['$box']; if (!box || box.length != 2) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'malformatted $within arg'); + throw new ParseError(ParseError.INVALID_JSON, 'malformatted $within arg'); } answer[key] = { $box: [ @@ -863,35 +863,35 @@ function transformConstraint(constraint, field, count = false) { let points; if (typeof polygon === 'object' && polygon.__type === 'Polygon') { if (!polygon.coordinates || polygon.coordinates.length < 3) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; Polygon.coordinates should contain at least 3 lon/lat pairs' ); } points = polygon.coordinates; } else if (polygon instanceof Array) { if (polygon.length < 3) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' ); } points = polygon; } else { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, "bad $geoWithin value; $polygon should be Polygon object or Array of Parse.GeoPoint's" ); } points = points.map(point => { if (point instanceof Array && point.length === 2) { - Parse.GeoPoint._validate(point[1], point[0]); + validateGeoPoint(point[1], point[0]); return point; } if (!GeoPointCoder.isValidJSON(point)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + throw new ParseError(ParseError.INVALID_JSON, 'bad $geoWithin value'); } else { - Parse.GeoPoint._validate(point.latitude, point.longitude); + validateGeoPoint(point.latitude, point.longitude); } return [point.longitude, point.latitude]; }); @@ -900,27 +900,27 @@ function transformConstraint(constraint, field, count = false) { }; } else if (centerSphere !== undefined) { if (!(centerSphere instanceof Array) || centerSphere.length < 2) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; $centerSphere should be an array of Parse.GeoPoint and distance' ); } // Get point, convert to geo point if necessary and validate let point = centerSphere[0]; if (point instanceof Array && point.length === 2) { - point = new Parse.GeoPoint(point[1], point[0]); + point = { latitude: point[1], longitude: point[0] }; } else if (!GeoPointCoder.isValidJSON(point)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; $centerSphere geo point invalid' ); } - Parse.GeoPoint._validate(point.latitude, point.longitude); + validateGeoPoint(point.latitude, point.longitude); // Get distance and validate const distance = centerSphere[1]; if (isNaN(distance) || distance < 0) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; $centerSphere distance invalid' ); } @@ -933,12 +933,12 @@ function transformConstraint(constraint, field, count = false) { case '$geoIntersects': { const point = constraint[key]['$point']; if (!GeoPointCoder.isValidJSON(point)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoIntersect value; $point should be GeoPoint' ); } else { - Parse.GeoPoint._validate(point.latitude, point.longitude); + validateGeoPoint(point.latitude, point.longitude); } answer[key] = { $geometry: { @@ -950,7 +950,7 @@ function transformConstraint(constraint, field, count = false) { } default: if (key.match(/^\$+/)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad constraint: ' + key); + throw new ParseError(ParseError.INVALID_JSON, 'bad constraint: ' + key); } return CannotTransform; } @@ -979,7 +979,7 @@ function transformUpdateOperator({ __op, amount, objects }, flatten) { case 'Increment': if (typeof amount !== 'number') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'incrementing must provide a number'); + throw new ParseError(ParseError.INVALID_JSON, 'incrementing must provide a number'); } if (flatten) { return amount; @@ -997,7 +997,7 @@ function transformUpdateOperator({ __op, amount, objects }, flatten) { case 'Add': case 'AddUnique': if (!(objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + throw new ParseError(ParseError.INVALID_JSON, 'objects to add must be an array'); } var toAdd = objects.map(transformInteriorAtom); if (flatten) { @@ -1012,7 +1012,7 @@ function transformUpdateOperator({ __op, amount, objects }, flatten) { case 'Remove': if (!(objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to remove must be an array'); + throw new ParseError(ParseError.INVALID_JSON, 'objects to remove must be an array'); } var toRemove = objects.map(transformInteriorAtom); if (flatten) { @@ -1022,8 +1022,8 @@ function transformUpdateOperator({ __op, amount, objects }, flatten) { } default: - throw new Parse.Error( - Parse.Error.COMMAND_UNAVAILABLE, + throw new ParseError( + ParseError.COMMAND_UNAVAILABLE, `The ${__op} operator is not supported yet.` ); } @@ -1055,7 +1055,7 @@ const nestedMongoObjectToNestedParseObject = mongoObject => { } if (mongoObject instanceof Date) { - return Parse._encode(mongoObject); + return encodeDate(mongoObject); } if (mongoObject instanceof mongodb.Long) { @@ -1118,7 +1118,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => { } if (mongoObject instanceof Date) { - return Parse._encode(mongoObject); + return encodeDate(mongoObject); } if (mongoObject instanceof mongodb.Long) { @@ -1168,19 +1168,19 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => { break; case 'updatedAt': case '_updated_at': - restObject['updatedAt'] = Parse._encode(new Date(mongoObject[key])).iso; + restObject['updatedAt'] = encodeDate(new Date(mongoObject[key])).iso; break; case 'createdAt': case '_created_at': - restObject['createdAt'] = Parse._encode(new Date(mongoObject[key])).iso; + restObject['createdAt'] = encodeDate(new Date(mongoObject[key])).iso; break; case 'expiresAt': case '_expiresAt': - restObject['expiresAt'] = Parse._encode(new Date(mongoObject[key])); + restObject['expiresAt'] = encodeDate(new Date(mongoObject[key])); break; case 'lastUsed': case '_last_used': - restObject['lastUsed'] = Parse._encode(new Date(mongoObject[key])).iso; + restObject['lastUsed'] = encodeDate(new Date(mongoObject[key])).iso; break; case 'timesUsed': case 'times_used': @@ -1378,7 +1378,7 @@ var PolygonCoder = { if (!GeoPointCoder.isValidDatabaseObject(point)) { return false; } - Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0])); + validateGeoPoint(parseFloat(point[1]), parseFloat(point[0])); } return true; }, @@ -1404,8 +1404,8 @@ var PolygonCoder = { return foundIndex === index; }); if (unique.length < 3) { - throw new Parse.Error( - Parse.Error.INTERNAL_SERVER_ERROR, + throw new ParseError( + ParseError.INTERNAL_SERVER_ERROR, 'GeoJSON: Loop must have at least 3 different vertices' ); } diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index b13179553f..d5cff36379 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -1,7 +1,7 @@ // @flow import { createClient } from './PostgresClient'; // @flow-disable-next -import Parse from 'parse/node'; +import ParseError from '../../../ParseError'; // @flow-disable-next import _ from 'lodash'; // @flow-disable-next @@ -9,7 +9,7 @@ import { v4 as uuidv4 } from 'uuid'; import sql from './sql'; import { StorageAdapter } from '../StorageAdapter'; import type { SchemaType, QueryType, QueryOptions } from '../StorageAdapter'; -const Utils = require('../../../Utils'); +const { relativeTimeToDate, validateGeoPoint } = require('../../../Utils'); const PostgresRelationDoesNotExistError = '42P01'; const PostgresDuplicateRelationError = '42P07'; @@ -252,8 +252,8 @@ const validateKeys = object => { } if (key.includes('$') || key.includes('.')) { - throw new Parse.Error( - Parse.Error.INVALID_NESTED_KEY, + throw new ParseError( + ParseError.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters" ); } @@ -402,8 +402,8 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus `(${constraintFieldName} <> $${index + 1} OR ${constraintFieldName} IS NULL)` ); } else if (typeof fieldValue.$ne === 'object' && fieldValue.$ne.$relativeTime) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, '$relativeTime can only be used with the $lt, $lte, $gt, and $gte operators' ); } else { @@ -436,8 +436,8 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus values.push(fieldValue.$eq); patterns.push(`${constraintFieldName} = $${index++}`); } else if (typeof fieldValue.$eq === 'object' && fieldValue.$eq.$relativeTime) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, '$relativeTime can only be used with the $lt, $lte, $gt, and $gte operators' ); } else { @@ -521,16 +521,16 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus ); } } else if (typeof fieldValue.$in !== 'undefined') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $in value'); + throw new ParseError(ParseError.INVALID_JSON, 'bad $in value'); } else if (typeof fieldValue.$nin !== 'undefined') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $nin value'); + throw new ParseError(ParseError.INVALID_JSON, 'bad $nin value'); } if (Array.isArray(fieldValue.$all) && isArrayField) { if (isAnyValueRegexStartsWith(fieldValue.$all)) { if (!isAllValuesRegexOrNone(fieldValue.$all)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'All $all values must be of regex type or none: ' + fieldValue.$all ); } @@ -555,8 +555,8 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus if (typeof fieldValue.$exists !== 'undefined') { if (typeof fieldValue.$exists === 'object' && fieldValue.$exists.$relativeTime) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, '$relativeTime can only be used with the $lt, $lte, $gt, and $gte operators' ); } else if (fieldValue.$exists) { @@ -571,7 +571,7 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus if (fieldValue.$containedBy) { const arr = fieldValue.$containedBy; if (!(arr instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $containedBy: should be an array`); + throw new ParseError(ParseError.INVALID_JSON, `bad $containedBy: should be an array`); } patterns.push(`$${index}:name <@ $${index + 1}::jsonb`); @@ -583,35 +583,35 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus const search = fieldValue.$text.$search; let language = 'english'; if (typeof search !== 'object') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $search, should be object`); + throw new ParseError(ParseError.INVALID_JSON, `bad $text: $search, should be object`); } if (!search.$term || typeof search.$term !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $term, should be string`); + throw new ParseError(ParseError.INVALID_JSON, `bad $text: $term, should be string`); } if (search.$language && typeof search.$language !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $language, should be string`); + throw new ParseError(ParseError.INVALID_JSON, `bad $text: $language, should be string`); } else if (search.$language) { language = search.$language; } if (search.$caseSensitive && typeof search.$caseSensitive !== 'boolean') { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `bad $text: $caseSensitive, should be boolean` ); } else if (search.$caseSensitive) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `bad $text: $caseSensitive not supported, please use $regex or create a separate lower case column.` ); } if (search.$diacriticSensitive && typeof search.$diacriticSensitive !== 'boolean') { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `bad $text: $diacriticSensitive, should be boolean` ); } else if (search.$diacriticSensitive === false) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `bad $text: $diacriticSensitive - false not supported, install Postgres Unaccent Extension` ); } @@ -655,27 +655,27 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus if (fieldValue.$geoWithin && fieldValue.$geoWithin.$centerSphere) { const centerSphere = fieldValue.$geoWithin.$centerSphere; if (!(centerSphere instanceof Array) || centerSphere.length < 2) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; $centerSphere should be an array of Parse.GeoPoint and distance' ); } // Get point, convert to geo point if necessary and validate let point = centerSphere[0]; if (point instanceof Array && point.length === 2) { - point = new Parse.GeoPoint(point[1], point[0]); + point = { latitude: point[1], longitude: point[0] }; } else if (!GeoPointCoder.isValidJSON(point)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; $centerSphere geo point invalid' ); } - Parse.GeoPoint._validate(point.latitude, point.longitude); + validateGeoPoint(point.latitude, point.longitude); // Get distance and validate const distance = centerSphere[1]; if (isNaN(distance) || distance < 0) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; $centerSphere distance invalid' ); } @@ -694,36 +694,36 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus let points; if (typeof polygon === 'object' && polygon.__type === 'Polygon') { if (!polygon.coordinates || polygon.coordinates.length < 3) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; Polygon.coordinates should contain at least 3 lon/lat pairs' ); } points = polygon.coordinates; } else if (polygon instanceof Array) { if (polygon.length < 3) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' ); } points = polygon; } else { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, "bad $geoWithin value; $polygon should be Polygon object or Array of Parse.GeoPoint's" ); } points = points .map(point => { if (point instanceof Array && point.length === 2) { - Parse.GeoPoint._validate(point[1], point[0]); + validateGeoPoint(point[1], point[0]); return `(${point[0]}, ${point[1]})`; } if (typeof point !== 'object' || point.__type !== 'GeoPoint') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + throw new ParseError(ParseError.INVALID_JSON, 'bad $geoWithin value'); } else { - Parse.GeoPoint._validate(point.latitude, point.longitude); + validateGeoPoint(point.latitude, point.longitude); } return `(${point.longitude}, ${point.latitude})`; }) @@ -736,12 +736,12 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus if (fieldValue.$geoIntersects && fieldValue.$geoIntersects.$point) { const point = fieldValue.$geoIntersects.$point; if (typeof point !== 'object' || point.__type !== 'GeoPoint') { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, 'bad $geoIntersect value; $point should be GeoPoint' ); } else { - Parse.GeoPoint._validate(point.latitude, point.longitude); + validateGeoPoint(point.latitude, point.longitude); } patterns.push(`$${index}:name::polygon @> $${index + 1}::point`); values.push(fieldName, `(${point.longitude}, ${point.latitude})`); @@ -814,19 +814,19 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus } else { if (typeof postgresValue === 'object' && postgresValue.$relativeTime) { if (schema.fields[fieldName].type !== 'Date') { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, '$relativeTime can only be used with Date field' ); } - const parserResult = Utils.relativeTimeToDate(postgresValue.$relativeTime); + const parserResult = relativeTimeToDate(postgresValue.$relativeTime); if (parserResult.status === 'success') { postgresValue = toPostgresValue(parserResult.result); } else { // eslint-disable-next-line no-console console.error('Error while parsing relative date', parserResult); - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `bad $relativeTime (${postgresValue.$relativeTime}) value. ${parserResult.info}` ); } @@ -840,8 +840,8 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus }); if (initialPatternsLength === patterns.length) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, `Postgres doesn't support this query type yet ${JSON.stringify(fieldValue)}` ); } @@ -978,11 +978,11 @@ export class PostgresStorageAdapter implements StorageAdapter { Object.keys(submittedIndexes).forEach(name => { const field = submittedIndexes[name]; if (existingIndexes[name] && field.__op !== 'Delete') { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Index ${name} exists, cannot update.`); + throw new ParseError(ParseError.INVALID_QUERY, `Index ${name} exists, cannot update.`); } if (!existingIndexes[name] && field.__op === 'Delete') { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, `Index ${name} does not exist, cannot delete.` ); } @@ -992,8 +992,8 @@ export class PostgresStorageAdapter implements StorageAdapter { } else { Object.keys(field).forEach(key => { if (!Object.prototype.hasOwnProperty.call(fields, key)) { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, `Field ${key} does not exist, cannot add index.` ); } @@ -1034,7 +1034,7 @@ export class PostgresStorageAdapter implements StorageAdapter { }) .catch(err => { if (err.code === PostgresUniqueIndexViolationError && err.detail.includes(className)) { - throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, `Class ${className} already exists.`); + throw new ParseError(ParseError.DUPLICATE_VALUE, `Class ${className} already exists.`); } throw err; }); @@ -1456,8 +1456,8 @@ export class PostgresStorageAdapter implements StorageAdapter { .then(() => ({ ops: [object] })) .catch(error => { if (error.code === PostgresUniqueIndexViolationError) { - const err = new Parse.Error( - Parse.Error.DUPLICATE_VALUE, + const err = new ParseError( + ParseError.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided' ); err.underlyingError = error; @@ -1504,7 +1504,7 @@ export class PostgresStorageAdapter implements StorageAdapter { .one(qs, values, a => +a.count) .then(count => { if (count === 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } else { return count; } @@ -1769,8 +1769,8 @@ export class PostgresStorageAdapter implements StorageAdapter { } else { debug('Not supported update', { fieldName, fieldValue }); return Promise.reject( - new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + new ParseError( + ParseError.OPERATION_FORBIDDEN, `Postgres doesn't support update ${JSON.stringify(fieldValue)} yet` ) ); @@ -1806,7 +1806,7 @@ export class PostgresStorageAdapter implements StorageAdapter { const createValue = Object.assign({}, query, update); return this.createObject(className, schema, createValue, transactionalSession).catch(error => { // ignore duplicate value errors as it's upsert - if (error.code !== Parse.Error.DUPLICATE_VALUE) { + if (error.code !== ParseError.DUPLICATE_VALUE) { throw error; } return this.findOneAndUpdate(className, schema, query, update, transactionalSession); @@ -2020,8 +2020,8 @@ export class PostgresStorageAdapter implements StorageAdapter { error.message.includes(constraintName) ) { // Cast the error into the proper parse error - throw new Parse.Error( - Parse.Error.DUPLICATE_VALUE, + throw new ParseError( + ParseError.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided' ); } else { @@ -2356,7 +2356,7 @@ export class PostgresStorageAdapter implements StorageAdapter { .catch(err => { if ( err.code === PostgresDuplicateRelationError || - err.code === Parse.Error.INVALID_CLASS_NAME + err.code === ParseError.INVALID_CLASS_NAME ) { return Promise.resolve(); } @@ -2495,8 +2495,8 @@ export class PostgresStorageAdapter implements StorageAdapter { error.message.includes(indexNameOptions.name) ) { // Cast the error into the proper parse error - throw new Parse.Error( - Parse.Error.DUPLICATE_VALUE, + throw new ParseError( + ParseError.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided' ); } else { @@ -2526,7 +2526,7 @@ export class PostgresStorageAdapter implements StorageAdapter { function convertPolygonToSQL(polygon) { if (polygon.length < 3) { - throw new Parse.Error(Parse.Error.INVALID_JSON, `Polygon must have at least 3 values`); + throw new ParseError(ParseError.INVALID_JSON, `Polygon must have at least 3 values`); } if ( polygon[0][0] !== polygon[polygon.length - 1][0] || @@ -2546,14 +2546,14 @@ function convertPolygonToSQL(polygon) { return foundIndex === index; }); if (unique.length < 3) { - throw new Parse.Error( - Parse.Error.INTERNAL_SERVER_ERROR, + throw new ParseError( + ParseError.INTERNAL_SERVER_ERROR, 'GeoJSON: Loop must have at least 3 different vertices' ); } const points = polygon .map(point => { - Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0])); + validateGeoPoint(parseFloat(point[1]), parseFloat(point[0])); return `(${point[1]}, ${point[0]})`; }) .join(', '); diff --git a/src/Auth.js b/src/Auth.js index d8bf7e651f..f56180849f 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -1,10 +1,13 @@ -const Parse = require('parse/node'); +import Parse from 'parse/node'; +import ParseError from './ParseError'; import { isDeepStrictEqual } from 'util'; import { getRequestObject, resolveError } from './triggers'; import { logger } from './logger'; import { LRUCache as LRU } from 'lru-cache'; import RestQuery from './RestQuery'; import RestWrite from './RestWrite'; +import { encodeDate } from './Utils'; +import { loadModule } from './Adapters/AdapterLoader'; // An Auth object tells you who is requesting something and whether // the master key was used. @@ -115,10 +118,10 @@ const renewSessionIfNeeded = async ({ config, session, sessionToken }) => { master(config), '_Session', { objectId: session.objectId }, - { expiresAt: Parse._encode(expiresAt) } + { expiresAt: encodeDate(expiresAt) } ).execute(); } catch (e) { - if (e?.code !== Parse.Error.OBJECT_NOT_FOUND) { + if (e?.code !== ParseError.OBJECT_NOT_FOUND) { logger.error('Could not update session expiry: ', e); } } @@ -168,7 +171,7 @@ const getAuthForSessionToken = async function ({ results = (await query.execute()).results; } else { results = ( - await new Parse.Query(Parse.Session) + await new Parse.Query('_Session') .limit(1) .include('user') .equalTo('sessionToken', sessionToken) @@ -177,18 +180,18 @@ const getAuthForSessionToken = async function ({ } if (results.length !== 1 || !results[0]['user']) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token'); } const session = results[0]; const now = new Date(), expiresAt = session.expiresAt ? new Date(session.expiresAt.iso) : undefined; if (expiresAt < now) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token is expired.'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Session token is expired.'); } const obj = session.user; if (typeof obj['objectId'] === 'string' && obj['objectId'].startsWith('role:')) { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Invalid object ID.'); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'Invalid object ID.'); } delete obj.password; @@ -225,7 +228,7 @@ var getAuthForLegacySessionToken = async function ({ config, sessionToken, insta return query.execute().then(response => { var results = response.results; if (results.length !== 1) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid legacy session token'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'invalid legacy session token'); } const obj = results[0]; obj.className = '_User'; @@ -255,7 +258,6 @@ Auth.prototype.getUserRoles = function () { }; Auth.prototype.getRolesForUser = async function () { - //Stack all Parse.Role const results = []; if (this.config) { const restWhere = { @@ -276,7 +278,7 @@ Auth.prototype.getRolesForUser = async function () { }); await query.each(result => results.push(result)); } else { - await new Parse.Query(Parse.Role) + await new Parse.Query('_Role') .equalTo('users', this.user) .each(result => results.push(result.toJSON()), { useMasterKey: true }); } @@ -346,11 +348,11 @@ Auth.prototype.getRolesByIds = async function (ins) { const results = []; // Build an OR query across all parentRoles if (!this.config) { - await new Parse.Query(Parse.Role) + await new Parse.Query('_Role') .containedIn( 'roles', ins.map(id => { - const role = new Parse.Object(Parse.Role); + const role = new Parse.Object('_Role'); role.id = id; return role; }) @@ -511,14 +513,15 @@ const checkIfUserHasProvidedConfiguredProvidersForLogin = ( return; } - throw new Parse.Error( - Parse.Error.OTHER_CAUSE, + throw new ParseError( + ParseError.OTHER_CAUSE, `Missing additional authData ${additionProvidersNotFound.join(',')}` ); }; // Validate each authData step-by-step and return the provider responses const handleAuthDataValidation = async (authData, req, foundUser) => { + const Parse = await loadModule('parse/node.js'); let user; if (foundUser) { user = Parse.User.fromJSON({ className: '_User', ...foundUser }); @@ -534,8 +537,7 @@ const handleAuthDataValidation = async (authData, req, foundUser) => { user.id = req.auth.isMaster ? req.getUserId() : req.auth.user.id; await user.fetch({ useMasterKey: true }); } - - const { updatedObject } = req.buildParseObjects(); + const { updatedObject } = req.buildParseObjects(Parse); const requestObject = getRequestObject(undefined, req.auth, updatedObject, user, req.config); // Perform validation as step-by-step pipeline for better error consistency // and also to avoid to trigger a provider (like OTP SMS) if another one fails @@ -551,8 +553,8 @@ const handleAuthDataValidation = async (authData, req, foundUser) => { const { validator } = req.config.authDataManager.getValidatorForProvider(provider) || {}; const authProvider = (req.config.auth || {})[provider] || {}; if (!validator || authProvider.enabled === false) { - throw new Parse.Error( - Parse.Error.UNSUPPORTED_SERVICE, + throw new ParseError( + ParseError.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.' ); } @@ -580,7 +582,7 @@ const handleAuthDataValidation = async (authData, req, foundUser) => { } } catch (err) { const e = resolveError(err, { - code: Parse.Error.SCRIPT_FAILED, + code: ParseError.SCRIPT_FAILED, message: 'Auth failed. Unknown error.', }); const userString = diff --git a/src/ClientSDK.js b/src/ClientSDK.js index 698729fc4f..45569f73ce 100644 --- a/src/ClientSDK.js +++ b/src/ClientSDK.js @@ -1,3 +1,5 @@ +import Parse from 'parse/node'; + var semver = require('semver'); function compatible(compatibleSDK) { @@ -34,6 +36,10 @@ function fromString(version) { } module.exports = { + applicationId: Parse.applicationId, + Object: Parse.Object, + Query: Parse.Query, + Schema: Parse.Schema, compatible, supportsForwardDelete, fromString, diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index 0050216e2c..23648783d0 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -2,8 +2,7 @@ // A database adapter that works with data exported from the hosted // Parse database. -// @flow-disable-next -import { Parse } from 'parse/node'; +import ParseError from '../ParseError'; // @flow-disable-next import _ from 'lodash'; // @flow-disable-next @@ -78,14 +77,14 @@ const validateQuery = ( isMaster = true; } if (query.ACL) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.'); + throw new ParseError(ParseError.INVALID_QUERY, 'Cannot query on ACL.'); } if (query.$or) { if (query.$or instanceof Array) { query.$or.forEach(value => validateQuery(value, isMaster, isMaintenance, update)); } else { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $or format - use an array value.'); + throw new ParseError(ParseError.INVALID_QUERY, 'Bad $or format - use an array value.'); } } @@ -93,7 +92,7 @@ const validateQuery = ( if (query.$and instanceof Array) { query.$and.forEach(value => validateQuery(value, isMaster, isMaintenance, update)); } else { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $and format - use an array value.'); + throw new ParseError(ParseError.INVALID_QUERY, 'Bad $and format - use an array value.'); } } @@ -101,8 +100,8 @@ const validateQuery = ( if (query.$nor instanceof Array && query.$nor.length > 0) { query.$nor.forEach(value => validateQuery(value, isMaster, isMaintenance, update)); } else { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, 'Bad $nor format - use an array of at least 1 value.' ); } @@ -112,8 +111,8 @@ const validateQuery = ( if (query && query[key] && query[key].$regex) { if (typeof query[key].$options === 'string') { if (!query[key].$options.match(/^[imxs]+$/)) { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, `Bad $options value for query: ${query[key].$options}` ); } @@ -124,7 +123,7 @@ const validateQuery = ( ((!specialQueryKeys.includes(key) && !isMaster && !update) || (update && isMaster && !specialMasterQueryKeys.includes(key))) ) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: ${key}`); + throw new ParseError(ParseError.INVALID_KEY_NAME, `Invalid key name: ${key}`); } }); }; @@ -275,7 +274,7 @@ const flattenUpdateOperatorsForCreate = object => { switch (object[key].__op) { case 'Increment': if (typeof object[key].amount !== 'number') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + throw new ParseError(ParseError.INVALID_JSON, 'objects to add must be an array'); } object[key] = object[key].amount; break; @@ -284,19 +283,19 @@ const flattenUpdateOperatorsForCreate = object => { break; case 'Add': if (!(object[key].objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + throw new ParseError(ParseError.INVALID_JSON, 'objects to add must be an array'); } object[key] = object[key].objects; break; case 'AddUnique': if (!(object[key].objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + throw new ParseError(ParseError.INVALID_JSON, 'objects to add must be an array'); } object[key] = object[key].objects; break; case 'Remove': if (!(object[key].objects instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array'); + throw new ParseError(ParseError.INVALID_JSON, 'objects to add must be an array'); } object[key] = []; break; @@ -304,8 +303,8 @@ const flattenUpdateOperatorsForCreate = object => { delete object[key]; break; default: - throw new Parse.Error( - Parse.Error.COMMAND_UNAVAILABLE, + throw new ParseError( + ParseError.COMMAND_UNAVAILABLE, `The ${object[key].__op} operator is not supported yet.` ); } @@ -416,7 +415,7 @@ class DatabaseController { validateClassName(className: string): Promise { if (!SchemaController.classNameIsValid(className)) { return Promise.reject( - new Parse.Error(Parse.Error.INVALID_CLASS_NAME, 'invalid className: ' + className) + new ParseError(ParseError.INVALID_CLASS_NAME, 'invalid className: ' + className) ); } return Promise.resolve(); @@ -497,7 +496,7 @@ class DatabaseController { try { Utils.checkProhibitedKeywords(this.options, update); } catch (error) { - return Promise.reject(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error)); + return Promise.reject(new ParseError(ParseError.INVALID_KEY_NAME, error)); } const originalQuery = query; const originalUpdate = update; @@ -558,8 +557,8 @@ class DatabaseController { .then(schema => { Object.keys(update).forEach(fieldName => { if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { - throw new Parse.Error( - Parse.Error.INVALID_KEY_NAME, + throw new ParseError( + ParseError.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}` ); } @@ -568,8 +567,8 @@ class DatabaseController { !SchemaController.fieldNameIsValid(rootFieldName, className) && !isSpecialUpdateKey(rootFieldName) ) { - throw new Parse.Error( - Parse.Error.INVALID_KEY_NAME, + throw new ParseError( + ParseError.INVALID_KEY_NAME, `Invalid field name for update: ${fieldName}` ); } @@ -582,8 +581,8 @@ class DatabaseController { innerKey => innerKey.includes('$') || innerKey.includes('.') ) ) { - throw new Parse.Error( - Parse.Error.INVALID_NESTED_KEY, + throw new ParseError( + ParseError.INVALID_NESTED_KEY, "Nested keys should not contain the '$' or '.' characters" ); } @@ -595,7 +594,7 @@ class DatabaseController { if (validateOnly) { return this.adapter.find(className, schema, query, {}).then(result => { if (!result || !result.length) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } return {}; }); @@ -629,7 +628,7 @@ class DatabaseController { }) .then((result: any) => { if (!result) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } if (validateOnly) { return result; @@ -748,7 +747,7 @@ class DatabaseController { ) .catch(error => { // We don't care if they try to delete a non-existent relation. - if (error.code == Parse.Error.OBJECT_NOT_FOUND) { + if (error.code == ParseError.OBJECT_NOT_FOUND) { return; } throw error; @@ -785,7 +784,7 @@ class DatabaseController { aclGroup ); if (!query) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } } // delete by query @@ -813,7 +812,7 @@ class DatabaseController { ) .catch(error => { // When deleting sessions while changing passwords, don't throw an error if they don't have any sessions. - if (className === '_Session' && error.code === Parse.Error.OBJECT_NOT_FOUND) { + if (className === '_Session' && error.code === ParseError.OBJECT_NOT_FOUND) { return Promise.resolve({}); } throw error; @@ -834,7 +833,7 @@ class DatabaseController { try { Utils.checkProhibitedKeywords(this.options, object); } catch (error) { - return Promise.reject(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error)); + return Promise.reject(new ParseError(ParseError.INVALID_KEY_NAME, error)); } // Make a copy of the object, so we don't mutate the incoming data. const originalObject = object; @@ -1242,12 +1241,12 @@ class DatabaseController { }; Object.keys(sort).forEach(fieldName => { if (fieldName.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`); + throw new ParseError(ParseError.INVALID_KEY_NAME, `Cannot sort by ${fieldName}`); } const rootFieldName = getRootFieldName(fieldName); if (!SchemaController.fieldNameIsValid(rootFieldName, className)) { - throw new Parse.Error( - Parse.Error.INVALID_KEY_NAME, + throw new ParseError( + ParseError.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.` ); } @@ -1285,7 +1284,7 @@ class DatabaseController { } if (!query) { if (op === 'get') { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } else { return []; } @@ -1354,7 +1353,7 @@ class DatabaseController { }) ) .catch(error => { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, error); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, error); }); } }); @@ -1381,7 +1380,7 @@ class DatabaseController { .then(() => this.adapter.count(className, { fields: {} }, null, '', false)) .then(count => { if (count > 0) { - throw new Parse.Error( + throw new ParseError( 255, `Class ${className} is not empty, contains ${count} objects, cannot drop schema.` ); @@ -1817,8 +1816,8 @@ class DatabaseController { true ); if (match) { - throw new Parse.Error( - Parse.Error.INVALID_KEY_NAME, + throw new ParseError( + ParseError.INVALID_KEY_NAME, `Prohibited keyword in request data: ${JSON.stringify(keyword)}.` ); } diff --git a/src/Controllers/FilesController.js b/src/Controllers/FilesController.js index a88c527b00..6de3c937f9 100644 --- a/src/Controllers/FilesController.js +++ b/src/Controllers/FilesController.js @@ -3,7 +3,7 @@ import { randomHexString } from '../cryptoUtils'; import AdaptableController from './AdaptableController'; import { validateFilename, FilesAdapter } from '../Adapters/Files/FilesAdapter'; import path from 'path'; -const Parse = require('parse').Parse; +import ParseError from '../ParseError'; const legacyFilesRegex = new RegExp( '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*' @@ -103,7 +103,7 @@ export class FilesController extends AdaptableController { if (typeof error !== 'string') { return error; } - return new Parse.Error(Parse.Error.INVALID_FILE_NAME, error); + return new ParseError(ParseError.INVALID_FILE_NAME, error); } return validateFilename(filename); } diff --git a/src/Controllers/HooksController.js b/src/Controllers/HooksController.js index 277104ef32..863d0fdaf0 100644 --- a/src/Controllers/HooksController.js +++ b/src/Controllers/HooksController.js @@ -2,7 +2,7 @@ import * as triggers from '../triggers'; // @flow-disable-next -import * as Parse from 'parse/node'; +import ParseError from '../ParseError'; // @flow-disable-next import request from '../request'; import { logger } from '../logger'; @@ -92,7 +92,7 @@ export class HooksController { } else if (hook.triggerName && hook.className && hook.url) { query = { className: hook.className, triggerName: hook.triggerName }; } else { - throw new Parse.Error(143, 'invalid hook declaration'); + throw new ParseError(143, 'invalid hook declaration'); } return this.database .update(DefaultHooksCollectionName, query, hook, { upsert: true }) @@ -134,7 +134,7 @@ export class HooksController { hook.url = aHook.url; hook.triggerName = aHook.triggerName; } else { - throw new Parse.Error(143, 'invalid hook declaration'); + throw new ParseError(143, 'invalid hook declaration'); } return this.addHook(hook); @@ -144,7 +144,7 @@ export class HooksController { if (aHook.functionName) { return this.getFunction(aHook.functionName).then(result => { if (result) { - throw new Parse.Error(143, `function name: ${aHook.functionName} already exists`); + throw new ParseError(143, `function name: ${aHook.functionName} already exists`); } else { return this.createOrUpdateHook(aHook); } @@ -152,7 +152,7 @@ export class HooksController { } else if (aHook.className && aHook.triggerName) { return this.getTrigger(aHook.className, aHook.triggerName).then(result => { if (result) { - throw new Parse.Error( + throw new ParseError( 143, `class ${aHook.className} already has trigger ${aHook.triggerName}` ); @@ -161,7 +161,7 @@ export class HooksController { }); } - throw new Parse.Error(143, 'invalid hook declaration'); + throw new ParseError(143, 'invalid hook declaration'); } updateHook(aHook) { @@ -170,17 +170,17 @@ export class HooksController { if (result) { return this.createOrUpdateHook(aHook); } - throw new Parse.Error(143, `no function named: ${aHook.functionName} is defined`); + throw new ParseError(143, `no function named: ${aHook.functionName} is defined`); }); } else if (aHook.className && aHook.triggerName) { return this.getTrigger(aHook.className, aHook.triggerName).then(result => { if (result) { return this.createOrUpdateHook(aHook); } - throw new Parse.Error(143, `class ${aHook.className} does not exist`); + throw new ParseError(143, `class ${aHook.className} does not exist`); }); } - throw new Parse.Error(143, 'invalid hook declaration'); + throw new ParseError(143, 'invalid hook declaration'); } } diff --git a/src/Controllers/LoggerController.js b/src/Controllers/LoggerController.js index 6dac43518f..e652a86f56 100644 --- a/src/Controllers/LoggerController.js +++ b/src/Controllers/LoggerController.js @@ -1,4 +1,4 @@ -import { Parse } from 'parse/node'; +import ParseError from '../ParseError'; import AdaptableController from './AdaptableController'; import { LoggerAdapter } from '../Adapters/Logger/LoggerAdapter'; @@ -223,11 +223,11 @@ export class LoggerController extends AdaptableController { // size (optional) Number of rows returned by search. Defaults to 10 getLogs(options = {}) { if (!this.adapter) { - throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not available'); + throw new ParseError(ParseError.PUSH_MISCONFIGURED, 'Logger adapter is not available'); } if (typeof this.adapter.query !== 'function') { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, 'Querying logs is not supported with this adapter' ); } diff --git a/src/Controllers/PushController.js b/src/Controllers/PushController.js index 04fb5c4fd0..859884bb27 100644 --- a/src/Controllers/PushController.js +++ b/src/Controllers/PushController.js @@ -1,4 +1,4 @@ -import { Parse } from 'parse/node'; +import ParseError from '../ParseError'; import RestQuery from '../RestQuery'; import RestWrite from '../RestWrite'; import { master } from '../Auth'; @@ -8,15 +8,15 @@ import { applyDeviceTokenExists } from '../Push/utils'; export class PushController { sendPush(body = {}, where = {}, config, auth, onPushStatusSaved = () => {}, now = new Date()) { if (!config.hasPushSupport) { - throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Missing push configuration'); + throw new ParseError(ParseError.PUSH_MISCONFIGURED, 'Missing push configuration'); } // Replace the expiration_time and push_time with a valid Unix epoch milliseconds time body.expiration_time = PushController.getExpirationTime(body); body.expiration_interval = PushController.getExpirationInterval(body); if (body.expiration_time && body.expiration_interval) { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, 'Both expiration_time and expiration_interval cannot be set' ); } @@ -144,15 +144,15 @@ export class PushController { } else if (typeof expirationTimeParam === 'string') { expirationTime = new Date(expirationTimeParam); } else { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, body['expiration_time'] + ' is not valid time.' ); } // Check expirationTime is valid or not, if it is not valid, expirationTime is NaN if (!isFinite(expirationTime)) { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, body['expiration_time'] + ' is not valid time.' ); } @@ -167,8 +167,8 @@ export class PushController { var expirationIntervalParam = body['expiration_interval']; if (typeof expirationIntervalParam !== 'number' || expirationIntervalParam <= 0) { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, `expiration_interval must be a number greater than 0` ); } @@ -195,15 +195,15 @@ export class PushController { isLocalTime = !PushController.pushTimeHasTimezoneComponent(pushTimeParam); date = new Date(pushTimeParam); } else { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, body['push_time'] + ' is not valid time.' ); } // Check pushTime is valid or not, if it is not valid, pushTime is NaN if (!isFinite(date)) { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, body['push_time'] + ' is not valid time.' ); } diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index fccadd23ce..0fe495eb55 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -15,7 +15,8 @@ // different databases. // TODO: hide all schema logic inside the database adapter. // @flow-disable-next -const Parse = require('parse/node').Parse; +import Parse from 'parse/node'; +import ParseError from '../ParseError'; import { StorageAdapter } from '../Adapters/Storage/StorageAdapter'; import SchemaCache from '../Adapters/Cache/SchemaCache'; import DatabaseController from './DatabaseController'; @@ -228,8 +229,8 @@ function validatePermissionKey(key, userIdRegExp) { // userId depends on startup options so it's dynamic const valid = matchesSome || key.match(userIdRegExp) !== null; if (!valid) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${key}' is not a valid key for class level permissions` ); } @@ -247,8 +248,8 @@ function validateProtectedFieldsKey(key, userIdRegExp) { // userId regex depends on launch options so it's dynamic const valid = matchesSome || key.match(userIdRegExp) !== null; if (!valid) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${key}' is not a valid key for class level permissions` ); } @@ -275,8 +276,8 @@ function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdR } for (const operationKey in perms) { if (CLPValidKeys.indexOf(operationKey) == -1) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `${operationKey} is not a valid operation for class level permissions` ); } @@ -307,8 +308,8 @@ function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdR const protectedFields = operation[entity]; if (!Array.isArray(protectedFields)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${protectedFields}' is not a valid value for protectedFields[${entity}] - expected an array.` ); } @@ -317,15 +318,15 @@ function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdR for (const field of protectedFields) { // do not alloow to protect default fields if (defaultColumns._Default[field]) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `Default field '${field}' can not be protected` ); } // field should exist on collection if (!Object.prototype.hasOwnProperty.call(fields, field)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `Field '${field}' in protectedFields:${entity} does not exist` ); } @@ -356,8 +357,8 @@ function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdR validatePointerPermission(pointerField, fields, operation); } } else { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${pointerFields}' is not a valid value for ${operationKey}[${entity}] - expected an array.` ); } @@ -369,29 +370,29 @@ function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdR if (operationKey === 'ACL') { if (Object.prototype.toString.call(permit) !== '[object Object]') { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${permit}' is not a valid value for class level permissions acl` ); } const invalidKeys = Object.keys(permit).filter(key => !['read', 'write'].includes(key)); const invalidValues = Object.values(permit).filter(key => typeof key !== 'boolean'); if (invalidKeys.length) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${invalidKeys.join(',')}' is not a valid key for class level permissions acl` ); } if (invalidValues.length) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${invalidValues.join(',')}' is not a valid value for class level permissions acl` ); } } else if (permit !== true) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${permit}' is not a valid value for class level permissions acl ${operationKey}:${entity}` ); } @@ -402,8 +403,8 @@ function validateCLP(perms: ClassLevelPermissions, fields: SchemaFields, userIdR function validateCLPjson(operation: any, operationKey: string) { if (operationKey === 'readUserFields' || operationKey === 'writeUserFields') { if (!Array.isArray(operation)) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${operation}' is not a valid value for class level permissions ${operationKey} - must be an array` ); } @@ -412,8 +413,8 @@ function validateCLPjson(operation: any, operationKey: string) { // ok to proceed return; } else { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${operation}' is not a valid value for class level permissions ${operationKey} - must be an object` ); } @@ -435,8 +436,8 @@ function validatePointerPermission(fieldName: string, fields: Object, operation: fields[fieldName].type == 'Array') ) ) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, + throw new ParseError( + ParseError.INVALID_JSON, `'${fieldName}' is not a valid column for class level pointer permissions ${operation}` ); } @@ -489,7 +490,7 @@ function invalidClassNameMessage(className: string): string { ); } -const invalidJsonError = new Parse.Error(Parse.Error.INVALID_JSON, 'invalid JSON'); +const invalidJsonError = new ParseError(ParseError.INVALID_JSON, 'invalid JSON'); const validNonRelationOrPointerTypes = [ 'Number', 'String', @@ -506,11 +507,11 @@ const validNonRelationOrPointerTypes = [ const fieldTypeIsInvalid = ({ type, targetClass }) => { if (['Pointer', 'Relation'].indexOf(type) >= 0) { if (!targetClass) { - return new Parse.Error(135, `type ${type} needs a class name`); + return new ParseError(135, `type ${type} needs a class name`); } else if (typeof targetClass !== 'string') { return invalidJsonError; } else if (!classNameIsValid(targetClass)) { - return new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(targetClass)); + return new ParseError(ParseError.INVALID_CLASS_NAME, invalidClassNameMessage(targetClass)); } else { return undefined; } @@ -519,7 +520,7 @@ const fieldTypeIsInvalid = ({ type, targetClass }) => { return invalidJsonError; } if (validNonRelationOrPointerTypes.indexOf(type) < 0) { - return new Parse.Error(Parse.Error.INCORRECT_TYPE, `invalid field type: ${type}`); + return new ParseError(ParseError.INCORRECT_TYPE, `invalid field type: ${type}`); } return undefined; }; @@ -838,10 +839,10 @@ export default class SchemaController { ): Promise { var validationError = this.validateNewClass(className, fields, classLevelPermissions); if (validationError) { - if (validationError instanceof Parse.Error) { + if (validationError instanceof ParseError) { return Promise.reject(validationError); } else if (validationError.code && validationError.error) { - return Promise.reject(new Parse.Error(validationError.code, validationError.error)); + return Promise.reject(new ParseError(validationError.code, validationError.error)); } return Promise.reject(validationError); } @@ -860,8 +861,8 @@ export default class SchemaController { const parseSchema = convertAdapterSchemaToParseSchema(adapterSchema); return parseSchema; } catch (error) { - if (error && error.code === Parse.Error.DUPLICATE_VALUE) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`); + if (error && error.code === ParseError.DUPLICATE_VALUE) { + throw new ParseError(ParseError.INVALID_CLASS_NAME, `Class ${className} already exists.`); } else { throw error; } @@ -885,10 +886,10 @@ export default class SchemaController { existingFields[name].type !== field.type && field.__op !== 'Delete' ) { - throw new Parse.Error(255, `Field ${name} exists, cannot update.`); + throw new ParseError(255, `Field ${name} exists, cannot update.`); } if (!existingFields[name] && field.__op === 'Delete') { - throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`); + throw new ParseError(255, `Field ${name} does not exist, cannot delete.`); } }); @@ -904,7 +905,7 @@ export default class SchemaController { Object.keys(existingFields) ); if (validationError) { - throw new Parse.Error(validationError.code, validationError.error); + throw new ParseError(validationError.code, validationError.error); } // Finally we have checked to make sure the request is valid and we can start deleting fields. @@ -965,8 +966,8 @@ export default class SchemaController { }) .catch(error => { if (error === undefined) { - throw new Parse.Error( - Parse.Error.INVALID_CLASS_NAME, + throw new ParseError( + ParseError.INVALID_CLASS_NAME, `Class ${className} does not exist.` ); } else { @@ -997,23 +998,23 @@ export default class SchemaController { if (this.schemaData[className]) { return this; } else { - throw new Parse.Error(Parse.Error.INVALID_JSON, `Failed to add ${className}`); + throw new ParseError(ParseError.INVALID_JSON, `Failed to add ${className}`); } }) .catch(() => { // The schema still doesn't validate. Give up - throw new Parse.Error(Parse.Error.INVALID_JSON, 'schema class name does not revalidate'); + throw new ParseError(ParseError.INVALID_JSON, 'schema class name does not revalidate'); }) ); } validateNewClass(className: string, fields: SchemaFields = {}, classLevelPermissions: any): any { if (this.schemaData[className]) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`); + throw new ParseError(ParseError.INVALID_CLASS_NAME, `Class ${className} already exists.`); } if (!classNameIsValid(className)) { return { - code: Parse.Error.INVALID_CLASS_NAME, + code: ParseError.INVALID_CLASS_NAME, error: invalidClassNameMessage(className), }; } @@ -1030,7 +1031,7 @@ export default class SchemaController { if (existingFieldNames.indexOf(fieldName) < 0) { if (!fieldNameIsValid(fieldName, className)) { return { - code: Parse.Error.INVALID_KEY_NAME, + code: ParseError.INVALID_KEY_NAME, error: 'invalid field name: ' + fieldName, }; } @@ -1049,13 +1050,13 @@ export default class SchemaController { defaultValueType = { type: defaultValueType }; } else if (typeof defaultValueType === 'object' && fieldType.type === 'Relation') { return { - code: Parse.Error.INCORRECT_TYPE, + code: ParseError.INCORRECT_TYPE, error: `The 'default value' option is not applicable for ${typeToString(fieldType)}`, }; } if (!dbTypeMatchesObjectType(fieldType, defaultValueType)) { return { - code: Parse.Error.INCORRECT_TYPE, + code: ParseError.INCORRECT_TYPE, error: `schema mismatch for ${className}.${fieldName} default value; expected ${typeToString( fieldType )} but got ${typeToString(defaultValueType)}`, @@ -1064,7 +1065,7 @@ export default class SchemaController { } else if (fieldType.required) { if (typeof fieldType === 'object' && fieldType.type === 'Relation') { return { - code: Parse.Error.INCORRECT_TYPE, + code: ParseError.INCORRECT_TYPE, error: `The 'required' option is not applicable for ${typeToString(fieldType)}`, }; } @@ -1081,7 +1082,7 @@ export default class SchemaController { ); if (geoPoints.length > 1) { return { - code: Parse.Error.INCORRECT_TYPE, + code: ParseError.INCORRECT_TYPE, error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + @@ -1135,7 +1136,7 @@ export default class SchemaController { fieldNameToValidate = fieldNameToValidate.substring(1); } if (!fieldNameIsValid(fieldNameToValidate, className)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`); + throw new ParseError(ParseError.INVALID_KEY_NAME, `Invalid field name: ${fieldName}.`); } // If someone tries to create a new field with null/undefined as the value, return; @@ -1154,8 +1155,8 @@ export default class SchemaController { defaultValueType = { type: defaultValueType }; } if (!dbTypeMatchesObjectType(type, defaultValueType)) { - throw new Parse.Error( - Parse.Error.INCORRECT_TYPE, + throw new ParseError( + ParseError.INCORRECT_TYPE, `schema mismatch for ${className}.${fieldName} default value; expected ${typeToString( type )} but got ${typeToString(defaultValueType)}` @@ -1165,8 +1166,8 @@ export default class SchemaController { if (expectedType) { if (!dbTypeMatchesObjectType(expectedType, type)) { - throw new Parse.Error( - Parse.Error.INCORRECT_TYPE, + throw new ParseError( + ParseError.INCORRECT_TYPE, `schema mismatch for ${className}.${fieldName}; expected ${typeToString( expectedType )} but got ${typeToString(type)}` @@ -1185,7 +1186,7 @@ export default class SchemaController { return this._dbAdapter .addFieldIfNotExists(className, fieldName, type) .catch(error => { - if (error.code == Parse.Error.INCORRECT_TYPE) { + if (error.code == ParseError.INCORRECT_TYPE) { // Make sure that we throw errors when it is appropriate to do so. throw error; } @@ -1212,7 +1213,7 @@ export default class SchemaController { type = { type: type }; } if (!expectedType || !dbTypeMatchesObjectType(expectedType, type)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, `Could not add field ${fieldName}`); + throw new ParseError(ParseError.INVALID_JSON, `Could not add field ${fieldName}`); } } } @@ -1231,24 +1232,24 @@ export default class SchemaController { // a database adapter and this function would close over it or access it via member. deleteFields(fieldNames: Array, className: string, database: DatabaseController) { if (!classNameIsValid(className)) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, invalidClassNameMessage(className)); + throw new ParseError(ParseError.INVALID_CLASS_NAME, invalidClassNameMessage(className)); } fieldNames.forEach(fieldName => { if (!fieldNameIsValid(fieldName, className)) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `invalid field name: ${fieldName}`); + throw new ParseError(ParseError.INVALID_KEY_NAME, `invalid field name: ${fieldName}`); } //Don't allow deleting the default fields. if (!fieldNameIsValidForClass(fieldName, className)) { - throw new Parse.Error(136, `field ${fieldName} cannot be changed`); + throw new ParseError(136, `field ${fieldName} cannot be changed`); } }); return this.getOneSchema(className, false, { clearCache: true }) .catch(error => { if (error === undefined) { - throw new Parse.Error( - Parse.Error.INVALID_CLASS_NAME, + throw new ParseError( + ParseError.INVALID_CLASS_NAME, `Class ${className} does not exist.` ); } else { @@ -1258,7 +1259,7 @@ export default class SchemaController { .then(schema => { fieldNames.forEach(fieldName => { if (!schema.fields[fieldName]) { - throw new Parse.Error(255, `Field ${fieldName} does not exist, cannot delete.`); + throw new ParseError(255, `Field ${fieldName} does not exist, cannot delete.`); } }); @@ -1295,8 +1296,8 @@ export default class SchemaController { } if (geocount > 1) { return Promise.reject( - new Parse.Error( - Parse.Error.INCORRECT_TYPE, + new ParseError( + ParseError.INCORRECT_TYPE, 'there can only be one geopoint field in a class' ) ); @@ -1349,7 +1350,7 @@ export default class SchemaController { }); if (missingColumns.length > 0) { - throw new Parse.Error(Parse.Error.INCORRECT_TYPE, missingColumns[0] + ' is required.'); + throw new ParseError(ParseError.INCORRECT_TYPE, missingColumns[0] + ' is required.'); } return Promise.resolve(this); } @@ -1403,13 +1404,13 @@ export default class SchemaController { if (perms['requiresAuthentication']) { // If aclGroup has * (public) if (!aclGroup || aclGroup.length == 0) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Permission denied, user needs to be authenticated.' ); } else if (aclGroup.indexOf('*') > -1 && aclGroup.length == 1) { - throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Permission denied, user needs to be authenticated.' ); } @@ -1425,8 +1426,8 @@ export default class SchemaController { // Reject create when write lockdown if (permissionField == 'writeUserFields' && operation == 'create') { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, `Permission denied for action ${operation} on class ${className}.` ); } @@ -1448,8 +1449,8 @@ export default class SchemaController { } } - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, `Permission denied for action ${operation} on class ${className}.` ); } @@ -1622,7 +1623,7 @@ function getObjectType(obj): ?(SchemaField | string) { } break; } - throw new Parse.Error(Parse.Error.INCORRECT_TYPE, 'This is not a valid ' + obj.__type); + throw new ParseError(ParseError.INCORRECT_TYPE, 'This is not a valid ' + obj.__type); } if (obj['$ne']) { return getObjectType(obj['$ne']); diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index 296b7f6868..0e5fcee3bd 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -3,9 +3,10 @@ import { inflate } from '../triggers'; import AdaptableController from './AdaptableController'; import MailAdapter from '../Adapters/Email/MailAdapter'; import rest from '../rest'; -import Parse from 'parse/node'; import AccountLockout from '../AccountLockout'; import Config from '../Config'; +import { encodeDate } from '../Utils'; +import { loadModule } from '../Adapters/AdapterLoader'; var RestQuery = require('../RestQuery'); var Auth = require('../Auth'); @@ -53,7 +54,7 @@ export class UserController extends AdaptableController { } if (this.config.emailVerifyTokenValidityDuration) { - user._email_verify_token_expires_at = Parse._encode( + user._email_verify_token_expires_at = encodeDate( this.config.generateEmailVerifyTokenExpiresAt() ); } @@ -77,7 +78,7 @@ export class UserController extends AdaptableController { // add additional query params and additional fields that need to be updated if (this.config.emailVerifyTokenValidityDuration) { query.emailVerified = false; - query._email_verify_token_expires_at = { $gt: Parse._encode(new Date()) }; + query._email_verify_token_expires_at = { $gt: encodeDate(new Date()) }; updateFields._email_verify_token_expires_at = { __op: 'Delete' }; } @@ -160,6 +161,7 @@ export class UserController extends AdaptableController { const fetchedUser = await this.getUserIfNeeded(user); let shouldSendEmail = this.config.sendUserEmailVerification; if (typeof shouldSendEmail === 'function') { + const Parse = await loadModule('parse/node.js'); const response = await Promise.resolve( this.config.sendUserEmailVerification({ user: Parse.Object.fromJSON({ className: '_User', ...fetchedUser }), @@ -204,6 +206,7 @@ export class UserController extends AdaptableController { ) { return Promise.resolve(true); } + const Parse = await loadModule('parse/node.js'); const shouldSend = await this.setEmailVerifyToken(user, { object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)), master, @@ -232,7 +235,7 @@ export class UserController extends AdaptableController { const token = { _perishable_token: randomString(25) }; if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) { - token._perishable_token_expires_at = Parse._encode( + token._perishable_token_expires_at = encodeDate( this.config.generatePasswordResetTokenExpiresAt() ); } @@ -307,7 +310,7 @@ export class UserController extends AdaptableController { return await accountLockoutPolicy.unlockAccount(); } catch (error) { if (error && error.message) { - // in case of Parse.Error, fail with the error message only + // in case of ParseError, fail with the error message only return Promise.reject(error.message); } return Promise.reject(error); diff --git a/src/GraphQL/ParseGraphQLSchema.js b/src/GraphQL/ParseGraphQLSchema.js index 154e774897..b74aa609b6 100644 --- a/src/GraphQL/ParseGraphQLSchema.js +++ b/src/GraphQL/ParseGraphQLSchema.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import { GraphQLSchema, GraphQLObjectType, DocumentNode, GraphQLNamedType } from 'graphql'; import { mergeSchemas } from '@graphql-tools/schema'; import { mergeTypeDefs } from '@graphql-tools/merge'; @@ -356,7 +356,7 @@ class ParseGraphQLSchema { } handleError(error) { - if (error instanceof Parse.Error) { + if (error instanceof ParseError) { this.log.error('Parse error: ', error); } else { this.log.error('Uncaught internal server error.', error, error.stack); diff --git a/src/GraphQL/helpers/objectsQueries.js b/src/GraphQL/helpers/objectsQueries.js index 1aae0e7f9c..0ca3a480a9 100644 --- a/src/GraphQL/helpers/objectsQueries.js +++ b/src/GraphQL/helpers/objectsQueries.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; import { offsetToCursor, cursorToOffset } from 'graphql-relay'; import rest from '../../rest'; import { transformQueryInputToParse } from '../transformers/query'; @@ -74,7 +74,7 @@ const getObject = async ( ); if (!response.results || response.results.length == 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } const object = response.results[0]; @@ -238,7 +238,7 @@ const calculateSkipAndLimit = (skipInput, first, after, last, before, maxLimit) // Validates the skip input if (skipInput || skipInput === 0) { if (skipInput < 0) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Skip should be a positive number'); + throw new ParseError(ParseError.INVALID_QUERY, 'Skip should be a positive number'); } skip = skipInput; } @@ -247,7 +247,7 @@ const calculateSkipAndLimit = (skipInput, first, after, last, before, maxLimit) if (after) { after = cursorToOffset(after); if ((!after && after !== 0) || after < 0) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'After is not a valid cursor'); + throw new ParseError(ParseError.INVALID_QUERY, 'After is not a valid cursor'); } // If skip and after are passed, a new skip is calculated by adding them @@ -257,7 +257,7 @@ const calculateSkipAndLimit = (skipInput, first, after, last, before, maxLimit) // Validates the first param if (first || first === 0) { if (first < 0) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'First should be a positive number'); + throw new ParseError(ParseError.INVALID_QUERY, 'First should be a positive number'); } // The first param is translated to the limit param of the Parse legacy API @@ -269,7 +269,7 @@ const calculateSkipAndLimit = (skipInput, first, after, last, before, maxLimit) // This method converts the cursor to the index of the object before = cursorToOffset(before); if ((!before && before !== 0) || before < 0) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Before is not a valid cursor'); + throw new ParseError(ParseError.INVALID_QUERY, 'Before is not a valid cursor'); } if ((skip || 0) >= before) { @@ -284,7 +284,7 @@ const calculateSkipAndLimit = (skipInput, first, after, last, before, maxLimit) // Validates the last param if (last || last === 0) { if (last < 0) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Last should be a positive number'); + throw new ParseError(ParseError.INVALID_QUERY, 'Last should be a positive number'); } if (last > maxLimit) { diff --git a/src/GraphQL/loaders/filesMutations.js b/src/GraphQL/loaders/filesMutations.js index 0a16a1c4a6..7d3b5e5c62 100644 --- a/src/GraphQL/loaders/filesMutations.js +++ b/src/GraphQL/loaders/filesMutations.js @@ -1,7 +1,7 @@ import { GraphQLNonNull } from 'graphql'; import { request } from 'http'; import { mutationWithClientMutationId } from 'graphql-relay'; -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; import * as defaultGraphQLTypes from './defaultGraphQLTypes'; import logger from '../../logger'; @@ -39,7 +39,7 @@ const handleUpload = async (upload, config) => { try { resolve(JSON.parse(data)); } catch (e) { - reject(new Parse.Error(Parse.error, data)); + reject(new ParseError(ParseError, data)); } }); } @@ -55,7 +55,7 @@ const handleUpload = async (upload, config) => { } catch (e) { stream.destroy(); logger.error('Error creating a file: ', e); - throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, `Could not store file: ${filename}.`); + throw new ParseError(ParseError.FILE_SAVE_ERROR, `Could not store file: ${filename}.`); } }; diff --git a/src/GraphQL/loaders/schemaMutations.js b/src/GraphQL/loaders/schemaMutations.js index ffb4d6523b..00c340044b 100644 --- a/src/GraphQL/loaders/schemaMutations.js +++ b/src/GraphQL/loaders/schemaMutations.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; import { GraphQLNonNull } from 'graphql'; import deepcopy from 'deepcopy'; import { mutationWithClientMutationId } from 'graphql-relay'; @@ -33,8 +33,8 @@ const load = parseGraphQLSchema => { enforceMasterKeyAccess(auth); if (auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to create a schema." ); } @@ -82,8 +82,8 @@ const load = parseGraphQLSchema => { enforceMasterKeyAccess(auth); if (auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to update a schema." ); } @@ -133,8 +133,8 @@ const load = parseGraphQLSchema => { enforceMasterKeyAccess(auth); if (auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to delete a schema." ); } diff --git a/src/GraphQL/loaders/schemaQueries.js b/src/GraphQL/loaders/schemaQueries.js index 25bc071919..78ae9a0c28 100644 --- a/src/GraphQL/loaders/schemaQueries.js +++ b/src/GraphQL/loaders/schemaQueries.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; import deepcopy from 'deepcopy'; import { GraphQLNonNull, GraphQLList } from 'graphql'; import { transformToGraphQL } from '../transformers/schemaFields'; @@ -10,9 +10,9 @@ const getClass = async (name, schema) => { return await schema.getOneSchema(name, true); } catch (e) { if (e === undefined) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${name} does not exist.`); + throw new ParseError(ParseError.INVALID_CLASS_NAME, `Class ${name} does not exist.`); } else { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.'); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'Database adapter error.'); } } }; diff --git a/src/GraphQL/loaders/usersMutations.js b/src/GraphQL/loaders/usersMutations.js index 2f59081a03..c883a5b841 100644 --- a/src/GraphQL/loaders/usersMutations.js +++ b/src/GraphQL/loaders/usersMutations.js @@ -6,7 +6,7 @@ import * as objectsMutations from '../helpers/objectsMutations'; import { OBJECT } from './defaultGraphQLTypes'; import { getUserFromSessionToken } from './usersQueries'; import { transformTypes } from '../transformers/mutation'; -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; const usersRouter = new UsersRouter(); @@ -305,10 +305,10 @@ const load = parseGraphQLSchema => { mutateAndGetPayload: async ({ password, token }, context) => { const { config } = context; if (!password) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'you must provide a password'); + throw new ParseError(ParseError.PASSWORD_MISSING, 'you must provide a password'); } if (!token) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'you must provide a token'); + throw new ParseError(ParseError.OTHER_CAUSE, 'you must provide a token'); } const userController = config.userController; diff --git a/src/GraphQL/loaders/usersQueries.js b/src/GraphQL/loaders/usersQueries.js index c64ce6b90d..71b2824910 100644 --- a/src/GraphQL/loaders/usersQueries.js +++ b/src/GraphQL/loaders/usersQueries.js @@ -1,6 +1,6 @@ import { GraphQLNonNull } from 'graphql'; import getFieldNames from 'graphql-list-fields'; -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; import rest from '../../rest'; import { extractKeysAndInclude } from './parseClassTypes'; import { Auth } from '../../Auth'; @@ -8,7 +8,7 @@ import { Auth } from '../../Auth'; const getUserFromSessionToken = async (context, queryInfo, keysPrefix, userId) => { const { info, config } = context; if (!info || !info.sessionToken) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token'); } const sessionToken = info.sessionToken; const selectedFields = getFieldNames(queryInfo) @@ -62,7 +62,7 @@ const getUserFromSessionToken = async (context, queryInfo, keysPrefix, userId) = info.context ); if (!response.results || response.results.length == 0) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token'); } else { const user = response.results[0]; return { diff --git a/src/GraphQL/parseGraphQLUtils.js b/src/GraphQL/parseGraphQLUtils.js index f1194784cb..355f69e428 100644 --- a/src/GraphQL/parseGraphQLUtils.js +++ b/src/GraphQL/parseGraphQLUtils.js @@ -1,19 +1,19 @@ -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import { GraphQLError } from 'graphql'; export function enforceMasterKeyAccess(auth) { if (!auth.isMaster) { - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'unauthorized: master key is required'); + throw new ParseError(ParseError.OPERATION_FORBIDDEN, 'unauthorized: master key is required'); } } export function toGraphQLError(error) { let code, message; - if (error instanceof Parse.Error) { + if (error instanceof ParseError) { code = error.code; message = error.message; } else { - code = Parse.Error.INTERNAL_SERVER_ERROR; + code = ParseError.INTERNAL_SERVER_ERROR; message = 'Internal server error'; } return new GraphQLError(message, { extensions: { code } }); diff --git a/src/GraphQL/transformers/mutation.js b/src/GraphQL/transformers/mutation.js index 833ec93294..d072bf00af 100644 --- a/src/GraphQL/transformers/mutation.js +++ b/src/GraphQL/transformers/mutation.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; import { fromGlobalId } from 'graphql-relay'; import { handleUpload } from '../loaders/filesMutations'; import * as objectsMutations from '../helpers/objectsMutations'; @@ -99,7 +99,7 @@ const transformers = { } else if (file && file.name) { return { name: file.name, __type: 'File', url: file.url }; } - throw new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.'); + throw new ParseError(ParseError.FILE_SAVE_ERROR, 'Invalid file upload.'); }, polygon: value => ({ __type: 'Polygon', @@ -148,8 +148,8 @@ const transformers = { { config, auth, info } ) => { if (Object.keys(value).length === 0) - { throw new Parse.Error( - Parse.Error.INVALID_POINTER, + { throw new ParseError( + ParseError.INVALID_POINTER, `You need to provide at least one operation on the relation mutation of field ${field}` ); } @@ -225,8 +225,8 @@ const transformers = { { config, auth, info } ) => { if (Object.keys(value).length > 1 || Object.keys(value).length === 0) - { throw new Parse.Error( - Parse.Error.INVALID_POINTER, + { throw new ParseError( + ParseError.INVALID_POINTER, `You need to provide link OR createLink on the pointer mutation of field ${field}` ); } diff --git a/src/GraphQL/transformers/schemaFields.js b/src/GraphQL/transformers/schemaFields.js index 4e3898737e..62a1fdda6d 100644 --- a/src/GraphQL/transformers/schemaFields.js +++ b/src/GraphQL/transformers/schemaFields.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../../ParseError'; const transformToParse = (graphQLSchemaFields, existingFields) => { if (!graphQLSchemaFields) { @@ -27,7 +27,7 @@ const transformToParse = (graphQLSchemaFields, existingFields) => { return parseSchemaFields; } if (parseSchemaFields[field.name] || (existingFields && existingFields[field.name])) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Duplicated field name: ${field.name}`); + throw new ParseError(ParseError.INVALID_KEY_NAME, `Duplicated field name: ${field.name}`); } if (type === 'Relation' || type === 'Pointer') { return { diff --git a/src/LiveQuery/ParseLiveQueryServer.ts b/src/LiveQuery/ParseLiveQueryServer.ts index 3e6048c345..27e0e9ee4a 100644 --- a/src/LiveQuery/ParseLiveQueryServer.ts +++ b/src/LiveQuery/ParseLiveQueryServer.ts @@ -1,5 +1,6 @@ import tv4 from 'tv4'; import Parse from 'parse/node'; +import ParseError from '../ParseError'; import { Subscription } from './Subscription'; import { Client } from './Client'; import { ParseWebSocketServer } from './ParseWebSocketServer'; @@ -529,7 +530,7 @@ class ParseLiveQueryServer { async _clearCachedRoles(userId: string) { try { - const validTokens = await new Parse.Query(Parse.Session) + const validTokens = await new Parse.Query('_Session') .equalTo('user', Parse.User.createWithoutData(userId)) .find({ useMasterKey: true }); await Promise.all( @@ -571,7 +572,7 @@ class ParseLiveQueryServer { .catch(error => { // There was an error with the session token const result: any = {}; - if (error && error.code === Parse.Error.INVALID_SESSION_TOKEN) { + if (error && error.code === ParseError.INVALID_SESSION_TOKEN) { result.error = error; this.authCache.set(sessionToken, Promise.resolve(result), this.config.cacheTimeout); } else { @@ -620,7 +621,7 @@ class ParseLiveQueryServer { // }); // }) // // it's rejected here, check the roles - // var rolesQuery = new Parse.Query(Parse.Role); + // var rolesQuery = new Parse.Query('_Role'); // rolesQuery.equalTo("users", user); // return rolesQuery.find({useMasterKey:true}); } @@ -899,7 +900,7 @@ class ParseLiveQueryServer { } else if (!request.master) { Client.pushError( parseWebsocket, - Parse.Error.INVALID_SESSION_TOKEN, + ParseError.INVALID_SESSION_TOKEN, 'Invalid session token', false, request.requestId diff --git a/src/LiveQuery/QueryTools.js b/src/LiveQuery/QueryTools.js index a839918088..3c030a4b45 100644 --- a/src/LiveQuery/QueryTools.js +++ b/src/LiveQuery/QueryTools.js @@ -1,7 +1,7 @@ var equalObjects = require('./equalObjects'); var Id = require('./Id'); -var Parse = require('parse/node'); - +import Parse from 'parse/node'; +import { containsPoint, radiansTo } from '../Utils'; /** * Query Hashes are deterministic hashes for Parse Queries. * Any two queries that have the same set of constraints will produce the same @@ -354,25 +354,21 @@ function matchesKeyConstraints(object, key, constraints) { geoPoint.latitude, geoPoint.longitude, ]); - const polygon = new Parse.Polygon(points); - return polygon.containsPoint(object[key]); + return containsPoint(points, object[key]); } if (compareTo.$centerSphere) { const [WGS84Point, maxDistance] = compareTo.$centerSphere; - const centerPoint = new Parse.GeoPoint({ + const centerPoint = { latitude: WGS84Point[1], longitude: WGS84Point[0], - }); - const point = new Parse.GeoPoint(object[key]); - const distance = point.radiansTo(centerPoint); + }; + const distance = radiansTo(object[key], centerPoint); return distance <= maxDistance; } break; } case '$geoIntersects': { - const polygon = new Parse.Polygon(object[key].coordinates); - const point = new Parse.GeoPoint(compareTo.$point); - return polygon.containsPoint(point); + return containsPoint(object[key].coordinates, compareTo.$point); } case '$options': // Not a query type, but a way to add options to $regex. Ignore and diff --git a/src/ParseError.js b/src/ParseError.js new file mode 100644 index 0000000000..1141137390 --- /dev/null +++ b/src/ParseError.js @@ -0,0 +1,5 @@ +import Parse from 'parse/node'; + +const ParseError = Parse.Error; + +module.exports = ParseError; diff --git a/src/ParseServer.ts b/src/ParseServer.ts index c0df4f3431..029d3f8722 100644 --- a/src/ParseServer.ts +++ b/src/ParseServer.ts @@ -7,7 +7,7 @@ var batch = require('./batch'), { parse } = require('graphql'), path = require('path'), fs = require('fs'); - +import ParseError from './ParseError'; import { ParseServerOptions, LiveQueryServerOptions } from './Options'; import defaults from './defaults'; import * as logging from './logger'; @@ -164,7 +164,7 @@ class ParseServer { try { await databaseController.performInitialization(); } catch (e) { - if (e.code !== Parse.Error.DUPLICATE_VALUE) { + if (e.code !== ParseError.DUPLICATE_VALUE) { throw e; } } diff --git a/src/ParseServerRESTController.js b/src/ParseServerRESTController.js index 9ec4b6f86e..6b775af061 100644 --- a/src/ParseServerRESTController.js +++ b/src/ParseServerRESTController.js @@ -1,7 +1,7 @@ const Config = require('./Config'); const Auth = require('./Auth'); import RESTController from 'parse/lib/node/RESTController'; -const Parse = require('parse/node'); +import ParseError from './ParseError'; function getSessionToken(options) { if (options && typeof options.sessionToken === 'string') { @@ -139,8 +139,8 @@ function ParseServerRESTController(applicationId, router) { }, err => { if ( - err instanceof Parse.Error && - err.code == Parse.Error.INVALID_JSON && + err instanceof ParseError && + err.code == ParseError.INVALID_JSON && err.message == `cannot route ${method} ${path}` ) { RESTController.request.apply(null, args).then(resolve, reject); diff --git a/src/PromiseRouter.js b/src/PromiseRouter.js index 3386daf223..3f3e60d7d4 100644 --- a/src/PromiseRouter.js +++ b/src/PromiseRouter.js @@ -5,7 +5,7 @@ // themselves use our routing information, without disturbing express // components that external developers may be modifying. -import Parse from 'parse/node'; +import ParseError from './ParseError'; import express from 'express'; import log from './logger'; import { inspect } from 'util'; @@ -121,7 +121,7 @@ export default class PromiseRouter { tryRouteRequest(method, path, request) { var match = this.match(method, path); if (!match) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route ' + method + ' ' + path); + throw new ParseError(ParseError.INVALID_JSON, 'cannot route ' + method + ' ' + path); } request.params = match.params; return new Promise((resolve, reject) => { diff --git a/src/Push/utils.js b/src/Push/utils.js index 5be13c272e..e1f6a13145 100644 --- a/src/Push/utils.js +++ b/src/Push/utils.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import deepcopy from 'deepcopy'; export function isPushIncrementing(body) { @@ -119,8 +119,8 @@ export function validatePushType(where = {}, validPushTypes = []) { for (var i = 0; i < deviceTypes.length; i++) { var deviceType = deviceTypes[i]; if (validPushTypes.indexOf(deviceType) < 0) { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, deviceType + ' is not supported push type.' ); } diff --git a/src/RestQuery.js b/src/RestQuery.js index 621700984b..183c4a4b80 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -2,7 +2,8 @@ // operation, encoded in the REST API format. var SchemaController = require('./Controllers/SchemaController'); -var Parse = require('parse/node').Parse; +import Parse from 'parse/node'; +import ParseError from './ParseError'; const triggers = require('./triggers'); const { continueWhile } = require('parse/lib/node/promiseUtils'); const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL']; @@ -48,7 +49,7 @@ async function RestQuery({ context, }) { if (![RestQuery.Method.find, RestQuery.Method.get].includes(method)) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'bad query type'); + throw new ParseError(ParseError.INVALID_QUERY, 'bad query type'); } enforceRoleSecurity(method, className, auth); const result = runBeforeFind @@ -116,7 +117,7 @@ function _UnsafeRestQuery( if (!this.auth.isMaster) { if (this.className == '_Session') { if (!this.auth.user) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token'); } this.restWhere = { $and: [ @@ -263,7 +264,7 @@ function _UnsafeRestQuery( case 'subqueryReadPreference': break; default: - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad option: ' + option); + throw new ParseError(ParseError.INVALID_JSON, 'bad option: ' + option); } } } @@ -417,8 +418,8 @@ _UnsafeRestQuery.prototype.validateClientClassCreation = function () { .then(schemaController => schemaController.hasClass(this.className)) .then(hasClass => { if (hasClass !== true) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, 'This user is not allowed to access ' + 'non-existent class: ' + this.className ); } @@ -458,7 +459,7 @@ _UnsafeRestQuery.prototype.replaceInQuery = async function () { // The inQuery value must have precisely two keys - where and className var inQueryValue = inQueryObject['$inQuery']; if (!inQueryValue.where || !inQueryValue.className) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $inQuery'); + throw new ParseError(ParseError.INVALID_QUERY, 'improper usage of $inQuery'); } const additionalOptions = { @@ -518,7 +519,7 @@ _UnsafeRestQuery.prototype.replaceNotInQuery = async function () { // The notInQuery value must have precisely two keys - where and className var notInQueryValue = notInQueryObject['$notInQuery']; if (!notInQueryValue.where || !notInQueryValue.className) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $notInQuery'); + throw new ParseError(ParseError.INVALID_QUERY, 'improper usage of $notInQuery'); } const additionalOptions = { @@ -591,7 +592,7 @@ _UnsafeRestQuery.prototype.replaceSelect = async function () { !selectValue.query.className || Object.keys(selectValue).length !== 2 ) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $select'); + throw new ParseError(ParseError.INVALID_QUERY, 'improper usage of $select'); } const additionalOptions = { @@ -655,7 +656,7 @@ _UnsafeRestQuery.prototype.replaceDontSelect = async function () { !dontSelectValue.query.className || Object.keys(dontSelectValue).length !== 2 ) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'improper usage of $dontSelect'); + throw new ParseError(ParseError.INVALID_QUERY, 'improper usage of $dontSelect'); } const additionalOptions = { redirectClassNameForKey: dontSelectValue.query.redirectClassNameForKey, @@ -796,8 +797,8 @@ _UnsafeRestQuery.prototype.denyProtectedFields = async function () { ) || []; for (const key of protectedFields) { if (this.restWhere[key]) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, `This user is not allowed to query ${key} on class ${this.className}` ); } diff --git a/src/RestWrite.js b/src/RestWrite.js index 78dd8c8878..5e418e0294 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -6,10 +6,10 @@ var SchemaController = require('./Controllers/SchemaController'); var deepcopy = require('deepcopy'); const Auth = require('./Auth'); -const Utils = require('./Utils'); +const { encodeDate, checkProhibitedKeywords } = require('./Utils'); var cryptoUtils = require('./cryptoUtils'); var passwordCrypto = require('./password'); -var Parse = require('parse/node'); +import ParseError from './ParseError'; var triggers = require('./triggers'); var ClientSDK = require('./ClientSDK'); const util = require('util'); @@ -17,6 +17,7 @@ import RestQuery from './RestQuery'; import _ from 'lodash'; import logger from './logger'; import { requiredColumns } from './Controllers/SchemaController'; +import { loadModule } from './Adapters/AdapterLoader'; // query and data are both provided in REST API format. So data // types are encoded by plain old objects. @@ -29,8 +30,8 @@ import { requiredColumns } from './Controllers/SchemaController'; // for the _User class. function RestWrite(config, auth, className, query, data, originalData, clientSDK, context, action) { if (auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, 'Cannot perform a write operation when using readOnlyMasterKey' ); } @@ -49,17 +50,17 @@ function RestWrite(config, auth, className, query, data, originalData, clientSDK if (!query) { if (this.config.allowCustomObjectId) { if (Object.prototype.hasOwnProperty.call(data, 'objectId') && !data.objectId) { - throw new Parse.Error( - Parse.Error.MISSING_OBJECT_ID, + throw new ParseError( + ParseError.MISSING_OBJECT_ID, 'objectId must not be empty, null or undefined' ); } } else { if (data.objectId) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.'); + throw new ParseError(ParseError.INVALID_KEY_NAME, 'objectId is an invalid field name.'); } if (data.id) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'id is an invalid field name.'); + throw new ParseError(ParseError.INVALID_KEY_NAME, 'id is an invalid field name.'); } } } @@ -79,7 +80,7 @@ function RestWrite(config, auth, className, query, data, originalData, clientSDK this.originalData = originalData; // The timestamp we'll use for this whole operation - this.updatedAt = Parse._encode(new Date()).iso; + this.updatedAt = encodeDate(new Date()).iso; // Shared SchemaController to be reused to reduce the number of loadSchema() calls per request // Once set the schemaData should be immutable @@ -162,7 +163,7 @@ RestWrite.prototype.execute = function () { } } if (this.storage.rejectSignup && this.config.preventSignupWithUnverifiedEmail) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); + throw new ParseError(ParseError.EMAIL_NOT_FOUND, 'User email is not verified.'); } return this.response; }); @@ -199,8 +200,8 @@ RestWrite.prototype.validateClientClassCreation = function () { .then(schemaController => schemaController.hasClass(this.className)) .then(hasClass => { if (hasClass !== true) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, 'This user is not allowed to access ' + 'non-existent class: ' + this.className ); } @@ -223,7 +224,7 @@ RestWrite.prototype.validateSchema = function () { // Runs any beforeSave triggers against this operation. // Any change leads to our data being mutated. -RestWrite.prototype.runBeforeSaveTrigger = function () { +RestWrite.prototype.runBeforeSaveTrigger = async function () { if (this.response || this.runOptions.many) { return; } @@ -234,8 +235,8 @@ RestWrite.prototype.runBeforeSaveTrigger = function () { ) { return Promise.resolve(); } - - const { originalObject, updatedObject } = this.buildParseObjects(); + const Parse = await loadModule('parse/node.js'); + const { originalObject, updatedObject } = this.buildParseObjects(Parse); const identifier = updatedObject._getStateIdentifier(); const stateController = Parse.CoreManager.getObjectStateController(); const [pending] = stateController.getPendingOps(identifier); @@ -270,7 +271,7 @@ RestWrite.prototype.runBeforeSaveTrigger = function () { // In the case that there is no permission for the operation, it throws an error return databasePromise.then(result => { if (!result || result.length <= 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } }); }) @@ -303,9 +304,9 @@ RestWrite.prototype.runBeforeSaveTrigger = function () { } } try { - Utils.checkProhibitedKeywords(this.config, this.data); + checkProhibitedKeywords(this.config, this.data); } catch (error) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, error); + throw new ParseError(ParseError.INVALID_KEY_NAME, error); } }); }; @@ -362,7 +363,7 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () { this.storage.fieldsChangedByTrigger.push(fieldName); } } else if (schema.fields[fieldName] && schema.fields[fieldName].required === true) { - throw new Parse.Error(Parse.Error.VALIDATION_ERROR, `${fieldName} is required`); + throw new ParseError(ParseError.VALIDATION_ERROR, `${fieldName} is required`); } } }; @@ -401,8 +402,8 @@ RestWrite.prototype.setRequiredFieldsIfNeeded = function () { const updatedAt = new Date(this.data.updatedAt.iso); if (updatedAt < createdAt) { - throw new Parse.Error( - Parse.Error.VALIDATION_ERROR, + throw new ParseError( + ParseError.VALIDATION_ERROR, 'updatedAt cannot occur before createdAt' ); } @@ -453,10 +454,10 @@ RestWrite.prototype.validateAuthData = function () { if (!this.query && !authData) { if (typeof this.data.username !== 'string' || _.isEmpty(this.data.username)) { - throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'bad or missing username'); + throw new ParseError(ParseError.USERNAME_MISSING, 'bad or missing username'); } if (typeof this.data.password !== 'string' || _.isEmpty(this.data.password)) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required'); + throw new ParseError(ParseError.PASSWORD_MISSING, 'password is required'); } } @@ -468,8 +469,8 @@ RestWrite.prototype.validateAuthData = function () { return; } else if (Object.prototype.hasOwnProperty.call(this.data, 'authData') && !this.data.authData) { // Handle saving authData to null - throw new Parse.Error( - Parse.Error.UNSUPPORTED_SERVICE, + throw new ParseError( + ParseError.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.' ); } @@ -484,8 +485,8 @@ RestWrite.prototype.validateAuthData = function () { return this.handleAuthData(authData); } } - throw new Parse.Error( - Parse.Error.UNSUPPORTED_SERVICE, + throw new ParseError( + ParseError.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.' ); }; @@ -528,12 +529,12 @@ RestWrite.prototype.ensureUniqueAuthDataId = async function () { const r = await Auth.findUsersWithAuthData(this.config, this.data.authData); const results = this.filteredObjectsByACL(r); if (results.length > 1) { - throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); + throw new ParseError(ParseError.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); } // use data.objectId in case of login time and found user during handle validateAuthData const userId = this.getUserId() || this.data.objectId; if (results.length === 1 && userId !== results[0].objectId) { - throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); + throw new ParseError(ParseError.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); } }; @@ -549,7 +550,7 @@ RestWrite.prototype.handleAuthData = async function (authData) { // To avoid https://github.com/parse-community/parse-server/security/advisories/GHSA-8w3j-g983-8jh5 // Let's run some validation before throwing await Auth.handleAuthDataValidation(authData, this, userResult); - throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); + throw new ParseError(ParseError.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); } // No user found with provided authData we need to validate @@ -661,7 +662,7 @@ RestWrite.prototype.checkRestrictedFields = async function () { if (!this.auth.isMaintenance && !this.auth.isMaster && 'emailVerified' in this.data) { const error = `Clients aren't allowed to manually update email verification.`; - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error); + throw new ParseError(ParseError.OPERATION_FORBIDDEN, error); } }; @@ -757,8 +758,8 @@ RestWrite.prototype._validateUserName = function () { ) .then(results => { if (results.length > 0) { - throw new Parse.Error( - Parse.Error.USERNAME_TAKEN, + throw new ParseError( + ParseError.USERNAME_TAKEN, 'Account already exists for this username.' ); } @@ -785,7 +786,7 @@ RestWrite.prototype._validateEmail = function () { // Validate basic email address format if (!this.data.email.match(/^.+@.+$/)) { return Promise.reject( - new Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS, 'Email address format is invalid.') + new ParseError(ParseError.INVALID_EMAIL_ADDRESS, 'Email address format is invalid.') ); } // Case insensitive match, see note above function. @@ -800,10 +801,10 @@ RestWrite.prototype._validateEmail = function () { {}, this.validSchemaController ) - .then(results => { + .then(async (results) => { if (results.length > 0) { - throw new Parse.Error( - Parse.Error.EMAIL_TAKEN, + throw new ParseError( + ParseError.EMAIL_TAKEN, 'Account already exists for this email address.' ); } @@ -814,7 +815,8 @@ RestWrite.prototype._validateEmail = function () { Object.keys(this.data.authData)[0] === 'anonymous') ) { // We updated the email, send a new validation - const { originalObject, updatedObject } = this.buildParseObjects(); + const Parse = await loadModule('parse/node.js'); + const { originalObject, updatedObject } = this.buildParseObjects(Parse); const request = { original: originalObject, object: updatedObject, @@ -855,7 +857,7 @@ RestWrite.prototype._validatePasswordRequirements = function () { (this.config.passwordPolicy.validatorCallback && !this.config.passwordPolicy.validatorCallback(this.data.password)) ) { - return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, policyError)); + return Promise.reject(new ParseError(ParseError.VALIDATION_ERROR, policyError)); } // check whether password contain username @@ -863,7 +865,7 @@ RestWrite.prototype._validatePasswordRequirements = function () { if (this.data.username) { // username is not passed during password reset if (this.data.password.indexOf(this.data.username) >= 0) - { return Promise.reject(new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError)); } + { return Promise.reject(new ParseError(ParseError.VALIDATION_ERROR, containsUsernameError)); } } else { // retrieve the User object using objectId during password reset return this.config.database.find('_User', { objectId: this.objectId() }).then(results => { @@ -872,7 +874,7 @@ RestWrite.prototype._validatePasswordRequirements = function () { } if (this.data.password.indexOf(results[0].username) >= 0) { return Promise.reject( - new Parse.Error(Parse.Error.VALIDATION_ERROR, containsUsernameError) + new ParseError(ParseError.VALIDATION_ERROR, containsUsernameError) ); } return Promise.resolve(); }); @@ -922,8 +924,8 @@ RestWrite.prototype._validatePasswordHistory = function () { if (err === 'REPEAT_PASSWORD') // a match was found { return Promise.reject( - new Parse.Error( - Parse.Error.VALIDATION_ERROR, + new ParseError( + ParseError.VALIDATION_ERROR, `New password should not be the same as last ${this.config.passwordPolicy.maxPasswordHistory} passwords.` ) ); } @@ -949,7 +951,8 @@ RestWrite.prototype.createSessionTokenIfNeeded = async function () { // If sign-up call if (!this.storage.authProvider) { // Create request object for verification functions - const { originalObject, updatedObject } = this.buildParseObjects(); + const Parse = await loadModule('parse/node.js'); + const { originalObject, updatedObject } = this.buildParseObjects(Parse); const request = { original: originalObject, object: updatedObject, @@ -1012,7 +1015,7 @@ RestWrite.createSession = function ( objectId: userId, }, createdWith, - expiresAt: Parse._encode(expiresAt), + expiresAt: encodeDate(expiresAt), }; if (installationId) { @@ -1106,21 +1109,21 @@ RestWrite.prototype.handleSession = function () { } if (!this.auth.user && !this.auth.isMaster && !this.auth.isMaintenance) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Session token required.'); } // TODO: Verify proper error to throw if (this.data.ACL) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'Cannot set ' + 'ACL on a Session.'); + throw new ParseError(ParseError.INVALID_KEY_NAME, 'Cannot set ' + 'ACL on a Session.'); } if (this.query) { if (this.data.user && !this.auth.isMaster && this.data.user.objectId != this.auth.user.id) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME); + throw new ParseError(ParseError.INVALID_KEY_NAME); } else if (this.data.installationId) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME); + throw new ParseError(ParseError.INVALID_KEY_NAME); } else if (this.data.sessionToken) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME); + throw new ParseError(ParseError.INVALID_KEY_NAME); } if (!this.auth.isMaster) { this.query = { @@ -1157,7 +1160,7 @@ RestWrite.prototype.handleSession = function () { return createSession().then(results => { if (!results.response) { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Error creating session.'); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'Error creating session.'); } sessionData['objectId'] = results.response['objectId']; this.response = { @@ -1185,7 +1188,7 @@ RestWrite.prototype.handleInstallation = function () { !this.data.installationId && !this.auth.installationId ) { - throw new Parse.Error( + throw new ParseError( 135, 'at least one ID field (deviceToken, installationId) ' + 'must be specified in this operation' ); @@ -1271,14 +1274,14 @@ RestWrite.prototype.handleInstallation = function () { // Sanity checks when running a query if (this.query && this.query.objectId) { if (!objectIdMatch) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for update.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found for update.'); } if ( this.data.installationId && objectIdMatch.installationId && this.data.installationId !== objectIdMatch.installationId ) { - throw new Parse.Error(136, 'installationId may not be changed in this ' + 'operation'); + throw new ParseError(136, 'installationId may not be changed in this ' + 'operation'); } if ( this.data.deviceToken && @@ -1287,14 +1290,14 @@ RestWrite.prototype.handleInstallation = function () { !this.data.installationId && !objectIdMatch.installationId ) { - throw new Parse.Error(136, 'deviceToken may not be changed in this ' + 'operation'); + throw new ParseError(136, 'deviceToken may not be changed in this ' + 'operation'); } if ( this.data.deviceType && this.data.deviceType && this.data.deviceType !== objectIdMatch.deviceType ) { - throw new Parse.Error(136, 'deviceType may not be changed in this ' + 'operation'); + throw new ParseError(136, 'deviceType may not be changed in this ' + 'operation'); } } @@ -1307,7 +1310,7 @@ RestWrite.prototype.handleInstallation = function () { } // need to specify deviceType only if it's new if (!this.query && !this.data.deviceType && !idMatch) { - throw new Parse.Error(135, 'deviceType must be specified in this operation'); + throw new ParseError(135, 'deviceType must be specified in this operation'); } }) .then(() => { @@ -1323,7 +1326,7 @@ RestWrite.prototype.handleInstallation = function () { // can just return the match. return deviceTokenMatches[0]['objectId']; } else if (!this.data.installationId) { - throw new Parse.Error( + throw new ParseError( 132, 'Must specify installationId when deviceToken ' + 'matches multiple Installation objects' @@ -1344,7 +1347,7 @@ RestWrite.prototype.handleInstallation = function () { delQuery['appIdentifier'] = this.data.appIdentifier; } this.config.database.destroy('_Installation', delQuery).catch(err => { - if (err.code == Parse.Error.OBJECT_NOT_FOUND) { + if (err.code == ParseError.OBJECT_NOT_FOUND) { // no deletions were made. Can be ignored. return; } @@ -1365,7 +1368,7 @@ RestWrite.prototype.handleInstallation = function () { return deviceTokenMatches[0]['objectId']; }) .catch(err => { - if (err.code == Parse.Error.OBJECT_NOT_FOUND) { + if (err.code == ParseError.OBJECT_NOT_FOUND) { // no deletions were made. Can be ignored return; } @@ -1403,7 +1406,7 @@ RestWrite.prototype.handleInstallation = function () { delQuery['appIdentifier'] = this.data.appIdentifier; } this.config.database.destroy('_Installation', delQuery).catch(err => { - if (err.code == Parse.Error.OBJECT_NOT_FOUND) { + if (err.code == ParseError.OBJECT_NOT_FOUND) { // no deletions were made. Can be ignored. return; } @@ -1450,8 +1453,8 @@ RestWrite.prototype.runDatabaseOperation = function () { } if (this.className === '_User' && this.query && this.auth.isUnauthenticated()) { - throw new Parse.Error( - Parse.Error.SESSION_MISSING, + throw new ParseError( + ParseError.SESSION_MISSING, `Cannot modify user ${this.query.objectId}.` ); } @@ -1463,7 +1466,7 @@ RestWrite.prototype.runDatabaseOperation = function () { // TODO: Add better detection for ACL, ensuring a user can't be locked from // their own user record. if (this.data.ACL && this.data.ACL['*unresolved']) { - throw new Parse.Error(Parse.Error.INVALID_ACL, 'Invalid ACL.'); + throw new ParseError(ParseError.INVALID_ACL, 'Invalid ACL.'); } if (this.query) { @@ -1484,7 +1487,7 @@ RestWrite.prototype.runDatabaseOperation = function () { this.config.passwordPolicy && this.config.passwordPolicy.maxPasswordAge ) { - this.data._password_changed_at = Parse._encode(new Date()); + this.data._password_changed_at = encodeDate(new Date()); } // Ignore createdAt when update delete this.data.createdAt; @@ -1539,9 +1542,9 @@ RestWrite.prototype.runDatabaseOperation = function () { false, this.validSchemaController ) - .then(response => { + .then(async (response) => { response.updatedAt = this.updatedAt; - this._updateResponseWithData(response, this.data); + await this._updateResponseWithData(response, this.data); this.response = { response }; }); }); @@ -1561,7 +1564,7 @@ RestWrite.prototype.runDatabaseOperation = function () { this.data.ACL = ACL; // password timestamp to be used when password expiry policy is enforced if (this.config.passwordPolicy && this.config.passwordPolicy.maxPasswordAge) { - this.data._password_changed_at = Parse._encode(new Date()); + this.data._password_changed_at = encodeDate(new Date()); } } @@ -1569,21 +1572,21 @@ RestWrite.prototype.runDatabaseOperation = function () { return this.config.database .create(this.className, this.data, this.runOptions, false, this.validSchemaController) .catch(error => { - if (this.className !== '_User' || error.code !== Parse.Error.DUPLICATE_VALUE) { + if (this.className !== '_User' || error.code !== ParseError.DUPLICATE_VALUE) { throw error; } // Quick check, if we were able to infer the duplicated field name if (error && error.userInfo && error.userInfo.duplicated_field === 'username') { - throw new Parse.Error( - Parse.Error.USERNAME_TAKEN, + throw new ParseError( + ParseError.USERNAME_TAKEN, 'Account already exists for this username.' ); } if (error && error.userInfo && error.userInfo.duplicated_field === 'email') { - throw new Parse.Error( - Parse.Error.EMAIL_TAKEN, + throw new ParseError( + ParseError.EMAIL_TAKEN, 'Account already exists for this email address.' ); } @@ -1603,8 +1606,8 @@ RestWrite.prototype.runDatabaseOperation = function () { ) .then(results => { if (results.length > 0) { - throw new Parse.Error( - Parse.Error.USERNAME_TAKEN, + throw new ParseError( + ParseError.USERNAME_TAKEN, 'Account already exists for this username.' ); } @@ -1616,25 +1619,25 @@ RestWrite.prototype.runDatabaseOperation = function () { }) .then(results => { if (results.length > 0) { - throw new Parse.Error( - Parse.Error.EMAIL_TAKEN, + throw new ParseError( + ParseError.EMAIL_TAKEN, 'Account already exists for this email address.' ); } - throw new Parse.Error( - Parse.Error.DUPLICATE_VALUE, + throw new ParseError( + ParseError.DUPLICATE_VALUE, 'A duplicate value for a field with unique values was provided' ); }); }) - .then(response => { + .then(async (response) => { response.objectId = this.data.objectId; response.createdAt = this.data.createdAt; if (this.responseShouldHaveUsername) { response.username = this.data.username; } - this._updateResponseWithData(response, this.data); + await this._updateResponseWithData(response, this.data); this.response = { status: 201, response, @@ -1645,7 +1648,7 @@ RestWrite.prototype.runDatabaseOperation = function () { }; // Returns nothing - doesn't wait for the trigger. -RestWrite.prototype.runAfterSaveTrigger = function () { +RestWrite.prototype.runAfterSaveTrigger = async function () { if (!this.response || !this.response.response || this.runOptions.many) { return; } @@ -1660,8 +1663,8 @@ RestWrite.prototype.runAfterSaveTrigger = function () { if (!hasAfterSaveHook && !hasLiveQuery) { return Promise.resolve(); } - - const { originalObject, updatedObject } = this.buildParseObjects(); + const Parse = await loadModule('parse/node.js'); + const { originalObject, updatedObject } = this.buildParseObjects(Parse); updatedObject._handleSaveResponse(this.response.response, this.response.status || 200); if (hasLiveQuery) { @@ -1689,13 +1692,13 @@ RestWrite.prototype.runAfterSaveTrigger = function () { this.config, this.context ) - .then(result => { + .then(async (result) => { const jsonReturned = result && !result._toFullJSON; if (jsonReturned) { this.pendingOps.operations = {}; this.response.response = result; } else { - this.response.response = this._updateResponseWithData( + this.response.response = await this._updateResponseWithData( (result || updatedObject).toJSON(), this.data ); @@ -1720,7 +1723,7 @@ RestWrite.prototype.objectId = function () { }; // Returns a copy of the data and delete bad keys (_auth_data, _hashed_password...) -RestWrite.prototype.sanitizedData = function () { +RestWrite.prototype.sanitizedData = function (Parse) { const data = Object.keys(this.data).reduce((data, key) => { // Regexp comes from Parse.Object.prototype.validate if (!/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) { @@ -1732,7 +1735,7 @@ RestWrite.prototype.sanitizedData = function () { }; // Returns an updated copy of the object -RestWrite.prototype.buildParseObjects = function () { +RestWrite.prototype.buildParseObjects = function (Parse) { const extraData = { className: this.className, objectId: this.query?.objectId }; let originalObject; if (this.query && this.query.objectId) { @@ -1771,7 +1774,7 @@ RestWrite.prototype.buildParseObjects = function () { return data; }, deepcopy(this.data)); - const sanitized = this.sanitizedData(); + const sanitized = this.sanitizedData(Parse); for (const attribute of readOnlyAttributes) { delete sanitized[attribute]; } @@ -1795,13 +1798,17 @@ RestWrite.prototype.cleanUserAuthData = function () { } }; -RestWrite.prototype._updateResponseWithData = function (response, data) { - const stateController = Parse.CoreManager.getObjectStateController(); - const [pending] = stateController.getPendingOps(this.pendingOps.identifier); - for (const key in this.pendingOps.operations) { - if (!pending[key]) { - data[key] = this.originalData ? this.originalData[key] : { __op: 'Delete' }; - this.storage.fieldsChangedByTrigger.push(key); +RestWrite.prototype._updateResponseWithData = async function (response, data) { + // Operations are set if beforeSave trigger is used + if (this.pendingOps.operations) { + const Parse = await loadModule('parse/node.js'); + const stateController = Parse.CoreManager.getObjectStateController(); + const [pending] = stateController.getPendingOps(this.pendingOps.identifier); + for (const key in this.pendingOps.operations) { + if (!pending[key]) { + data[key] = this.originalData ? this.originalData[key] : { __op: 'Delete' }; + this.storage.fieldsChangedByTrigger.push(key); + } } } const skipKeys = [...(requiredColumns.read[this.className] || [])]; diff --git a/src/Routers/AggregateRouter.js b/src/Routers/AggregateRouter.js index cf9b5cd190..66b2458ea5 100644 --- a/src/Routers/AggregateRouter.js +++ b/src/Routers/AggregateRouter.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import * as middleware from '../middlewares'; import rest from '../rest'; import ClassesRouter from './ClassesRouter'; @@ -48,7 +48,7 @@ export class AggregateRouter extends ClassesRouter { } return { response }; } catch (e) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, e.message); + throw new ParseError(ParseError.INVALID_QUERY, e.message); } } @@ -90,8 +90,8 @@ export class AggregateRouter extends ClassesRouter { return pipeline.map(stage => { const keys = Object.keys(stage); if (keys.length !== 1) { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, `Pipeline stages should only have one key but found ${keys.join(', ')}.` ); } @@ -105,18 +105,18 @@ export class AggregateRouter extends ClassesRouter { return; } if (stageName[0] !== '$') { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Invalid aggregate stage '${stageName}'.`); + throw new ParseError(ParseError.INVALID_QUERY, `Invalid aggregate stage '${stageName}'.`); } if (stageName === '$group') { if (Object.prototype.hasOwnProperty.call(stage[stageName], 'objectId')) { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, `Cannot use 'objectId' in aggregation stage $group.` ); } if (!Object.prototype.hasOwnProperty.call(stage[stageName], '_id')) { - throw new Parse.Error( - Parse.Error.INVALID_QUERY, + throw new ParseError( + ParseError.INVALID_QUERY, `Invalid parameter for query: group. Missing key _id` ); } diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index 8b6e447757..0e86c38731 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -1,7 +1,7 @@ import PromiseRouter from '../PromiseRouter'; import rest from '../rest'; import _ from 'lodash'; -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import { promiseEnsureIdempotency } from '../middlewares'; const ALLOWED_GET_QUERY_KEYS = [ @@ -53,7 +53,7 @@ export class ClassesRouter extends PromiseRouter { for (const key of Object.keys(body)) { if (ALLOWED_GET_QUERY_KEYS.indexOf(key) === -1) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter'); + throw new ParseError(ParseError.INVALID_QUERY, 'Improper encode of parameter'); } } @@ -88,7 +88,7 @@ export class ClassesRouter extends PromiseRouter { ) .then(response => { if (!response.results || response.results.length == 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } if (this.className(req) === '_User') { @@ -111,7 +111,7 @@ export class ClassesRouter extends PromiseRouter { typeof req.body?.objectId === 'string' && req.body.objectId.startsWith('role:') ) { - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.'); + throw new ParseError(ParseError.OPERATION_FORBIDDEN, 'Invalid object ID.'); } return rest.create( req.config, @@ -178,7 +178,7 @@ export class ClassesRouter extends PromiseRouter { for (const key of Object.keys(body)) { if (allowConstraints.indexOf(key) === -1) { - throw new Parse.Error(Parse.Error.INVALID_QUERY, `Invalid parameter for query: ${key}`); + throw new ParseError(ParseError.INVALID_QUERY, `Invalid parameter for query: ${key}`); } } const options = {}; diff --git a/src/Routers/CloudCodeRouter.js b/src/Routers/CloudCodeRouter.js index 58408151e7..2587a58996 100644 --- a/src/Routers/CloudCodeRouter.js +++ b/src/Routers/CloudCodeRouter.js @@ -1,5 +1,5 @@ import PromiseRouter from '../PromiseRouter'; -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import rest from '../rest'; const triggers = require('../triggers'); const middleware = require('../middlewares'); @@ -14,8 +14,8 @@ function formatJobSchedule(job_schedule) { function validateJobSchedule(config, job_schedule) { const jobs = triggers.getJobs(config.applicationId) || {}; if (job_schedule.jobName && !jobs[job_schedule.jobName]) { - throw new Parse.Error( - Parse.Error.INTERNAL_SERVER_ERROR, + throw new ParseError( + ParseError.INTERNAL_SERVER_ERROR, 'Cannot Schedule a job that is not deployed' ); } diff --git a/src/Routers/FilesRouter.js b/src/Routers/FilesRouter.js index 0bec64c9aa..e508519ed0 100644 --- a/src/Routers/FilesRouter.js +++ b/src/Routers/FilesRouter.js @@ -1,8 +1,9 @@ import express from 'express'; import * as Middlewares from '../middlewares'; -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import Config from '../Config'; import logger from '../logger'; +import { loadModule } from '../Adapters/AdapterLoader'; const triggers = require('../triggers'); const http = require('http'); const Utils = require('../Utils'); @@ -39,7 +40,7 @@ export class FilesRouter { router.get('/files/:appId/metadata/:filename', this.metadataHandler); router.post('/files', function (req, res, next) { - next(new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename not provided.')); + next(new ParseError(ParseError.INVALID_FILE_NAME, 'Filename not provided.')); }); router.post( @@ -69,7 +70,7 @@ export class FilesRouter { const config = Config.get(req.params.appId); if (!config) { res.status(403); - const err = new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid application ID.'); + const err = new ParseError(ParseError.OPERATION_FORBIDDEN, 'Invalid application ID.'); res.json({ code: err.code, error: err.message }); return; } @@ -79,16 +80,20 @@ export class FilesRouter { const filesController = config.filesController; const mime = (await import('mime')).default; let contentType = mime.getType(filename); - let file = new Parse.File(filename, { base64: '' }, contentType); - const triggerResult = await triggers.maybeRunFileTrigger( - triggers.Types.beforeFind, - { file }, - config, - req.auth - ); - if (triggerResult?.file?._name) { - filename = triggerResult?.file?._name; - contentType = mime.getType(filename); + const hasBeforeFindTrigger = triggers.triggerExists('@File', triggers.Types.beforeFind, config.applicationId); + if (hasBeforeFindTrigger) { + const Parse = await loadModule('parse/node.js'); + const file = new Parse.File(filename, { base64: '' }, contentType); + const triggerResult = await triggers.maybeRunFileTrigger( + triggers.Types.beforeFind, + { file }, + config, + req.auth + ); + if (triggerResult?.file?._name) { + filename = triggerResult?.file?._name; + contentType = mime.getType(filename); + } } if (isFileStreamable(req, filesController)) { @@ -108,19 +113,22 @@ export class FilesRouter { if (!data) { return; } - file = new Parse.File(filename, { base64: data.toString('base64') }, contentType); - const afterFind = await triggers.maybeRunFileTrigger( - triggers.Types.afterFind, - { file, forceDownload: false }, - config, - req.auth - ); - + const hasAfterFindHook = triggers.triggerExists('@File', triggers.Types.afterFind, config.applicationId); + let afterFind; + if (hasAfterFindHook) { + const Parse = await loadModule('parse/node.js'); + const file = new Parse.File(filename, { base64: data.toString('base64') }, contentType); + afterFind = await triggers.maybeRunFileTrigger( + triggers.Types.afterFind, + { file, forceDownload: false }, + config, + req.auth + ); + } if (afterFind?.file) { contentType = mime.getType(afterFind.file._name); data = Buffer.from(afterFind.file._data, 'base64'); } - res.status(200); res.set('Content-Type', contentType); res.set('Content-Length', data.length); @@ -130,7 +138,7 @@ export class FilesRouter { res.end(data); } catch (e) { const err = triggers.resolveError(e, { - code: Parse.Error.SCRIPT_FAILED, + code: ParseError.SCRIPT_FAILED, message: `Could not find file: ${filename}.`, }); res.status(403); @@ -142,24 +150,24 @@ export class FilesRouter { const config = req.config; const user = req.auth.user; const isMaster = req.auth.isMaster; - const isLinked = user && Parse.AnonymousUtils.isLinked(user); + const isLinked = user && user._isLinked('anonymous'); if (!isMaster && !config.fileUpload.enableForAnonymousUser && isLinked) { next( - new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by anonymous user is disabled.') + new ParseError(ParseError.FILE_SAVE_ERROR, 'File upload by anonymous user is disabled.') ); return; } if (!isMaster && !config.fileUpload.enableForAuthenticatedUser && !isLinked && user) { next( - new Parse.Error( - Parse.Error.FILE_SAVE_ERROR, + new ParseError( + ParseError.FILE_SAVE_ERROR, 'File upload by authenticated user is disabled.' ) ); return; } if (!isMaster && !config.fileUpload.enableForPublic && !user) { - next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by public is disabled.')); + next(new ParseError(ParseError.FILE_SAVE_ERROR, 'File upload by public is disabled.')); return; } const filesController = config.filesController; @@ -167,7 +175,7 @@ export class FilesRouter { const contentType = req.get('Content-type'); if (!req.body || !req.body.length) { - next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.')); + next(new ParseError(ParseError.FILE_SAVE_ERROR, 'Invalid file upload.')); return; } @@ -200,8 +208,8 @@ export class FilesRouter { if (extension && !isValidExtension(extension)) { next( - new Parse.Error( - Parse.Error.FILE_SAVE_ERROR, + new ParseError( + ParseError.FILE_SAVE_ERROR, `File upload of extension ${extension} is disabled.` ) ); @@ -210,6 +218,8 @@ export class FilesRouter { } const base64 = req.body.toString('base64'); + // TODO: Move to beforeSave trigger check + const Parse = await loadModule('parse/node.js'); const file = new Parse.File(filename, { base64 }, contentType); const { metadata = {}, tags = {} } = req.fileData || {}; try { @@ -217,7 +227,7 @@ export class FilesRouter { Utils.checkProhibitedKeywords(config, metadata); Utils.checkProhibitedKeywords(config, tags); } catch (error) { - next(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error)); + next(new ParseError(ParseError.INVALID_KEY_NAME, error)); return; } file.setTags(tags); @@ -287,7 +297,7 @@ export class FilesRouter { } catch (e) { logger.error('Error creating a file: ', e); const error = triggers.resolveError(e, { - code: Parse.Error.FILE_SAVE_ERROR, + code: ParseError.FILE_SAVE_ERROR, message: `Could not store file: ${fileObject.file._name}.`, }); next(error); @@ -298,19 +308,23 @@ export class FilesRouter { try { const { filesController } = req.config; const { filename } = req.params; - // run beforeDeleteFile trigger - const file = new Parse.File(filename); - file._url = await filesController.adapter.getFileLocation(req.config, filename); - const fileObject = { file, fileSize: null }; - await triggers.maybeRunFileTrigger( - triggers.Types.beforeDelete, - fileObject, - req.config, - req.auth - ); - // delete file + const fileObject = { file: null, fileSize: null }; + + const hasBeforeDeleteHook = triggers.triggerExists('@File', triggers.Types.beforeDelete, req.config.applicationId); + if (hasBeforeDeleteHook) { + const Parse = await loadModule('parse/node.js'); + const file = new Parse.File(filename); + file._url = await filesController.adapter.getFileLocation(req.config, filename); + fileObject.file = file; + await triggers.maybeRunFileTrigger( + triggers.Types.beforeDelete, + fileObject, + req.config, + req.auth + ); + } await filesController.deleteFile(req.config, filename); - // run afterDeleteFile trigger + await triggers.maybeRunFileTrigger( triggers.Types.afterDelete, fileObject, @@ -323,7 +337,7 @@ export class FilesRouter { } catch (e) { logger.error('Error deleting a file: ', e); const error = triggers.resolveError(e, { - code: Parse.Error.FILE_DELETE_ERROR, + code: ParseError.FILE_DELETE_ERROR, message: 'Could not delete file.', }); next(error); diff --git a/src/Routers/FunctionsRouter.js b/src/Routers/FunctionsRouter.js index e88a7521dd..19f6d4bba3 100644 --- a/src/Routers/FunctionsRouter.js +++ b/src/Routers/FunctionsRouter.js @@ -1,18 +1,18 @@ // FunctionsRouter.js - -var Parse = require('parse/node').Parse, - triggers = require('../triggers'); +import ParseError from '../ParseError'; +const triggers = require('../triggers'); import PromiseRouter from '../PromiseRouter'; import { promiseEnforceMasterKeyAccess, promiseEnsureIdempotency } from '../middlewares'; import { jobStatusHandler } from '../StatusHandler'; import _ from 'lodash'; import { logger } from '../logger'; +import { loadModule } from '../Adapters/AdapterLoader'; -function parseObject(obj, config) { +function parseObject(obj, config, Parse) { if (Array.isArray(obj)) { return obj.map(item => { - return parseObject(item, config); + return parseObject(item, config, Parse); }); } else if (obj && obj.__type == 'Date') { return Object.assign(new Date(obj.iso), obj); @@ -25,14 +25,14 @@ function parseObject(obj, config) { objectId: obj.objectId, }); } else if (obj && typeof obj === 'object') { - return parseParams(obj, config); + return parseParams(obj, config, Parse); } else { return obj; } } -function parseParams(params, config) { - return _.mapValues(params, item => parseObject(item, config)); +function parseParams(params, config, Parse) { + return _.mapValues(params, item => parseObject(item, config, Parse)); } export class FunctionsRouter extends PromiseRouter { @@ -57,16 +57,17 @@ export class FunctionsRouter extends PromiseRouter { }); } - static handleCloudJob(req) { + static async handleCloudJob(req) { const jobName = req.params.jobName || req.body?.jobName; const applicationId = req.config.applicationId; const jobHandler = jobStatusHandler(req.config); const jobFunction = triggers.getJob(jobName, applicationId); if (!jobFunction) { - throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Invalid job.'); + throw new ParseError(ParseError.SCRIPT_FAILED, 'Invalid job.'); } + const Parse = await loadModule('parse/node.js'); let params = Object.assign({}, req.body, req.query); - params = parseParams(params, req.config); + params = parseParams(params, req.config, Parse); const request = { params: params, log: req.config.loggerController, @@ -102,7 +103,7 @@ export class FunctionsRouter extends PromiseRouter { }); } - static createResponseObject(resolve, reject) { + static createResponseObject(resolve, reject, Parse) { return { success: function (result) { resolve({ @@ -117,16 +118,17 @@ export class FunctionsRouter extends PromiseRouter { }, }; } - static handleCloudFunction(req) { + static async handleCloudFunction(req) { const functionName = req.params.functionName; const applicationId = req.config.applicationId; const theFunction = triggers.getFunction(functionName, applicationId); if (!theFunction) { - throw new Parse.Error(Parse.Error.SCRIPT_FAILED, `Invalid function: "${functionName}"`); + throw new ParseError(ParseError.SCRIPT_FAILED, `Invalid function: "${functionName}"`); } + const Parse = await loadModule('parse/node.js'); let params = Object.assign({}, req.body, req.query); - params = parseParams(params, req.config); + params = parseParams(params, req.config, Parse); const request = { params: params, master: req.auth && req.auth.isMaster, @@ -180,7 +182,8 @@ export class FunctionsRouter extends PromiseRouter { } catch (e) { reject(e); } - } + }, + Parse, ); return Promise.resolve() .then(() => { diff --git a/src/Routers/GlobalConfigRouter.js b/src/Routers/GlobalConfigRouter.js index 5a28b3bae1..32dea67afa 100644 --- a/src/Routers/GlobalConfigRouter.js +++ b/src/Routers/GlobalConfigRouter.js @@ -1,10 +1,11 @@ // global_config.js -import Parse from 'parse/node'; import PromiseRouter from '../PromiseRouter'; import * as middleware from '../middlewares'; import * as triggers from '../triggers'; +import ParseError from '../ParseError'; +import { loadModule } from '../Adapters/AdapterLoader'; -const getConfigFromParams = params => { +const getConfigFromParams = (params, Parse) => { const config = new Parse.Config(); for (const attr in params) { config.attributes[attr] = Parse._decode(undefined, params[attr]); @@ -41,8 +42,8 @@ export class GlobalConfigRouter extends PromiseRouter { async updateGlobalConfig(req) { if (req.auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to update the config." ); } @@ -54,18 +55,22 @@ export class GlobalConfigRouter extends PromiseRouter { acc[`masterKeyOnly.${key}`] = masterKeyOnly[key] || false; return acc; }, {}); - const className = triggers.getClassName(Parse.Config); + const className = '@Config'; const hasBeforeSaveHook = triggers.triggerExists(className, triggers.Types.beforeSave, req.config.applicationId); const hasAfterSaveHook = triggers.triggerExists(className, triggers.Types.afterSave, req.config.applicationId); let originalConfigObject; let updatedConfigObject; - const configObject = new Parse.Config(); - configObject.attributes = params; + let configObject; const results = await req.config.database.find('_GlobalConfig', { objectId: '1' }, { limit: 1 }); const isNew = results.length !== 1; - if (!isNew && (hasBeforeSaveHook || hasAfterSaveHook)) { - originalConfigObject = getConfigFromParams(results[0].params); + if (hasBeforeSaveHook || hasAfterSaveHook) { + const Parse = await loadModule('parse/node.js'); + configObject = new Parse.Config(); + configObject.attributes = params; + if (!isNew) { + originalConfigObject = getConfigFromParams(results[0].params, Parse); + } } try { await triggers.maybeRunGlobalConfigTrigger(triggers.Types.beforeSave, req.auth, configObject, originalConfigObject, req.config, req.context); @@ -74,13 +79,16 @@ export class GlobalConfigRouter extends PromiseRouter { updatedConfigObject = configObject; } else { const result = await req.config.database.update('_GlobalConfig', { objectId: '1' }, update, {}, true); - updatedConfigObject = getConfigFromParams(result.params); + if (hasAfterSaveHook) { + const Parse = await loadModule('parse/node.js'); + updatedConfigObject = getConfigFromParams(result.params, Parse); + } } await triggers.maybeRunGlobalConfigTrigger(triggers.Types.afterSave, req.auth, updatedConfigObject, originalConfigObject, req.config, req.context); return { response: { result: true } } } catch (err) { const error = triggers.resolveError(err, { - code: Parse.Error.SCRIPT_FAILED, + code: ParseError.SCRIPT_FAILED, message: 'Script failed. Unknown error.', }); throw error; diff --git a/src/Routers/GraphQLRouter.js b/src/Routers/GraphQLRouter.js index d472ac9df5..2dee714400 100644 --- a/src/Routers/GraphQLRouter.js +++ b/src/Routers/GraphQLRouter.js @@ -1,4 +1,4 @@ -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import PromiseRouter from '../PromiseRouter'; import * as middleware from '../middlewares'; @@ -14,8 +14,8 @@ export class GraphQLRouter extends PromiseRouter { async updateGraphQLConfig(req) { if (req.auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to update the GraphQL config." ); } diff --git a/src/Routers/HooksRouter.js b/src/Routers/HooksRouter.js index 104ef799c2..8d61be4daf 100644 --- a/src/Routers/HooksRouter.js +++ b/src/Routers/HooksRouter.js @@ -1,4 +1,4 @@ -import { Parse } from 'parse/node'; +import ParseError from '../ParseError'; import PromiseRouter from '../PromiseRouter'; import * as middleware from '../middlewares'; @@ -20,7 +20,7 @@ export class HooksRouter extends PromiseRouter { if (req.params.functionName) { return hooksController.getFunction(req.params.functionName).then(foundFunction => { if (!foundFunction) { - throw new Parse.Error(143, `no function named: ${req.params.functionName} is defined`); + throw new ParseError(143, `no function named: ${req.params.functionName} is defined`); } return Promise.resolve({ response: foundFunction }); }); @@ -43,7 +43,7 @@ export class HooksRouter extends PromiseRouter { .getTrigger(req.params.className, req.params.triggerName) .then(foundTrigger => { if (!foundTrigger) { - throw new Parse.Error(143, `class ${req.params.className} does not exist`); + throw new ParseError(143, `class ${req.params.className} does not exist`); } return Promise.resolve({ response: foundTrigger }); }); @@ -76,7 +76,7 @@ export class HooksRouter extends PromiseRouter { hook.triggerName = req.params.triggerName; hook.url = req.body.url; } else { - throw new Parse.Error(143, 'invalid hook declaration'); + throw new ParseError(143, 'invalid hook declaration'); } return this.updateHook(hook, req.config); } diff --git a/src/Routers/IAPValidationRouter.js b/src/Routers/IAPValidationRouter.js index bae6f593e9..c5d89f692c 100644 --- a/src/Routers/IAPValidationRouter.js +++ b/src/Routers/IAPValidationRouter.js @@ -1,7 +1,7 @@ import PromiseRouter from '../PromiseRouter'; const request = require('../request'); const rest = require('../rest'); -import Parse from 'parse/node'; +import ParseError from '../ParseError'; // TODO move validation logic in IAPValidationController const IAP_SANDBOX_URL = 'https://sandbox.itunes.apple.com/verifyReceipt'; @@ -58,7 +58,7 @@ function getFileForProductIdentifier(productIdentifier, req) { const products = result.results; if (!products || products.length != 1) { // Error not found or too many - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found.'); } var download = products[0].download; @@ -73,7 +73,7 @@ export class IAPValidationRouter extends PromiseRouter { if (!receipt || !productIdentifier) { // TODO: Error, malformed request - throw new Parse.Error(Parse.Error.INVALID_JSON, 'missing receipt or productIdentifier'); + throw new ParseError(ParseError.INVALID_JSON, 'missing receipt or productIdentifier'); } // Transform the object if there diff --git a/src/Routers/LogsRouter.js b/src/Routers/LogsRouter.js index 182a4f1669..46a28f56bc 100644 --- a/src/Routers/LogsRouter.js +++ b/src/Routers/LogsRouter.js @@ -1,4 +1,4 @@ -import { Parse } from 'parse/node'; +import ParseError from '../ParseError'; import PromiseRouter from '../PromiseRouter'; import * as middleware from '../middlewares'; @@ -17,7 +17,7 @@ export class LogsRouter extends PromiseRouter { validateRequest(req) { if (!req.config || !req.config.loggerController) { - throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Logger adapter is not available'); + throw new ParseError(ParseError.PUSH_MISCONFIGURED, 'Logger adapter is not available'); } } diff --git a/src/Routers/PagesRouter.js b/src/Routers/PagesRouter.js index 1ea3211684..1ad4ff386f 100644 --- a/src/Routers/PagesRouter.js +++ b/src/Routers/PagesRouter.js @@ -3,7 +3,7 @@ import Config from '../Config'; import express from 'express'; import path from 'path'; import { promises as fs } from 'fs'; -import { Parse } from 'parse/node'; +import ParseError from '../ParseError'; import Utils from '../Utils'; import mustache from 'mustache'; import Page from '../Page'; @@ -186,11 +186,11 @@ export class PagesRouter extends PromiseRouter { } if (!token) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token'); + throw new ParseError(ParseError.OTHER_CAUSE, 'Missing token'); } if (!new_password) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'Missing password'); + throw new ParseError(ParseError.PASSWORD_MISSING, 'Missing password'); } return config.userController @@ -217,7 +217,7 @@ export class PagesRouter extends PromiseRouter { }); } if (result.err) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, `${result.err}`); + throw new ParseError(ParseError.OTHER_CAUSE, `${result.err}`); } } diff --git a/src/Routers/PublicAPIRouter.js b/src/Routers/PublicAPIRouter.js index 2ec993f390..a2bb5ec8de 100644 --- a/src/Routers/PublicAPIRouter.js +++ b/src/Routers/PublicAPIRouter.js @@ -4,7 +4,7 @@ import express from 'express'; import path from 'path'; import fs from 'fs'; import qs from 'querystring'; -import { Parse } from 'parse/node'; +import ParseError from '../ParseError'; import Deprecator from '../Deprecator/Deprecator'; const public_html = path.resolve(__dirname, '../../public_html'); @@ -170,11 +170,11 @@ export class PublicAPIRouter extends PromiseRouter { } if (!token) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Missing token'); + throw new ParseError(ParseError.OTHER_CAUSE, 'Missing token'); } if (!new_password) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'Missing password'); + throw new ParseError(ParseError.PASSWORD_MISSING, 'Missing password'); } return config.userController @@ -214,7 +214,7 @@ export class PublicAPIRouter extends PromiseRouter { }); } if (result.err) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, `${result.err}`); + throw new ParseError(ParseError.OTHER_CAUSE, `${result.err}`); } } diff --git a/src/Routers/PurgeRouter.js b/src/Routers/PurgeRouter.js index 3195d134af..a3716e156c 100644 --- a/src/Routers/PurgeRouter.js +++ b/src/Routers/PurgeRouter.js @@ -1,12 +1,12 @@ import PromiseRouter from '../PromiseRouter'; import * as middleware from '../middlewares'; -import Parse from 'parse/node'; +import ParseError from '../ParseError'; export class PurgeRouter extends PromiseRouter { handlePurge(req) { if (req.auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to purge a schema." ); } @@ -22,7 +22,7 @@ export class PurgeRouter extends PromiseRouter { return { response: {} }; }) .catch(error => { - if (!error || (error && error.code === Parse.Error.OBJECT_NOT_FOUND)) { + if (!error || (error && error.code === ParseError.OBJECT_NOT_FOUND)) { return { response: {} }; } throw error; diff --git a/src/Routers/PushRouter.js b/src/Routers/PushRouter.js index 1c1c8f3b5f..c73e1c9a8e 100644 --- a/src/Routers/PushRouter.js +++ b/src/Routers/PushRouter.js @@ -1,6 +1,6 @@ import PromiseRouter from '../PromiseRouter'; import * as middleware from '../middlewares'; -import { Parse } from 'parse/node'; +import ParseError from '../ParseError'; export class PushRouter extends PromiseRouter { mountRoutes() { @@ -9,14 +9,14 @@ export class PushRouter extends PromiseRouter { static handlePOST(req) { if (req.auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to send push notifications." ); } const pushController = req.config.pushController; if (!pushController) { - throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Push controller is not set'); + throw new ParseError(ParseError.PUSH_MISCONFIGURED, 'Push controller is not set'); } const where = PushRouter.getQueryCondition(req); @@ -58,8 +58,8 @@ export class PushRouter extends PromiseRouter { let where; if (hasWhere && hasChannels) { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, 'Channels and query can not be set at the same time.' ); } else if (hasWhere) { @@ -71,8 +71,8 @@ export class PushRouter extends PromiseRouter { }, }; } else { - throw new Parse.Error( - Parse.Error.PUSH_MISCONFIGURED, + throw new ParseError( + ParseError.PUSH_MISCONFIGURED, 'Sending a push requires either "channels" or a "where" query.' ); } diff --git a/src/Routers/SchemasRouter.js b/src/Routers/SchemasRouter.js index 0a42123af7..f3bd455d1d 100644 --- a/src/Routers/SchemasRouter.js +++ b/src/Routers/SchemasRouter.js @@ -1,14 +1,14 @@ // schemas.js -var Parse = require('parse/node').Parse, - SchemaController = require('../Controllers/SchemaController'); +const SchemaController = require('../Controllers/SchemaController'); import PromiseRouter from '../PromiseRouter'; import * as middleware from '../middlewares'; +import ParseError from '../ParseError'; function classNameMismatchResponse(bodyClass, pathClass) { - throw new Parse.Error( - Parse.Error.INVALID_CLASS_NAME, + throw new ParseError( + ParseError.INVALID_CLASS_NAME, `Class name mismatch between ${bodyClass} and ${pathClass}.` ); } @@ -28,17 +28,17 @@ function getOneSchema(req) { .then(schema => ({ response: schema })) .catch(error => { if (error === undefined) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`); + throw new ParseError(ParseError.INVALID_CLASS_NAME, `Class ${className} does not exist.`); } else { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.'); + throw new ParseError(ParseError.INTERNAL_SERVER_ERROR, 'Database adapter error.'); } }); } const checkIfDefinedSchemasIsUsed = req => { if (req.config?.schema?.lockSchemas === true) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, 'Cannot perform this operation when schemas options is used.' ); } @@ -72,8 +72,8 @@ export const internalUpdateSchema = async (className, body, config) => { async function createSchema(req) { checkIfDefinedSchemasIsUsed(req); if (req.auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to create a schema." ); } @@ -85,7 +85,7 @@ async function createSchema(req) { const className = req.params.className || req.body?.className; if (!className) { - throw new Parse.Error(135, `POST ${req.path} needs a class name.`); + throw new ParseError(135, `POST ${req.path} needs a class name.`); } return await internalCreateSchema(className, req.body || {}, req.config); @@ -94,8 +94,8 @@ async function createSchema(req) { function modifySchema(req) { checkIfDefinedSchemasIsUsed(req); if (req.auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to update a schema." ); } @@ -109,14 +109,14 @@ function modifySchema(req) { const deleteSchema = req => { if (req.auth.isReadOnly) { - throw new Parse.Error( - Parse.Error.OPERATION_FORBIDDEN, + throw new ParseError( + ParseError.OPERATION_FORBIDDEN, "read-only masterKey isn't allowed to delete a schema." ); } if (!SchemaController.classNameIsValid(req.params.className)) { - throw new Parse.Error( - Parse.Error.INVALID_CLASS_NAME, + throw new ParseError( + ParseError.INVALID_CLASS_NAME, SchemaController.invalidClassNameMessage(req.params.className) ); } diff --git a/src/Routers/SessionsRouter.js b/src/Routers/SessionsRouter.js index e5275fae06..f543d20be7 100644 --- a/src/Routers/SessionsRouter.js +++ b/src/Routers/SessionsRouter.js @@ -1,5 +1,5 @@ import ClassesRouter from './ClassesRouter'; -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import rest from '../rest'; import Auth from '../Auth'; import RestWrite from '../RestWrite'; @@ -12,7 +12,7 @@ export class SessionsRouter extends ClassesRouter { handleMe(req) { // TODO: Verify correct behavior if (!req.info || !req.info.sessionToken) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Session token required.'); } return rest .find( @@ -26,7 +26,7 @@ export class SessionsRouter extends ClassesRouter { ) .then(response => { if (!response.results || response.results.length == 0) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token not found.'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Session token not found.'); } return { response: response.results[0], @@ -40,7 +40,7 @@ export class SessionsRouter extends ClassesRouter { // Issue #2720 // Calling without a session token would result in a not found user if (!user) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'invalid session'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'invalid session'); } const { sessionData, createSession } = RestWrite.createSession(config, { userId: user.id, diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index 7668562965..b47459724a 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -1,6 +1,6 @@ // These methods handle the User-related routes. -import Parse from 'parse/node'; +import ParseError from '../ParseError'; import Config from '../Config'; import AccountLockout from '../AccountLockout'; import ClassesRouter from './ClassesRouter'; @@ -12,10 +12,13 @@ import { Types as TriggerTypes, getRequestObject, resolveError, + triggerExists, } from '../triggers'; import { promiseEnsureIdempotency } from '../middlewares'; import RestWrite from '../RestWrite'; import { logger } from '../logger'; +import { encodeDate } from '../Utils'; +import { loadModule } from '../Adapters/AdapterLoader'; export class UsersRouter extends ClassesRouter { className() { @@ -79,17 +82,17 @@ export class UsersRouter extends ClassesRouter { // TODO: use the right error codes / descriptions. if (!username && !email) { - throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'username/email is required.'); + throw new ParseError(ParseError.USERNAME_MISSING, 'username/email is required.'); } if (!password) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required.'); + throw new ParseError(ParseError.PASSWORD_MISSING, 'password is required.'); } if ( typeof password !== 'string' || (email && typeof email !== 'string') || (username && typeof username !== 'string') ) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Invalid username/password.'); } let user; @@ -106,7 +109,7 @@ export class UsersRouter extends ClassesRouter { .find('_User', query, {}, Auth.maintenance(req.config)) .then(results => { if (!results.length) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Invalid username/password.'); } if (results.length > 1) { @@ -128,33 +131,33 @@ export class UsersRouter extends ClassesRouter { }) .then(async () => { if (!isValidPassword) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Invalid username/password.'); } // Ensure the user isn't locked out // A locked out user won't be able to login // To lock a user out, just set the ACL to `masterKey` only ({}). // Empty ACL is OK if (!req.auth.isMaster && user.ACL && Object.keys(user.ACL).length == 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Invalid username/password.'); } - // Create request object for verification functions - const request = { - master: req.auth.isMaster, - ip: req.config.ip, - installationId: req.auth.installationId, - object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)), - }; // If request doesn't use master or maintenance key with ignoring email verification if (!((req.auth.isMaster || req.auth.isMaintenance) && ignoreEmailVerification)) { - + const Parse = await loadModule('parse/node.js'); + // Create request object for verification functions + const request = { + master: req.auth.isMaster, + ip: req.config.ip, + installationId: req.auth.installationId, + object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)), + }; // Get verification conditions which can be booleans or functions; the purpose of this async/await // structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the // conditional statement below, as a developer may decide to execute expensive operations in them const verifyUserEmails = async () => req.config.verifyUserEmails === true || (typeof req.config.verifyUserEmails === 'function' && await Promise.resolve(req.config.verifyUserEmails(request)) === true); const preventLoginWithUnverifiedEmail = async () => req.config.preventLoginWithUnverifiedEmail === true || (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request)) === true); if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail() && !user.emailVerified) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); + throw new ParseError(ParseError.EMAIL_NOT_FOUND, 'User email is not verified.'); } } @@ -170,7 +173,7 @@ export class UsersRouter extends ClassesRouter { handleMe(req) { if (!req.info || !req.info.sessionToken) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token'); } const sessionToken = req.info.sessionToken; return rest @@ -185,7 +188,7 @@ export class UsersRouter extends ClassesRouter { ) .then(response => { if (!response.results || response.results.length == 0 || !response.results[0].user) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token'); } else { const user = response.results[0].user; // Send token back on the login, because SDKs expect that. @@ -241,7 +244,7 @@ export class UsersRouter extends ClassesRouter { req.config.database.update( '_User', { username: user.username }, - { _password_changed_at: Parse._encode(changedAt) } + { _password_changed_at: encodeDate(changedAt) } ); } else { // check whether the password has expired @@ -254,8 +257,8 @@ export class UsersRouter extends ClassesRouter { ); if (expiresAt < new Date()) // fail of current time is past password expiry time - { throw new Parse.Error( - Parse.Error.OBJECT_NOT_FOUND, + { throw new ParseError( + ParseError.OBJECT_NOT_FOUND, 'Your password has expired. Please reset your password.' ); } } @@ -267,14 +270,18 @@ export class UsersRouter extends ClassesRouter { await req.config.filesController.expandFilesInObject(req.config, user); // Before login trigger; throws if failure - await maybeRunTrigger( - TriggerTypes.beforeLogin, - req.auth, - Parse.User.fromJSON(Object.assign({ className: '_User' }, user)), - null, - req.config, - req.info.context - ); + const hasBeforeLoginTrigger = triggerExists('_User', TriggerTypes.beforeLogin, req.config.applicationId); + if (hasBeforeLoginTrigger) { + const Parse = await loadModule('parse/node.js'); + await maybeRunTrigger( + TriggerTypes.beforeLogin, + req.auth, + Parse.User.fromJSON(Object.assign({ className: '_User' }, user)), + null, + req.config, + req.info.context + ); + } // If we have some new validated authData update directly if (validatedAuthData && Object.keys(validatedAuthData).length) { @@ -299,15 +306,19 @@ export class UsersRouter extends ClassesRouter { await createSession(); - const afterLoginUser = Parse.User.fromJSON(Object.assign({ className: '_User' }, user)); - await maybeRunTrigger( - TriggerTypes.afterLogin, - { ...req.auth, user: afterLoginUser }, - afterLoginUser, - null, - req.config, - req.info.context - ); + const hasAfterLoginTrigger = triggerExists('_User', TriggerTypes.afterLogin, req.config.applicationId); + if (hasAfterLoginTrigger) { + const Parse = await loadModule('parse/node.js'); + const afterLoginUser = Parse.User.fromJSON(Object.assign({ className: '_User' }, user)); + await maybeRunTrigger( + TriggerTypes.afterLogin, + { ...req.auth, user: afterLoginUser }, + afterLoginUser, + null, + req.config, + req.info.context + ); + } if (authDataResponse) { user.authDataResponse = authDataResponse; @@ -333,13 +344,13 @@ export class UsersRouter extends ClassesRouter { */ async handleLogInAs(req) { if (!req.auth.isMaster) { - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'master key is required'); + throw new ParseError(ParseError.OPERATION_FORBIDDEN, 'master key is required'); } const userId = req.body?.userId || req.query.userId; if (!userId) { - throw new Parse.Error( - Parse.Error.INVALID_VALUE, + throw new ParseError( + ParseError.INVALID_VALUE, 'userId must not be empty, null, or undefined' ); } @@ -347,7 +358,7 @@ export class UsersRouter extends ClassesRouter { const queryResults = await req.config.database.find('_User', { objectId: userId }); const user = queryResults[0]; if (!user) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'user not found'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'user not found'); } this._sanitizeAuthData(user); @@ -401,13 +412,17 @@ export class UsersRouter extends ClassesRouter { records.results[0].objectId, req.info.context ); - await maybeRunTrigger( - TriggerTypes.afterLogout, - req.auth, - Parse.Session.fromJSON(Object.assign({ className: '_Session' }, records.results[0])), - null, - req.config - ); + const hasAfterLogoutTrigger = triggerExists('_Session', TriggerTypes.afterLogout, req.config.applicationId); + if (hasAfterLogoutTrigger) { + const Parse = await loadModule('parse/node.js'); + await maybeRunTrigger( + TriggerTypes.afterLogout, + req.auth, + Parse.Session.fromJSON(Object.assign({ className: '_Session' }, records.results[0])), + null, + req.config + ); + } } } return success; @@ -425,8 +440,8 @@ export class UsersRouter extends ClassesRouter { } catch (e) { if (typeof e === 'string') { // Maybe we need a Bad Configuration error, but the SDKs won't understand it. For now, Internal Server Error. - throw new Parse.Error( - Parse.Error.INTERNAL_SERVER_ERROR, + throw new ParseError( + ParseError.INTERNAL_SERVER_ERROR, 'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.' ); } else { @@ -442,20 +457,20 @@ export class UsersRouter extends ClassesRouter { const token = req.body?.token; if (!email && !token) { - throw new Parse.Error(Parse.Error.EMAIL_MISSING, 'you must provide an email'); + throw new ParseError(ParseError.EMAIL_MISSING, 'you must provide an email'); } if (token) { const results = await req.config.database.find('_User', { _perishable_token: token, - _perishable_token_expires_at: { $lt: Parse._encode(new Date()) }, + _perishable_token_expires_at: { $lt: encodeDate(new Date()) }, }); if (results && results[0] && results[0].email) { email = results[0].email; } } if (typeof email !== 'string') { - throw new Parse.Error( - Parse.Error.INVALID_EMAIL_ADDRESS, + throw new ParseError( + ParseError.INVALID_EMAIL_ADDRESS, 'you must provide a valid email string' ); } @@ -466,7 +481,7 @@ export class UsersRouter extends ClassesRouter { response: {}, }; } catch (err) { - if (err.code === Parse.Error.OBJECT_NOT_FOUND) { + if (err.code === ParseError.OBJECT_NOT_FOUND) { if (req.config.passwordPolicy?.resetPasswordSuccessOnInvalidEmail ?? true) { return { response: {}, @@ -483,18 +498,18 @@ export class UsersRouter extends ClassesRouter { const { email } = req.body || {}; if (!email) { - throw new Parse.Error(Parse.Error.EMAIL_MISSING, 'you must provide an email'); + throw new ParseError(ParseError.EMAIL_MISSING, 'you must provide an email'); } if (typeof email !== 'string') { - throw new Parse.Error( - Parse.Error.INVALID_EMAIL_ADDRESS, + throw new ParseError( + ParseError.INVALID_EMAIL_ADDRESS, 'you must provide a valid email string' ); } const results = await req.config.database.find('_User', { email: email }, {}, Auth.maintenance(req.config)); if (!results.length || results.length < 1) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}`); + throw new ParseError(ParseError.EMAIL_NOT_FOUND, `No user found with email ${email}`); } const user = results[0]; @@ -502,7 +517,7 @@ export class UsersRouter extends ClassesRouter { delete user.password; if (user.emailVerified) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, `Email ${email} is already verified.`); + throw new ParseError(ParseError.OTHER_CAUSE, `Email ${email} is already verified.`); } const userController = req.config.userController; @@ -520,8 +535,8 @@ export class UsersRouter extends ClassesRouter { let user; if (username || email) { if (!password) { - throw new Parse.Error( - Parse.Error.OTHER_CAUSE, + throw new ParseError( + ParseError.OTHER_CAUSE, 'You provided username or email, you need to also provide password.' ); } @@ -529,11 +544,11 @@ export class UsersRouter extends ClassesRouter { } if (!challengeData) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'Nothing to challenge.'); + throw new ParseError(ParseError.OTHER_CAUSE, 'Nothing to challenge.'); } if (typeof challengeData !== 'object') { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'challengeData should be an object.'); + throw new ParseError(ParseError.OTHER_CAUSE, 'challengeData should be an object.'); } let request; @@ -542,18 +557,18 @@ export class UsersRouter extends ClassesRouter { // Try to find user by authData if (authData) { if (typeof authData !== 'object') { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'authData should be an object.'); + throw new ParseError(ParseError.OTHER_CAUSE, 'authData should be an object.'); } if (user) { - throw new Parse.Error( - Parse.Error.OTHER_CAUSE, + throw new ParseError( + ParseError.OTHER_CAUSE, 'You cannot provide username/email and authData, only use one identification method.' ); } if (Object.keys(authData).filter(key => authData[key].id).length > 1) { - throw new Parse.Error( - Parse.Error.OTHER_CAUSE, + throw new ParseError( + ParseError.OTHER_CAUSE, 'You cannot provide more than one authData provider with an id.' ); } @@ -562,11 +577,11 @@ export class UsersRouter extends ClassesRouter { try { if (!results[0] || results.length > 1) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'User not found.'); } // Find the provider used to find the user const provider = Object.keys(authData).find(key => authData[key].id); - + const Parse = await loadModule('parse/node.js'); parseUser = Parse.User.fromJSON({ className: '_User', ...results[0] }); request = getRequestObject(undefined, req.auth, parseUser, parseUser, req.config); request.isChallenge = true; @@ -579,12 +594,13 @@ export class UsersRouter extends ClassesRouter { } catch (e) { // Rewrite the error to avoid guess id attack logger.error(e); - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'User not found.'); } } - if (!parseUser) { - parseUser = user ? Parse.User.fromJSON({ className: '_User', ...user }) : undefined; + if (!parseUser && user) { + const Parse = await loadModule('parse/node.js'); + parseUser = Parse.User.fromJSON({ className: '_User', ...user }); } if (!request) { @@ -614,7 +630,7 @@ export class UsersRouter extends ClassesRouter { } } catch (err) { const e = resolveError(err, { - code: Parse.Error.SCRIPT_FAILED, + code: ParseError.SCRIPT_FAILED, message: 'Challenge failed. Unknown error.', }); const userString = req.auth && req.auth.user ? req.auth.user.id : undefined; diff --git a/src/SchemaMigrations/DefinedSchemas.js b/src/SchemaMigrations/DefinedSchemas.js index cf2b1761f4..6a1334777f 100644 --- a/src/SchemaMigrations/DefinedSchemas.js +++ b/src/SchemaMigrations/DefinedSchemas.js @@ -1,6 +1,5 @@ // @flow -// @flow-disable-next Cannot resolve module `parse/node`. -const Parse = require('parse/node'); +import * as Parse from '../ClientSDK'; import { logger } from '../logger'; import Config from '../Config'; import { internalCreateSchema, internalUpdateSchema } from '../Routers/SchemasRouter'; @@ -380,14 +379,14 @@ export class DefinedSchemas { handleCLP( localSchema: Migrations.JSONSchema, - newLocalSchema: Parse.Schema, - cloudSchema: Parse.Schema + newLocalSchema: Migrations.JSONSchema, + cloudSchema: Migrations.JSONSchema ) { if (!localSchema.classLevelPermissions && !cloudSchema) { logger.warn(`classLevelPermissions not provided for ${localSchema.className}.`); } // Use spread to avoid read only issue (encountered by Moumouls using directAccess) - const clp = ({ ...localSchema.classLevelPermissions || {} }: Parse.CLP.PermissionsMap); + const clp = ({ ...localSchema.classLevelPermissions || {} }: any); // To avoid inconsistency we need to remove all rights on addField clp.addField = {}; newLocalSchema.setCLP(clp); diff --git a/src/SharedRest.js b/src/SharedRest.js index 0b4a07c320..b333a38c13 100644 --- a/src/SharedRest.js +++ b/src/SharedRest.js @@ -1,3 +1,5 @@ +import ParseError from './ParseError'; + const classesWithMasterOnlyAccess = [ '_JobStatus', '_PushStatus', @@ -11,7 +13,7 @@ function enforceRoleSecurity(method, className, auth) { if (className === '_Installation' && !auth.isMaster && !auth.isMaintenance) { if (method === 'delete' || method === 'find') { const error = `Clients aren't allowed to perform the ${method} operation on the installation collection.`; - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error); + throw new ParseError(ParseError.OPERATION_FORBIDDEN, error); } } @@ -22,13 +24,13 @@ function enforceRoleSecurity(method, className, auth) { !auth.isMaintenance ) { const error = `Clients aren't allowed to perform the ${method} operation on the ${className} collection.`; - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error); + throw new ParseError(ParseError.OPERATION_FORBIDDEN, error); } // readOnly masterKey is not allowed if (auth.isReadOnly && (method === 'delete' || method === 'create' || method === 'update')) { const error = `read-only masterKey isn't allowed to perform the ${method} operation.`; - throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error); + throw new ParseError(ParseError.OPERATION_FORBIDDEN, error); } } diff --git a/src/Utils.js b/src/Utils.js index 72b49aeeb2..a88ac4e15d 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -410,6 +410,97 @@ class Utils { '%' + char.charCodeAt(0).toString(16).toUpperCase() ); } + + static encodeDate(value) { + if (Object.prototype.toString.call(value) === '[object Date]') { + if (isNaN(value)) { + throw new Error('Tried to encode an invalid date.'); + } + return { __type: 'Date', iso: value.toJSON() }; + } + } + + static validateGeoPoint(latitude, longitude) { + if ( + isNaN(latitude) || + isNaN(longitude) || + typeof latitude !== 'number' || + typeof longitude !== 'number' + ) { + throw new TypeError('GeoPoint latitude and longitude must be valid numbers'); + } + if (latitude < -90.0) { + throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' < -90.0.'); + } + if (latitude > 90.0) { + throw new TypeError('GeoPoint latitude out of bounds: ' + latitude + ' > 90.0.'); + } + if (longitude < -180.0) { + throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' < -180.0.'); + } + if (longitude > 180.0) { + throw new TypeError('GeoPoint longitude out of bounds: ' + longitude + ' > 180.0.'); + } + } + + static containsPoint(points, point) { + let minX = points[0][0]; + let maxX = points[0][0]; + let minY = points[0][1]; + let maxY = points[0][1]; + + for (let i = 1; i < points.length; i += 1) { + const p = points[i]; + minX = Math.min(p[0], minX); + maxX = Math.max(p[0], maxX); + minY = Math.min(p[1], minY); + maxY = Math.max(p[1], maxY); + } + + const outside = + point.latitude < minX || + point.latitude > maxX || + point.longitude < minY || + point.longitude > maxY; + if (outside) { + return false; + } + + let inside = false; + for (let i = 0, j = points.length - 1; i < points.length; j = i++) { + const startX = points[i][0]; + const startY = points[i][1]; + const endX = points[j][0]; + const endY = points[j][1]; + + const intersect = + startY > point.longitude != endY > point.longitude && + point.latitude < ((endX - startX) * (point.longitude - startY)) / (endY - startY) + startX; + + if (intersect) { + inside = !inside; + } + } + return inside; + } + + static radiansTo(pointA, pointB) { + const d2r = Math.PI / 180.0; + const lat1rad = pointA.latitude * d2r; + const long1rad = pointA.longitude * d2r; + const lat2rad = pointB.latitude * d2r; + const long2rad = pointB.longitude * d2r; + const deltaLat = lat1rad - lat2rad; + const deltaLong = long1rad - long2rad; + const sinDeltaLatDiv2 = Math.sin(deltaLat / 2); + const sinDeltaLongDiv2 = Math.sin(deltaLong / 2); + // Square of half the straight line chord distance between both points. + let a = + sinDeltaLatDiv2 * sinDeltaLatDiv2 + + Math.cos(lat1rad) * Math.cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2; + a = Math.min(1.0, a); + return 2 * Math.asin(Math.sqrt(a)); + } } module.exports = Utils; diff --git a/src/batch.js b/src/batch.js index 80fa028cc6..883270bccb 100644 --- a/src/batch.js +++ b/src/batch.js @@ -1,4 +1,4 @@ -const Parse = require('parse/node').Parse; +import ParseError from './ParseError'; const path = require('path'); // These methods handle batch requests. const batchPath = '/batch'; @@ -28,7 +28,7 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) { const makeRoutablePath = function (requestPath) { // The routablePath is the path minus the api prefix if (requestPath.slice(0, apiPrefix.length) != apiPrefix) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'cannot route batch path ' + requestPath); + throw new ParseError(ParseError.INVALID_JSON, 'cannot route batch path ' + requestPath); } return path.posix.join('/', requestPath.slice(apiPrefix.length)); }; @@ -65,7 +65,7 @@ function makeBatchRoutingPathFunction(originalUrl, serverURL, publicServerURL) { // TODO: pass along auth correctly function handleBatch(router, req) { if (!Array.isArray(req.body?.requests)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'requests must be an array'); + throw new ParseError(ParseError.INVALID_JSON, 'requests must be an array'); } // The batch paths are all from the root of our domain. diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 3f33e5100d..9d2af772e9 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -1,4 +1,4 @@ -import { Parse } from 'parse/node'; +import Parse from 'parse/node'; import * as triggers from '../triggers'; import { addRateLimit } from '../middlewares'; const Config = require('../Config'); diff --git a/src/middlewares.js b/src/middlewares.js index bf8029844a..ad1916427c 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -1,5 +1,5 @@ import AppCache from './cache'; -import Parse from 'parse/node'; +import ParseError from './ParseError'; import auth from './Auth'; import Config from './Config'; import ClientSDK from './ClientSDK'; @@ -8,11 +8,13 @@ import rest from './rest'; import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter'; import PostgresStorageAdapter from './Adapters/Storage/Postgres/PostgresStorageAdapter'; import rateLimit from 'express-rate-limit'; +import { RateLimitZone } from './cloud-code/Parse.Server'; import { RateLimitOptions } from './Options/Definitions'; import { pathToRegexp } from 'path-to-regexp'; import RedisStore from 'rate-limit-redis'; import { createClient } from 'redis'; import { BlockList, isIPv4 } from 'net'; +import { encodeDate } from './Utils'; export const DEFAULT_ALLOWED_HEADERS = 'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control'; @@ -208,7 +210,7 @@ export async function handleParseHeaders(req, res, next) { if (config.state && config.state !== 'ok') { res.status(500); res.json({ - code: Parse.Error.INTERNAL_SERVER_ERROR, + code: ParseError.INTERNAL_SERVER_ERROR, error: `Invalid server state: ${config.state}`, }); return; @@ -324,7 +326,7 @@ const handleRateLimit = async (req, res, next) => { if (pathExp.test(req.url)) { await limit.handler(req, res, err => { if (err) { - if (err.code === Parse.Error.CONNECTION_FAILED) { + if (err.code === ParseError.CONNECTION_FAILED) { throw err; } req.config.loggerController.error( @@ -338,7 +340,7 @@ const handleRateLimit = async (req, res, next) => { ); } catch (error) { res.status(429); - res.json({ code: Parse.Error.CONNECTION_FAILED, error: error.message }); + res.json({ code: ParseError.CONNECTION_FAILED, error: error.message }); return; } next(); @@ -372,13 +374,13 @@ export const handleParseSession = async (req, res, next) => { req.auth = requestAuth; next(); } catch (error) { - if (error instanceof Parse.Error) { + if (error instanceof ParseError) { next(error); return; } // TODO: Determine the correct error scenario. req.config.loggerController.error('error getting auth for sessionToken', error); - throw new Parse.Error(Parse.Error.UNKNOWN_ERROR, error); + throw new ParseError(ParseError.UNKNOWN_ERROR, error); } }; @@ -460,17 +462,17 @@ export function allowMethodOverride(req, res, next) { export function handleParseErrors(err, req, res, next) { const log = (req.config && req.config.loggerController) || defaultLogger; - if (err instanceof Parse.Error) { + if (err instanceof ParseError) { if (req.config && req.config.enableExpressErrorHandler) { return next(err); } let httpStatus; // TODO: fill out this mapping switch (err.code) { - case Parse.Error.INTERNAL_SERVER_ERROR: + case ParseError.INTERNAL_SERVER_ERROR: httpStatus = 500; break; - case Parse.Error.OBJECT_NOT_FOUND: + case ParseError.OBJECT_NOT_FOUND: httpStatus = 404; break; default: @@ -489,7 +491,7 @@ export function handleParseErrors(err, req, res, next) { log.error('Uncaught internal server error.', err, err.stack); res.status(500); res.json({ - code: Parse.Error.INTERNAL_SERVER_ERROR, + code: ParseError.INTERNAL_SERVER_ERROR, message: 'Internal server error.', }); if (!(process && process.env.TESTING)) { @@ -572,7 +574,7 @@ export const addRateLimit = (route, config, cloud) => { message: route.errorResponseMessage || RateLimitOptions.errorResponseMessage.default, handler: (request, response, next, options) => { throw { - code: Parse.Error.CONNECTION_FAILED, + code: ParseError.CONNECTION_FAILED, message: options.message, }; }, @@ -598,14 +600,14 @@ export const addRateLimit = (route, config, cloud) => { return request.auth?.isMaster; }, keyGenerator: async request => { - if (route.zone === Parse.Server.RateLimitZone.global) { + if (route.zone === RateLimitZone.global) { return request.config.appId; } const token = request.info.sessionToken; - if (route.zone === Parse.Server.RateLimitZone.session && token) { + if (route.zone === RateLimitZone.session && token) { return token; } - if (route.zone === Parse.Server.RateLimitZone.user && token) { + if (route.zone === RateLimitZone.user && token) { if (!request.auth) { await new Promise(resolve => handleParseSession(request, null, resolve)); } @@ -666,11 +668,11 @@ export function promiseEnsureIdempotency(req) { return rest .create(config, auth.master(config), '_Idempotency', { reqId: requestId, - expire: Parse._encode(expiryDate), + expire: encodeDate(expiryDate), }) .catch(e => { - if (e.code == Parse.Error.DUPLICATE_VALUE) { - throw new Parse.Error(Parse.Error.DUPLICATE_REQUEST, 'Duplicate request'); + if (e.code == ParseError.DUPLICATE_VALUE) { + throw new ParseError(ParseError.DUPLICATE_REQUEST, 'Duplicate request'); } throw e; }); @@ -683,7 +685,7 @@ function invalidRequest(req, res) { function malformedContext(req, res) { res.status(400); - res.json({ code: Parse.Error.INVALID_JSON, error: 'Invalid object for context.' }); + res.json({ code: ParseError.INVALID_JSON, error: 'Invalid object for context.' }); } /** diff --git a/src/rest.js b/src/rest.js index 1f9dbacb73..f25fe4dda5 100644 --- a/src/rest.js +++ b/src/rest.js @@ -7,8 +7,8 @@ // routes. That's useful for the routes that do really similar // things. -var Parse = require('parse/node').Parse; - +import Parse from 'parse/node'; +import ParseError from './ParseError'; var RestQuery = require('./RestQuery'); var RestWrite = require('./RestWrite'); var triggers = require('./triggers'); @@ -58,11 +58,11 @@ const get = async (config, auth, className, objectId, restOptions, clientSDK, co // Returns a promise that doesn't resolve to any useful value. function del(config, auth, className, objectId, context) { if (typeof objectId !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad objectId'); + throw new ParseError(ParseError.INVALID_JSON, 'bad objectId'); } if (className === '_User' && auth.isUnauthenticated()) { - throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Insufficient auth to delete user'); + throw new ParseError(ParseError.SESSION_MISSING, 'Insufficient auth to delete user'); } enforceRoleSecurity('delete', className, auth); @@ -88,7 +88,7 @@ function del(config, auth, className, objectId, context) { firstResult.className = className; if (className === '_Session' && !auth.isMaster && !auth.isMaintenance) { if (!auth.user || firstResult.user.objectId !== auth.user.id) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token'); + throw new ParseError(ParseError.INVALID_SESSION_TOKEN, 'Invalid session token'); } } var cacheAdapter = config.cacheController; @@ -103,7 +103,7 @@ function del(config, auth, className, objectId, context) { context ); } - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found for delete.'); + throw new ParseError(ParseError.OBJECT_NOT_FOUND, 'Object not found for delete.'); }); } return Promise.resolve({}); @@ -215,11 +215,11 @@ function handleSessionMissingError(error, className, auth) { // If we're trying to update a user without / with bad session token if ( className === '_User' && - error.code === Parse.Error.OBJECT_NOT_FOUND && + error.code === ParseError.OBJECT_NOT_FOUND && !auth.isMaster && !auth.isMaintenance ) { - throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Insufficient auth.'); + throw new ParseError(ParseError.SESSION_MISSING, 'Insufficient auth.'); } throw error; } diff --git a/src/triggers.js b/src/triggers.js index 2dfbeff7ac..bc5735ca26 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -1,5 +1,6 @@ // triggers.js import Parse from 'parse/node'; +import ParseError from './ParseError'; import { logger } from './logger'; export const Types = { @@ -369,7 +370,7 @@ export function getResponseObject(request, resolve, reject) { }, error: function (error) { const e = resolveError(error, { - code: Parse.Error.SCRIPT_FAILED, + code: ParseError.SCRIPT_FAILED, message: 'Script failed. Unknown error.', }); reject(e); @@ -614,7 +615,7 @@ export function maybeRunQueryTrigger( }, err => { const error = resolveError(err, { - code: Parse.Error.SCRIPT_FAILED, + code: ParseError.SCRIPT_FAILED, message: 'Script failed. Unknown error.', }); throw error; @@ -627,21 +628,21 @@ export function resolveError(message, defaultOpts) { defaultOpts = {}; } if (!message) { - return new Parse.Error( - defaultOpts.code || Parse.Error.SCRIPT_FAILED, + return new ParseError( + defaultOpts.code || ParseError.SCRIPT_FAILED, defaultOpts.message || 'Script failed.' ); } - if (message instanceof Parse.Error) { + if (message instanceof ParseError) { return message; } - const code = defaultOpts.code || Parse.Error.SCRIPT_FAILED; + const code = defaultOpts.code || ParseError.SCRIPT_FAILED; // If it's an error, mark it as a script failed if (typeof message === 'string') { - return new Parse.Error(code, message); + return new ParseError(code, message); } - const error = new Parse.Error(code, message.message || message); + const error = new ParseError(code, message.message || message); if (message instanceof Error) { error.stack = message.stack; } @@ -667,7 +668,7 @@ export function maybeRunValidator(request, functionName, auth) { }) .catch(e => { const error = resolveError(e, { - code: Parse.Error.VALIDATION_ERROR, + code: ParseError.VALIDATION_ERROR, message: 'Validation failed.', }); reject(error);