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

Refresh copilot Gitlab access token automatically when needed. #92

Merged
merged 1 commit into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,10 @@ module.exports = {
ROLE_ID_SUBMITTER: process.env.ROLE_ID_SUBMITTER || '732339e7-8e30-49d7-9198-cccf9451e221',
TYPE_ID_TASK: process.env.TYPE_ID_TASK || 'ecd58c69-238f-43a4-a4bb-d172719b9f31',
DEFAULT_TIMELINE_TEMPLATE_ID: process.env.DEFAULT_TIMELINE_TEMPLATE_ID || '53a307ce-b4b3-4d6f-b9a1-3741a58f77e6',
DEFAULT_TRACK_ID: process.env.DEFAULT_TRACK_ID || '9b6fc876-f4d9-4ccb-9dfd-419247628825'
DEFAULT_TRACK_ID: process.env.DEFAULT_TRACK_ID || '9b6fc876-f4d9-4ccb-9dfd-419247628825',
GITLAB_ACCESS_TOKEN_DEFAULT_EXPIRATION: 3600 * 2,
GITLAB_REFRESH_TOKEN_BEFORE_EXPIRATION: 300,
GITLAB_CLIENT_ID: process.env.GITLAB_CLIENT_ID,
GITLAB_CLIENT_SECRET: process.env.GITLAB_CLIENT_SECRET,
GITLAB_OWNER_USER_CALLBACK_URL: process.env.GITLAB_OWNER_USER_CALLBACK_URL
};
3 changes: 3 additions & 0 deletions configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ The following config parameters are supported, they are defined in `config/defau
| NEW_CHALLENGE_DURATION_IN_DAYS | the duration of new challenge | 5 |
|TC_URL| the base URL of topcoder to get the challenge URL| defaults to `https://www.topcoder-dev.com`|
|GITLAB_API_BASE_URL| the URL for gitlab host| defaults to `https://gitlab.com`|
| GITLAB_CLIENT_ID | the GitLab client id | No default - needs to be set up with same value found in topcoder-x-ui |
| GITLAB_CLIENT_SECRET | the GitLab client secret | No default - needs to be set up with same value found in topcoder-x-ui |
| 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 |
|PAID_ISSUE_LABEL|the label name for paid, should be one of the label configured in topcoder x ui|'tcx_Paid'|
|FIX_ACCEPTED_ISSUE_LABEL|the label name for fix accepted, should be one of the label configured in topcoder x ui|'tcx_FixAccepted'|
|ASSIGNED_ISSUE_LABEL| the label name for assigned, should be one of the label configured in topcoder x ui| 'tcx_Assigned'|
Expand Down
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ process.on('unhandledRejection', (err) => {
});

