Skip to content

Commit

Permalink
Merge pull request #52 from invisiblejs/more_id_fixes
Browse files Browse the repository at this point in the history
More id fixes
  • Loading branch information
facundoolano committed May 1, 2015
2 parents 5f63dfb + 8e80e52 commit dac7504
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 52 deletions.
50 changes: 25 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# [Invisible.js](http://invisiblejs.github.io) [![Build Status](https://secure.travis-ci.org/invisiblejs/invisible.png)](http://travis-ci.org/invisiblejs/invisible) [![Dependencies](https://david-dm.org/invisiblejs/invisible.png)](https://david-dm.org/invisiblejs/invisible) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/invisiblejs/invisible?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

Invisible is a JavaScript library that leverages
[browserify](https://github.com/substack/node-browserify) to achieve the Holy Grail of web programming:
Invisible is a JavaScript library that leverages
[browserify](https://github.com/substack/node-browserify) to achieve the Holy Grail of web programming:
model reuse in the client and the server.

## Installation and setup
Expand Down Expand Up @@ -73,13 +73,13 @@ In the client, just add the invisible script:
</script>
```

Invisible.js uses [browserify](https://github.com/substack/node-browserify) to expose you server defined
Invisible.js uses [browserify](https://github.com/substack/node-browserify) to expose you server defined
models in the browser, so you can use any broserify-able library to implement them. Note that this
integration is seamless, no need to build a bundle, Invisible.js does that for you on the fly.

## REST and MongoDB integration

In addition to making your models available everywhere, Invisible extends them with methods to handle
In addition to making your models available everywhere, Invisible extends them with methods to handle
persistence. In the server this means interacting with MongoDB and in the client making requests to an
auto-generated REST API, that subsequently performs the same DB action.

Expand All @@ -94,7 +94,7 @@ jane.save(function(err, result){
}
})
```
The first time the save method is called, it creates the model in the database and sets its `_id` attribute.
The first time the save method is called, it creates the model in the database and sets its `_id` attribute.
Subsequent calls update the model. Validations are called upon saving, see the validations section for details.

Note that a full Invisible model instance is passed to the callback, and the calling instance is also updated
Expand Down Expand Up @@ -147,11 +147,11 @@ Invisible.Person.query({}, {sort: "lastName", limit: 10}, function(err, results)
});
```

Queries the database for existent models. The first two optional arguments correspond to the
[Query object](http://mongodb.github.io/node-mongodb-native/markdown-docs/queries.html#query-object)
and [Query options](http://mongodb.github.io/node-mongodb-native/markdown-docs/queries.html#query-options)
in the MongoDB Node.JS driver. The one difference is that when using ids you can pass strings,
that will be converted to ObjectID when necessary.
Queries the database for existent models. The first two optional arguments correspond to the
[Query object](http://mongodb.github.io/node-mongodb-native/markdown-docs/queries.html#query-object)
and [Query options](http://mongodb.github.io/node-mongodb-native/markdown-docs/queries.html#query-options)
in the MongoDB Node.JS driver. The one difference is that when using ids you can pass strings,
that will be converted to ObjectID when necessary.

### Find by id
```javascript
Expand All @@ -170,8 +170,8 @@ a string id instead of an ObjectID instance.

## Validations

Invisible.js integrates with [revalidator](https://github.com/flatiron/revalidator) to handle model validations.
A `validate` method is added to each model which looks for a defined validation schema, and is executed each time
Invisible.js integrates with [revalidator](https://github.com/flatiron/revalidator) to handle model validations.
A `validate` method is added to each model which looks for a defined validation schema, and is executed each time
a model is saved, both in the client and the server. For example:

```javascript
Expand All @@ -195,8 +195,8 @@ john.save(function(err, result){

john.email = "invalid";
john.save(function(err, result){
console.log(err);
/* Prints: {valid: false, errors: [{attribute: 'format',
console.log(err);
/* Prints: {valid: false, errors: [{attribute: 'format',
property: 'email', message: 'is not a valid email'}]} */
})
```
Expand All @@ -221,12 +221,12 @@ Person.prototype.checkUnique = function(cb) {
```

The custom validation method takes a callback and should call it with the return format of revalidator: an object
with a "valid" boolean field and an "errors" list. Note that the method validations are only called if the
with a "valid" boolean field and an "errors" list. Note that the method validations are only called if the
properties validations succeed, and stop the validation process upon the first failure.

## Real time events

Invisible.js uses [socket.io](http://socket.io/) to emmit an event whenever something changes for a model, and lets you add listener
Invisible.js uses [socket.io](http://socket.io/) to emmit an event whenever something changes for a model, and lets you add listener
functions to react to those changes in realtime.

To add realtime features:
Expand Down Expand Up @@ -256,8 +256,8 @@ Invisible.js provides a default method to authenticate the requests to the REST
Owner Password flow](http://techblog.hybris.com/2012/06/11/oauth2-resource-owner-password-flow/). This means than when activating authentication, a route is exposed that exchanges user
credentials for a request token used to sign the rest of the API calls. Unsigned calls will get a 401 response.

To use authentication, you must first define a user model in whatever way you like; the only constraint is that
you must be able to identify a user with a pair of credentials such as username/password. By default the `User`
To use authentication, you must first define a user model in whatever way you like; the only constraint is that
you must be able to identify a user with a pair of credentials such as username/password. By default the `User`
name is assumed for the model, but this can be overriden via the `userModel` configuration.

```javascript
Expand All @@ -277,7 +277,7 @@ User.prototype.isPassword = function(rawPassword){
module.exports = Invisible.createModel("User", User);
```

Once the User model is defined, authentication is activated by defining an `authenticate` function in the
Once the User model is defined, authentication is activated by defining an `authenticate` function in the
configuration, that takes the credentials and returns the authenticated user:

```javascript
Expand Down Expand Up @@ -343,17 +343,17 @@ function Message(from_id, to_id, text){

Message.prototype.allowCreate = function(user, cb) {
//a user can only create messages sent by him
return cb(null, this.from_id === user._id.toString());
return cb(null, this.from_id === user._id);
}

Message.prototype.allowUpdate = function(user, cb) {
//a user can only update messages sent by him
return cb(null, this.from_id === user._id.toString());
return cb(null, this.from_id === user._id;
}

Message.prototype.allowFind = function(user, cb) {
//a user can only get messages sent by him or to him
return cb(null, this.from_id === user._id.toString() || this.to_id === user._id.toString());
return cb(null, this.from_id === user._id || this.to_id === user._id;
}

Message.prototype.allowDelete = function(user, cb) {
Expand All @@ -367,7 +367,7 @@ Similarly, `allowEvents` can be used to decide if a user is authorized to receiv
```javascript
Message.prototype.allowEvents = function(user, cb) {
//only sent events when a message is sent to the user
return cb(null, this.to_id === user._id.toString());
return cb(null, this.to_id === user._id;
}
```
Expand All @@ -378,10 +378,10 @@ is and-ed with the criteria used in `query` to filter out unauthorized data. Fol
```javascript
Message.baseQuery = function(user, cb){
//only expose message sent to or by the user
return cb(null, {$or: [{from: user._id.toString()}, {to: user._id.toString()}]})
return cb(null, {$or: [{from: user._id}, {to: user._id}]})
}
```
Now when calling `Invisible.Message.query` in the client, only messages sent by and to the logged user will be
Now when calling `Invisible.Message.query` in the client, only messages sent by and to the logged user will be
received.
8 changes: 4 additions & 4 deletions examples/auth/models/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ function Message(fromId, toId, text) {

Message.prototype.allowCreate = function(user, cb) {
//a user can only create messages sent by him
return cb(null, this.from_id === user._id.toString());
return cb(null, this.from_id === user._id);
};

Message.prototype.allowUpdate = function(user, cb) {
//a user can only update messages sent by him
return cb(null, this.from_id === user._id.toString());
return cb(null, this.from_id === user._id);
};

Message.prototype.allowFind = function(user, cb) {
//a user can only get messages sent by him or to him
return cb(null, this.from_id === user._id.toString() || this.to_id === user._id.toString());
return cb(null, this.from_id === user._id || this.to_id === user._id);
};

Message.prototype.allowDelete = function(user, cb) {
Expand All @@ -30,7 +30,7 @@ Message.prototype.allowDelete = function(user, cb) {

Message.prototype.allowEvents = function(user, cb) {
//only sent events when a message is sent to the user
return cb(null, this.to_id === user._id.toString());
return cb(null, this.to_id === user._id);
};

module.exports = Invisible.createModel('Message', Message);
4 changes: 2 additions & 2 deletions lib/auth/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ exports.findUser = function(tokenData, cb) {
}

var User = Invisible.models[config.userModel || 'User'];
User.findById(tokenModel.user.toString(), function(err, user) {
User.findById(tokenModel.user, function(err, user) {
if (err) {
return cb(err);
}
Expand Down Expand Up @@ -122,7 +122,7 @@ exports.route = function(req, res) {
return col.remove({
refresh: token.refresh
}, function() {
return Invisible.models[config.userModel || 'User'].findById(token.user.toString(), sendToken);
return Invisible.models[config.userModel || 'User'].findById(token.user, sendToken);
});
});
} else {
Expand Down
1 change: 1 addition & 0 deletions lib/model/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module.exports = function(InvisibleModel) {
}

var model = _.extend(new InvisibleModel(), result);
model._id = model._id.toString();
return cb(null, model);
});
};
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "invisible",
"version": "0.4.0",
"version": "0.4.1",
"description": "DRY models for client and server",
"main": "index.js",
"scripts": {
Expand All @@ -21,7 +21,7 @@
},
"dependencies": {
"body-parser": "^1.2.0",
"browserify": "9.x",
"browserify": "10.x",
"coffee-script": "^1.9.2",
"coffeeify": "^1.1.0",
"express": "^4.12.3",
Expand Down
40 changes: 21 additions & 19 deletions test/server/auth.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,13 @@ describe 'Authentication routes', () ->

it 'Should allow authorized create', (done)->
Invisible.models.Message.prototype.allowCreate = (user, cb)->
return cb(null, this.from.toString() == user._id.toString())
console.log(user._id, typeof(user._id))
console.log(this.from, typeof(this.from))
return cb(null, this.from == user._id)

request(app)
.post("/invisible/Message/")
.send({text:"howdy", from:facundo._id.toString()})
.send({text:"howdy", from:facundo._id})
.set('Authorization', 'Bearer access')
.end (err, res) ->
assert.equal(res.statusCode, 200)
Expand All @@ -246,23 +248,23 @@ describe 'Authentication routes', () ->

it 'Should send a 401 for unauthorized create', (done)->
Invisible.models.Message.prototype.allowCreate = (user, cb)->
return cb(null, this.from.toString() == user._id.toString())
return cb(null, this.from == user._id)

request(app)
.post("/invisible/Message/")
.send({text:"howdy", from:martin._id.toString()})
.send({text:"howdy", from:martin._id})
.set('Authorization', 'Bearer access')
.end (err, res) ->
assert.equal(res.statusCode, 401)
done()

it 'Should allow authorized update', (done)->
Invisible.models.Message.prototype.allowUpdate = (user, cb)->
return cb(null, this.from.toString() == user._id.toString())
return cb(null, this.from == user._id)

request(app)
.put("/invisible/Message/#{m1._id}/")
.send({text:"howdy", from:facundo._id.toString()})
.send({text:"howdy", from:facundo._id})
.set('Authorization', 'Bearer access')
.end (err, res) ->
assert.equal(res.statusCode, 200)
Expand All @@ -271,19 +273,19 @@ describe 'Authentication routes', () ->

it 'Should send a 401 for unauthorized update', (done)->
Invisible.models.Message.prototype.allowUpdate = (user, cb)->
return cb(null, this.from.toString() == user._id.toString())
return cb(null, this.from == user._id)

request(app)
.put("/invisible/Message/#{m2._id}/")
.send({text:"howdy", from:facundo._id.toString()})
.send({text:"howdy", from:facundo._id})
.set('Authorization', 'Bearer access')
.end (err, res) ->
assert.equal(res.statusCode, 401)
done()

it 'Should allow authorized find', (done)->
Invisible.models.Message.prototype.allowFind = (user, cb)->
return cb(null, this.from.toString() == user._id.toString())
return cb(null, this.from == user._id)

request(app)
.get("/invisible/Message/#{m1._id}/")
Expand All @@ -306,7 +308,7 @@ describe 'Authentication routes', () ->

it 'Should allow authorized delete', (done)->
Invisible.models.Message.prototype.allowDelete = (user, cb)->
return cb(null, this.from.toString() == user._id.toString())
return cb(null, this.from == user._id)

request(app)
.del("/invisible/Message/#{m1._id}/")
Expand All @@ -318,7 +320,7 @@ describe 'Authentication routes', () ->

it 'Should send a 401 for unauthorized delete', (done)->
Invisible.models.Message.prototype.allowDelete = (user, cb)->
return cb(null, this.from.toString() == user._id.toString())
return cb(null, this.from == user._id)

request(app)
.del("/invisible/Message/#{m2._id}/")
Expand Down Expand Up @@ -493,22 +495,22 @@ describe 'Server socket authorization', () ->
class Message
constructor: (@text, @from, @to) ->
allowEvents: (user, cb)->
cb(null, user._id.toString() == @to.toString())
cb(null, user._id == @to)

Invisible.createModel("Message", Message)
facundo = new Invisible.models.User("Facundo", "pass")
facundo.save()
martin = new Invisible.models.User("Martin", "pass")
martin.save()
client = new ClientSocketMock(5)
server = new ServerSocketMock()
facundo = new Invisible.models.User("Facundo", "pass")
facundo.save ()->

Invisible.models.User.serverSocket = server.nsps["/User"]
Invisible.models.Message.serverSocket = server.nsps["/Message"]
Invisible.models.User.serverSocket = server.nsps["/User"]
Invisible.models.Message.serverSocket = server.nsps["/Message"]

client.auth = true
client.client.user = facundo
done()
client.auth = true
client.client.user = facundo
done()

it 'Should send events to any client if allow method not defined', (done)->
server.connect("/User", client)
Expand Down

0 comments on commit dac7504

Please sign in to comment.