From 2f372a865487427ff97ad17edd0e6adfbb478c80 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 20 Jul 2016 16:23:58 +0200 Subject: [PATCH] Server: implement refresh token --- server/controllers/api/v1/users.js | 1 + server/initializers/constants.js | 6 +++++ server/initializers/installer.js | 2 +- server/lib/oauth-model.js | 32 ++++++++++++++++++++------ server/middlewares/oauth.js | 3 +++ server/models/oauth-client.js | 4 ++-- server/models/oauth-token.js | 37 +++++++++++++++++++++++------- server/models/user.js | 8 +++---- server/tests/api/users.js | 8 ++++++- 9 files changed, 78 insertions(+), 23 deletions(-) diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index 3f8d8ad92..fbbe6e472 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js @@ -12,6 +12,7 @@ const router = express.Router() router.get('/client', getAngularClient) router.post('/token', oAuth.token, success) +// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged,, implement revoke token route // --------------------------------------------------------------------------- diff --git a/server/initializers/constants.js b/server/initializers/constants.js index 32fe1645f..7fcf5b01b 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js @@ -12,6 +12,11 @@ const FRIEND_SCORE = { // Time to wait between requests to the friends (10 min) let INTERVAL = 600000 +const OAUTH_LIFETIME = { + ACCESS_TOKEN: 3600 * 4, // 4 hours + REFRESH_TOKEN: 1209600 // 2 weeks +} + // Number of results by default for the pagination const PAGINATION_COUNT_DEFAULT = 15 @@ -71,6 +76,7 @@ module.exports = { API_VERSION: API_VERSION, FRIEND_SCORE: FRIEND_SCORE, INTERVAL: INTERVAL, + OAUTH_LIFETIME: OAUTH_LIFETIME, PAGINATION_COUNT_DEFAULT: PAGINATION_COUNT_DEFAULT, PODS_SCORE: PODS_SCORE, REQUESTS_IN_PARALLEL: REQUESTS_IN_PARALLEL, diff --git a/server/initializers/installer.js b/server/initializers/installer.js index 490084104..32830d4da 100644 --- a/server/initializers/installer.js +++ b/server/initializers/installer.js @@ -66,7 +66,7 @@ function createOAuthClientIfNotExist (callback) { const secret = passwordGenerator(32, false) const client = new Client({ clientSecret: secret, - grants: [ 'password' ] + grants: [ 'password', 'refresh_token' ] }) client.save(function (err, createdClient) { diff --git a/server/lib/oauth-model.js b/server/lib/oauth-model.js index f4fd9805a..555a54e90 100644 --- a/server/lib/oauth-model.js +++ b/server/lib/oauth-model.js @@ -12,6 +12,7 @@ const OAuthModel = { getClient: getClient, getRefreshToken: getRefreshToken, getUser: getUser, + revokeToken: revokeToken, saveToken: saveToken } @@ -20,7 +21,7 @@ const OAuthModel = { function getAccessToken (bearerToken) { logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') - return OAuthToken.loadByTokenAndPopulateUser(bearerToken) + return OAuthToken.getByTokenAndPopulateUser(bearerToken) } function getClient (clientId, clientSecret) { @@ -28,19 +29,36 @@ function getClient (clientId, clientSecret) { // TODO req validator const mongoId = new mongoose.mongo.ObjectID(clientId) - return OAuthClient.loadByIdAndSecret(mongoId, clientSecret) + return OAuthClient.getByIdAndSecret(mongoId, clientSecret) } -function getRefreshToken (refreshToken) { +function getRefreshToken (refreshToken, callback) { logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') - return OAuthToken.loadByRefreshToken(refreshToken) + return OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken) } function getUser (username, password) { logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') - return User.loadByUsernameAndPassword(username, password) + return User.getByUsernameAndPassword(username, password) +} + +function revokeToken (token) { + return OAuthToken.getByRefreshToken(token.refreshToken).then(function (tokenDB) { + if (tokenDB) tokenDB.remove() + + /* + * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js + * "As per the discussion we need set older date + * revokeToken will expected return a boolean in future version + * https://github.com/oauthjs/node-oauth2-server/pull/274 + * https://github.com/oauthjs/node-oauth2-server/issues/290" + */ + const expiredToken = tokenDB + expiredToken.refreshTokenExpiresAt = new Date('2015-05-28T06:59:53.000Z') + return expiredToken + }) } function saveToken (token, client, user) { @@ -48,10 +66,10 @@ function saveToken (token, client, user) { const tokenObj = new OAuthToken({ accessToken: token.accessToken, - accessTokenExpiresOn: token.accessTokenExpiresOn, + accessTokenExpiresAt: token.accessTokenExpiresAt, client: client.id, refreshToken: token.refreshToken, - refreshTokenExpiresOn: token.refreshTokenExpiresOn, + refreshTokenExpiresAt: token.refreshTokenExpiresAt, user: user.id }) diff --git a/server/middlewares/oauth.js b/server/middlewares/oauth.js index 3d7429f1d..91a990509 100644 --- a/server/middlewares/oauth.js +++ b/server/middlewares/oauth.js @@ -2,9 +2,12 @@ const OAuthServer = require('express-oauth-server') +const constants = require('../initializers/constants') const logger = require('../helpers/logger') const oAuthServer = new OAuthServer({ + accessTokenLifetime: constants.OAUTH_LIFETIME.ACCESS_TOKEN, + refreshTokenLifetime: constants.OAUTH_LIFETIME.REFRESH_TOKEN, model: require('../lib/oauth-model') }) diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js index 048e5af48..830f68857 100644 --- a/server/models/oauth-client.js +++ b/server/models/oauth-client.js @@ -11,8 +11,8 @@ const OAuthClientSchema = mongoose.Schema({ OAuthClientSchema.path('clientSecret').required(true) OAuthClientSchema.statics = { + getByIdAndSecret: getByIdAndSecret, list: list, - loadByIdAndSecret: loadByIdAndSecret, loadFirstClient: loadFirstClient } @@ -28,6 +28,6 @@ function loadFirstClient (callback) { return this.findOne({}, callback) } -function loadByIdAndSecret (id, clientSecret) { +function getByIdAndSecret (id, clientSecret) { return this.findOne({ _id: id, clientSecret: clientSecret }) } diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js index 5da5da417..23c698732 100644 --- a/server/models/oauth-token.js +++ b/server/models/oauth-token.js @@ -1,13 +1,15 @@ const mongoose = require('mongoose') +const logger = require('../helpers/logger') + // --------------------------------------------------------------------------- const OAuthTokenSchema = mongoose.Schema({ accessToken: String, - accessTokenExpiresOn: Date, + accessTokenExpiresAt: Date, client: { type: mongoose.Schema.Types.ObjectId, ref: 'OAuthClient' }, refreshToken: String, - refreshTokenExpiresOn: Date, + refreshTokenExpiresAt: Date, user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } }) @@ -16,19 +18,38 @@ OAuthTokenSchema.path('client').required(true) OAuthTokenSchema.path('user').required(true) OAuthTokenSchema.statics = { - loadByRefreshToken: loadByRefreshToken, - loadByTokenAndPopulateUser: loadByTokenAndPopulateUser + getByRefreshTokenAndPopulateClient: getByRefreshTokenAndPopulateClient, + getByTokenAndPopulateUser: getByTokenAndPopulateUser, + getByRefreshToken: getByRefreshToken } mongoose.model('OAuthToken', OAuthTokenSchema) // --------------------------------------------------------------------------- -function loadByRefreshToken (refreshToken, callback) { - return this.findOne({ refreshToken: refreshToken }, callback) +function getByRefreshTokenAndPopulateClient (refreshToken) { + return this.findOne({ refreshToken: refreshToken }).populate('client').then(function (token) { + if (!token) return token + + const tokenInfos = { + refreshToken: token.refreshToken, + refreshTokenExpiresAt: token.refreshTokenExpiresAt, + client: { + id: token.client._id.toString() + }, + user: token.user + } + + return tokenInfos + }).catch(function (err) { + logger.info('getRefreshToken error.', { error: err }) + }) } -function loadByTokenAndPopulateUser (bearerToken, callback) { - // FIXME: allow to use callback +function getByTokenAndPopulateUser (bearerToken) { return this.findOne({ accessToken: bearerToken }).populate('user') } + +function getByRefreshToken (refreshToken) { + return this.findOne({ refreshToken: refreshToken }) +} diff --git a/server/models/user.js b/server/models/user.js index 130b49b55..14ffecbff 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -11,8 +11,8 @@ UserSchema.path('password').required(true) UserSchema.path('username').required(true) UserSchema.statics = { - list: list, - loadByUsernameAndPassword: loadByUsernameAndPassword + getByUsernameAndPassword: getByUsernameAndPassword, + list: list } mongoose.model('User', UserSchema) @@ -23,6 +23,6 @@ function list (callback) { return this.find(callback) } -function loadByUsernameAndPassword (username, password, callback) { - return this.findOne({ username: username, password: password }, callback) +function getByUsernameAndPassword (username, password) { + return this.findOne({ username: username, password: password }) } diff --git a/server/tests/api/users.js b/server/tests/api/users.js index 749aa8af8..68ba9de33 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js @@ -144,7 +144,7 @@ describe('Test users', function () { utils.removeVideo(server.url, accessToken, videoId, done) }) - it('Should logout') + it('Should logout (revoke token)') it('Should not be able to upload a video') @@ -152,6 +152,12 @@ describe('Test users', function () { it('Should be able to login again') + it('Should have an expired access token') + + it('Should refresh the token') + + it('Should be able to upload a video again') + after(function (done) { process.kill(-server.app.pid)