var loopback = require('../loopback'); var assert = require('assert'); // Authentication schemes var AuthenticationSchemeSchema = { 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 = { /** * 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 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 }; /** * 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 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 // 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, // Push notification pushSettings: PushNotificationSettingSchema, // 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 // Timestamps created: {type: Date, default: Date}, modified: {type: Date, default: Date} }; /*! * Application management functions */ var crypto = require('crypto'); function generateKey(hmacKey, algorithm, encoding) { hmacKey = hmacKey || 'loopback'; algorithm = algorithm || 'sha1'; encoding = encoding || 'hex'; var hmac = crypto.createHmac(algorithm, hmacKey); var buf = crypto.randomBytes(32); hmac.update(buf); var key = hmac.digest(encoding); return key; } /** * Manage client applications and organize their users. * @class * @inherits {Model} */ var Application = loopback.createModel('Application', ApplicationSchema); /*! * A hook to generate keys before creation * @param next */ Application.beforeCreate = function (next) { var app = this; app.created = app.modified = new Date(); app.id = generateKey('id', 'md5'); app.clientKey = generateKey('client'); app.javaScriptKey = generateKey('javaScript'); app.restApiKey = generateKey('restApi'); app.windowsKey = generateKey('windows'); app.masterKey = generateKey('master'); next(); }; /** * Register a new application * @param owner Owner's user id * @param name Name of the application * @param options Other options * @param cb Callback function */ Application.register = function (owner, name, options, cb) { assert(owner, 'owner is required'); assert(name, 'name is required'); 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]; } } this.create(props, cb); }; /** * Reset keys for the application instance * @callback {Function} callback * @param {Error} err */ 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); }; /** * Reset keys for a given application by the appId * @param {Any} appId * @callback {Function} callback * @param {Error} err */ Application.resetKeys = function (appId, cb) { this.findById(appId, function (err, app) { if (err) { cb && cb(err, app); return; } app.resetKeys(cb); }); }; /** * Authenticate the application id and key. * * `matched` parameter is one of: * - clientKey * - javaScriptKey * - restApiKey * - windowsKey * - masterKey * * @param {Any} appId * @param {String} key * @callback {Function} callback * @param {Error} err * @param {String} matched The matching key */ Application.authenticate = function (appId, key, cb) { this.findById(appId, function (err, app) { if (err || !app) { cb && cb(err, null); return; } var result = null; var keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey']; for (var i = 0; i < keyNames.length; i++) { if (app[keyNames[i]] === key) { result = { application: app, keyType: keyNames[i] }; break; } } cb && cb(null, result); }); }; module.exports = Application;