I am using an exchange API to create a bot. The exchange uses SHA1 and a private key to sign HTTP requests. Everything is working except when one of my POST parameters is an array.
I tried using JSON.stringify and querystring.stringify instead of just having the array as is. I even tried sending the parameters as a string, but the API response says it has to be an array.
let api_key = 'api_key';
let secret = fs.readFileSync('./key.pem').toString('ascii');
var params: any = {
amount: '1',
api_key,
begin_time: '',
end_time: '',
market: 'USDT',
market_type: '1',
page: '1',
price: '150',
size: '10',
token: 'LTC',
tokens: 'LTC', //this must be an array, but when I make it an array the signature becomes invalid
type: '2',
};
const hash = crypto.createSign('sha1');
hash.update(JSON.stringify(params));
const signature = encodeURIComponent(hash.sign(secret, 'base64'));
params = {
sign: signature,
...params,
};
console.log(params);
var api_endpoint = '/api_market/getBalance';
var url = 'https://api.bcex.vip' + api_endpoint;
var http_header = {
'Content-Type': 'application/x-www-form-urlencoded',
};
request.post(
url,
{
headers: http_header,
body: querystring.stringify(params),
},
function(error, response, body) {
if (error) {
// res.json(error);
console.error(error);
} else {
// res.json(body);
// console.log(response);
console.log(JSON.parse(body));
}
},
);
}
API Wiki:
https://github.com/BCEX-TECHNOLOGY-LIMITED/API_Docs/wiki/Interface
Signature Instructions
https://github.com/BCEX-TECHNOLOGY-LIMITED/API_Docs/wiki/Sign
The endpoint point getBalance needs tokens to be an array
It turns out that the problem is with querystring library, I changed it to qs and now it works.
Related
I am currently writing to an API to try and get a token. I'm nearly there but fallen at the last hurdle..
const fs = require('fs');
const https = require('https');
const ConfigParams = JSON.parse(fs.readFileSync('Config.json', 'utf8'));
const jwt = require('jsonwebtoken');
const apikey = ConfigParams.client_id;
var privateKey = fs.readFileSync(**MY KEY**);
var tkn;
const jwtOptions = {
algorithm: 'RS512',
header: { kid: 'test-1' }
}
const jwtPayload = {
iss: apikey,
sub: apikey,
aud: **API TOKEN ENDPOINT**,
jti: '1',
exp: 300
}
jwt.sign(jwtPayload,
privateKey,
jwtOptions,
(err, token) => {
console.log(err);
//console.log(token);
tkn = token;
let = tokenPayload = {
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer/',
client_assertion: tkn
}
tokenAuthOptions = {
payload: tokenPayload,
host: **HOST**,
path: **PATH**,
method: 'POST',
}
https.request(
tokenAuthOptions,
resp => {
var body = '';
resp.on('data', function (chunk) {
body += chunk;
});
resp.on('end', function () {
console.log(body);
console.log(resp.statusCode);
});
}
).end();
}
)
the encoded token comes back fine for the first part, the https request though returns a problem.
the response I get back is grant_type is missing, so I know I have a formatting problem due to this x-www-form-urlencoded, but I can't figure out how to fix it.
here is what the website said:
You need to include the following data in the request body in
x-www-form-urlencoded format:
grant_type = client_credentials client_assertion_type =
urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion = <your signed JWT from step 4> Here's a complete
example, as a CURL command:
curl -X POST -H "content-type:application/x-www-form-urlencoded"
--data \ "grant_type=client_credentials\ &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion="
END POINT
Ideally I want a solution using the https request, but if that's not possible I'm open to other solutions.
Any help is greatly appreciated.
Thanks,
Craig
Edit - I updated my code based on a suggestion to:
const params = new url.URLSearchParams({
grant_type: 'client_credentials',
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer/',
client_assertion: tkn
});
axios.post("URL", params.toString()).then(resp => {
console.log("response was : " + resp.status);
}).catch(err => {
console.log("there was an error: " + err);
})
But I'm still getting an error code 400, but now with less detail as to why. (error code 400 has multiple message failures)
Postman is the best.
Thank for #Anatoly for your support which helped to point me in the right direction. I had no luck so used postman for the first time, and found it had a code snippet section, with four different ways of achieving this using node.js.
The solution with Axion was:
const axios = require('axios').default;
const qs = require('qs');
var data = qs.stringify({
'grant_type': 'client_credentials',
'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion': tkn
});
var config = {
method: 'post',
url: '',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.status));
})
.catch(function (error) {
console.log(error);
});
I believe the issue was that I was not passing the information into 'data:' in combination with the querystring problem. Using qs.stringify to format the object, then passing this into the data: key solved the problem.
I'm trying to make a SOAP request to a WSDL service with ws-security.
The code is simple:
var fs = require('fs');
var soap = require('soap');
var url = '<WSDL-URL>';
var args = {
"param1": '1',
"param2": '2',
"param3": '3',
"param4": '4',
};
const options = {
envelopeKey: 'soapenv',
forceSoapVersion: '1.1',
};
var privateKey = fs.readFileSync('<PRIVATE-KEY>');
var publicKey = fs.readFileSync('<PUBLIC-KEY>');
var password = ''; // NO PASSWORD
var wsOptions = { hasTimeStamp: true };
soap.WSSecurityCert(privateKey, publicKey, password, wsOptions)
soap.createClient(url, options, function(err, client) {
//console.log(err);
client.setEndpoint('<SERVICE-ENDPOINT>');
client.setSecurity(new soap.ClientSSLSecurity('<SSL-KEY>', '<SSL-CERT>', {}));
client.METHOD(args, function(err, result, envelope, soapHeader) {
console.log(err);
//console.log(client.lastRequest);
//console.log('Response Envelope: \n' + envelope);
// 'result' is the response body
//console.log('Result invoke: \n' + JSON.stringify(result));
});
});
but when i try to run the script, i got this error:
Error: soap:Server: These policy alternatives can not be satisfied:
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}AsymmetricBinding: Received Timestamp does not match the requirements
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}X509Token: The received token does not match the token inclusion requirement
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}InitiatorToken
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}RecipientToken
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}IncludeTimestamp: Received Timestamp does not match the requirements
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}SignedParts: {http://schemas.xmlsoap.org/soap/envelope/}Body not SIGNED
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}EncryptedParts: {http://schemas.xmlsoap.org/soap/envelope/}Body not ENCRYPTED
I think I don't have set correctly the ws-security to the header and i'm still missing how to encrypt my body too.
Can someone help me to figure out how to solve this issue?
I've created an app which works for Spotify Premium users only (PUT methods don't work for non-premium users according to Spotify's documentation). It's a ten-question interactive quiz where a playlist generates in your Spotify account, plays it and you have to guess the name of each song. It's generated with a NodeJS Backend and displayed via ReactJS. The game can be demoed here: https://am-spotify-quiz.herokuapp.com/
Code can be reviewed below:
server.js
const express = require('express');
const request = require('request');
const cors = require('cors');
const querystring = require('querystring');
const cookieParser = require('cookie-parser');
const client_id = ''; // Hiding for now
const client_secret = ''; // Hiding
const redirect_uri = 'https://am-spotify-quiz-api.herokuapp.com/callback/';
const appUrl = 'https://am-spotify-quiz.herokuapp.com/#';
/**
* Generates a random string containing numbers and letters
* #param {number} length The length of the string
* #return {string} The generated string
*/
var generateRandomString = function(length) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
var stateKey = 'spotify_auth_state';
var app = express();
app.use(express.static(__dirname + '/public'))
.use(cors())
.use(cookieParser());
app.get('/login', function(req, res) {
var state = generateRandomString(16);
res.cookie(stateKey, state);
// scopes needed to make required functions work
var scope = 'user-read-private ' +
'user-read-email ' +
'user-read-playback-state ' +
'user-top-read ' +
'playlist-modify-public ' +
'playlist-modify-private ' +
'user-modify-playback-state ' +
'user-read-playback-state';
res.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: client_id,
scope: scope,
redirect_uri: redirect_uri,
state: state
}));
});
app.get('/callback/', function(req, res) {
// your application requests refresh and access tokens
// after checking the state parameter
var code = req.query.code || null;
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;
if (state === null || state !== storedState) {
res.redirect(appUrl +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.clearCookie(stateKey);
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')),
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
var options = {
url: 'https://api.spotify.com/v1/me',
headers: {
'Authorization': 'Bearer ' + access_token,
'Content-Type': 'application/json' // May not need
},
body: { // Likely don't need this anymore!
'name': 'Test Playlist',
'public': false
},
json: true
};
// use the access token to access the Spotify Web API
request.get(options, function(error, response, body) {
console.log(body);
});
// we can also pass the token to the browser to make requests from there
res.redirect(appUrl +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect(appUrl +
querystring.stringify({
error: 'invalid_token'
}));
}
});
}
});
// AM - May not even need this anymore!
app.get('/refresh_token', function(req, res) {
// requesting access token from refresh token
var refresh_token = req.query.refresh_token;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: { 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) },
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
res.send({
'access_token': access_token
});
}
});
});
console.log('Listening on 8888');
app.listen(process.env.PORT || 8888);
I have a react component which displays as soon as the user is logged in, called premium.js. If you need all the code, you can see it here. Below are the two PUT methods that I need for my game; one to turn off the shuffle feature and the other one used to play the playlist:
removeShuffle() {
axios({
url: 'https://api.spotify.com/v1/me/player/shuffle?state=false',
method: "PUT",
headers: {
'Authorization': 'Bearer ' + this.state.accesstoken
}
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error)
})
}
// Then... play the playlist to get started
playPlaylist(contextUri) {
axios({
url: 'https://api.spotify.com/v1/me/player/play',
method: "PUT",
data: {
context_uri: contextUri
},
headers: {
'Authorization': 'Bearer ' + this.state.accesstoken
}
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error)
})
}
These work perfectly fine when I, the creator of the game, try it; however, I had another premium user try it and found this error:
This doesn't seem to make much sense as I've discovered this error happens with another user, regardless of whether they are using Windows or Mac. Does anyone know what this means, and how can I solve? Thanks in advance!
I've also been using Spotify's API and I eventually got the same error when trying to PUT https://api.spotify.com/v1/me/player/play after an inactivity period, where no device was marked as active (I don't know exactly how long, but no more than a couple of hours).
Apparently one device must be set as active so that you can invoke the play endpoint successfully.
If you want to change the status of a device as active, according to their documentation, you can first try to GET https://api.spotify.com/v1/me/player/devices in order to obtain the list of available devices:
// Example response
{
"devices" : [ {
"id" : "5fbb3ba6aa454b5534c4ba43a8c7e8e45a63ad0e",
"is_active" : false,
"is_private_session": true,
"is_restricted" : false,
"name" : "My fridge",
"type" : "Computer",
"volume_percent" : 100
} ]
}
and then select one of the available devices by invoking the player endpoint PUT https://api.spotify.com/v1/me/player, including:
device_ids Required. A JSON array containing the ID of the device on which playback should be started/transferred.
For example: {device_ids:["74ASZWbe4lXaubB36ztrGX"]}
Note: Although an array is accepted, only a single device_id is currently supported. Supplying more than one will return 400 Bad Request
play with value true if you want to start playing right away.
Most likely you didn't get that error yourself because one of your devices was already active when you tested it. If you have no activity during a couple of hours on your own account and then try to invoke the v1/me/player/play endpoint, I'd expect you to get the same error.
An easy workaround to make sure that this was in fact your problem would be to ask your test user to please start playing a song on the Spotify app (no matter which), then pause it, and then trigger the function on your app that invokes the v1/me/player/play endpoint. That shouldn't return the No active device found error anymore.
The way I understand it is you are trying to play a playlist that does not belong to the current user (/me) Which could be the cause of the error.
I want to request my dialogflow agent through its api.
Here is my code:
const request = require('request');
const _ = require('underscore');
const apiBase = 'https://api.dialogflow.com/v1/query/';
const token = ...;
let options = {
'url': apiBase,
'headers': {
'Authorization': 'Bearer ' + token
},
'json': {
'v': '20150910',
'lang': 'fr',
'sessionId': 'abcdefgh'
},
}
let query = 'welcome_intent';
options.json.query = query;
request.post(options , (e, r, b) => {
console.log('result: ', b.result)
});
The string 'welcome_intent' has been added to the user says of the welcomeIntent. I get the following result:
result:
{ source: 'agent',
resolvedQuery: 'welcome_intent',
speech: '',
action: 'intent.welcome',
parameters: {},
metadata:
{ inputContexts: [],
outputContexts: [],
intentName: 'welcomeIntent',
intentId: '104a6251-fa1a-404a-a042-dfa1ce9118a8',
webhookUsed: 'true',
webhookForSlotFillingUsed: 'false',
contexts: [Array] },
score: 1 },
It triggers the correct intent which should use the webhook to fulfill the answer. But it does not actually request my webhook. It is said in this doc that I should have a field fulfilment in my result json. I don't have this field.
Am I doing something wrong ? I can make a subsequent call to my webhook using the DF result, but can I avoid it ?
thanks
I'm trying to implement https://developers.podio.com/doc/items/add-new-item-22362 Podio API addItem call in a nodejs module. Here is the code:
var _makeRequest = function(type, url, params, cb) {
var headers = {};
if(_isAuthenticated) {
headers.Authorization = 'OAuth2 ' + _access_token ;
}
console.log(url,params);
_request({method: type, url: url, json: true, form: params, headers: headers},function (error, response, body) {
if(!error && response.statusCode == 200) {
cb.call(this,body);
} else {
console.log('Error occured while launching a request to Podio: ' + error + '; body: ' + JSON.stringify (body));
}
});
}
exports.addItem = function(app_id, field_values, cb) {
_makeRequest('POST', _baseUrl + "/item/app/" + app_id + '/',{fields: {'title': 'fgdsfgdsf'}},function(response) {
cb.call(this,response);
});
It returns the following error:
{"error_propagate":false,"error_parameters":{},"error_detail":null,"error_description":"No matching operation could be found. No body was given.","error":"not_found"}
Only "title" attribute is required in the app - I checked that in Podio GUI. I also tried to remove trailing slash from the url where I post to, then a similar error occurs, but with the URL not found message in the error description.
I'm going to setup a proxy to catch a raw request, but maybe someone just sees the error in the code?
Any help is appreciated.
Nevermind on this, I found a solution. The thing is that addItem call was my first "real"-API method implementation with JSON parameters in the body. The former calls were authentication and getApp which is GET and doesn't have any parameters.
The problem is that Podio supports POST key-value pairs for authentication, but doesn't support this for all the calls, and I was trying to utilize single _makeRequest() method for all the calls, both auth and real-API ones.
Looks like I need to implement one for auth and one for all API calls.
Anyway, if someone needs a working proof of concept for addItem call on node, here it is (assuming you've got an auth token beforehand)
_request({method: 'POST', url: "https://api.podio.com/item/app/" + app_id + '/', headers: headers, body: JSON.stringify({fields: {'title': 'gdfgdsfgds'}})},function(error, response, body) {
console.log(body);
});
You should set content-type to application/json
send the body as stringfied json.
const getHeaders = async () => {
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
};
const token = "YOUR APP TOKEN HERE";
headers.Authorization = `Bearer ${token}`;
return headers;
}
const createItem = async (data) => {
const uri = `https://api.podio.com/item/app/${APP_ID}/`;
const payload = {
fields: {
[data.FIELD_ID]: [data.FIELD_VALUE],
},
};
const response = await fetch(uri, {
method: 'POST',
headers: await getHeaders(),
body: JSON.stringify(payload),
});
const newItem = await response.json();
return newItem;
}