How to sign API Gateway URL that has a query string - javascript

I am trying to use SignatureV4 to sign a request for an API Gateway endpoint that uses IAM Authorizor. An issue is that I keep getting a 403 error whenever I append a query string to my URL, i.e. /pets?type=1. Everything works fine when a query string is not included, i.e. /pets
This is how I build a request:
const region = 'xxx'
const method = 'GET'
const protocol = 'https:'
const host = `xxx.execute-api.${region}.amazonaws.com`
const path = '/dev/pets'
const query = {
type: 1,
}
const request = new HttpRequest({
method: method,
protocol: protocol,
hostname: host,
path: path,
query: query,
headers: {
'Content-Type': 'application/json',
host: host ,
},
})
const signer = new SignatureV4({
credentials: AWS.config.credentials,
service: 'execute-api',
region: region,
sha256: Sha256,
})
const { headers } = await signer.sign(request)
const response = await fetch(`${protocol}//${host}${path}?type=1`, {
headers,
method
}).then((res) => res.json())
I've tried running the same query within Postman and it worked just fine. So, I have to assume that an issue is with my implementation.

An issue was due to getCanonicalQuery ignoring values that are not string or array:
https://github.com/aws/aws-sdk-js-v3/blob/main/packages/signature-v4/src/getCanonicalQuery.ts#L19
So, to fix this I had to swap my query to below:
const query = {
type: '1',
}

Related

Cross-Origin Request Blocked using NodeJS/Coinbase API

I'm trying to get Coinbase to work using NodeJS
I get the following error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:3000/create-payment. (Reason: CORS request did not succeed). Status code: (null).
I've tried downloading one of those Corse plugins that should allow it but I just can't get it to work.
Javascript code:
<script> const button = document.getElementById('order');
button.addEventListener('click', event => { const form = document.getElementById('payment-form');
event.preventDefault(); // Prevent the form from submitting to the server
form.submit();
const price = "<?php echo $price; ?>";
const amount = document.getElementById('quantitymills').value;
const total_price = document.getElementById('inputd').value;
const fname = document.getElementById('fname').value;
const email = document.getElementById('email').value;
fetch('http://localhost:3000/create-payment', {
method: 'POST',
body: JSON.stringify({ price }),
body: JSON.stringify({ amount }),
body: JSON.stringify({ total_price }),
body: JSON.stringify({ name }),
body: JSON.stringify({ email }),
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(payment => {
// Do something with the payment details
console.log(payment);
})
.catch(error => console.error(error)); });
Nodejs code:
const express = require('express');
const axios = require('axios');
const bodyParser = require('body-parser');
const mysql = require('mysql2');
const app = express();
// Parse application/x-www-form-urlencoded requests app.use(bodyParser.urlencoded({ extended: false }));
// Connect to the database
const db = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '',
database: 'coinbase' });
// Create a payment
app.post('http://localhost:3000/create-payment', async (req, res) => { try {
// Make an API request to create the payment
const response = await axios.post(
'https://api.commerce.coinbase.com/charges',
{
name: 'Product one',
description: 'Product one',
local_price: {
amount: req.body.total_price,
currency: 'USD' },
metadata: {},
pricing_type: 'fixed_price',
redirect_url: 'http://localhost/index.php'
},
{
headers:
{ 'X-CC-Api-Key': 'myapikey' } }
);
// Store the payment URL in the database
const payment = {
url: response.data.hosted_url,
amount: req.body.total_price,
currency: 'USD',
status: 'pending'
};
db.query('INSERT INTO payments SET ?', payment, (error, result) => {
if (error) throw error;
res.send(payment);
});
} catch (error) { console.error(error); } });
app.listen(3000, () => { console.log('Server listening on port 3000'); });
It might be something else wrong in my code, I am still new to coding and could have messed up in another area not quite sure.
Any help would be greatly appreciated!
Downloading cors plugin
tried 127.0.0.1 instead of localhost
tried switching ports
Try it like this:
npm install --save cors
Then inside your code put
const cors = require('cors');
at the begining and this before the row with app.listen(3000....)
app.use(cors({
origin: '*'
}));
This will allow request from all domains to your backend.
To authorize only your domain (your browser requests) substitute the "star" after origin with the domain (even localhost:port) where your frontend is running.
Hint: you should create environments variable for the domain names. So if you deploy it to a production site you can dynamically substitute them.

aws signature v4 InvalidSignatureException

