Take access token expiration into account

This commit is contained in:
Pedro Pombeiro 2018-01-21 11:09:16 +01:00
parent 490c7f38b5
commit 52a626d645
No known key found for this signature in database
GPG Key ID: A65DEB11E4BBC647
5 changed files with 137 additions and 67 deletions

View File

@ -85,7 +85,7 @@ async function assignPullRequestToReview(gitHubContext, githubPayload, robot) {
robot.logger.debug(`Created card: ${ghcard.data.url}`, ghcard.data.id);
// Send message to Slack
robot.messageRoom(githubConfig.slack.notification.room, `Assigned PR to ``${reviewColumnName}`` in ``${projectBoardName}`` project\n${githubPayload.pull_request.html_url}`);
robot.messageRoom(githubConfig.slack.notification.room, `Assigned PR to ${reviewColumnName} in ${projectBoardName} project\n${githubPayload.pull_request.html_url}`);
} catch (err) {
robot.logger.error(`Couldn't create project card for the PR: ${err}`, column.id, githubPayload.pull_request.id);
}

View File

@ -86,7 +86,7 @@ async function assignIssueToBountyAwaitingForApproval(gitHubContext, githubPaylo
robot.logger.debug(`Created card: ${ghcard.data.url}`, ghcard.data.id);
// Send message to Slack
robot.messageRoom(githubConfig.slack.notification.room, `Assigned issue to ``${approvalColumnName}`` in ``${projectBoardName}`` project\n${githubPayload.issue.html_url}`);
robot.messageRoom(githubConfig.slack.notification.room, `Assigned issue to ${approvalColumnName} in ${projectBoardName} project\n${githubPayload.issue.html_url}`);
} catch (err) {
robot.logger.error(`Couldn't create project card for the issue: ${err}`, column.id, githubPayload.issue.id);
}

View File

