fix: Server internal error details leaking in error messages returned to clients (#9937)
This commit is contained in:
@@ -20,6 +20,7 @@ import { StorageAdapter } from '../Adapters/Storage/StorageAdapter';
|
||||
import SchemaCache from '../Adapters/Cache/SchemaCache';
|
||||
import DatabaseController from './DatabaseController';
|
||||
import Config from '../Config';
|
||||
import { createSanitizedError } from '../Error';
|
||||
// @flow-disable-next
|
||||
import deepcopy from 'deepcopy';
|
||||
import type {
|
||||
@@ -1403,12 +1404,12 @@ export default class SchemaController {
|
||||
if (perms['requiresAuthentication']) {
|
||||
// If aclGroup has * (public)
|
||||
if (!aclGroup || aclGroup.length == 0) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Permission denied, user needs to be authenticated.'
|
||||
);
|
||||
} else if (aclGroup.indexOf('*') > -1 && aclGroup.length == 1) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OBJECT_NOT_FOUND,
|
||||
'Permission denied, user needs to be authenticated.'
|
||||
);
|
||||
@@ -1425,7 +1426,7 @@ export default class SchemaController {
|
||||
|
||||
// Reject create when write lockdown
|
||||
if (permissionField == 'writeUserFields' && operation == 'create') {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Permission denied for action ${operation} on class ${className}.`
|
||||
);
|
||||
@@ -1448,7 +1449,7 @@ export default class SchemaController {
|
||||
}
|
||||
}
|
||||
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Permission denied for action ${operation} on class ${className}.`
|
||||
);
|
||||
|
||||
44
src/Error.js
Normal file
44
src/Error.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import defaultLogger from './logger';
|
||||
|
||||
/**
|
||||
* Creates a sanitized error that hides detailed information from clients
|
||||
* while logging the detailed message server-side.
|
||||
*
|
||||
* @param {number} errorCode - The Parse.Error code (e.g., Parse.Error.OPERATION_FORBIDDEN)
|
||||
* @param {string} detailedMessage - The detailed error message to log server-side
|
||||
* @returns {Parse.Error} A Parse.Error with sanitized message
|
||||
*/
|
||||
function createSanitizedError(errorCode, detailedMessage) {
|
||||
// On testing we need to add a prefix to the message to allow to find the correct call in the TestUtils.js file
|
||||
if (process.env.TESTING) {
|
||||
defaultLogger.error('Sanitized error:', detailedMessage);
|
||||
} else {
|
||||
defaultLogger.error(detailedMessage);
|
||||
}
|
||||
|
||||
return new Parse.Error(errorCode, 'Permission denied');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a sanitized error from a regular Error object
|
||||
* Used for non-Parse.Error errors (e.g., Express errors)
|
||||
*
|
||||
* @param {number} statusCode - HTTP status code (e.g., 403)
|
||||
* @param {string} detailedMessage - The detailed error message to log server-side
|
||||
* @returns {Error} An Error with sanitized message
|
||||
*/
|
||||
function createSanitizedHttpError(statusCode, detailedMessage) {
|
||||
// On testing we need to add a prefix to the message to allow to find the correct call in the TestUtils.js file
|
||||
if (process.env.TESTING) {
|
||||
defaultLogger.error('Sanitized error:', detailedMessage);
|
||||
} else {
|
||||
defaultLogger.error(detailedMessage);
|
||||
}
|
||||
|
||||
const error = new Error();
|
||||
error.status = statusCode;
|
||||
error.message = 'Permission denied';
|
||||
return error;
|
||||
}
|
||||
|
||||
export { createSanitizedError, createSanitizedHttpError };
|
||||
@@ -6,6 +6,7 @@ import * as schemaTypes from './schemaTypes';
|
||||
import { transformToParse, transformToGraphQL } from '../transformers/schemaFields';
|
||||
import { enforceMasterKeyAccess } from '../parseGraphQLUtils';
|
||||
import { getClass } from './schemaQueries';
|
||||
import { createSanitizedError } from '../../Error';
|
||||
|
||||
const load = parseGraphQLSchema => {
|
||||
const createClassMutation = mutationWithClientMutationId({
|
||||
@@ -33,9 +34,9 @@ const load = parseGraphQLSchema => {
|
||||
enforceMasterKeyAccess(auth);
|
||||
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to create a schema."
|
||||
"read-only masterKey isn't allowed to create a schema.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ const load = parseGraphQLSchema => {
|
||||
enforceMasterKeyAccess(auth);
|
||||
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to update a schema."
|
||||
);
|
||||
@@ -133,9 +134,9 @@ const load = parseGraphQLSchema => {
|
||||
enforceMasterKeyAccess(auth);
|
||||
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to delete a schema."
|
||||
"read-only masterKey isn't allowed to delete a schema.",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ import Parse from 'parse/node';
|
||||
import rest from '../../rest';
|
||||
import { extractKeysAndInclude } from './parseClassTypes';
|
||||
import { Auth } from '../../Auth';
|
||||
import { createSanitizedError } from '../../Error';
|
||||
|
||||
const getUserFromSessionToken = async (context, queryInfo, keysPrefix, userId) => {
|
||||
const { info, config } = context;
|
||||
if (!info || !info.sessionToken) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
const sessionToken = info.sessionToken;
|
||||
const selectedFields = getFieldNames(queryInfo)
|
||||
@@ -62,7 +63,7 @@ const getUserFromSessionToken = async (context, queryInfo, keysPrefix, userId) =
|
||||
info.context
|
||||
);
|
||||
if (!response.results || response.results.length == 0) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
} else {
|
||||
const user = response.results[0];
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import Parse from 'parse/node';
|
||||
import { GraphQLError } from 'graphql';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export function enforceMasterKeyAccess(auth) {
|
||||
if (!auth.isMaster) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'unauthorized: master key is required');
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'unauthorized: master key is required',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const triggers = require('./triggers');
|
||||
const { continueWhile } = require('parse/lib/node/promiseUtils');
|
||||
const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL'];
|
||||
const { enforceRoleSecurity } = require('./SharedRest');
|
||||
const { createSanitizedError } = require('./Error');
|
||||
|
||||
// restOptions can include:
|
||||
// skip
|
||||
@@ -120,7 +121,7 @@ function _UnsafeRestQuery(
|
||||
if (!this.auth.isMaster) {
|
||||
if (this.className == '_Session') {
|
||||
if (!this.auth.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
this.restWhere = {
|
||||
$and: [
|
||||
@@ -421,7 +422,7 @@ _UnsafeRestQuery.prototype.validateClientClassCreation = function () {
|
||||
.then(schemaController => schemaController.hasClass(this.className))
|
||||
.then(hasClass => {
|
||||
if (hasClass !== true) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to access ' + 'non-existent class: ' + this.className
|
||||
);
|
||||
@@ -800,7 +801,7 @@ _UnsafeRestQuery.prototype.denyProtectedFields = async function () {
|
||||
) || [];
|
||||
for (const key of protectedFields) {
|
||||
if (this.restWhere[key]) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`This user is not allowed to query ${key} on class ${this.className}`
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import RestQuery from './RestQuery';
|
||||
import _ from 'lodash';
|
||||
import logger from './logger';
|
||||
import { requiredColumns } from './Controllers/SchemaController';
|
||||
import { createSanitizedError } from './Error';
|
||||
|
||||
// query and data are both provided in REST API format. So data
|
||||
// types are encoded by plain old objects.
|
||||
@@ -29,9 +30,9 @@ import { requiredColumns } from './Controllers/SchemaController';
|
||||
// for the _User class.
|
||||
function RestWrite(config, auth, className, query, data, originalData, clientSDK, context, action) {
|
||||
if (auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'Cannot perform a write operation when using readOnlyMasterKey'
|
||||
'Cannot perform a write operation when using readOnlyMasterKey',
|
||||
);
|
||||
}
|
||||
this.config = config;
|
||||
@@ -199,9 +200,9 @@ RestWrite.prototype.validateClientClassCreation = function () {
|
||||
.then(schemaController => schemaController.hasClass(this.className))
|
||||
.then(hasClass => {
|
||||
if (hasClass !== true) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'This user is not allowed to access ' + 'non-existent class: ' + this.className
|
||||
'This user is not allowed to access non-existent class: ' + this.className,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -566,7 +567,6 @@ RestWrite.prototype.handleAuthData = async function (authData) {
|
||||
|
||||
// User found with provided authData
|
||||
if (results.length === 1) {
|
||||
|
||||
this.storage.authProvider = Object.keys(authData).join(',');
|
||||
|
||||
const { hasMutatedAuthData, mutatedAuthData } = Auth.hasMutatedAuthData(
|
||||
@@ -660,8 +660,10 @@ RestWrite.prototype.checkRestrictedFields = async function () {
|
||||
}
|
||||
|
||||
if (!this.auth.isMaintenance && !this.auth.isMaster && 'emailVerified' in this.data) {
|
||||
const error = `Clients aren't allowed to manually update email verification.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"Clients aren't allowed to manually update email verification."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1450,7 +1452,7 @@ RestWrite.prototype.runDatabaseOperation = function () {
|
||||
}
|
||||
|
||||
if (this.className === '_User' && this.query && this.auth.isUnauthenticated()) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.SESSION_MISSING,
|
||||
`Cannot modify user ${this.query.objectId}.`
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import rest from '../rest';
|
||||
import _ from 'lodash';
|
||||
import Parse from 'parse/node';
|
||||
import { promiseEnsureIdempotency } from '../middlewares';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
const ALLOWED_GET_QUERY_KEYS = [
|
||||
'keys',
|
||||
@@ -111,7 +112,7 @@ export class ClassesRouter extends PromiseRouter {
|
||||
typeof req.body?.objectId === 'string' &&
|
||||
req.body.objectId.startsWith('role:')
|
||||
) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.');
|
||||
throw createSanitizedError(Parse.Error.OPERATION_FORBIDDEN, 'Invalid object ID.');
|
||||
}
|
||||
return rest.create(
|
||||
req.config,
|
||||
|
||||
@@ -5,6 +5,7 @@ import Config from '../Config';
|
||||
import logger from '../logger';
|
||||
const triggers = require('../triggers');
|
||||
const Utils = require('../Utils');
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export class FilesRouter {
|
||||
expressRouter({ maxUploadSize = '20Mb' } = {}) {
|
||||
@@ -43,7 +44,7 @@ export class FilesRouter {
|
||||
const config = Config.get(req.params.appId);
|
||||
if (!config) {
|
||||
res.status(403);
|
||||
const err = new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Invalid application ID.');
|
||||
const err = createSanitizedError(Parse.Error.OPERATION_FORBIDDEN, 'Invalid application ID.');
|
||||
res.json({ code: err.code, error: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import Parse from 'parse/node';
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import * as triggers from '../triggers';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
const getConfigFromParams = params => {
|
||||
const config = new Parse.Config();
|
||||
@@ -41,9 +42,9 @@ export class GlobalConfigRouter extends PromiseRouter {
|
||||
|
||||
async updateGlobalConfig(req) {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to update the config."
|
||||
"read-only masterKey isn't allowed to update the config.",
|
||||
);
|
||||
}
|
||||
const params = req.body.params || {};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Parse from 'parse/node';
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
const GraphQLConfigPath = '/graphql-config';
|
||||
|
||||
@@ -14,9 +15,9 @@ export class GraphQLRouter extends PromiseRouter {
|
||||
|
||||
async updateGraphQLConfig(req) {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to update the GraphQL config."
|
||||
"read-only masterKey isn't allowed to update the GraphQL config.",
|
||||
);
|
||||
}
|
||||
const data = await req.config.parseGraphQLController.updateGraphQLConfig(req.body?.params || {});
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import Parse from 'parse/node';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export class PurgeRouter extends PromiseRouter {
|
||||
handlePurge(req) {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to purge a schema."
|
||||
"read-only masterKey isn't allowed to purge a schema.",
|
||||
);
|
||||
}
|
||||
return req.config.database
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import { Parse } from 'parse/node';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export class PushRouter extends PromiseRouter {
|
||||
mountRoutes() {
|
||||
@@ -9,9 +10,9 @@ export class PushRouter extends PromiseRouter {
|
||||
|
||||
static handlePOST(req) {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to send push notifications."
|
||||
"read-only masterKey isn't allowed to send push notifications.",
|
||||
);
|
||||
}
|
||||
const pushController = req.config.pushController;
|
||||
|
||||
@@ -5,6 +5,7 @@ var Parse = require('parse/node').Parse,
|
||||
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from '../middlewares';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
function classNameMismatchResponse(bodyClass, pathClass) {
|
||||
throw new Parse.Error(
|
||||
@@ -72,9 +73,9 @@ export const internalUpdateSchema = async (className, body, config) => {
|
||||
async function createSchema(req) {
|
||||
checkIfDefinedSchemasIsUsed(req);
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to create a schema."
|
||||
"read-only masterKey isn't allowed to create a schema.",
|
||||
);
|
||||
}
|
||||
if (req.params.className && req.body?.className) {
|
||||
@@ -94,9 +95,9 @@ async function createSchema(req) {
|
||||
function modifySchema(req) {
|
||||
checkIfDefinedSchemasIsUsed(req);
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to update a schema."
|
||||
"read-only masterKey isn't allowed to update a schema.",
|
||||
);
|
||||
}
|
||||
if (req.body?.className && req.body.className != req.params.className) {
|
||||
@@ -109,9 +110,9 @@ function modifySchema(req) {
|
||||
|
||||
const deleteSchema = req => {
|
||||
if (req.auth.isReadOnly) {
|
||||
throw new Parse.Error(
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
"read-only masterKey isn't allowed to delete a schema."
|
||||
"read-only masterKey isn't allowed to delete a schema.",
|
||||
);
|
||||
}
|
||||
if (!SchemaController.classNameIsValid(req.params.className)) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { promiseEnsureIdempotency } from '../middlewares';
|
||||
import RestWrite from '../RestWrite';
|
||||
import { logger } from '../logger';
|
||||
import { createSanitizedError } from '../Error';
|
||||
|
||||
export class UsersRouter extends ClassesRouter {
|
||||
className() {
|
||||
@@ -171,7 +172,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
handleMe(req) {
|
||||
if (!req.info || !req.info.sessionToken) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
const sessionToken = req.info.sessionToken;
|
||||
return rest
|
||||
@@ -186,7 +187,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
)
|
||||
.then(response => {
|
||||
if (!response.results || response.results.length == 0 || !response.results[0].user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
} else {
|
||||
const user = response.results[0].user;
|
||||
// Send token back on the login, because SDKs expect that.
|
||||
@@ -334,7 +335,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 createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
'master key is required',
|
||||
);
|
||||
}
|
||||
|
||||
const userId = req.body?.userId || req.query.userId;
|
||||
|
||||
@@ -6,12 +6,16 @@ const classesWithMasterOnlyAccess = [
|
||||
'_JobSchedule',
|
||||
'_Idempotency',
|
||||
];
|
||||
const { createSanitizedError } = require('./Error');
|
||||
|
||||
// Disallowing access to the _Role collection except by master key
|
||||
function enforceRoleSecurity(method, className, auth) {
|
||||
if (className === '_Installation' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (method === 'delete' || method === 'find') {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the installation collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Clients aren't allowed to perform the ${method} operation on the installation collection.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +25,18 @@ function enforceRoleSecurity(method, className, auth) {
|
||||
!auth.isMaster &&
|
||||
!auth.isMaintenance
|
||||
) {
|
||||
const error = `Clients aren't allowed to perform the ${method} operation on the ${className} collection.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`Clients aren't allowed to perform the ${method} operation on the ${className} collection.`
|
||||
);
|
||||
}
|
||||
|
||||
// readOnly masterKey is not allowed
|
||||
if (auth.isReadOnly && (method === 'delete' || method === 'create' || method === 'update')) {
|
||||
const error = `read-only masterKey isn't allowed to perform the ${method} operation.`;
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
throw createSanitizedError(
|
||||
Parse.Error.OPERATION_FORBIDDEN,
|
||||
`read-only masterKey isn't allowed to perform the ${method} operation.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,3 +81,4 @@ export class Connections {
|
||||
return this.sockets.size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { pathToRegexp } from 'path-to-regexp';
|
||||
import RedisStore from 'rate-limit-redis';
|
||||
import { createClient } from 'redis';
|
||||
import { BlockList, isIPv4 } from 'net';
|
||||
import { createSanitizedHttpError } from './Error';
|
||||
|
||||
export const DEFAULT_ALLOWED_HEADERS =
|
||||
'X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control';
|
||||
@@ -501,8 +502,9 @@ export function handleParseErrors(err, req, res, next) {
|
||||
|
||||
export function enforceMasterKeyAccess(req, res, next) {
|
||||
if (!req.auth.isMaster) {
|
||||
res.status(403);
|
||||
res.end('{"error":"unauthorized: master key is required"}');
|
||||
const error = createSanitizedHttpError(403, 'unauthorized: master key is required');
|
||||
res.status(error.status);
|
||||
res.end(`{"error":"${error.message}"}`);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
@@ -510,10 +512,7 @@ export function enforceMasterKeyAccess(req, res, next) {
|
||||
|
||||
export function promiseEnforceMasterKeyAccess(request) {
|
||||
if (!request.auth.isMaster) {
|
||||
const error = new Error();
|
||||
error.status = 403;
|
||||
error.message = 'unauthorized: master key is required';
|
||||
throw error;
|
||||
throw createSanitizedHttpError(403, 'unauthorized: master key is required');
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ var RestQuery = require('./RestQuery');
|
||||
var RestWrite = require('./RestWrite');
|
||||
var triggers = require('./triggers');
|
||||
const { enforceRoleSecurity } = require('./SharedRest');
|
||||
const { createSanitizedError } = require('./Error');
|
||||
|
||||
function checkTriggers(className, config, types) {
|
||||
return types.some(triggerType => {
|
||||
@@ -195,7 +196,7 @@ function del(config, auth, className, objectId, context) {
|
||||
firstResult.className = className;
|
||||
if (className === '_Session' && !auth.isMaster && !auth.isMaintenance) {
|
||||
if (!auth.user || firstResult.user.objectId !== auth.user.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
throw createSanitizedError(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
|
||||
}
|
||||
}
|
||||
var cacheAdapter = config.cacheController;
|
||||
@@ -326,7 +327,7 @@ function handleSessionMissingError(error, className, auth) {
|
||||
!auth.isMaster &&
|
||||
!auth.isMaintenance
|
||||
) {
|
||||
throw new Parse.Error(Parse.Error.SESSION_MISSING, 'Insufficient auth.');
|
||||
throw createSanitizedError(Parse.Error.SESSION_MISSING, 'Insufficient auth.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user