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

View File

@ -3,109 +3,167 @@ var assert = require('assert');
var Application = loopback.Application;
describe('Application', function () {
var registeredApp = null;
var registeredApp = null;
it('Create a new application', function (done) {
Application.create({owner: 'rfeng', name: 'MyApp1', description: 'My first mobile application'}, function (err, result) {
var app = result;
assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp1');
assert.equal(app.description, 'My first mobile application');
assert(app.clientKey);
assert(app.javaScriptKey);
assert(app.restApiKey);
assert(app.windowsKey);
assert(app.masterKey);
assert(app.created);
assert(app.modified);
done(err, result);
});
it('Create a new application', function (done) {
Application.create({owner: 'rfeng',
name: 'MyApp1',
description: 'My first mobile application'}, function (err, result) {
var app = result;
assert.equal(app.owner, 'rfeng');
assert.equal(app.name, 'MyApp1');
assert.equal(app.description, 'My first mobile application');
assert(app.clientKey);
assert(app.javaScriptKey);
assert(app.restApiKey);
assert(app.windowsKey);
assert(app.masterKey);
assert(app.created);
assert(app.modified);
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('Create a new application with push settings', function (done) {
Application.create({owner: 'rfeng',
name: 'MyAppWithPush',
description: 'My push mobile application',
pushSettings: {
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'
}
}},
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) {
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);
it('Authenticate with application id & clientKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.clientKey,
function (err, result) {
assert.equal(result, 'clientKey');
done(err, result);
});
});
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);
it('Authenticate with application id & javaScriptKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.javaScriptKey,
function (err, result) {
assert.equal(result, 'javaScriptKey');
done(err, result);
});
});
assert(app.created);
assert(app.modified);
registeredApp = app;
done(err, result);
});
});
it('Authenticate with application id & restApiKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.restApiKey,
function (err, result) {
assert.equal(result, 'restApiKey');
done(err, result);
});
});
it('Authenticate with application id & clientKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.clientKey, function (err, result) {
assert.equal(result, 'clientKey');
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 & javaScriptKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.javaScriptKey, function (err, result) {
assert.equal(result, 'javaScriptKey');
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('Authenticate with application id & restApiKey', function (done) {
Application.authenticate(registeredApp.id, registeredApp.restApiKey, function (err, result) {
assert.equal(result, 'restApiKey');
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);
});
});
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);
});
});
});