Server: implement refresh token
authorChocobozzz <florian.bigard@gmail.com>
Wed, 20 Jul 2016 14:23:58 +0000 (16:23 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Wed, 20 Jul 2016 14:23:58 +0000 (16:23 +0200)
server/controllers/api/v1/users.js
server/initializers/constants.js
server/initializers/installer.js
server/lib/oauth-model.js
server/middlewares/oauth.js
server/models/oauth-client.js
server/models/oauth-token.js
server/models/user.js
server/tests/api/users.js

index 3f8d8ad92e4974567ebb433ca75bd46190cbaacd..fbbe6e4726276140409dee8b7247659034e0aa4a 100644 (file)
@@ -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
 
 // ---------------------------------------------------------------------------
 
index 32fe1645f607792acbee50fd9cdcdd455e342c6b..7fcf5b01bb2fb219f489e8d3379be35acd740eb0 100644 (file)
@@ -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,
index 490084104d142612b247a08baa6781b2860c7ff6..32830d4dab5d3aa811428074296d4d7b56578a38 100644 (file)
@@ -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) {
index f4fd9805a2d2f6d287fb3e6c72459c36aabf03f5..555a54e9077d6f3acff06bae9d1ecbf83bda45bd 100644 (file)
@@ -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
   })
 
index 3d7429f1d44cea7cc36d315afa3b6214db399b7d..91a99050913eead153e1c281bd5b86c524fc5993 100644 (file)
@@ -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')
 })
 
index 048e5af481584534839436ef9c51ba1314e7fabf..830f68857a10d55c60d70f08297fb495b0d9091b 100644 (file)
@@ -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 })
 }
index 5da5da41703a0656929a2b2ba88e2fff373f4722..23c6987321733bfa70b51f077b5a70dbf175ed42 100644 (file)
@@ -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 })
+}
index 130b49b55268cbc4d60705d3081a61a665aef83d..14ffecbff1ca89ae5139a26946d1b54344fc5e12 100644 (file)
@@ -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 })
 }
index 749aa8af8abffad589dc316c797614ece23bd203..68ba9de336d088e07542772d921d74898c0a896c 100644 (file)
@@ -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)