I am trying to trying to make a request to apigateway use the signature v4. No matter what I do it seems to fail. I have an endpoint in apigateway that just echoes back the request that's sent to it. I am also doing this in a react application. All the examples of how to use this #aws-sdk/signature-v4 library are using node. Which may be the problem but I dont know. I am able to generate a signature but I get a 403 InvalidSignatureException every time. This endpoint does work when I use postman and use their built in aws signature feature. One thing I noticed is the signedHeaders postman gives me back has host in the string. I dont get that when I create the signedHeaders myself.
import { SignatureV4 } from "#aws-sdk/signature-v4";
import { HttpRequest } from "#aws-sdk/protocol-http";
import { HttpRequest as IHttpRequest } from "#aws-sdk/types";
import { Sha256 } from "#aws-crypto/sha256-js";
interface CreateSignHttpRequestParams {
body?: string;
headers?: Record<string, string>;
hostname: string;
method?: string;
path?: string;
protocol?: string;
query?: Record<string, string>;
service: string;
}
export async function createSignedHttpRequest({
body,
headers,
hostname,
method = "POST",
path = "/",
protocol = "https:",
query,
service,
}: CreateSignHttpRequestParams): Promise<IHttpRequest> {
const httpRequest = new HttpRequest({
body,
headers,
hostname,
method,
path,
protocol,
query,
});
const signer = new SignatureV4({
applyChecksum: false,
credentials: {
accessKeyId: 'XXX',
secretAccessKey: 'XXX',
sessionToken: 'XXX',
},
region: 'XXX',
service,
sha256: Sha256
});
return signer.sign(({
method: 'POST',
protocol: 'https',
hostname: `https://XXX.execute-api.XXX.amazonaws.com/STAGENAME/`,
path: '/',
query: {},
headers: {},
}));
}
const results = createSignedHttpRequest({hostname: 'https://XXX.execute-api.XXX.amazonaws.com/STAGENAME', service: 'execute-api'}).then( async response => {
var myHeaders = new Headers();
myHeaders.append("X-Amz-Security-Token", response.headers['x-amz-security-token']);
myHeaders.append("X-Amz-Date", response.headers['x-amz-date']);
myHeaders.append("Authorization", response.headers.authorization);
var requestOptions = {
method: 'POST',
headers: myHeaders
};
fetch("https://XXX.execute-api.XXX.amazonaws.com/STAGENAME/", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
})

Spotify API OAUTH2 Server Error Response when requesting token

Spotify Oauth2 Docs - Client Credential Flow
I asked a similar question, and I was able to get the code running in google apps script (javascript). I am using Client Credential Flow. However, I tried to recreate this code in Nodejs. When running this code, I receive a server error.
error: "server_error"
This is the problem code in Nodejs
const defaultConfig = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
// this encodes the app_id and api_key into a base64 string like the documentation states
'Authorization': `Basic ${btoa(spotify.API_ID + ':' + spotify.API_KEY)}`
},
body: JSON.stringify({
'grant_type': 'client_credentials',
})
};
// this function is wrapped in a list titled, "apiSettings" with other functions. I just included the authorization function here:
const apiSettings = {
getSpotifyAuthorizeToken: async () => {
const endpoint = "https://accounts.spotify.com/api/token";
return await (await fetch(endpoint, defaultConfig)).json();
},
};
I got this similar code to work in google apps script with no problems.
Could the server error be a result of me running Nodejs on a "localhost:xxxx"? The documentation states that, "The Client Credentials flow is used in server-to-server authentication."
if this is not the case, could you provide a working
Spotify API OAUTH2 Server Error Response when requesting token
From your previous question, I would like to propose the following sample scripts.
Sample script 1:
If you want to convert the script of this answer to Node.js and node-fetch, how about the following script?
const spotify = { API_ID: "API_ID", API_KEY: "API_KEY" }; // Please set your client ID and client secret.
const buf = Buffer.from(spotify.API_ID + ":" + spotify.API_KEY);
const para = new URLSearchParams();
para.append("grant_type", "client_credentials");
const defaultConfig = {
method: "POST",
headers: { Authorization: `Basic ${buf.toString("base64")}` },
body: para,
};
const apiSettings = {
getSpotifyAuthorizeToken: async () => {
const endpoint = "https://accounts.spotify.com/api/token";
return await (await fetch(endpoint, defaultConfig)).json();
},
};
Sample script 2:
If you want to convert the script of this answer to Node.js and node-fetch, how about the following script?
const spotify = { API_ID: "API_ID", API_KEY: "API_KEY" }; // Please set your client ID and client secret.
const para = new URLSearchParams();
para.append("grant_type", "client_credentials");
para.append("client_id", spotify.API_ID);
para.append("client_secret", spotify.API_KEY);
const defaultConfig = {
method: "POST",
body: para,
};
const apiSettings = {
getSpotifyAuthorizeToken: async () => {
const endpoint = "https://accounts.spotify.com/api/token";
return await (await fetch(endpoint, defaultConfig)).json();
},
};
Reference:
node-fetch

NodeJs / Axios: timeout issues linked to high level of concurrency

