Complete Email Sortierer implementation with Appwrite and Stripe integration
This commit is contained in:
196
server/node_modules/stripe/esm/Webhooks.js
generated
vendored
Normal file
196
server/node_modules/stripe/esm/Webhooks.js
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
import { StripeError, StripeSignatureVerificationError } from './Error.js';
|
||||
import { CryptoProviderOnlySupportsAsyncError, } from './crypto/CryptoProvider.js';
|
||||
export function createWebhooks(platformFunctions) {
|
||||
const Webhook = {
|
||||
DEFAULT_TOLERANCE: 300,
|
||||
// @ts-ignore
|
||||
signature: null,
|
||||
constructEvent(payload, header, secret, tolerance, cryptoProvider, receivedAt) {
|
||||
try {
|
||||
this.signature.verifyHeader(payload, header, secret, tolerance || Webhook.DEFAULT_TOLERANCE, cryptoProvider, receivedAt);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof CryptoProviderOnlySupportsAsyncError) {
|
||||
e.message +=
|
||||
'\nUse `await constructEventAsync(...)` instead of `constructEvent(...)`';
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
const jsonPayload = payload instanceof Uint8Array
|
||||
? JSON.parse(new TextDecoder('utf8').decode(payload))
|
||||
: JSON.parse(payload);
|
||||
return jsonPayload;
|
||||
},
|
||||
async constructEventAsync(payload, header, secret, tolerance, cryptoProvider, receivedAt) {
|
||||
await this.signature.verifyHeaderAsync(payload, header, secret, tolerance || Webhook.DEFAULT_TOLERANCE, cryptoProvider, receivedAt);
|
||||
const jsonPayload = payload instanceof Uint8Array
|
||||
? JSON.parse(new TextDecoder('utf8').decode(payload))
|
||||
: 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 || getCryptoProvider();
|
||||
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, receivedAt) {
|
||||
const { decodedHeader: header, decodedPayload: payload, details, suspectPayloadType, } = parseEventDetails(encodedPayload, encodedHeader, this.EXPECTED_SCHEME);
|
||||
const secretContainsWhitespace = /\s/.test(secret);
|
||||
cryptoProvider = cryptoProvider || getCryptoProvider();
|
||||
const expectedSignature = cryptoProvider.computeHMACSignature(makeHMACContent(payload, details), secret);
|
||||
validateComputedSignature(payload, header, details, expectedSignature, tolerance, suspectPayloadType, secretContainsWhitespace, receivedAt);
|
||||
return true;
|
||||
},
|
||||
async verifyHeaderAsync(encodedPayload, encodedHeader, secret, tolerance, cryptoProvider, receivedAt) {
|
||||
const { decodedHeader: header, decodedPayload: payload, details, suspectPayloadType, } = parseEventDetails(encodedPayload, encodedHeader, this.EXPECTED_SCHEME);
|
||||
const secretContainsWhitespace = /\s/.test(secret);
|
||||
cryptoProvider = cryptoProvider || getCryptoProvider();
|
||||
const expectedSignature = await cryptoProvider.computeHMACSignatureAsync(makeHMACContent(payload, details), secret);
|
||||
return validateComputedSignature(payload, header, details, expectedSignature, tolerance, suspectPayloadType, secretContainsWhitespace, receivedAt);
|
||||
},
|
||||
};
|
||||
function makeHMACContent(payload, details) {
|
||||
return `${details.timestamp}.${payload}`;
|
||||
}
|
||||
function parseEventDetails(encodedPayload, encodedHeader, expectedScheme) {
|
||||
if (!encodedPayload) {
|
||||
throw new StripeSignatureVerificationError(encodedHeader, encodedPayload, {
|
||||
message: 'No webhook payload was provided.',
|
||||
});
|
||||
}
|
||||
const suspectPayloadType = typeof encodedPayload != 'string' &&
|
||||
!(encodedPayload instanceof Uint8Array);
|
||||
const textDecoder = new TextDecoder('utf8');
|
||||
const decodedPayload = encodedPayload instanceof Uint8Array
|
||||
? textDecoder.decode(encodedPayload)
|
||||
: 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.');
|
||||
}
|
||||
if (encodedHeader == null || encodedHeader == '') {
|
||||
throw new StripeSignatureVerificationError(encodedHeader, encodedPayload, {
|
||||
message: 'No stripe-signature header value was provided.',
|
||||
});
|
||||
}
|
||||
const decodedHeader = encodedHeader instanceof Uint8Array
|
||||
? textDecoder.decode(encodedHeader)
|
||||
: encodedHeader;
|
||||
const details = parseHeader(decodedHeader, expectedScheme);
|
||||
if (!details || details.timestamp === -1) {
|
||||
throw new StripeSignatureVerificationError(decodedHeader, decodedPayload, {
|
||||
message: 'Unable to extract timestamp and signatures from header',
|
||||
});
|
||||
}
|
||||
if (!details.signatures.length) {
|
||||
throw new StripeSignatureVerificationError(decodedHeader, decodedPayload, {
|
||||
message: 'No signatures found with expected scheme',
|
||||
});
|
||||
}
|
||||
return {
|
||||
decodedPayload,
|
||||
decodedHeader,
|
||||
details,
|
||||
suspectPayloadType,
|
||||
};
|
||||
}
|
||||
function validateComputedSignature(payload, header, details, expectedSignature, tolerance, suspectPayloadType, secretContainsWhitespace, receivedAt) {
|
||||
const signatureFound = !!details.signatures.filter(platformFunctions.secureCompare.bind(platformFunctions, expectedSignature)).length;
|
||||
const docsLocation = '\nLearn more about webhook signing and explore webhook integration examples for various frameworks at ' +
|
||||
'https://github.com/stripe/stripe-node#webhook-signing';
|
||||
const whitespaceMessage = secretContainsWhitespace
|
||||
? '\n\nNote: The provided signing secret contains whitespace. This often indicates an extra newline or space is in the value'
|
||||
: '';
|
||||
if (!signatureFound) {
|
||||
if (suspectPayloadType) {
|
||||
throw new StripeSignatureVerificationError(header, payload, {
|
||||
message: 'Webhook payload must be provided as a string or a Buffer (https://nodejs.org/api/buffer.html) instance representing the _raw_ request body.' +
|
||||
'Payload was provided as a parsed JavaScript object instead. \n' +
|
||||
'Signature verification is impossible without access to the original signed material. \n' +
|
||||
docsLocation +
|
||||
'\n' +
|
||||
whitespaceMessage,
|
||||
});
|
||||
}
|
||||
throw new StripeSignatureVerificationError(header, payload, {
|
||||
message: 'No signatures found matching the expected signature for payload.' +
|
||||
' Are you passing the raw request body you received from Stripe? \n' +
|
||||
' If a webhook request is being forwarded by a third-party tool,' +
|
||||
' ensure that the exact request body, including JSON formatting and new line style, is preserved.\n' +
|
||||
docsLocation +
|
||||
'\n' +
|
||||
whitespaceMessage,
|
||||
});
|
||||
}
|
||||
const timestampAge = Math.floor((typeof receivedAt === 'number' ? receivedAt : Date.now()) / 1000) - details.timestamp;
|
||||
if (tolerance > 0 && timestampAge > tolerance) {
|
||||
// @ts-ignore
|
||||
throw new StripeSignatureVerificationError(header, payload, {
|
||||
message: 'Timestamp outside the tolerance zone',
|
||||
});
|
||||
}
|
||||
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 = parseInt(kv[1], 10);
|
||||
}
|
||||
if (kv[0] === scheme) {
|
||||
accum.signatures.push(kv[1]);
|
||||
}
|
||||
return accum;
|
||||
}, {
|
||||
timestamp: -1,
|
||||
signatures: [],
|
||||
});
|
||||
}
|
||||
let webhooksCryptoProviderInstance = null;
|
||||
/**
|
||||
* Lazily instantiate a CryptoProvider instance. This is a stateless object
|
||||
* so a singleton can be used here.
|
||||
*/
|
||||
function getCryptoProvider() {
|
||||
if (!webhooksCryptoProviderInstance) {
|
||||
webhooksCryptoProviderInstance = platformFunctions.createDefaultCryptoProvider();
|
||||
}
|
||||
return webhooksCryptoProviderInstance;
|
||||
}
|
||||
Webhook.signature = signature;
|
||||
return Webhook;
|
||||
}
|
||||
Reference in New Issue
Block a user