@ -6,18 +6,33 @@
// Author:
// PombeirP
let initializing = false;
module.exports = function(robot) {
robot.brain.on('loaded', function() {
const gitHubContext = require('./github-context.js')();
appID = robot.brain.get("github-app_id");
if (appID) {
gitHubContext.initialize(robot, appID);
robot.brain.on('loaded', async function() {
if (initializing) {
return;
}
robot.logger.debug("Bot ready");
} else {
robot.logger.debug("Bot waiting to be installed");
initializing = true;
try {
const gitHubContext = require('./github-context.js')();
appID = robot.brain.get("github-app_id");
installationID = robot.brain.get("github-installation_id");
if (installationID) {
await gitHubContext.initialize(robot, appID, installationID);
robot.logger.debug("Bot ready");
} else {
robot.logger.debug("Bot waiting to be installed");
}
} catch(err) {
robot.logger.error(err);
} finally {
initializing = false;
}
})
}

View File

@ -11,6 +11,7 @@ const GitHubApi = require('github');
let cachedRobotNameRegex;
let initialized = false;
let initializing = false;
const githubAPI = new GitHubApi({
timeout: 15000,
requestMedia: 'application/vnd.github.v3+json'
@ -23,44 +24,23 @@ module.exports = function() {
config() { return githubConfig; },
initialize(robot, integrationID) {
if (initialized) { return; }
async initialize(robot, integrationID, installationID) {
if (initialized || initializing) { return; }
initializing = true;
githubConfig = loadConfig(robot, './github.yaml')
try {
if (githubConfig == null) {
githubConfig = loadConfig(robot, './github.yaml')
}
await ensureValidToken(robot, integrationID, installationID);
const token = robot.brain.get('github-token');
if (token) {
initialized = true;
process.env.HUBOT_GITHUB_TOKEN = token;
robot.logger.debug("Reused cached GitHub token");
githubAPI.authenticate({ type: 'token', token });
return;
} catch (err) {
// Do nothing
} finally {
initializing = false;
}
const jwtLib = require('jwt-simple');
// Private key contents
let privateKey = process.env.GITHUB_PEM;
const now = Math.round(Date.now() / 1000);
// Generate the JWT
const payload = {
// issued at time
iat: now,
// JWT expiration time (10 minute maximum)
exp: now + (1 * 60),
// GitHub App's identifier
iss: integrationID
};
jwt = jwtLib.encode(payload, privateKey, 'RS256');
githubAPI.authenticate({
type: 'integration',
token: jwt
});
robot.logger.debug("Configured integration authentication with JWT", jwt);
initialized = true;
},
equalsRobotName(robot, str) {
@ -68,6 +48,41 @@ module.exports = function() {
}
};
async function ensureValidToken(robot, integrationID, installationID) {
const token = getToken(robot);
if (token) {
process.env.HUBOT_GITHUB_TOKEN = token;
robot.logger.debug("Reused cached GitHub token");
githubAPI.authenticate({ type: 'token', token });
return;
}
const jwtLib = require('jwt-simple');
// Private key contents
let privateKey = process.env.GITHUB_PEM;
const now = Math.round(Date.now() / 1000);
// Generate the JWT
const payload = {
// issued at time
iat: now,
// JWT expiration time (10 minute maximum)
exp: now + (1 * 60),
// GitHub App's identifier
iss: integrationID
};
jwt = jwtLib.encode(payload, privateKey, 'RS256');
githubAPI.authenticate({
type: 'integration',
token: jwt
});
robot.logger.debug("Configured integration authentication with JWT", jwt);
await createAccessToken(robot, githubAPI, installationID);
}
function getRegexForRobotName(robot) {
// This comes straight out of Hubot's Robot.coffee
// - they didn't get a nice way of extracting that method though
@ -99,4 +114,60 @@ function loadConfig(robot, fileName) {
}
return null;
}
}
function getToken(robot) {
const expiresAt = robot.brain.get('github-token-expires-at');
token = robot.brain.get('github-token');
if (expiresAt) {
expiresAt = Date.parse(expiresAt);
if (Date.now() >= expiresAt - 60 * 1000) {
robot.logger.debug("Cached GitHub token has expired");
token = null; // Token has expired
}
} else {
// If no expiration is set, assume this is an old deployment and invalidate the token
token = null;
}
return token;
}
function expireGitHubToken(robot) {
const gitHubContext = require('./github-context.js')();
robot.brain.set('github-token', null);
process.env.HUBOT_GITHUB_TOKEN = null;
appID = robot.brain.get("github-app_id");
installationID = robot.brain.get("github-installation_id");
if (installationID) {
gitHubContext.ensureValidToken(robot, appID, installationID);
}
}
async function createAccessToken(robot, github, id) {
try {
robot.logger.debug("Creating GitHub access token for installation");
response = await github.apps.createInstallationToken({ installation_id: id });
process.env.HUBOT_GITHUB_TOKEN = response.data.token;
robot.brain.set('github-token', response.data.token);
robot.brain.set('github-token-expires-at', response.data.expires_at);
expiresAt = Date.parse(response.data.expires_at);
setTimeout(expireGitHubToken, (expiresAt - 60 * 1000) - Date.now(), robot);
github.authenticate({
type: 'token',
token: response.data.token
});
robot.logger.debug(`Created GitHub access token for installation, expires at ${response.data.expires_at}`);
} catch (err) {
robot.logger.error(`Couldn't create installation token: ${err}`, id);
throw err;
}
}

View File

@ -28,10 +28,10 @@ module.exports = function(robot) {
// App was installed on an organization
robot.logger.info(`Initializing installation for app with ID ${githubPayload.installation.app_id} and installation ID ${githubPayload.installation.id}`);
robot.brain.set('github-app_install-payload', JSON.stringify(githubPayload));
robot.brain.set('github-app_id', githubPayload.installation.app_id);
robot.brain.set('github-installation_id', githubPayload.installation.id);
gitHubContext.initialize(robot, githubPayload.installation.app_id);
await gitHubContext.initialize(robot, githubPayload.installation.app_id, githubPayload.installation.id);
var perms = githubPayload.installation.permissions;
if (perms.repository_projects !== 'write') { robot.logger.error(formatPermMessage('repository_projects', 'write')); }
@ -43,14 +43,14 @@ module.exports = function(robot) {
robot.logger.error("Please enable 'pull_request' events in the app configuration on github.com");
}
await createAccessToken(robot, gitHubContext.api(), githubPayload.installation.id);
break;
case "deleted":
// App was uninstalled from an organization
robot.logger.info(`Removing installation for app with ID ${githubPayload.installation.app_id} and installation ID ${githubPayload.installation.id}`);
robot.brain.set('github-app_id', null);
robot.brain.set('github-app_install-payload', null);
robot.brain.set('github-installation_id', null);
robot.brain.set('github-token-expires-at', null);
robot.brain.set('github-token', null);
process.env.HUBOT_GITHUB_TOKEN = null;
break;
@ -60,20 +60,4 @@ module.exports = function(robot) {
});
};
async function createAccessToken(robot, github, id) {
try {
response = await github.apps.createInstallationToken({ installation_id: id });
robot.brain.set('github-token', response.data.token);
// TODO: Set Redis expiration date to value from response.data.expires_at
process.env.HUBOT_GITHUB_TOKEN = response.data.token;
github.authenticate({
type: 'token',
token: response.data.token
});
} catch (err) {
robot.logger.error(`Couldn't create installation token: ${err}`, id);
}
}
var formatPermMessage = (permName, perm) => `Please enable '${permName}' ${perm} permission in the app configuration on github.com`;
var formatPermMessage = (permName, perm) => `Please enable '${permName}' ${perm} permission in the app configuration on github.com`;