I've set up a nodeJS server which basically receives an ad request, sends ad requests through Axios to multiple endpoints with a timeout (usually 1000 ms), and then parse and sends back all results.
On each pod around 600 requests per minute are performed to various external endpoints.
When we start the pod, requests run pretty well at the beginning, than after 1 or 2 minutes, all requests returns as timed out...
I use Promise.all to manage concurrency requests, you'll find below the adapterRequest component in charge of sending requests.
I also try to send some data within my cluster, I also get a timeout, which makes me confident on the fact that the issue is linked to Axios.
Getting into details, I create promise arrays thanks to the following module, than I use Promise.all to fetch data
const axios = require('axios');
const fakeServer = require('../test/fake-server');
let promiseCount = 0;
module.exports = (ssp, payload, endpoint, method, timeout) => new Promise((resolve) => {
const cmd = process.env.NODE_ENV === 'test' ? fakeServer : axios;
const start = Date.now();
const config = {
ssp,
url: endpoint,
method,
timeout,
header: {
'Content-Type': 'application/json; charset=utf-8',
Accept: 'application/json',
Connexion: 'keep-alive',
},
data: payload,
};
cmd(config)
.then((response) => {
promiseCount += 1;
console.log(ssp, 'RESPONSEOK', promiseCount);
resolve({
ssp,
uri: config.url,
requestbody: payload,
requestheaders: config.header,
responsebody: response.data,
status: response.status,
responsetimemillis: Date.now() - start,
});
})
.catch((error) => {
promiseCount += 1;
console.log(ssp, error.code, promiseCount);
let responsebody;
let status;
if (error.response === undefined) {
responsebody = undefined;
status = undefined;
} else {
responsebody = error.response.data ? error.response.data : error.message;
status = error.response.status;
}
resolve({
ssp,
uri: config.url,
requestbody: payload,
requestheaders: config.header,
responsebody,
status,
responsetimemillis: Date.now() - start,
});
});
});
Should I try to use such modules as agentkeepalive, with one Axios instance per endpoint?
Thanks!
Just for debugging. before you try another module...
Inside axios config could you change header into headers and add KeepAlive like this
const http = require('http')
const https = require('https')
const config = {
ssp,
url: endpoint,
method,
timeout,
headers: {
'Content-Type': 'application/json; charset=utf-8',
Accept: 'application/json',
},
data: payload,
//keepAlive pools and reuses TCP connections, so it's faster
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
}

How to Fix Coinbase Pro API Request Headers?

I am trying to code to execute orders using Coinbase Pro API according to the Documentation provided. However, I got an error like this.
Access to XMLHttpRequest at 'https://api.pro.coinbase.com/orders' from origin 'http://localhost:8000' has been blocked by CORS policy: Request header field cb-access-key is not allowed by Access-Control-Allow-Headers in preflight response.
And this is the code that I wrote.
var vm = this;
var coinbasePro = {
passphrase: 'xxxxxxxxx',
key: 'xxxxxxxxxxxxxxxxxxxxxxx',
secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==',
apiURI: 'https://api.pro.coinbase.com',
};
var dataRequest = {
url: '/orders',
method: 'POST',
timestamp: Date.now() / 1000,
};
var dataBody = JSON.stringify({
price: '1.0',
size: '1.0',
side: 'buy',
product_id: 'BTC-USD'
});
var what = vm.dataRequest.timestamp + vm.dataRequest.method + vm.dataRequest.url + dataBody;
var key = Buffer.from(vm.coinbasePro.secret, 'base64');
var hmac = cryptoJs.createHmac('sha256', key);
var sign = hmac.update(what).digest('base64');
vm.$http({
url: vm.coinbasePro.apiURI+vm.dataRequest.url,
method: vm.dataRequest.method,
headers: {
'Accept': 'application/json',
'CB-ACCESS-KEY': vm.coinbasePro.key,
'CB-ACCESS-SIGN': sign,
'CB-ACCESS-PASSPHRASE': vm.coinbasePro.passphrase,
'CB-ACCESS-TIMESTAMP': vm.dataRequest.timestamp,
},
}).then((res) => {
console.log(res);
}).catch((err) => {
});
I have tried different ways to get things going and applied some of the references I have come across. Thank you for the help.
Their API does support CORS, however it is misconfigured and does not permit the security headers that they require you to use! You can work around this by running an express proxy with middleware to re-write the headers:
import express from 'express'
import { createProxyMiddleware } from 'http-proxy-middleware'
const app = express()
app.use(express.static('client'))
const apiProxy = createProxyMiddleware({
target: 'https://api.pro.coinbase.com',
changeOrigin: true,
onProxyRes: res => {
res.headers = {
...res.headers,
'access-control-allow-headers':
'Content-Type, cb-access-key, cb-access-sign, cb-access-timestamp, cb-access-passphrase',
}
},
})
app.use('/', apiProxy)
app.listen(3001)

Categories

Resources