I tried controlling my GoSund smart socket using Tuya IoT Development Platform, but I'm stuck on this error response when trying to switch its state:
{"code":1004,"msg":"sign invalid","success":false,"t":1658384161392,"tid":"97e938e608bc11eda4f0322e56e3d437"}
The following code is basically slightly modified copy of develop code sample from official Tuya API site with my keys and deviceId pasted(https://developer.tuya.com/en/docs/iot/singnature?id=Ka43a5mtx1gsc)
When I tried to do the exact same thing using Tuya's site debug device option it just works. When I try to do it using their code sample in a web app, it fails with 1004. Except for the token that is new every time I call this, basically all the request headers are the same as when calling them from Tuya's site. Payload is the same too, but the response is very different.
same request on Tuya website device debugging & in a web app
Adding sign_version: '2.0' to request headers or using different url (const url = /v1.0/iot-03/devices/${deviceId}/commands;) doesn't seem to help.
const config = {
/* openapi host */
//host: 'https://openapi.tuyacn.com',
host: 'https://openapi.tuyaeu.com',
/* fetch from openapi platform */
accessKey: 'I pasted here my Access ID/Client ID from iot.tuya.com',
/* fetch from openapi platform */
secretKey: 'I pasted here my Access Secret/Client Secret from iot.tuya.com',
/* Interface example device_ID */
deviceId: 'I pasted here Device ID of my GoSund smart plug',
};
const httpClient = axios.create({
baseURL: config.host,
timeout: 5 * 1e3,
});
async main(switchValue: boolean) {
try{
await this.getToken();
const data = await this.getDeviceInfo(config.deviceId, switchValue);
console.log('fetch success: ', JSON.stringify(data));
}catch(error){
console.log(error);
}
}
/**
* fetch highway login token
*/
async getToken() {
const method = 'GET';
const timestamp = Date.now().toString();
const signUrl = '/v1.0/token?grant_type=1';
const contentHash = crypto.createHash('sha256').update('').digest('hex');
const stringToSign = [method, contentHash, '', signUrl].join('\n');
const signStr = config.accessKey + timestamp + stringToSign;
const headers = {
t: timestamp,
sign_method: 'HMAC-SHA256',
client_id: config.accessKey,
sign: await this.encryptStr(signStr, config.secretKey),
};
const { data: login } = await httpClient.get('/v1.0/token?grant_type=1', { headers });
if (!login || !login.success) {
throw Error(`fetch failed: ${login.msg}`);
}
this.setState({ token: login.result.access_token })
}
/**
* fetch highway business data
*/
async getDeviceInfo(deviceId: string, switchValue: boolean) {
const query = {};
const method = 'POST';
const url = `/v1.0/devices/${deviceId}/commands`;
const reqHeaders: { [k: string]: string } = await this.getRequestSign(url, method, {}, query);
const { data } = await httpClient.request({
method,
data: {commands: [{code: "countdown_1", value: 0}, {code: "switch", value: switchValue}]},
params: {},
headers: reqHeaders,
url: reqHeaders.path,
});
if (!data || !data.success) {
throw Error(`request api failed: ${data.msg}`);
}
}
/**
* HMAC-SHA256 crypto function
*/
async encryptStr(str: string, secret: string): Promise<string> {
return crypto.createHmac('sha256', secret).update(str, 'utf8').digest('hex').toUpperCase();
}
/**
* request sign, save headers
* #param path
* #param method
* #param headers
* #param query
* #param body
*/
async getRequestSign(
path: string,
method: string,
headers: { [k: string]: string } = {},
query: { [k: string]: any } = {},
body: { [k: string]: any } = {},
) {
const t = Date.now().toString();
const [uri, pathQuery] = path.split('?');
const queryMerged = Object.assign(query, qs.parse(pathQuery));
const sortedQuery: { [k: string]: string } = {};
Object.keys(queryMerged)
.sort()
.forEach((i) => (sortedQuery[i] = query[i]));
const querystring = decodeURIComponent(qs.stringify(sortedQuery));
const url = querystring ? `${uri}?${querystring}` : uri;
const contentHash = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
const client_id = config.accessKey
const access_token = this.state.token
const stringToSign = [method, contentHash, '', url].join('\n');
const signStr = client_id + access_token + t + stringToSign;
return {
t,
path: url,
client_id: config.accessKey,
sign: await this.encryptStr(signStr, config.secretKey),
sign_method: 'HMAC-SHA256',
sign_version: '2.0',
access_token: access_token
};
}
Looks like you're not passing the body to the signature method.
The whole request needs to be signed including any body. You can't change the request details after signing it, except to add the sign header.
It's probably worth structuring your call into three steps - one to build up the request object. One to add the signing header based on the whole request object (so it's responsible for signing the right fields). Then finally send it to httpClient.request to make the call.
I presume there's a bit of left over "trying things out to get it working" in your code, e.g. setting the url to the requestHeaders.path. And I think you need a timestamp header in there too. All should be in the docu, or look at Tuya's postman collection's pre-request script.
Their example script has a few errors:
In getDeviceInfo():
set method to GET instead of POST
set url to "/v1.0/iot-03/devices/${deviceId}/functions" or "/v1.0/iot-03/devices/${deviceId}/specification"
return data; at the end, so it gets output
This made it work for me.
Ah, and this answer relates to today's version of their example:
import * as qs from 'qs';
import * as crypto from 'crypto';
import { default as axios } from 'axios';
let token = '';
const config = {
/* openapi host */
host: 'https://openapi.tuyacn.com',
/* fetch from openapi platform */
accessKey: '',
/* fetch from openapi platform */
secretKey: '',
/* Interface example device_ID */
deviceId: '',
};
const httpClient = axios.create({
baseURL: config.host,
timeout: 5 * 1e3,
});
async function main() {
await getToken();
const data = await getDeviceInfo(config.deviceId);
console.log('fetch success: ', JSON.stringify(data));
}
/**
* fetch highway login token
*/
async function getToken() {
const method = 'GET';
const timestamp = Date.now().toString();
const signUrl = '/v1.0/token?grant_type=1';
const contentHash = crypto.createHash('sha256').update('').digest('hex');
const stringToSign = [method, contentHash, '', signUrl].join('\n');
const signStr = config.accessKey + timestamp + stringToSign;
const headers = {
t: timestamp,
sign_method: 'HMAC-SHA256',
client_id: config.accessKey,
sign: await encryptStr(signStr, config.secretKey),
};
const { data: login } = await httpClient.get('/v1.0/token?grant_type=1', { headers });
if (!login || !login.success) {
throw Error(`fetch failed: ${login.msg}`);
}
token = login.result.access_token;
}
/**
* fetch highway business data
*/
async function getDeviceInfo(deviceId: string) {
const query = {};
const method = 'POST';
const url = `/v1.0/devices/${deviceId}/commands`;
const reqHeaders: { [k: string]: string } = await getRequestSign(url, method, {}, query);
const { data } = await httpClient.request({
method,
data: {},
params: {},
headers: reqHeaders,
url: reqHeaders.path,
});
if (!data || !data.success) {
throw Error(`request api failed: ${data.msg}`);
}
}
/**
* HMAC-SHA256 crypto function
*/
async function encryptStr(str: string, secret: string): Promise<string> {
return crypto.createHmac('sha256', secret).update(str, 'utf8').digest('hex').toUpperCase();
}
/**
* request sign, save headers
* #param path
* #param method
* #param headers
* #param query
* #param body
*/
async function getRequestSign(
path: string,
method: string,
headers: { [k: string]: string } = {},
query: { [k: string]: any } = {},
body: { [k: string]: any } = {},
) {
const t = Date.now().toString();
const [uri, pathQuery] = path.split('?');
const queryMerged = Object.assign(query, qs.parse(pathQuery));
const sortedQuery: { [k: string]: string } = {};
Object.keys(queryMerged)
.sort()
.forEach((i) => (sortedQuery[i] = query[i]));
const querystring = decodeURIComponent(qs.stringify(sortedQuery));
const url = querystring ? `${uri}?${querystring}` : uri;
const contentHash = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
const stringToSign = [method, contentHash, '', url].join('\n');
const signStr = config.accessKey + token + t + stringToSign;
return {
t,
path: url,
client_id: config.accessKey,
sign: await encryptStr(signStr, config.secretKey),
sign_method: 'HMAC-SHA256',
access_token: token,
};
}
main().catch(err => {
throw Error(`error: ${err}`);
});
I create a function that I can log in to my Spotify and get the access token and I create a function to refresh my token but it does not work properly when I pass it to the request function with Axios and it returns 400 or 404.
what should I do ?
here is my code :
const AUTH_URL =
" https://accounts.spotify.com/authorize?client_id=MY_ID&response_type=token&redirect_uri=http://localhost:3000/&scope=user-read-playback-state";
let Login = () => {
const spotifyHandle = (params) => {
const afterHashtag = params.substring(1);
const param = afterHashtag.split("&");
const paramsSplit = param.reduce((Para, currentPara) => {
const [key, value] = currentPara.split("=");
Para[key] = value;
return Para;
}, {});
return paramsSplit;
};
useEffect(() => {
if (window.location.hash) {
const { access_token, expires_in } = spotifyHandle(window.location.hash);
localStorage.clear();
localStorage.setItem("accessToken", access_token);
localStorage.setItem("expiresIn", expires_in);
}
});
return (
<div>
<a href={AUTH_URL}>
<button>Login</button>
</a>
</div>
);
};
here the refresh function:
let refresh = async () => {
const clientId = "id";
const clientSecret = "secret";
const headers = {
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
auth: {
username: clientId,
password: clientSecret,
},
};
const data = {
grant_type: "client_credentials",
};
try {
const response = await axios.post(
"https://accounts.spotify.com/api/token",
qs.stringify(data),
headers
);
console.log(response.data.access_token);
return response.data.access_token;
} catch (error) {
console.log(error);
}
};
The Spotify API follows the OAuth 2.0 specs and it requires (as presented at this Spotify's documentation section):
grant_type to be equal to authorization_code
code to be equal to the authorization code returned from the initial request to the Account /authorize endpoint
redirect_uri This parameter is used for validation only (there is no actual redirection). The value of this parameter must exactly match the value of redirect_uri supplied when requesting the authorization code.
And a Authorization is also required at the request header, as stated at the docs:
Base 64 encoded string that contains the client ID and client secret key. The field must have the format: Authorization: Basic *<base64 encoded client_id:client_secret>*
I'm trying to filter the results of a scan of a DynamoDB table using a FilterExpression in my query. I'm using a Lambda function deployed on API Gateway on AWS.
Function Querying the Endpoint
// Scan the table for entries and use data to populate HTML table.
async function scanTable(){
var query = {
TableName: "bookings",
ProjectionExpression: "id, username, start_booking, end_booking",
FilterExpression: "username = :u",
ExpressionAttributeValues: {":u":"human#gmail.com"}
};
try{
const response = await axios({ method: 'get', url: `${url}/bookings`, params: query });
}catch(err){
console.log(err);
}
}
Lambda Function
'use strict'
const AWS = require('aws-sdk');
exports.handler = async (event, context) => {
const documentClient = new AWS.DynamoDB.DocumentClient({convertEmptyValues: true});
let responseBody = "";
let statusCode = 0;
const params = (event.queryStringParameters);
// console.log(params);
try{
const data = await documentClient.scan(params).promise();
responseBody = JSON.stringify(data.Items);
statusCode = 200;
}catch(err){
responseBody = `Unable to get the bookings: ${err}`;
statusCode = 403;
}
console.log("Response Body: " + responseBody);
const response = {
statusCode: statusCode,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
body: responseBody
};
return response;
}
The complete error I receive is
ValidationException: ExpressionAttributeValues contains invalid key: Syntax error; key: "11"
where the key value at the end of the error (key:"11") changes somewhat dependent on the value assigned in ExpressionAttributeValues. I've tried to follow the FilterExpression example in the DynamoDB Docs for the scan function to no avail. Here is another example.
Here is a similar question from the AWS forums.
I have a pretty straight forward post request using axios.
I have played around with JSON.stringfy, adjusted my try/catch statement, and played around with various headers. No luck.
After doing some reading I think it has to do with the fact that I am not (req, res) anywhere. Thus, the lamda function returns undefined but the post request still works.
Just looking for a direction to go in because I am lost with how to implement (req,res).
const axios = require("axios");
// Declare spreadsheet and values to append
const spreadsheetId = "SECRET";
// build data for a POST request
const baseUrl = "https://pushtogsheet.herokuapp.com";
const query = `SECRET`;
const url = new URL(
`/proxy/google-sheets/${spreadsheetId}/values/A1:append?${query}`,
baseUrl
);
module.exports.handler = async (event, context) => {
try {
const dataHanlder = event.body;
const data = await JSON.parse(dataHanlder);
const {
firstName,
lastName,
email,
company,
empRange,
phone,
leadCountry,
signupType,
timezone,
utmSource,
utmMedium,
utmCampaign,
utmTerm,
utmContent,
} = data;
const excelArray = [
[
firstName,
lastName,
email,
company,
phone,
empRange,
timezone,
leadCountry,
signupType,
utmSource,
utmMedium,
utmCampaign,
utmTerm,
utmContent,
],
];
const excelString = await JSON.stringify({ values: excelArray });
const config = {
headers: {
"Pizzly-Auth-Id": "SECRET",
// "Content-Type": "text/plain",
},
};
const res = axios.post(url.href, excelString, config);
console.log("POST request status code", res.status);
} catch (error) {
console.log(error);
}
};
I'm writing a node.js script to generate a GitHub installation access token. Here's what I've got:
const axios = require("axios");
var fs = require('fs');
var jwt = require("jsonwebtoken");
var gitInstallationAccessToken = {
genJWTToken: function(callback) {
var private_key = fs.readFileSync("/path/to/my/pemfile.pem");
const now = Math.round(Date.now() / 1000);
const payload = {
iat : now,
exp : now + (10 * 60),
iss : 7233
};
const token = jwt.sign(payload, private_key, { algorithm: 'RS256' })
callback(token);
},
genInstallationAccessToken: function(token, callback) {
var jwt = gitInstallationAccessToken.genJWTToken(function(token) {
return token;
});
console.log("JWT: ", jwt)
var instance = axios({
method: "post",
url: "https://api.github.com/installations/:installation_id/access_tokens",
headers: {
"Accept" : "application/vnd.github.machine-man-preview+json",
"Authorization" : `Bearer ${jwt}`
}
})
.then(function(response) {
console.log("Response: ",response.data);
callback(response);
})
.catch(function(error) {
console.warn("Unable to authenticate");
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (error.response) {
console.warn(`Status ${error.response.status}`);
console.warn(`${error.response.data.message}`);
}
});
}
}
module.exports = gitInstallationAccessToken;
gitInstallationAccessToken.genInstallationAccessToken(function(response) {
console.log("response: ", response)
});
My JWT token is getting generated by genJWTToken. I can see that if I add a console.log("Token: ", token) before the callback in genJWTToken.
I now need to use that token in genInstallationAccessToken but I'm clearly calling it wrong. As the following returns undefined:
var jwt = gitInstallationAccessToken.genJWTToken(function(token) {
return token;
});
console.log("JWT: ", jwt)
How do I fix this?
I think you should consider refactoring this and use chained promises it will be easier to understand and control..
Something like this:
function getToken() {
return new Promise(function(resolve, reject) {
resolve('token')
})
}
function chainPromise() {
var token
getToken().then((response) => {
token = response
console.log(token)
}).then(() => {
console.log('I am here and also see: ', token)
})
}
chainPromise()
You should then be able to track down the path of your token quite easily