Validating SNS message in NodeJS, promisified
Validating SNS message in NodeJS, promisified
If you want to subscribe to an SNS topic, it's a good idea to validate if the received request is an actual valid SNS message. AWS has a (rather old) NPM package for validation. To prevent callback hell, I've created a convenience function that validates the message using the provided signature.
The function uses the sns-validator
package under the hood, which:
- Gets the provided certificates, after validating the domain hosting the certificates is AWS
- Hashes the relevant fields, and compares the signature to the provided signature
After this validation, you can be sure that:
- The message wasn't tampered with
- Was provided by AWS SNS
You still don't know if it was provided by the topic you're actually subscribed to/wanting to subscribe to. I'd strongly recommend at least checking the topic ARN against an allowlist before doing something with the payload.
The function expects an SNS message delivered via http, formatted like this:
{
"Type" : "Notification",
"MessageId" : "da41e39f-ea4d-435a-b922-c6aae3915ebe",
"TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
"Subject" : "test",
"Message" : "test message",
"Timestamp" : "2012-04-25T21:49:25.719Z",
"SignatureVersion" : "1",
"Signature" : "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",
"SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
"UnsubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:123456789012:MyTopic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"
}
Besides validating if the message is valid, the function will subscribe to the topic if the provided message is a 'SubscriptionConfirmation' type notification. In all other cases it just returns the original message.
import * as https from 'https';
import MessageValidator from 'sns-validator';
async function validateMessage(message: any): Promise<any> {
const validator = new MessageValidator();
return new Promise((resolve, reject) => {
validator.validate(message, (err, aMessage) => {
if (err) {
return reject(err);
}
if (aMessage?.Type === 'SubscriptionConfirmation' && aMessage.SubscribeURL) {
https.get(aMessage.SubscribeURL, function (_res) {
// You have confirmed your endpoint subscription
});
}
resolve(aMessage);
});
});
}