Update session / token documentation

This commit is contained in:
Ritchie Martori 2013-11-14 15:27:36 -08:00
parent 64d8ff986b
commit 1bb95607b9
6 changed files with 123 additions and 50 deletions

View File

@ -211,6 +211,46 @@ app.use(loopback.rest());
View generated REST documentation by visiting: [http://localhost:3000/_docs](http://localhost:3000/_docs).
### Middleware
LoopBack comes bundled with several `connect` / `express` style middleware.
#### loopback.token(options)
**Options**
- `cookies` - An `Array` of cookie names
- `headers` - An `Array` of header names
- `params` - An `Array` of param names
Each array is used to add additional keys to find an `accessToken` for a `request`.
The following example illustrates how to check for an `accessToken` in a custom cookie, query string parameter
and header called `foo-auth`.
```js
app.use(loopback.token({
cookies: ['foo-auth'],
headers: ['foo-auth', 'X-Foo-Auth'],
cookies: ['foo-auth', 'foo_auth']
}));
```
**Defaults**
By default the following names will be checked. These names are appended to any optional names. They will always
be checked, but any names specified will be checked first.
```js
params.push('access_token');
headers.push('X-Access-Token');
headers.push('authorization');
cookies.push('access_token');
cookies.push('authorization');
```
> **NOTE:** The `loopback.token()` middleware will only check for [signed cookies](http://expressjs.com/api.html#req.signedCookies).
### Model
A Loopback `Model` is a vanilla JavaScript class constructor with an attached set of properties and options. A `Model` instance is created by passing a data object containing properties to the `Model` constructor. A `Model` constructor will clean the object passed to it and only set the values matching the properties you define.
@ -565,8 +605,8 @@ User.login = function (username, password, fn) {
} else if(!user) {
fn(failErr);
} else if(user.password === passwordHash) {
MySessionModel.create({userId: user.id}, function (err, session) {
fn(null, session.id);
MyAccessTokenModel.create({userId: user.id}, function (err, accessToken) {
fn(null, accessToken.id);
});
} else {
fn(failErr);
@ -585,7 +625,7 @@ loopback.remoteMethod(
{arg: 'username', type: 'string', required: true},
{arg: 'password', type: 'string', required: true}
],
returns: {arg: 'sessionId', type: 'any'},
returns: {arg: 'accessTokenId', type: 'any'},
http: {path: '/sign-in'}
}
);
@ -637,7 +677,7 @@ Define an instance method.
```js
User.prototype.logout = function (fn) {
MySessionModel.destroyAll({userId: this.id}, fn);
MyAccessTokenModel.destroyAll({userId: this.id}, fn);
}
```

View File

@ -25,16 +25,16 @@ var User = loopback.User.extend('user');
// attach to the memory connector
User.attachTo(memory);
// also attach the session model to a data source
User.session.attachTo(memory);
// also attach the accessToken model to a data source
User.accessToken.attachTo(memory);
// expose over the app's api
app.model(User);
```
**Note:** By default the `loopback.User` model uses the `loopback.Session` model to persist sessions. You can change this by setting the `session` property.
**Note:** By default the `loopback.User` model uses the `loopback.AccessToken` model to persist access tokens. You can change this by setting the `accessToken` property.
**Note:** You must attach both the `User` and `User.session` model's to a data source!
**Note:** You must attach both the `User` and `User.accessToken` model's to a data source!
#### User Creation
@ -49,13 +49,13 @@ User.create({email: 'foo@bar.com', password: 'bar'}, function(err, user) {
#### Login a User
Create a session for a user using the local auth strategy.
Create an `accessToken` for a user using the local auth strategy.
**Node.js**
```js
User.login({username: 'foo', password: 'bar'}, function(err, session) {
console.log(session);
User.login({username: 'foo', password: 'bar'}, function(err, accessToken) {
console.log(accessToken);
});
```
@ -88,8 +88,8 @@ POST
```js
// login a user and logout
User.login({"email": "foo@bar.com", "password": "bar"}, function(err, session) {
User.logout(session.id, function(err) {
User.login({"email": "foo@bar.com", "password": "bar"}, function(err, accessToken) {
User.logout(accessToken.id, function(err) {
// user logged out
});
});
@ -106,7 +106,7 @@ User.findOne({email: 'foo@bar.com'}, function(err, user) {
POST /users/logout
...
{
"sid": "<session id from user login>"
"sid": "<accessToken id from user login>"
}
```
@ -175,23 +175,23 @@ User.confirmReset(token, function(err) {
});
```
### Session Model
### AccessToken Model
Identify users by creating sessions when they connect to your loopback app. By default the `loopback.User` model uses the `loopback.Session` model to persist sessions. You can change this by setting the `session` property.
Identify users by creating accessTokens when they connect to your loopback app. By default the `loopback.User` model uses the `loopback.AccessToken` model to persist accessTokens. You can change this by setting the `accessToken` property.
```js
// define a custom session model
var MySession = loopback.Session.extend('my-session');
// define a custom accessToken model
var MyAccessToken = loopback.AccessToken.extend('MyAccessToken');
// define a custom User model
var User = loopback.User.extend('user');
// use the custom session model
User.session = MySession;
// use the custom accessToken model
User.accessToken = MyAccessToken;
// attach both Session and User to a data source
// attach both AccessToken and User to a data source
User.attachTo(loopback.memory());
MySession.attachTo(loopback.memory());
MyAccessToken.attachTo(loopback.memory());
```
### Email Model

View File

@ -10,7 +10,7 @@ A LoopBack model consists of:
Apps use the model API to display information to the user or trigger actions
on the models to interact with backend systems. LoopBack supports both "dynamic" schema-less models and "static", schema-driven models.
_Dynamic models_ require only a name. The format of the data are specified completely and flexibly by the client application. Well-suited for data that originates on the client, dynamic models enable you to persist data both between sessions and between devices without involving a schema.
_Dynamic models_ require only a name. The format of the data are specified completely and flexibly by the client application. Well-suited for data that originates on the client, dynamic models enable you to persist data both between accessTokens and between devices without involving a schema.
_Static models_ require more code up front, with the format of the data specified completely in JSON. Well-suited to both existing data and large, intricate datasets, static models provide structure and
consistency to their data, preventing bugs that can result from unexpected data in the database.

View File

@ -11,9 +11,8 @@ var RemoteObjects = require('strong-remoting');
module.exports = token;
function token(app, options) {
function token(options) {
options = options || {};
var tokenModelName = options.tokenModelName || 'AccessToken';
var TokenModel = options.model;
assert(TokenModel, 'loopback.token() middleware requires a AccessToken model');

View File

@ -5,7 +5,8 @@
var Model = require('../loopback').Model
, loopback = require('../loopback')
, crypto = require('crypto')
, uid = require('uid2');
, uid = require('uid2')
, DEFAULT_TOKEN_LEN = 64;
/**
* Default AccessToken properties.
@ -30,11 +31,11 @@ var AccessToken = module.exports = Model.extend('AccessToken', properties);
*/
AccessToken.createAccessTokenId = function (fn) {
uid(this.settings.accessTokenIdLength || 64, function(err, buf) {
uid(this.settings.accessTokenIdLength || DEFAULT_TOKEN_LEN, function(err, guid) {
if(err) {
fn(err);
} else {
fn(null, buf.toString('base64'));
fn(null, guid);
}
});
}
@ -98,16 +99,16 @@ function tokenIdForRequest(req, options) {
}
}
for(length = headers.length; i < length; i++) {
id = req.header(params[i]);
for(i = 0, length = headers.length; i < length; i++) {
id = req.header(headers[i]);
if(typeof id === 'string') {
return id;
}
}
for(length = headers.length; i < length; i++) {
id = req.signedCookies(cookies[i]);
for(i = 0, length = headers.length; i < length; i++) {
id = req.signedCookies[cookies[i]];
if(typeof id === 'string') {
return id;

View File

@ -4,30 +4,36 @@ var Token = loopback.AccessToken.extend('MyToken');
// attach Token to testing memory ds
Token.attachTo(loopback.memory());
describe('loopback.token(app, options)', function() {
describe('loopback.token(options)', function() {
beforeEach(createTestingToken);
it('should populate req.token from the query string', function (done) {
var app = loopback();
var options = {};
options.model = Token;
var testToken = this.token;
app.use(loopback.token(app, options));
app.get('/', function (req, res) {
try {
assert(req.accessToken, 'req should have accessToken');
assert(req.accessToken.id === testToken.id);
} catch(e) {
return done(e);
}
res.send('ok');
});
request(app)
createTestAppAndRequest(this.token, done)
.get('/?access_token=' + this.token.id)
.expect(200)
.end(done);
});
it('should populate req.token from a header', function (done) {
createTestAppAndRequest(this.token, done)
.get('/')
.set('authorization', this.token.id)
.expect(200)
.end(done);
});
it('should populate req.token from a secure cookie', function (done) {
var app = createTestApp(this.token, done);
request(app)
.get('/token')
.end(function(err, res) {
request(app)
.get('/')
.set('Cookie', res.header['set-cookie'])
.end(done);
});
});
});
function createTestingToken(done) {
@ -38,3 +44,30 @@ function createTestingToken(done) {
done();
});
}
function createTestAppAndRequest(testToken, done) {
var app = createTestApp(testToken, done);
return request(app);
}
function createTestApp(testToken, done) {
var app = loopback();
app.use(loopback.cookieParser('secret'));
app.use(loopback.token({model: Token}));
app.get('/token', function(req, res) {
res.cookie('authorization', testToken.id, {signed: true});
res.end();
});
app.get('/', function (req, res) {
try {
assert(req.accessToken, 'req should have accessToken');
assert(req.accessToken.id === testToken.id);
} catch(e) {
return done(e);
}
res.send('ok');
});
return app;
}