amazon-sp-api-timeout
Version:
Amazon Selling Partner API client
179 lines (156 loc) • 7.29 kB
JavaScript
const crypto = require('crypto-js');
const qs = require('qs');
const utils = require('./utils');
class Signer {
constructor(region, use_sandbox, user_agent){
this._region = region;
this._aws_regions = {
'eu':'eu-west-1',
'na':'us-east-1',
'fe':'us-west-2'
};
let sandbox_prefix = use_sandbox ? 'sandbox.' : '';
this._api_endpoint = sandbox_prefix + 'sellingpartnerapi-' + this._region + '.amazon.com';
this._user_agent = user_agent;
this._iso_date;
}
_createUTCISODate(){
let iso_date = new Date().toISOString().replace(/[:\-]|\.\d{3}/g, '');
this._iso_date = {
short:iso_date.substr(0,8),
full:iso_date
};
}
_encodeApiPath(api_path){
return api_path.split('/').map((url_part) => {
return utils.encodeURIComponent(url_part);
}).join('/');
}
_constructEncodedQueryString(query, encode_twice){
if (query){
let key_is_array = false;
return qs.stringify(query, {
encoder:(value, defaultEncoder, charset, type) => {
if (type === 'key'){
key_is_array = Array.isArray(query[value]);
}
return encode_twice ? utils.doubleEncodeURICompenent(value, key_is_array) : utils.encodeURIComponent(value);
},
arrayFormat:'comma',
commaRoundTrip:false,
sort:(a, b) => {
return a.localeCompare(b);
}
});
}
return '';
}
_constructCanonicalRequestForRoleCredentials(encoded_query_string){
let canonical = [];
canonical.push('POST');
canonical.push('/');
canonical.push('');
canonical.push('host:sts.amazonaws.com');
canonical.push('user-agent:' + this._user_agent);
canonical.push('x-amz-content-sha256:' + crypto.SHA256(encoded_query_string));
canonical.push('x-amz-date:' + this._iso_date.full);
canonical.push('');
canonical.push('host;user-agent;x-amz-content-sha256;x-amz-date');
canonical.push(crypto.SHA256(encoded_query_string));
return canonical.join('\n');
}
_constructCanonicalRequestForAPI(access_token, params, encoded_query_string){
let canonical = [];
canonical.push(params.method);
// URI components must be double encoded when constructing the canonical request
// --> https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
// First encode is done inside version operation definitions when constructing the api_path (utils.checkAndEncodeParams)
canonical.push(this._encodeApiPath(params.api_path));
canonical.push(encoded_query_string);
canonical.push('host:' + this._api_endpoint);
canonical.push('user-agent:' + this._user_agent);
canonical.push('x-amz-access-token:' + access_token);
canonical.push('x-amz-date:' + this._iso_date.full);
canonical.push('');
canonical.push('host;user-agent;x-amz-access-token;x-amz-date');
canonical.push(crypto.SHA256(params.body ? JSON.stringify(params.body) : ''));
return canonical.join('\n');
}
_constructStringToSign(region, action_type, canonical_request){
let string_to_sign = [];
string_to_sign.push('AWS4-HMAC-SHA256')
string_to_sign.push(this._iso_date.full);
string_to_sign.push(this._iso_date.short + '/' + region + '/' + action_type + '/aws4_request');
string_to_sign.push(crypto.SHA256(canonical_request));
return string_to_sign.join('\n');
}
_constructSignature(region, action_type, string_to_sign, secret){
let signature = crypto.HmacSHA256(this._iso_date.short, 'AWS4' + secret);
signature = crypto.HmacSHA256(region, signature);
signature = crypto.HmacSHA256(action_type, signature);
signature = crypto.HmacSHA256('aws4_request', signature);
return crypto.HmacSHA256(string_to_sign, signature).toString(crypto.enc.Hex);
}
_constructURL(req_params, encoded_query_string){
// We don't have to encode api_path parts here because parts have already been encoded in version operation definitions
let url = 'https://' + this._api_endpoint + req_params.api_path;
if (encoded_query_string !== ''){
url += '?' + encoded_query_string;
}
return url;
}
signAPIRequest(access_token, role_credentials, req_params){
this._createUTCISODate();
let encoded_query_string = this._constructEncodedQueryString(req_params.query, req_params.encode_twice);
let canonical_request = this._constructCanonicalRequestForAPI(access_token, req_params, encoded_query_string);
let string_to_sign = this._constructStringToSign(this._aws_regions[this._region], 'execute-api', canonical_request);
let signature = this._constructSignature(this._aws_regions[this._region], 'execute-api', string_to_sign, role_credentials.secret);
return {
method:req_params.method,
url:this._constructURL(req_params, encoded_query_string),
body:req_params.body ? JSON.stringify(req_params.body) : null,
headers:{
'Authorization':'AWS4-HMAC-SHA256 Credential=' + role_credentials.id + '/' + this._iso_date.short + '/' + this._aws_regions[this._region] + '/execute-api/aws4_request, SignedHeaders=host;user-agent;x-amz-access-token;x-amz-date, Signature=' + signature,
'Content-Type': 'application/json; charset=utf-8',
'host':this._api_endpoint,
'user-agent':this._user_agent,
'x-amz-access-token':access_token,
'x-amz-security-token':role_credentials.security_token,
'x-amz-date':this._iso_date.full
},
response_timeout: req_params.options.response_timeout ? req_params.options.response_timeout : null,
deadline_timeout: req_params.options.deadline_timeout ? req_params.options.deadline_timeout : null,
proxy: req_params.options.proxy ? req_params.options.proxy : null
};
}
signRoleCredentialsRequest(aws_user){
let query = {
'Action':'AssumeRole',
'DurationSeconds':'3600',
'RoleArn':aws_user.role,
'RoleSessionName':'SPAPISession',
'Version':'2011-06-15'
};
this._createUTCISODate();
let encoded_query_string = this._constructEncodedQueryString(query);
let canonical_request = this._constructCanonicalRequestForRoleCredentials(encoded_query_string);
// TODO: Find out why role credentials request only works with us-east-1 AWS region
let string_to_sign = this._constructStringToSign('us-east-1', 'sts', canonical_request);
let signature = this._constructSignature('us-east-1', 'sts', string_to_sign, aws_user.secret);
return {
method:'POST',
url:'https://sts.amazonaws.com',
body:encoded_query_string,
headers:{
'Authorization':'AWS4-HMAC-SHA256 Credential=' + aws_user.id + '/' + this._iso_date.short + '/us-east-1/sts/aws4_request, SignedHeaders=host;user-agent;x-amz-content-sha256;x-amz-date, Signature=' + signature,
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Host':'sts.amazonaws.com',
'user-agent': this._user_agent,
...(aws_user.session_token ? {'X-Amz-Security-Token': aws_user.session_token} : {}), // include only if available
'X-Amz-Content-Sha256':crypto.SHA256(encoded_query_string).toString(crypto.enc.Hex),
'X-Amz-Date':this._iso_date.full
}
};
}
};
module.exports = Signer;