I am trying to set up a webhook in Xero. I have created an endpoint which Xero hits and send some header and payload. I extract the hash from the header and match with the hash of payload but i never get the same hash. I am using the below code to do that.
router.post('/weebhook', function(req, res, next) {
console.log(req.headers)
console.dir(req.body);
try {
var xero_signature = req.headers['x-xero-signature']
var encoded_data = encodePayload(req.body)
console.log(xero_signature)
console.log(encoded_data)
if (encoded_data == xero_signature) {
res.status(200).json();
} else {
res.status(401).json();
}
}catch(eror) {
console.log(eror)
}
});
function encodePayload(payload) {
console.log(JSON.stringify(payload))
const secret = 'TbJjeMSPAvJiMiD2WdHbjP20iodKCA3bL5is8vo47/pCcuGCsjtUDb7cBnWo20e0TBwZsQ/lPM41QgypzZE6lQ==';
const hash = crypto.createHmac('sha256',secret,true)
.update(JSON.stringify(payload))
.digest().toString('base64');
return hash
}
Xero hash - NzQOq6yw6W6TKs1sQ1AJtMWX24uzzkyvh92fMxukreE=
my hash - L74zFdcuRsK3zHmzu9K37Y1mAVIAIsDgneAPHaJ+vI4=
Please let me know what is the issue ?
There's a typescript sample application provided by Xero that implements the webhooks signature verification.
Does the code in here help you at all? https://github.com/XeroAPI/XeroWebhooksReceiver-Node/blob/master/src/server/server.ts#L58L59
Also, please delete and recreate your webhook as you've just provided everyone with your secret webhooks key.
Change .update(JSON.stringify(payload)) to .update(payload.toString())
Related
I'm attempting to verify a webhook signature from Patreon using Node.js. Here is my code:
const crypto = require("crypto");
...
function validateJsonWebhook(request) {
const secret = SECRET_KEY_GIVEN_BY_PATREON;
const hash = crypto.createHmac("md5", secret)
.update(JSON.stringify(request.body))
.digest("hex");
if (request.header("x-patreon-signature") === hash) {
return true;
} else {
return false;
}
}
Patreon webhooks use MD5 - see https://docs.patreon.com/#webhooks.
I've verified the secret key multiple times so I know that's not the issue.
Both "request.header("x-patreon-signature")" and "hash" are returning the correct format (i.e. they're both a 32 digit letter-number combination) but they're just not matching.
Any idea about what's going on?
So #gaiazov's comment led me to do some Googling which led me to the first two comments on Stripe verify web-hook signature HMAC sha254 HAPI.js by Karl Reid which led me to https://github.com/stripe/stripe-node/issues/331#issuecomment-314917167.
For anyone who finds this in the future: DON'T use JSON.stringify(request.body) - use request.rawBody instead, as the signature is calculated based on the raw JSON. I feel like this should be emphasized in Patreon's documentation, as all the examples I found used the code I originally posted. My new, working code is as follows (I cleaned up the "if (request.header("x-patreon-signature") === hash)" part at the end):
const crypto = require("crypto");
...
function validateJsonWebhook(request) {
// Secret key given by Patreon.
const secret = patreonSecret;
const hash = crypto.createHmac("md5", secret)
.update(request.rawBody)
.digest("hex");
return (request.header("x-patreon-signature") === hash);
}
I need to validate Xero webhook in my node js project. This is Xero documentation steps to validate: https://developer.xero.com/documentation/webhooks/creating-webhooks#STATUS
var crypto = require("crypto")
function getHmacSha256(message, secret) {
return crypto.createHmac("sha256", secret).update(message).digest("base64")
}
// webhookPayload and signature get from webhook body and header
const webhookPayload = {
events: [],
firstEventSequence: 0,
lastEventSequence: 0,
entropy: 'OSHPXTUSXASRFBBCJFEN'
}
const signature = "OXLaeyZanKI5QDnLkXIVB35XrZygYsPMeK8WfoXUMU8="
const myKey = "1y5VYfv7WbimUQIMXiQCB6W6TKIp+5ZZJNjn3Fsa/veK5X/C8BZ4yzvPkmr7LvuL+yfKwm4imnfAB5tEoJfc4A=="
var hash = getHmacSha256(JSON.stringify(webhookPayload), myKey)
//If the payload is hashed using HMACSHA256 with your webhook signing key and base64 encoded, it should match the signature in the header.
if (signature === hash) {
return res.status(200).end()
}else{
return res.status(401).end()
}
Every time my signature and hash are different so it returns with 401 every time.
So I failed to complete Intent to receive
From what you're describing, my guess is you are unintentionally modifying the request body. You need to accept the raw request body from the webhook event without modification. If this body is modified at all, your code will fail to verify the signature and will fail Xero’s “Intent to receive” validation. Check out this blog post for details.
I am using the Xero Api with Nodejs and the xero-node library.
I have completed the oAuth flow and saved the token to the database. The issue i am now having is continually getting a 403 forbidden error when attempting to get anything from Xero be that Contacts, Accounts or Users. Sample code is below
I can get tenants ok without an issue however anything else doesn't work. I have checked the scopes to make sure when I am setting up the client they are correct which they are.
var getStuff = async(tokenSet) => {
await xero.setTokenSet(tokenSet);
const tenants = await xero.updateTenants();
const xeroTenantId = tenants[0].id // {String} Xero identifier for Tenant
const ifModifiedSince = new Date("2020-02-06T12:17:43.202-08:00");
const where = 'IsSubscriber==true'; // {String} Filter by an any element
const order = 'LastName ASC'; // {String} Order by an any element
console.log(tenants);
try {
const response = await xero.accountingApi.getUsers(xeroTenantId, ifModifiedSince, where, order);
console.log(response.body || response.response.statusCode)
}
catch (err) {
/// console.log(err);
console.log(`There was an ERROR! \n Status Code: ${err.response.statusCode}.`);
console.log(`ERROR: \n ${JSON.stringify(err.response.body, null, 2)}`);
}
}
Which scopes have been added to the access token you are passing through? You can decode your token here https://jwt.io/
Also - you need to pass the ‘tenant.tenantId’ to the function. I believe the tenant.id actually relates to the connection id which is coming back from the /connections endpoint.
My hunch is that is the issue. Also curious how that’s working, as updateTenants() should have the empty function call on the end. Is that working for you?
Im making use of the following node library azure-keyvault to get retrieve stored secrets from azure keyvault. Ive only found the client.getSecret api exposed to retrieve a secret value. Im searching for a way to retrieve multiple secret values in one call. I hav'nt found one yet. Is there a way to do this that i'm missing or its simply not supported.
const { SecretClient } = require('#azure/keyvault-secrets')
const client = new SecretClient(
`https://${KEYVAULT_NAME}.vault.azure.net`,
new DefaultAzureCredential()
)
const [secret1, secret2] = await Promise.all([
client.getSecret(`secret1`),
client.getSecret(`secret2`)
])
Here is the complete code for getting the multiple client secret at once:
var credentials = new KeyVault.KeyVaultCredentials(authenticator);
var client = new KeyVault.KeyVaultClient(credentials);
client.setSecret(vaultUri, 'mysecret', 'my password', options, function (err, secretBundle) {
// List all secrets
var parsedId = KeyVault.parseSecretIdentifier(secretBundle.id);
client.getSecrets(parsedId.vault, parsedId.name, function (err, result) {
if (err) throw err;
var loop = function (nextLink) {
if (nextLink !== null && nextLink !== undefined) {
client.getSecretsNext(nextLink, function (err, res) {
console.log(res);
loop(res.nextLink);
});
}
};
console.log(result);
loop(result.nextLink);
});
});
You can find the complete reference for azure key vault using node js below:
http://azure.github.io/azure-sdk-for-node/azure-keyvault/latest/KeyVaultClient.html#getSecrets
http://azure.github.io/azure-sdk-for-node/azure-keyvault/latest/
Hope it helps.
You can use read-azure-secrets npm package which will return all secrets to you.
E.g.
const secretClient = require('read-azure-secrets');
async function loadKeyVaultValues() {
let applicationID = '';
let applicationSecret = '';
let vaultURL = 'https://<your-key-vault-name>.vault.azure.net/';
let secrets = await secretClient.getSecrets(applicationID, applicationSecret, vaultURL);
secrets.forEach(secret => {
console.log(secret);
});
}
loadKeyVaultValues();
You can try using client.getSecrets(..) method exposed by the REST Api.
Kindly go through the following useful blog, in which all methods have been implemented.
LINK: https://www.red-gate.com/simple-talk/cloud/platform-as-a-service/using-azure-keyvault-with-node-js/
You haven't specified what information about the secret you want to fetch so I am going to assume that you are looking for the secret's value. I am also going to assume you are looking to minimize network traffic for fetching multiple secrets (either for costs or for performance).
Looking at the Azure REST API documentation while there is a route to list multiple secrets it only provides the secret identifier and metadata about the secret (attributes, tags, etc). So if you want to get the secret's value (the actual secret) you will need to make individual calls although get-secrets route can be used to find all the secrets stored in the Key Vault.
As far as the client library, #azure/keyvault-secrets maps pretty closely to the REST API it supports so it will not provide a method that fetches multiple secrets. Even if it did, it would just be a facade over multiple network calls so it would not help reduce the number of network trips.
So to answer your question - it does not look possible today unless all you want is metadata about the secret and not the secret value itself.
I've built out an integration using DocuSign's Node SDK. While testing using a DocuSign sandbox account, the authentication flow works just fine using the example in the docs.
I'm now trying to do the same within a live DocuSign production account using the Integrator Key that was promoted from the sandbox account. authApi.login() seems to work just fine, I get no error and the status code of the response is 200. However, the value of loginInfo comes back as exports {} with no account info included.
I've made sure to change the base path from https://demo.docusign.net/restapi to www.docusign.net/restapi and as far as I can tell from the docs, there doesn't seem to be anything else I need to make the switch to production. Here is the code I am using:
apiClient.setBasePath('www.docusign.net/restapi');
apiClient.addDefaultHeader('Authorization', 'Bearer ' + token);
docusign.Configuration.default.setDefaultApiClient(apiClient);
const authApi = new docusign.AuthenticationApi();
const loginOps = {
apiPassword: true,
includeAccountIdGuid: true
};
authApi.login(loginOps, function (err, loginInfo, response) {
if (err) {
console.log(err);
}
if (loginInfo) {
// loginInfo returns 'exports {}' so the variables below cannot be set.
const loginAccounts = loginInfo.loginAccounts;
const loginAccount = loginAccounts[0];
const baseUrl = loginAccount.baseUrl;
const accountDomain = baseUrl.split('/v2');
const accountId = loginAccount.accountId;
apiClient.setBasePath(accountDomain[0]);
docusign.Configuration.default.setDefaultApiClient(apiClient);
www.docusign.net endpoint will only work if your PROD account is in NA1, if your PROD Account is in NA2, then you need to use na2.docusign.net and if it is in NA3 then na3.docusign.net. This is the main reason you should use /oauth/userinfo call with OAUTH2 Access Token to know your base URL, and then call all APIs with this baseURL. You can find more details at https://docs.docusign.com/esign/guide/authentication/userinfo.html