@@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user