diff --git a/doc/auth.md b/doc/auth.md index e89ef1417..1e00ed987 100644 --- a/doc/auth.md +++ b/doc/auth.md @@ -23,9 +23,8 @@ of the request. For GET, DELETE, and OPTIONS requests, you must sign the raw query string parameters. In addition to the parameters required for each individual request, you must -also include a `__nonce` parameter. This value should be an integer and must be -incremented with every request. A common practice is to simply use the current -UNIX timestamp. +also include a `__nonce` parameter. This value should be a string and must be +unique with every request. A common practice is to simply use UUID v4. In addition to the request parameters and nonce, you will also sign the HTTP method and request path. Ultimately the string you will sign will be: diff --git a/lib/server/middleware/authenticate.js b/lib/server/middleware/authenticate.js index 89f9f5e8c..d1ffbe111 100644 --- a/lib/server/middleware/authenticate.js +++ b/lib/server/middleware/authenticate.js @@ -16,6 +16,7 @@ const fromQuery = ['GET', 'DELETE', 'OPTIONS']; function AuthenticateMiddlewareFactory(storage) { const PublicKey = storage.models.PublicKey; const User = storage.models.User; + const UserNonce = storage.models.UserNonce; function authenticate(req, res, next) { let strategy = AuthenticateMiddlewareFactory._detectStrategy(req); @@ -74,18 +75,23 @@ function AuthenticateMiddlewareFactory(storage) { )); } - if (!params.__nonce || params.__nonce < user.__nonce) { - return next(new errors.NotAuthorizedError( - 'Invalid nonce supplied' - )); - } + var userNonce = new UserNonce({ + user: user.id, + nonce: params.__nonce + }); - user.__nonce = params.__nonce; + userNonce.save(function(err) { + if (err && err.code === '11000') { + return next(new errors.NotAuthorizedError( + 'Invalid nonce supplied' + )); + } - req.user = user; - req.pubkey = pubkey; + req.user = user; + req.pubkey = pubkey; - user.save(next); + return next(err); + }); }); }); break; diff --git a/lib/storage/index.js b/lib/storage/index.js index 23ef22403..6df887ae6 100644 --- a/lib/storage/index.js +++ b/lib/storage/index.js @@ -91,6 +91,7 @@ Storage.prototype._createBoundModels = function() { Bucket: require('./models/bucket'), PublicKey: require('./models/pubkey'), User: require('./models/user'), + UserNonce: require('./models/usernonce'), Token: require('./models/token'), Contact: require('./models/contact'), Shard: require('./models/shard'), diff --git a/lib/storage/models/user.js b/lib/storage/models/user.js index 62ad98bc8..da17d97ce 100644 --- a/lib/storage/models/user.js +++ b/lib/storage/models/user.js @@ -29,11 +29,6 @@ var User = new mongoose.Schema({ activated: { type: Boolean, default: false - }, - // incremented on API request - __nonce: { - type: Number, - default: 0 } }); @@ -44,7 +39,6 @@ User.set('toObject', { transform: function(doc, ret) { delete ret.__v; delete ret._id; - delete ret.__nonce; delete ret.hashpass; delete ret.activator; } diff --git a/lib/storage/models/usernonce.js b/lib/storage/models/usernonce.js new file mode 100644 index 000000000..830e50013 --- /dev/null +++ b/lib/storage/models/usernonce.js @@ -0,0 +1,29 @@ +'use strict'; + +const mongoose = require('mongoose'); +const SchemaOptions = require('../options'); + +/** + * Represents a unique user & nonce + * @constructor + */ +var UserNonce = new mongoose.Schema({ + user: { + type: mongoose.SchemaTypes.Email, + ref: 'User', + required: true + }, + nonce: { + type: String, + required: true, + expires: '5m' + }, +}); + +UserNonce.plugin(SchemaOptions); + +UserNonce.index({ user: 1, nonce: 1 }, {unique: true}); + +module.exports = function(connection) { + return connection.model('UserNonce', UserNonce); +};