diff --git a/spec/AccountLockoutPolicy.spec.js b/spec/AccountLockoutPolicy.spec.js index 43212d0e69..4d598417d5 100644 --- a/spec/AccountLockoutPolicy.spec.js +++ b/spec/AccountLockoutPolicy.spec.js @@ -3,13 +3,14 @@ const Config = require('../lib/Config'); const Definitions = require('../lib/Options/Definitions'); const request = require('../lib/request'); +const { ErrorMessage } = require('../lib/Errors/message'); const loginWithWrongCredentialsShouldFail = function (username, password) { return new Promise((resolve, reject) => { Parse.User.logIn(username, password) .then(() => reject('login should have failed')) .catch(err => { - if (err.message === 'Invalid username/password.') { + if (err.message === ErrorMessage.invalid('username/password')) { resolve(); } else { reject(err); @@ -24,12 +25,7 @@ const isAccountLockoutError = function (username, password, duration, waitTime) Parse.User.logIn(username, password) .then(() => reject('login should have failed')) .catch(err => { - if ( - err.message === - 'Your account is locked due to multiple failed login attempts. Please try again after ' + - duration + - ' minute(s)' - ) { + if (err.message === ErrorMessage.accountLocked(duration)) { resolve(); } else { reject(err); diff --git a/spec/AuthenticationAdapters.spec.js b/spec/AuthenticationAdapters.spec.js index 89eeb51231..d723c3f81c 100644 --- a/spec/AuthenticationAdapters.spec.js +++ b/spec/AuthenticationAdapters.spec.js @@ -250,7 +250,7 @@ describe('AuthenticationProviders', function () { }) .then(fail, ({ data }) => { expect(data.code).toBe(208); - expect(data.error).toBe('this auth is already used'); + expect(data.error).toBe('This auth is already used.'); done(); }) .catch(done.fail); diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index 50b626de0d..b9c2b964a2 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -2,6 +2,7 @@ const Config = require('../lib/Config'); const request = require('../lib/request'); +const { ErrorMessage } = require('../lib/Errors/message'); describe('Email Verification Token Expiration: ', () => { it('show the invalid verification link page, if the user clicks on the verify email link after the email verify token expires', done => { @@ -848,7 +849,7 @@ describe('Email Verification Token Expiration: ', () => { .then(response => { expect(response.status).toBe(400); expect(response.data.code).toBe(Parse.Error.EMAIL_MISSING); - expect(response.data.error).toBe('you must provide an email'); + expect(response.data.error).toBe(ErrorMessage.required('email')); expect(sendVerificationEmailCallCount).toBe(0); expect(sendEmailOptions).not.toBeDefined(); done(); diff --git a/spec/ParseGraphQLServer.spec.js b/spec/ParseGraphQLServer.spec.js index d204886fe5..94ede6c8ce 100644 --- a/spec/ParseGraphQLServer.spec.js +++ b/spec/ParseGraphQLServer.spec.js @@ -4188,7 +4188,7 @@ describe('ParseGraphQLServer', () => { .slice(0, 3) .map(obj => expectAsync(getObject(obj.className, obj.id)).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ) ) ); @@ -4232,7 +4232,7 @@ describe('ParseGraphQLServer', () => { getObject(object2.className, object2.id, { 'X-Parse-Session-Token': user3.getSessionToken(), }) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await Promise.all( [object1, object3, object4].map(async obj => expect( @@ -4250,7 +4250,7 @@ describe('ParseGraphQLServer', () => { getObject(obj.className, obj.id, { 'X-Parse-Session-Token': user4.getSessionToken(), }) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')) + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')) ) ); expect( @@ -4266,7 +4266,7 @@ describe('ParseGraphQLServer', () => { getObject(obj.className, obj.id, { 'X-Parse-Session-Token': user5.getSessionToken(), }) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')) + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')) ) ); expect( @@ -6036,7 +6036,7 @@ describe('ParseGraphQLServer', () => { updateObject(obj.className, obj.id, { someField: 'changedValue1', }) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); }) @@ -6122,7 +6122,7 @@ describe('ParseGraphQLServer', () => { { someField: 'changedValue5' }, { 'X-Parse-Session-Token': user3.getSessionToken() } ) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await object2.fetch({ useMasterKey: true }); expect(object2.get('someField')).toEqual(originalFieldValue); await Promise.all( @@ -6135,7 +6135,7 @@ describe('ParseGraphQLServer', () => { { someField: 'changedValue6' }, { 'X-Parse-Session-Token': user4.getSessionToken() } ) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); }) @@ -6162,7 +6162,7 @@ describe('ParseGraphQLServer', () => { { someField: 'changedValue7' }, { 'X-Parse-Session-Token': user5.getSessionToken() } ) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); }) @@ -6234,7 +6234,7 @@ describe('ParseGraphQLServer', () => { updateObject(obj.className, obj.id, { someField: 'changedValue1', }) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); }) @@ -6330,7 +6330,7 @@ describe('ParseGraphQLServer', () => { { someField: 'changedValue5' }, { 'X-Parse-Session-Token': user3.getSessionToken() } ) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await object2.fetch({ useMasterKey: true }); expect(object2.get('someField')).toEqual(originalFieldValue); await Promise.all( @@ -6343,7 +6343,7 @@ describe('ParseGraphQLServer', () => { { someField: 'changedValue6' }, { 'X-Parse-Session-Token': user4.getSessionToken() } ) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); }) @@ -6372,7 +6372,7 @@ describe('ParseGraphQLServer', () => { { someField: 'changedValue7' }, { 'X-Parse-Session-Token': user5.getSessionToken() } ) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); }) @@ -6446,7 +6446,7 @@ describe('ParseGraphQLServer', () => { expect(result.data.deleteCustomer.customer.someField2).toEqual('someField2Value1'); await expectAsync(obj.fetch({ useMasterKey: true })).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); }); @@ -6482,7 +6482,7 @@ describe('ParseGraphQLServer', () => { objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync(deleteObject(obj.className, obj.id)).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); @@ -6495,7 +6495,7 @@ describe('ParseGraphQLServer', () => { deleteObject(obj.className, obj.id, { 'X-Parse-Session-Token': user4.getSessionToken(), }) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); }) @@ -6506,7 +6506,7 @@ describe('ParseGraphQLServer', () => { ] ).toEqual({ objectId: object4.id, __typename: 'PublicClass' }); await expectAsync(object4.fetch({ useMasterKey: true })).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); expect( ( @@ -6516,7 +6516,7 @@ describe('ParseGraphQLServer', () => { ).data.delete[object1.className.charAt(0).toLowerCase() + object1.className.slice(1)] ).toEqual({ objectId: object1.id, __typename: 'GraphQLClass' }); await expectAsync(object1.fetch({ useMasterKey: true })).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); expect( ( @@ -6526,7 +6526,7 @@ describe('ParseGraphQLServer', () => { ).data.delete[object2.className.charAt(0).toLowerCase() + object2.className.slice(1)] ).toEqual({ objectId: object2.id, __typename: 'GraphQLClass' }); await expectAsync(object2.fetch({ useMasterKey: true })).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); expect( ( @@ -6536,7 +6536,7 @@ describe('ParseGraphQLServer', () => { ).data.delete[object3.className.charAt(0).toLowerCase() + object3.className.slice(1)] ).toEqual({ objectId: object3.id, __typename: 'GraphQLClass' }); await expectAsync(object3.fetch({ useMasterKey: true })).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); }); @@ -6572,7 +6572,7 @@ describe('ParseGraphQLServer', () => { objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync(deleteObject(obj.className, obj.id)).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); @@ -6585,7 +6585,7 @@ describe('ParseGraphQLServer', () => { deleteObject(obj.className, obj.id, { 'X-Parse-Session-Token': user4.getSessionToken(), }) - ).toBeRejectedWith(jasmine.stringMatching('Object not found')); + ).toBeRejectedWith(jasmine.stringMatching('Object not found.')); await obj.fetch({ useMasterKey: true }); expect(obj.get('someField')).toEqual(originalFieldValue); }) @@ -6596,7 +6596,7 @@ describe('ParseGraphQLServer', () => { ][object4.className.charAt(0).toLowerCase() + object4.className.slice(1)].objectId ).toEqual(object4.id); await expectAsync(object4.fetch({ useMasterKey: true })).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); expect( ( @@ -6608,7 +6608,7 @@ describe('ParseGraphQLServer', () => { ].objectId ).toEqual(object1.id); await expectAsync(object1.fetch({ useMasterKey: true })).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); expect( ( @@ -6632,7 +6632,7 @@ describe('ParseGraphQLServer', () => { ].objectId ).toEqual(object3.id); await expectAsync(object3.fetch({ useMasterKey: true })).toBeRejectedWith( - jasmine.stringMatching('Object not found') + jasmine.stringMatching('Object not found.') ); }); }); diff --git a/spec/ParseInstallation.spec.js b/spec/ParseInstallation.spec.js index b11f1337e5..d777bd56b9 100644 --- a/spec/ParseInstallation.spec.js +++ b/spec/ParseInstallation.spec.js @@ -1232,7 +1232,9 @@ describe('Installations', () => { }, err => { expect(err.code).toBe(136); - expect(err.message).toBe('installationId may not be changed in this operation'); + expect(err.message).toBe( + 'The field installationId can not be changed in this operation.' + ); done(); } ); diff --git a/spec/ParseQuery.FullTextSearch.spec.js b/spec/ParseQuery.FullTextSearch.spec.js index e6614e5d81..96cfebc0c1 100644 --- a/spec/ParseQuery.FullTextSearch.spec.js +++ b/spec/ParseQuery.FullTextSearch.spec.js @@ -3,6 +3,7 @@ const Config = require('../lib/Config'); const Parse = require('parse/node'); const request = require('../lib/request'); +const { ErrorMessage } = require('../lib/Errors/message'); let databaseAdapter; const fullTextHelper = async () => { @@ -110,7 +111,10 @@ describe('Parse.Query Full Text Search testing', () => { } }; await expectAsync(invalidQuery()).toBeRejectedWith( - new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $search, should be object') + new Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('object', '$text.$search') + ) ); }); @@ -119,7 +123,10 @@ describe('Parse.Query Full Text Search testing', () => { const query = new Parse.Query('TestObject'); query.fullText('subject', 'leche', { language: true }); await expectAsync(query.find()).toBeRejectedWith( - new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $language, should be string') + new Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('string', '$text.$language') + ) ); }); @@ -128,7 +135,10 @@ describe('Parse.Query Full Text Search testing', () => { const query = new Parse.Query('TestObject'); query.fullText('subject', 'leche', { caseSensitive: 'string' }); await expectAsync(query.find()).toBeRejectedWith( - new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $caseSensitive, should be boolean') + new Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('boolean', '$text.$caseSensitive') + ) ); }); @@ -137,7 +147,10 @@ describe('Parse.Query Full Text Search testing', () => { const query = new Parse.Query('TestObject'); query.fullText('subject', 'leche', { diacriticSensitive: 'string' }); await expectAsync(query.find()).toBeRejectedWith( - new Parse.Error(Parse.Error.INVALID_JSON, 'bad $text: $diacriticSensitive, should be boolean') + new Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('boolean', '$text.$diacriticSensitive') + ) ); }); }); diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 6de0957999..d49ce31434 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -5,6 +5,7 @@ 'use strict'; const Parse = require('parse/node'); +const { ErrorMessage } = require('../lib/Errors/message'); const request = require('../lib/request'); const masterKeyHeaders = { @@ -980,7 +981,7 @@ describe('Parse.Query testing', () => { .then(done.fail) .catch(response => { equal(response.data.code, Parse.Error.INVALID_JSON); - equal(response.data.error, 'bad $containedBy: should be an array'); + equal(response.data.error, ErrorMessage.queryValueTypeInvalid('an array', '$containedBy')); done(); }); }); @@ -1319,7 +1320,7 @@ describe('Parse.Query testing', () => { .then(done.fail) .catch(error => { equal(error.code, Parse.Error.INVALID_JSON); - equal(error.message, 'bad $in value'); + equal(error.message, ErrorMessage.objectFieldValueInvalid('$in')); done(); }); }); @@ -1339,7 +1340,7 @@ describe('Parse.Query testing', () => { .then(done.fail) .catch(error => { equal(error.code, Parse.Error.INVALID_JSON); - equal(error.message, 'bad $nin value'); + equal(error.message, ErrorMessage.objectFieldValueInvalid('$nin')); done(); }); }); diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 1b136808b9..4ca63fe8af 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -4,6 +4,7 @@ const ParseServer = require('../lib/ParseServer').default; const Parse = require('parse/node').Parse; const semver = require('semver'); const TestUtils = require('../lib/TestUtils'); +const { ErrorMessage } = require('../lib/Errors/message'); let RESTController; @@ -569,7 +570,7 @@ describe('ParseServerRESTController', () => { fail('Success callback should not be called when passing an empty username.'); } catch (err) { expect(err.code).toBe(Parse.Error.USERNAME_MISSING); - expect(err.message).toBe('bad or missing username'); + expect(err.message).toBe(ErrorMessage.required('username', '')); } }); @@ -582,7 +583,7 @@ describe('ParseServerRESTController', () => { fail('Success callback should not be called when passing an empty password.'); } catch (err) { expect(err.code).toBe(Parse.Error.PASSWORD_MISSING); - expect(err.message).toBe('password is required'); + expect(err.message).toBe(ErrorMessage.required('password', '')); } }); diff --git a/spec/ParseUser.spec.js b/spec/ParseUser.spec.js index 80f6254be8..faa1446cd4 100644 --- a/spec/ParseUser.spec.js +++ b/spec/ParseUser.spec.js @@ -12,6 +12,7 @@ const request = require('../lib/request'); const passwordCrypto = require('../lib/password'); const Config = require('../lib/Config'); const cryptoUtils = require('../lib/cryptoUtils'); +const { ErrorMessage } = require('../lib/Errors/message'); describe('Parse.User testing', () => { it('user sign up class method', async done => { @@ -73,7 +74,9 @@ describe('Parse.User testing', () => { .catch(err => { expect(err.status).toBe(404); expect(err.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }); @@ -102,7 +105,9 @@ describe('Parse.User testing', () => { .catch(err => { expect(err.status).toBe(404); expect(err.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }); @@ -231,7 +236,7 @@ describe('Parse.User testing', () => { }) .then(done.fail) .catch(err => { - expect(err.message).toBe('Invalid username/password.'); + expect(err.message).toBe(ErrorMessage.invalid('username/password')); expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND); done(); }); @@ -1910,7 +1915,7 @@ describe('Parse.User testing', () => { }, err => { expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND); - expect(err.message).toEqual('Invalid username/password.'); + expect(err.message).toEqual(ErrorMessage.invalid('username/password')); done(); } ); @@ -2206,7 +2211,7 @@ describe('Parse.User testing', () => { user2.set('username', 'Test1'); user2.set('password', 'test'); await expectAsync(user2.signUp()).toBeRejectedWith( - new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.') + new Parse.Error(Parse.Error.USERNAME_TAKEN, ErrorMessage.exists('Account', 'username')) ); }); @@ -2220,7 +2225,7 @@ describe('Parse.User testing', () => { user2.setUsername('Test1'); user2.setPassword('test'); await expectAsync(user2.signUp()).toBeRejectedWith( - new Parse.Error(Parse.Error.USERNAME_TAKEN, 'Account already exists for this username.') + new Parse.Error(Parse.Error.USERNAME_TAKEN, ErrorMessage.exists('Account', 'username')) ); }); @@ -2236,7 +2241,7 @@ describe('Parse.User testing', () => { user2.setPassword('test'); user2.setEmail('Test@Example.Com'); await expectAsync(user2.signUp()).toBeRejectedWith( - new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.') + new Parse.Error(Parse.Error.EMAIL_TAKEN, ErrorMessage.exists('Account', 'email address')) ); }); @@ -2255,7 +2260,7 @@ describe('Parse.User testing', () => { user2.setEmail('Test@Example.Com'); await expectAsync(user2.save()).toBeRejectedWith( - new Parse.Error(Parse.Error.EMAIL_TAKEN, 'Account already exists for this email address.') + new Parse.Error(Parse.Error.EMAIL_TAKEN, ErrorMessage.exists('Account', 'email address')) ); }); @@ -2505,7 +2510,7 @@ describe('Parse.User testing', () => { body: JSON.stringify({ foo: 'bar' }), }).then(fail, response => { const b = response.data; - expect(b.error).toBe('Session token required.'); + expect(b.error).toBe(ErrorMessage.required('Session Token', '')); done(); }); }); @@ -3022,7 +3027,7 @@ describe('Parse.User testing', () => { expect(emailCalled).toBeTruthy(); expect(emailOptions).toBeDefined(); expect(err.status).toBe(400); - expect(err.text).toMatch('{"code":125,"error":"you must provide a valid email string"}'); + expect(err.text).toMatch('{"code":125,"error":"' + ErrorMessage.invalid('email') + '"}'); done(); }); }); @@ -3718,7 +3723,7 @@ describe('Parse.User testing', () => { }) .then(done.fail) .catch(err => { - expect(err.data.error).toEqual('Invalid username/password.'); + expect(err.data.error).toEqual(ErrorMessage.invalid('username/password')); done(); }); }); @@ -3743,7 +3748,7 @@ describe('Parse.User testing', () => { }) .then(done.fail) .catch(err => { - expect(err.data.error).toEqual('Invalid username/password.'); + expect(err.data.error).toEqual(ErrorMessage.invalid('username/password')); done(); }); }); @@ -3768,7 +3773,7 @@ describe('Parse.User testing', () => { }) .then(done.fail) .catch(err => { - expect(err.data.error).toEqual('username/email is required.'); + expect(err.data.error).toEqual(ErrorMessage.required('username/email', '')); done(); }); }); @@ -3834,7 +3839,7 @@ describe('Parse.User testing', () => { }) .then(done.fail) .catch(err => { - expect(err.message).toEqual('Invalid username/password.'); + expect(err.message).toEqual(ErrorMessage.invalid('username/password')); done(); }); }); @@ -3859,7 +3864,7 @@ describe('Parse.User testing', () => { }) .then(done.fail) .catch(err => { - expect(err.data.error).toEqual('password is required.'); + expect(err.data.error).toEqual(ErrorMessage.required('password', '')); done(); }); }); @@ -4162,7 +4167,7 @@ describe('login as other user', () => { done(); } catch (err) { expect(err.data.code).toBe(Parse.Error.OBJECT_NOT_FOUND); - expect(err.data.error).toBe('user not found'); + expect(err.data.error).toBe(ErrorMessage.notFound('User')); } const sessionsQuery = new Parse.Query(Parse.Session); @@ -4227,7 +4232,7 @@ describe('login as other user', () => { done(); } catch (err) { expect(err.data.code).toBe(Parse.Error.OPERATION_FORBIDDEN); - expect(err.data.error).toBe('master key is required'); + expect(err.data.error).toBe(ErrorMessage.required('Master Key', '')); } const sessionsQuery = new Parse.Query(Parse.Session); diff --git a/spec/RegexVulnerabilities.spec.js b/spec/RegexVulnerabilities.spec.js index 62af4f04e2..c85e061a1e 100644 --- a/spec/RegexVulnerabilities.spec.js +++ b/spec/RegexVulnerabilities.spec.js @@ -1,3 +1,4 @@ +const { ErrorMessage } = require('../lib/Errors/message'); const request = require('../lib/request'); const serverURL = 'http://localhost:8378/1'; @@ -148,7 +149,7 @@ describe('Regex Vulnerabilities', function () { fail('should not work'); } catch (e) { expect(e.code).toEqual(Parse.Error.OBJECT_NOT_FOUND); - expect(e.message).toEqual('Invalid username/password.'); + expect(e.message).toEqual(ErrorMessage.invalid('username/password')); } }); diff --git a/spec/RestQuery.spec.js b/spec/RestQuery.spec.js index c8e3adb49d..181fbf06b0 100644 --- a/spec/RestQuery.spec.js +++ b/spec/RestQuery.spec.js @@ -7,6 +7,7 @@ const RestQuery = require('../lib/RestQuery'); const request = require('../lib/request'); const querystring = require('querystring'); +const { ErrorMessage } = require('../lib/Errors/message'); let config; let database; @@ -165,7 +166,7 @@ describe('rest query', () => { err => { expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN); expect(err.message).toEqual( - 'This user is not allowed to access ' + 'non-existent class: ClientClassCreation' + ErrorMessage.unauthorizedAccess('class', 'ClientClassCreation') ); done(); } diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index d7cc72c7b0..8071d43477 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -3,6 +3,7 @@ const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions'); const request = require('../lib/request'); const Config = require('../lib/Config'); +const { ErrorMessage } = require('../lib/Errors/message.js'); describe('Custom Pages, Email Verification, Password Reset', () => { it('should set the custom pages', done => { @@ -231,7 +232,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => { done(); }, error => { - expect(error.message).toEqual('User email is not verified.'); + expect(error.message).toEqual(ErrorMessage.unverified('User email')); done(); } ); @@ -369,9 +370,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => { done(); }, error => { - expect(error.message).toEqual( - 'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.' - ); + expect(error.message).toEqual(ErrorMessage.fieldMissingForVerificationFunc()); done(); } ); @@ -405,9 +404,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => { done(); }, error => { - expect(error.message).toEqual( - 'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.' - ); + expect(error.message).toEqual(ErrorMessage.fieldMissingForVerificationFunc()); done(); } ); @@ -438,9 +435,7 @@ describe('Custom Pages, Email Verification, Password Reset', () => { done(); }, error => { - expect(error.message).toEqual( - 'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.' - ); + expect(error.message).toEqual(ErrorMessage.fieldMissingForVerificationFunc()); done(); } ); diff --git a/spec/VerifyUserPassword.spec.js b/spec/VerifyUserPassword.spec.js index 6734dcdb71..2dedadf821 100644 --- a/spec/VerifyUserPassword.spec.js +++ b/spec/VerifyUserPassword.spec.js @@ -1,5 +1,6 @@ 'use strict'; +const { ErrorMessage } = require('../lib/Errors/message'); const request = require('../lib/request'); const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions'); @@ -23,12 +24,7 @@ const isAccountLockoutError = function (username, password, duration, waitTime) Parse.User.logIn(username, password) .then(() => reject('login should have failed')) .catch(err => { - if ( - err.message === - 'Your account is locked due to multiple failed login attempts. Please try again after ' + - duration + - ' minute(s)' - ) { + if (err.message === ErrorMessage.accountLocked(duration)) { resolve(); } else { reject(err); @@ -80,7 +76,9 @@ describe('Verify User Password', () => { .catch(err => { expect(err.status).toBe(404); expect(err.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }); @@ -112,7 +110,9 @@ describe('Verify User Password', () => { }) .catch(err => { expect(err.status).toBe(400); - expect(err.text).toMatch('{"code":200,"error":"username/email is required."}'); + expect(err.text).toMatch( + '{"code":200,"error":"' + ErrorMessage.required('username/email', '') + '"}' + ); done(); }); }); @@ -143,7 +143,9 @@ describe('Verify User Password', () => { }) .catch(err => { expect(err.status).toBe(400); - expect(err.text).toMatch('{"code":200,"error":"username/email is required."}'); + expect(err.text).toMatch( + '{"code":200,"error":"' + ErrorMessage.required('username/email', '') + '"}' + ); done(); }); }); @@ -160,7 +162,9 @@ describe('Verify User Password', () => { }) .then(res => { expect(res.status).toBe(400); - expect(res.text).toMatch('{"code":200,"error":"username/email is required."}'); + expect(res.text).toMatch( + '{"code":200,"error":"' + ErrorMessage.required('username/email', '') + '"}' + ); done(); }) .catch(err => { @@ -181,7 +185,9 @@ describe('Verify User Password', () => { }) .then(res => { expect(res.status).toBe(400); - expect(res.text).toMatch('{"code":200,"error":"username/email is required."}'); + expect(res.text).toMatch( + '{"code":200,"error":"' + ErrorMessage.required('username/email', '') + '"}' + ); done(); }) .catch(err => { @@ -202,7 +208,9 @@ describe('Verify User Password', () => { }) .then(res => { expect(res.status).toBe(400); - expect(res.text).toMatch('{"code":201,"error":"password is required."}'); + expect(res.text).toMatch( + '{"code":201,"error":"' + ErrorMessage.required('password', '') + '"}' + ); done(); }) .catch(err => { @@ -224,7 +232,9 @@ describe('Verify User Password', () => { .then(res => { expect(res.status).toBe(404); expect(res.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }) @@ -247,7 +257,9 @@ describe('Verify User Password', () => { .then(res => { expect(res.status).toBe(404); expect(res.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }) @@ -270,7 +282,9 @@ describe('Verify User Password', () => { .then(res => { expect(res.status).toBe(404); expect(res.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }) @@ -293,7 +307,9 @@ describe('Verify User Password', () => { .then(res => { expect(res.status).toBe(404); expect(res.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }) @@ -316,7 +332,9 @@ describe('Verify User Password', () => { .then(res => { expect(res.status).toBe(404); expect(res.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }) @@ -330,7 +348,9 @@ describe('Verify User Password', () => { .then(res => { expect(res.status).toBe(404); expect(res.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }) @@ -344,7 +364,9 @@ describe('Verify User Password', () => { .then(res => { expect(res.status).toBe(404); expect(res.text).toMatch( - `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"Invalid username/password."}` + `{"code":${Parse.Error.OBJECT_NOT_FOUND},"error":"${ErrorMessage.invalid( + 'username/password' + )}"}` ); done(); }) diff --git a/spec/rest.spec.js b/spec/rest.spec.js index db3082ec74..cdf6e53ac8 100644 --- a/spec/rest.spec.js +++ b/spec/rest.spec.js @@ -6,6 +6,7 @@ const Parse = require('parse/node').Parse; const rest = require('../lib/rest'); const RestWrite = require('../lib/RestWrite'); const request = require('../lib/request'); +const { ErrorMessage } = require('../lib/Errors/message'); let config; let database; @@ -77,7 +78,7 @@ describe('rest create', () => { objectId: '', }; - const err = 'objectId must not be empty, null or undefined'; + const err = ErrorMessage.noEmpty('ObjectId'); expect(() => rest.create(config, auth.nobody(config), 'MyClass', objIdEmpty)).toThrowError(err); @@ -214,7 +215,7 @@ describe('rest create', () => { err => { expect(err.code).toEqual(Parse.Error.OPERATION_FORBIDDEN); expect(err.message).toEqual( - 'This user is not allowed to access ' + 'non-existent class: ClientClassCreation' + ErrorMessage.unauthorizedAccess('class', 'ClientClassCreation') ); done(); } @@ -504,7 +505,7 @@ describe('rest create', () => { }).then(fail, response => { const b = response.data; expect(b.code).toEqual(105); - expect(b.error).toEqual('objectId is an invalid field name.'); + expect(b.error).toEqual(ErrorMessage.invalid('objectId')); done(); }); }); @@ -526,7 +527,7 @@ describe('rest create', () => { }).then(fail, response => { const b = response.data; expect(b.code).toEqual(105); - expect(b.error).toEqual('id is an invalid field name.'); + expect(b.error).toEqual(ErrorMessage.invalid('id')); done(); }); }); @@ -866,7 +867,7 @@ describe('read-only masterKey', () => { }).toThrow( new Parse.Error( Parse.Error.OPERATION_FORBIDDEN, - 'Cannot perform a write operation when using readOnlyMasterKey' + 'Cannot perform a write operation when using readOnlyMasterKey.' ) ); }); diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 9557dd7924..2156c69bf5 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -5,6 +5,7 @@ const dd = require('deep-diff'); const Config = require('../lib/Config'); const request = require('../lib/request'); const TestUtils = require('../lib/TestUtils'); +const { ErrorMessage } = require('../lib/Errors/message'); const SchemaController = require('../lib/Controllers/SchemaController').SchemaController; let config; @@ -1119,7 +1120,7 @@ describe('schemas', () => { fail('Should fail'); } catch (e) { expect(e.code).toEqual(142); - expect(e.message).toEqual('newRequiredField is required'); + expect(e.message).toEqual(ErrorMessage.required('newRequiredField', undefined, true)); } obj.set('newRequiredField', 'some value'); await obj.save(); @@ -1134,7 +1135,7 @@ describe('schemas', () => { fail('Should fail'); } catch (e) { expect(e.code).toEqual(142); - expect(e.message).toEqual('newRequiredField is required'); + expect(e.message).toEqual(ErrorMessage.required('newRequiredField', undefined, true)); } obj.unset('newRequiredField'); try { @@ -1142,7 +1143,7 @@ describe('schemas', () => { fail('Should fail'); } catch (e) { expect(e.code).toEqual(142); - expect(e.message).toEqual('newRequiredField is required'); + expect(e.message).toEqual(ErrorMessage.required('newRequiredField', undefined, true)); } obj.set('newRequiredField', 'some value2'); await obj.save(); @@ -1157,7 +1158,9 @@ describe('schemas', () => { fail('Should fail'); } catch (e) { expect(e.code).toEqual(142); - expect(e.message).toEqual('newRequiredFieldWithDefaultValue is required'); + expect(e.message).toEqual( + ErrorMessage.required('newRequiredFieldWithDefaultValue', undefined, true) + ); } obj.set('newRequiredFieldWithDefaultValue', ''); try { @@ -1165,7 +1168,9 @@ describe('schemas', () => { fail('Should fail'); } catch (e) { expect(e.code).toEqual(142); - expect(e.message).toEqual('newRequiredFieldWithDefaultValue is required'); + expect(e.message).toEqual( + ErrorMessage.required('newRequiredFieldWithDefaultValue', undefined, true) + ); } obj.set('newRequiredFieldWithDefaultValue', 'some value2'); obj.set('newNotRequiredField', ''); @@ -1276,7 +1281,7 @@ describe('schemas', () => { await obj.save(); fail('should fail'); } catch (e) { - expect(e.message).toEqual('foo2 is required'); + expect(e.message).toEqual(ErrorMessage.required('foo2', undefined, true)); } Parse.Cloud.beforeSave('NewClassForBeforeSaveTest', req => { @@ -1295,7 +1300,7 @@ describe('schemas', () => { await obj.save(); fail('should fail'); } catch (e) { - expect(e.message).toEqual('foo2 is required'); + expect(e.message).toEqual(ErrorMessage.required('foo2', undefined, true)); } }); diff --git a/src/AccountLockout.js b/src/AccountLockout.js index 13d655e6b7..84fa004342 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 { ErrorMessage } from '../lib/Errors/message'; export class AccountLockout { constructor(user, config) { @@ -118,9 +119,7 @@ export class AccountLockout { if (Array.isArray(users) && users.length > 0) { throw new Parse.Error( Parse.Error.OBJECT_NOT_FOUND, - 'Your account is locked due to multiple failed login attempts. Please try again after ' + - this._config.accountLockout.duration + - ' minute(s)' + ErrorMessage.accountLocked(this._config.accountLockout.duration) ); } }); diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index c1fe63ba9b..9291851085 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -18,6 +18,7 @@ import Parse from 'parse/node'; import _ from 'lodash'; import defaults from '../../../defaults'; import logger from '../../../logger'; +import { ErrorMessage } from '../../../Errors/message'; // @flow-disable-next const mongodb = require('mongodb'); @@ -482,10 +483,7 @@ export class MongoStorageAdapter implements StorageAdapter { .catch(error => { if (error.code === 11000) { // Duplicate value - const err = new Parse.Error( - Parse.Error.DUPLICATE_VALUE, - 'A duplicate value for a field with unique values was provided' - ); + const err = new Parse.Error(Parse.Error.DUPLICATE_VALUE, ErrorMessage.duplicateValue()); err.underlyingError = error; if (error.message) { const matches = error.message.match(/index:[\sa-zA-Z0-9_\-\.]+\$?([a-zA-Z_-]+)_1/); @@ -519,12 +517,15 @@ export class MongoStorageAdapter implements StorageAdapter { .then( ({ result }) => { if (result.n === 0) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, ErrorMessage.notFound('Object')); } return Promise.resolve(); }, () => { - throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error'); + throw new Parse.Error( + Parse.Error.INTERNAL_SERVER_ERROR, + ErrorMessage.databaseAdapterError() + ); } ); } @@ -567,10 +568,7 @@ export class MongoStorageAdapter implements StorageAdapter { .then(result => mongoObjectToParseObject(className, result.value, schema)) .catch(error => { if (error.code === 11000) { - throw new Parse.Error( - Parse.Error.DUPLICATE_VALUE, - 'A duplicate value for a field with unique values was provided' - ); + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, ErrorMessage.duplicateValue()); } throw error; }) @@ -708,10 +706,7 @@ export class MongoStorageAdapter implements StorageAdapter { .then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest)) .catch(error => { if (error.code === 11000) { - throw new Parse.Error( - Parse.Error.DUPLICATE_VALUE, - 'Tried to ensure field uniqueness for a class that already has duplicates.' - ); + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, ErrorMessage.duplicateValue()); } throw error; }) diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 91ad23fa4a..00c7fd4bf1 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -1,5 +1,6 @@ import log from '../../../logger'; import _ from 'lodash'; +import { ErrorMessage } from '../../../Errors/message'; var mongodb = require('mongodb'); var Parse = require('parse/node').Parse; const Utils = require('../../../Utils'); @@ -708,7 +709,10 @@ 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 Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.objectFieldValueInvalid(key) + ); } answer[key] = _.flatMap(arr, value => { return (atom => { @@ -724,7 +728,10 @@ 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 Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.objectFieldValueInvalid(key) + ); } answer[key] = arr.map(transformInteriorAtom); @@ -749,7 +756,10 @@ 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 Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('an array', '$containedBy') + ); } answer.$elemMatch = { $nin: arr.map(transformer), @@ -763,24 +773,33 @@ 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 Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('object', '$text.$search') + ); } if (!search.$term || typeof search.$term !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $term, should be string`); + throw new Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('string', '$text.$term') + ); } 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 Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('string', '$text.$language') + ); } else if (search.$language) { answer[key].$language = search.$language; } if (search.$caseSensitive && typeof search.$caseSensitive !== 'boolean') { throw new Parse.Error( Parse.Error.INVALID_JSON, - `bad $text: $caseSensitive, should be boolean` + ErrorMessage.queryValueTypeInvalid('boolean', '$text.$caseSensitive') ); } else if (search.$caseSensitive) { answer[key].$caseSensitive = search.$caseSensitive; @@ -788,7 +807,7 @@ function transformConstraint(constraint, field, count = false) { if (search.$diacriticSensitive && typeof search.$diacriticSensitive !== 'boolean') { throw new Parse.Error( Parse.Error.INVALID_JSON, - `bad $text: $diacriticSensitive, should be boolean` + ErrorMessage.queryValueTypeInvalid('boolean', '$text.$diacriticSensitive') ); } else if (search.$diacriticSensitive) { answer[key].$diacriticSensitive = search.$diacriticSensitive; @@ -878,7 +897,10 @@ function transformConstraint(constraint, field, count = false) { return point; } if (!GeoPointCoder.isValidJSON(point)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + throw new Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.objectFieldValueInvalid('$geoWithin') + ); } else { Parse.GeoPoint._validate(point.latitude, point.longitude); } diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 7477270a3d..8c9d094141 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -7,6 +7,7 @@ import _ from 'lodash'; // @flow-disable-next import { v4 as uuidv4 } from 'uuid'; import sql from './sql'; +import { ErrorMessage } from '../../../Errors/message'; import { StorageAdapter } from '../StorageAdapter'; import type { SchemaType, QueryType, QueryOptions } from '../StorageAdapter'; const Utils = require('../../../Utils'); @@ -226,10 +227,7 @@ const validateKeys = object => { } if (key.includes('$') || key.includes('.')) { - throw new Parse.Error( - Parse.Error.INVALID_NESTED_KEY, - "Nested keys should not contain the '$' or '.' characters" - ); + throw new Parse.Error(Parse.Error.INVALID_NESTED_KEY, ErrorMessage.invalidNestedKey()); } } } @@ -489,9 +487,9 @@ 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 Parse.Error(Parse.Error.INVALID_JSON, ErrorMessage.objectFieldValueInvalid('$in')); } else if (typeof fieldValue.$nin !== 'undefined') { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $nin value'); + throw new Parse.Error(Parse.Error.INVALID_JSON, ErrorMessage.objectFieldValueInvalid('$nin')); } if (Array.isArray(fieldValue.$all) && isArrayField) { @@ -499,7 +497,7 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus if (!isAllValuesRegexOrNone(fieldValue.$all)) { throw new Parse.Error( Parse.Error.INVALID_JSON, - 'All $all values must be of regex type or none: ' + fieldValue.$all + ErrorMessage.queryValueTypeInvalid(`regex type or none: ${fieldValue.$all}`, '$all') ); } @@ -539,7 +537,10 @@ 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 Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('an array', '$containedBy') + ); } patterns.push(`$${index}:name <@ $${index + 1}::jsonb`); @@ -551,36 +552,48 @@ 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 Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('object', '$text.$search') + ); } if (!search.$term || typeof search.$term !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $term, should be string`); + throw new Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('string', '$text.$term') + ); } if (search.$language && typeof search.$language !== 'string') { - throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $language, should be string`); + throw new Parse.Error( + Parse.Error.INVALID_JSON, + ErrorMessage.queryValueTypeInvalid('string', '$text.$language') + ); } else if (search.$language) { language = search.$language; } if (search.$caseSensitive && typeof search.$caseSensitive !== 'boolean') { throw new Parse.Error( Parse.Error.INVALID_JSON, - `bad $text: $caseSensitive, should be boolean` + ErrorMessage.queryValueTypeInvalid('boolean', '$text.$caseSensitive') ); } else if (search.$caseSensitive) { throw new Parse.Error( Parse.Error.INVALID_JSON, - `bad $text: $caseSensitive not supported, please use $regex or create a separate lower case column.` + ErrorMessage.queryValueTypeInvalid('boolean', '$text.$caseSensitive') ); } if (search.$diacriticSensitive && typeof search.$diacriticSensitive !== 'boolean') { throw new Parse.Error( Parse.Error.INVALID_JSON, - `bad $text: $diacriticSensitive, should be boolean` + ErrorMessage.queryValueTypeInvalid('boolean', '$text.$diacriticSensitive') ); } else if (search.$diacriticSensitive === false) { throw new Parse.Error( Parse.Error.INVALID_JSON, - `bad $text: $diacriticSensitive - false not supported, install Postgres Unaccent Extension` + ErrorMessage.databasePostgresExtensionRequired( + '$text: $diacriticSensitive - false', + 'Postgres Unaccent' + ) ); } patterns.push( @@ -625,7 +638,10 @@ const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClaus if (!(centerSphere instanceof Array) || centerSphere.length < 2) { throw new Parse.Error( Parse.Error.INVALID_JSON, - 'bad $geoWithin value; $centerSphere should be an array of Parse.GeoPoint and distance' + ErrorMessage.queryValueTypeInvalid( + 'an array of Parse.GeoPoint and distance', + '$geoWithin value; $centerSphere' + ) ); } // Get point, convert to geo point if necessary and validate @@ -1004,7 +1020,10 @@ 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 Parse.Error( + Parse.Error.DUPLICATE_VALUE, + ErrorMessage.exists('Class', className) + ); } throw err; }); @@ -1419,10 +1438,7 @@ export class PostgresStorageAdapter implements StorageAdapter { .then(() => ({ ops: [object] })) .catch(error => { if (error.code === PostgresUniqueIndexViolationError) { - const err = new Parse.Error( - Parse.Error.DUPLICATE_VALUE, - 'A duplicate value for a field with unique values was provided' - ); + const err = new Parse.Error(Parse.Error.DUPLICATE_VALUE, ErrorMessage.duplicateValue()); err.underlyingError = error; if (error.constraint) { const matches = error.constraint.match(/unique_([a-zA-Z]+)/); @@ -1467,7 +1483,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 Parse.Error(Parse.Error.OBJECT_NOT_FOUND, ErrorMessage.notFound('Object')); } else { return count; } @@ -1984,10 +2000,7 @@ 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, - 'A duplicate value for a field with unique values was provided' - ); + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, ErrorMessage.duplicateValue()); } else { throw error; } @@ -2458,7 +2471,7 @@ export class PostgresStorageAdapter implements StorageAdapter { // Cast the error into the proper parse error throw new Parse.Error( Parse.Error.DUPLICATE_VALUE, - 'A duplicate value for a field with unique values was provided' + ErrorMessage.duplicateValue() ); } else { throw error; @@ -2494,7 +2507,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 Parse.Error(Parse.Error.INVALID_JSON, ErrorMessage.invalidPolygonValues()); } if ( polygon[0][0] !== polygon[polygon.length - 1][0] || @@ -2514,10 +2527,7 @@ function convertPolygonToSQL(polygon) { return foundIndex === index; }); if (unique.length < 3) { - throw new Parse.Error( - Parse.Error.INTERNAL_SERVER_ERROR, - 'GeoJSON: Loop must have at least 3 different vertices' - ); + throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, ErrorMessage.geoJsonInvalidVertices()); } const points = polygon .map(point => { diff --git a/src/Errors/message.js b/src/Errors/message.js new file mode 100644 index 0000000000..d58cea24fa --- /dev/null +++ b/src/Errors/message.js @@ -0,0 +1,41 @@ +export const ErrorMessage = { + masterKeyReadOnly: () => 'Cannot perform a write operation when using readOnlyMasterKey.', + noEmpty: (field, isField = true) => + `${isField === true ? 'The field ' : ''}${field} must not be empty, null or undefined.`, + invalid: (field, isField = true) => `${isField === true ? 'The field' : ''} ${field} is invalid.`, + unauthorizedAccess: (type, value) => `This user is not allowed to access ${type}: ${value}`, + notFound: type => `${type} not found.`, + exists: (type, key) => `${type} ${key} already exists`, + required: (field, op = 'to save/update the object', isField = false) => + `A value for ${isField === true ? 'field ' : ''}${field} is required${op ? ' ' : ''}${op}.`, + verified: type => `${type} is already verified.`, + unverified: type => `${type} is not verified.`, + noUpdate: (type, key) => `Can not update ${type} ${key}`, + unsupportedService: () => 'This authentication method is unsupported.', + accountLinked: () => 'This auth is already used.', + accountLocked: duration => + `Your account is locked due to multiple failed login attempts. Please try again after ${duration} minute(s).`, + clientEmailVerification: () => "Clients aren't allowed to manually update email verification.", + passwordPolicy: () => 'Password does not meet the Password Policy requirements.', + usernameInPassword: () => 'Password cannot contain your username.', + passwordMatchesExistingPassword: maxPasswordHistory => + `New password should not be the same as last ${maxPasswordHistory} passwords.`, + aclSession: () => 'Cannot set ACL on a Session.', + sessionError: () => 'Error creating session.', + noOp: field => `The field ${field} can not be changed in this operation.`, + reqOp: field => `The field ${field} must be specified in this operation.`, + installationIdWithDeviceToken: () => + 'Must specify installationId when deviceToken matches multiple Installation objects.', + invalidAcl: () => 'Invalid ACL.', + duplicateValue: () => 'A duplicate value for a field with unique values was provided.', + databaseAdapterError: () => 'Database adapter error.', + geoJsonInvalidVertices: () => 'GeoJSON: Loop must have at least 3 different vertices.', + invalidPolygonValues: () => 'Polygon must have at least 3 values.', + invalidNestedKey: () => "Nested keys should not contain the '$' or '.' characters", + objectFieldValueInvalid: type => `Invalid ${type} value provided.`, + queryValueTypeInvalid: (type, key) => `Value of '${key}' must be of type ${type}.`, + databasePostgresExtensionRequired: (key, extension) => + `Query key ${key} requires Postgres extension ${extension}.`, + fieldMissingForVerificationFunc: () => + 'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.', +}; diff --git a/src/GraphQL/loaders/usersMutations.js b/src/GraphQL/loaders/usersMutations.js index c38905cd90..b630c1aca6 100644 --- a/src/GraphQL/loaders/usersMutations.js +++ b/src/GraphQL/loaders/usersMutations.js @@ -7,6 +7,7 @@ import { OBJECT } from './defaultGraphQLTypes'; import { getUserFromSessionToken } from './usersQueries'; import { transformTypes } from '../transformers/mutation'; import Parse from 'parse/node'; +import { ErrorMessage } from '../../Errors/message'; const usersRouter = new UsersRouter(); @@ -279,13 +280,13 @@ const load = parseGraphQLSchema => { mutateAndGetPayload: async ({ username, password, token }, context) => { const { config } = context; if (!username) { - throw new Parse.Error(Parse.Error.USERNAME_MISSING, 'you must provide a username'); + throw new Parse.Error(Parse.Error.USERNAME_MISSING, ErrorMessage.required('username', '')); } if (!password) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'you must provide a password'); + throw new Parse.Error(Parse.Error.PASSWORD_MISSING, ErrorMessage.required('password', '')); } if (!token) { - throw new Parse.Error(Parse.Error.OTHER_CAUSE, 'you must provide a token'); + throw new Parse.Error(Parse.Error.OTHER_CAUSE, ErrorMessage.fieldIsRequired('token')); } const userController = config.userController; diff --git a/src/RestQuery.js b/src/RestQuery.js index be96683451..cf9ced0ace 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -6,6 +6,8 @@ var Parse = require('parse/node').Parse; const triggers = require('./triggers'); const { continueWhile } = require('parse/lib/node/promiseUtils'); const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL']; +const { ErrorMessage } = require('./Errors/message'); + // restOptions can include: // skip // limit @@ -335,7 +337,7 @@ RestQuery.prototype.validateClientClassCreation = function () { if (hasClass !== true) { throw new Parse.Error( Parse.Error.OPERATION_FORBIDDEN, - 'This user is not allowed to access ' + 'non-existent class: ' + this.className + ErrorMessage.unauthorizedAccess('class', this.className) ); } }); diff --git a/src/RestWrite.js b/src/RestWrite.js index a651cf9c6c..674b767e5a 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -14,6 +14,7 @@ var ClientSDK = require('./ClientSDK'); import RestQuery from './RestQuery'; import _ from 'lodash'; import logger from './logger'; +import { ErrorMessage } from './Errors/message'; // query and data are both provided in REST API format. So data // types are encoded by plain old objects. @@ -26,10 +27,7 @@ import logger from './logger'; // 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, - 'Cannot perform a write operation when using readOnlyMasterKey' - ); + throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, ErrorMessage.masterKeyReadOnly()); } this.config = config; this.auth = auth; @@ -46,17 +44,14 @@ 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, - 'objectId must not be empty, null or undefined' - ); + throw new Parse.Error(Parse.Error.MISSING_OBJECT_ID, ErrorMessage.noEmpty('ObjectId')); } } else { if (data.objectId) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.'); + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, ErrorMessage.invalid('objectId')); } if (data.id) { - throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'id is an invalid field name.'); + throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, ErrorMessage.invalid('id')); } } } @@ -178,7 +173,7 @@ RestWrite.prototype.validateClientClassCreation = function () { if (hasClass !== true) { throw new Parse.Error( Parse.Error.OPERATION_FORBIDDEN, - 'This user is not allowed to access ' + 'non-existent class: ' + this.className + ErrorMessage.unauthorizedAccess('class', this.className) ); } }); @@ -250,7 +245,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 Parse.Error(Parse.Error.OBJECT_NOT_FOUND, ErrorMessage.notFound('Object')); } }); }) @@ -337,7 +332,10 @@ 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 Parse.Error( + Parse.Error.VALIDATION_ERROR, + ErrorMessage.required('field' + ' ' + fieldName) + ); } } }; @@ -376,10 +374,10 @@ RestWrite.prototype.validateAuthData = function () { if (!this.query && !this.data.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 Parse.Error(Parse.Error.USERNAME_MISSING, ErrorMessage.required('username', '')); } if (typeof this.data.password !== 'string' || _.isEmpty(this.data.password)) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required'); + throw new Parse.Error(Parse.Error.PASSWORD_MISSING, ErrorMessage.required('password', '')); } } @@ -391,10 +389,7 @@ 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, - 'This authentication method is unsupported.' - ); + throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, ErrorMessage.unsupportedService()); } var authData = this.data.authData; @@ -409,10 +404,7 @@ RestWrite.prototype.validateAuthData = function () { return this.handleAuthData(authData); } } - throw new Parse.Error( - Parse.Error.UNSUPPORTED_SERVICE, - 'This authentication method is unsupported.' - ); + throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, ErrorMessage.unsupportedService()); }; RestWrite.prototype.handleAuthDataValidation = function (authData) { @@ -422,10 +414,7 @@ RestWrite.prototype.handleAuthDataValidation = function (authData) { } const validateAuthData = this.config.authDataManager.getValidatorForProvider(provider); if (!validateAuthData) { - throw new Parse.Error( - Parse.Error.UNSUPPORTED_SERVICE, - 'This authentication method is unsupported.' - ); + throw new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, ErrorMessage.unsupportedService()); } return validateAuthData(authData[provider]); }); @@ -549,7 +538,7 @@ RestWrite.prototype.handleAuthData = function (authData) { // Trying to update auth data but users // are different if (userResult.objectId !== userId) { - throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); + throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, ErrorMessage.accountLinked()); } // No auth data was mutated, just keep going if (!hasMutatedAuthData) { @@ -560,7 +549,7 @@ RestWrite.prototype.handleAuthData = function (authData) { return this.handleAuthDataValidation(authData).then(() => { if (results.length > 1) { // More than 1 user with the passed id's - throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, 'this auth is already used'); + throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, ErrorMessage.accountLinked()); } }); }); @@ -575,8 +564,7 @@ RestWrite.prototype.transformUser = function () { } if (!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 Parse.Error(Parse.Error.OPERATION_FORBIDDEN, ErrorMessage.clientEmailVerification()); } // Do not cleanup session if objectId is not set @@ -660,7 +648,7 @@ RestWrite.prototype._validateUserName = function () { if (results.length > 0) { throw new Parse.Error( Parse.Error.USERNAME_TAKEN, - 'Account already exists for this username.' + ErrorMessage.exists('Account', 'username') ); } return; @@ -686,7 +674,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 Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS, ErrorMessage.invalid('Email address')) ); } // Case insensitive match, see note above function. @@ -705,7 +693,7 @@ RestWrite.prototype._validateEmail = function () { if (results.length > 0) { throw new Parse.Error( Parse.Error.EMAIL_TAKEN, - 'Account already exists for this email address.' + ErrorMessage.exists('Account', 'email address') ); } if ( @@ -739,8 +727,8 @@ RestWrite.prototype._validatePasswordRequirements = function () { // b. making a custom password reset page that shows the requirements const policyError = this.config.passwordPolicy.validationError ? this.config.passwordPolicy.validationError - : 'Password does not meet the Password Policy requirements.'; - const containsUsernameError = 'Password cannot contain your username.'; + : ErrorMessage.passwordPolicy(); + const containsUsernameError = ErrorMessage.usernameInPassword(); // check whether the password meets the password strength requirements if ( @@ -817,7 +805,9 @@ RestWrite.prototype._validatePasswordHistory = function () { return Promise.reject( new Parse.Error( Parse.Error.VALIDATION_ERROR, - `New password should not be the same as last ${this.config.passwordPolicy.maxPasswordHistory} passwords.` + ErrorMessage.passwordMatchesExistingPassword( + this.config.passwordPolicy.maxPasswordHistory + ) ) ); throw err; @@ -985,12 +975,15 @@ RestWrite.prototype.handleSession = function () { } if (!this.auth.user && !this.auth.isMaster) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Session token required.'); + throw new Parse.Error( + Parse.Error.INVALID_SESSION_TOKEN, + ErrorMessage.required('Session Token', '') + ); } // 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 Parse.Error(Parse.Error.INVALID_KEY_NAME, ErrorMessage.aclSession()); } if (this.query) { @@ -1022,7 +1015,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 Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, ErrorMessage.sessionError()); } sessionData['objectId'] = results.response['objectId']; this.response = { @@ -1050,10 +1043,7 @@ RestWrite.prototype.handleInstallation = function () { !this.data.installationId && !this.auth.installationId ) { - throw new Parse.Error( - 135, - 'at least one ID field (deviceToken, installationId) ' + 'must be specified in this operation' - ); + throw new Parse.Error(135, ErrorMessage.required('deviceToken or installationId')); } // If the device token is 64 characters long, we assume it is for iOS @@ -1136,14 +1126,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 Parse.Error(Parse.Error.OBJECT_NOT_FOUND, ErrorMessage.notFound('Object')); } 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 Parse.Error(136, ErrorMessage.noOp('installationId')); } if ( this.data.deviceToken && @@ -1152,14 +1142,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 Parse.Error(136, ErrorMessage.noOp('deviceToken')); } 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 Parse.Error(136, ErrorMessage.noOp('deviceType')); } } @@ -1172,7 +1162,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 Parse.Error(135, ErrorMessage.reqOp('deviceType')); } }) .then(() => { @@ -1188,11 +1178,7 @@ RestWrite.prototype.handleInstallation = function () { // can just return the match. return deviceTokenMatches[0]['objectId']; } else if (!this.data.installationId) { - throw new Parse.Error( - 132, - 'Must specify installationId when deviceToken ' + - 'matches multiple Installation objects' - ); + throw new Parse.Error(132, ErrorMessage.installationIdWithDeviceToken()); } else { // Multiple device token matches and we specified an installation ID, // or a single match where both the passed and matching objects have @@ -1314,7 +1300,7 @@ RestWrite.prototype.runDatabaseOperation = function () { if (this.className === '_User' && this.query && this.auth.isUnauthenticated()) { throw new Parse.Error( Parse.Error.SESSION_MISSING, - `Cannot modify user ${this.query.objectId}.` + ErrorMessage.noUpdate('User', this.query.objectId) ); } @@ -1325,7 +1311,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 Parse.Error(Parse.Error.INVALID_ACL, ErrorMessage.invalidAcl()); } if (this.query) { @@ -1433,14 +1419,14 @@ RestWrite.prototype.runDatabaseOperation = function () { if (error && error.userInfo && error.userInfo.duplicated_field === 'username') { throw new Parse.Error( Parse.Error.USERNAME_TAKEN, - 'Account already exists for this username.' + ErrorMessage.exists('Account', 'username') ); } if (error && error.userInfo && error.userInfo.duplicated_field === 'email') { throw new Parse.Error( Parse.Error.EMAIL_TAKEN, - 'Account already exists for this email address.' + ErrorMessage.exists('Account', 'email address') ); } @@ -1461,7 +1447,7 @@ RestWrite.prototype.runDatabaseOperation = function () { if (results.length > 0) { throw new Parse.Error( Parse.Error.USERNAME_TAKEN, - 'Account already exists for this username.' + ErrorMessage.exists('Account', 'username') ); } return this.config.database.find( @@ -1474,13 +1460,10 @@ RestWrite.prototype.runDatabaseOperation = function () { if (results.length > 0) { throw new Parse.Error( Parse.Error.EMAIL_TAKEN, - 'Account already exists for this email address.' + ErrorMessage.exists('Account', 'email address') ); } - throw new Parse.Error( - Parse.Error.DUPLICATE_VALUE, - 'A duplicate value for a field with unique values was provided' - ); + throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, ErrorMessage.duplicateValue()); }); }) .then(response => { diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index cdce6a1348..ac88fac5c5 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -10,6 +10,7 @@ import passwordCrypto from '../password'; import { maybeRunTrigger, Types as TriggerTypes } from '../triggers'; import { promiseEnsureIdempotency } from '../middlewares'; import RestWrite from '../RestWrite'; +import { ErrorMessage } from '../Errors/message'; export class UsersRouter extends ClassesRouter { className() { @@ -73,17 +74,23 @@ 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 Parse.Error( + Parse.Error.USERNAME_MISSING, + ErrorMessage.required('username/email', '') + ); } if (!password) { - throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required.'); + throw new Parse.Error(Parse.Error.PASSWORD_MISSING, ErrorMessage.required('password', '')); } 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 Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + ErrorMessage.invalid('username/password') + ); } let user; @@ -100,7 +107,10 @@ export class UsersRouter extends ClassesRouter { .find('_User', query) .then(results => { if (!results.length) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); + throw new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + ErrorMessage.invalid('username/password') + ); } if (results.length > 1) { @@ -122,21 +132,30 @@ export class UsersRouter extends ClassesRouter { }) .then(() => { if (!isValidPassword) { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); + throw new Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + ErrorMessage.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 Parse.Error( + Parse.Error.OBJECT_NOT_FOUND, + ErrorMessage.invalid('username/password') + ); } if ( req.config.verifyUserEmails && req.config.preventLoginWithUnverifiedEmail && !user.emailVerified ) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); + throw new Parse.Error( + Parse.Error.EMAIL_NOT_FOUND, + ErrorMessage.unverified('User email') + ); } this._sanitizeAuthData(user); @@ -151,7 +170,10 @@ 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 Parse.Error( + Parse.Error.INVALID_SESSION_TOKEN, + ErrorMessage.invalid('Session token', false) + ); } const sessionToken = req.info.sessionToken; return rest @@ -166,7 +188,10 @@ 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 Parse.Error( + Parse.Error.INVALID_SESSION_TOKEN, + ErrorMessage.invalid('Session token', false) + ); } else { const user = response.results[0].user; // Send token back on the login, because SDKs expect that. @@ -269,7 +294,10 @@ 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 Parse.Error( + Parse.Error.OPERATION_FORBIDDEN, + ErrorMessage.required('Master Key', '') + ); } const userId = req.body.userId || req.query.userId; @@ -283,7 +311,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 Parse.Error(Parse.Error.OBJECT_NOT_FOUND, ErrorMessage.notFound('User')); } this._sanitizeAuthData(user); @@ -376,7 +404,7 @@ export class UsersRouter extends ClassesRouter { // 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, - 'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.' + ErrorMessage.fieldMissingForVerificationFunc() ); } else { throw e; @@ -389,13 +417,10 @@ 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 Parse.Error(Parse.Error.EMAIL_MISSING, ErrorMessage.required('email')); } if (typeof email !== 'string') { - throw new Parse.Error( - Parse.Error.INVALID_EMAIL_ADDRESS, - 'you must provide a valid email string' - ); + throw new Parse.Error(Parse.Error.INVALID_EMAIL_ADDRESS, ErrorMessage.invalid('email')); } const userController = req.config.userController; return userController.sendPasswordResetEmail(email).then( @@ -423,7 +448,7 @@ 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 Parse.Error(Parse.Error.EMAIL_MISSING, ErrorMessage.required('email')); } if (typeof email !== 'string') { throw new Parse.Error( @@ -434,7 +459,7 @@ export class UsersRouter extends ClassesRouter { return req.config.database.find('_User', { email: email }).then(results => { if (!results.length || results.length < 1) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}`); + throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, ErrorMessage.notFound('User')); } const user = results[0]; @@ -442,7 +467,10 @@ 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 Parse.Error( + Parse.Error.OTHER_CAUSE, + ErrorMessage.verified('Email ' + user.email) + ); } const userController = req.config.userController;