FileUpload options for Server Config (#7071)
* New: fileUpload options to restrict file uploads * review changes * update review * Update helper.js * added complete fileUpload values for tests * fixed config validation * allow file upload only for authenicated user by default * fixed inconsistent error messages * consolidated and extended tests * minor compacting * removed irregular whitespace * added changelog entry * always allow file upload with master key * fix lint * removed fit Co-authored-by: Manuel Trezza <trezza.m@gmail.com>
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
### master
|
### master
|
||||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/4.5.0...master)
|
[Full Changelog](https://github.com/parse-community/parse-server/compare/4.5.0...master)
|
||||||
|
|
||||||
|
__BREAKING CHANGES:__
|
||||||
|
- NEW: Added file upload restriction. File upload is now only allowed for authenticated users by default for improved security. To allow file upload also for Anonymous Users or Public, set the `fileUpload` parameter in the [Parse Server Options](https://parseplatform.org/parse-server/api/master/ParseServerOptions.html). [#7071](https://github.com/parse-community/parse-server/pull/7071). Thanks to [dblythy](https://github.com/dblythy).
|
||||||
|
___
|
||||||
- IMPROVE: Optimize queries on classes with pointer permissions. [#7061](https://github.com/parse-community/parse-server/pull/7061). Thanks to [Pedro Diaz](https://github.com/pdiaz)
|
- IMPROVE: Optimize queries on classes with pointer permissions. [#7061](https://github.com/parse-community/parse-server/pull/7061). Thanks to [Pedro Diaz](https://github.com/pdiaz)
|
||||||
|
|
||||||
### 4.5.0
|
### 4.5.0
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ function getENVPrefix(iface) {
|
|||||||
'LiveQueryOptions' : 'PARSE_SERVER_LIVEQUERY_',
|
'LiveQueryOptions' : 'PARSE_SERVER_LIVEQUERY_',
|
||||||
'IdempotencyOptions' : 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_',
|
'IdempotencyOptions' : 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_',
|
||||||
'AccountLockoutOptions' : 'PARSE_SERVER_ACCOUNT_LOCKOUT_',
|
'AccountLockoutOptions' : 'PARSE_SERVER_ACCOUNT_LOCKOUT_',
|
||||||
'PasswordPolicyOptions' : 'PARSE_SERVER_PASSWORD_POLICY_'
|
'PasswordPolicyOptions' : 'PARSE_SERVER_PASSWORD_POLICY_',
|
||||||
|
'FileUploadOptions' : 'PARSE_SERVER_FILE_UPLOAD_'
|
||||||
}
|
}
|
||||||
if (options[iface.id.name]) {
|
if (options[iface.id.name]) {
|
||||||
return options[iface.id.name]
|
return options[iface.id.name]
|
||||||
@@ -163,14 +164,8 @@ function parseDefaultValue(elt, value, t) {
|
|||||||
if (type == 'NumberOrBoolean') {
|
if (type == 'NumberOrBoolean') {
|
||||||
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
|
literalValue = t.numericLiteral(parsers.numberOrBoolParser('')(value));
|
||||||
}
|
}
|
||||||
if (type == 'CustomPagesOptions') {
|
const literalTypes = ['IdempotencyOptions','FileUploadOptions','CustomPagesOptions'];
|
||||||
const object = parsers.objectParser(value);
|
if (literalTypes.includes(type)) {
|
||||||
const props = Object.keys(object).map((key) => {
|
|
||||||
return t.objectProperty(key, object[value]);
|
|
||||||
});
|
|
||||||
literalValue = t.objectExpression(props);
|
|
||||||
}
|
|
||||||
if (type == 'IdempotencyOptions') {
|
|
||||||
const object = parsers.objectParser(value);
|
const object = parsers.objectParser(value);
|
||||||
const props = Object.keys(object).map((key) => {
|
const props = Object.keys(object).map((key) => {
|
||||||
return t.objectProperty(key, object[value]);
|
return t.objectProperty(key, object[value]);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const request = require('../lib/request');
|
const request = require('../lib/request');
|
||||||
|
const Definitions = require('../src/Options/Definitions');
|
||||||
|
|
||||||
const str = 'Hello World!';
|
const str = 'Hello World!';
|
||||||
const data = [];
|
const data = [];
|
||||||
@@ -860,4 +861,196 @@ describe('Parse.File testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('file upload configuration', () => {
|
||||||
|
it('allows file upload only for authenticated user by default', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
fileUpload: {
|
||||||
|
enableForPublic: Definitions.FileUploadOptions.enableForPublic.default,
|
||||||
|
enableForAnonymousUser: Definitions.FileUploadOptions.enableForAnonymousUser.default,
|
||||||
|
enableForAuthenticatedUser: Definitions.FileUploadOptions.enableForAuthenticatedUser.default,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
await expectAsync(file.save()).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by public is disabled.')
|
||||||
|
);
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const anonUser = await Parse.AnonymousUtils.logIn();
|
||||||
|
await expectAsync(file.save({ sessionToken: anonUser.getSessionToken() })).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by anonymous user is disabled.')
|
||||||
|
);
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const authUser = await Parse.User.signUp('user', 'password');
|
||||||
|
await expectAsync(file.save({ sessionToken: authUser.getSessionToken() })).toBeResolved();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows file upload with master key', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
fileUpload: {
|
||||||
|
enableForPublic: false,
|
||||||
|
enableForAnonymousUser: false,
|
||||||
|
enableForAuthenticatedUser: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
await expectAsync(file.save({ useMasterKey: true })).toBeResolved();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects all file uploads', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
fileUpload: {
|
||||||
|
enableForPublic: false,
|
||||||
|
enableForAnonymousUser: false,
|
||||||
|
enableForAuthenticatedUser: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
await expectAsync(file.save()).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by public is disabled.')
|
||||||
|
);
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const anonUser = await Parse.AnonymousUtils.logIn();
|
||||||
|
await expectAsync(file.save({ sessionToken: anonUser.getSessionToken() })).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by anonymous user is disabled.')
|
||||||
|
);
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const authUser = await Parse.User.signUp('user', 'password');
|
||||||
|
await expectAsync(file.save({ sessionToken: authUser.getSessionToken() })).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by authenticated user is disabled.')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows all file uploads', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
fileUpload: {
|
||||||
|
enableForPublic: true,
|
||||||
|
enableForAnonymousUser: true,
|
||||||
|
enableForAuthenticatedUser: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
await expectAsync(file.save()).toBeResolved();
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const anonUser = await Parse.AnonymousUtils.logIn();
|
||||||
|
await expectAsync(file.save({ sessionToken: anonUser.getSessionToken() })).toBeResolved();
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const authUser = await Parse.User.signUp('user', 'password');
|
||||||
|
await expectAsync(file.save({ sessionToken: authUser.getSessionToken() })).toBeResolved();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows file upload only for public', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
fileUpload: {
|
||||||
|
enableForPublic: true,
|
||||||
|
enableForAnonymousUser: false,
|
||||||
|
enableForAuthenticatedUser: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
await expectAsync(file.save()).toBeResolved();
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const anonUser = await Parse.AnonymousUtils.logIn();
|
||||||
|
await expectAsync(file.save({ sessionToken: anonUser.getSessionToken() })).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by anonymous user is disabled.')
|
||||||
|
);
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const authUser = await Parse.User.signUp('user', 'password');
|
||||||
|
await expectAsync(file.save({ sessionToken: authUser.getSessionToken() })).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by authenticated user is disabled.')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows file upload only for anonymous user', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
fileUpload: {
|
||||||
|
enableForPublic: false,
|
||||||
|
enableForAnonymousUser: true,
|
||||||
|
enableForAuthenticatedUser: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
await expectAsync(file.save()).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by public is disabled.')
|
||||||
|
);
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const anonUser = await Parse.AnonymousUtils.logIn();
|
||||||
|
await expectAsync(file.save({ sessionToken: anonUser.getSessionToken() })).toBeResolved();
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const authUser = await Parse.User.signUp('user', 'password');
|
||||||
|
await expectAsync(file.save({ sessionToken: authUser.getSessionToken() })).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by authenticated user is disabled.')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows file upload only for authenticated user', async () => {
|
||||||
|
await reconfigureServer({
|
||||||
|
fileUpload: {
|
||||||
|
enableForPublic: false,
|
||||||
|
enableForAnonymousUser: false,
|
||||||
|
enableForAuthenticatedUser: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
await expectAsync(file.save()).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by public is disabled.')
|
||||||
|
);
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const anonUser = await Parse.AnonymousUtils.logIn();
|
||||||
|
await expectAsync(file.save({ sessionToken: anonUser.getSessionToken() })).toBeRejectedWith(
|
||||||
|
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by anonymous user is disabled.')
|
||||||
|
);
|
||||||
|
file = new Parse.File('hello.txt', data, 'text/plain');
|
||||||
|
const authUser = await Parse.User.signUp('user', 'password');
|
||||||
|
await expectAsync(file.save({ sessionToken: authUser.getSessionToken() })).toBeResolved();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects invalid fileUpload configuration', async () => {
|
||||||
|
const invalidConfigs = [
|
||||||
|
{ fileUpload: [] },
|
||||||
|
{ fileUpload: 1 },
|
||||||
|
{ fileUpload: "string" },
|
||||||
|
];
|
||||||
|
const validConfigs = [
|
||||||
|
{ fileUpload: {} },
|
||||||
|
{ fileUpload: null },
|
||||||
|
{ fileUpload: undefined },
|
||||||
|
];
|
||||||
|
const keys = [
|
||||||
|
"enableForPublic",
|
||||||
|
"enableForAnonymousUser",
|
||||||
|
"enableForAuthenticatedUser",
|
||||||
|
];
|
||||||
|
const invalidValues = [
|
||||||
|
[],
|
||||||
|
{},
|
||||||
|
1,
|
||||||
|
"string",
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
const validValues = [
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
];
|
||||||
|
for (const config of invalidConfigs) {
|
||||||
|
await expectAsync(reconfigureServer(config)).toBeRejectedWith(
|
||||||
|
'fileUpload must be an object value.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const config of validConfigs) {
|
||||||
|
await expectAsync(reconfigureServer(config)).toBeResolved();
|
||||||
|
}
|
||||||
|
for (const key of keys) {
|
||||||
|
for (const value of invalidValues) {
|
||||||
|
await expectAsync(reconfigureServer({ fileUpload: { [key]: value }})).toBeRejectedWith(
|
||||||
|
`fileUpload.${key} must be a boolean value.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const value of validValues) {
|
||||||
|
await expectAsync(reconfigureServer({ fileUpload: { [key]: value }})).toBeResolved();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ const defaultConfiguration = {
|
|||||||
fileKey: 'test',
|
fileKey: 'test',
|
||||||
silent,
|
silent,
|
||||||
logLevel,
|
logLevel,
|
||||||
|
fileUpload: {
|
||||||
|
enableForPublic: true,
|
||||||
|
enableForAnonymousUser: true,
|
||||||
|
enableForAuthenticatedUser: true,
|
||||||
|
},
|
||||||
push: {
|
push: {
|
||||||
android: {
|
android: {
|
||||||
senderId: 'yolo',
|
senderId: 'yolo',
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import AppCache from './cache';
|
|||||||
import SchemaCache from './Controllers/SchemaCache';
|
import SchemaCache from './Controllers/SchemaCache';
|
||||||
import DatabaseController from './Controllers/DatabaseController';
|
import DatabaseController from './Controllers/DatabaseController';
|
||||||
import net from 'net';
|
import net from 'net';
|
||||||
import { IdempotencyOptions } from './Options/Definitions';
|
import {
|
||||||
|
IdempotencyOptions,
|
||||||
|
FileUploadOptions,
|
||||||
|
} from './Options/Definitions';
|
||||||
|
|
||||||
function removeTrailingSlash(str) {
|
function removeTrailingSlash(str) {
|
||||||
if (!str) {
|
if (!str) {
|
||||||
@@ -71,6 +74,7 @@ export class Config {
|
|||||||
allowHeaders,
|
allowHeaders,
|
||||||
idempotencyOptions,
|
idempotencyOptions,
|
||||||
emailVerifyTokenReuseIfValid,
|
emailVerifyTokenReuseIfValid,
|
||||||
|
fileUpload,
|
||||||
}) {
|
}) {
|
||||||
if (masterKey === readOnlyMasterKey) {
|
if (masterKey === readOnlyMasterKey) {
|
||||||
throw new Error('masterKey and readOnlyMasterKey should be different');
|
throw new Error('masterKey and readOnlyMasterKey should be different');
|
||||||
@@ -88,8 +92,8 @@ export class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.validateAccountLockoutPolicy(accountLockout);
|
this.validateAccountLockoutPolicy(accountLockout);
|
||||||
|
|
||||||
this.validatePasswordPolicy(passwordPolicy);
|
this.validatePasswordPolicy(passwordPolicy);
|
||||||
|
this.validateFileUploadOptions(fileUpload);
|
||||||
|
|
||||||
if (typeof revokeSessionOnPasswordReset !== 'boolean') {
|
if (typeof revokeSessionOnPasswordReset !== 'boolean') {
|
||||||
throw 'revokeSessionOnPasswordReset must be a boolean value';
|
throw 'revokeSessionOnPasswordReset must be a boolean value';
|
||||||
@@ -245,6 +249,30 @@ export class Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static validateFileUploadOptions(fileUpload) {
|
||||||
|
if (!fileUpload) {
|
||||||
|
fileUpload = {};
|
||||||
|
}
|
||||||
|
if (typeof fileUpload !== 'object' || fileUpload instanceof Array) {
|
||||||
|
throw 'fileUpload must be an object value.';
|
||||||
|
}
|
||||||
|
if (fileUpload.enableForAnonymousUser === undefined) {
|
||||||
|
fileUpload.enableForAnonymousUser = FileUploadOptions.enableForAnonymousUser.default;
|
||||||
|
} else if (typeof fileUpload.enableForAnonymousUser !== 'boolean') {
|
||||||
|
throw 'fileUpload.enableForAnonymousUser must be a boolean value.';
|
||||||
|
}
|
||||||
|
if (fileUpload.enableForPublic === undefined) {
|
||||||
|
fileUpload.enableForPublic = FileUploadOptions.enableForPublic.default;
|
||||||
|
} else if (typeof fileUpload.enableForPublic !== 'boolean') {
|
||||||
|
throw 'fileUpload.enableForPublic must be a boolean value.';
|
||||||
|
}
|
||||||
|
if (fileUpload.enableForAuthenticatedUser === undefined) {
|
||||||
|
fileUpload.enableForAuthenticatedUser = FileUploadOptions.enableForAuthenticatedUser.default;
|
||||||
|
} else if (typeof fileUpload.enableForAuthenticatedUser !== 'boolean') {
|
||||||
|
throw 'fileUpload.enableForAuthenticatedUser must be a boolean value.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static validateMasterKeyIps(masterKeyIps) {
|
static validateMasterKeyIps(masterKeyIps) {
|
||||||
for (const ip of masterKeyIps) {
|
for (const ip of masterKeyIps) {
|
||||||
if (!net.isIP(ip)) {
|
if (!net.isIP(ip)) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@
|
|||||||
* @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true
|
* @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true
|
||||||
* @property {String} fileKey Key for your files
|
* @property {String} fileKey Key for your files
|
||||||
* @property {Adapter<FilesAdapter>} filesAdapter Adapter module for the files sub-system
|
* @property {Adapter<FilesAdapter>} 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} graphQLPath Mount path for the GraphQL endpoint, defaults to /graphql
|
||||||
* @property {String} graphQLSchema Full path to your GraphQL custom schema.graphql file
|
* @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
|
* @property {String} host The host to serve ParseServer on, defaults to 0.0.0.0
|
||||||
@@ -137,3 +138,10 @@
|
|||||||
* @property {Function} validatorCallback a callback function to be invoked to validate the password
|
* @property {Function} validatorCallback a callback function to be invoked to validate the password
|
||||||
* @property {String} validatorPattern a RegExp object or a regex string representing the pattern to enforce
|
* @property {String} validatorPattern a RegExp object or a regex string representing the pattern to enforce
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface FileUploadOptions
|
||||||
|
* @property {Boolean} enableForAnonymousUser Is true if file upload should be allowed for anonymous users.
|
||||||
|
* @property {Boolean} enableForAuthenticatedUser Is true if file upload should be allowed for authenticated users.
|
||||||
|
* @property {Boolean} enableForPublic Is true if file upload should be allowed for anyone, regardless of user authentication.
|
||||||
|
*/
|
||||||
|
|||||||
@@ -198,6 +198,9 @@ export interface ParseServerOptions {
|
|||||||
:ENV: PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS
|
:ENV: PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS
|
||||||
:DEFAULT: false */
|
:DEFAULT: false */
|
||||||
idempotencyOptions: ?IdempotencyOptions;
|
idempotencyOptions: ?IdempotencyOptions;
|
||||||
|
/* Options for file uploads
|
||||||
|
:ENV: PARSE_SERVER_FILE_UPLOAD_OPTIONS */
|
||||||
|
fileUpload: ?FileUploadOptions;
|
||||||
/* Full path to your GraphQL custom schema.graphql file */
|
/* Full path to your GraphQL custom schema.graphql file */
|
||||||
graphQLSchema: ?string;
|
graphQLSchema: ?string;
|
||||||
/* Mounts the GraphQL endpoint
|
/* Mounts the GraphQL endpoint
|
||||||
@@ -315,3 +318,15 @@ export interface PasswordPolicyOptions {
|
|||||||
/* resend token if it's still valid */
|
/* resend token if it's still valid */
|
||||||
resetTokenReuseIfValid: ?boolean;
|
resetTokenReuseIfValid: ?boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FileUploadOptions {
|
||||||
|
/* Is true if file upload should be allowed for anonymous users.
|
||||||
|
:DEFAULT: false */
|
||||||
|
enableForAnonymousUser: ?boolean;
|
||||||
|
/* Is true if file upload should be allowed for authenticated users.
|
||||||
|
:DEFAULT: true */
|
||||||
|
enableForAuthenticatedUser: ?boolean;
|
||||||
|
/* Is true if file upload should be allowed for anyone, regardless of user authentication.
|
||||||
|
:DEFAULT: false */
|
||||||
|
enableForPublic: ?boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -94,6 +94,27 @@ export class FilesRouter {
|
|||||||
|
|
||||||
async createHandler(req, res, next) {
|
async createHandler(req, res, next) {
|
||||||
const config = req.config;
|
const config = req.config;
|
||||||
|
const user = req.auth.user;
|
||||||
|
const isMaster = req.auth.isMaster;
|
||||||
|
const isLinked = user && Parse.AnonymousUtils.isLinked(user);
|
||||||
|
if (!isMaster && !config.fileUpload.enableForAnonymousUser && isLinked) {
|
||||||
|
next(new Parse.Error(
|
||||||
|
Parse.Error.FILE_SAVE_ERROR,
|
||||||
|
'File upload by anonymous user is disabled.'
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isMaster && !config.fileUpload.enableForAuthenticatedUser && !isLinked && user) {
|
||||||
|
next(new Parse.Error(
|
||||||
|
Parse.Error.FILE_SAVE_ERROR,
|
||||||
|
'File upload by authenticated user is disabled.'
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isMaster && !config.fileUpload.enableForPublic && !user) {
|
||||||
|
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by public is disabled.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const filesController = config.filesController;
|
const filesController = config.filesController;
|
||||||
const { filename } = req.params;
|
const { filename } = req.params;
|
||||||
const contentType = req.get('Content-type');
|
const contentType = req.get('Content-type');
|
||||||
|
|||||||
Reference in New Issue
Block a user