Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 5e46a7f

Browse files
committed
Refresh copilot Gitlab access token automatically when needed.
1 parent 85a732d commit 5e46a7f

File tree

5 files changed

+70
-11
lines changed

5 files changed

+70
-11
lines changed

config/default.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,10 @@ module.exports = {
7474
ROLE_ID_SUBMITTER: process.env.ROLE_ID_SUBMITTER || '732339e7-8e30-49d7-9198-cccf9451e221',
7575
TYPE_ID_TASK: process.env.TYPE_ID_TASK || 'ecd58c69-238f-43a4-a4bb-d172719b9f31',
7676
DEFAULT_TIMELINE_TEMPLATE_ID: process.env.DEFAULT_TIMELINE_TEMPLATE_ID || '53a307ce-b4b3-4d6f-b9a1-3741a58f77e6',
77-
DEFAULT_TRACK_ID: process.env.DEFAULT_TRACK_ID || '9b6fc876-f4d9-4ccb-9dfd-419247628825'
77+
DEFAULT_TRACK_ID: process.env.DEFAULT_TRACK_ID || '9b6fc876-f4d9-4ccb-9dfd-419247628825',
78+
GITLAB_ACCESS_TOKEN_DEFAULT_EXPIRATION: 3600 * 2,
79+
GITLAB_REFRESH_TOKEN_BEFORE_EXPIRATION: 300,
80+
GITLAB_CLIENT_ID: process.env.GITLAB_CLIENT_ID,
81+
GITLAB_CLIENT_SECRET: process.env.GITLAB_CLIENT_SECRET,
82+
GITLAB_OWNER_USER_CALLBACK_URL: process.env.GITLAB_OWNER_USER_CALLBACK_URL
7883
};

configuration.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ The following config parameters are supported, they are defined in `config/defau
1616
| NEW_CHALLENGE_DURATION_IN_DAYS | the duration of new challenge | 5 |
1717
|TC_URL| the base URL of topcoder to get the challenge URL| defaults to `https://www.topcoder-dev.com`|
1818
|GITLAB_API_BASE_URL| the URL for gitlab host| defaults to `https://gitlab.com`|
19+
| GITLAB_CLIENT_ID | the GitLab client id | No default - needs to be set up with same value found in topcoder-x-ui |
20+
| GITLAB_CLIENT_SECRET | the GitLab client secret | No default - needs to be set up with same value found in topcoder-x-ui |
21+
| GITLAB_OWNER_USER_CALLBACK_URL | the GitLab callback redirect uri for refreshing copilot token | No default - needs to be set up with same owner user callback value in topcoder-x-ui |
1922
|PAID_ISSUE_LABEL|the label name for paid, should be one of the label configured in topcoder x ui|'tcx_Paid'|
2023
|FIX_ACCEPTED_ISSUE_LABEL|the label name for fix accepted, should be one of the label configured in topcoder x ui|'tcx_FixAccepted'|
2124
|ASSIGNED_ISSUE_LABEL| the label name for assigned, should be one of the label configured in topcoder x ui| 'tcx_Assigned'|

index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ process.on('unhandledRejection', (err) => {
2222
});
2323

2424
// dump the configuration to logger
25-
const ignoreConfigLog = ['cert', 'key', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_SECRET'];
25+
const ignoreConfigLog = ['cert', 'key', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_SECRET',
26+
'GITLAB_CLIENT_ID', 'GITLAB_CLIENT_SECRET'];
27+
2628
/**
2729
* Print configs to logger
2830
* @param {Object} params the config params

services/GitlabService.js

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,18 @@ const GitlabAPI = require('node-gitlab-api');
1616
const logger = require('../utils/logger');
1717
const errors = require('../utils/errors');
1818
const 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

2027
const 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) {
8089
async 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 = {
107117
async 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 = {
133144
async 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 = {
164176
async 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
*/
180193
async 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
*/
198212
async 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 = {
220235
async 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 = {
263279
async 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 = {
289306
async 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

309356
module.exports = {
310357
createComment,

services/UserService.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ async function getRepositoryCopilotOrOwner(provider, repoFullName) {
9797

9898
return {
9999
accessToken: user.accessToken,
100+
accessTokenExpiration: user.accessTokenExpiration,
101+
refreshToken: user.refreshToken,
100102
userProviderId: user.userProviderId,
101103
topcoderUsername: userMapping.topcoderUsername
102104
};

0 commit comments

Comments
 (0)