Merge branch 'master' of https://github.com/strongloop/loopback
This commit is contained in:
commit
5172d879a1
|
@ -26,7 +26,7 @@ _TODO_
|
|||
|
||||
### App
|
||||
|
||||
Create A Loopbackapplication.
|
||||
Create a Loopback application.
|
||||
|
||||
var loopback = require('loopback');
|
||||
var app = loopback();
|
||||
|
|
|
@ -36,7 +36,7 @@ Application.create(data, function(err, data) {
|
|||
});
|
||||
|
||||
|
||||
Application.register('MyApp', 'My first mobile application', 'rfeng', function (err, result) {
|
||||
Application.register('rfeng', 'MyApp', {description: 'My first mobile application'}, function (err, result) {
|
||||
console.log(result.toObject());
|
||||
|
||||
result.resetKeys(function (err, result) {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
var DataSource = require('loopback-data').DataSource
|
||||
, ModelBuilder = require('loopback-data').ModelBuilder
|
||||
, assert = require('assert')
|
||||
, RemoteObjects = require('sl-remoting');
|
||||
, RemoteObjects = require('strong-remoting');
|
||||
|
||||
/**
|
||||
* Export the app prototype.
|
||||
|
|
|
@ -35,7 +35,7 @@ function Connector(options) {
|
|||
inherits(Connector, EventEmitter);
|
||||
|
||||
/*!
|
||||
* Create an adapter instance from a JugglingDB adapter.
|
||||
* Create an connector instance from a JugglingDB adapter.
|
||||
*/
|
||||
|
||||
Connector._createJDBAdapter = function (jdbModule) {
|
||||
|
@ -49,6 +49,6 @@ Connector._createJDBAdapter = function (jdbModule) {
|
|||
* Add default crud operations from a JugglingDB adapter.
|
||||
*/
|
||||
|
||||
Connector.prototype._addCrudOperationsFromJDBAdapter = function (adapter) {
|
||||
Connector.prototype._addCrudOperationsFromJDBAdapter = function (connector) {
|
||||
|
||||
}
|
|
@ -23,7 +23,7 @@ var Connector = require('./base-connector')
|
|||
*/
|
||||
|
||||
function Memory() {
|
||||
// TODO implement entire memory adapter
|
||||
// TODO implement entire memory connector
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
var loopback = require('../loopback');
|
||||
var RemoteObjects = require('sl-remoting');
|
||||
var RemoteObjects = require('strong-remoting');
|
||||
|
||||
/**
|
||||
* Export the middleware.
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
# Application
|
||||
|
||||
Application model captures the metadata for a loopback application.
|
||||
|
||||
## Each application has the following basic properties:
|
||||
|
||||
* id: Automatically generated id
|
||||
* name: Name of the application (required)
|
||||
* description: Description of the application (optional)
|
||||
* icon: URL of the icon
|
||||
* status: Status of the application, such as production/sandbox/disabled
|
||||
* created: Timestamp of the record being created
|
||||
* modified: Timestamp of the record being modified
|
||||
|
||||
## An application has the following properties linking to users:
|
||||
|
||||
* owner: The user id of the developer who registers the application
|
||||
* collaborators: A array of users ids who have permissions to work on this app
|
||||
|
||||
## oAuth 2.0 settings
|
||||
|
||||
* url: The application url
|
||||
* callbackUrls: An array of preregistered callback urls for oAuth 2.0
|
||||
* permissions: An array of oAuth 2.0 scopes that can be requested by the application
|
||||
|
||||
## Security keys
|
||||
|
||||
The following keys are automatically generated by the application creation process. They can be reset upon request.
|
||||
|
||||
* clientKey: Secret for mobile clients
|
||||
* javaScriptKey: Secret for JavaScript clients
|
||||
* restApiKey: Secret for REST APIs
|
||||
* windowsKey: Secret for Windows applications
|
||||
* masterKey: Secret for REST APIS. It bypasses model level permissions
|
||||
|
||||
## Push notification settings
|
||||
|
||||
The application can be configured to support multiple methods of push notifications.
|
||||
|
||||
* pushSettings
|
||||
|
||||
|
||||
{
|
||||
pushSettings: [
|
||||
{ "platform": "apns",
|
||||
"apns": {
|
||||
"pushOptions": {
|
||||
"gateway": "gateway.sandbox.push.apple.com",
|
||||
"cert": "credentials/apns_cert_dev.pem",
|
||||
"key": "credentials/apns_key_dev.pem"
|
||||
},
|
||||
|
||||
"feedbackOptions": {
|
||||
"gateway": "feedback.sandbox.push.apple.com",
|
||||
"cert": "credentials/apns_cert_dev.pem",
|
||||
"key": "credentials/apns_key_dev.pem",
|
||||
"batchFeedback": true,
|
||||
"interval": 300
|
||||
}
|
||||
}}
|
||||
]}
|
||||
|
||||
|
||||
## Authentication schemes
|
||||
|
||||
|
||||
* authenticationEnabled
|
||||
* anonymousAllowed
|
||||
* authenticationSchemes
|
||||
|
||||
### Authentication scheme settings
|
||||
|
||||
* scheme: Name of the authentication scheme, such as local, facebook, google, twitter, linkedin, github
|
||||
* credential: Scheme-specific credentials
|
||||
|
||||
## APIs for Application model
|
||||
|
||||
In addition to the CRUD methods, the Application model also has the following apis:
|
||||
|
||||
### Register a new application
|
||||
|
||||
You can register a new application by providing the owner user id, applicaiton name, and other properties in the options object.
|
||||
|
||||
Application.register('rfeng', 'MyApp1', {description: 'My first loopback application'}, function (err, result) {
|
||||
var app = result;
|
||||
...
|
||||
});
|
||||
|
||||
### Reset keys
|
||||
|
||||
You can reset keys for a given application by id.
|
||||
|
||||
Application.resetKeys(appId, function (err, result) {
|
||||
var app = result;
|
||||
...
|
||||
});
|
||||
|
||||
### Authenticate by appId and key
|
||||
|
||||
You can authenticate an application by id and one of the keys. If successful, it calls back with the key name in the result argument. Otherwise, the keyName is null.
|
||||
|
||||
Application.authenticate(appId, clientKey, function (err, keyName) {
|
||||
assert.equal(keyName, 'clientKey');
|
||||
...
|
||||
});
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
Installation captures the installation of the application on devices.
|
||||
|
||||
Each record of installation has the following properties:
|
||||
|
||||
* id: Generated id that uniquely identifies the installation
|
||||
* appId: Application id
|
||||
* appVersion: Application version
|
||||
* userId: The current user id that logs into the application
|
||||
* deviceToken: Device token
|
||||
* deviceType: Device type, such as apns
|
||||
* subscriptions: An Array of tags that represents subscriptions of notifications
|
||||
* status: Status of the application, production/sandbox/disabled
|
||||
* created: Timestamp of the recored being created
|
||||
* modified: Timestamp of the recored being modified
|
||||
|
||||
|
|
@ -20,19 +20,32 @@ Factors to be authorized against:
|
|||
Class level permissions, for example, Album
|
||||
* model name: Album
|
||||
* methods
|
||||
// blog posts
|
||||
allow: ['owner', 'admin'] to: '*' // allow owner's of posts and admins to do anything
|
||||
allow: '*' to: ['find', 'read'] // allow everyone to read and find
|
||||
// comments
|
||||
allow '*' to: ['find', 'read'] // read aka findById
|
||||
allow 'user' to: ['create']
|
||||
allow ['owner', 'admin'] to: '*'
|
||||
|
||||
// users only section
|
||||
allow: '*' to: ['find', 'read', 'create']
|
||||
allow: 'owner' to: ['*.destroy', '*.save']
|
||||
URL/Route level permissions
|
||||
* url pattern
|
||||
* application id
|
||||
* ip addresses
|
||||
* http headers
|
||||
|
||||
// scopes
|
||||
Map to oAuth 2.0 scopes
|
||||
|
||||
// URL level permissions
|
||||
*/
|
||||
|
||||
var ACLSchema = {
|
||||
model: String, // The model name
|
||||
properties: [String], // A list of property names
|
||||
methods: [String], // A list of methods
|
||||
roles: [String], // A list of roles
|
||||
permission: {type: String, enum: ['Allow', 'Deny']}, // Allow/Deny
|
||||
status: String, // Enabled/disabled
|
||||
created: Date,
|
||||
modified: Date
|
||||
}
|
||||
|
||||
// readAccess, writeAccess --> public, userId, role
|
||||
|
||||
module.exports = function(dataSource) {
|
||||
dataSource = dataSource || new require('loopback-data').ModelBuilder();
|
||||
var ACL = dataSource.define('ACL', ACLSchema);
|
||||
return ACL;
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
var assert = require('assert');
|
||||
|
||||
// Authentication schemes
|
||||
var AuthenticationSchemeSchema = {
|
||||
scheme: String, // local, facebook, google, twitter, linkedin, github
|
||||
|
@ -33,7 +35,7 @@ var PushNotificationSettingSchema = {
|
|||
var ApplicationSchema = {
|
||||
|
||||
// Basic information
|
||||
id: {type: String, required: true}, // The id
|
||||
id: {type: String, required: true, generated: true, id: true}, // The id
|
||||
name: {type: String, required: true}, // The name
|
||||
description: String, // The description
|
||||
icon: String, // The icon image url
|
||||
|
@ -79,9 +81,10 @@ var ApplicationSchema = {
|
|||
|
||||
var crypto = require('crypto');
|
||||
|
||||
function generateKey(hmacKey, algorithm) {
|
||||
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);
|
||||
|
@ -102,7 +105,8 @@ module.exports = function (dataSource) {
|
|||
// Application.hasMany(AuthenticationScheme, {as: 'authenticationSchemes', foreignKey: 'appId'});
|
||||
// Application.hasMany(PushNotificationSetting, {as: 'pushNotificationSettings', foreignKey: 'appId'});
|
||||
|
||||
Application.afterInitialize = function () {
|
||||
Application.beforeCreate = function (next) {
|
||||
// console.trace();
|
||||
var app = this;
|
||||
// use data argument to update object
|
||||
app.created = app.modified = new Date();
|
||||
|
@ -112,22 +116,84 @@ module.exports = function (dataSource) {
|
|||
app.restApiKey = generateKey('restApi');
|
||||
app.windowsKey = generateKey('windows');
|
||||
app.masterKey = generateKey('master');
|
||||
next();
|
||||
};
|
||||
|
||||
// Register a new application
|
||||
Application.register = function (name, description, owner, cb) {
|
||||
Application.create({name: name, description: description, owner: owner}, cb);
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset keys for a given application by the appId
|
||||
* @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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param appId
|
||||
* @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);
|
||||
});
|
||||
}
|
||||
|
||||
return Application;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
exports.Model = require('./model');
|
||||
exports.Email = require('./email');
|
||||
exports.User = require('./user');
|
||||
exports.Session = require('./session');
|
||||
|
||||
exports.Application = require('./application');
|
||||
exports.ACL = require('./acl');
|
||||
exports.Role = require('./role');
|
||||
exports.Installation = require('./installation');
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ var InstallationSchema = {
|
|||
appId: String, // Application id
|
||||
appVersion: String, // Application version
|
||||
userId: String, // User id
|
||||
deviceToken: String,
|
||||
deviceType: String,
|
||||
deviceToken: String, // Device token
|
||||
deviceType: String, // Device type, such as apns
|
||||
subscriptions: [String],
|
||||
|
||||
status: {type: String, default: 'active'}, // Status of the application, production/sandbox/disabled
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"debug": "latest",
|
||||
"express": "~3.1.1",
|
||||
"loopback-data": "git+ssh://git@github.com:strongloop/loopback-data.git",
|
||||
"sl-remoting": "git+ssh://git@github.com:strongloop/sl-remoting.git",
|
||||
"strong-remoting": "git+ssh://git@github.com:strongloop/strong-remoting.git",
|
||||
"inflection": "~1.2.5",
|
||||
"bcrypt": "~0.7.6",
|
||||
"passport": "~0.1.17",
|
||||
|
|
|
@ -4,7 +4,7 @@ describe('loopback', function() {
|
|||
var dataSource = loopback.createDataSource({
|
||||
connector: loopback.Memory
|
||||
});
|
||||
assert(dataSource.connector());
|
||||
assert(dataSource.connector);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -31,4 +31,4 @@ describe('loopback', function() {
|
|||
assert.equal(Product.stats.shared, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
var models = require('../lib/models');
|
||||
var loopback = require(('../'));
|
||||
var assert = require('assert');
|
||||
|
||||
var dataSource = loopback.createDataSource('db', {connector: loopback.Memory});
|
||||
|
||||
var Application = models.Application(dataSource);
|
||||
|
||||
describe('Application', function () {
|
||||
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('Register a new application', 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('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 & javaScriptKey', function (done) {
|
||||
|
||||
Application.authenticate(registeredApp.id, registeredApp.javaScriptKey, function (err, result) {
|
||||
assert.equal(result, 'javaScriptKey');
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -192,7 +192,21 @@ describe('Model', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Model.destroyAll(callback)', function() {
|
||||
describe('Model.deleteById([callback])', function () {
|
||||
it("Delete a model instance from the attached data source", function (done) {
|
||||
User.create({first: 'joe', last: 'bob'}, function (err, user) {
|
||||
User.deleteById(user.id, function (err) {
|
||||
User.findById(user.id, function (err, notFound) {
|
||||
assert(!err);
|
||||
assert.equal(notFound, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model.destroyAll(callback)', function() {
|
||||
it("Delete all Model instances from data source", function(done) {
|
||||
(new TaskEmitter())
|
||||
.task(User, 'create', {first: 'jill'})
|
||||
|
|
Loading…
Reference in New Issue