Take access token expiration into account
This commit is contained in:
parent
490c7f38b5
commit
52a626d645
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`;
|
||||
|
|
Loading…
Reference in New Issue