@@ -16,9 +16,18 @@ const GitlabAPI = require('node-gitlab-api');
1616const logger = require ( '../utils/logger' ) ;
1717const errors = require ( '../utils/errors' ) ;
1818const helper = require ( '../utils/helper' ) ;
19+ const dbHelper = require ( '../utils/db-helper' ) ;
20+ const superagent = require ( 'superagent' ) ;
21+ const superagentPromise = require ( 'superagent-promise' ) ;
22+
23+ const request = superagentPromise ( superagent , Promise ) ;
24+ // milliseconds per second
25+ const MS_PER_SECOND = 1000 ;
1926
2027const copilotUserSchema = Joi . object ( ) . keys ( {
2128 accessToken : Joi . string ( ) . required ( ) ,
29+ accessTokenExpiration : Joi . date ( ) . required ( ) ,
30+ refreshToken : Joi . string ( ) . required ( ) ,
2231 userProviderId : Joi . number ( ) . required ( ) ,
2332 topcoderUsername : Joi . string ( )
2433} ) . required ( ) ;
@@ -80,7 +89,8 @@ function _getIssueUrl(repoPath, issueId) {
8089async function createComment ( copilot , project , issueId , body ) {
8190 const projectId = project . id ;
8291 Joi . attempt ( { copilot, projectId, issueId, body} , createComment . schema ) ;
83- const gitlab = await _authenticate ( copilot . accessToken ) ;
92+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
93+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
8494 try {
8595 body = helper . prepareAutomatedComment ( body , copilot ) ;
8696 await gitlab . projects . issues . notes . create ( projectId , issueId , { body} ) ;
@@ -107,7 +117,8 @@ createComment.schema = {
107117async function updateIssue ( copilot , project , issueId , title ) {
108118 const projectId = project . id ;
109119 Joi . attempt ( { copilot, projectId, issueId, title} , updateIssue . schema ) ;
110- const gitlab = await _authenticate ( copilot . accessToken ) ;
120+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
121+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
111122 try {
112123 await gitlab . projects . issues . edit ( projectId , issueId , { title} ) ;
113124 } catch ( err ) {
@@ -133,7 +144,8 @@ updateIssue.schema = {
133144async function assignUser ( copilot , project , issueId , userId ) {
134145 const projectId = project . id ;
135146 Joi . attempt ( { copilot, projectId, issueId, userId} , assignUser . schema ) ;
136- const gitlab = await _authenticate ( copilot . accessToken ) ;
147+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
148+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
137149 try {
138150 const issue = await gitlab . projects . issues . show ( projectId , issueId ) ;
139151 const oldAssignees = _ . without ( issue . assignee_ids , userId ) ;
@@ -164,7 +176,8 @@ assignUser.schema = {
164176async function removeAssign ( copilot , project , issueId , userId ) {
165177 const projectId = project . id ;
166178 Joi . attempt ( { copilot, projectId, issueId, userId} , removeAssign . schema ) ;
167- const gitlab = await _authenticate ( copilot . accessToken ) ;
179+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
180+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
168181 await _removeAssignees ( gitlab , projectId , issueId , [ userId ] ) ;
169182 logger . debug ( `Gitlab user ${ userId } is unassigned from issue number ${ issueId } ` ) ;
170183}
@@ -179,7 +192,8 @@ removeAssign.schema = assignUser.schema;
179192 */
180193async function getUsernameById ( copilot , userId ) {
181194 Joi . attempt ( { copilot, userId} , getUsernameById . schema ) ;
182- const gitlab = await _authenticate ( copilot . accessToken ) ;
195+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
196+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
183197 const user = await gitlab . users . show ( userId ) ;
184198 return user ? user . username : null ;
185199}
@@ -197,7 +211,8 @@ getUsernameById.schema = {
197211 */
198212async function getUserIdByLogin ( copilot , login ) {
199213 Joi . attempt ( { copilot, login} , getUserIdByLogin . schema ) ;
200- const gitlab = await _authenticate ( copilot . accessToken ) ;
214+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
215+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
201216 const user = await gitlab . users . all ( { username : login } ) ;
202217 return user . length ? user [ 0 ] . id : null ;
203218}
@@ -220,7 +235,8 @@ getUserIdByLogin.schema = {
220235async function markIssueAsPaid ( copilot , project , issueId , challengeUUID , existLabels , winner , createCopilotPayments ) { // eslint-disable-line max-params
221236 const projectId = project . id ;
222237 Joi . attempt ( { copilot, projectId, issueId, challengeUUID, existLabels, winner, createCopilotPayments} , markIssueAsPaid . schema ) ;
223- const gitlab = await _authenticate ( copilot . accessToken ) ;
238+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
239+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
224240 const labels = _ ( existLabels ) . filter ( ( i ) => i !== config . FIX_ACCEPTED_ISSUE_LABEL )
225241 . push ( config . FIX_ACCEPTED_ISSUE_LABEL , config . PAID_ISSUE_LABEL ) . value ( ) ;
226242 try {
@@ -263,7 +279,8 @@ markIssueAsPaid.schema = {
263279async function changeState ( copilot , project , issueId , state ) {
264280 const projectId = project . id ;
265281 Joi . attempt ( { copilot, projectId, issueId, state} , changeState . schema ) ;
266- const gitlab = await _authenticate ( copilot . accessToken ) ;
282+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
283+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
267284 try {
268285 await gitlab . projects . issues . edit ( projectId , issueId , { state_event : state } ) ;
269286 } catch ( err ) {
@@ -289,7 +306,8 @@ changeState.schema = {
289306async function addLabels ( copilot , project , issueId , labels ) {
290307 const projectId = project . id ;
291308 Joi . attempt ( { copilot, projectId, issueId, labels} , addLabels . schema ) ;
292- const gitlab = await _authenticate ( copilot . accessToken ) ;
309+ const refreshedCopilot = await _refreshGitlabUserAccessToken ( copilot ) ;
310+ const gitlab = await _authenticate ( refreshedCopilot . accessToken ) ;
293311 try {
294312 await gitlab . projects . issues . edit ( projectId , issueId , { labels : _ . join ( labels , ',' ) } ) ;
295313 } catch ( err ) {
@@ -305,6 +323,35 @@ addLabels.schema = {
305323 labels : Joi . array ( ) . items ( Joi . string ( ) ) . required ( )
306324} ;
307325
326+ /**
327+ * Refresh the copilot access token if token is needed
328+ * @param {Object } copilot the copilot
329+ * @returns {Promise } the promise result of copilot with refreshed token
330+ */
331+ async function _refreshGitlabUserAccessToken ( copilot ) {
332+ if ( copilot . accessTokenExpiration && new Date ( ) . getTime ( ) > copilot . accessTokenExpiration . getTime ( ) -
333+ ( config . GITLAB_REFRESH_TOKEN_BEFORE_EXPIRATION * MS_PER_SECOND ) ) {
334+ const refreshTokenResult = await request
335+ . post ( `${ config . GITLAB_API_BASE_URL } /oauth/token` )
336+ . query ( {
337+ client_id : config . GITLAB_CLIENT_ID ,
338+ client_secret : config . GITLAB_CLIENT_SECRET ,
339+ refresh_token : copilot . refreshToken ,
340+ grant_type : 'refresh_token' ,
341+ redirect_uri : config . GITLAB_OWNER_USER_CALLBACK_URL ,
342+ } )
343+ . end ( ) ;
344+ // save user token data
345+ const expiresIn = refreshTokenResult . body . expires_in || config . GITLAB_ACCESS_TOKEN_DEFAULT_EXPIRATION ;
346+ return await dbHelper . update ( User , copilot . id , {
347+ accessToken : refreshTokenResult . body . access_token ,
348+ accessTokenExpiration : new Date ( new Date ( ) . getTime ( ) + expiresIn * MS_PER_SECOND ) ,
349+ refreshToken : refreshTokenResult . body . refresh_token ,
350+ } ) ;
351+ }
352+ return copilot ;
353+ }
354+
308355
309356module . exports = {
310357 createComment,
0 commit comments