Hawk authentication error with #hapi/hawk - node.js

I'm trying a basic sample to authenticate a request using Hawk scheme and Hapi but the hawk plugin fails because it is trying to access a payload property that does not exist:
Error:
Server started listening on http://localhost:3000
Debug: internal, implementation, error
TypeError: Cannot read property 'payload' of undefined
at Object.authenticate (D:\TEST\node\sample3\node_modules\#hapi\hawk\lib\plugin.js:45:45)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:189:7)
The relevant Hawk plugin code where the error is generated:
...
if (request.route.settings.auth.payload) {
request.events.once('peek', (chunk) => {
...
Server code:
const Hapi = require('#hapi/hapi');
const Hawk = require('#hapi/hawk');
const credentials = {
John: {
key: 'secret',
algorithm: 'sha256'
}
};
const getCredentialsFunc = function (id) {
return credentials[id];
};
const start = async () => {
const server = Hapi.server({ port: 3000, host: 'localhost' });
await server.register(Hawk);
server.auth.strategy('default', 'hawk', { getCredentialsFunc });
server.auth.default('default');
server.route({
method: 'GET',
path: '/',
handler: function (request, h) {
return 'Welcome';
}
});
await server.start();
console.log('Server started listening on %s', server.info.uri);
};
start();
Client code:
const Request = require('request');
const Hawk = require('#hapi/hawk');
const credentials = {
id: 'John',
key: 'secret',
algorithm: 'sha256'
};
const requestOptions = {
uri: 'http://localhost:3000/',
method: 'GET',
headers: {}
};
const { header } = Hawk.client.header(requestOptions.uri, requestOptions.method, { credentials: credentials, ext: 'some-app-data' });
requestOptions.headers.Authorization = header;
Request(requestOptions, function (error, response, body) {
const isValid = Hawk.client.authenticate(response, credentials, header.artifacts, { payload: body });
console.log(`${response.statusCode}: ${body}` + (isValid ? ' (valid)' : ' (invalid)'));
});

I created a PR for this exact problem :)
https://github.com/hapijs/hawk/pull/259

Related

Keep getting pending request when trying to call endpoint, what's wrong?

I have made an endpoint to get token for paytm payment integration. In backend when i'm calling api i'm getting reqdata json but in frontend when i'm logging await transactionAPI, i'm getting only pending promise. i've tried using then in backend in PaytmChecksum.generateSignature method & in frontend in fetch but nothing is working. Keep getting the same result. Pls help.
Frontend code:
const makePayment = async () => {
const data = {
oid: String(new Date().valueOf()),
amount: '1.00',
email: 'abc#gmail.com',
};
let transactionAPI = fetch('http://localhost:3000/api/pretransact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
console.log(await transactionAPI);
}
Backend code:
const https = require('https');
const PaytmChecksum = require('./PaytmChecksum');
export default async function handler(req, res) {
if (req.method == 'POST') {
const { oid, amount, email } = req.body;
let mid = process.env.PAYTM_MID;
let paytmParams = {};
paytmParams.body = {
requestType: 'Payment',
mid,
websiteName: process.env.WEBSITE,
orderId: oid,
callbackUrl: 'http://localhost:3000/api/callback',
txnAmount: {
value: amount,
currency: 'INR',
},
userInfo: {
custId: email,
},
};
const checksum = await PaytmChecksum.generateSignature(
JSON.stringify(paytmParams.body),
process.env.MERCHANT_KEY
);
paytmParams.head = {
signature: checksum,
};
var post_data = JSON.stringify(paytmParams);
const requestAsync = () => {
return new Promise((resolve, reject) => {
var options = {
hostname: 'securegw-stage.paytm.in',
// hostname: 'securegw.paytm.in',
port: 443,
path: `/theia/api/v1/initiateTransaction?mid=${mid}&orderId=${oid}`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': post_data.length,
},
};
var response = '';
var post_req = https.request(options, function (post_res) {
post_res.on('data', function (chunk) {
response += chunk;
});
post_res.on('end', function () {
resolve(response);
});
});
post_req.write(post_data);
post_req.end();
});
};
const reqdata = await requestAsync();
res.send(200).json(reqdata);
}
}

Extracting values from API data in node.js

Thank you for your time.
I'm trying to use OAuth2 in discord, but I'm having a hard time figuring out how to retrieve the username.
Can someone please tell me how to do it?
code
const fetch = require('node-fetch');
const express = require('express');
const app = express();
app.get('/', async ({ query }, response) => {
const { code } = query;
if (code) {
try {
const oauthResult = await fetch('https://discord.com/api/oauth2/token', {
method: 'POST',
body: new URLSearchParams({
client_id: process.env['ci'],
client_secret: process.env['cs'],
code,
grant_type: 'authorization_code',
redirect_uri: `https://oauth.aiueominato1111.repl.co`,
scope: 'identify',
}),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
const oauthData = await oauthResult.json();
const userResult = await fetch('https://discord.com/api/users/#me', {
headers: {
authorization: `${oauthData.token_type} ${oauthData.access_token}`,
},
});
console.log( await userResult.json());
} catch (error) {
// NOTE: An unauthorized token will not throw an error;
// it will return a 401 Unauthorized response in the try block above
console.error(error);
}
}
return response.sendFile('index.html', { root: '.' });
});
app.listen(port, () => console.log(`App listening at http://localhost:${port}`));
thank you
You already have the JSON data (from await userResult.json()) with it you can either get the property from the object or destructure it.
Getting property
const user = await userResult.json();
const username = user.username
Destructuring
const { username, id } = await userResult.json()
You can find out more about what properties you can extract from the Discord documentation

Write After End Error when Reading a File using FS

I built a program where a user can send request with a PDF URL then sever download it and forward into an external API endpoint. Now, the code able to download file but it hit this error when it start to read the file.
I must admit that Promises is something I hate to learn hence I used Async Function with Awaits and in other cases I used normal functions. Promises is so hard to grasp. The syntax make it hard to read.
Code is below:
const fs = require('fs');
const url = require("url");
const path = require("path");
const http = require('http')
const rp = require('request-promise');
const app = express();
const port = 8999;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/upload-invoice', (req, res) => {
var parsed = url.parse(req.body.FileURL);
var filename = (path.basename(parsed.pathname));
var downloaded_file_path = `invoices/${filename}`;
function download() {
var options = {
hostname: parsed.hostname,
port: 2799,
path: parsed.path,
method: 'GET'
}
const file = fs.createWriteStream(`invoices/${filename}`);
const make_request = http.request(options, (response) => {
response.pipe(file);
});
make_request.end();
try {
setTimeout(function () {
upload()
}, 1000);
} catch (error) {
console.log('An Error occured when uploading file,'+error);
}
}
async function upload() {
const flow = "Upload Invoice"
var file_stream = fs.createReadStream(downloaded_file_path)
var options = {
method: 'POST',
strictSSL: true,
uri: 'https://endpoint.goes.here',
formData: {
'file': file_stream
},
headers: {
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryzte28ISYHOkrmyQT'
},
json: req.body,
resolveWithFullResponse: true
}
try {
var response = await rp(options)
res.send(response.body)
}
catch (error) {
console.log(`An error on ${flow} flow for unknown user. Here is more info about error,
${error}
`)
res.send("Error")
}
}
download()
});
app.listen(port)
Update:
formData: {
name: filename,
file: {
value: fs.createReadStream(downloaded_file_path),
options: {
filename: filename,
contentType: 'application/pdf'
}
}
}
I've tried this code too but it output same error.
It works after removing json: req.body
My fault.

node-fetch timeout issue/ express timeout/ lambda timeout or sth else?

below code works fine when running locally, but "getAccessToken ()" does not work as expected when running inside an aws lambda function, when doing "POST" to /webhook endpoint. I am using node 8.10, "aws-serverless-express": "^3.3.6", "express": "^4.16.4" and "node-fetch": "^2.5.0", basically it print the correct jwt token but node-fetch does not return anything, sample log.
START RequestId: c8efba59-1869-4eaa-b9d8-aa15a7507d52 Version: $LATEST
2019-05-27T19:55:32.328Z c8efba59-1869-4eaa-b9d8-aa15a7507d52 start getExecution
2019-05-27T19:55:32.328Z c8efba59-1869-4eaa-b9d8-aa15a7507d52 exectution_url:
2019-05-27T19:55:32.328Z c8efba59-1869-4eaa-b9d8-aa15a7507d52 https://cloudmanager.adobe.io/somevalidurl
2019-05-27T19:55:32.328Z c8efba59-1869-4eaa-b9d8-aa15a7507d52 start getAccessToken
END RequestId: c8efba59-1869-4eaa-b9d8-aa15a7507d52
REPORT RequestId: c8efba59-1869-4eaa-b9d8-aa15a7507d52 Duration: 6657.00 ms Billed Duration: 6700 ms Memory Size: 128 MB Max Memory Used: 37 MB
I made sure lambda timeout is 30 seconds, tried to disable "node-fetch" timeout by setting it to 0 and , used a middleware for all the routes "app.use(timeout("30000"))" also for that specific webhook request timeout. (I receive the 200 pong response immediately but getexectuion async function does not work properly)
const express = require('express')
const bodyParser = require('body-parser')
const crypto = require('crypto')
const jsrsasign = require('jsrsasign')
const fetch = require('node-fetch')
const timeout = require('connect-timeout')
const URL = require('url').URL
const URLSearchParams = require('url').URLSearchParams
//require('dotenv').config()
const app = express()
async function getAccessToken () {
console.log("start getAccessToken")
const EXPIRATION = 60 * 60 // 1 hour
const header = {
'alg': 'RS256',
'typ': 'JWT'
}
const payload = {
'exp': Math.round(new Date().getTime() / 1000) + EXPIRATION,
'iss': process.env.ORGANIZATION_ID,
'sub': process.env.TECHNICAL_ACCOUNT_ID,
'aud': `https://ims-na1.adobelogin.com/c/${process.env.API_KEY}`,
'https://ims-na1.adobelogin.com/s/ent_cloudmgr_sdk': true
}
const jwtToken = jsrsasign.jws.JWS.sign('RS256', JSON.stringify(header), JSON.stringify(payload), process.env.PRIVATE_KEY)
//console.log("jwt token:")
//console.log(jwtToken)
const body = new URLSearchParams({
client_id: process.env.API_KEY,
client_secret: process.env.CLIENT_SECRET,
jwt_token: jwtToken
})
const response = await fetch('https://ims-na1.adobelogin.com/ims/exchange/jwt', {
method: 'POST',
options: { timeout: 0},
timeout: 0,
size: 0,
body: body
})//.catch(error => {
// console.log("an error happend in fetchg")
// console.log(error)
//})
const json = await response.json()
if ((response.status !== 200) && (response.status !== 201)) {
console.error(`Invalid response status ${ response.status }.`);
throw json;
}
console.log("access_token:")
console.log(json['access_token'])
return json['access_token']
}
async function makeApiCall (accessToken, url, method) {
console.log("start make api call")
const response = await fetch(url, {
'method': method,
'headers': {
'x-gw-ims-org-id': process.env.ORGANIZATION_ID,
'x-api-key': process.env.API_KEY,
'Authorization': `Bearer ${accessToken}`
}
})
console.log("finish make api call")
const json = await response.json()
return json
}
function getLink (obj, linkType) {
return obj['_links'][linkType].href
}
async function getExecution (executionUrl) {
console.log("start getExecution")
console.log("exectution_url:")
console.log(executionUrl)
const accessToken = await getAccessToken()
console.log("access-token:")
console.log(accessToken)
const execution = await makeApiCall(accessToken, executionUrl, 'GET')
console.log(execution)
console.log("aaaa")
const program = await makeApiCall(accessToken, new URL(getLink(execution, 'http://ns.adobe.com/adobecloud/rel/program'), executionUrl))
console.log(execution)
console.log("here")
execution.program = program
return execution
}
//app.use(bodyParser.json())
app.use(bodyParser.json({
verify: (req, res, buf, encoding) => {
const signature = req.header('x-adobe-signature')
if (signature) {
const hmac = crypto.createHmac('sha256', process.env.CLIENT_SECRET)
hmac.update(buf)
const digest = hmac.digest('base64')
if (signature !== digest) {
throw new Error('x-adobe-signature HMAC check failed')
}
} else if (!process.env.DEBUG && req.method === 'POST') {
throw new Error('x-adobe-signature required')
}
}
}))
app.use(timeout("30000"))
app.post('/webhook', (req, res) => {
req.setTimeout(120000, function(){
console.log('Request has timed out.');
res.send(408);
});
res.writeHead(200, { 'Content-Type': 'application/text' })
res.end('pong')
getExecution("https://cloudmanager.adobe.io/<somevalidurl>").then(execution => {
console.log(`Execution for ${execution.program.name} started`)
})
})
module.exports = app;
//const port = process.env.PORT || 3000
//app.listen(port, () =>
// console.log(`App is listening on port ${port}.`)
//)

Node SOAP client to connect to InDesign Server

I am trying to get node/express to send soap request to indesign server.
Posting the request from Soap.ui or Postman works fine. Loading the "soap" page in the browser errors.
I also tried the node client with a few sample scripts from the new and they work, so the install should be OK.
This is what I have so far:
router.get('/soap', function(req, res, next) {
var url = 'http://<server_ip>:8088/service?wsdl';
var args = { "IDSP:RunScriptParameters" :
{ 'scriptLanguage': 'javascript',
'scriptFile': 'C:/indesign_scripts/test.jsx'
}
};
soap.createClient(url, function(err, client){
client.Service.Service.RunScript(args, function(err, result) {
if (err) console.log(err);
console.log(result);
});
});
client.describe() returns:
{ Service:
{ Service:
{ RunScript: [Object],
BeginSession: [Object],
EndSession: [Object] } } }
I am trying to use RunScript object.
client.describe().Service.Service.RunScript:
{ input:
{ runScriptParameters:
{ scriptText: 'xsd:string',
scriptLanguage: 'xsd:string',
scriptFile: 'xsd:string',
'scriptArgs[]': [Object],
targetNSAlias: 'IDSP',
targetNamespace: 'http://ns.adobe.com/InDesign/soap/' } },
output:
{ errorNumber: 'xsd:int',
errorString: 'xsd:string',
scriptResult:
{ data: 'xsd:anyType',
targetNSAlias: 'IDSP',
targetNamespace: 'http://ns.adobe.com/InDesign/soap/' } } }
Console shows this error:
[Error: connect ECONNREFUSED 127.0.0.1:8088]
code: 'ECONNREFUSED',
errno: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 8088 }
Indesign Server wsdl could be viewed here:
https://gist.github.com/tomtaylor/1034317
I suspect this is something with args variable format.
You can fix this issue by adding line below
client.setEndpoint('http://<server_ip>:8088');
I tried to add "Access-Control-Allow-Origin" to my headers in my app file, as suggested by #Chirdeep Tomar, but I am still getting the same errors.
The workaround I came up with was to use http request or curl for ajax post.
The example with request:
var express = require('express');
var request = require('request');
var parser = require('xml2json');
var router = express.Router();
router.get('/ProductionBooks/:id', function(req, res) {
var myId = req.params.id;
var myBody = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://ns.adobe.com/InDesign/soap/"><soapenv:Body>'
+'<soap:RunScript>'
+'<runScriptParameters>'
+'<scriptLanguage>javascript</scriptLanguage>'
+'<scriptFile>C:/indesign_scripts/test.jsx</scriptFile>'
+'</runScriptParameters>'
+'</soap:RunScript>'
+'</soapenv:Body>'
+'</soapenv:Envelope>';
request({
url: 'http://192.168.0.129:8088', //URL to hit
method: 'POST',
headers: {
'Content-Type': 'application/xml',
'Content-Length': Buffer.byteLength(myBody)
},
body: myBody
}, function(error, response, body){
if(error) {
console.log(error);
} else {
console.log(response.statusCode + '\n');
var objJSON = JSON.parse(parser.toJson(body));
console.log(objJSON['SOAP-ENV:Envelope']['SOAP-ENV:Body']['IDSP:RunScriptResponse'].errorNumber);
}
});
res.end();
});
Example with curl:
var curl = require("curl");
curl.post(url, soapBody, options, function(err, response, body) {
try {
console.log(response.body);
}
catch (err) {
console.log(err);
}
});
res.end();
});
using easy-soap-request:
const soapRequest = require('easy-soap-request');
const url = 'http://[YOUR SERVER ADDRESS]:[IDS PORT]/service?wsdl';
const sampleHeaders = {
'user-agent': 'sampleTest',
'Content-Type': 'text/xml;charset=UTF-8',
};
const xml = '<?xml version="1.0" encoding="UTF-8"?>'
+'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:IDSP="http://ns.adobe.com/InDesign/soap/">'
+'<SOAP-ENV:Body><IDSP:RunScript><IDSP:runScriptParameters>'
+'<IDSP:scriptLanguage>javascript</IDSP:scriptLanguage>'
+'<IDSP:scriptFile>[PATH TO SCRIPTS]/Hello.jsx</IDSP:scriptFile>'
+'<IDSP:scriptArgs><IDSP:name>myArg</IDSP:name><IDSP:value>Test Value</IDSP:value></IDSP:scriptArgs>'
+'</IDSP:runScriptParameters></IDSP:RunScript>'
+'<IDSP:RunScriptResponse><errorNumber>0</errorNumber><scriptResult><data xsi:type="IDSP:List"><item><data xsi:type="xsd:string">1</data></item></data></scriptResult></IDSP:RunScriptResponse>'
+'</SOAP-ENV:Body></SOAP-ENV:Envelope>';
// usage of module
(async () => {
const { response } = await soapRequest({ url: url, headers: sampleHeaders, xml: xml, timeout: 1000 }); // Optional timeout parameter(milliseconds)
const { headers, body, statusCode } = response;
console.log(headers);
console.log(body);
console.log(statusCode);
})();
Sample script (hello.jsx):
var arg = app.scriptArgs.get("myArg");
var res = "Your input: " + arg;
res;

Resources