Merge pull request #105 from strongloop/feature/push-settings

Allow cert/key data to be shared by push/feedback
This commit is contained in:
Raymond Feng 2013-12-18 12:34:00 -08:00
commit 3652c1584a
2 changed files with 268 additions and 198 deletions

View File

@ -3,81 +3,93 @@ var assert = require('assert');
// Authentication schemes // Authentication schemes
var AuthenticationSchemeSchema = { var AuthenticationSchemeSchema = {
scheme: String, // local, facebook, google, twitter, linkedin, github scheme: String, // local, facebook, google, twitter, linkedin, github
credential: Object // Scheme-specific credentials credential: Object // Scheme-specific credentials
}; };
// See https://github.com/argon/node-apn/blob/master/doc/apn.markdown
var APNSSettingSchema = { var APNSSettingSchema = {
pushOptions: {type: { /**
gateway: String, * production or development mode. It denotes what default APNS servers to be
cert: String, * used to send notifications
key: String * - true (production mode)
}}, * - push: gateway.push.apple.com:2195
* - feedback: feedback.push.apple.com:2196
* - false (development mode, the default)
* - push: gateway.sandbox.push.apple.com:2195
* - feedback: feedback.sandbox.push.apple.com:2196
*/
production: Boolean,
certData: String, // The certificate data loaded from the cert.pem file
keyData: String, // The key data loaded from the key.pem file
feedbackOptions: {type: { pushOptions: {type: {
gateway: String, gateway: String,
cert: String, port: Number
key: String, }},
batchFeedback: Boolean,
interval: Number feedbackOptions: {type: {
}} gateway: String,
port: Number,
batchFeedback: Boolean,
interval: Number
}}
}; };
var GcmSettingsSchema = { var GcmSettingsSchema = {
serverApiKey: String serverApiKey: String
} };
// Push notification settings // Push notification settings
var PushNotificationSettingSchema = { var PushNotificationSettingSchema = {
apns: APNSSettingSchema, apns: APNSSettingSchema,
gcm: GcmSettingsSchema gcm: GcmSettingsSchema
}; };
/** /**
* Data model for Application * Data model for Application
*/ */
var ApplicationSchema = { var ApplicationSchema = {
id: {type: String, id: true, generated: true}, id: {type: String, id: true, generated: true},
// Basic information // Basic information
name: {type: String, required: true}, // The name name: {type: String, required: true}, // The name
description: String, // The description description: String, // The description
icon: String, // The icon image url icon: String, // The icon image url
owner: String, // The user id of the developer who registers the application owner: String, // The user id of the developer who registers the application
collaborators: [String], // A list of users ids who have permissions to work on this app collaborators: [String], // A list of users ids who have permissions to work on this app
// EMail // EMail
email: String, // e-mail address email: String, // e-mail address
emailVerified: Boolean, // Is the e-mail verified emailVerified: Boolean, // Is the e-mail verified
// oAuth 2.0 settings // oAuth 2.0 settings
url: String, // The application url url: String, // The application url
callbackUrls: [String], // oAuth 2.0 code/token callback url callbackUrls: [String], // oAuth 2.0 code/token callback url
permissions: [String], // A list of permissions required by the application permissions: [String], // A list of permissions required by the application
// Keys // Keys
clientKey: String, clientKey: String,
javaScriptKey: String, javaScriptKey: String,
restApiKey: String, restApiKey: String,
windowsKey: String, windowsKey: String,
masterKey: String, masterKey: String,
// Push notification // Push notification
pushSettings: PushNotificationSettingSchema, pushSettings: PushNotificationSettingSchema,
// User Authentication // User Authentication
authenticationEnabled: {type: Boolean, default: true}, authenticationEnabled: {type: Boolean, default: true},
anonymousAllowed: {type: Boolean, default: true}, anonymousAllowed: {type: Boolean, default: true},
authenticationSchemes: [AuthenticationSchemeSchema], authenticationSchemes: [AuthenticationSchemeSchema],
status: {type: String, default: 'sandbox'}, // Status of the application, production/sandbox/disabled status: {type: String, default: 'sandbox'}, // Status of the application, production/sandbox/disabled
// Timestamps // Timestamps
created: {type: Date, default: Date}, created: {type: Date, default: Date},
modified: {type: Date, default: Date} modified: {type: Date, default: Date}
}; };
/** /**
* Application management functions * Application management functions
*/ */
@ -85,13 +97,13 @@ var ApplicationSchema = {
var crypto = require('crypto'); var crypto = require('crypto');
function generateKey(hmacKey, algorithm, encoding) { function generateKey(hmacKey, algorithm, encoding) {
hmacKey = hmacKey || 'loopback'; hmacKey = hmacKey || 'loopback';
algorithm = algorithm || 'sha256'; algorithm = algorithm || 'sha256';
encoding = encoding || 'base64'; encoding = encoding || 'base64';
var hmac = crypto.createHmac(algorithm, hmacKey); var hmac = crypto.createHmac(algorithm, hmacKey);
var buf = crypto.randomBytes(64); var buf = crypto.randomBytes(64);
hmac.update(buf); hmac.update(buf);
return hmac.digest('base64'); return hmac.digest('base64');
} }
var Application = loopback.createModel('Application', ApplicationSchema); var Application = loopback.createModel('Application', ApplicationSchema);
@ -101,15 +113,15 @@ var Application = loopback.createModel('Application', ApplicationSchema);
* @param next * @param next
*/ */
Application.beforeCreate = function (next) { Application.beforeCreate = function (next) {
var app = this; var app = this;
app.created = app.modified = new Date(); app.created = app.modified = new Date();
app.id = generateKey('id', 'sha1'); app.id = generateKey('id', 'sha1');
app.clientKey = generateKey('client'); app.clientKey = generateKey('client');
app.javaScriptKey = generateKey('javaScript'); app.javaScriptKey = generateKey('javaScript');
app.restApiKey = generateKey('restApi'); app.restApiKey = generateKey('restApi');
app.windowsKey = generateKey('windows'); app.windowsKey = generateKey('windows');
app.masterKey = generateKey('master'); app.masterKey = generateKey('master');
next(); next();
}; };
/** /**
@ -120,34 +132,34 @@ Application.beforeCreate = function (next) {
* @param cb Callback function * @param cb Callback function
*/ */
Application.register = function (owner, name, options, cb) { Application.register = function (owner, name, options, cb) {
assert(owner, 'owner is required'); assert(owner, 'owner is required');
assert(name, 'name is required'); assert(name, 'name is required');
if(typeof options === 'function' && !cb) { if (typeof options === 'function' && !cb) {
cb = options; cb = options;
options = {}; options = {};
}
var props = {owner: owner, name: name};
for (var p in options) {
if (!(p in props)) {
props[p] = options[p];
} }
var props = {owner: owner, name: name}; }
for(var p in options) { Application.create(props, cb);
if(!(p in props)) {
props[p] = options[p];
}
}
Application.create(props, cb);
}; };
/** /**
* Reset keys for the application instance * Reset keys for the application instance
* @param cb * @param cb
*/ */
Application.prototype.resetKeys = function(cb) { Application.prototype.resetKeys = function (cb) {
this.clientKey = generateKey('client'); this.clientKey = generateKey('client');
this.javaScriptKey = generateKey('javaScript'); this.javaScriptKey = generateKey('javaScript');
this.restApiKey = generateKey('restApi'); this.restApiKey = generateKey('restApi');
this.windowsKey = generateKey('windows'); this.windowsKey = generateKey('windows');
this.masterKey = generateKey('master'); this.masterKey = generateKey('master');
this.modified = new Date(); this.modified = new Date();
this.save(cb); this.save(cb);
}; };
/** /**
@ -155,14 +167,14 @@ Application.prototype.resetKeys = function(cb) {
* @param appId * @param appId
* @param cb * @param cb
*/ */
Application.resetKeys = function(appId, cb) { Application.resetKeys = function (appId, cb) {
Application.findById(appId, function(err, app) { Application.findById(appId, function (err, app) {
if(err) { if (err) {
cb && cb(err, app); cb && cb(err, app);
return; return;
} }
app.resetKeys(cb); app.resetKeys(cb);
}); });
}; };
/** /**
@ -171,20 +183,20 @@ Application.resetKeys = function(appId, cb) {
* @param key * @param key
* @param cb * @param cb
*/ */
Application.authenticate = function(appId, key, cb) { Application.authenticate = function (appId, key, cb) {
Application.findById(appId, function(err, app) { Application.findById(appId, function (err, app) {
if(err || !app) { if (err || !app) {
cb && cb(err, null); cb && cb(err, null);
return; return;
} }
var matched = null; var matched = null;
['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey'].forEach(function(k) { ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey'].forEach(function (k) {
if(app[k] === key) { if (app[k] === key) {
matched = k; matched = k;
} }
});
cb && cb(null, matched);
}); });
cb && cb(null, matched);
});
}; };
module.exports = Application; module.exports = Application;

View File

@ -3,109 +3,167 @@ var assert = require('assert');
var Application = loopback.Application; var Application = loopback.Application;
describe('Application', function () { describe('Application', function () {
var registeredApp = null; var registeredApp = null;
it('Create a new application', function (done) { it('Create a new application', function (done) {
Application.create({owner: 'rfeng', name: 'MyApp1', description: 'My first mobile application'}, function (err, result) { Application.create({owner: 'rfeng',
var app = result; name: 'MyApp1',
assert.equal(app.owner, 'rfeng'); description: 'My first mobile application'}, function (err, result) {
assert.equal(app.name, 'MyApp1'); var app = result;
assert.equal(app.description, 'My first mobile application'); assert.equal(app.owner, 'rfeng');
assert(app.clientKey); assert.equal(app.name, 'MyApp1');
assert(app.javaScriptKey); assert.equal(app.description, 'My first mobile application');
assert(app.restApiKey); assert(app.clientKey);
assert(app.windowsKey); assert(app.javaScriptKey);
assert(app.masterKey); assert(app.restApiKey);
assert(app.created); assert(app.windowsKey);
assert(app.modified); assert(app.masterKey);
done(err, result); assert(app.created);
}); assert(app.modified);
done(err, result);
}); });
});
beforeEach(function (done) { it('Create a new application with push settings', function (done) {
Application.register('rfeng', 'MyApp2', {description: 'My second mobile application'}, function (err, result) { Application.create({owner: 'rfeng',
var app = result; name: 'MyAppWithPush',
assert.equal(app.owner, 'rfeng'); description: 'My push mobile application',
assert.equal(app.name, 'MyApp2'); pushSettings: {
assert.equal(app.description, 'My second mobile application'); apns: {
assert(app.clientKey); production: false,
assert(app.javaScriptKey); certData: 'cert',
assert(app.restApiKey); keyData: 'key',
assert(app.windowsKey); pushOptions: {
assert(app.masterKey); gateway: 'gateway.sandbox.push.apple.com',
assert(app.created); port: 2195
assert(app.modified); },
registeredApp = app; feedbackOptions: {
done(err, result); gateway: 'feedback.sandbox.push.apple.com',
port: 2196,
interval: 300,
batchFeedback: true
}
},
gcm: {
serverApiKey: 'serverKey'
}
}},
function (err, result) {
var app = result;
assert.deepEqual(app.pushSettings.toObject(), {
apns: {
production: false,
certData: 'cert',
keyData: 'key',
pushOptions: {
gateway: 'gateway.sandbox.push.apple.com',
port: 2195
},
feedbackOptions: {
gateway: 'feedback.sandbox.push.apple.com',
port: 2196,
interval: 300,
batchFeedback: true
}
},
gcm: {
serverApiKey: 'serverKey'
}
}); });
done(err, result);
});
});
beforeEach(function (done) {
Application.register('rfeng', 'MyApp2',
{description: 'My second mobile application'}, function (err, result) {
var app = result;
assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp2');
assert.equal(app.description, 'My second mobile application');
assert(app.clientKey);
assert(app.javaScriptKey);
assert(app.restApiKey);
assert(app.windowsKey);
assert(app.masterKey);
assert(app.created);
assert(app.modified);
registeredApp = app;
done(err, result);
});
});
it('Reset keys', function (done) {
Application.resetKeys(registeredApp.id, function (err, result) {
var app = result;
assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp2');
assert.equal(app.description, 'My second mobile application');
assert(app.clientKey);
assert(app.javaScriptKey);
assert(app.restApiKey);
assert(app.windowsKey);
assert(app.masterKey);
assert(app.clientKey !== registeredApp.clientKey);
assert(app.javaScriptKey !== registeredApp.javaScriptKey);
assert(app.restApiKey !== registeredApp.restApiKey);
assert(app.windowsKey !== registeredApp.windowsKey);
assert(app.masterKey !== registeredApp.masterKey);
assert(app.created);
assert(app.modified);
registeredApp = app;
done(err, result);
}); });
});
it('Reset keys', function (done) { it('Authenticate with application id & clientKey', function (done) {
Application.resetKeys(registeredApp.id, function (err, result) { Application.authenticate(registeredApp.id, registeredApp.clientKey,
var app = result; function (err, result) {
assert.equal(app.owner, 'rfeng'); assert.equal(result, 'clientKey');
assert.equal(app.name, 'MyApp2'); done(err, result);
assert.equal(app.description, 'My second mobile application'); });
assert(app.clientKey); });
assert(app.javaScriptKey);
assert(app.restApiKey);
assert(app.windowsKey);
assert(app.masterKey);
assert(app.clientKey !== registeredApp.clientKey); it('Authenticate with application id & javaScriptKey', function (done) {
assert(app.javaScriptKey !== registeredApp.javaScriptKey); Application.authenticate(registeredApp.id, registeredApp.javaScriptKey,
assert(app.restApiKey !== registeredApp.restApiKey); function (err, result) {
assert(app.windowsKey !== registeredApp.windowsKey); assert.equal(result, 'javaScriptKey');
assert(app.masterKey !== registeredApp.masterKey); done(err, result);
});
});
assert(app.created); it('Authenticate with application id & restApiKey', function (done) {
assert(app.modified); Application.authenticate(registeredApp.id, registeredApp.restApiKey,
registeredApp = app; function (err, result) {
done(err, result); assert.equal(result, 'restApiKey');
}); done(err, result);
}); });
});
it('Authenticate with application id & clientKey', function (done) { it('Authenticate with application id & masterKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.clientKey, function (err, result) { Application.authenticate(registeredApp.id, registeredApp.masterKey,
assert.equal(result, 'clientKey'); function (err, result) {
done(err, result); assert.equal(result, 'masterKey');
}); done(err, result);
}); });
});
it('Authenticate with application id & javaScriptKey', function (done) { it('Authenticate with application id & windowsKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.javaScriptKey, function (err, result) { Application.authenticate(registeredApp.id, registeredApp.windowsKey,
assert.equal(result, 'javaScriptKey'); function (err, result) {
done(err, result); assert.equal(result, 'windowsKey');
}); done(err, result);
}); });
});
it('Authenticate with application id & restApiKey', function (done) { it('Fail to authenticate with application id & invalid key', function (done) {
Application.authenticate(registeredApp.id, registeredApp.restApiKey, function (err, result) { Application.authenticate(registeredApp.id, 'invalid-key',
assert.equal(result, 'restApiKey'); function (err, result) {
done(err, result); assert(!result);
}); done(err, result);
}); });
});
it('Authenticate with application id & masterKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.masterKey, function (err, result) {
assert.equal(result, 'masterKey');
done(err, result);
});
});
it('Authenticate with application id & windowsKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.windowsKey, function (err, result) {
assert.equal(result, 'windowsKey');
done(err, result);
});
});
it('Fail to authenticate with application id & invalid key', function (done) {
Application.authenticate(registeredApp.id, 'invalid-key', function (err, result) {
assert(!result);
done(err, result);
});
});
}); });