rittenhop-ghost/versions/5.94.2/node_modules/stripe/lib/Webhooks.js

278 lines
6.8 KiB
JavaScript

'use strict';
const utils = require('./utils');
const {StripeError, StripeSignatureVerificationError} = require('./Error');
const Webhook = {
DEFAULT_TOLERANCE: 300, // 5 minutes
constructEvent(payload, header, secret, tolerance, cryptoProvider) {
this.signature.verifyHeader(
payload,
header,
secret,
tolerance || Webhook.DEFAULT_TOLERANCE,
cryptoProvider
);
const jsonPayload = JSON.parse(payload);
return jsonPayload;
},
async constructEventAsync(
payload,
header,
secret,
tolerance,
cryptoProvider
) {
await this.signature.verifyHeaderAsync(
payload,
header,
secret,
tolerance || Webhook.DEFAULT_TOLERANCE,
cryptoProvider
);
const jsonPayload = JSON.parse(payload);
return jsonPayload;
},
/**
* Generates a header to be used for webhook mocking
*
* @typedef {object} opts
* @property {number} timestamp - Timestamp of the header. Defaults to Date.now()
* @property {string} payload - JSON stringified payload object, containing the 'id' and 'object' parameters
* @property {string} secret - Stripe webhook secret 'whsec_...'
* @property {string} scheme - Version of API to hit. Defaults to 'v1'.
* @property {string} signature - Computed webhook signature
* @property {CryptoProvider} cryptoProvider - Crypto provider to use for computing the signature if none was provided. Defaults to NodeCryptoProvider.
*/
generateTestHeaderString: function(opts) {
if (!opts) {
throw new StripeError({
message: 'Options are required',
});
}
opts.timestamp =
Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000);
opts.scheme = opts.scheme || signature.EXPECTED_SCHEME;
opts.cryptoProvider = opts.cryptoProvider || getNodeCryptoProvider();
opts.signature =
opts.signature ||
opts.cryptoProvider.computeHMACSignature(
opts.timestamp + '.' + opts.payload,
opts.secret
);
const generatedHeader = [
't=' + opts.timestamp,
opts.scheme + '=' + opts.signature,
].join(',');
return generatedHeader;
},
};
const signature = {
EXPECTED_SCHEME: 'v1',
verifyHeader(
encodedPayload,
encodedHeader,
secret,
tolerance,
cryptoProvider
) {
const {
decodedHeader: header,
decodedPayload: payload,
details,
} = parseEventDetails(encodedPayload, encodedHeader, this.EXPECTED_SCHEME);
cryptoProvider = cryptoProvider || getNodeCryptoProvider();
const expectedSignature = cryptoProvider.computeHMACSignature(
makeHMACContent(payload, details),
secret
);
validateComputedSignature(
payload,
header,
details,
expectedSignature,
tolerance
);
return true;
},
async verifyHeaderAsync(
encodedPayload,
encodedHeader,
secret,
tolerance,
cryptoProvider
) {
const {
decodedHeader: header,
decodedPayload: payload,
details,
} = parseEventDetails(encodedPayload, encodedHeader, this.EXPECTED_SCHEME);
cryptoProvider = cryptoProvider || getNodeCryptoProvider();
const expectedSignature = await cryptoProvider.computeHMACSignatureAsync(
makeHMACContent(payload, details),
secret
);
return validateComputedSignature(
payload,
header,
details,
expectedSignature,
tolerance
);
},
};
function makeHMACContent(payload, details) {
return `${details.timestamp}.${payload}`;
}
function parseEventDetails(encodedPayload, encodedHeader, expectedScheme) {
const decodedPayload = Buffer.isBuffer(encodedPayload)
? encodedPayload.toString('utf8')
: encodedPayload;
// Express's type for `Request#headers` is `string | []string`
// which is because the `set-cookie` header is an array,
// but no other headers are an array (docs: https://nodejs.org/api/http.html#http_message_headers)
// (Express's Request class is an extension of http.IncomingMessage, and doesn't appear to be relevantly modified: https://github.com/expressjs/express/blob/master/lib/request.js#L31)
if (Array.isArray(encodedHeader)) {
throw new Error(
'Unexpected: An array was passed as a header, which should not be possible for the stripe-signature header.'
);
}
const decodedHeader = Buffer.isBuffer(encodedHeader)
? encodedHeader.toString('utf8')
: encodedHeader;
const details = parseHeader(decodedHeader, expectedScheme);
if (!details || details.timestamp === -1) {
throw new StripeSignatureVerificationError({
message: 'Unable to extract timestamp and signatures from header',
detail: {
decodedHeader,
decodedPayload,
},
});
}
if (!details.signatures.length) {
throw new StripeSignatureVerificationError({
message: 'No signatures found with expected scheme',
detail: {
decodedHeader,
decodedPayload,
},
});
}
return {
decodedPayload,
decodedHeader,
details,
};
}
function validateComputedSignature(
payload,
header,
details,
expectedSignature,
tolerance
) {
const signatureFound = !!details.signatures.filter(
utils.secureCompare.bind(utils, expectedSignature)
).length;
if (!signatureFound) {
throw new StripeSignatureVerificationError({
message:
'No signatures found matching the expected signature for payload.' +
' Are you passing the raw request body you received from Stripe?' +
' https://github.com/stripe/stripe-node#webhook-signing',
detail: {
header,
payload,
},
});
}
const timestampAge = Math.floor(Date.now() / 1000) - details.timestamp;
if (tolerance > 0 && timestampAge > tolerance) {
throw new StripeSignatureVerificationError({
message: 'Timestamp outside the tolerance zone',
detail: {
header,
payload,
},
});
}
return true;
}
function parseHeader(header, scheme) {
if (typeof header !== 'string') {
return null;
}
return header.split(',').reduce(
(accum, item) => {
const kv = item.split('=');
if (kv[0] === 't') {
accum.timestamp = kv[1];
}
if (kv[0] === scheme) {
accum.signatures.push(kv[1]);
}
return accum;
},
{
timestamp: -1,
signatures: [],
}
);
}
let webhooksNodeCryptoProviderInstance = null;
/**
* Lazily instantiate a NodeCryptoProvider instance. This is a stateless object
* so a singleton can be used here.
*/
function getNodeCryptoProvider() {
if (!webhooksNodeCryptoProviderInstance) {
const NodeCryptoProvider = require('./crypto/NodeCryptoProvider');
webhooksNodeCryptoProviderInstance = new NodeCryptoProvider();
}
return webhooksNodeCryptoProviderInstance;
}
Webhook.signature = signature;
module.exports = Webhook;