Improves AdapterLoader, enforces configuraiton on Adapters
This commit is contained in:
@@ -2,15 +2,17 @@
|
||||
var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
|
||||
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
|
||||
|
||||
describe("AdaptableController", ()=>{
|
||||
describe("AdapterLoader", ()=>{
|
||||
|
||||
it("should instantiate an adapter from string in object", (done) => {
|
||||
var adapterPath = require('path').resolve("./spec/MockAdapter");
|
||||
|
||||
var adapter = loadAdapter({
|
||||
adapter: adapterPath,
|
||||
options: {
|
||||
key: "value",
|
||||
foo: "bar"
|
||||
}
|
||||
});
|
||||
|
||||
expect(adapter instanceof Object).toBe(true);
|
||||
@@ -24,7 +26,6 @@ describe("AdaptableController", ()=>{
|
||||
var adapter = loadAdapter(adapterPath);
|
||||
|
||||
expect(adapter instanceof Object).toBe(true);
|
||||
expect(adapter.options).toBe(adapterPath);
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -65,4 +66,22 @@ describe("AdaptableController", ()=>{
|
||||
expect(adapter).toBe(originalAdapter);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail loading an improperly configured adapter", (done) => {
|
||||
var Adapter = function(options) {
|
||||
if (!options.foo) {
|
||||
throw "foo is required for that adapter";
|
||||
}
|
||||
}
|
||||
var adapterOptions = {
|
||||
param: "key",
|
||||
doSomething: function() {}
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
var adapter = loadAdapter(adapterOptions, Adapter);
|
||||
expect(adapter).toEqual(adapterOptions);
|
||||
}).not.toThrow("foo is required for that adapter");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
module.exports = function(options) {
|
||||
this.options = options;
|
||||
}
|
||||
return {
|
||||
options: options
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ module.exports = options => {
|
||||
throw "Options were not provided"
|
||||
}
|
||||
return {
|
||||
sendVerificationEmail: () => Promise.resolve()
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendMail: () => Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
|
||||
var OneSignalPushAdapter = require('../src/Adapters/Push/OneSignalPushAdapter');
|
||||
var classifyInstallations = require('../src/Adapters/Push/PushAdapterUtils').classifyInstallations;
|
||||
describe('OneSignalPushAdapter', () => {
|
||||
it('can be initialized', (done) => {
|
||||
// Make mock config
|
||||
var pushConfig = {
|
||||
|
||||
// Make mock config
|
||||
var pushConfig = {
|
||||
oneSignalAppId:"APP ID",
|
||||
oneSignalApiKey:"API KEY"
|
||||
};
|
||||
};
|
||||
|
||||
describe('OneSignalPushAdapter', () => {
|
||||
it('can be initialized', (done) => {
|
||||
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
||||
|
||||
@@ -18,8 +20,16 @@ describe('OneSignalPushAdapter', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
it('cannt be initialized if options are missing', (done) => {
|
||||
|
||||
expect(() => {
|
||||
new OneSignalPushAdapter();
|
||||
}).toThrow("Trying to initialiazed OneSignalPushAdapter without oneSignalAppId or oneSignalApiKey");
|
||||
done();
|
||||
});
|
||||
|
||||
it('can get valid push types', (done) => {
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter();
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
||||
|
||||
expect(oneSignalPushAdapter.getValidPushTypes()).toEqual(['ios', 'android']);
|
||||
done();
|
||||
@@ -56,7 +66,7 @@ describe('OneSignalPushAdapter', () => {
|
||||
|
||||
|
||||
it('can send push notifications', (done) => {
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter();
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
||||
|
||||
// Mock android ios senders
|
||||
var androidSender = jasmine.createSpy('send')
|
||||
@@ -108,7 +118,7 @@ describe('OneSignalPushAdapter', () => {
|
||||
});
|
||||
|
||||
it("can send iOS notifications", (done) => {
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter();
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
||||
var sendToOneSignal = jasmine.createSpy('sendToOneSignal');
|
||||
oneSignalPushAdapter.sendToOneSignal = sendToOneSignal;
|
||||
|
||||
@@ -135,7 +145,7 @@ describe('OneSignalPushAdapter', () => {
|
||||
});
|
||||
|
||||
it("can send Android notifications", (done) => {
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter();
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
||||
var sendToOneSignal = jasmine.createSpy('sendToOneSignal');
|
||||
oneSignalPushAdapter.sendToOneSignal = sendToOneSignal;
|
||||
|
||||
@@ -157,10 +167,7 @@ describe('OneSignalPushAdapter', () => {
|
||||
});
|
||||
|
||||
it("can post the correct data", (done) => {
|
||||
var pushConfig = {
|
||||
oneSignalAppId:"APP ID",
|
||||
oneSignalApiKey:"API KEY"
|
||||
};
|
||||
|
||||
var oneSignalPushAdapter = new OneSignalPushAdapter(pushConfig);
|
||||
|
||||
var write = jasmine.createSpy('write');
|
||||
|
||||
@@ -51,7 +51,8 @@ describe('Parse.User testing', () => {
|
||||
|
||||
it('sends verification email if email verification is enabled', done => {
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: () => Promise.resolve()
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendMail: () => Promise.resolve()
|
||||
}
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
@@ -89,7 +90,8 @@ describe('Parse.User testing', () => {
|
||||
|
||||
it('does not send verification email if email verification is disabled', done => {
|
||||
var emailAdapter = {
|
||||
sendVerificationEmail: () => Promise.resolve()
|
||||
sendVerificationEmail: () => Promise.resolve(),
|
||||
sendMail: () => Promise.resolve()
|
||||
}
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
@@ -131,7 +133,8 @@ describe('Parse.User testing', () => {
|
||||
expect(options.appName).toEqual('emailing app');
|
||||
expect(options.user.get('email')).toEqual('user@parse.com');
|
||||
done();
|
||||
}
|
||||
},
|
||||
sendMail: () => {}
|
||||
}
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
@@ -175,7 +178,8 @@ describe('Parse.User testing', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
sendMail: () => {}
|
||||
}
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
@@ -232,7 +236,8 @@ describe('Parse.User testing', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
sendMail: () => {}
|
||||
}
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
|
||||
@@ -1,36 +1,43 @@
|
||||
export function loadAdapter(options, defaultAdapter) {
|
||||
let adapter;
|
||||
export function loadAdapter(adapter, defaultAdapter, options) {
|
||||
|
||||
// We have options and options have adapter key
|
||||
if (options) {
|
||||
// Pass an adapter as a module name, a function or an instance
|
||||
if (typeof options == "string" || typeof options == "function" || options.constructor != Object) {
|
||||
adapter = options;
|
||||
if (!adapter)
|
||||
{
|
||||
if (!defaultAdapter) {
|
||||
return options;
|
||||
}
|
||||
if (options.adapter) {
|
||||
adapter = options.adapter;
|
||||
// Load from the default adapter when no adapter is set
|
||||
return loadAdapter(defaultAdapter, undefined, options);
|
||||
} else if (typeof adapter === "function") {
|
||||
try {
|
||||
return adapter(options);
|
||||
} catch(e) {
|
||||
var Adapter = adapter;
|
||||
return new Adapter(options);
|
||||
}
|
||||
}
|
||||
|
||||
if (!adapter) {
|
||||
adapter = defaultAdapter;
|
||||
}
|
||||
|
||||
// This is a string, require the module
|
||||
if (typeof adapter === "string") {
|
||||
} else if (typeof adapter === "string") {
|
||||
adapter = require(adapter);
|
||||
// If it's define as a module, get the default
|
||||
if (adapter.default) {
|
||||
adapter = adapter.default;
|
||||
}
|
||||
|
||||
return loadAdapter(adapter, undefined, options);
|
||||
} else if (adapter.module) {
|
||||
return loadAdapter(adapter.module, undefined, adapter.options);
|
||||
} else if (adapter.class) {
|
||||
return loadAdapter(adapter.class, undefined, adapter.options);
|
||||
} else if (adapter.adapter) {
|
||||
return loadAdapter(adapter.adapter, undefined, adapter.options);
|
||||
} else {
|
||||
// Try to load the defaultAdapter with the options
|
||||
// The default adapter should throw if the options are
|
||||
// incompatible
|
||||
try {
|
||||
return loadAdapter(defaultAdapter, undefined, adapter);
|
||||
} catch (e) {};
|
||||
}
|
||||
// From there it's either a function or an object
|
||||
// if it's an function, instanciate and pass the options
|
||||
if (typeof adapter === "function") {
|
||||
var Adapter = adapter;
|
||||
adapter = new Adapter(options);
|
||||
}
|
||||
// return the adapter as is as it's unusable otherwise
|
||||
return adapter;
|
||||
}
|
||||
|
||||
module.exports = { loadAdapter }
|
||||
export default loadAdapter;
|
||||
|
||||
6
src/Adapters/Email/MailAdapter.js
Normal file
6
src/Adapters/Email/MailAdapter.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export class MailAdapter {
|
||||
sendVerificationEmail(options) {}
|
||||
sendMail(options) {}
|
||||
}
|
||||
|
||||
export default MailAdapter;
|
||||
@@ -6,7 +6,7 @@ let SimpleMailgunAdapter = mailgunOptions => {
|
||||
}
|
||||
let mailgun = Mailgun(mailgunOptions);
|
||||
|
||||
let sendMail = (to, subject, text) => {
|
||||
let sendMail = ({to, subject, text}) => {
|
||||
let data = {
|
||||
from: mailgunOptions.fromAddress,
|
||||
to: to,
|
||||
@@ -24,16 +24,21 @@ let SimpleMailgunAdapter = mailgunOptions => {
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
return Object.freeze({
|
||||
sendVerificationEmail: ({ link, user, appName, }) => {
|
||||
let verifyMessage =
|
||||
"Hi,\n\n" +
|
||||
"You are being asked to confirm the e-mail address " + user.email + " with " + appName + "\n\n" +
|
||||
"" +
|
||||
"Click here to confirm it:\n" + link;
|
||||
return sendMail(user.email, 'Please verify your e-mail for ' + appName, verifyMessage);
|
||||
}
|
||||
}
|
||||
return sendMail({
|
||||
to:user.email,
|
||||
subject: 'Please verify your e-mail for ' + appName,
|
||||
text: verifyMessage
|
||||
});
|
||||
},
|
||||
sendMail: sendMail
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = SimpleMailgunAdapter
|
||||
|
||||
@@ -4,21 +4,36 @@
|
||||
|
||||
import * as AWS from 'aws-sdk';
|
||||
import { FilesAdapter } from './FilesAdapter';
|
||||
import requiredParameter from '../../requiredParameter';
|
||||
|
||||
const DEFAULT_S3_REGION = "us-east-1";
|
||||
|
||||
function parseS3AdapterOptions(...options) {
|
||||
if (options.length === 1 && typeof options[0] == "object") {
|
||||
return options;
|
||||
}
|
||||
|
||||
const additionalOptions = options[3] || {};
|
||||
|
||||
return {
|
||||
accessKey: options[0],
|
||||
secretKey: options[1],
|
||||
bucket: options[2],
|
||||
region: additionalOptions.region
|
||||
}
|
||||
}
|
||||
|
||||
export class S3Adapter extends FilesAdapter {
|
||||
// Creates an S3 session.
|
||||
// Providing AWS access and secret keys is mandatory
|
||||
// Region and bucket will use sane defaults if omitted
|
||||
constructor(
|
||||
accessKey,
|
||||
secretKey,
|
||||
accessKey = requiredParameter('S3Adapter requires an accessKey'),
|
||||
secretKey = requiredParameter('S3Adapter requires a secretKey'),
|
||||
bucket,
|
||||
{ region = DEFAULT_S3_REGION,
|
||||
bucketPrefix = '',
|
||||
directAccess = false } = {}
|
||||
) {
|
||||
directAccess = false } = {}) {
|
||||
super();
|
||||
|
||||
this._region = region;
|
||||
|
||||
@@ -99,9 +99,12 @@ let _verifyTransports = ({infoLogger, errorLogger, logsFolder}) => {
|
||||
}
|
||||
|
||||
export class FileLoggerAdapter extends LoggerAdapter {
|
||||
constructor(options = {}) {
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
if (options && !options.logsFolder) {
|
||||
throw "FileLoggerAdapter requires logsFolder";
|
||||
}
|
||||
options = options || {};
|
||||
this._logsFolder = options.logsFolder || LOGS_FOLDER;
|
||||
|
||||
// check logs folder exists
|
||||
|
||||
@@ -18,6 +18,10 @@ export class OneSignalPushAdapter extends PushAdapter {
|
||||
this.validPushTypes = ['ios', 'android'];
|
||||
this.senderMap = {};
|
||||
this.OneSignalConfig = {};
|
||||
const { oneSignalAppId, oneSignalApiKey } = pushConfig;
|
||||
if (!oneSignalAppId || !oneSignalApiKey) {
|
||||
throw "Trying to initialiazed OneSignalPushAdapter without oneSignalAppId or oneSignalApiKey";
|
||||
}
|
||||
this.OneSignalConfig['appId'] = pushConfig['oneSignalAppId'];
|
||||
this.OneSignalConfig['apiKey'] = pushConfig['oneSignalApiKey'];
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
export default options => {
|
||||
if (!options) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof options === 'string') {
|
||||
//Configuring via module name with no options
|
||||
return require(options)();
|
||||
}
|
||||
|
||||
if (!options.module && !options.class) {
|
||||
//Configuring via object
|
||||
return options;
|
||||
}
|
||||
|
||||
if (options.module) {
|
||||
//Configuring via module name + options
|
||||
return require(options.module)(options.options)
|
||||
}
|
||||
|
||||
if (options.class) {
|
||||
//Configuring via class + options
|
||||
return options.class(options.options);
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ export class Config {
|
||||
this.allowClientClassCreation = cacheInfo.allowClientClassCreation;
|
||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId, cacheInfo.collectionPrefix);
|
||||
|
||||
this.mailController = cacheInfo.mailController;
|
||||
this.verifyUserEmails = cacheInfo.verifyUserEmails;
|
||||
this.emailAdapter = cacheInfo.emailAdapter;
|
||||
this.appName = cacheInfo.appName;
|
||||
|
||||
this.hooksController = cacheInfo.hooksController;
|
||||
|
||||
@@ -31,7 +31,6 @@ export class AdaptableController {
|
||||
}
|
||||
|
||||
validateAdapter(adapter) {
|
||||
|
||||
if (!adapter) {
|
||||
throw new Error(this.constructor.name+" requires an adapter");
|
||||
}
|
||||
|
||||
29
src/Controllers/MailController.js
Normal file
29
src/Controllers/MailController.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import AdaptableController from './AdaptableController';
|
||||
import { MailAdapter } from '../Adapters/Email/MailAdapter';
|
||||
import { randomString } from '../cryptoUtils';
|
||||
import { inflate } from '../triggers';
|
||||
|
||||
export class MailController extends AdaptableController {
|
||||
setEmailVerificationStatus(user, status) {
|
||||
if (status == false) {
|
||||
user._email_verify_token = randomString(25);
|
||||
}
|
||||
user.emailVerified = status;
|
||||
}
|
||||
sendVerificationEmail(user, config) {
|
||||
const token = encodeURIComponent(user._email_verify_token);
|
||||
const username = encodeURIComponent(user.username);
|
||||
let link = `${config.mount}/verify_email?token=${token}&username=${username}`;
|
||||
this.adapter.sendVerificationEmail({
|
||||
appName: config.appName,
|
||||
link: link,
|
||||
user: inflate('_User', user),
|
||||
});
|
||||
}
|
||||
sendMail(options) {
|
||||
this.adapter.sendMail(options);
|
||||
}
|
||||
expectedAdapterType() {
|
||||
return MailAdapter;
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
req.params.className = '_User';
|
||||
|
||||
if (req.config.verifyUserEmails) {
|
||||
req.body._email_verify_token = cryptoUtils.randomString(25);
|
||||
req.body.emailVerified = false;
|
||||
req.config.mailController.setEmailVerificationStatus(req.body, false);
|
||||
}
|
||||
|
||||
let p = super.handleCreate(req);
|
||||
@@ -37,12 +36,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
if (req.config.verifyUserEmails) {
|
||||
// Send email as fire-and-forget once the user makes it into the DB.
|
||||
p.then(() => {
|
||||
let link = req.config.mount + "/verify_email?token=" + encodeURIComponent(req.body._email_verify_token) + "&username=" + encodeURIComponent(req.body.username);
|
||||
req.config.emailAdapter.sendVerificationEmail({
|
||||
appName: req.config.appName,
|
||||
link: link,
|
||||
user: triggers.inflate('_User', req.body),
|
||||
});
|
||||
req.config.mailController.sendVerificationEmail(req.body, req.config);
|
||||
});
|
||||
}
|
||||
return p;
|
||||
|
||||
14
src/index.js
14
src/index.js
@@ -16,11 +16,11 @@ import ParsePushAdapter from './Adapters/Push/ParsePushAdapter';
|
||||
//import passwordReset from './passwordReset';
|
||||
import PromiseRouter from './PromiseRouter';
|
||||
import verifyEmail from './verifyEmail';
|
||||
import loadAdapter from './Adapters/loadAdapter';
|
||||
import { AnalyticsRouter } from './Routers/AnalyticsRouter';
|
||||
import { ClassesRouter } from './Routers/ClassesRouter';
|
||||
import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter';
|
||||
import { FilesController } from './Controllers/FilesController';
|
||||
import { MailController } from './Controllers/MailController';
|
||||
import { FilesRouter } from './Routers/FilesRouter';
|
||||
import { FunctionsRouter } from './Routers/FunctionsRouter';
|
||||
import { GridStoreAdapter } from './Adapters/Files/GridStoreAdapter';
|
||||
@@ -30,7 +30,7 @@ import { HooksRouter } from './Routers/HooksRouter';
|
||||
|
||||
import { HooksController } from './Controllers/HooksController';
|
||||
import { InstallationsRouter } from './Routers/InstallationsRouter';
|
||||
import { AdapterLoader } from './Adapters/AdapterLoader';
|
||||
import { loadAdapter } from './Adapters/AdapterLoader';
|
||||
import { LoggerController } from './Controllers/LoggerController';
|
||||
import { PushController } from './Controllers/PushController';
|
||||
import { PushRouter } from './Routers/PushRouter';
|
||||
@@ -79,9 +79,6 @@ let validateEmailConfiguration = (verifyUserEmails, appName, emailAdapter) => {
|
||||
if (!emailAdapter) {
|
||||
throw 'User email verification was enabled, but no email adapter was provided';
|
||||
}
|
||||
if (typeof emailAdapter.sendVerificationEmail !== 'function') {
|
||||
throw 'Invalid email adapter: no sendVerificationEmail() function was provided';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,11 +161,10 @@ function ParseServer({
|
||||
appName: appName,
|
||||
});
|
||||
|
||||
if (verifyUserEmails && process.env.PARSE_EXPERIMENTAL_EMAIL_VERIFICATION_ENABLED || process.env.TESTING == 1) {
|
||||
emailAdapter = loadAdapter(emailAdapter);
|
||||
validateEmailConfiguration(verifyUserEmails, appName, emailAdapter);
|
||||
if (verifyUserEmails && (process.env.PARSE_EXPERIMENTAL_EMAIL_VERIFICATION_ENABLED || process.env.TESTING == 1)) {
|
||||
let mailController = new MailController(loadAdapter(emailAdapter));
|
||||
cache.apps[appId].mailController = mailController;
|
||||
cache.apps[appId].verifyUserEmails = verifyUserEmails;
|
||||
cache.apps[appId].emailAdapter = emailAdapter;
|
||||
}
|
||||
|
||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
||||
|
||||
Reference in New Issue
Block a user