diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2016-07-20 16:23:58 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2016-07-20 16:23:58 +0200 |
commit | 2f372a865487427ff97ad17edd0e6adfbb478c80 (patch) | |
tree | 84e8b2ba2240e83dcdeeb057579327b0a663974b | |
parent | 66698b833f77806045b36fdeba612acda43dd7f7 (diff) | |
download | PeerTube-2f372a865487427ff97ad17edd0e6adfbb478c80.tar.gz PeerTube-2f372a865487427ff97ad17edd0e6adfbb478c80.tar.zst PeerTube-2f372a865487427ff97ad17edd0e6adfbb478c80.zip |
Server: implement refresh token
-rw-r--r-- | server/controllers/api/v1/users.js | 1 | ||||
-rw-r--r-- | server/initializers/constants.js | 6 | ||||
-rw-r--r-- | server/initializers/installer.js | 2 | ||||
-rw-r--r-- | server/lib/oauth-model.js | 32 | ||||
-rw-r--r-- | server/middlewares/oauth.js | 3 | ||||
-rw-r--r-- | server/models/oauth-client.js | 4 | ||||
-rw-r--r-- | server/models/oauth-token.js | 37 | ||||
-rw-r--r-- | server/models/user.js | 8 | ||||
-rw-r--r-- | 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() | |||
12 | 12 | ||
13 | router.get('/client', getAngularClient) | 13 | router.get('/client', getAngularClient) |
14 | router.post('/token', oAuth.token, success) | 14 | router.post('/token', oAuth.token, success) |
15 | // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged,, implement revoke token route | ||
15 | 16 | ||
16 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
17 | 18 | ||
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 = { | |||
12 | // Time to wait between requests to the friends (10 min) | 12 | // Time to wait between requests to the friends (10 min) |
13 | let INTERVAL = 600000 | 13 | let INTERVAL = 600000 |
14 | 14 | ||
15 | const OAUTH_LIFETIME = { | ||
16 | ACCESS_TOKEN: 3600 * 4, // 4 hours | ||
17 | REFRESH_TOKEN: 1209600 // 2 weeks | ||
18 | } | ||
19 | |||
15 | // Number of results by default for the pagination | 20 | // Number of results by default for the pagination |
16 | const PAGINATION_COUNT_DEFAULT = 15 | 21 | const PAGINATION_COUNT_DEFAULT = 15 |
17 | 22 | ||
@@ -71,6 +76,7 @@ module.exports = { | |||
71 | API_VERSION: API_VERSION, | 76 | API_VERSION: API_VERSION, |
72 | FRIEND_SCORE: FRIEND_SCORE, | 77 | FRIEND_SCORE: FRIEND_SCORE, |
73 | INTERVAL: INTERVAL, | 78 | INTERVAL: INTERVAL, |
79 | OAUTH_LIFETIME: OAUTH_LIFETIME, | ||
74 | PAGINATION_COUNT_DEFAULT: PAGINATION_COUNT_DEFAULT, | 80 | PAGINATION_COUNT_DEFAULT: PAGINATION_COUNT_DEFAULT, |
75 | PODS_SCORE: PODS_SCORE, | 81 | PODS_SCORE: PODS_SCORE, |
76 | REQUESTS_IN_PARALLEL: REQUESTS_IN_PARALLEL, | 82 | 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) { | |||
66 | const secret = passwordGenerator(32, false) | 66 | const secret = passwordGenerator(32, false) |
67 | const client = new Client({ | 67 | const client = new Client({ |
68 | clientSecret: secret, | 68 | clientSecret: secret, |
69 | grants: [ 'password' ] | 69 | grants: [ 'password', 'refresh_token' ] |
70 | }) | 70 | }) |
71 | 71 | ||
72 | client.save(function (err, createdClient) { | 72 | 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 = { | |||
12 | getClient: getClient, | 12 | getClient: getClient, |
13 | getRefreshToken: getRefreshToken, | 13 | getRefreshToken: getRefreshToken, |
14 | getUser: getUser, | 14 | getUser: getUser, |
15 | revokeToken: revokeToken, | ||
15 | saveToken: saveToken | 16 | saveToken: saveToken |
16 | } | 17 | } |
17 | 18 | ||
@@ -20,7 +21,7 @@ const OAuthModel = { | |||
20 | function getAccessToken (bearerToken) { | 21 | function getAccessToken (bearerToken) { |
21 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') | 22 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') |
22 | 23 | ||
23 | return OAuthToken.loadByTokenAndPopulateUser(bearerToken) | 24 | return OAuthToken.getByTokenAndPopulateUser(bearerToken) |
24 | } | 25 | } |
25 | 26 | ||
26 | function getClient (clientId, clientSecret) { | 27 | function getClient (clientId, clientSecret) { |
@@ -28,19 +29,36 @@ function getClient (clientId, clientSecret) { | |||
28 | 29 | ||
29 | // TODO req validator | 30 | // TODO req validator |
30 | const mongoId = new mongoose.mongo.ObjectID(clientId) | 31 | const mongoId = new mongoose.mongo.ObjectID(clientId) |
31 | return OAuthClient.loadByIdAndSecret(mongoId, clientSecret) | 32 | return OAuthClient.getByIdAndSecret(mongoId, clientSecret) |
32 | } | 33 | } |
33 | 34 | ||
34 | function getRefreshToken (refreshToken) { | 35 | function getRefreshToken (refreshToken, callback) { |
35 | logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') | 36 | logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') |
36 | 37 | ||
37 | return OAuthToken.loadByRefreshToken(refreshToken) | 38 | return OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken) |
38 | } | 39 | } |
39 | 40 | ||
40 | function getUser (username, password) { | 41 | function getUser (username, password) { |
41 | logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') | 42 | logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') |
42 | 43 | ||
43 | return User.loadByUsernameAndPassword(username, password) | 44 | return User.getByUsernameAndPassword(username, password) |
45 | } | ||
46 | |||
47 | function revokeToken (token) { | ||
48 | return OAuthToken.getByRefreshToken(token.refreshToken).then(function (tokenDB) { | ||
49 | if (tokenDB) tokenDB.remove() | ||
50 | |||
51 | /* | ||
52 | * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js | ||
53 | * "As per the discussion we need set older date | ||
54 | * revokeToken will expected return a boolean in future version | ||
55 | * https://github.com/oauthjs/node-oauth2-server/pull/274 | ||
56 | * https://github.com/oauthjs/node-oauth2-server/issues/290" | ||
57 | */ | ||
58 | const expiredToken = tokenDB | ||
59 | expiredToken.refreshTokenExpiresAt = new Date('2015-05-28T06:59:53.000Z') | ||
60 | return expiredToken | ||
61 | }) | ||
44 | } | 62 | } |
45 | 63 | ||
46 | function saveToken (token, client, user) { | 64 | function saveToken (token, client, user) { |
@@ -48,10 +66,10 @@ function saveToken (token, client, user) { | |||
48 | 66 | ||
49 | const tokenObj = new OAuthToken({ | 67 | const tokenObj = new OAuthToken({ |
50 | accessToken: token.accessToken, | 68 | accessToken: token.accessToken, |
51 | accessTokenExpiresOn: token.accessTokenExpiresOn, | 69 | accessTokenExpiresAt: token.accessTokenExpiresAt, |
52 | client: client.id, | 70 | client: client.id, |
53 | refreshToken: token.refreshToken, | 71 | refreshToken: token.refreshToken, |
54 | refreshTokenExpiresOn: token.refreshTokenExpiresOn, | 72 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, |
55 | user: user.id | 73 | user: user.id |
56 | }) | 74 | }) |
57 | 75 | ||
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 @@ | |||
2 | 2 | ||
3 | const OAuthServer = require('express-oauth-server') | 3 | const OAuthServer = require('express-oauth-server') |
4 | 4 | ||
5 | const constants = require('../initializers/constants') | ||
5 | const logger = require('../helpers/logger') | 6 | const logger = require('../helpers/logger') |
6 | 7 | ||
7 | const oAuthServer = new OAuthServer({ | 8 | const oAuthServer = new OAuthServer({ |
9 | accessTokenLifetime: constants.OAUTH_LIFETIME.ACCESS_TOKEN, | ||
10 | refreshTokenLifetime: constants.OAUTH_LIFETIME.REFRESH_TOKEN, | ||
8 | model: require('../lib/oauth-model') | 11 | model: require('../lib/oauth-model') |
9 | }) | 12 | }) |
10 | 13 | ||
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({ | |||
11 | OAuthClientSchema.path('clientSecret').required(true) | 11 | OAuthClientSchema.path('clientSecret').required(true) |
12 | 12 | ||
13 | OAuthClientSchema.statics = { | 13 | OAuthClientSchema.statics = { |
14 | getByIdAndSecret: getByIdAndSecret, | ||
14 | list: list, | 15 | list: list, |
15 | loadByIdAndSecret: loadByIdAndSecret, | ||
16 | loadFirstClient: loadFirstClient | 16 | loadFirstClient: loadFirstClient |
17 | } | 17 | } |
18 | 18 | ||
@@ -28,6 +28,6 @@ function loadFirstClient (callback) { | |||
28 | return this.findOne({}, callback) | 28 | return this.findOne({}, callback) |
29 | } | 29 | } |
30 | 30 | ||
31 | function loadByIdAndSecret (id, clientSecret) { | 31 | function getByIdAndSecret (id, clientSecret) { |
32 | return this.findOne({ _id: id, clientSecret: clientSecret }) | 32 | return this.findOne({ _id: id, clientSecret: clientSecret }) |
33 | } | 33 | } |
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 @@ | |||
1 | const mongoose = require('mongoose') | 1 | const mongoose = require('mongoose') |
2 | 2 | ||
3 | const logger = require('../helpers/logger') | ||
4 | |||
3 | // --------------------------------------------------------------------------- | 5 | // --------------------------------------------------------------------------- |
4 | 6 | ||
5 | const OAuthTokenSchema = mongoose.Schema({ | 7 | const OAuthTokenSchema = mongoose.Schema({ |
6 | accessToken: String, | 8 | accessToken: String, |
7 | accessTokenExpiresOn: Date, | 9 | accessTokenExpiresAt: Date, |
8 | client: { type: mongoose.Schema.Types.ObjectId, ref: 'OAuthClient' }, | 10 | client: { type: mongoose.Schema.Types.ObjectId, ref: 'OAuthClient' }, |
9 | refreshToken: String, | 11 | refreshToken: String, |
10 | refreshTokenExpiresOn: Date, | 12 | refreshTokenExpiresAt: Date, |
11 | user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } | 13 | user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } |
12 | }) | 14 | }) |
13 | 15 | ||
@@ -16,19 +18,38 @@ OAuthTokenSchema.path('client').required(true) | |||
16 | OAuthTokenSchema.path('user').required(true) | 18 | OAuthTokenSchema.path('user').required(true) |
17 | 19 | ||
18 | OAuthTokenSchema.statics = { | 20 | OAuthTokenSchema.statics = { |
19 | loadByRefreshToken: loadByRefreshToken, | 21 | getByRefreshTokenAndPopulateClient: getByRefreshTokenAndPopulateClient, |
20 | loadByTokenAndPopulateUser: loadByTokenAndPopulateUser | 22 | getByTokenAndPopulateUser: getByTokenAndPopulateUser, |
23 | getByRefreshToken: getByRefreshToken | ||
21 | } | 24 | } |
22 | 25 | ||
23 | mongoose.model('OAuthToken', OAuthTokenSchema) | 26 | mongoose.model('OAuthToken', OAuthTokenSchema) |
24 | 27 | ||
25 | // --------------------------------------------------------------------------- | 28 | // --------------------------------------------------------------------------- |
26 | 29 | ||
27 | function loadByRefreshToken (refreshToken, callback) { | 30 | function getByRefreshTokenAndPopulateClient (refreshToken) { |
28 | return this.findOne({ refreshToken: refreshToken }, callback) | 31 | return this.findOne({ refreshToken: refreshToken }).populate('client').then(function (token) { |
32 | if (!token) return token | ||
33 | |||
34 | const tokenInfos = { | ||
35 | refreshToken: token.refreshToken, | ||
36 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, | ||
37 | client: { | ||
38 | id: token.client._id.toString() | ||
39 | }, | ||
40 | user: token.user | ||
41 | } | ||
42 | |||
43 | return tokenInfos | ||
44 | }).catch(function (err) { | ||
45 | logger.info('getRefreshToken error.', { error: err }) | ||
46 | }) | ||
29 | } | 47 | } |
30 | 48 | ||
31 | function loadByTokenAndPopulateUser (bearerToken, callback) { | 49 | function getByTokenAndPopulateUser (bearerToken) { |
32 | // FIXME: allow to use callback | ||
33 | return this.findOne({ accessToken: bearerToken }).populate('user') | 50 | return this.findOne({ accessToken: bearerToken }).populate('user') |
34 | } | 51 | } |
52 | |||
53 | function getByRefreshToken (refreshToken) { | ||
54 | return this.findOne({ refreshToken: refreshToken }) | ||
55 | } | ||
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) | |||
11 | UserSchema.path('username').required(true) | 11 | UserSchema.path('username').required(true) |
12 | 12 | ||
13 | UserSchema.statics = { | 13 | UserSchema.statics = { |
14 | list: list, | 14 | getByUsernameAndPassword: getByUsernameAndPassword, |
15 | loadByUsernameAndPassword: loadByUsernameAndPassword | 15 | list: list |
16 | } | 16 | } |
17 | 17 | ||
18 | mongoose.model('User', UserSchema) | 18 | mongoose.model('User', UserSchema) |
@@ -23,6 +23,6 @@ function list (callback) { | |||
23 | return this.find(callback) | 23 | return this.find(callback) |
24 | } | 24 | } |
25 | 25 | ||
26 | function loadByUsernameAndPassword (username, password, callback) { | 26 | function getByUsernameAndPassword (username, password) { |
27 | return this.findOne({ username: username, password: password }, callback) | 27 | return this.findOne({ username: username, password: password }) |
28 | } | 28 | } |
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 () { | |||
144 | utils.removeVideo(server.url, accessToken, videoId, done) | 144 | utils.removeVideo(server.url, accessToken, videoId, done) |
145 | }) | 145 | }) |
146 | 146 | ||
147 | it('Should logout') | 147 | it('Should logout (revoke token)') |
148 | 148 | ||
149 | it('Should not be able to upload a video') | 149 | it('Should not be able to upload a video') |
150 | 150 | ||
@@ -152,6 +152,12 @@ describe('Test users', function () { | |||
152 | 152 | ||
153 | it('Should be able to login again') | 153 | it('Should be able to login again') |
154 | 154 | ||
155 | it('Should have an expired access token') | ||
156 | |||
157 | it('Should refresh the token') | ||
158 | |||
159 | it('Should be able to upload a video again') | ||
160 | |||
155 | after(function (done) { | 161 | after(function (done) { |
156 | process.kill(-server.app.pid) | 162 | process.kill(-server.app.pid) |
157 | 163 | ||