Skip to content

Instantly share code, notes, and snippets.

@made2591
Last active Jun 27, 2020
Embed
What would you like to do?
AWS Lambda Node.js as slack command handler (with specified grammar)
const AWS = require('aws-sdk');
const Slack = require("slack-node");
AWS.config.region = process.env.REGION;
const lambda = new AWS.Lambda();
// ####################################################
// ################# SECURITY CHECK ###################
// ####################################################
// check if slack token is equal to env var
function checkToken(token) {
return token == process.env.SLACK_TOKEN;
}
// check if slack team id is equal to env var
function checkTeamID(teamID) {
return teamID == process.env.SLACK_TEAM_ID;
}
// check if slack channel id is equal to env var
function checkChannelID(channelID) {
return channelID == process.env.SLACK_CHANNEL_ID;
}
// check if slack user id is equal to env var
function checkUserID(userID) {
return userID == process.env.SLACK_USER_ID;
}
// check if slack command string is equal to env var
function checkSlackBotCommand(command) {
return command == process.env.SLACK_BOT_COMMAND;
}
// error if some security check fail
const errorStandardMessage = { 'text' : 'Some is wrong...you supposts to be slack, but you are not.' };
// ####################################################
// ################### PARSER LIB #####################
// ####################################################
function checkSecurity(event) {
return new Promise(function(resolve, reject) {
if (!checkToken(event.token) ||
!checkTeamID(event.team_id) ||
!checkChannelID(event.channel_id) ||
!checkUserID(event.user_id) ||
!checkSlackBotCommand(event.command)) {
reject(errorStandardMessage);
} else {
resolve(event.text);
}
});
}
// create payload from text slack command
function parseText(text) {
return new Promise(function(resolve, reject) {
if (text) {
// parsed parameters
var payloads = [];
// splits contexts
var contexts = text.split(";").map(function(context) {
return context.trim();
});
// for each context
contexts.forEach(function(contextValue, contextIndex) {
// isolate context
var context = contextValue;
// splits actions
var actions = context.split(",").map(function(actionContent) {
return actionContent.trim();
});
actions.forEach(function(contextContent, contextIndex) {
var action = "";
var parameters = [];
// splits context name and action + parameters
var contentValues = contextContent.split("->").map(function(contentValue) {
return contentValue.trim();
});
// isolate context from actions + parameters
if (contentValues.length > 1) {
// isolate context or action from parameters
if (contextIndex == 0) {
context = contentValues[0];
} else {
action = contentValues[0];
}
var remainingContent = contentValues[1];
// splits action and parameters
var actionParameters = remainingContent.split(" ").map(function(contentValue) {
return contentValue.trim();
});
// isolate context from actions + parameters
if (actionParameters.length > 0) {
// isolate first element as action
action = actionParameters[0];
// isolate remaining element as parameters
if (actionParameters.length > 1) {
parameters = actionParameters.slice(1, actionParameters.length);
}
payloads.push({ context : context, action : action, parameters : parameters });
}
}
});
});
resolve(payloads);
} else {
reject(errorStandardMessage);
}
});
}
// check for integrity with respect to common configuration hosted in S3 bucket
function checkIntegrity(payloads) {
console.log(payloads);
return new Promise(function(resolve, reject) {
// S3 handler
var S3 = new AWS.S3({
maxRetries: 0,
region: process.env.REGION
});
S3.getObject({
Bucket: process.env.S3_BUCKET_NAME,
Key: process.env.S3_BUCKET_OBJECT,
}, function(error, data) {
if (error) {
reject(error);
} else {
// parse context from S3 config file
var contexts = JSON.parse(data.Body.toString('utf-8'));
// payload to remove
var toRemove = [];
// for each passed payload
payloads.forEach(function(payload, index) {
// if context is admitted
if (Object.keys(contexts).includes(payload['context'])) {
// if action is allowed in context
if (!Object.keys(contexts[payload['context']]).includes(payload['action'])) {
toRemove.push(index);
}
} else {
toRemove.push(index);
}
});
// remove invalid payload
toRemove.forEach(function(i) {
payloads.splice(i, 1);
});
resolve(payloads);
}
});
});
}
function invokeLambda(payloads) {
return new Promise(function(resolve, reject){
var params = {
FunctionName: process.env.INTERNAL_LAMBDA_NAME,
InvocationType: 'RequestResponse',
Payload: JSON.stringify(payloads)
};
lambda.invoke(params, function(error, data) {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
function sendMessage(url, data) {
// Return new promise
return new Promise(function(resolve, reject) {
var slack = new Slack();
slack.setWebhook(url);
slack.webhook({
channel: process.env.SLACK_AWS_CHANNEL,
username: process.env.SLACK_AWS_BOT_NAME,
text: data
}, function(error, response) {
if (error) {
console.log(error);
reject(error);
} else {
resolve(response);
}
});
});
}
function publishActionOnSNSTopic(message) {
var sns = new AWS.SNS();
sns.publish({
Message: message,
TopicArn: process.env.VPC_ACTIONS_TOPIC
}, function(err, data) {
if (err) {
console.log(err.stack);
return;
}
console.log('push sent');
console.log(data);
context.done(null, 'Function Finished!');
});
}
// export handler function
exports.handler = (event, context, callback) => {
console.log('0', JSON.stringify(event));
checkSecurity(event).then(function(text) {
sendMessage(event.response_url, 'Security check passed: ok').then(function(slackResponse) {
parseText(text).then(function(payloads) {
console.log('a', JSON.stringify(payloads));
sendMessage(event.response_url, 'Parsed command: '+JSON.stringify(payloads)).then(function(slackResponse) {
checkIntegrity(payloads).then(function(payloadAdmitted) {
console.log('a', JSON.stringify(payloadAdmitted));
sendMessage(event.response_url, 'Admitted command: '+JSON.stringify(payloadAdmitted)).then(function(slackResponse) {
publishActionOnSNSTopic(JSON.stringify(payloadAdmitted));
// invokeLambda(payloadAdmitted).then(function(lambdaResponse) {
// sendMessage(event.response_url, 'Lambda response: '+JSON.stringify(payloadAdmitted)).then(function(slackResponseInternal) {
// console.log('9', slackResponseInternal);
// callback(null, slackResponseInternal);
// }).catch((error) => { console.log('8', error); callback(null, error); });
// }).catch((error) => { console.log('7', error); callback(null, error); });
}).catch((error) => { console.log('6', error); callback(null, error); });
}).catch((error) => { console.log('5', error); callback(null, error); });
}).catch((error) => { console.log('4', error); callback(null, error); });
}).catch((error) => { console.log('3', error); callback(null, error); });
}).catch((error) => { console.log('2', error); callback(null, error); });
}).catch((error) => { console.log('1', error); callback(null, error); });
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment