Use Prettier JS (#5017)

* Adds prettier

* Run lint before tests
This commit is contained in:
Florent Vilmart
2018-09-01 13:58:06 -04:00
committed by GitHub
parent 189cd259ee
commit d83a0b6808
240 changed files with 41098 additions and 29020 deletions

View File

@@ -1,8 +1,8 @@
import ClassesRouter from './ClassesRouter';
import rest from '../rest';
import * as middleware from '../middlewares';
import Parse from 'parse/node';
import UsersRouter from './UsersRouter';
import Parse from 'parse/node';
import UsersRouter from './UsersRouter';
const BASE_KEYS = ['where', 'distinct', 'pipeline'];
@@ -37,9 +37,11 @@ const PIPELINE_KEYS = [
const ALLOWED_KEYS = [...BASE_KEYS, ...PIPELINE_KEYS];
export class AggregateRouter extends ClassesRouter {
handleFind(req) {
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
const body = Object.assign(
req.body,
ClassesRouter.JSONFromQuery(req.query)
);
const options = {};
if (body.distinct) {
options.distinct = String(body.distinct);
@@ -48,14 +50,23 @@ export class AggregateRouter extends ClassesRouter {
if (typeof body.where === 'string') {
body.where = JSON.parse(body.where);
}
return rest.find(req.config, req.auth, this.className(req), body.where, options, req.info.clientSDK).then((response) => {
for(const result of response.results) {
if(typeof result === 'object') {
UsersRouter.removeHiddenProperties(result);
return rest
.find(
req.config,
req.auth,
this.className(req),
body.where,
options,
req.info.clientSDK
)
.then(response => {
for (const result of response.results) {
if (typeof result === 'object') {
UsersRouter.removeHiddenProperties(result);
}
}
}
return { response };
});
return { response };
});
}
/* Builds a pipeline from the body. Originally the body could be passed as a single object,
@@ -87,13 +98,17 @@ export class AggregateRouter extends ClassesRouter {
let pipeline = body.pipeline || body;
if (!Array.isArray(pipeline)) {
pipeline = Object.keys(pipeline).map((key) => { return { [key]: pipeline[key] } });
pipeline = Object.keys(pipeline).map(key => {
return { [key]: pipeline[key] };
});
}
return pipeline.map((stage) => {
return pipeline.map(stage => {
const keys = Object.keys(stage);
if (keys.length != 1) {
throw new Error(`Pipeline stages should only have one key found ${keys.join(', ')}`);
throw new Error(
`Pipeline stages should only have one key found ${keys.join(', ')}`
);
}
return AggregateRouter.transformStage(keys[0], stage);
});
@@ -126,7 +141,14 @@ export class AggregateRouter extends ClassesRouter {
}
mountRoutes() {
this.route('GET','/aggregate/:className', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleFind(req); });
this.route(
'GET',
'/aggregate/:className',
middleware.promiseEnforceMasterKeyAccess,
req => {
return this.handleFind(req);
}
);
}
}

View File

@@ -11,10 +11,9 @@ function trackEvent(req) {
return analyticsController.trackEvent(req);
}
export class AnalyticsRouter extends PromiseRouter {
mountRoutes() {
this.route('POST','/events/AppOpened', appOpened);
this.route('POST','/events/:eventName', trackEvent);
this.route('POST', '/events/AppOpened', appOpened);
this.route('POST', '/events/:eventName', trackEvent);
}
}

View File

@@ -3,41 +3,84 @@ import rest from '../rest';
import * as middleware from '../middlewares';
export class AudiencesRouter extends ClassesRouter {
className() {
return '_Audience';
}
handleFind(req) {
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
const body = Object.assign(
req.body,
ClassesRouter.JSONFromQuery(req.query)
);
const options = ClassesRouter.optionsFromBody(body);
return rest.find(req.config, req.auth, '_Audience', body.where, options, req.info.clientSDK)
.then((response) => {
response.results.forEach((item) => {
return rest
.find(
req.config,
req.auth,
'_Audience',
body.where,
options,
req.info.clientSDK
)
.then(response => {
response.results.forEach(item => {
item.query = JSON.parse(item.query);
});
return {response: response};
return { response: response };
});
}
handleGet(req) {
return super.handleGet(req)
.then((data) => {
data.response.query = JSON.parse(data.response.query);
return super.handleGet(req).then(data => {
data.response.query = JSON.parse(data.response.query);
return data;
});
return data;
});
}
mountRoutes() {
this.route('GET','/push_audiences', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleFind(req); });
this.route('GET','/push_audiences/:objectId', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleGet(req); });
this.route('POST','/push_audiences', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleCreate(req); });
this.route('PUT','/push_audiences/:objectId', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleUpdate(req); });
this.route('DELETE','/push_audiences/:objectId', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleDelete(req); });
this.route(
'GET',
'/push_audiences',
middleware.promiseEnforceMasterKeyAccess,
req => {
return this.handleFind(req);
}
);
this.route(
'GET',
'/push_audiences/:objectId',
middleware.promiseEnforceMasterKeyAccess,
req => {
return this.handleGet(req);
}
);
this.route(
'POST',
'/push_audiences',
middleware.promiseEnforceMasterKeyAccess,
req => {
return this.handleCreate(req);
}
);
this.route(
'PUT',
'/push_audiences/:objectId',
middleware.promiseEnforceMasterKeyAccess,
req => {
return this.handleUpdate(req);
}
);
this.route(
'DELETE',
'/push_audiences/:objectId',
middleware.promiseEnforceMasterKeyAccess,
req => {
return this.handleDelete(req);
}
);
}
}

View File

@@ -1,21 +1,22 @@
import PromiseRouter from '../PromiseRouter';
import rest from '../rest';
import _ from 'lodash';
import Parse from 'parse/node';
import rest from '../rest';
import _ from 'lodash';
import Parse from 'parse/node';
const ALLOWED_GET_QUERY_KEYS = ['keys', 'include'];
export class ClassesRouter extends PromiseRouter {
className(req) {
return req.params.className;
}
handleFind(req) {
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
const body = Object.assign(
req.body,
ClassesRouter.JSONFromQuery(req.query)
);
const options = ClassesRouter.optionsFromBody(body);
if (req.config.maxLimit && (body.limit > req.config.maxLimit)) {
if (req.config.maxLimit && body.limit > req.config.maxLimit) {
// Silently replace the limit on the query with the max configured
options.limit = Number(req.config.maxLimit);
}
@@ -25,20 +26,34 @@ export class ClassesRouter extends PromiseRouter {
if (typeof body.where === 'string') {
body.where = JSON.parse(body.where);
}
return rest.find(req.config, req.auth, this.className(req), body.where, options, req.info.clientSDK)
.then((response) => {
return rest
.find(
req.config,
req.auth,
this.className(req),
body.where,
options,
req.info.clientSDK
)
.then(response => {
return { response: response };
});
}
// Returns a promise for a {response} object.
handleGet(req) {
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
const body = Object.assign(
req.body,
ClassesRouter.JSONFromQuery(req.query)
);
const options = {};
for (const key of Object.keys(body)) {
if (ALLOWED_GET_QUERY_KEYS.indexOf(key) === -1) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter');
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'Improper encode of parameter'
);
}
}
@@ -49,17 +64,27 @@ export class ClassesRouter extends PromiseRouter {
options.include = String(body.include);
}
return rest.get(req.config, req.auth, this.className(req), req.params.objectId, options, req.info.clientSDK)
.then((response) => {
return rest
.get(
req.config,
req.auth,
this.className(req),
req.params.objectId,
options,
req.info.clientSDK
)
.then(response => {
if (!response.results || response.results.length == 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Object not found.'
);
}
if (this.className(req) === "_User") {
if (this.className(req) === '_User') {
delete response.results[0].sessionToken;
const user = response.results[0];
const user = response.results[0];
if (req.auth.user && user.objectId == req.auth.user.id) {
// Force the session token
@@ -71,18 +96,38 @@ export class ClassesRouter extends PromiseRouter {
}
handleCreate(req) {
return rest.create(req.config, req.auth, this.className(req), req.body, req.info.clientSDK);
return rest.create(
req.config,
req.auth,
this.className(req),
req.body,
req.info.clientSDK
);
}
handleUpdate(req) {
const where = { objectId: req.params.objectId }
return rest.update(req.config, req.auth, this.className(req), where, req.body, req.info.clientSDK);
const where = { objectId: req.params.objectId };
return rest.update(
req.config,
req.auth,
this.className(req),
where,
req.body,
req.info.clientSDK
);
}
handleDelete(req) {
return rest.del(req.config, req.auth, this.className(req), req.params.objectId, req.info.clientSDK)
return rest
.del(
req.config,
req.auth,
this.className(req),
req.params.objectId,
req.info.clientSDK
)
.then(() => {
return {response: {}};
return { response: {} };
});
}
@@ -95,16 +140,28 @@ export class ClassesRouter extends PromiseRouter {
json[key] = value;
}
}
return json
return json;
}
static optionsFromBody(body) {
const allowConstraints = ['skip', 'limit', 'order', 'count', 'keys',
'include', 'includeAll', 'redirectClassNameForKey', 'where'];
const allowConstraints = [
'skip',
'limit',
'order',
'count',
'keys',
'include',
'includeAll',
'redirectClassNameForKey',
'where',
];
for (const key of Object.keys(body)) {
if (allowConstraints.indexOf(key) === -1) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, `Invalid parameter for query: ${key}`);
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Invalid parameter for query: ${key}`
);
}
}
const options = {};
@@ -135,11 +192,21 @@ export class ClassesRouter extends PromiseRouter {
}
mountRoutes() {
this.route('GET', '/classes/:className', (req) => { return this.handleFind(req); });
this.route('GET', '/classes/:className/:objectId', (req) => { return this.handleGet(req); });
this.route('POST', '/classes/:className', (req) => { return this.handleCreate(req); });
this.route('PUT', '/classes/:className/:objectId', (req) => { return this.handleUpdate(req); });
this.route('DELETE', '/classes/:className/:objectId', (req) => { return this.handleDelete(req); });
this.route('GET', '/classes/:className', req => {
return this.handleFind(req);
});
this.route('GET', '/classes/:className/:objectId', req => {
return this.handleGet(req);
});
this.route('POST', '/classes/:className', req => {
return this.handleCreate(req);
});
this.route('PUT', '/classes/:className/:objectId', req => {
return this.handleUpdate(req);
});
this.route('DELETE', '/classes/:className/:objectId', req => {
return this.handleDelete(req);
});
}
}

View File

@@ -1,8 +1,8 @@
import PromiseRouter from '../PromiseRouter';
import Parse from 'parse/node';
import rest from '../rest';
const triggers = require('../triggers');
const middleware = require('../middlewares');
import PromiseRouter from '../PromiseRouter';
import Parse from 'parse/node';
import rest from '../rest';
const triggers = require('../triggers');
const middleware = require('../middlewares');
function formatJobSchedule(job_schedule) {
if (typeof job_schedule.startAfter === 'undefined') {
@@ -14,63 +14,111 @@ function formatJobSchedule(job_schedule) {
function validateJobSchedule(config, job_schedule) {
const jobs = triggers.getJobs(config.applicationId) || {};
if (job_schedule.jobName && !jobs[job_schedule.jobName]) {
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Cannot Schedule a job that is not deployed');
throw new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'Cannot Schedule a job that is not deployed'
);
}
}
export class CloudCodeRouter extends PromiseRouter {
mountRoutes() {
this.route('GET', '/cloud_code/jobs', middleware.promiseEnforceMasterKeyAccess, CloudCodeRouter.getJobs);
this.route('GET', '/cloud_code/jobs/data', middleware.promiseEnforceMasterKeyAccess, CloudCodeRouter.getJobsData);
this.route('POST', '/cloud_code/jobs', middleware.promiseEnforceMasterKeyAccess, CloudCodeRouter.createJob);
this.route('PUT', '/cloud_code/jobs/:objectId', middleware.promiseEnforceMasterKeyAccess, CloudCodeRouter.editJob);
this.route('DELETE', '/cloud_code/jobs/:objectId', middleware.promiseEnforceMasterKeyAccess, CloudCodeRouter.deleteJob);
this.route(
'GET',
'/cloud_code/jobs',
middleware.promiseEnforceMasterKeyAccess,
CloudCodeRouter.getJobs
);
this.route(
'GET',
'/cloud_code/jobs/data',
middleware.promiseEnforceMasterKeyAccess,
CloudCodeRouter.getJobsData
);
this.route(
'POST',
'/cloud_code/jobs',
middleware.promiseEnforceMasterKeyAccess,
CloudCodeRouter.createJob
);
this.route(
'PUT',
'/cloud_code/jobs/:objectId',
middleware.promiseEnforceMasterKeyAccess,
CloudCodeRouter.editJob
);
this.route(
'DELETE',
'/cloud_code/jobs/:objectId',
middleware.promiseEnforceMasterKeyAccess,
CloudCodeRouter.deleteJob
);
}
static getJobs(req) {
return rest.find(req.config, req.auth, '_JobSchedule', {}, {}).then((scheduledJobs) => {
return {
response: scheduledJobs.results
}
});
return rest
.find(req.config, req.auth, '_JobSchedule', {}, {})
.then(scheduledJobs => {
return {
response: scheduledJobs.results,
};
});
}
static getJobsData(req) {
const config = req.config;
const jobs = triggers.getJobs(config.applicationId) || {};
return rest.find(req.config, req.auth, '_JobSchedule', {}, {}).then((scheduledJobs) => {
return {
response: {
in_use: scheduledJobs.results.map((job) => job.jobName),
jobs: Object.keys(jobs),
}
};
});
return rest
.find(req.config, req.auth, '_JobSchedule', {}, {})
.then(scheduledJobs => {
return {
response: {
in_use: scheduledJobs.results.map(job => job.jobName),
jobs: Object.keys(jobs),
},
};
});
}
static createJob(req) {
const { job_schedule } = req.body;
validateJobSchedule(req.config, job_schedule);
return rest.create(req.config, req.auth, '_JobSchedule', formatJobSchedule(job_schedule), req.client);
return rest.create(
req.config,
req.auth,
'_JobSchedule',
formatJobSchedule(job_schedule),
req.client
);
}
static editJob(req) {
const { objectId } = req.params;
const { job_schedule } = req.body;
validateJobSchedule(req.config, job_schedule);
return rest.update(req.config, req.auth, '_JobSchedule', { objectId }, formatJobSchedule(job_schedule)).then((response) => {
return {
response
}
});
return rest
.update(
req.config,
req.auth,
'_JobSchedule',
{ objectId },
formatJobSchedule(job_schedule)
)
.then(response => {
return {
response,
};
});
}
static deleteJob(req) {
const { objectId } = req.params;
return rest.del(req.config, req.auth, '_JobSchedule', objectId).then((response) => {
return {
response
}
});
return rest
.del(req.config, req.auth, '_JobSchedule', objectId)
.then(response => {
return {
response,
};
});
}
}

View File

@@ -1,56 +1,63 @@
import { version } from '../../package.json';
import PromiseRouter from '../PromiseRouter';
import * as middleware from "../middlewares";
import { version } from '../../package.json';
import PromiseRouter from '../PromiseRouter';
import * as middleware from '../middlewares';
export class FeaturesRouter extends PromiseRouter {
mountRoutes() {
this.route('GET','/serverInfo', middleware.promiseEnforceMasterKeyAccess, req => {
const features = {
globalConfig: {
create: true,
read: true,
update: true,
delete: true,
},
hooks: {
create: true,
read: true,
update: true,
delete: true,
},
cloudCode: {
jobs: true,
},
logs: {
level: true,
size: true,
order: true,
until: true,
from: true,
},
push: {
immediatePush: req.config.hasPushSupport,
scheduledPush: req.config.hasPushScheduledSupport,
storedPushData: req.config.hasPushSupport,
pushAudiences: true,
localization: true,
},
schemas: {
addField: true,
removeField: true,
addClass: true,
removeClass: true,
clearAllDataFromClass: true,
exportClass: false,
editClassLevelPermissions: true,
editPointerPermissions: true,
},
};
this.route(
'GET',
'/serverInfo',
middleware.promiseEnforceMasterKeyAccess,
req => {
const features = {
globalConfig: {
create: true,
read: true,
update: true,
delete: true,
},
hooks: {
create: true,
read: true,
update: true,
delete: true,
},
cloudCode: {
jobs: true,
},
logs: {
level: true,
size: true,
order: true,
until: true,
from: true,
},
push: {
immediatePush: req.config.hasPushSupport,
scheduledPush: req.config.hasPushScheduledSupport,
storedPushData: req.config.hasPushSupport,
pushAudiences: true,
localization: true,
},
schemas: {
addField: true,
removeField: true,
addClass: true,
removeClass: true,
clearAllDataFromClass: true,
exportClass: false,
editClassLevelPermissions: true,
editPointerPermissions: true,
},
};
return { response: {
features: features,
parseServerVersion: version,
} };
});
return {
response: {
features: features,
parseServerVersion: version,
},
};
}
);
}
}

View File

@@ -1,30 +1,37 @@
import express from 'express';
import BodyParser from 'body-parser';
import * as Middlewares from '../middlewares';
import Parse from 'parse/node';
import Config from '../Config';
import mime from 'mime';
import logger from '../logger';
import express from 'express';
import BodyParser from 'body-parser';
import * as Middlewares from '../middlewares';
import Parse from 'parse/node';
import Config from '../Config';
import mime from 'mime';
import logger from '../logger';
export class FilesRouter {
expressRouter({ maxUploadSize = '20Mb' } = {}) {
var router = express.Router();
router.get('/files/:appId/:filename', this.getHandler);
router.post('/files', function(req, res, next) {
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
'Filename not provided.'));
next(
new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename not provided.')
);
});
router.post('/files/:filename',
router.post(
'/files/:filename',
Middlewares.allowCrossDomain,
BodyParser.raw({type: () => { return true; }, limit: maxUploadSize }), // Allow uploads without Content-Type, or with any Content-Type.
BodyParser.raw({
type: () => {
return true;
},
limit: maxUploadSize,
}), // Allow uploads without Content-Type, or with any Content-Type.
Middlewares.handleParseHeaders,
this.createHandler
);
router.delete('/files/:filename',
router.delete(
'/files/:filename',
Middlewares.allowCrossDomain,
Middlewares.handleParseHeaders,
Middlewares.enforceMasterKeyAccess,
@@ -39,43 +46,55 @@ export class FilesRouter {
const filename = req.params.filename;
const contentType = mime.getType(filename);
if (isFileStreamable(req, filesController)) {
filesController.getFileStream(config, filename).then((stream) => {
handleFileStream(stream, req, res, contentType);
}).catch(() => {
res.status(404);
res.set('Content-Type', 'text/plain');
res.end('File not found.');
});
filesController
.getFileStream(config, filename)
.then(stream => {
handleFileStream(stream, req, res, contentType);
})
.catch(() => {
res.status(404);
res.set('Content-Type', 'text/plain');
res.end('File not found.');
});
} else {
filesController.getFileData(config, filename).then((data) => {
res.status(200);
res.set('Content-Type', contentType);
res.set('Content-Length', data.length);
res.end(data);
}).catch(() => {
res.status(404);
res.set('Content-Type', 'text/plain');
res.end('File not found.');
});
filesController
.getFileData(config, filename)
.then(data => {
res.status(200);
res.set('Content-Type', contentType);
res.set('Content-Length', data.length);
res.end(data);
})
.catch(() => {
res.status(404);
res.set('Content-Type', 'text/plain');
res.end('File not found.');
});
}
}
createHandler(req, res, next) {
if (!req.body || !req.body.length) {
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR,
'Invalid file upload.'));
next(
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.')
);
return;
}
if (req.params.filename.length > 128) {
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
'Filename too long.'));
next(
new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename too long.')
);
return;
}
if (!req.params.filename.match(/^[_a-zA-Z0-9][a-zA-Z0-9@\.\ ~_-]*$/)) {
next(new Parse.Error(Parse.Error.INVALID_FILE_NAME,
'Filename contains invalid characters.'));
next(
new Parse.Error(
Parse.Error.INVALID_FILE_NAME,
'Filename contains invalid characters.'
)
);
return;
}
@@ -84,35 +103,53 @@ export class FilesRouter {
const config = req.config;
const filesController = config.filesController;
filesController.createFile(config, filename, req.body, contentType).then((result) => {
res.status(201);
res.set('Location', result.url);
res.json(result);
}).catch((e) => {
logger.error(e.message, e);
next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Could not store file.'));
});
filesController
.createFile(config, filename, req.body, contentType)
.then(result => {
res.status(201);
res.set('Location', result.url);
res.json(result);
})
.catch(e => {
logger.error(e.message, e);
next(
new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Could not store file.')
);
});
}
deleteHandler(req, res, next) {
const filesController = req.config.filesController;
filesController.deleteFile(req.config, req.params.filename).then(() => {
res.status(200);
// TODO: return useful JSON here?
res.end();
}).catch(() => {
next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR,
'Could not delete file.'));
});
filesController
.deleteFile(req.config, req.params.filename)
.then(() => {
res.status(200);
// TODO: return useful JSON here?
res.end();
})
.catch(() => {
next(
new Parse.Error(
Parse.Error.FILE_DELETE_ERROR,
'Could not delete file.'
)
);
});
}
}
function isFileStreamable(req, filesController){
return req.get('Range') && typeof filesController.adapter.getFileStream === 'function';
function isFileStreamable(req, filesController) {
return (
req.get('Range') &&
typeof filesController.adapter.getFileStream === 'function'
);
}
function getRange(req) {
const parts = req.get('Range').replace(/bytes=/, "").split("-");
const parts = req
.get('Range')
.replace(/bytes=/, '')
.split('-');
return { start: parseInt(parts[0], 10), end: parseInt(parts[1], 10) };
}
@@ -121,12 +158,10 @@ function getRange(req) {
function handleFileStream(stream, req, res, contentType) {
const buffer_size = 1024 * 1024; //1024Kb
// Range request, partiall stream the file
let {
start, end
} = getRange(req);
let { start, end } = getRange(req);
const notEnded = (!end && end !== 0);
const notStarted = (!start && start !== 0);
const notEnded = !end && end !== 0;
const notStarted = !start && start !== 0;
// No end provided, we want all bytes
if (notEnded) {
end = stream.length - 1;
@@ -142,7 +177,7 @@ function handleFileStream(stream, req, res, contentType) {
end = start + buffer_size - 1;
}
const contentLength = (end - start) + 1;
const contentLength = end - start + 1;
res.writeHead(206, {
'Content-Range': 'bytes ' + start + '-' + end + '/' + stream.length,
@@ -151,14 +186,14 @@ function handleFileStream(stream, req, res, contentType) {
'Content-Type': contentType,
});
stream.seek(start, function () {
stream.seek(start, function() {
// get gridFile stream
const gridFileStream = stream.stream(true);
let bufferAvail = 0;
let remainingBytesToWrite = contentLength;
let totalBytesWritten = 0;
// write to response
gridFileStream.on('data', function (data) {
gridFileStream.on('data', function(data) {
bufferAvail += data.length;
if (bufferAvail > 0) {
// slice returns the same buffer if overflowing

View File

@@ -11,7 +11,7 @@ import { logger } from '../logger';
function parseObject(obj) {
if (Array.isArray(obj)) {
return obj.map((item) => {
return obj.map(item => {
return parseObject(item);
});
} else if (obj && obj.__type == 'Date') {
@@ -30,12 +30,20 @@ function parseParams(params) {
}
export class FunctionsRouter extends PromiseRouter {
mountRoutes() {
this.route('POST', '/functions/:functionName', FunctionsRouter.handleCloudFunction);
this.route('POST', '/jobs/:jobName', promiseEnforceMasterKeyAccess, function(req) {
return FunctionsRouter.handleCloudJob(req);
});
this.route(
'POST',
'/functions/:functionName',
FunctionsRouter.handleCloudFunction
);
this.route(
'POST',
'/jobs/:jobName',
promiseEnforceMasterKeyAccess,
function(req) {
return FunctionsRouter.handleCloudJob(req);
}
);
this.route('POST', '/jobs', promiseEnforceMasterKeyAccess, function(req) {
return FunctionsRouter.handleCloudJob(req);
});
@@ -57,27 +65,32 @@ export class FunctionsRouter extends PromiseRouter {
headers: req.config.headers,
ip: req.config.ip,
jobName,
message: jobHandler.setMessage.bind(jobHandler)
message: jobHandler.setMessage.bind(jobHandler),
};
return jobHandler.setRunning(jobName, params).then((jobStatus) => {
request.jobId = jobStatus.objectId
return jobHandler.setRunning(jobName, params).then(jobStatus => {
request.jobId = jobStatus.objectId;
// run the function async
process.nextTick(() => {
Promise.resolve().then(() => {
return jobFunction(request);
}).then((result) => {
jobHandler.setSucceeded(result);
}, (error) => {
jobHandler.setFailed(error);
});
Promise.resolve()
.then(() => {
return jobFunction(request);
})
.then(
result => {
jobHandler.setSucceeded(result);
},
error => {
jobHandler.setFailed(error);
}
);
});
return {
headers: {
'X-Parse-Job-Status-Id': jobStatus.objectId
'X-Parse-Job-Status-Id': jobStatus.objectId,
},
response: {}
}
response: {},
};
});
}
@@ -86,8 +99,8 @@ export class FunctionsRouter extends PromiseRouter {
success: function(result) {
resolve({
response: {
result: Parse._encode(result)
}
result: Parse._encode(result),
},
});
},
error: function(message) {
@@ -106,17 +119,23 @@ export class FunctionsRouter extends PromiseRouter {
}
reject(new Parse.Error(code, message));
},
message: message
}
message: message,
};
}
static handleCloudFunction(req) {
const functionName = req.params.functionName;
const applicationId = req.config.applicationId;
const theFunction = triggers.getFunction(functionName, applicationId);
const theValidator = triggers.getValidator(req.params.functionName, applicationId);
const theValidator = triggers.getValidator(
req.params.functionName,
applicationId
);
if (!theFunction) {
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, `Invalid function: "${functionName}"`);
throw new Parse.Error(
Parse.Error.SCRIPT_FAILED,
`Invalid function: "${functionName}"`
);
}
let params = Object.assign({}, req.body, req.query);
params = parseParams(params);
@@ -128,53 +147,65 @@ export class FunctionsRouter extends PromiseRouter {
log: req.config.loggerController,
headers: req.config.headers,
ip: req.config.ip,
functionName
functionName,
};
if (theValidator && typeof theValidator === "function") {
if (theValidator && typeof theValidator === 'function') {
var result = theValidator(request);
if (!result) {
throw new Parse.Error(Parse.Error.VALIDATION_ERROR, 'Validation failed.');
throw new Parse.Error(
Parse.Error.VALIDATION_ERROR,
'Validation failed.'
);
}
}
return new Promise(function (resolve, reject) {
const userString = (req.auth && req.auth.user) ? req.auth.user.id : undefined;
return new Promise(function(resolve, reject) {
const userString =
req.auth && req.auth.user ? req.auth.user.id : undefined;
const cleanInput = logger.truncateLogMessage(JSON.stringify(params));
const { success, error, message } = FunctionsRouter.createResponseObject((result) => {
try {
const cleanResult = logger.truncateLogMessage(JSON.stringify(result.response.result));
logger.info(
`Ran cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput }\n Result: ${cleanResult }`,
{
functionName,
params,
user: userString,
}
);
resolve(result);
} catch (e) {
reject(e);
const { success, error, message } = FunctionsRouter.createResponseObject(
result => {
try {
const cleanResult = logger.truncateLogMessage(
JSON.stringify(result.response.result)
);
logger.info(
`Ran cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Result: ${cleanResult}`,
{
functionName,
params,
user: userString,
}
);
resolve(result);
} catch (e) {
reject(e);
}
},
error => {
try {
logger.error(
`Failed running cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Error: ` +
JSON.stringify(error),
{
functionName,
error,
params,
user: userString,
}
);
reject(error);
} catch (e) {
reject(e);
}
}
}, (error) => {
try {
logger.error(
`Failed running cloud function ${functionName} for user ${userString} with:\n Input: ${cleanInput}\n Error: ` + JSON.stringify(error),
{
functionName,
error,
params,
user: userString
}
);
reject(error);
} catch (e) {
reject(e);
}
});
return Promise.resolve().then(() => {
return theFunction(request, { message });
}).then(success, error);
);
return Promise.resolve()
.then(() => {
return theFunction(request, { message });
})
.then(success, error);
});
}
}

View File

@@ -1,23 +1,28 @@
// global_config.js
import Parse from 'parse/node';
import PromiseRouter from '../PromiseRouter';
import * as middleware from "../middlewares";
import Parse from 'parse/node';
import PromiseRouter from '../PromiseRouter';
import * as middleware from '../middlewares';
export class GlobalConfigRouter extends PromiseRouter {
getGlobalConfig(req) {
return req.config.database.find('_GlobalConfig', { objectId: "1" }, { limit: 1 }).then((results) => {
if (results.length != 1) {
// If there is no config in the database - return empty config.
return { response: { params: {} } };
}
const globalConfig = results[0];
return { response: { params: globalConfig.params } };
});
return req.config.database
.find('_GlobalConfig', { objectId: '1' }, { limit: 1 })
.then(results => {
if (results.length != 1) {
// If there is no config in the database - return empty config.
return { response: { params: {} } };
}
const globalConfig = results[0];
return { response: { params: globalConfig.params } };
});
}
updateGlobalConfig(req) {
if (req.auth.isReadOnly) {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'read-only masterKey isn\'t allowed to update the config.');
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
"read-only masterKey isn't allowed to update the config."
);
}
const params = req.body.params;
// Transform in dot notation to make sure it works
@@ -25,12 +30,23 @@ export class GlobalConfigRouter extends PromiseRouter {
acc[`params.${key}`] = params[key];
return acc;
}, {});
return req.config.database.update('_GlobalConfig', {objectId: "1"}, update, {upsert: true}).then(() => ({ response: { result: true } }));
return req.config.database
.update('_GlobalConfig', { objectId: '1' }, update, { upsert: true })
.then(() => ({ response: { result: true } }));
}
mountRoutes() {
this.route('GET', '/config', req => { return this.getGlobalConfig(req) });
this.route('PUT', '/config', middleware.promiseEnforceMasterKeyAccess, req => { return this.updateGlobalConfig(req) });
this.route('GET', '/config', req => {
return this.getGlobalConfig(req);
});
this.route(
'PUT',
'/config',
middleware.promiseEnforceMasterKeyAccess,
req => {
return this.updateGlobalConfig(req);
}
);
}
}

View File

@@ -1,14 +1,18 @@
import { Parse } from 'parse/node';
import PromiseRouter from '../PromiseRouter';
import * as middleware from "../middlewares";
import { Parse } from 'parse/node';
import PromiseRouter from '../PromiseRouter';
import * as middleware from '../middlewares';
export class HooksRouter extends PromiseRouter {
createHook(aHook, config) {
return config.hooksController.createHook(aHook).then((hook) => ({response: hook}));
return config.hooksController
.createHook(aHook)
.then(hook => ({ response: hook }));
}
updateHook(aHook, config) {
return config.hooksController.updateHook(aHook).then((hook) => ({response: hook}));
return config.hooksController
.updateHook(aHook)
.then(hook => ({ response: hook }));
}
handlePost(req) {
@@ -18,67 +22,84 @@ export class HooksRouter extends PromiseRouter {
handleGetFunctions(req) {
var hooksController = req.config.hooksController;
if (req.params.functionName) {
return hooksController.getFunction(req.params.functionName).then((foundFunction) => {
if (!foundFunction) {
throw new Parse.Error(143, `no function named: ${req.params.functionName} is defined`);
}
return Promise.resolve({response: foundFunction});
});
return hooksController
.getFunction(req.params.functionName)
.then(foundFunction => {
if (!foundFunction) {
throw new Parse.Error(
143,
`no function named: ${req.params.functionName} is defined`
);
}
return Promise.resolve({ response: foundFunction });
});
}
return hooksController.getFunctions().then((functions) => {
return { response: functions || [] };
}, (err) => {
throw err;
});
return hooksController.getFunctions().then(
functions => {
return { response: functions || [] };
},
err => {
throw err;
}
);
}
handleGetTriggers(req) {
var hooksController = req.config.hooksController;
if (req.params.className && req.params.triggerName) {
return hooksController.getTrigger(req.params.className, req.params.triggerName).then((foundTrigger) => {
if (!foundTrigger) {
throw new Parse.Error(143,`class ${req.params.className} does not exist`);
}
return Promise.resolve({response: foundTrigger});
});
return hooksController
.getTrigger(req.params.className, req.params.triggerName)
.then(foundTrigger => {
if (!foundTrigger) {
throw new Parse.Error(
143,
`class ${req.params.className} does not exist`
);
}
return Promise.resolve({ response: foundTrigger });
});
}
return hooksController.getTriggers().then((triggers) => ({ response: triggers || [] }));
return hooksController
.getTriggers()
.then(triggers => ({ response: triggers || [] }));
}
handleDelete(req) {
var hooksController = req.config.hooksController;
if (req.params.functionName) {
return hooksController.deleteFunction(req.params.functionName).then(() => ({response: {}}))
return hooksController
.deleteFunction(req.params.functionName)
.then(() => ({ response: {} }));
} else if (req.params.className && req.params.triggerName) {
return hooksController.deleteTrigger(req.params.className, req.params.triggerName).then(() => ({response: {}}))
return hooksController
.deleteTrigger(req.params.className, req.params.triggerName)
.then(() => ({ response: {} }));
}
return Promise.resolve({response: {}});
return Promise.resolve({ response: {} });
}
handleUpdate(req) {
var hook;
if (req.params.functionName && req.body.url) {
hook = {}
hook = {};
hook.functionName = req.params.functionName;
hook.url = req.body.url;
} else if (req.params.className && req.params.triggerName && req.body.url) {
hook = {}
hook = {};
hook.className = req.params.className;
hook.triggerName = req.params.triggerName;
hook.url = req.body.url
hook.url = req.body.url;
} else {
throw new Parse.Error(143, "invalid hook declaration");
throw new Parse.Error(143, 'invalid hook declaration');
}
return this.updateHook(hook, req.config);
}
handlePut(req) {
var body = req.body;
if (body.__op == "Delete") {
if (body.__op == 'Delete') {
return this.handleDelete(req);
} else {
return this.handleUpdate(req);
@@ -86,14 +107,54 @@ export class HooksRouter extends PromiseRouter {
}
mountRoutes() {
this.route('GET', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this));
this.route('GET', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this));
this.route('GET', '/hooks/functions/:functionName', middleware.promiseEnforceMasterKeyAccess, this.handleGetFunctions.bind(this));
this.route('GET', '/hooks/triggers/:className/:triggerName', middleware.promiseEnforceMasterKeyAccess, this.handleGetTriggers.bind(this));
this.route('POST', '/hooks/functions', middleware.promiseEnforceMasterKeyAccess, this.handlePost.bind(this));
this.route('POST', '/hooks/triggers', middleware.promiseEnforceMasterKeyAccess, this.handlePost.bind(this));
this.route('PUT', '/hooks/functions/:functionName', middleware.promiseEnforceMasterKeyAccess, this.handlePut.bind(this));
this.route('PUT', '/hooks/triggers/:className/:triggerName', middleware.promiseEnforceMasterKeyAccess, this.handlePut.bind(this));
this.route(
'GET',
'/hooks/functions',
middleware.promiseEnforceMasterKeyAccess,
this.handleGetFunctions.bind(this)
);
this.route(
'GET',
'/hooks/triggers',
middleware.promiseEnforceMasterKeyAccess,
this.handleGetTriggers.bind(this)
);
this.route(
'GET',
'/hooks/functions/:functionName',
middleware.promiseEnforceMasterKeyAccess,
this.handleGetFunctions.bind(this)
);
this.route(
'GET',
'/hooks/triggers/:className/:triggerName',
middleware.promiseEnforceMasterKeyAccess,
this.handleGetTriggers.bind(this)
);
this.route(
'POST',
'/hooks/functions',
middleware.promiseEnforceMasterKeyAccess,
this.handlePost.bind(this)
);
this.route(
'POST',
'/hooks/triggers',
middleware.promiseEnforceMasterKeyAccess,
this.handlePost.bind(this)
);
this.route(
'PUT',
'/hooks/functions/:functionName',
middleware.promiseEnforceMasterKeyAccess,
this.handlePut.bind(this)
);
this.route(
'PUT',
'/hooks/triggers/:className/:triggerName',
middleware.promiseEnforceMasterKeyAccess,
this.handlePut.bind(this)
);
}
}

View File

@@ -1,81 +1,97 @@
import PromiseRouter from '../PromiseRouter';
var request = require("request");
var rest = require("../rest");
var request = require('request');
var rest = require('../rest');
import Parse from 'parse/node';
// TODO move validation logic in IAPValidationController
const IAP_SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt";
const IAP_PRODUCTION_URL = "https://buy.itunes.apple.com/verifyReceipt";
const IAP_SANDBOX_URL = 'https://sandbox.itunes.apple.com/verifyReceipt';
const IAP_PRODUCTION_URL = 'https://buy.itunes.apple.com/verifyReceipt';
const APP_STORE_ERRORS = {
21000: "The App Store could not read the JSON object you provided.",
21002: "The data in the receipt-data property was malformed or missing.",
21003: "The receipt could not be authenticated.",
21004: "The shared secret you provided does not match the shared secret on file for your account.",
21005: "The receipt server is not currently available.",
21006: "This receipt is valid but the subscription has expired.",
21007: "This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.",
21008: "This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead."
}
21000: 'The App Store could not read the JSON object you provided.',
21002: 'The data in the receipt-data property was malformed or missing.',
21003: 'The receipt could not be authenticated.',
21004: 'The shared secret you provided does not match the shared secret on file for your account.',
21005: 'The receipt server is not currently available.',
21006: 'This receipt is valid but the subscription has expired.',
21007: 'This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.',
21008: 'This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.',
};
function appStoreError(status) {
status = parseInt(status);
var errorString = APP_STORE_ERRORS[status] || "unknown error.";
return { status: status, error: errorString }
var errorString = APP_STORE_ERRORS[status] || 'unknown error.';
return { status: status, error: errorString };
}
function validateWithAppStore(url, receipt) {
return new Promise(function(fulfill, reject) {
request.post({
url: url,
body: { "receipt-data": receipt },
json: true,
}, function(err, res, body) {
var status = body.status;
if (status == 0) {
// No need to pass anything, status is OK
return fulfill();
request.post(
{
url: url,
body: { 'receipt-data': receipt },
json: true,
},
function(err, res, body) {
var status = body.status;
if (status == 0) {
// No need to pass anything, status is OK
return fulfill();
}
// receipt is from test and should go to test
return reject(body);
}
// receipt is from test and should go to test
return reject(body);
});
);
});
}
function getFileForProductIdentifier(productIdentifier, req) {
return rest.find(req.config, req.auth, '_Product', { productIdentifier: productIdentifier }, undefined, req.info.clientSDK).then(function(result){
const products = result.results;
if (!products || products.length != 1) {
// Error not found or too many
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.')
}
return rest
.find(
req.config,
req.auth,
'_Product',
{ productIdentifier: productIdentifier },
undefined,
req.info.clientSDK
)
.then(function(result) {
const products = result.results;
if (!products || products.length != 1) {
// Error not found or too many
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Object not found.'
);
}
var download = products[0].download;
return Promise.resolve({response: download});
});
var download = products[0].download;
return Promise.resolve({ response: download });
});
}
export class IAPValidationRouter extends PromiseRouter {
handleRequest(req) {
let receipt = req.body.receipt;
const productIdentifier = req.body.productIdentifier;
if (!receipt || ! productIdentifier) {
if (!receipt || !productIdentifier) {
// TODO: Error, malformed request
throw new Parse.Error(Parse.Error.INVALID_JSON, "missing receipt or productIdentifier");
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'missing receipt or productIdentifier'
);
}
// Transform the object if there
// otherwise assume it's in Base64 already
if (typeof receipt == "object") {
if (receipt["__type"] == "Bytes") {
if (typeof receipt == 'object') {
if (receipt['__type'] == 'Bytes') {
receipt = receipt.base64;
}
}
if (process.env.TESTING == "1" && req.body.bypassAppStoreValidation) {
if (process.env.TESTING == '1' && req.body.bypassAppStoreValidation) {
return getFileForProductIdentifier(productIdentifier, req);
}
@@ -84,28 +100,31 @@ export class IAPValidationRouter extends PromiseRouter {
}
function errorCallback(error) {
return Promise.resolve({response: appStoreError(error.status) });
return Promise.resolve({ response: appStoreError(error.status) });
}
return validateWithAppStore(IAP_PRODUCTION_URL, receipt).then(() => {
return successCallback();
}, (error) => {
if (error.status == 21007) {
return validateWithAppStore(IAP_SANDBOX_URL, receipt).then(() => {
return successCallback();
}, (error) => {
return errorCallback(error);
return validateWithAppStore(IAP_PRODUCTION_URL, receipt).then(
() => {
return successCallback();
},
error => {
if (error.status == 21007) {
return validateWithAppStore(IAP_SANDBOX_URL, receipt).then(
() => {
return successCallback();
},
error => {
return errorCallback(error);
}
);
}
);
}
return errorCallback(error);
});
return errorCallback(error);
}
);
}
mountRoutes() {
this.route("POST","/validate_purchase", this.handleRequest);
this.route('POST', '/validate_purchase', this.handleRequest);
}
}

View File

@@ -9,21 +9,41 @@ export class InstallationsRouter extends ClassesRouter {
}
handleFind(req) {
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
const body = Object.assign(
req.body,
ClassesRouter.JSONFromQuery(req.query)
);
const options = ClassesRouter.optionsFromBody(body);
return rest.find(req.config, req.auth,
'_Installation', body.where, options, req.info.clientSDK)
.then((response) => {
return {response: response};
return rest
.find(
req.config,
req.auth,
'_Installation',
body.where,
options,
req.info.clientSDK
)
.then(response => {
return { response: response };
});
}
mountRoutes() {
this.route('GET','/installations', req => { return this.handleFind(req); });
this.route('GET','/installations/:objectId', req => { return this.handleGet(req); });
this.route('POST','/installations', req => { return this.handleCreate(req); });
this.route('PUT','/installations/:objectId', req => { return this.handleUpdate(req); });
this.route('DELETE','/installations/:objectId', req => { return this.handleDelete(req); });
this.route('GET', '/installations', req => {
return this.handleFind(req);
});
this.route('GET', '/installations/:objectId', req => {
return this.handleGet(req);
});
this.route('POST', '/installations', req => {
return this.handleCreate(req);
});
this.route('PUT', '/installations/:objectId', req => {
return this.handleUpdate(req);
});
this.route('DELETE', '/installations/:objectId', req => {
return this.handleDelete(req);
});
}
}

View File

@@ -1,19 +1,26 @@
import { Parse } from 'parse/node';
import PromiseRouter from '../PromiseRouter';
import * as middleware from "../middlewares";
import * as middleware from '../middlewares';
export class LogsRouter extends PromiseRouter {
mountRoutes() {
this.route('GET','/scriptlog', middleware.promiseEnforceMasterKeyAccess, this.validateRequest, (req) => {
return this.handleGET(req);
});
this.route(
'GET',
'/scriptlog',
middleware.promiseEnforceMasterKeyAccess,
this.validateRequest,
req => {
return this.handleGET(req);
}
);
}
validateRequest(req) {
if (!req.config || !req.config.loggerController) {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
'Logger adapter is not available');
throw new Parse.Error(
Parse.Error.PUSH_MISCONFIGURED,
'Logger adapter is not available'
);
}
}
@@ -33,21 +40,21 @@ export class LogsRouter extends PromiseRouter {
size = req.query.n;
}
const order = req.query.order
const order = req.query.order;
const level = req.query.level;
const options = {
from,
until,
size,
order,
level
level,
};
return req.config.loggerController.getLogs(options).then((result) => {
return req.config.loggerController.getLogs(options).then(result => {
return Promise.resolve({
response: result
response: result,
});
})
});
}
}

View File

@@ -5,17 +5,16 @@ import path from 'path';
import fs from 'fs';
import qs from 'querystring';
const public_html = path.resolve(__dirname, "../../public_html");
const public_html = path.resolve(__dirname, '../../public_html');
const views = path.resolve(__dirname, '../../views');
export class PublicAPIRouter extends PromiseRouter {
verifyEmail(req) {
const { token, username } = req.query;
const appId = req.params.appId;
const config = Config.get(appId);
if(!config){
if (!config) {
this.invalidRequest();
}
@@ -28,15 +27,18 @@ export class PublicAPIRouter extends PromiseRouter {
}
const userController = config.userController;
return userController.verifyEmail(username, token).then(() => {
const params = qs.stringify({username});
return Promise.resolve({
status: 302,
location: `${config.verifyEmailSuccessURL}?${params}`
});
}, ()=> {
return this.invalidVerificationLink(req);
})
return userController.verifyEmail(username, token).then(
() => {
const params = qs.stringify({ username });
return Promise.resolve({
status: 302,
location: `${config.verifyEmailSuccessURL}?${params}`,
});
},
() => {
return this.invalidVerificationLink(req);
}
);
}
resendVerificationEmail(req) {
@@ -44,7 +46,7 @@ export class PublicAPIRouter extends PromiseRouter {
const appId = req.params.appId;
const config = Config.get(appId);
if(!config){
if (!config) {
this.invalidRequest();
}
@@ -58,51 +60,60 @@ export class PublicAPIRouter extends PromiseRouter {
const userController = config.userController;
return userController.resendVerificationEmail(username).then(() => {
return Promise.resolve({
status: 302,
location: `${config.linkSendSuccessURL}`
});
}, ()=> {
return Promise.resolve({
status: 302,
location: `${config.linkSendFailURL}`
});
})
return userController.resendVerificationEmail(username).then(
() => {
return Promise.resolve({
status: 302,
location: `${config.linkSendSuccessURL}`,
});
},
() => {
return Promise.resolve({
status: 302,
location: `${config.linkSendFailURL}`,
});
}
);
}
changePassword(req) {
return new Promise((resolve, reject) => {
const config = Config.get(req.query.id);
if(!config){
if (!config) {
this.invalidRequest();
}
if (!config.publicServerURL) {
return resolve({
status: 404,
text: 'Not found.'
text: 'Not found.',
});
}
// Should we keep the file in memory or leave like that?
fs.readFile(path.resolve(views, "choose_password"), 'utf-8', (err, data) => {
if (err) {
return reject(err);
fs.readFile(
path.resolve(views, 'choose_password'),
'utf-8',
(err, data) => {
if (err) {
return reject(err);
}
data = data.replace(
'PARSE_SERVER_URL',
`'${config.publicServerURL}'`
);
resolve({
text: data,
});
}
data = data.replace("PARSE_SERVER_URL", `'${config.publicServerURL}'`);
resolve({
text: data
})
});
);
});
}
requestResetPassword(req) {
const config = req.config;
if(!config){
if (!config) {
this.invalidRequest();
}
@@ -116,22 +127,29 @@ export class PublicAPIRouter extends PromiseRouter {
return this.invalidLink(req);
}
return config.userController.checkResetTokenValidity(username, token).then(() => {
const params = qs.stringify({token, id: config.applicationId, username, app: config.appName, });
return Promise.resolve({
status: 302,
location: `${config.choosePasswordURL}?${params}`
})
}, () => {
return this.invalidLink(req);
})
return config.userController.checkResetTokenValidity(username, token).then(
() => {
const params = qs.stringify({
token,
id: config.applicationId,
username,
app: config.appName,
});
return Promise.resolve({
status: 302,
location: `${config.choosePasswordURL}?${params}`,
});
},
() => {
return this.invalidLink(req);
}
);
}
resetPassword(req) {
const config = req.config;
if(!config){
if (!config) {
this.invalidRequest();
}
@@ -139,46 +157,55 @@ export class PublicAPIRouter extends PromiseRouter {
return this.missingPublicServerURL();
}
const {
username,
token,
new_password
} = req.body;
const { username, token, new_password } = req.body;
if (!username || !token || !new_password) {
return this.invalidLink(req);
}
return config.userController.updatePassword(username, token, new_password).then(() => {
const params = qs.stringify({username: username});
return Promise.resolve({
status: 302,
location: `${config.passwordResetSuccessURL}?${params}`
});
}, (err) => {
const params = qs.stringify({username: username, token: token, id: config.applicationId, error:err, app:config.appName});
return Promise.resolve({
status: 302,
location: `${config.choosePasswordURL}?${params}`
});
});
return config.userController
.updatePassword(username, token, new_password)
.then(
() => {
const params = qs.stringify({ username: username });
return Promise.resolve({
status: 302,
location: `${config.passwordResetSuccessURL}?${params}`,
});
},
err => {
const params = qs.stringify({
username: username,
token: token,
id: config.applicationId,
error: err,
app: config.appName,
});
return Promise.resolve({
status: 302,
location: `${config.choosePasswordURL}?${params}`,
});
}
);
}
invalidLink(req) {
return Promise.resolve({
status: 302,
location: req.config.invalidLinkURL
location: req.config.invalidLinkURL,
});
}
invalidVerificationLink(req) {
const config = req.config;
if (req.query.username && req.params.appId) {
const params = qs.stringify({username: req.query.username, appId: req.params.appId});
const params = qs.stringify({
username: req.query.username,
appId: req.params.appId,
});
return Promise.resolve({
status: 302,
location: `${config.invalidVerificationLinkURL}?${params}`
location: `${config.invalidVerificationLinkURL}?${params}`,
});
} else {
return this.invalidLink(req);
@@ -187,15 +214,15 @@ export class PublicAPIRouter extends PromiseRouter {
missingPublicServerURL() {
return Promise.resolve({
text: 'Not found.',
status: 404
text: 'Not found.',
status: 404,
});
}
invalidRequest() {
const error = new Error();
error.status = 403;
error.message = "unauthorized";
error.message = 'unauthorized';
throw error;
}
@@ -205,30 +232,59 @@ export class PublicAPIRouter extends PromiseRouter {
}
mountRoutes() {
this.route('GET','/apps/:appId/verify_email',
req => { this.setConfig(req) },
req => { return this.verifyEmail(req); });
this.route(
'GET',
'/apps/:appId/verify_email',
req => {
this.setConfig(req);
},
req => {
return this.verifyEmail(req);
}
);
this.route('POST', '/apps/:appId/resend_verification_email',
req => { this.setConfig(req); },
req => { return this.resendVerificationEmail(req); });
this.route(
'POST',
'/apps/:appId/resend_verification_email',
req => {
this.setConfig(req);
},
req => {
return this.resendVerificationEmail(req);
}
);
this.route('GET','/apps/choose_password',
req => { return this.changePassword(req); });
this.route('GET', '/apps/choose_password', req => {
return this.changePassword(req);
});
this.route('POST','/apps/:appId/request_password_reset',
req => { this.setConfig(req) },
req => { return this.resetPassword(req); });
this.route(
'POST',
'/apps/:appId/request_password_reset',
req => {
this.setConfig(req);
},
req => {
return this.resetPassword(req);
}
);
this.route('GET','/apps/:appId/request_password_reset',
req => { this.setConfig(req) },
req => { return this.requestResetPassword(req); });
this.route(
'GET',
'/apps/:appId/request_password_reset',
req => {
this.setConfig(req);
},
req => {
return this.requestResetPassword(req);
}
);
}
expressRouter() {
const router = express.Router();
router.use("/apps", express.static(public_html));
router.use("/", super.expressRouter());
router.use('/apps', express.static(public_html));
router.use('/', super.expressRouter());
return router;
}
}

View File

@@ -3,12 +3,15 @@ import * as middleware from '../middlewares';
import Parse from 'parse/node';
export class PurgeRouter extends PromiseRouter {
handlePurge(req) {
if (req.auth.isReadOnly) {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'read-only masterKey isn\'t allowed to purge a schema.');
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
"read-only masterKey isn't allowed to purge a schema."
);
}
return req.config.database.purgeCollection(req.params.className)
return req.config.database
.purgeCollection(req.params.className)
.then(() => {
var cacheAdapter = req.config.cacheController;
if (req.params.className == '_Session') {
@@ -16,17 +19,25 @@ export class PurgeRouter extends PromiseRouter {
} else if (req.params.className == '_Role') {
cacheAdapter.role.clear();
}
return {response: {}};
}).catch((error) => {
return { response: {} };
})
.catch(error => {
if (!error || (error && error.code === Parse.Error.OBJECT_NOT_FOUND)) {
return {response: {}};
return { response: {} };
}
throw error;
});
}
mountRoutes() {
this.route('DELETE', '/purge/:className', middleware.promiseEnforceMasterKeyAccess, (req) => { return this.handlePurge(req); });
this.route(
'DELETE',
'/purge/:className',
middleware.promiseEnforceMasterKeyAccess,
req => {
return this.handlePurge(req);
}
);
}
}

View File

@@ -1,41 +1,56 @@
import PromiseRouter from '../PromiseRouter';
import * as middleware from "../middlewares";
import { Parse } from "parse/node";
import PromiseRouter from '../PromiseRouter';
import * as middleware from '../middlewares';
import { Parse } from 'parse/node';
export class PushRouter extends PromiseRouter {
mountRoutes() {
this.route("POST", "/push", middleware.promiseEnforceMasterKeyAccess, PushRouter.handlePOST);
this.route(
'POST',
'/push',
middleware.promiseEnforceMasterKeyAccess,
PushRouter.handlePOST
);
}
static handlePOST(req) {
if (req.auth.isReadOnly) {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'read-only masterKey isn\'t allowed to send push notifications.');
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
"read-only masterKey isn't allowed to send push notifications."
);
}
const pushController = req.config.pushController;
if (!pushController) {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Push controller is not set');
throw new Parse.Error(
Parse.Error.PUSH_MISCONFIGURED,
'Push controller is not set'
);
}
const where = PushRouter.getQueryCondition(req);
let resolve;
const promise = new Promise((_resolve) => {
const promise = new Promise(_resolve => {
resolve = _resolve;
});
let pushStatusId;
pushController.sendPush(req.body, where, req.config, req.auth, (objectId) => {
pushStatusId = objectId;
resolve({
headers: {
'X-Parse-Push-Status-Id': pushStatusId
},
response: {
result: true
}
pushController
.sendPush(req.body, where, req.config, req.auth, objectId => {
pushStatusId = objectId;
resolve({
headers: {
'X-Parse-Push-Status-Id': pushStatusId,
},
response: {
result: true,
},
});
})
.catch(err => {
req.config.loggerController.error(
`_PushStatus ${pushStatusId}: error while sending push`,
err
);
});
}).catch((err) => {
req.config.loggerController.error(`_PushStatus ${pushStatusId}: error while sending push`, err);
});
return promise;
}
@@ -51,18 +66,23 @@ export class PushRouter extends PromiseRouter {
let where;
if (hasWhere && hasChannels) {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
'Channels and query can not be set at the same time.');
throw new Parse.Error(
Parse.Error.PUSH_MISCONFIGURED,
'Channels and query can not be set at the same time.'
);
} else if (hasWhere) {
where = body.where;
} else if (hasChannels) {
where = {
"channels": {
"$in": body.channels
}
}
channels: {
$in: body.channels,
},
};
} else {
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Sending a push requires either "channels" or a "where" query.');
throw new Parse.Error(
Parse.Error.PUSH_MISCONFIGURED,
'Sending a push requires either "channels" or a "where" query.'
);
}
return where;
}

View File

@@ -1,4 +1,3 @@
import ClassesRouter from './ClassesRouter';
export class RolesRouter extends ClassesRouter {
@@ -7,11 +6,21 @@ export class RolesRouter extends ClassesRouter {
}
mountRoutes() {
this.route('GET','/roles', req => { return this.handleFind(req); });
this.route('GET','/roles/:objectId', req => { return this.handleGet(req); });
this.route('POST','/roles', req => { return this.handleCreate(req); });
this.route('PUT','/roles/:objectId', req => { return this.handleUpdate(req); });
this.route('DELETE','/roles/:objectId', req => { return this.handleDelete(req); });
this.route('GET', '/roles', req => {
return this.handleFind(req);
});
this.route('GET', '/roles/:objectId', req => {
return this.handleGet(req);
});
this.route('POST', '/roles', req => {
return this.handleCreate(req);
});
this.route('PUT', '/roles/:objectId', req => {
return this.handleUpdate(req);
});
this.route('DELETE', '/roles/:objectId', req => {
return this.handleDelete(req);
});
}
}

View File

@@ -3,8 +3,8 @@
var Parse = require('parse/node').Parse,
SchemaController = require('../Controllers/SchemaController');
import PromiseRouter from '../PromiseRouter';
import * as middleware from "../middlewares";
import PromiseRouter from '../PromiseRouter';
import * as middleware from '../middlewares';
function classNameMismatchResponse(bodyClass, pathClass) {
throw new Parse.Error(
@@ -14,32 +14,46 @@ function classNameMismatchResponse(bodyClass, pathClass) {
}
function getAllSchemas(req) {
return req.config.database.loadSchema({ clearCache: true})
return req.config.database
.loadSchema({ clearCache: true })
.then(schemaController => schemaController.getAllClasses(true))
.then(schemas => ({ response: { results: schemas } }));
}
function getOneSchema(req) {
const className = req.params.className;
return req.config.database.loadSchema({ clearCache: true})
return req.config.database
.loadSchema({ clearCache: true })
.then(schemaController => schemaController.getOneSchema(className, true))
.then(schema => ({ response: schema }))
.catch(error => {
if (error === undefined) {
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
throw new Parse.Error(
Parse.Error.INVALID_CLASS_NAME,
`Class ${className} does not exist.`
);
} else {
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.');
throw new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'Database adapter error.'
);
}
});
}
function createSchema(req) {
if (req.auth.isReadOnly) {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'read-only masterKey isn\'t allowed to create a schema.');
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
"read-only masterKey isn't allowed to create a schema."
);
}
if (req.params.className && req.body.className) {
if (req.params.className != req.body.className) {
return classNameMismatchResponse(req.body.className, req.params.className);
return classNameMismatchResponse(
req.body.className,
req.params.className
);
}
}
@@ -48,14 +62,25 @@ function createSchema(req) {
throw new Parse.Error(135, `POST ${req.path} needs a class name.`);
}
return req.config.database.loadSchema({ clearCache: true})
.then(schema => schema.addClassIfNotExists(className, req.body.fields, req.body.classLevelPermissions, req.body.indexes))
return req.config.database
.loadSchema({ clearCache: true })
.then(schema =>
schema.addClassIfNotExists(
className,
req.body.fields,
req.body.classLevelPermissions,
req.body.indexes
)
)
.then(schema => ({ response: schema }));
}
function modifySchema(req) {
if (req.auth.isReadOnly) {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'read-only masterKey isn\'t allowed to update a schema.');
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
"read-only masterKey isn't allowed to update a schema."
);
}
if (req.body.className && req.body.className != req.params.className) {
return classNameMismatchResponse(req.body.className, req.params.className);
@@ -64,29 +89,75 @@ function modifySchema(req) {
const submittedFields = req.body.fields || {};
const className = req.params.className;
return req.config.database.loadSchema({ clearCache: true})
.then(schema => schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.body.indexes, req.config.database))
.then(result => ({response: result}));
return req.config.database
.loadSchema({ clearCache: true })
.then(schema =>
schema.updateClass(
className,
submittedFields,
req.body.classLevelPermissions,
req.body.indexes,
req.config.database
)
)
.then(result => ({ response: result }));
}
const deleteSchema = req => {
if (req.auth.isReadOnly) {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'read-only masterKey isn\'t allowed to delete a schema.');
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
"read-only masterKey isn't allowed to delete a schema."
);
}
if (!SchemaController.classNameIsValid(req.params.className)) {
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, SchemaController.invalidClassNameMessage(req.params.className));
throw new Parse.Error(
Parse.Error.INVALID_CLASS_NAME,
SchemaController.invalidClassNameMessage(req.params.className)
);
}
return req.config.database.deleteSchema(req.params.className)
return req.config.database
.deleteSchema(req.params.className)
.then(() => ({ response: {} }));
}
};
export class SchemasRouter extends PromiseRouter {
mountRoutes() {
this.route('GET', '/schemas', middleware.promiseEnforceMasterKeyAccess, getAllSchemas);
this.route('GET', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, getOneSchema);
this.route('POST', '/schemas', middleware.promiseEnforceMasterKeyAccess, createSchema);
this.route('POST', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, createSchema);
this.route('PUT', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, modifySchema);
this.route('DELETE', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, deleteSchema);
this.route(
'GET',
'/schemas',
middleware.promiseEnforceMasterKeyAccess,
getAllSchemas
);
this.route(
'GET',
'/schemas/:className',
middleware.promiseEnforceMasterKeyAccess,
getOneSchema
);
this.route(
'POST',
'/schemas',
middleware.promiseEnforceMasterKeyAccess,
createSchema
);
this.route(
'POST',
'/schemas/:className',
middleware.promiseEnforceMasterKeyAccess,
createSchema
);
this.route(
'PUT',
'/schemas/:className',
middleware.promiseEnforceMasterKeyAccess,
modifySchema
);
this.route(
'DELETE',
'/schemas/:className',
middleware.promiseEnforceMasterKeyAccess,
deleteSchema
);
}
}

View File

@@ -1,11 +1,9 @@
import ClassesRouter from './ClassesRouter';
import Parse from 'parse/node';
import rest from '../rest';
import Auth from '../Auth';
import Parse from 'parse/node';
import rest from '../rest';
import Auth from '../Auth';
export class SessionsRouter extends ClassesRouter {
className() {
return '_Session';
}
@@ -13,17 +11,29 @@ export class SessionsRouter extends ClassesRouter {
handleMe(req) {
// TODO: Verify correct behavior
if (!req.info || !req.info.sessionToken) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
'Session token required.');
throw new Parse.Error(
Parse.Error.INVALID_SESSION_TOKEN,
'Session token required.'
);
}
return rest.find(req.config, Auth.master(req.config), '_Session', { sessionToken: req.info.sessionToken }, undefined, req.info.clientSDK)
.then((response) => {
return rest
.find(
req.config,
Auth.master(req.config),
'_Session',
{ sessionToken: req.info.sessionToken },
undefined,
req.info.clientSDK
)
.then(response => {
if (!response.results || response.results.length == 0) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
'Session token not found.');
throw new Parse.Error(
Parse.Error.INVALID_SESSION_TOKEN,
'Session token not found.'
);
}
return {
response: response.results[0]
response: response.results[0],
};
});
}
@@ -36,37 +46,54 @@ export class SessionsRouter extends ClassesRouter {
if (!user) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'invalid session');
}
const {
sessionData,
createSession
} = Auth.createSession(config, {
const { sessionData, createSession } = Auth.createSession(config, {
userId: user.id,
createdWith: {
'action': 'upgrade',
action: 'upgrade',
},
installationId: req.auth.installationId,
});
return createSession().then(() => {
// delete the session token, use the db to skip beforeSave
return config.database.update('_User', {
objectId: user.id
}, {
sessionToken: {__op: 'Delete'}
return createSession()
.then(() => {
// delete the session token, use the db to skip beforeSave
return config.database.update(
'_User',
{
objectId: user.id,
},
{
sessionToken: { __op: 'Delete' },
}
);
})
.then(() => {
return Promise.resolve({ response: sessionData });
});
}).then(() => {
return Promise.resolve({ response: sessionData });
});
}
mountRoutes() {
this.route('GET','/sessions/me', req => { return this.handleMe(req); });
this.route('GET', '/sessions', req => { return this.handleFind(req); });
this.route('GET', '/sessions/:objectId', req => { return this.handleGet(req); });
this.route('POST', '/sessions', req => { return this.handleCreate(req); });
this.route('PUT', '/sessions/:objectId', req => { return this.handleUpdate(req); });
this.route('DELETE', '/sessions/:objectId', req => { return this.handleDelete(req); });
this.route('POST', '/upgradeToRevocableSession', req => { return this.handleUpdateToRevocableSession(req); })
this.route('GET', '/sessions/me', req => {
return this.handleMe(req);
});
this.route('GET', '/sessions', req => {
return this.handleFind(req);
});
this.route('GET', '/sessions/:objectId', req => {
return this.handleGet(req);
});
this.route('POST', '/sessions', req => {
return this.handleCreate(req);
});
this.route('PUT', '/sessions/:objectId', req => {
return this.handleUpdate(req);
});
this.route('DELETE', '/sessions/:objectId', req => {
return this.handleDelete(req);
});
this.route('POST', '/upgradeToRevocableSession', req => {
return this.handleUpdateToRevocableSession(req);
});
}
}

View File

@@ -9,7 +9,6 @@ import Auth from '../Auth';
import passwordCrypto from '../password';
export class UsersRouter extends ClassesRouter {
className() {
return '_User';
}
@@ -22,7 +21,7 @@ export class UsersRouter extends ClassesRouter {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// Regexp comes from Parse.Object.prototype.validate
if (key !== "__type" && !(/^[A-Za-z][0-9A-Za-z_]*$/).test(key)) {
if (key !== '__type' && !/^[A-Za-z][0-9A-Za-z_]*$/.test(key)) {
delete obj[key];
}
}
@@ -39,26 +38,36 @@ export class UsersRouter extends ClassesRouter {
return new Promise((resolve, reject) => {
// Use query parameters instead if provided in url
let payload = req.body;
if (!payload.username && req.query.username || !payload.email && req.query.email) {
if (
(!payload.username && req.query.username) ||
(!payload.email && req.query.email)
) {
payload = req.query;
}
const {
username,
email,
password,
} = payload;
const { username, email, password } = payload;
// 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,
'username/email is required.'
);
}
if (!password) {
throw new Parse.Error(Parse.Error.PASSWORD_MISSING, 'password is required.');
throw new Parse.Error(
Parse.Error.PASSWORD_MISSING,
'password is required.'
);
}
if (typeof password !== 'string'
|| email && typeof email !== 'string'
|| username && typeof username !== 'string') {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
if (
typeof password !== 'string' ||
(email && typeof email !== 'string') ||
(username && typeof username !== 'string')
) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Invalid username/password.'
);
}
let user;
@@ -71,39 +80,63 @@ export class UsersRouter extends ClassesRouter {
} else {
query = { $or: [{ username }, { email: username }] };
}
return req.config.database.find('_User', query)
.then((results) => {
return req.config.database
.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,
'Invalid username/password.'
);
}
if (results.length > 1) { // corner case where user1 has username == user2 email
req.config.loggerController.warn('There is a user which email is the same as another user\'s username, logging in based on username');
user = results.filter((user) => user.username === username)[0];
if (results.length > 1) {
// corner case where user1 has username == user2 email
req.config.loggerController.warn(
"There is a user which email is the same as another user's username, logging in based on username"
);
user = results.filter(user => user.username === username)[0];
} else {
user = results[0];
}
return passwordCrypto.compare(password, user.password);
})
.then((correct) => {
.then(correct => {
isValidPassword = correct;
const accountLockoutPolicy = new AccountLockout(user, req.config);
return accountLockoutPolicy.handleLoginAttempt(isValidPassword);
})
.then(() => {
if (!isValidPassword) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Invalid username/password.'
);
}
// Ensure the user isn't locked out
// A locked out user won't be able to login
// To lock a user out, just set the ACL to `masterKey` only ({}).
// Empty ACL is OK
if (!req.auth.isMaster && user.ACL && Object.keys(user.ACL).length == 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
if (
!req.auth.isMaster &&
user.ACL &&
Object.keys(user.ACL).length == 0
) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'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.');
if (
req.config.verifyUserEmails &&
req.config.preventLoginWithUnverifiedEmail &&
!user.emailVerified
) {
throw new Parse.Error(
Parse.Error.EMAIL_NOT_FOUND,
'User email is not verified.'
);
}
delete user.password;
@@ -111,7 +144,7 @@ export class UsersRouter extends ClassesRouter {
// Sometimes the authData still has null on that keys
// https://github.com/parse-community/parse-server/issues/935
if (user.authData) {
Object.keys(user.authData).forEach((provider) => {
Object.keys(user.authData).forEach(provider => {
if (user.authData[provider] === null) {
delete user.authData[provider];
}
@@ -122,7 +155,8 @@ export class UsersRouter extends ClassesRouter {
}
return resolve(user);
}).catch((error) => {
})
.catch(error => {
return reject(error);
});
});
@@ -130,17 +164,31 @@ 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,
'Invalid session token'
);
}
const sessionToken = req.info.sessionToken;
return rest.find(req.config, Auth.master(req.config), '_Session',
{ sessionToken },
{ include: 'user' }, req.info.clientSDK)
.then((response) => {
if (!response.results ||
return rest
.find(
req.config,
Auth.master(req.config),
'_Session',
{ sessionToken },
{ include: 'user' },
req.info.clientSDK
)
.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');
!response.results[0].user
) {
throw new Parse.Error(
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.
@@ -157,43 +205,54 @@ export class UsersRouter extends ClassesRouter {
handleLogIn(req) {
let user;
return this._authenticateUserFromRequest(req)
.then((res) => {
.then(res => {
user = res;
// handle password expiry policy
if (req.config.passwordPolicy && req.config.passwordPolicy.maxPasswordAge) {
if (
req.config.passwordPolicy &&
req.config.passwordPolicy.maxPasswordAge
) {
let changedAt = user._password_changed_at;
if (!changedAt) {
// password was created before expiry policy was enabled.
// simply update _User object so that it will start enforcing from now
changedAt = new Date();
req.config.database.update('_User', { username: user.username },
{ _password_changed_at: Parse._encode(changedAt) });
req.config.database.update(
'_User',
{ username: user.username },
{ _password_changed_at: Parse._encode(changedAt) }
);
} else {
// check whether the password has expired
if (changedAt.__type == 'Date') {
changedAt = new Date(changedAt.iso);
}
// Calculate the expiry time.
const expiresAt = new Date(changedAt.getTime() + 86400000 * req.config.passwordPolicy.maxPasswordAge);
if (expiresAt < new Date()) // fail of current time is past password expiry time
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Your password has expired. Please reset your password.');
const expiresAt = new Date(
changedAt.getTime() +
86400000 * req.config.passwordPolicy.maxPasswordAge
);
if (expiresAt < new Date())
// fail of current time is past password expiry time
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Your password has expired. Please reset your password.'
);
}
}
// Remove hidden properties.
UsersRouter.removeHiddenProperties(user);
const {
sessionData,
createSession
} = Auth.createSession(req.config, {
userId: user.objectId, createdWith: {
'action': 'login',
'authProvider': 'password'
}, installationId: req.info.installationId
const { sessionData, createSession } = Auth.createSession(req.config, {
userId: user.objectId,
createdWith: {
action: 'login',
authProvider: 'password',
},
installationId: req.info.installationId,
});
user.sessionToken = sessionData.sessionToken;
@@ -209,13 +268,13 @@ export class UsersRouter extends ClassesRouter {
handleVerifyPassword(req) {
return this._authenticateUserFromRequest(req)
.then((user) => {
.then(user => {
// Remove hidden properties.
UsersRouter.removeHiddenProperties(user);
return { response: user };
}).catch((error) => {
})
.catch(error => {
throw error;
});
}
@@ -223,18 +282,30 @@ export class UsersRouter extends ClassesRouter {
handleLogOut(req) {
const success = { response: {} };
if (req.info && req.info.sessionToken) {
return rest.find(req.config, Auth.master(req.config), '_Session',
{ sessionToken: req.info.sessionToken }, undefined, req.info.clientSDK
).then((records) => {
if (records.results && records.results.length) {
return rest.del(req.config, Auth.master(req.config), '_Session',
records.results[0].objectId
).then(() => {
return Promise.resolve(success);
});
}
return Promise.resolve(success);
});
return rest
.find(
req.config,
Auth.master(req.config),
'_Session',
{ sessionToken: req.info.sessionToken },
undefined,
req.info.clientSDK
)
.then(records => {
if (records.results && records.results.length) {
return rest
.del(
req.config,
Auth.master(req.config),
'_Session',
records.results[0].objectId
)
.then(() => {
return Promise.resolve(success);
});
}
return Promise.resolve(success);
});
}
return Promise.resolve(success);
}
@@ -245,12 +316,16 @@ export class UsersRouter extends ClassesRouter {
emailAdapter: req.config.userController.adapter,
appName: req.config.appName,
publicServerURL: req.config.publicServerURL,
emailVerifyTokenValidityDuration: req.config.emailVerifyTokenValidityDuration
emailVerifyTokenValidityDuration:
req.config.emailVerifyTokenValidityDuration,
});
} catch (e) {
if (typeof e === 'string') {
// Maybe we need a Bad Configuration error, but the SDKs won't understand it. For now, Internal Server Error.
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.');
throw new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'An appName, publicServerURL, and emailAdapter are required for password reset and email verification functionality.'
);
} else {
throw e;
}
@@ -262,23 +337,35 @@ 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,
'you must provide an 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,
'you must provide a valid email string'
);
}
const userController = req.config.userController;
return userController.sendPasswordResetEmail(email).then(() => {
return Promise.resolve({
response: {}
});
}, err => {
if (err.code === Parse.Error.OBJECT_NOT_FOUND) {
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}.`);
} else {
throw err;
return userController.sendPasswordResetEmail(email).then(
() => {
return Promise.resolve({
response: {},
});
},
err => {
if (err.code === Parse.Error.OBJECT_NOT_FOUND) {
throw new Parse.Error(
Parse.Error.EMAIL_NOT_FOUND,
`No user found with email ${email}.`
);
} else {
throw err;
}
}
});
);
}
handleVerificationEmailRequest(req) {
@@ -286,15 +373,24 @@ 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,
'you must provide an 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,
'you must provide a valid email string'
);
}
return req.config.database.find('_User', { email: email }).then((results) => {
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,
`No user found with email ${email}`
);
}
const user = results[0];
@@ -302,7 +398,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,
`Email ${email} is already verified.`
);
}
const userController = req.config.userController;
@@ -313,20 +412,43 @@ export class UsersRouter extends ClassesRouter {
});
}
mountRoutes() {
this.route('GET', '/users', req => { return this.handleFind(req); });
this.route('POST', '/users', req => { return this.handleCreate(req); });
this.route('GET', '/users/me', req => { return this.handleMe(req); });
this.route('GET', '/users/:objectId', req => { return this.handleGet(req); });
this.route('PUT', '/users/:objectId', req => { return this.handleUpdate(req); });
this.route('DELETE', '/users/:objectId', req => { return this.handleDelete(req); });
this.route('GET', '/login', req => { return this.handleLogIn(req); });
this.route('POST', '/login', req => { return this.handleLogIn(req); });
this.route('POST', '/logout', req => { return this.handleLogOut(req); });
this.route('POST', '/requestPasswordReset', req => { return this.handleResetRequest(req); });
this.route('POST', '/verificationEmailRequest', req => { return this.handleVerificationEmailRequest(req); });
this.route('GET', '/verifyPassword', req => { return this.handleVerifyPassword(req); });
this.route('GET', '/users', req => {
return this.handleFind(req);
});
this.route('POST', '/users', req => {
return this.handleCreate(req);
});
this.route('GET', '/users/me', req => {
return this.handleMe(req);
});
this.route('GET', '/users/:objectId', req => {
return this.handleGet(req);
});
this.route('PUT', '/users/:objectId', req => {
return this.handleUpdate(req);
});
this.route('DELETE', '/users/:objectId', req => {
return this.handleDelete(req);
});
this.route('GET', '/login', req => {
return this.handleLogIn(req);
});
this.route('POST', '/login', req => {
return this.handleLogIn(req);
});
this.route('POST', '/logout', req => {
return this.handleLogOut(req);
});
this.route('POST', '/requestPasswordReset', req => {
return this.handleResetRequest(req);
});
this.route('POST', '/verificationEmailRequest', req => {
return this.handleVerificationEmailRequest(req);
});
this.route('GET', '/verifyPassword', req => {
return this.handleVerifyPassword(req);
});
}
}