@@ -13,7 +13,6 @@ var _adapter = Symbol();
|
||||
import Config from '../Config';
|
||||
|
||||
export class AdaptableController {
|
||||
|
||||
constructor(adapter, appId, options) {
|
||||
this.options = options;
|
||||
this.appId = appId;
|
||||
@@ -34,7 +33,7 @@ export class AdaptableController {
|
||||
}
|
||||
|
||||
expectedAdapterType() {
|
||||
throw new Error("Subclasses should implement expectedAdapterType()");
|
||||
throw new Error('Subclasses should implement expectedAdapterType()');
|
||||
}
|
||||
|
||||
validateAdapter(adapter) {
|
||||
@@ -43,7 +42,7 @@ export class AdaptableController {
|
||||
|
||||
static validateAdapter(adapter, self, ExpectedType) {
|
||||
if (!adapter) {
|
||||
throw new Error(this.constructor.name + " requires an adapter");
|
||||
throw new Error(this.constructor.name + ' requires an adapter');
|
||||
}
|
||||
|
||||
const Type = ExpectedType || self.expectedAdapterType();
|
||||
@@ -53,20 +52,27 @@ export class AdaptableController {
|
||||
}
|
||||
|
||||
// Makes sure the prototype matches
|
||||
const mismatches = Object.getOwnPropertyNames(Type.prototype).reduce((obj, key) => {
|
||||
const adapterType = typeof adapter[key];
|
||||
const expectedType = typeof Type.prototype[key];
|
||||
if (adapterType !== expectedType) {
|
||||
obj[key] = {
|
||||
expected: expectedType,
|
||||
actual: adapterType
|
||||
const mismatches = Object.getOwnPropertyNames(Type.prototype).reduce(
|
||||
(obj, key) => {
|
||||
const adapterType = typeof adapter[key];
|
||||
const expectedType = typeof Type.prototype[key];
|
||||
if (adapterType !== expectedType) {
|
||||
obj[key] = {
|
||||
expected: expectedType,
|
||||
actual: adapterType,
|
||||
};
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
if (Object.keys(mismatches).length > 0) {
|
||||
throw new Error("Adapter prototype don't match expected prototype", adapter, mismatches);
|
||||
throw new Error(
|
||||
"Adapter prototype don't match expected prototype",
|
||||
adapter,
|
||||
mismatches
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,29 @@ import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter';
|
||||
|
||||
export class AnalyticsController extends AdaptableController {
|
||||
appOpened(req) {
|
||||
return Promise.resolve().then(() => {
|
||||
return this.adapter.appOpened(req.body, req);
|
||||
}).then((response) => {
|
||||
return { response: response || {} };
|
||||
}).catch(() => {
|
||||
return { response: {} };
|
||||
});
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return this.adapter.appOpened(req.body, req);
|
||||
})
|
||||
.then(response => {
|
||||
return { response: response || {} };
|
||||
})
|
||||
.catch(() => {
|
||||
return { response: {} };
|
||||
});
|
||||
}
|
||||
|
||||
trackEvent(req) {
|
||||
return Promise.resolve().then(() => {
|
||||
return this.adapter.trackEvent(req.params.eventName, req.body, req);
|
||||
}).then((response) => {
|
||||
return { response: response || {} };
|
||||
}).catch(() => {
|
||||
return { response: {} };
|
||||
});
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return this.adapter.trackEvent(req.params.eventName, req.body, req);
|
||||
})
|
||||
.then(response => {
|
||||
return { response: response || {} };
|
||||
})
|
||||
.catch(() => {
|
||||
return { response: {} };
|
||||
});
|
||||
}
|
||||
|
||||
expectedAdapterType() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AdaptableController from './AdaptableController';
|
||||
import CacheAdapter from '../Adapters/Cache/CacheAdapter';
|
||||
import CacheAdapter from '../Adapters/Cache/CacheAdapter';
|
||||
|
||||
const KEY_SEPARATOR_CHAR = ':';
|
||||
|
||||
@@ -39,9 +39,7 @@ export class SubCache {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CacheController extends AdaptableController {
|
||||
|
||||
constructor(adapter, appId, options = {}) {
|
||||
super(adapter, appId, options);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,16 +5,16 @@ import { FilesAdapter } from '../Adapters/Files/FilesAdapter';
|
||||
import path from 'path';
|
||||
import mime from 'mime';
|
||||
|
||||
const legacyFilesRegex = new RegExp("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*");
|
||||
const legacyFilesRegex = new RegExp(
|
||||
'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}-.*'
|
||||
);
|
||||
|
||||
export class FilesController extends AdaptableController {
|
||||
|
||||
getFileData(config, filename) {
|
||||
return this.adapter.getFileData(filename);
|
||||
}
|
||||
|
||||
createFile(config, filename, data, contentType) {
|
||||
|
||||
const extname = path.extname(filename);
|
||||
|
||||
const hasExtension = extname.length > 0;
|
||||
@@ -33,7 +33,7 @@ export class FilesController extends AdaptableController {
|
||||
return this.adapter.createFile(filename, data, contentType).then(() => {
|
||||
return Promise.resolve({
|
||||
url: location,
|
||||
name: filename
|
||||
name: filename,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export class FilesController extends AdaptableController {
|
||||
*/
|
||||
expandFilesInObject(config, object) {
|
||||
if (object instanceof Array) {
|
||||
object.map((obj) => this.expandFilesInObject(config, obj));
|
||||
object.map(obj => this.expandFilesInObject(config, obj));
|
||||
return;
|
||||
}
|
||||
if (typeof object !== 'object') {
|
||||
@@ -69,9 +69,17 @@ export class FilesController extends AdaptableController {
|
||||
fileObject['url'] = this.adapter.getFileLocation(config, filename);
|
||||
} else {
|
||||
if (filename.indexOf('tfss-') === 0) {
|
||||
fileObject['url'] = 'http://files.parsetfss.com/' + config.fileKey + '/' + encodeURIComponent(filename);
|
||||
fileObject['url'] =
|
||||
'http://files.parsetfss.com/' +
|
||||
config.fileKey +
|
||||
'/' +
|
||||
encodeURIComponent(filename);
|
||||
} else if (legacyFilesRegex.test(filename)) {
|
||||
fileObject['url'] = 'http://files.parse.com/' + config.fileKey + '/' + encodeURIComponent(filename);
|
||||
fileObject['url'] =
|
||||
'http://files.parse.com/' +
|
||||
config.fileKey +
|
||||
'/' +
|
||||
encodeURIComponent(filename);
|
||||
} else {
|
||||
fileObject['url'] = this.adapter.getFileLocation(config, filename);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
/** @flow weak */
|
||||
|
||||
import * as triggers from "../triggers";
|
||||
import * as triggers from '../triggers';
|
||||
// @flow-disable-next
|
||||
import * as Parse from "parse/node";
|
||||
import * as Parse from 'parse/node';
|
||||
// @flow-disable-next
|
||||
import * as request from "request";
|
||||
import { logger } from '../logger';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import * as request from 'request';
|
||||
import { logger } from '../logger';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
|
||||
const DefaultHooksCollectionName = "_Hooks";
|
||||
const DefaultHooksCollectionName = '_Hooks';
|
||||
const HTTPAgents = {
|
||||
http: new http.Agent({ keepAlive: true }),
|
||||
https: new https.Agent({ keepAlive: true }),
|
||||
}
|
||||
};
|
||||
|
||||
export class HooksController {
|
||||
_applicationId:string;
|
||||
_webhookKey:string;
|
||||
_applicationId: string;
|
||||
_webhookKey: string;
|
||||
database: any;
|
||||
|
||||
constructor(applicationId:string, databaseController, webhookKey) {
|
||||
constructor(applicationId: string, databaseController, webhookKey) {
|
||||
this._applicationId = applicationId;
|
||||
this._webhookKey = webhookKey;
|
||||
this.database = databaseController;
|
||||
@@ -29,14 +29,16 @@ export class HooksController {
|
||||
load() {
|
||||
return this._getHooks().then(hooks => {
|
||||
hooks = hooks || [];
|
||||
hooks.forEach((hook) => {
|
||||
hooks.forEach(hook => {
|
||||
this.addHookToTriggers(hook);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getFunction(functionName) {
|
||||
return this._getHooks({ functionName: functionName }).then(results => results[0]);
|
||||
return this._getHooks({ functionName: functionName }).then(
|
||||
results => results[0]
|
||||
);
|
||||
}
|
||||
|
||||
getFunctions() {
|
||||
@@ -44,11 +46,17 @@ export class HooksController {
|
||||
}
|
||||
|
||||
getTrigger(className, triggerName) {
|
||||
return this._getHooks({ className: className, triggerName: triggerName }).then(results => results[0]);
|
||||
return this._getHooks({
|
||||
className: className,
|
||||
triggerName: triggerName,
|
||||
}).then(results => results[0]);
|
||||
}
|
||||
|
||||
getTriggers() {
|
||||
return this._getHooks({ className: { $exists: true }, triggerName: { $exists: true } });
|
||||
return this._getHooks({
|
||||
className: { $exists: true },
|
||||
triggerName: { $exists: true },
|
||||
});
|
||||
}
|
||||
|
||||
deleteFunction(functionName) {
|
||||
@@ -58,16 +66,21 @@ export class HooksController {
|
||||
|
||||
deleteTrigger(className, triggerName) {
|
||||
triggers.removeTrigger(triggerName, className, this._applicationId);
|
||||
return this._removeHooks({ className: className, triggerName: triggerName });
|
||||
return this._removeHooks({
|
||||
className: className,
|
||||
triggerName: triggerName,
|
||||
});
|
||||
}
|
||||
|
||||
_getHooks(query = {}) {
|
||||
return this.database.find(DefaultHooksCollectionName, query).then((results) => {
|
||||
return results.map((result) => {
|
||||
delete result.objectId;
|
||||
return result;
|
||||
return this.database
|
||||
.find(DefaultHooksCollectionName, query)
|
||||
.then(results => {
|
||||
return results.map(result => {
|
||||
delete result.objectId;
|
||||
return result;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_removeHooks(query) {
|
||||
@@ -79,24 +92,36 @@ export class HooksController {
|
||||
saveHook(hook) {
|
||||
var query;
|
||||
if (hook.functionName && hook.url) {
|
||||
query = { functionName: hook.functionName }
|
||||
query = { functionName: hook.functionName };
|
||||
} else if (hook.triggerName && hook.className && hook.url) {
|
||||
query = { className: hook.className, triggerName: hook.triggerName }
|
||||
query = { className: hook.className, triggerName: hook.triggerName };
|
||||
} else {
|
||||
throw new Parse.Error(143, "invalid hook declaration");
|
||||
throw new Parse.Error(143, 'invalid hook declaration');
|
||||
}
|
||||
return this.database.update(DefaultHooksCollectionName, query, hook, {upsert: true}).then(() => {
|
||||
return Promise.resolve(hook);
|
||||
})
|
||||
return this.database
|
||||
.update(DefaultHooksCollectionName, query, hook, { upsert: true })
|
||||
.then(() => {
|
||||
return Promise.resolve(hook);
|
||||
});
|
||||
}
|
||||
|
||||
addHookToTriggers(hook) {
|
||||
var wrappedFunction = wrapToHTTPRequest(hook, this._webhookKey);
|
||||
wrappedFunction.url = hook.url;
|
||||
if (hook.className) {
|
||||
triggers.addTrigger(hook.triggerName, hook.className, wrappedFunction, this._applicationId)
|
||||
triggers.addTrigger(
|
||||
hook.triggerName,
|
||||
hook.className,
|
||||
wrappedFunction,
|
||||
this._applicationId
|
||||
);
|
||||
} else {
|
||||
triggers.addFunction(hook.functionName, wrappedFunction, null, this._applicationId);
|
||||
triggers.addFunction(
|
||||
hook.functionName,
|
||||
wrappedFunction,
|
||||
null,
|
||||
this._applicationId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +136,19 @@ export class HooksController {
|
||||
hook = {};
|
||||
hook.functionName = aHook.functionName;
|
||||
hook.url = aHook.url;
|
||||
} else if (aHook && aHook.className && aHook.url && aHook.triggerName && triggers.Types[aHook.triggerName]) {
|
||||
} else if (
|
||||
aHook &&
|
||||
aHook.className &&
|
||||
aHook.url &&
|
||||
aHook.triggerName &&
|
||||
triggers.Types[aHook.triggerName]
|
||||
) {
|
||||
hook = {};
|
||||
hook.className = aHook.className;
|
||||
hook.url = aHook.url;
|
||||
hook.triggerName = aHook.triggerName;
|
||||
|
||||
} else {
|
||||
throw new Parse.Error(143, "invalid hook declaration");
|
||||
throw new Parse.Error(143, 'invalid hook declaration');
|
||||
}
|
||||
|
||||
return this.addHook(hook);
|
||||
@@ -126,47 +156,62 @@ export class HooksController {
|
||||
|
||||
createHook(aHook) {
|
||||
if (aHook.functionName) {
|
||||
return this.getFunction(aHook.functionName).then((result) => {
|
||||
return this.getFunction(aHook.functionName).then(result => {
|
||||
if (result) {
|
||||
throw new Parse.Error(143, `function name: ${aHook.functionName} already exits`);
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`function name: ${aHook.functionName} already exits`
|
||||
);
|
||||
} else {
|
||||
return this.createOrUpdateHook(aHook);
|
||||
}
|
||||
});
|
||||
} else if (aHook.className && aHook.triggerName) {
|
||||
return this.getTrigger(aHook.className, aHook.triggerName).then((result) => {
|
||||
if (result) {
|
||||
throw new Parse.Error(143, `class ${aHook.className} already has trigger ${aHook.triggerName}`);
|
||||
return this.getTrigger(aHook.className, aHook.triggerName).then(
|
||||
result => {
|
||||
if (result) {
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`class ${aHook.className} already has trigger ${
|
||||
aHook.triggerName
|
||||
}`
|
||||
);
|
||||
}
|
||||
return this.createOrUpdateHook(aHook);
|
||||
}
|
||||
return this.createOrUpdateHook(aHook);
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
throw new Parse.Error(143, "invalid hook declaration");
|
||||
throw new Parse.Error(143, 'invalid hook declaration');
|
||||
}
|
||||
|
||||
updateHook(aHook) {
|
||||
if (aHook.functionName) {
|
||||
return this.getFunction(aHook.functionName).then((result) => {
|
||||
return this.getFunction(aHook.functionName).then(result => {
|
||||
if (result) {
|
||||
return this.createOrUpdateHook(aHook);
|
||||
}
|
||||
throw new Parse.Error(143, `no function named: ${aHook.functionName} is defined`);
|
||||
throw new Parse.Error(
|
||||
143,
|
||||
`no function named: ${aHook.functionName} is defined`
|
||||
);
|
||||
});
|
||||
} else if (aHook.className && aHook.triggerName) {
|
||||
return this.getTrigger(aHook.className, aHook.triggerName).then((result) => {
|
||||
if (result) {
|
||||
return this.createOrUpdateHook(aHook);
|
||||
return this.getTrigger(aHook.className, aHook.triggerName).then(
|
||||
result => {
|
||||
if (result) {
|
||||
return this.createOrUpdateHook(aHook);
|
||||
}
|
||||
throw new Parse.Error(143, `class ${aHook.className} does not exist`);
|
||||
}
|
||||
throw new Parse.Error(143, `class ${aHook.className} does not exist`);
|
||||
});
|
||||
);
|
||||
}
|
||||
throw new Parse.Error(143, "invalid hook declaration");
|
||||
throw new Parse.Error(143, 'invalid hook declaration');
|
||||
}
|
||||
}
|
||||
|
||||
function wrapToHTTPRequest(hook, key) {
|
||||
return (req) => {
|
||||
return req => {
|
||||
const jsonBody = {};
|
||||
for (var i in req) {
|
||||
jsonBody[i] = req[i];
|
||||
@@ -181,32 +226,36 @@ function wrapToHTTPRequest(hook, key) {
|
||||
}
|
||||
const jsonRequest: any = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(jsonBody),
|
||||
};
|
||||
|
||||
const agent = hook.url.startsWith('https') ? HTTPAgents['https'] : HTTPAgents['http'];
|
||||
const agent = hook.url.startsWith('https')
|
||||
? HTTPAgents['https']
|
||||
: HTTPAgents['http'];
|
||||
jsonRequest.agent = agent;
|
||||
|
||||
if (key) {
|
||||
jsonRequest.headers['X-Parse-Webhook-Key'] = key;
|
||||
} else {
|
||||
logger.warn('Making outgoing webhook request without webhookKey being set!');
|
||||
logger.warn(
|
||||
'Making outgoing webhook request without webhookKey being set!'
|
||||
);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(hook.url, jsonRequest, function (err, httpResponse, body) {
|
||||
request.post(hook.url, jsonRequest, function(err, httpResponse, body) {
|
||||
var result;
|
||||
if (body) {
|
||||
if (typeof body === "string") {
|
||||
if (typeof body === 'string') {
|
||||
try {
|
||||
body = JSON.parse(body);
|
||||
} catch (e) {
|
||||
err = {
|
||||
error: "Malformed response",
|
||||
error: 'Malformed response',
|
||||
code: -1,
|
||||
partialResponse: body.substring(0, 100)
|
||||
partialResponse: body.substring(0, 100),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -222,13 +271,13 @@ function wrapToHTTPRequest(hook, key) {
|
||||
delete result.createdAt;
|
||||
delete result.updatedAt;
|
||||
}
|
||||
return resolve({object: result});
|
||||
return resolve({ object: result });
|
||||
} else {
|
||||
return resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default HooksController;
|
||||
|
||||
@@ -11,7 +11,7 @@ export class LiveQueryController {
|
||||
} else if (config.classNames instanceof Array) {
|
||||
this.classNames = new Set(config.classNames);
|
||||
} else {
|
||||
throw 'liveQuery.classes should be an array of string'
|
||||
throw 'liveQuery.classes should be an array of string';
|
||||
}
|
||||
this.liveQueryPublisher = new ParseCloudCodePublisher(config);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export class LiveQueryController {
|
||||
|
||||
_makePublisherRequest(currentObject: any, originalObject: any): any {
|
||||
const req = {
|
||||
object: currentObject
|
||||
object: currentObject,
|
||||
};
|
||||
if (currentObject) {
|
||||
req.original = originalObject;
|
||||
|
||||
@@ -9,26 +9,18 @@ const truncationMarker = '... (truncated)';
|
||||
|
||||
export const LogLevel = {
|
||||
INFO: 'info',
|
||||
ERROR: 'error'
|
||||
}
|
||||
ERROR: 'error',
|
||||
};
|
||||
|
||||
export const LogOrder = {
|
||||
DESCENDING: 'desc',
|
||||
ASCENDING: 'asc'
|
||||
}
|
||||
ASCENDING: 'asc',
|
||||
};
|
||||
|
||||
const logLevels = [
|
||||
'error',
|
||||
'warn',
|
||||
'info',
|
||||
'debug',
|
||||
'verbose',
|
||||
'silly',
|
||||
]
|
||||
const logLevels = ['error', 'warn', 'info', 'debug', 'verbose', 'silly'];
|
||||
|
||||
export class LoggerController extends AdaptableController {
|
||||
|
||||
constructor(adapter, appId, options = {logLevel: 'info'}) {
|
||||
constructor(adapter, appId, options = { logLevel: 'info' }) {
|
||||
super(adapter, appId, options);
|
||||
let level = 'info';
|
||||
if (options.verbose) {
|
||||
@@ -39,7 +31,8 @@ export class LoggerController extends AdaptableController {
|
||||
}
|
||||
const index = logLevels.indexOf(level); // info by default
|
||||
logLevels.forEach((level, levelIndex) => {
|
||||
if (levelIndex > index) { // silence the levels that are > maxIndex
|
||||
if (levelIndex > index) {
|
||||
// silence the levels that are > maxIndex
|
||||
this[level] = () => {};
|
||||
}
|
||||
});
|
||||
@@ -50,8 +43,8 @@ export class LoggerController extends AdaptableController {
|
||||
const query = urlObj.query;
|
||||
let sanitizedQuery = '?';
|
||||
|
||||
for(const key in query) {
|
||||
if(key !== 'password') {
|
||||
for (const key in query) {
|
||||
if (key !== 'password') {
|
||||
// normal value
|
||||
sanitizedQuery += key + '=' + query[key] + '&';
|
||||
} else {
|
||||
@@ -83,7 +76,8 @@ export class LoggerController extends AdaptableController {
|
||||
// for strings
|
||||
if (typeof e.url === 'string') {
|
||||
e.url = this.maskSensitiveUrl(e.url);
|
||||
} else if (Array.isArray(e.url)) { // for strings in array
|
||||
} else if (Array.isArray(e.url)) {
|
||||
// for strings in array
|
||||
e.url = e.url.map(item => {
|
||||
if (typeof item === 'string') {
|
||||
return this.maskSensitiveUrl(item);
|
||||
@@ -119,10 +113,15 @@ export class LoggerController extends AdaptableController {
|
||||
log(level, args) {
|
||||
// make the passed in arguments object an array with the spread operator
|
||||
args = this.maskSensitive([...args]);
|
||||
args = [].concat(level, args.map((arg) => {
|
||||
if (typeof arg === 'function') { return arg(); }
|
||||
return arg;
|
||||
}));
|
||||
args = [].concat(
|
||||
level,
|
||||
args.map(arg => {
|
||||
if (typeof arg === 'function') {
|
||||
return arg();
|
||||
}
|
||||
return arg;
|
||||
})
|
||||
);
|
||||
this.adapter.log.apply(this.adapter, args);
|
||||
}
|
||||
|
||||
@@ -150,33 +149,28 @@ export class LoggerController extends AdaptableController {
|
||||
return this.log('silly', arguments);
|
||||
}
|
||||
|
||||
logRequest({
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
body
|
||||
}) {
|
||||
this.verbose(() => {
|
||||
const stringifiedBody = JSON.stringify(body, null, 2);
|
||||
return `REQUEST for [${method}] ${url}: ${stringifiedBody}`;
|
||||
}, {
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
body
|
||||
});
|
||||
logRequest({ method, url, headers, body }) {
|
||||
this.verbose(
|
||||
() => {
|
||||
const stringifiedBody = JSON.stringify(body, null, 2);
|
||||
return `REQUEST for [${method}] ${url}: ${stringifiedBody}`;
|
||||
},
|
||||
{
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
body,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
logResponse({
|
||||
method,
|
||||
url,
|
||||
result
|
||||
}) {
|
||||
logResponse({ method, url, result }) {
|
||||
this.verbose(
|
||||
() => { const stringifiedResponse = JSON.stringify(result, null, 2);
|
||||
() => {
|
||||
const stringifiedResponse = JSON.stringify(result, null, 2);
|
||||
return `RESPONSE from [${method}] ${url}: ${stringifiedResponse}`;
|
||||
},
|
||||
{result: result}
|
||||
{ result: result }
|
||||
);
|
||||
}
|
||||
// check that date input is valid
|
||||
@@ -195,7 +189,8 @@ export class LoggerController extends AdaptableController {
|
||||
|
||||
truncateLogMessage(string) {
|
||||
if (string && string.length > LOG_STRING_TRUNCATE_LENGTH) {
|
||||
const truncated = string.substring(0, LOG_STRING_TRUNCATE_LENGTH) + truncationMarker;
|
||||
const truncated =
|
||||
string.substring(0, LOG_STRING_TRUNCATE_LENGTH) + truncationMarker;
|
||||
return truncated;
|
||||
}
|
||||
|
||||
@@ -203,7 +198,8 @@ export class LoggerController extends AdaptableController {
|
||||
}
|
||||
|
||||
static parseOptions(options = {}) {
|
||||
const from = LoggerController.validDateTime(options.from) ||
|
||||
const from =
|
||||
LoggerController.validDateTime(options.from) ||
|
||||
new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY);
|
||||
const until = LoggerController.validDateTime(options.until) || new Date();
|
||||
const size = Number(options.size) || 10;
|
||||
@@ -228,12 +224,16 @@ export class LoggerController extends AdaptableController {
|
||||
// size (optional) Number of rows returned by search. Defaults to 10
|
||||
getLogs(options = {}) {
|
||||
if (!this.adapter) {
|
||||
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'
|
||||
);
|
||||
}
|
||||
if (typeof this.adapter.query !== 'function') {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Querying logs is not supported with this adapter');
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Querying logs is not supported with this adapter'
|
||||
);
|
||||
}
|
||||
options = LoggerController.parseOptions(options);
|
||||
return this.adapter.query(options);
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import { Parse } from 'parse/node';
|
||||
import RestQuery from '../RestQuery';
|
||||
import RestWrite from '../RestWrite';
|
||||
import { master } from '../Auth';
|
||||
import { pushStatusHandler } from '../StatusHandler';
|
||||
import { Parse } from 'parse/node';
|
||||
import RestQuery from '../RestQuery';
|
||||
import RestWrite from '../RestWrite';
|
||||
import { master } from '../Auth';
|
||||
import { pushStatusHandler } from '../StatusHandler';
|
||||
import { applyDeviceTokenExists } from '../Push/utils';
|
||||
|
||||
export class PushController {
|
||||
|
||||
sendPush(body = {}, where = {}, config, auth, onPushStatusSaved = () => {}, now = new Date()) {
|
||||
sendPush(
|
||||
body = {},
|
||||
where = {},
|
||||
config,
|
||||
auth,
|
||||
onPushStatusSaved = () => {},
|
||||
now = new Date()
|
||||
) {
|
||||
if (!config.hasPushSupport) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Missing push configuration');
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Missing push configuration'
|
||||
);
|
||||
}
|
||||
|
||||
// Replace the expiration_time and push_time with a valid Unix epoch milliseconds time
|
||||
@@ -19,13 +27,14 @@ export class PushController {
|
||||
if (body.expiration_time && body.expiration_interval) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Both expiration_time and expiration_interval cannot be set');
|
||||
'Both expiration_time and expiration_interval cannot be set'
|
||||
);
|
||||
}
|
||||
|
||||
// Immediate push
|
||||
if (body.expiration_interval && !body.hasOwnProperty('push_time')) {
|
||||
const ttlMs = body.expiration_interval * 1000;
|
||||
body.expiration_time = (new Date(now.valueOf() + ttlMs)).valueOf();
|
||||
body.expiration_time = new Date(now.valueOf() + ttlMs).valueOf();
|
||||
}
|
||||
|
||||
const pushTime = PushController.getPushTime(body);
|
||||
@@ -37,18 +46,22 @@ export class PushController {
|
||||
// pushes to be sent. We probably change this behaviour in the future.
|
||||
let badgeUpdate = () => {
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
if (body.data && body.data.badge) {
|
||||
const badge = body.data.badge;
|
||||
let restUpdate = {};
|
||||
if (typeof badge == 'string' && badge.toLowerCase() === 'increment') {
|
||||
restUpdate = { badge: { __op: 'Increment', amount: 1 } }
|
||||
} else if (typeof badge == 'object' && typeof badge.__op == 'string' &&
|
||||
badge.__op.toLowerCase() == 'increment' && Number(badge.amount)) {
|
||||
restUpdate = { badge: { __op: 'Increment', amount: badge.amount } }
|
||||
restUpdate = { badge: { __op: 'Increment', amount: 1 } };
|
||||
} else if (
|
||||
typeof badge == 'object' &&
|
||||
typeof badge.__op == 'string' &&
|
||||
badge.__op.toLowerCase() == 'increment' &&
|
||||
Number(badge.amount)
|
||||
) {
|
||||
restUpdate = { badge: { __op: 'Increment', amount: badge.amount } };
|
||||
} else if (Number(badge)) {
|
||||
restUpdate = { badge: badge }
|
||||
restUpdate = { badge: badge };
|
||||
} else {
|
||||
throw "Invalid value for badge, expected number or 'Increment' or {increment: number}";
|
||||
}
|
||||
@@ -57,44 +70,75 @@ export class PushController {
|
||||
const updateWhere = applyDeviceTokenExists(where);
|
||||
badgeUpdate = () => {
|
||||
// Build a real RestQuery so we can use it in RestWrite
|
||||
const restQuery = new RestQuery(config, master(config), '_Installation', updateWhere);
|
||||
const restQuery = new RestQuery(
|
||||
config,
|
||||
master(config),
|
||||
'_Installation',
|
||||
updateWhere
|
||||
);
|
||||
return restQuery.buildRestWhere().then(() => {
|
||||
const write = new RestWrite(config, master(config), '_Installation', restQuery.restWhere, restUpdate);
|
||||
const write = new RestWrite(
|
||||
config,
|
||||
master(config),
|
||||
'_Installation',
|
||||
restQuery.restWhere,
|
||||
restUpdate
|
||||
);
|
||||
write.runOptions.many = true;
|
||||
return write.execute();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
const pushStatus = pushStatusHandler(config);
|
||||
return Promise.resolve().then(() => {
|
||||
return pushStatus.setInitial(body, where);
|
||||
}).then(() => {
|
||||
onPushStatusSaved(pushStatus.objectId);
|
||||
return badgeUpdate();
|
||||
}).then(() => {
|
||||
// Update audience lastUsed and timesUsed
|
||||
if (body.audience_id) {
|
||||
const audienceId = body.audience_id;
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
return pushStatus.setInitial(body, where);
|
||||
})
|
||||
.then(() => {
|
||||
onPushStatusSaved(pushStatus.objectId);
|
||||
return badgeUpdate();
|
||||
})
|
||||
.then(() => {
|
||||
// Update audience lastUsed and timesUsed
|
||||
if (body.audience_id) {
|
||||
const audienceId = body.audience_id;
|
||||
|
||||
var updateAudience = {
|
||||
lastUsed: { __type: "Date", iso: new Date().toISOString() },
|
||||
timesUsed: { __op: "Increment", "amount": 1 }
|
||||
};
|
||||
const write = new RestWrite(config, master(config), '_Audience', {objectId: audienceId}, updateAudience);
|
||||
write.execute();
|
||||
}
|
||||
// Don't wait for the audience update promise to resolve.
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
if (body.hasOwnProperty('push_time') && config.hasPushScheduledSupport) {
|
||||
var updateAudience = {
|
||||
lastUsed: { __type: 'Date', iso: new Date().toISOString() },
|
||||
timesUsed: { __op: 'Increment', amount: 1 },
|
||||
};
|
||||
const write = new RestWrite(
|
||||
config,
|
||||
master(config),
|
||||
'_Audience',
|
||||
{ objectId: audienceId },
|
||||
updateAudience
|
||||
);
|
||||
write.execute();
|
||||
}
|
||||
// Don't wait for the audience update promise to resolve.
|
||||
return Promise.resolve();
|
||||
}
|
||||
return config.pushControllerQueue.enqueue(body, where, config, auth, pushStatus);
|
||||
}).catch((err) => {
|
||||
return pushStatus.fail(err).then(() => {
|
||||
throw err;
|
||||
})
|
||||
.then(() => {
|
||||
if (
|
||||
body.hasOwnProperty('push_time') &&
|
||||
config.hasPushScheduledSupport
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return config.pushControllerQueue.enqueue(
|
||||
body,
|
||||
where,
|
||||
config,
|
||||
auth,
|
||||
pushStatus
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
return pushStatus.fail(err).then(() => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,13 +158,17 @@ export class PushController {
|
||||
} else if (typeof expirationTimeParam === 'string') {
|
||||
expirationTime = new Date(expirationTimeParam);
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['expiration_time'] + ' is not valid time.');
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['expiration_time'] + ' is not valid time.'
|
||||
);
|
||||
}
|
||||
// Check expirationTime is valid or not, if it is not valid, expirationTime is NaN
|
||||
if (!isFinite(expirationTime)) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['expiration_time'] + ' is not valid time.');
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['expiration_time'] + ' is not valid time.'
|
||||
);
|
||||
}
|
||||
return expirationTime.valueOf();
|
||||
}
|
||||
@@ -132,9 +180,14 @@ export class PushController {
|
||||
}
|
||||
|
||||
var expirationIntervalParam = body['expiration_interval'];
|
||||
if (typeof expirationIntervalParam !== 'number' || expirationIntervalParam <= 0) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
`expiration_interval must be a number greater than 0`);
|
||||
if (
|
||||
typeof expirationIntervalParam !== 'number' ||
|
||||
expirationIntervalParam <= 0
|
||||
) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
`expiration_interval must be a number greater than 0`
|
||||
);
|
||||
}
|
||||
return expirationIntervalParam;
|
||||
}
|
||||
@@ -159,13 +212,17 @@ export class PushController {
|
||||
isLocalTime = !PushController.pushTimeHasTimezoneComponent(pushTimeParam);
|
||||
date = new Date(pushTimeParam);
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['push_time'] + ' is not valid time.');
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['push_time'] + ' is not valid time.'
|
||||
);
|
||||
}
|
||||
// Check pushTime is valid or not, if it is not valid, pushTime is NaN
|
||||
if (!isFinite(date)) {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['push_time'] + ' is not valid time.');
|
||||
throw new Parse.Error(
|
||||
Parse.Error.PUSH_MISCONFIGURED,
|
||||
body['push_time'] + ' is not valid time.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -181,8 +238,10 @@ export class PushController {
|
||||
*/
|
||||
static pushTimeHasTimezoneComponent(pushTimeParam: string): boolean {
|
||||
const offsetPattern = /(.+)([+-])\d\d:\d\d$/;
|
||||
return pushTimeParam.indexOf('Z') === pushTimeParam.length - 1 // 2007-04-05T12:30Z
|
||||
|| offsetPattern.test(pushTimeParam); // 2007-04-05T12:30.000+02:00, 2007-04-05T12:30.000-02:00
|
||||
return (
|
||||
pushTimeParam.indexOf('Z') === pushTimeParam.length - 1 || // 2007-04-05T12:30Z
|
||||
offsetPattern.test(pushTimeParam)
|
||||
); // 2007-04-05T12:30.000+02:00, 2007-04-05T12:30.000-02:00
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,8 +250,15 @@ export class PushController {
|
||||
* @param isLocalTime {boolean}
|
||||
* @returns {string}
|
||||
*/
|
||||
static formatPushTime({ date, isLocalTime }: { date: Date, isLocalTime: boolean }) {
|
||||
if (isLocalTime) { // Strip 'Z'
|
||||
static formatPushTime({
|
||||
date,
|
||||
isLocalTime,
|
||||
}: {
|
||||
date: Date,
|
||||
isLocalTime: boolean,
|
||||
}) {
|
||||
if (isLocalTime) {
|
||||
// Strip 'Z'
|
||||
const isoString = date.toISOString();
|
||||
return isoString.substring(0, isoString.indexOf('Z'));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const MAIN_SCHEMA = "__MAIN_SCHEMA";
|
||||
const SCHEMA_CACHE_PREFIX = "__SCHEMA";
|
||||
const ALL_KEYS = "__ALL_KEYS";
|
||||
const MAIN_SCHEMA = '__MAIN_SCHEMA';
|
||||
const SCHEMA_CACHE_PREFIX = '__SCHEMA';
|
||||
const ALL_KEYS = '__ALL_KEYS';
|
||||
|
||||
import { randomString } from '../cryptoUtils';
|
||||
import defaults from '../defaults';
|
||||
@@ -8,7 +8,11 @@ import defaults from '../defaults';
|
||||
export default class SchemaCache {
|
||||
cache: Object;
|
||||
|
||||
constructor(cacheController, ttl = defaults.schemaCacheTTL, singleCache = false) {
|
||||
constructor(
|
||||
cacheController,
|
||||
ttl = defaults.schemaCacheTTL,
|
||||
singleCache = false
|
||||
) {
|
||||
this.ttl = ttl;
|
||||
if (typeof ttl == 'string') {
|
||||
this.ttl = parseInt(ttl);
|
||||
@@ -21,10 +25,13 @@ export default class SchemaCache {
|
||||
}
|
||||
|
||||
put(key, value) {
|
||||
return this.cache.get(this.prefix + ALL_KEYS).then((allKeys) => {
|
||||
return this.cache.get(this.prefix + ALL_KEYS).then(allKeys => {
|
||||
allKeys = allKeys || {};
|
||||
allKeys[key] = true;
|
||||
return Promise.all([this.cache.put(this.prefix + ALL_KEYS, allKeys, this.ttl), this.cache.put(key, value, this.ttl)]);
|
||||
return Promise.all([
|
||||
this.cache.put(this.prefix + ALL_KEYS, allKeys, this.ttl),
|
||||
this.cache.put(key, value, this.ttl),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,13 +60,13 @@ export default class SchemaCache {
|
||||
if (!this.ttl) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return this.cache.get(this.prefix + className).then((schema) => {
|
||||
return this.cache.get(this.prefix + className).then(schema => {
|
||||
if (schema) {
|
||||
return Promise.resolve(schema);
|
||||
}
|
||||
return this.cache.get(this.prefix + MAIN_SCHEMA).then((cachedSchemas) => {
|
||||
return this.cache.get(this.prefix + MAIN_SCHEMA).then(cachedSchemas => {
|
||||
cachedSchemas = cachedSchemas || [];
|
||||
schema = cachedSchemas.find((cachedSchema) => {
|
||||
schema = cachedSchemas.find(cachedSchema => {
|
||||
return cachedSchema.className === className;
|
||||
});
|
||||
if (schema) {
|
||||
@@ -72,11 +79,11 @@ export default class SchemaCache {
|
||||
|
||||
clear() {
|
||||
// That clears all caches...
|
||||
return this.cache.get(this.prefix + ALL_KEYS).then((allKeys) => {
|
||||
return this.cache.get(this.prefix + ALL_KEYS).then(allKeys => {
|
||||
if (!allKeys) {
|
||||
return;
|
||||
}
|
||||
const promises = Object.keys(allKeys).map((key) => {
|
||||
const promises = Object.keys(allKeys).map(key => {
|
||||
return this.cache.del(key);
|
||||
});
|
||||
return Promise.all(promises);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,14 @@
|
||||
import { randomString } from '../cryptoUtils';
|
||||
import { inflate } from '../triggers';
|
||||
import { randomString } from '../cryptoUtils';
|
||||
import { inflate } from '../triggers';
|
||||
import AdaptableController from './AdaptableController';
|
||||
import MailAdapter from '../Adapters/Email/MailAdapter';
|
||||
import rest from '../rest';
|
||||
import Parse from 'parse/node';
|
||||
import MailAdapter from '../Adapters/Email/MailAdapter';
|
||||
import rest from '../rest';
|
||||
import Parse from 'parse/node';
|
||||
|
||||
var RestQuery = require('../RestQuery');
|
||||
var Auth = require('../Auth');
|
||||
|
||||
export class UserController extends AdaptableController {
|
||||
|
||||
constructor(adapter, appId, options = {}) {
|
||||
super(adapter, appId, options);
|
||||
}
|
||||
@@ -36,7 +35,9 @@ export class UserController extends AdaptableController {
|
||||
user.emailVerified = false;
|
||||
|
||||
if (this.config.emailVerifyTokenValidityDuration) {
|
||||
user._email_verify_token_expires_at = Parse._encode(this.config.generateEmailVerifyTokenExpiresAt());
|
||||
user._email_verify_token_expires_at = Parse._encode(
|
||||
this.config.generateEmailVerifyTokenExpiresAt()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,8 +49,11 @@ export class UserController extends AdaptableController {
|
||||
throw undefined;
|
||||
}
|
||||
|
||||
const query = {username: username, _email_verify_token: token};
|
||||
const updateFields = { emailVerified: true, _email_verify_token: {__op: 'Delete'}};
|
||||
const query = { username: username, _email_verify_token: token };
|
||||
const updateFields = {
|
||||
emailVerified: true,
|
||||
_email_verify_token: { __op: 'Delete' },
|
||||
};
|
||||
|
||||
// if the email verify token needs to be validated then
|
||||
// add additional query params and additional fields that need to be updated
|
||||
@@ -57,10 +61,15 @@ export class UserController extends AdaptableController {
|
||||
query.emailVerified = false;
|
||||
query._email_verify_token_expires_at = { $gt: Parse._encode(new Date()) };
|
||||
|
||||
updateFields._email_verify_token_expires_at = {__op: 'Delete'};
|
||||
updateFields._email_verify_token_expires_at = { __op: 'Delete' };
|
||||
}
|
||||
const masterAuth = Auth.master(this.config);
|
||||
var checkIfAlreadyVerified = new RestQuery(this.config, Auth.master(this.config), '_User', {username: username, emailVerified: true});
|
||||
var checkIfAlreadyVerified = new RestQuery(
|
||||
this.config,
|
||||
Auth.master(this.config),
|
||||
'_User',
|
||||
{ username: username, emailVerified: true }
|
||||
);
|
||||
return checkIfAlreadyVerified.execute().then(result => {
|
||||
if (result.results.length) {
|
||||
return Promise.resolve(result.results.length[0]);
|
||||
@@ -70,25 +79,34 @@ export class UserController extends AdaptableController {
|
||||
}
|
||||
|
||||
checkResetTokenValidity(username, token) {
|
||||
return this.config.database.find('_User', {
|
||||
username: username,
|
||||
_perishable_token: token
|
||||
}, {limit: 1}).then(results => {
|
||||
if (results.length != 1) {
|
||||
throw undefined;
|
||||
}
|
||||
|
||||
if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) {
|
||||
let expiresDate = results[0]._perishable_token_expires_at;
|
||||
if (expiresDate && expiresDate.__type == 'Date') {
|
||||
expiresDate = new Date(expiresDate.iso);
|
||||
return this.config.database
|
||||
.find(
|
||||
'_User',
|
||||
{
|
||||
username: username,
|
||||
_perishable_token: token,
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
.then(results => {
|
||||
if (results.length != 1) {
|
||||
throw undefined;
|
||||
}
|
||||
if (expiresDate < new Date())
|
||||
throw 'The password reset link has expired';
|
||||
}
|
||||
|
||||
return results[0];
|
||||
});
|
||||
if (
|
||||
this.config.passwordPolicy &&
|
||||
this.config.passwordPolicy.resetTokenValidityDuration
|
||||
) {
|
||||
let expiresDate = results[0]._perishable_token_expires_at;
|
||||
if (expiresDate && expiresDate.__type == 'Date') {
|
||||
expiresDate = new Date(expiresDate.iso);
|
||||
}
|
||||
if (expiresDate < new Date())
|
||||
throw 'The password reset link has expired';
|
||||
}
|
||||
|
||||
return results[0];
|
||||
});
|
||||
}
|
||||
|
||||
getUserIfNeeded(user) {
|
||||
@@ -103,13 +121,18 @@ export class UserController extends AdaptableController {
|
||||
where.email = user.email;
|
||||
}
|
||||
|
||||
var query = new RestQuery(this.config, Auth.master(this.config), '_User', where);
|
||||
return query.execute().then(function(result){
|
||||
var query = new RestQuery(
|
||||
this.config,
|
||||
Auth.master(this.config),
|
||||
'_User',
|
||||
where
|
||||
);
|
||||
return query.execute().then(function(result) {
|
||||
if (result.results.length != 1) {
|
||||
throw undefined;
|
||||
}
|
||||
return result.results[0];
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
sendVerificationEmail(user) {
|
||||
@@ -118,10 +141,15 @@ export class UserController extends AdaptableController {
|
||||
}
|
||||
const token = encodeURIComponent(user._email_verify_token);
|
||||
// We may need to fetch the user in case of update email
|
||||
this.getUserIfNeeded(user).then((user) => {
|
||||
this.getUserIfNeeded(user).then(user => {
|
||||
const username = encodeURIComponent(user.username);
|
||||
|
||||
const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config);
|
||||
const link = buildEmailLink(
|
||||
this.config.verifyEmailURL,
|
||||
username,
|
||||
token,
|
||||
this.config
|
||||
);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
@@ -143,11 +171,15 @@ export class UserController extends AdaptableController {
|
||||
*/
|
||||
regenerateEmailVerifyToken(user) {
|
||||
this.setEmailVerifyToken(user);
|
||||
return this.config.database.update('_User', { username: user.username }, user);
|
||||
return this.config.database.update(
|
||||
'_User',
|
||||
{ username: user.username },
|
||||
user
|
||||
);
|
||||
}
|
||||
|
||||
resendVerificationEmail(username) {
|
||||
return this.getUserIfNeeded({username: username}).then((aUser) => {
|
||||
return this.getUserIfNeeded({ username: username }).then(aUser => {
|
||||
if (!aUser || aUser.emailVerified) {
|
||||
throw undefined;
|
||||
}
|
||||
@@ -160,93 +192,141 @@ export class UserController extends AdaptableController {
|
||||
setPasswordResetToken(email) {
|
||||
const token = { _perishable_token: randomString(25) };
|
||||
|
||||
if (this.config.passwordPolicy && this.config.passwordPolicy.resetTokenValidityDuration) {
|
||||
token._perishable_token_expires_at = Parse._encode(this.config.generatePasswordResetTokenExpiresAt());
|
||||
if (
|
||||
this.config.passwordPolicy &&
|
||||
this.config.passwordPolicy.resetTokenValidityDuration
|
||||
) {
|
||||
token._perishable_token_expires_at = Parse._encode(
|
||||
this.config.generatePasswordResetTokenExpiresAt()
|
||||
);
|
||||
}
|
||||
|
||||
return this.config.database.update('_User', { $or: [{email}, {username: email, email: {$exists: false}}] }, token, {}, true)
|
||||
return this.config.database.update(
|
||||
'_User',
|
||||
{ $or: [{ email }, { username: email, email: { $exists: false } }] },
|
||||
token,
|
||||
{},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
sendPasswordResetEmail(email) {
|
||||
if (!this.adapter) {
|
||||
throw "Trying to send a reset password but no adapter is set";
|
||||
throw 'Trying to send a reset password but no adapter is set';
|
||||
// TODO: No adapter?
|
||||
}
|
||||
|
||||
return this.setPasswordResetToken(email)
|
||||
.then(user => {
|
||||
const token = encodeURIComponent(user._perishable_token);
|
||||
const username = encodeURIComponent(user.username);
|
||||
return this.setPasswordResetToken(email).then(user => {
|
||||
const token = encodeURIComponent(user._perishable_token);
|
||||
const username = encodeURIComponent(user.username);
|
||||
|
||||
const link = buildEmailLink(this.config.requestResetPasswordURL, username, token, this.config);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
user: inflate('_User', user),
|
||||
};
|
||||
const link = buildEmailLink(
|
||||
this.config.requestResetPasswordURL,
|
||||
username,
|
||||
token,
|
||||
this.config
|
||||
);
|
||||
const options = {
|
||||
appName: this.config.appName,
|
||||
link: link,
|
||||
user: inflate('_User', user),
|
||||
};
|
||||
|
||||
if (this.adapter.sendPasswordResetEmail) {
|
||||
this.adapter.sendPasswordResetEmail(options);
|
||||
} else {
|
||||
this.adapter.sendMail(this.defaultResetPasswordEmail(options));
|
||||
}
|
||||
if (this.adapter.sendPasswordResetEmail) {
|
||||
this.adapter.sendPasswordResetEmail(options);
|
||||
} else {
|
||||
this.adapter.sendMail(this.defaultResetPasswordEmail(options));
|
||||
}
|
||||
|
||||
return Promise.resolve(user);
|
||||
});
|
||||
return Promise.resolve(user);
|
||||
});
|
||||
}
|
||||
|
||||
updatePassword(username, token, password) {
|
||||
return this.checkResetTokenValidity(username, token)
|
||||
.then(user => updateUserPassword(user.objectId, password, this.config))
|
||||
// clear reset password token
|
||||
.then(() => this.config.database.update('_User', {username}, {
|
||||
_perishable_token: {__op: 'Delete'},
|
||||
_perishable_token_expires_at: {__op: 'Delete'}
|
||||
})).catch((error) => {
|
||||
if (error.message) { // in case of Parse.Error, fail with the error message only
|
||||
return Promise.reject(error.message);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
return (
|
||||
this.checkResetTokenValidity(username, token)
|
||||
.then(user => updateUserPassword(user.objectId, password, this.config))
|
||||
// clear reset password token
|
||||
.then(() =>
|
||||
this.config.database.update(
|
||||
'_User',
|
||||
{ username },
|
||||
{
|
||||
_perishable_token: { __op: 'Delete' },
|
||||
_perishable_token_expires_at: { __op: 'Delete' },
|
||||
}
|
||||
)
|
||||
)
|
||||
.catch(error => {
|
||||
if (error.message) {
|
||||
// in case of Parse.Error, fail with the error message only
|
||||
return Promise.reject(error.message);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
defaultVerificationEmail({link, user, appName, }) {
|
||||
const text = "Hi,\n\n" +
|
||||
"You are being asked to confirm the e-mail address " + user.get("email") + " with " + appName + "\n\n" +
|
||||
"" +
|
||||
"Click here to confirm it:\n" + link;
|
||||
const to = user.get("email");
|
||||
defaultVerificationEmail({ link, user, appName }) {
|
||||
const text =
|
||||
'Hi,\n\n' +
|
||||
'You are being asked to confirm the e-mail address ' +
|
||||
user.get('email') +
|
||||
' with ' +
|
||||
appName +
|
||||
'\n\n' +
|
||||
'' +
|
||||
'Click here to confirm it:\n' +
|
||||
link;
|
||||
const to = user.get('email');
|
||||
const subject = 'Please verify your e-mail for ' + appName;
|
||||
return { text, to, subject };
|
||||
}
|
||||
|
||||
defaultResetPasswordEmail({link, user, appName, }) {
|
||||
const text = "Hi,\n\n" +
|
||||
"You requested to reset your password for " + appName +
|
||||
(user.get('username') ? (" (your username is '" + user.get('username') + "')") : "") + ".\n\n" +
|
||||
"" +
|
||||
"Click here to reset it:\n" + link;
|
||||
const to = user.get("email") || user.get('username');
|
||||
const subject = 'Password Reset for ' + appName;
|
||||
defaultResetPasswordEmail({ link, user, appName }) {
|
||||
const text =
|
||||
'Hi,\n\n' +
|
||||
'You requested to reset your password for ' +
|
||||
appName +
|
||||
(user.get('username')
|
||||
? " (your username is '" + user.get('username') + "')"
|
||||
: '') +
|
||||
'.\n\n' +
|
||||
'' +
|
||||
'Click here to reset it:\n' +
|
||||
link;
|
||||
const to = user.get('email') || user.get('username');
|
||||
const subject = 'Password Reset for ' + appName;
|
||||
return { text, to, subject };
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this private
|
||||
function updateUserPassword(userId, password, config) {
|
||||
return rest.update(config, Auth.master(config), '_User', { objectId: userId }, {
|
||||
password: password
|
||||
});
|
||||
return rest.update(
|
||||
config,
|
||||
Auth.master(config),
|
||||
'_User',
|
||||
{ objectId: userId },
|
||||
{
|
||||
password: password,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function buildEmailLink(destination, username, token, config) {
|
||||
const usernameAndToken = `token=${token}&username=${username}`
|
||||
const usernameAndToken = `token=${token}&username=${username}`;
|
||||
|
||||
if (config.parseFrameURL) {
|
||||
const destinationWithoutHost = destination.replace(config.publicServerURL, '');
|
||||
const destinationWithoutHost = destination.replace(
|
||||
config.publicServerURL,
|
||||
''
|
||||
);
|
||||
|
||||
return `${config.parseFrameURL}?link=${encodeURIComponent(destinationWithoutHost)}&${usernameAndToken}`;
|
||||
return `${config.parseFrameURL}?link=${encodeURIComponent(
|
||||
destinationWithoutHost
|
||||
)}&${usernameAndToken}`;
|
||||
} else {
|
||||
return `${destination}?${usernameAndToken}`;
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import authDataManager from '../Adapters/Auth';
|
||||
import { ParseServerOptions } from '../Options';
|
||||
import { loadAdapter } from '../Adapters/AdapterLoader';
|
||||
import defaults from '../defaults';
|
||||
import url from 'url';
|
||||
import authDataManager from '../Adapters/Auth';
|
||||
import { ParseServerOptions } from '../Options';
|
||||
import { loadAdapter } from '../Adapters/AdapterLoader';
|
||||
import defaults from '../defaults';
|
||||
import url from 'url';
|
||||
// Controllers
|
||||
import { LoggerController } from './LoggerController';
|
||||
import { FilesController } from './FilesController';
|
||||
import { HooksController } from './HooksController';
|
||||
import { UserController } from './UserController';
|
||||
import { CacheController } from './CacheController';
|
||||
import { LiveQueryController } from './LiveQueryController';
|
||||
import { AnalyticsController } from './AnalyticsController';
|
||||
import { PushController } from './PushController';
|
||||
import { PushQueue } from '../Push/PushQueue';
|
||||
import { PushWorker } from '../Push/PushWorker';
|
||||
import DatabaseController from './DatabaseController';
|
||||
import SchemaCache from './SchemaCache';
|
||||
import { LoggerController } from './LoggerController';
|
||||
import { FilesController } from './FilesController';
|
||||
import { HooksController } from './HooksController';
|
||||
import { UserController } from './UserController';
|
||||
import { CacheController } from './CacheController';
|
||||
import { LiveQueryController } from './LiveQueryController';
|
||||
import { AnalyticsController } from './AnalyticsController';
|
||||
import { PushController } from './PushController';
|
||||
import { PushQueue } from '../Push/PushQueue';
|
||||
import { PushWorker } from '../Push/PushWorker';
|
||||
import DatabaseController from './DatabaseController';
|
||||
import SchemaCache from './SchemaCache';
|
||||
|
||||
// Adapters
|
||||
import { GridStoreAdapter } from '../Adapters/Files/GridStoreAdapter';
|
||||
import { GridStoreAdapter } from '../Adapters/Files/GridStoreAdapter';
|
||||
import { WinstonLoggerAdapter } from '../Adapters/Logger/WinstonLoggerAdapter';
|
||||
import { InMemoryCacheAdapter } from '../Adapters/Cache/InMemoryCacheAdapter';
|
||||
import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter';
|
||||
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||
import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter';
|
||||
import ParsePushAdapter from '@parse/push-adapter';
|
||||
import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter';
|
||||
import MongoStorageAdapter from '../Adapters/Storage/Mongo/MongoStorageAdapter';
|
||||
import PostgresStorageAdapter from '../Adapters/Storage/Postgres/PostgresStorageAdapter';
|
||||
import ParsePushAdapter from '@parse/push-adapter';
|
||||
|
||||
export function getControllers(options: ParseServerOptions) {
|
||||
const loggerController = getLoggerController(options);
|
||||
@@ -35,7 +35,7 @@ export function getControllers(options: ParseServerOptions) {
|
||||
hasPushScheduledSupport,
|
||||
hasPushSupport,
|
||||
pushControllerQueue,
|
||||
pushWorker
|
||||
pushWorker,
|
||||
} = getPushController(options);
|
||||
const cacheController = getCacheController(options);
|
||||
const analyticsController = getAnalyticsController(options);
|
||||
@@ -61,7 +61,9 @@ export function getControllers(options: ParseServerOptions) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getLoggerController(options: ParseServerOptions): LoggerController {
|
||||
export function getLoggerController(
|
||||
options: ParseServerOptions
|
||||
): LoggerController {
|
||||
const {
|
||||
appId,
|
||||
jsonLogs,
|
||||
@@ -72,11 +74,17 @@ export function getLoggerController(options: ParseServerOptions): LoggerControll
|
||||
loggerAdapter,
|
||||
} = options;
|
||||
const loggerOptions = { jsonLogs, logsFolder, verbose, logLevel, silent };
|
||||
const loggerControllerAdapter = loadAdapter(loggerAdapter, WinstonLoggerAdapter, loggerOptions);
|
||||
const loggerControllerAdapter = loadAdapter(
|
||||
loggerAdapter,
|
||||
WinstonLoggerAdapter,
|
||||
loggerOptions
|
||||
);
|
||||
return new LoggerController(loggerControllerAdapter, appId, loggerOptions);
|
||||
}
|
||||
|
||||
export function getFilesController(options: ParseServerOptions): FilesController {
|
||||
export function getFilesController(
|
||||
options: ParseServerOptions
|
||||
): FilesController {
|
||||
const {
|
||||
appId,
|
||||
databaseURI,
|
||||
@@ -90,43 +98,52 @@ export function getFilesController(options: ParseServerOptions): FilesController
|
||||
const filesControllerAdapter = loadAdapter(filesAdapter, () => {
|
||||
return new GridStoreAdapter(databaseURI);
|
||||
});
|
||||
return new FilesController(filesControllerAdapter, appId, { preserveFileName });
|
||||
return new FilesController(filesControllerAdapter, appId, {
|
||||
preserveFileName,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserController(options: ParseServerOptions): UserController {
|
||||
const {
|
||||
appId,
|
||||
emailAdapter,
|
||||
verifyUserEmails,
|
||||
} = options;
|
||||
const { appId, emailAdapter, verifyUserEmails } = options;
|
||||
const emailControllerAdapter = loadAdapter(emailAdapter);
|
||||
return new UserController(emailControllerAdapter, appId, { verifyUserEmails });
|
||||
return new UserController(emailControllerAdapter, appId, {
|
||||
verifyUserEmails,
|
||||
});
|
||||
}
|
||||
|
||||
export function getCacheController(options: ParseServerOptions): CacheController {
|
||||
const {
|
||||
appId,
|
||||
export function getCacheController(
|
||||
options: ParseServerOptions
|
||||
): CacheController {
|
||||
const { appId, cacheAdapter, cacheTTL, cacheMaxSize } = options;
|
||||
const cacheControllerAdapter = loadAdapter(
|
||||
cacheAdapter,
|
||||
cacheTTL,
|
||||
cacheMaxSize,
|
||||
} = options;
|
||||
const cacheControllerAdapter = loadAdapter(cacheAdapter, InMemoryCacheAdapter, {appId: appId, ttl: cacheTTL, maxSize: cacheMaxSize });
|
||||
InMemoryCacheAdapter,
|
||||
{ appId: appId, ttl: cacheTTL, maxSize: cacheMaxSize }
|
||||
);
|
||||
return new CacheController(cacheControllerAdapter, appId);
|
||||
}
|
||||
|
||||
export function getAnalyticsController(options: ParseServerOptions): AnalyticsController {
|
||||
const {
|
||||
export function getAnalyticsController(
|
||||
options: ParseServerOptions
|
||||
): AnalyticsController {
|
||||
const { analyticsAdapter } = options;
|
||||
const analyticsControllerAdapter = loadAdapter(
|
||||
analyticsAdapter,
|
||||
} = options;
|
||||
const analyticsControllerAdapter = loadAdapter(analyticsAdapter, AnalyticsAdapter);
|
||||
AnalyticsAdapter
|
||||
);
|
||||
return new AnalyticsController(analyticsControllerAdapter);
|
||||
}
|
||||
|
||||
export function getLiveQueryController(options: ParseServerOptions): LiveQueryController {
|
||||
export function getLiveQueryController(
|
||||
options: ParseServerOptions
|
||||
): LiveQueryController {
|
||||
return new LiveQueryController(options.liveQuery);
|
||||
}
|
||||
|
||||
export function getDatabaseController(options: ParseServerOptions, cacheController: CacheController): DatabaseController {
|
||||
export function getDatabaseController(
|
||||
options: ParseServerOptions,
|
||||
cacheController: CacheController
|
||||
): DatabaseController {
|
||||
const {
|
||||
databaseURI,
|
||||
databaseOptions,
|
||||
@@ -134,39 +151,48 @@ export function getDatabaseController(options: ParseServerOptions, cacheControll
|
||||
schemaCacheTTL,
|
||||
enableSingleSchemaCache,
|
||||
} = options;
|
||||
let {
|
||||
let { databaseAdapter } = options;
|
||||
if (
|
||||
(databaseOptions ||
|
||||
(databaseURI && databaseURI !== defaults.databaseURI) ||
|
||||
collectionPrefix !== defaults.collectionPrefix) &&
|
||||
databaseAdapter
|
||||
} = options;
|
||||
if ((databaseOptions || (databaseURI && databaseURI !== defaults.databaseURI) || collectionPrefix !== defaults.collectionPrefix) && databaseAdapter) {
|
||||
) {
|
||||
throw 'You cannot specify both a databaseAdapter and a databaseURI/databaseOptions/collectionPrefix.';
|
||||
} else if (!databaseAdapter) {
|
||||
databaseAdapter = getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions)
|
||||
databaseAdapter = getDatabaseAdapter(
|
||||
databaseURI,
|
||||
collectionPrefix,
|
||||
databaseOptions
|
||||
);
|
||||
} else {
|
||||
databaseAdapter = loadAdapter(databaseAdapter)
|
||||
databaseAdapter = loadAdapter(databaseAdapter);
|
||||
}
|
||||
return new DatabaseController(databaseAdapter, new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache));
|
||||
return new DatabaseController(
|
||||
databaseAdapter,
|
||||
new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache)
|
||||
);
|
||||
}
|
||||
|
||||
export function getHooksController(options: ParseServerOptions, databaseController: DatabaseController): HooksController {
|
||||
const {
|
||||
appId,
|
||||
webhookKey,
|
||||
} = options;
|
||||
export function getHooksController(
|
||||
options: ParseServerOptions,
|
||||
databaseController: DatabaseController
|
||||
): HooksController {
|
||||
const { appId, webhookKey } = options;
|
||||
return new HooksController(appId, databaseController, webhookKey);
|
||||
}
|
||||
|
||||
interface PushControlling {
|
||||
pushController: PushController,
|
||||
hasPushScheduledSupport: boolean,
|
||||
pushControllerQueue: PushQueue,
|
||||
pushWorker: PushWorker
|
||||
pushController: PushController;
|
||||
hasPushScheduledSupport: boolean;
|
||||
pushControllerQueue: PushQueue;
|
||||
pushWorker: PushWorker;
|
||||
}
|
||||
|
||||
export function getPushController(options: ParseServerOptions): PushControlling {
|
||||
const {
|
||||
scheduledPush,
|
||||
push,
|
||||
} = options;
|
||||
export function getPushController(
|
||||
options: ParseServerOptions
|
||||
): PushControlling {
|
||||
const { scheduledPush, push } = options;
|
||||
|
||||
const pushOptions = Object.assign({}, push);
|
||||
const pushQueueOptions = pushOptions.queueOptions || {};
|
||||
@@ -175,16 +201,18 @@ export function getPushController(options: ParseServerOptions): PushControlling
|
||||
}
|
||||
|
||||
// Pass the push options too as it works with the default
|
||||
const pushAdapter = loadAdapter(pushOptions && pushOptions.adapter, ParsePushAdapter, pushOptions);
|
||||
const pushAdapter = loadAdapter(
|
||||
pushOptions && pushOptions.adapter,
|
||||
ParsePushAdapter,
|
||||
pushOptions
|
||||
);
|
||||
// We pass the options and the base class for the adatper,
|
||||
// Note that passing an instance would work too
|
||||
const pushController = new PushController();
|
||||
const hasPushSupport = !!(pushAdapter && push);
|
||||
const hasPushScheduledSupport = hasPushSupport && (scheduledPush === true);
|
||||
const hasPushScheduledSupport = hasPushSupport && scheduledPush === true;
|
||||
|
||||
const {
|
||||
disablePushWorker
|
||||
} = pushQueueOptions;
|
||||
const { disablePushWorker } = pushQueueOptions;
|
||||
|
||||
const pushControllerQueue = new PushQueue(pushQueueOptions);
|
||||
let pushWorker;
|
||||
@@ -196,36 +224,39 @@ export function getPushController(options: ParseServerOptions): PushControlling
|
||||
hasPushSupport,
|
||||
hasPushScheduledSupport,
|
||||
pushControllerQueue,
|
||||
pushWorker
|
||||
}
|
||||
pushWorker,
|
||||
};
|
||||
}
|
||||
|
||||
export function getAuthDataManager(options: ParseServerOptions) {
|
||||
const {
|
||||
auth,
|
||||
enableAnonymousUsers
|
||||
} = options;
|
||||
return authDataManager(auth, enableAnonymousUsers)
|
||||
const { auth, enableAnonymousUsers } = options;
|
||||
return authDataManager(auth, enableAnonymousUsers);
|
||||
}
|
||||
|
||||
export function getDatabaseAdapter(databaseURI, collectionPrefix, databaseOptions) {
|
||||
export function getDatabaseAdapter(
|
||||
databaseURI,
|
||||
collectionPrefix,
|
||||
databaseOptions
|
||||
) {
|
||||
let protocol;
|
||||
try {
|
||||
const parsedURI = url.parse(databaseURI);
|
||||
protocol = parsedURI.protocol ? parsedURI.protocol.toLowerCase() : null;
|
||||
} catch(e) { /* */ }
|
||||
} catch (e) {
|
||||
/* */
|
||||
}
|
||||
switch (protocol) {
|
||||
case 'postgres:':
|
||||
return new PostgresStorageAdapter({
|
||||
uri: databaseURI,
|
||||
collectionPrefix,
|
||||
databaseOptions
|
||||
});
|
||||
default:
|
||||
return new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
collectionPrefix,
|
||||
mongoOptions: databaseOptions,
|
||||
});
|
||||
case 'postgres:':
|
||||
return new PostgresStorageAdapter({
|
||||
uri: databaseURI,
|
||||
collectionPrefix,
|
||||
databaseOptions,
|
||||
});
|
||||
default:
|
||||
return new MongoStorageAdapter({
|
||||
uri: databaseURI,
|
||||
collectionPrefix,
|
||||
mongoOptions: databaseOptions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
export type LoadSchemaOptions = {
|
||||
clearCache: boolean
|
||||
clearCache: boolean,
|
||||
};
|
||||
|
||||
export type SchemaField = {
|
||||
type: string;
|
||||
targetClass?: ?string;
|
||||
}
|
||||
type: string,
|
||||
targetClass?: ?string,
|
||||
};
|
||||
|
||||
export type SchemaFields = { [string]: SchemaField }
|
||||
export type SchemaFields = { [string]: SchemaField };
|
||||
|
||||
export type Schema = {
|
||||
className: string,
|
||||
fields: SchemaFields,
|
||||
classLevelPermissions: ClassLevelPermissions,
|
||||
indexes?: ?any
|
||||
indexes?: ?any,
|
||||
};
|
||||
|
||||
export type ClassLevelPermissions = {
|
||||
find?: {[string]: boolean};
|
||||
count?: {[string]: boolean};
|
||||
get?: {[string]: boolean};
|
||||
create?: {[string]: boolean};
|
||||
update?: {[string]: boolean};
|
||||
delete?: {[string]: boolean};
|
||||
addField?: {[string]: boolean};
|
||||
readUserFields?: string[];
|
||||
writeUserFields?: string[];
|
||||
find?: { [string]: boolean },
|
||||
count?: { [string]: boolean },
|
||||
get?: { [string]: boolean },
|
||||
create?: { [string]: boolean },
|
||||
update?: { [string]: boolean },
|
||||
delete?: { [string]: boolean },
|
||||
addField?: { [string]: boolean },
|
||||
readUserFields?: string[],
|
||||
writeUserFields?: string[],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user