diff --git a/CHANGELOG.md b/CHANGELOG.md index f4abc5f613..9882230ba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,42 @@ ## Parse Server Changelog ### master -[Full Changelog](https://github.com/parse-community/parse-server/compare/4.3.0...master) +[Full Changelog](https://github.com/parse-community/parse-server/compare/4.4.0...master) + +### 4.4.0 +[Full Changelog](https://github.com/parse-community/parse-server/compare/4.3.0...4.4.0) +- IMPROVE: Update PostgresStorageAdapter.js. [#6981](https://github.com/parse-community/parse-server/pull/6981). Thanks to [Vitaly Tomilov](https://github.com/vitaly-t) +- NEW: skipWithMasterKey on Built-In Validator. [#6972](https://github.com/parse-community/parse-server/issues/6972). Thanks to [dblythy](https://github.com/dblythy). +- NEW: Add fileKey rotation to GridFSBucketAdapter. [#6768](https://github.com/parse-community/parse-server/pull/6768). Thanks to [Corey Baker](https://github.com/cbaker6). +- IMPROVE: Remove unused parameter in Cloud Function. [#6969](https://github.com/parse-community/parse-server/issues/6969). Thanks to [Diamond Lewis](https://github.com/dplewis). +- IMPROVE: Validation Handler Update. [#6968](https://github.com/parse-community/parse-server/issues/6968). Thanks to [dblythy](https://github.com/dblythy). +- FIX: (directAccess): Properly handle response status. [#6966](https://github.com/parse-community/parse-server/issues/6966). Thanks to [Diamond Lewis](https://github.com/dplewis). +- FIX: Remove hostnameMaxLen for Mongo URL. [#6693](https://github.com/parse-community/parse-server/issues/6693). Thanks to [markhoward02](https://github.com/markhoward02). +- IMPROVE: Show a message if cloud functions are duplicated. [#6963](https://github.com/parse-community/parse-server/issues/6963). Thanks to [dblythy](https://github.com/dblythy). +- FIX: Pass request.query to afterFind. [#6960](https://github.com/parse-community/parse-server/issues/6960). Thanks to [dblythy](https://github.com/dblythy). +- SECURITY FIX: Patch session vulnerability over Live Query. See [GHSA-2xm2-xj2q-qgpj](https://github.com/parse-community/parse-server/security/advisories/GHSA-2xm2-xj2q-qgpj) for more details about the vulnerability and [78b59fb](https://github.com/parse-community/parse-server/commit/78b59fb26b1c36e3cdbd42ba9fec025003267f58) for the fix. Thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo). +- IMPROVE: LiveQueryEvent Error Logging Improvements. [#6951](https://github.com/parse-community/parse-server/issues/6951). Thanks to [dblythy](https://github.com/dblythy). +- IMPROVE: Include stack in Cloud Code. [#6958](https://github.com/parse-community/parse-server/issues/6958). Thanks to [dblythy](https://github.com/dblythy). +- FIX: (jobs): Add Error Message to JobStatus Failure. [#6954](https://github.com/parse-community/parse-server/issues/6954). Thanks to [Diamond Lewis](https://github.com/dplewis). +- NEW: Create Cloud function afterLiveQueryEvent. [#6859](https://github.com/parse-community/parse-server/issues/6859). Thanks to [dblythy](https://github.com/dblythy). +- FIX: Update vkontakte API to the latest version. [#6944](https://github.com/parse-community/parse-server/issues/6944). Thanks to [Antonio Davi Macedo Coelho de Castro](https://github.com/davimacedo). +- FIX: Use an empty object as default value of options for Google Sign in. [#6844](https://github.com/parse-community/parse-server/issues/6844). Thanks to [Kevin Kuang](https://github.com/kvnkuang). +- FIX: Postgres: prepend className to unique indexes. [#6741](https://github.com/parse-community/parse-server/pull/6741). Thanks to [Corey Baker](https://github.com/cbaker6). +- FIX: GraphQL: Transform input types also on user mutations. [#6934](https://github.com/parse-community/parse-server/pull/6934). Thanks to [Antoine Cormouls](https://github.com/Moumouls). +- FIX: Set objectId into query for Email Validation. [#6930](https://github.com/parse-community/parse-server/pull/6930). Thanks to [Danaru](https://github.com/Danaru87). +- FIX: GraphQL: Optimize queries, fixes some null returns (on object), fix stitched GraphQLUpload. [#6709](https://github.com/parse-community/parse-server/pull/6709). Thanks to [Antoine Cormouls](https://github.com/Moumouls). +- FIX: Do not throw error if user provide a pointer like index onMongo. [#6923](https://github.com/parse-community/parse-server/pull/6923). Thanks to [Antoine Cormouls](https://github.com/Moumouls). +- FIX: Hotfix instagram api. [#6922](https://github.com/parse-community/parse-server/issues/6922). Thanks to [Tim](https://github.com/timination). +- FIX: (directAccess/cloud-code): Pass installationId with LogIn. [#6903](https://github.com/parse-community/parse-server/issues/6903). Thanks to [Diamond Lewis](https://github.com/dplewis). +- FIX: Fix bcrypt binary incompatibility. [#6891](https://github.com/parse-community/parse-server/issues/6891). Thanks to [Manuel Trezza](https://github.com/mtrezza). +- NEW: Keycloak auth adapter. [#6376](https://github.com/parse-community/parse-server/issues/6376). Thanks to [Rhuan](https://github.com/rhuanbarreto). +- IMPROVE: Changed incorrect key name in apple auth adapter tests. [#6861](https://github.com/parse-community/parse-server/issues/6861). Thanks to [Manuel Trezza](https://github.com/mtrezza). +- FIX: Fix mutating beforeSubscribe Query. [#6868](https://github.com/parse-community/parse-server/issues/6868). Thanks to [dblythy](https://github.com/dblythy). +- FIX: Fix beforeLogin for users logging in with AuthData. [#6872](https://github.com/parse-community/parse-server/issues/6872). Thanks to [Kevin Kuang](https://github.com/kvnkuang). +- FIX: Remove Facebook AccountKit auth. [#6870](https://github.com/parse-community/parse-server/issues/6870). Thanks to [Diamond Lewis](https://github.com/dplewis). +- FIX: Updated TOKEN_ISSUER to 'accounts.google.com'. [#6836](https://github.com/parse-community/parse-server/issues/6836). Thanks to [Arjun Vedak](https://github.com/arjun3396). - IMPROVE: Optimized deletion of class field from schema by using an index if available to do an index scan instead of a collection scan. [#6815](https://github.com/parse-community/parse-server/issues/6815). Thanks to [Manuel Trezza](https://github.com/mtrezza). +- IMPROVE: Enable MongoDB transaction test for MongoDB >= 4.0.4 [#6827](https://github.com/parse-community/parse-server/pull/6827). Thanks to [Manuel](https://github.com/mtrezza). ### 4.3.0 [Full Changelog](https://github.com/parse-community/parse-server/compare/4.2.0...4.3.0) diff --git a/README.md b/README.md index 99181820bf..b500230c13 100644 --- a/README.md +++ b/README.md @@ -92,14 +92,22 @@ $ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongo ### Inside a Docker container + ```bash $ git clone https://github.com/parse-community/parse-server $ cd parse-server $ docker build --tag parse-server . $ docker run --name my-mongo -d mongo -$ docker run --name my-parse-server -v cloud-code-vol:/parse-server/cloud -v config-vol:/parse-server/config -p 1337:1337 --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test ``` +#### Running the Parse Server Image + +```bash +$ docker run --name my-parse-server -v config-vol:/parse-server/config -p 1337:1337 --link my-mongo:mongo -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test +``` + +***Note:*** *If you want to use [Cloud Code](https://docs.parseplatform.org/cloudcode/guide/) feature, please add `-v cloud-code-vol:/parse-server/cloud --cloud /parse-server/cloud/main.js` to command above. Make sure the `main.js` file is available in the `cloud-code-vol` directory before run this command. Otherwise, an error will occur.* + You can use any arbitrary string as your application id and master key. These will be used by your clients to authenticate with the Parse Server. That's it! You are now running a standalone version of Parse Server on your machine. @@ -206,7 +214,7 @@ var app = express(); var api = new ParseServer({ databaseURI: 'mongodb://localhost:27017/dev', // Connection string for your MongoDB database - cloud: '/home/myApp/cloud/main.js', // Absolute path to your Cloud Code + cloud: './cloud/main.js', // Path to your Cloud Code appId: 'myAppId', masterKey: 'myMasterKey', // Keep this key secret! fileKey: 'optionalFileKey', @@ -472,9 +480,16 @@ $ git clone https://github.com/parse-community/parse-server $ cd parse-server $ docker build --tag parse-server . $ docker run --name my-mongo -d mongo -$ docker run --name my-parse-server --link my-mongo:mongo -v cloud-code-vol:/parse-server/cloud -v config-vol:/parse-server/config -p 1337:1337 -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test --publicServerURL http://localhost:1337/parse --mountGraphQL --mountPlayground ``` +#### Running the Parse Server Image + +```bash +$ docker run --name my-parse-server --link my-mongo:mongo -v config-vol:/parse-server/config -p 1337:1337 -d parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://mongo/test --publicServerURL http://localhost:1337/parse --mountGraphQL --mountPlayground +``` + +***Note:*** *If you want to use [Cloud Code](https://docs.parseplatform.org/cloudcode/guide/) feature, please add `-v cloud-code-vol:/parse-server/cloud --cloud /parse-server/cloud/main.js` to command above. Make sure the `main.js` file is available in the `cloud-code-vol` directory before run this command. Otherwise, an error will occur.* + After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API. ***Note:*** Do ***NOT*** use --mountPlayground option in production. [Parse Dashboard](https://github.com/parse-community/parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps. diff --git a/package-lock.json b/package-lock.json index 268921f28a..ef031dace3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "4.3.0", + "version": "4.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10448,25 +10448,17 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.3.3.tgz", - "integrity": "sha512-wmUyoQM/Xzmo62wgOdQAn5tl7u+IA1ZYK7qbuppi+3E+Gj4hlUxVHjInulieWrd0SfHi/ADriTb5ILJ/lsJrSg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.4.0.tgz", + "integrity": "sha512-01LcNrAf+mBI46c78mE86I5o5KkOM942lLiSBdiCfgHTR+oUNIjh1fKClWeoPNHJz2oXe/VUSqtk1vwAQYwWEg==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", - "pg-connection-string": "^2.3.0", + "pg-connection-string": "^2.4.0", "pg-pool": "^3.2.1", - "pg-protocol": "^1.2.5", + "pg-protocol": "^1.3.0", "pg-types": "^2.1.0", - "pgpass": "1.x", - "semver": "4.3.2" - }, - "dependencies": { - "semver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" - } + "pgpass": "1.x" } }, "pg-connection-string": { @@ -10485,17 +10477,17 @@ "integrity": "sha512-ujanxJJB9CSDUvlAOshtjdKAywOPR2vY0a7D+vvgk5rbrYcthZA7TjpN+Z+UwZsz/G/bUexYDT6huE33vYVN0g==" }, "pg-pool": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.1.tgz", - "integrity": "sha512-BQDPWUeKenVrMMDN9opfns/kZo4lxmSWhIqo+cSAF7+lfi9ZclQbr9vfnlNaPr8wYF3UYjm5X0yPAhbcgqNOdA==" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz", + "integrity": "sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA==" }, "pg-promise": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.6.2.tgz", - "integrity": "sha512-MrS7JatbX3cBsL5hjojkdcUggV+514Owkx+LJfrKS2STAt1MAQAGmBLDQVglrKmFA5Yus8ZKPKsQaXEJcU5MdA==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/pg-promise/-/pg-promise-10.7.0.tgz", + "integrity": "sha512-jQui6HvPUpvnFGDo8hxJ1/4KamqRGjYan7+QApp+dA7hOv5GIJP+IRZayYqsX6+ktHqUFqxvn3se+Bd4WW/vmw==", "requires": { "assert-options": "0.6.2", - "pg": "8.3.3", + "pg": "8.4.0", "pg-minify": "1.6.1", "spex": "3.0.2" } @@ -10518,11 +10510,11 @@ } }, "pgpass": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", - "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", "requires": { - "split": "^1.0.0" + "split2": "^3.1.1" } }, "picomatch": { @@ -11626,14 +11618,6 @@ "resolved": "https://registry.npmjs.org/spex/-/spex-3.0.2.tgz", "integrity": "sha512-ZNCrOso+oNv5P01HCO4wuxV9Og5rS6ms7gGAqugfBPjx1QwfNXJI3T02ldfaap1O0dlT1sB0Rk+mhDqxt3Z27w==" }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2" - } - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -11644,6 +11628,26 @@ "extend-shallow": "^3.0.0" } }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12037,7 +12041,8 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true }, "through2": { "version": "0.6.5", diff --git a/package.json b/package.json index f5a9b4b6fa..f41d8c4ac7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-server", - "version": "4.3.0", + "version": "4.4.0", "description": "An express module providing a Parse-compatible API server", "main": "lib/index.js", "repository": { @@ -48,7 +48,7 @@ "mime": "2.4.6", "mongodb": "3.6.2", "parse": "2.17.0", - "pg-promise": "10.6.2", + "pg-promise": "10.7.0", "pluralize": "8.0.0", "redis": "3.0.2", "semver": "7.3.2", diff --git a/resources/buildConfigDefinitions.js b/resources/buildConfigDefinitions.js index a640e1c3c7..e73070c7db 100644 --- a/resources/buildConfigDefinitions.js +++ b/resources/buildConfigDefinitions.js @@ -55,6 +55,9 @@ function getENVPrefix(iface) { if (iface.id.name === 'IdempotencyOptions') { return 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_'; } + if (iface.id.name === 'FileUploadOptions') { + return 'PARSE_SERVER_FILE_UPLOAD_'; + } } function processProperty(property, iface) { @@ -173,7 +176,7 @@ function parseDefaultValue(elt, value, t) { }); literalValue = t.objectExpression(props); } - if (type == 'IdempotencyOptions') { + if (type == 'IdempotencyOptions' || type == 'FileUploadOptions') { const object = parsers.objectParser(value); const props = Object.keys(object).map((key) => { return t.objectProperty(key, object[value]); diff --git a/spec/ParseFile.spec.js b/spec/ParseFile.spec.js index 410d15c81b..bdd84bb36c 100644 --- a/spec/ParseFile.spec.js +++ b/spec/ParseFile.spec.js @@ -860,4 +860,126 @@ describe('Parse.File testing', () => { }); }); }); + + describe('file upload restrictions', () => { + it('can reject file upload with unspecified', async () => { + await reconfigureServer({ + fileUpload: {}, + }); + try { + const file = new Parse.File('hello.txt', data, 'text/plain'); + await file.save(); + fail('should not have been able to save file.'); + } catch (e) { + expect(e.code).toBe(130); + expect(e.message).toBe('Public file upload is not enabled.'); + } + }); + it('disable file upload', async () => { + await reconfigureServer({ + fileUpload: { + enabledForPublic: false, + enabledForAnonymousUser: false, + enabledForAuthenticatedUser: false, + }, + }); + try { + const file = new Parse.File('hello.txt', data, 'text/plain'); + await file.save(); + fail('should not have been able to save file.'); + } catch (e) { + expect(e.code).toBe(130); + expect(e.message).toBe('Public file upload is not enabled.'); + } + }); + it('disable for public', async () => { + await reconfigureServer({ + fileUpload: { + enabledForPublic: false, + }, + }); + try { + const file = new Parse.File('hello.txt', data, 'text/plain'); + await file.save(); + fail('should not have been able to save file.'); + } catch (e) { + expect(e.code).toBe(130); + expect(e.message).toBe('Public file upload is not enabled.'); + } + }); + + it('disable for public allow user', async () => { + await reconfigureServer({ + fileUpload: { + enabledForPublic: false, + }, + }); + try { + const user = await Parse.User.signUp('myUser', 'password'); + const file = new Parse.File('hello.txt', data, 'text/plain'); + await file.save({ sessionToken: user.getSessionToken() }); + } catch (e) { + fail('should have allowed file to save.'); + } + }); + + it('disable for anonymous', async () => { + await reconfigureServer({ + fileUpload: { + enabledForAnonymousUser: false, + }, + }); + try { + const user = await Parse.AnonymousUtils.logIn(); + const file = new Parse.File('hello.txt', data, 'text/plain'); + await file.save({ sessionToken: user.getSessionToken() }); + fail('should not have been able to save file.'); + } catch (e) { + expect(e.code).toBe(130); + expect(e.message).toBe('Anonymous file upload is not enabled.'); + } + }); + + it('enable for anonymous', async () => { + await reconfigureServer({ + fileUpload: { + enabledForPublic: false, + enabledForAnonymousUser: true, + }, + }); + try { + const user = await Parse.AnonymousUtils.logIn(); + const file = new Parse.File('hello.txt', data, 'text/plain'); + await file.save({ sessionToken: user.getSessionToken() }); + } catch (e) { + fail('should have allowed file to save.'); + } + }); + + it('enable for anonymous but not authenticated', async () => { + await reconfigureServer({ + fileUpload: { + enabledForPublic: false, + enabledForAnonymousUser: true, + enabledForAuthenticatedUser: false, + }, + }); + try { + const user = await Parse.AnonymousUtils.logIn(); + const file = new Parse.File('hello.txt', data, 'text/plain'); + await file.save({ sessionToken: user.getSessionToken() }); + } catch (e) { + fail('should have allowed file to save.'); + } + try { + const user = await Parse.User.signUp('myUser', 'password'); + const file = new Parse.File('hello.txt', data, 'text/plain'); + await file.save({ sessionToken: user.getSessionToken() }); + fail('should have not allowed file to save.'); + } catch (e) { + expect(e.code).toBe(130); + expect(e.message).toBe('Authenticated file upload is not enabled.'); + } + }); + }); }); diff --git a/spec/helper.js b/spec/helper.js index a7f6cf2280..385b0aa51b 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -88,6 +88,9 @@ const defaultConfiguration = { fileKey: 'test', silent, logLevel, + fileUpload: { + enabledForPublic: true, + }, push: { android: { senderId: 'yolo', diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 3ca9cd43f9..aa2ddf3f40 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -12,7 +12,6 @@ const PostgresDuplicateColumnError = '42701'; const PostgresMissingColumnError = '42703'; const PostgresDuplicateObjectError = '42710'; const PostgresUniqueIndexViolationError = '23505'; -const PostgresTransactionAbortedError = '25P02'; const logger = require('../../../logger'); const debug = function (...args: any) { @@ -986,29 +985,21 @@ export class PostgresStorageAdapter implements StorageAdapter { conn = conn || this._client; return conn .tx('create-class', async t => { - const q1 = this.createTable(className, schema, t); - const q2 = t.none( + await this.createTable(className, schema, t); + await t.none( 'INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($, $, true)', { className, schema } ); - const q3 = this.setIndexesWithSchemaFormat( + await this.setIndexesWithSchemaFormat( className, schema.indexes, {}, schema.fields, t ); - // TODO: The test should not verify the returned value, and then - // the method can be simplified, to avoid returning useless stuff. - return t.batch([q1, q2, q3]); - }) - .then(() => { return toParseSchema(schema); }) .catch(err => { - if (err.data[0].result.code === PostgresTransactionAbortedError) { - err = err.data[1].result; - } if ( err.code === PostgresUniqueIndexViolationError && err.detail.includes(className) @@ -2463,20 +2454,19 @@ export class PostgresStorageAdapter implements StorageAdapter { }); return Promise.all(promises) .then(() => { - return this._client.tx('perform-initialization', t => { - return t.batch([ - t.none(sql.misc.jsonObjectSetKeys), - t.none(sql.array.add), - t.none(sql.array.addUnique), - t.none(sql.array.remove), - t.none(sql.array.containsAll), - t.none(sql.array.containsAllRegex), - t.none(sql.array.contains), - ]); + return this._client.tx('perform-initialization', async t => { + await t.none(sql.misc.jsonObjectSetKeys); + await t.none(sql.array.add); + await t.none(sql.array.addUnique); + await t.none(sql.array.remove); + await t.none(sql.array.containsAll); + await t.none(sql.array.containsAllRegex); + await t.none(sql.array.contains); + return t.ctx; }); }) - .then(data => { - debug(`initializationDone in ${data.duration}`); + .then(ctx => { + debug(`initializationDone in ${ctx.duration}`); }) .catch(error => { /* eslint-disable no-console */ diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index c3c1271786..c0011bf194 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -149,10 +149,6 @@ module.exports.ParseServerOptions = { action: parsers.booleanParser, default: false, }, - encryptionKey: { - env: 'PARSE_SERVER_ENCRYPTION_KEY', - help: 'Key for encrypting your files', - }, expireInactiveSessions: { env: 'PARSE_SERVER_EXPIRE_INACTIVE_SESSIONS', help: 'Sets wether we should expire the inactive sessions, defaults to true', @@ -168,6 +164,12 @@ module.exports.ParseServerOptions = { help: 'Adapter module for the files sub-system', action: parsers.moduleOrObjectParser, }, + fileUpload: { + env: 'PARSE_SERVER_FILE_UPLOAD_OPTIONS', + help: 'Options for file uploads', + action: parsers.objectParser, + default: {}, + }, graphQLPath: { env: 'PARSE_SERVER_GRAPHQL_PATH', help: 'Mount path for the GraphQL endpoint, defaults to /graphql', @@ -545,3 +547,24 @@ module.exports.IdempotencyOptions = { default: 300, }, }; +module.exports.FileUploadOptions = { + enabledForAnonymousUser: { + env: 'PARSE_SERVER_FILE_UPLOAD_ENABLED_FOR_ANONYMOUS_USER', + help: 'File upload is enabled for Anonymous Users.', + action: parsers.booleanParser, + default: false, + }, + enabledForAuthenticatedUser: { + env: 'PARSE_SERVER_FILE_UPLOAD_ENABLED_FOR_AUTHENTICATED_USER', + help: 'File upload is enabled for authenticated users.', + action: parsers.booleanParser, + default: true, + }, + enabledForPublic: { + env: 'PARSE_SERVER_FILE_UPLOAD_ENABLED_FOR_PUBLIC', + help: + 'File upload is enabled for anyone with access to the Parse Server file upload endpoint, regardless of user authentication.', + action: parsers.booleanParser, + default: false, + }, +}; diff --git a/src/Options/docs.js b/src/Options/docs.js index febe3d77cc..c036059232 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -30,6 +30,7 @@ * @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true * @property {String} fileKey Key for your files * @property {Adapter} filesAdapter Adapter module for the files sub-system + * @property {FileUploadOptions} fileUpload Options for file uploads * @property {String} graphQLPath Mount path for the GraphQL endpoint, defaults to /graphql * @property {String} graphQLSchema Full path to your GraphQL custom schema.graphql file * @property {String} host The host to serve ParseServer on, defaults to 0.0.0.0 @@ -118,3 +119,10 @@ * @property {String[]} paths An array of paths for which the feature should be enabled. The mount path must not be included, for example instead of `/parse/functions/myFunction` specifiy `functions/myFunction`. The entries are interpreted as regular expression, for example `functions/.*` matches all functions, `jobs/.*` matches all jobs, `classes/.*` matches all classes, `.*` matches all paths. * @property {Number} ttl The duration in seconds after which a request record is discarded from the database, defaults to 300s. */ + +/** + * @interface FileUploadOptions + * @property {Boolean} enabledForAnonymousUser File upload is enabled for Anonymous Users. + * @property {Boolean} enabledForAuthenticatedUser File upload is enabled for authenticated users. + * @property {Boolean} enabledForPublic File upload is enabled for anyone with access to the Parse Server file upload endpoint, regardless of user authentication. + */ diff --git a/src/Options/index.js b/src/Options/index.js index 1283f849a7..f06ce6eba0 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -192,6 +192,10 @@ export interface ParseServerOptions { :ENV: PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS :DEFAULT: false */ idempotencyOptions: ?IdempotencyOptions; + /* Options for file uploads + :ENV: PARSE_SERVER_FILE_UPLOAD_OPTIONS + :DEFAULT: false */ + fileUpload: ?FileUploadOptions; /* Full path to your GraphQL custom schema.graphql file */ graphQLSchema: ?string; /* Mounts the GraphQL endpoint @@ -285,3 +289,14 @@ export interface IdempotencyOptions { :DEFAULT: 300 */ ttl: ?number; } +export interface FileUploadOptions { + /* File upload is enabled for Anonymous Users. + :DEFAULT: false */ + enabledForAnonymousUser: ?boolean; + /* File upload is enabled for anyone with access to the Parse Server file upload endpoint, regardless of user authentication. + :DEFAULT: false */ + enabledForPublic: ?boolean; + /* File upload is enabled for authenticated users. + :DEFAULT: true */ + enabledForAuthenticatedUser: ?boolean; +} diff --git a/src/Routers/FilesRouter.js b/src/Routers/FilesRouter.js index 2b0140fe7d..bb333a8c4f 100644 --- a/src/Routers/FilesRouter.js +++ b/src/Routers/FilesRouter.js @@ -94,6 +94,29 @@ export class FilesRouter { async createHandler(req, res, next) { const config = req.config; + if ( + !req.config.fileUpload.enabledForAnonymousUser && + req.auth.user && + Parse.AnonymousUtils.isLinked(req.auth.user) + ) { + next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Anonymous file upload is not enabled.')); + return; + } + if ( + !req.config.fileUpload.enabledForAuthenticatedUser && + req.config.fileUpload.enabledForAuthenticatedUser != null && + req.auth.user && + !Parse.AnonymousUtils.isLinked(req.auth.user) + ) { + next( + new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Authenticated file upload is not enabled.') + ); + return; + } + if (!req.config.fileUpload.enabledForPublic && !req.auth.user) { + next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Public file upload is not enabled.')); + return; + } const filesController = config.filesController; const { filename } = req.params; const contentType = req.get('Content-type');