// dump the configuration to logger
const ignoreConfigLog = ['cert', 'key', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_SECRET'];
const ignoreConfigLog = ['cert', 'key', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AUTH0_CLIENT_ID', 'AUTH0_CLIENT_SECRET',
'GITLAB_CLIENT_ID', 'GITLAB_CLIENT_SECRET'];

/**
* Print configs to logger
* @param {Object} params the config params
Expand Down
65 changes: 56 additions & 9 deletions services/GitlabService.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@ const GitlabAPI = require('node-gitlab-api');
const logger = require('../utils/logger');
const errors = require('../utils/errors');
const helper = require('../utils/helper');
const dbHelper = require('../utils/db-helper');
const superagent = require('superagent');
const superagentPromise = require('superagent-promise');

const request = superagentPromise(superagent, Promise);
// milliseconds per second
const MS_PER_SECOND = 1000;

const copilotUserSchema = Joi.object().keys({
accessToken: Joi.string().required(),
accessTokenExpiration: Joi.date().required(),
refreshToken: Joi.string().required(),
userProviderId: Joi.number().required(),
topcoderUsername: Joi.string()
}).required();
Expand Down Expand Up @@ -80,7 +89,8 @@ function _getIssueUrl(repoPath, issueId) {
async function createComment(copilot, project, issueId, body) {
const projectId = project.id;
Joi.attempt({copilot, projectId, issueId, body}, createComment.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
try {
body = helper.prepareAutomatedComment(body, copilot);
await gitlab.projects.issues.notes.create(projectId, issueId, {body});
Expand All @@ -107,7 +117,8 @@ createComment.schema = {
async function updateIssue(copilot, project, issueId, title) {
const projectId = project.id;
Joi.attempt({copilot, projectId, issueId, title}, updateIssue.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
try {
await gitlab.projects.issues.edit(projectId, issueId, {title});
} catch (err) {
Expand All @@ -133,7 +144,8 @@ updateIssue.schema = {
async function assignUser(copilot, project, issueId, userId) {
const projectId = project.id;
Joi.attempt({copilot, projectId, issueId, userId}, assignUser.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
try {
const issue = await gitlab.projects.issues.show(projectId, issueId);
const oldAssignees = _.without(issue.assignee_ids, userId);
Expand Down Expand Up @@ -164,7 +176,8 @@ assignUser.schema = {
async function removeAssign(copilot, project, issueId, userId) {
const projectId = project.id;
Joi.attempt({copilot, projectId, issueId, userId}, removeAssign.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
await _removeAssignees(gitlab, projectId, issueId, [userId]);
logger.debug(`Gitlab user ${userId} is unassigned from issue number ${issueId}`);
}
Expand All @@ -179,7 +192,8 @@ removeAssign.schema = assignUser.schema;
*/
async function getUsernameById(copilot, userId) {
Joi.attempt({copilot, userId}, getUsernameById.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
const user = await gitlab.users.show(userId);
return user ? user.username : null;
}
Expand All @@ -197,7 +211,8 @@ getUsernameById.schema = {
*/
async function getUserIdByLogin(copilot, login) {
Joi.attempt({copilot, login}, getUserIdByLogin.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
const user = await gitlab.users.all({username: login});
return user.length ? user[0].id : null;
}
Expand All @@ -220,7 +235,8 @@ getUserIdByLogin.schema = {
async function markIssueAsPaid(copilot, project, issueId, challengeUUID, existLabels, winner, createCopilotPayments) { // eslint-disable-line max-params
const projectId = project.id;
Joi.attempt({copilot, projectId, issueId, challengeUUID, existLabels, winner, createCopilotPayments}, markIssueAsPaid.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
const labels = _(existLabels).filter((i) => i !== config.FIX_ACCEPTED_ISSUE_LABEL)
.push(config.FIX_ACCEPTED_ISSUE_LABEL, config.PAID_ISSUE_LABEL).value();
try {
Expand Down Expand Up @@ -263,7 +279,8 @@ markIssueAsPaid.schema = {
async function changeState(copilot, project, issueId, state) {
const projectId = project.id;
Joi.attempt({copilot, projectId, issueId, state}, changeState.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
try {
await gitlab.projects.issues.edit(projectId, issueId, {state_event: state});
} catch (err) {
Expand All @@ -289,7 +306,8 @@ changeState.schema = {
async function addLabels(copilot, project, issueId, labels) {
const projectId = project.id;
Joi.attempt({copilot, projectId, issueId, labels}, addLabels.schema);
const gitlab = await _authenticate(copilot.accessToken);
const refreshedCopilot = await _refreshGitlabUserAccessToken(copilot);
const gitlab = await _authenticate(refreshedCopilot.accessToken);
try {
await gitlab.projects.issues.edit(projectId, issueId, {labels: _.join(labels, ',')});
} catch (err) {
Expand All @@ -305,6 +323,35 @@ addLabels.schema = {
labels: Joi.array().items(Joi.string()).required()
};

/**
* Refresh the copilot access token if token is needed
* @param {Object} copilot the copilot
* @returns {Promise} the promise result of copilot with refreshed token
*/
async function _refreshGitlabUserAccessToken(copilot) {
if (copilot.accessTokenExpiration && new Date().getTime() > copilot.accessTokenExpiration.getTime() -
(config.GITLAB_REFRESH_TOKEN_BEFORE_EXPIRATION * MS_PER_SECOND)) {
const refreshTokenResult = await request
.post(`${config.GITLAB_API_BASE_URL}/oauth/token`)
.query({
client_id: config.GITLAB_CLIENT_ID,
client_secret: config.GITLAB_CLIENT_SECRET,
refresh_token: copilot.refreshToken,
grant_type: 'refresh_token',
redirect_uri: config.GITLAB_OWNER_USER_CALLBACK_URL,
})
.end();
// save user token data
const expiresIn = refreshTokenResult.body.expires_in || config.GITLAB_ACCESS_TOKEN_DEFAULT_EXPIRATION;
return await dbHelper.update(User, copilot.id, {
accessToken: refreshTokenResult.body.access_token,
accessTokenExpiration: new Date(new Date().getTime() + expiresIn * MS_PER_SECOND),
refreshToken: refreshTokenResult.body.refresh_token,
});
}
return copilot;
}


module.exports = {
createComment,
Expand Down
2 changes: 2 additions & 0 deletions services/UserService.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ async function getRepositoryCopilotOrOwner(provider, repoFullName) {

return {
accessToken: user.accessToken,
accessTokenExpiration: user.accessTokenExpiration,
refreshToken: user.refreshToken,
userProviderId: user.userProviderId,
topcoderUsername: userMapping.topcoderUsername
};
Expand Down