How to encode a request body using HMAC sha 256 and base64.
The request object that i receives from xero webhook.
HEADER:
"x-xero-signature" : HASH_VALUE
PAYLOAD:
{
"events": [],
"lastEventSequence": 0,
"firstEventSequence": 0,
"entropy": "S0m3r4Nd0mt3xt"
}
The note from xero documentation says "If the payload is hashed using HMACSHA256 with your webhook signing key and base64 encoded, it should match the signature in the header. This is a correctly signed payload. If the signature does not match the hashed payload it is an incorrectly signed payload."
And I followed this example : https://devblog.xero.com/using-xero-webhooks-with-node-express-hapi-examples-7c607b423379
const express = require("express");
const router = express.Router();
const base64 = require('base-64');
const crypto = require('crypto')
const bodyParser = require('body-parser')
const xero_webhook_key = '00fRRlJBYiYN4ZGjmTtG+g/pulyb1Eru68YYL3PFoLsa78dadfQtGrOMuISuVBHxpXeEYo0Yy1Gc+hHMhDkSI/EEcgtrA==';
let options = {
type: 'application/json'
};
let itrBodyParser = bodyParser.raw(options);
router.post("/", itrBodyParser, async (req, res, next) =>{
// console.log('::::WebhookPost:::');
const reSign = req.headers['x-xero-signature'];
console.log(req.headers['x-xero-signature']);
console.log('::::::::');
console.log(req.body);
console.log("Body: "+JSON.stringify(req.body))
console.log(req.body.toString());
console.log("Xero Signature: "+ reSign);
console.log('Server key::::',xero_webhook_key);
// Create our HMAC hash of the body, using our webhooks key
let hmac = crypto.createHmac("sha256", xero_webhook_key).update(req.body.toString()).digest('base64');
console.log("Resp Signature: ",hmac)
if (req.headers['x-xero-signature'] == hmac) {
res.statusCode = 200
} else {
res.statusCode = 401
}
console.log("Response Code: "+res.statusCode)
return res.send();
});
Hey I recently did a video on implementing webhooks with Xero, let me know if this gets you unstuck. I found that trying to pass itrBodyParser on the route the way you have wasn't working for me so I switched it with an app.use statement on my specific webhooks endpoint. If you prefer a written guide over video, here's the blog post
I solved it using this solution.! I was using express framework and the request were not getting as raw request also .toString of didn't worked as mentioned in xero documentation.
const server = http.createServer(async (req, resp) => {
try {
console.log(`::::Webhook::: ${webhookPort}`);
console.log("::::x-xero-signature:::");
console.log(req.headers["x-xero-signature"]);
console.log(`--------------------------------------`);
if (req.method === "POST") {
if(req.headers["x-xero-signature"]){
const rData = await new Promise((resolve, reject) => {
return collectRequestData(req, (result) => {
console.log(result);
let hmac = crypto
.createHmac("sha256", xero_webhook_key)
.update(result)
.digest("base64");
console.log("Resp Signature: ", hmac);
return resolve({
hmac,result
});
});
});
console.log(">>Resp Signature: ", rData);
console.log('>>x-xero-signature:::',req.headers["x-xero-signature"]);
if(rData.result){
const result = JSON.parse(rData.result);
console.log('result:::',result);
for(let { resourceId } of result.events) {
console.log('::INVOICE ID = ',resourceId);
getInvoiceData(resourceId);
}
}
if(rData.hmac == req.headers["x-xero-signature"] ){
console.log('::YES');
resp.statusCode = 200;
}else{
console.log('::NO');
resp.statusCode = 401;
}
}
resp.end();
}
console.log("::::Webhookgetsssss:::");
resp.message = 'Get API'
resp.end();
} catch (error) {
resp.statusCode = 200;
resp.end();
}
});
server.listen(webhookPort);
function collectRequestData(request, callback) {
let body = "";
request.on("data", (chunk) => {
body += chunk.toString();
});
request.on("end", () => {
callback(body);
});
}
Related
like the title says, here is my server file, I have tried every solution I could find on google yet I am still getting CORS errors. specifically XHROPTIONShttps://slug-panel-api.onrender.com/login
server.js:
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser')
const mongoose = require('mongoose')
const userSchema = require('../../SlugAPI/Schemas/SlugSchemas')
const divisionSchema = require('../src/SlugSchemas/DivisionSchemas/DivisionSchema')
const subDivisionSchema = require('../src/SlugSchemas/DivisionSchemas/SubDivisionSchema')
const teamSchema = require('../src/SlugSchemas/DivisionSchemas/TeamSchema')
const divisionMemberSchema = require('../src/SlugSchemas/DivisionSchemas/DivisionMemberSchema')
let CryptoJS = require('crypto-js')
const PORT = process.env.PORT || 8080;
const app = express();
app.use(cors({
origin: "https://slug-panel.onrender.com",
headers: {
"Access-Control-Allow-Origin": "https://slug-panel.onrender.com",
"Access-Control-Allow-Credentials": true
},
}));
mongoose.set("debug")
const usar_db = mongoose.createConnection("mongodb:/<username>:<password>#slug-db:27017/usarAdmin?authSource=admin");
const User = usar_db.model('User', userSchema)
const Division = usar_db.model('Division', divisionSchema)
const SubDivision = usar_db.model('SubDivision', subDivisionSchema)
const Team = usar_db.model('Team', teamSchema)
const DivisionMember = usar_db.model('Division_Member', divisionMemberSchema)
function generateUserRegistrationKey(username, discord_id, rank, authentication_level) {
let key = username + '/' + discord_id.toString() + '/' + rank + '/' + authentication_level
const ciphertext = CryptoJS.AES.encrypt(key, 'secret').toString()
return ciphertext
}
function decryptUserRegistrationKey(key) {
const bytes = CryptoJS.AES.decrypt(key, 'secret')
const originalText = bytes.toString(CryptoJS.enc.Utf8)
return originalText
}
app.post('/login', bodyParser.json(), async (req, res, next) => {
const user = req.body.username
let pw = req.body.password
pw = CryptoJS.SHA256(pw)
let exists = await User.findOne({username: user})
if (exists) {
if (pw.toString() === exists['password']) {
res.send({
token: 'test123'
})
} else {
res.send({
error: 'passwordNotFound'
})
}
} else {
res.send({
error: 'userNotFound'
})
}
});
app.post('/generate', bodyParser.json(), async function (req, res, next) {
let username = req.body.username
let discord_id = req.body.discord_id
let rank = req.body.rank
let authentication_level = req.body.authentication_level
let exists = await User.exists({discord_id: discord_id})
let regKey = generateUserRegistrationKey(username, discord_id, rank, authentication_level)
const newUser = User({
username: username,
discord_id: discord_id,
rank: rank,
regKey: regKey,
authentication_level: authentication_level,
})
if (!exists) {
newUser.save()
.then(r => console.log("User " + username + " added to db"))
res.send({regKey: regKey})
}
})
app.post('/register', bodyParser.json(), async function (req, res, next) {
let key = req.body.regKey
let pw = CryptoJS.SHA256(req.body.password).toString()
let decryptedKey = decryptUserRegistrationKey(key).split('/')
let exists = await User.find({regKey: key}, function(err, docs) {
if (err) {
console.log(err)
} else {
console.log('Result: ', docs)
console.log(pw)
}
}).clone()
if (!exists) {
res.send({user: null})
} else {
res.send(JSON.stringify(exists))
}
await User.findOneAndUpdate({regKey: key}, { is_registered: true, password: pw, authentication_level: decryptedKey[decryptedKey.length - 1]})
})
app.post('/createDivision', bodyParser.json(), async function (req, res, next) {
let div_name = req.body.division_name
let div_id = req.body.division_id
let exists = await Division.findOne({division_name: div_name}, function (err, docs) {
if (err) {
console.log(err)
} else {
console.log(docs)
}
}).clone()
let idexists = await Division.findOne({division_id: div_id}, function (err, docs) {
if (err) {
console.log(err)
} else {
console.log(docs)
}
}).clone()
if (!exists || !idexists) {
const newDivision = new Division({
division_name: div_name,
division_id: div_id
})
newDivision.save()
.then(() => console.log('Division ' + div_name + ' has been added to the db'))
res.send(JSON.stringify(newDivision))
} else {
res.send({errorcode: 420})
}
})
app.post('/createSubDivision/:divid', bodyParser.json(), async function (req, res, next) {
const division = req.params['divid']
const sub_name = req.body.subdivision_name
const sub_id = req.body.subdivision_id
let exists = await Division.findOne({division_id: division}, function (err, docs) {
if (err) {
console.log(err)
} else {
console.log(docs)
}
}).clone()
if (exists) {
let subdivid_exists = await Division.findOne({
division_id: division,
subdivisions: {
$elemMatch: {subdivision_id: sub_id}
}
})
let subdiv_exists = await Division.findOne({
division_id: division,
subdivisions: {
$elemMatch: {subdivision_name: sub_name}
}
})
if (!subdivid_exists || !subdiv_exists) {
const subDiv = new SubDivision({
subdivision_name: sub_name,
subdivision_id: sub_id,
})
await Division.findOneAndUpdate({division_id: division}, { $push: {subdivisions: subDiv}})
console.log('subdivision ' + sub_name + ' added to: ' + exists.division_name)
res.send(JSON.stringify(subDiv))
} else {
res.send({division:'exists'})
}
}
})
app.listen(PORT, () => console.log('API is running on ' + PORT));
Tried every solution I could find both on random google websites and on stackoverflow. as stated previously it worked fine on the development server hosted locally.
for reference, here is how I am using fetch throughout the frontend
async function loginUser(credentials) {
return fetch('https://slugga-api.onrender.com/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'true'
},
body: JSON.stringify(credentials)
})
.then(data => data.json())
}
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://slugga-api.onrender.com/login. (Reason: CORS request did not succeed). Status code: (null).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://slugga-api.onrender.com/login. (Reason: CORS request did not succeed). Status code: (null).
Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource. asyncToGenerator.js:6:4
Babel 6
c Login.js:20
React 11
bind_applyFunctionN self-hosted:1683
Wt self-hosted:1640
React 3
forEach self-hosted:4909
React 2
<anonymous> index.js:7
<anonymous> index.js:17
<anonymous> index.js:17
You're misusing those CORS headers, both on the client side and on the server side. You should familiarise better with CORS and with the API of Express.js's CORS middleware.
Client side
The Access-Control-Allow-Origin header is a response header; including it in a request makes no sense.
async function loginUser(credentials) {
return fetch('https://slugga-api.onrender.com/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'true' // incorrect
},
body: JSON.stringify(credentials)
})
.then(data => data.json())
}
Server side
The headers property of your CORS config corresponds to request headers that you wish to allow, but what you've listed are response headers.
app.use(cors({
origin: "https://slug-panel.onrender.com",
headers: {
"Access-Control-Allow-Origin": "https://slug-panel.onrender.com", // incorrect
"Access-Control-Allow-Credentials": true // incorrect
},
}));
Instead, you most likely want something like
app.use(cors({
origin: "https://slug-panel.onrender.com",
headers: ["Content-Type"],
credentials: true,
}));
Fixed the issue by changing the CORS initialization in server.js
const app = express();
app.use(cors({
origin: "https://slug-panel.onrender.com"
}
))
app.options('*', cors())
Here is how my API works:
You can find SeaweedFS here on GitHub.
And the code here:
// /drivers/seaweedfs.js Defines how API interacts with SeaweedFS
const { error } = require("console");
const http = require("http");
module.exports = class Weed {
constructor(mserver) {
this.mserver = new URL(mserver);
}
get(fileId) {
return new Promise((resolve, reject) => {
let options = {
hostname: this.mserver.hostname,
port: this.mserver.port,
method: "GET",
path: `/${fileId}`,
timeout: 6000,
};
let data;
const fileReq = http.request(options, (res) => {
console.log(`Statuscode ${res.statusCode}`);
res.on("data", (response) => {
data += response;
});
res.on("end", () => {
resolve(data);
});
});
fileReq.on("error", () => {
console.error(error);
reject();
});
fileReq.end();
});
}
};
// /routes/file.js An Express router
const express = require("express");
const router = express.Router();
const Weed = require("../drivers/seaweedfs");
let weedClient = new Weed("http://localhost:60002");
router.get("/:fileId", (req, res) => {
weedClient.get(req.params.fileId)
.then(data=>{
res.write(data)
res.end()
})
}
)
module.exports = router;
MongoDB driver not yet implemented.
When I try to GET a file(using Firefox, Hoppscotch says Could not send request: Unable to reach the API endpoint. Check your network connection and try again.), I get something whose MIME type is application/octet-stream for some reason. It's bigger than the original file. I know there must be some problems with my code, but I don't know where and how to fix it.
I would like to create a UI which is somehow similar to Telerik's Fiddler. For this purpose, I created a proxy with node-http-proxy which works perfectly :-)
But now I have the problem of how I match the response to my request and I have absolutely no idea what I could try.
Here is my current code:
let httpProxy = require('http-proxy');
let connect = require('connect');
let bodyParser = require('body-parser');
let http = require('http');
let proxy = httpProxy.createProxyServer();
let app = connect()
.use(bodyParser.json()) //json parser
.use((req, res) => {
let options = {
target: "myTargetHere"
};
proxy.web(req, res, options);
});
http.createServer(app).listen(8000);
proxy.on("proxyReq", (proxyReq, req, res) => {
if (req.body) {
let bodyData = JSON.stringify(req.body);
let bodyLength = Buffer.byteLength(bodyData)
proxyReq.setHeader("Content-Length", bodyLength);
proxyReq.write(bodyData);
}
});
proxy.on('proxyRes', (proxyRes, req, res) => {
let body = new Buffer.alloc(0);
let bodyLength = 0;
proxyRes.on('data' , data => {
process.stdout.write("c ");
body = Buffer.concat([body, data]);
});
proxyRes.on("end", () => {
bodyLength = Buffer.byteLength(body);
body = body.toString();
console.log("response body length: ", bodyLength);
});
});
Does anyone have an idea how I can match the response in proxy.on('proxyRes', ...) with my request in proxy.on('proxyReq', ...)? Is there something that is unique within every request/response?
const express = require('express'),
bodyParser = require('body-parser'),
Sequelize = require('sequelize')
const app=express()
app.use(bodyParser.json())
var google = require('googleapis');
var contacts = google.people('v1');
const nconf = require('nconf');
const readline = require('readline');
const plus = google.plus('v1');
const path = require('path');
const OAuth2Client = google.auth.OAuth2;
nconf.argv().env().file(path.join(__dirname, '/oauth2.keys.json'));
const keys = nconf.get('web');
const CLIENT_ID = '1058912681476-uat19si2uli37vlehs2avqfue2l0b6ku.apps.googleusercontent.com';
const CLIENT_SECRET = 'PbY8AVICTQsywb4qiqCJ8gMB';
const REDIRECT_URL = 'urn:ietf:wg:oauth:2.0:oob';
const oauth2Client = new OAuth2Client(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function getAccessToken (oauth2Client, callback) {
// generate consent page url
const url = oauth2Client.generateAuthUrl({
access_type: 'offline', // will return a refresh token
scope: 'https://www.googleapis.com/auth/plus.me' // can be a space-delimited string or an array of scopes
});
console.log('Visit the url: ', url);
rl.question('Enter the code here:', code => {
// request access token
oauth2Client.getToken(code, (err, tokens) => {
if (err) {
return callback(err);
}
// set tokens to the client
// TODO: tokens should be set by OAuth2 client.
oauth2Client.credentials=tokens;
callback();
});
});
}
// retrieve an access token
getAccessToken(oauth2Client, () => {
// retrieve user profile
plus.people.get({ userId: 'me', auth: oauth2Client }, (err, profile) => {
if (err) {
throw err;
}
console.log(profile.displayName, ':', profile.tagline);
});
});
//here-not working
contacts.people.connections.list({
auth: oauth2Client //authetication object generated in step-3
}, function (err, response) {
// handle err and response
if(err){
throw err;
}
console.log(response.names);
});
app.listen(8080)
I'm trying to get the contacts of an user from the google people api(https://developers.google.com/people/api/rest/v1/people.connections/list) but when I try to call contacts.people.connection.list() I get Error:
Missing required parameters: resourceName
at Object.createAPIRequest (/home/ubuntu/workspace/contactmanager/backend/node_modules/googleapis/build/src/lib/apirequest.js:94:18);
The error says it all. The resourceName is a required parameter. If you look in the people.connections.list you must provide a 'people/me' value as parameter. Other values are invalid.
GET https://people.googleapis.com/v1/{resourceName=people/me}/connections
#noogui already answered the reason . Here is the more elaborative code for the use case:
function listConnectionNames(auth) {
const service = google.people({
version: "v1",
auth
});
service.people.connections.list({
resourceName: "people/me",
pageSize: 10,
personFields: "names,emailAddresses"
},
(err, res) => {
if (err) return console.error("The API returned an error: " + err);
const connections = res.data.connections;
if (connections) {
console.log("Connections:");
connections.forEach(person => {
if (person.names && person.names.length > 0) {
console.log(person.names[0].displayName);
} else {
console.log("No display name found for connection.");
}
});
} else {
console.log("No connections found.");
}
}
);
}
I had a problem in req.body at server as i received req.body as {} empty, here is the code at service
addEmployeeCollection(addArray: EmployeeSchema) {
let url: string = Constants.DOMAIN + Constants.CREATE_EMPLOYEE_ROUTE;
console.log('addArray at employee service', addArray)
var body = addArray;
return this._httpService.post(url, body).map(res => res.json()).catch(this._errorHandler);
}
this._httpService.post(url, body) will go to the interceptor, there the http.post method returned, here is the interceptor code,
post(url: string, body: Object, options ? : RequestOptionsArgs): Observable < Response > {
this.headers.append('Content-Type', 'application/json');
let token = localStorage.getItem('realtoken');
console.log('http token', token);
if (typeof url === 'string') {
if (!options) {
options = {
headers: this.headers
};
}
options.headers.set('Authorization', ` ${token}`);
}
return super.post(url, body, options).catch(this.catchAuthError(this));
}
now here the request payload at headers contains the information,
The response from the server side is
{"__v":0,"_id":"58ba4969ed69821fd6f7e573"}// details of employee is not saved
here the server side code
var mongoose = require('mongoose');
var express = require('express');
var router = express.Router();
var bodyParser = require('body-parser');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
//schema-models
var employeeCollection = require('../../model/employee.model.js');
router.post('/new', function(req, res) {
var employee_data = req.body;
console.log('req.body=====> ', req.body)
var addcontent = new employeeCollection(employee_data);
console.log('addcontent--->', addcontent);
addcontent.save(function(err, data) {
if (err) {
res.send(err);
} else {
console.log('data', data);
res.json(data);
}
});
});
module.exports = router;
At terminal, req.body=====> {}, the employee details are sent correctly in the request, but at server side am getting as empty req.body
As a beginner to this concepts,i could not figure out where i am getting wrong, please help me out