2025-04-24 17:03:28 +08:00

159 lines
3.9 KiB
JavaScript

'use strict';
var debug = require('debug')('ali-oss:sts');
var crypto = require('crypto');
var querystring = require('querystring');
var copy = require('copy-to');
var AgentKeepalive = require('agentkeepalive');
var is = require('is-type-of');
var ms = require('humanize-ms');
var urllib = require('urllib');
var globalHttpAgent = new AgentKeepalive();
module.exports = STS;
function STS(options) {
if (!(this instanceof STS)) {
return new STS(options);
}
if (!options
|| !options.accessKeyId
|| !options.accessKeySecret) {
throw new Error('require accessKeyId, accessKeySecret');
}
this.options = {
endpoint: options.endpoint || 'https://sts.aliyuncs.com',
format: 'JSON',
apiVersion: '2015-04-01',
sigMethod: 'HMAC-SHA1',
sigVersion: '1.0',
timeout: '60s'
};
copy(options).to(this.options);
// support custom agent and urllib client
if (this.options.urllib) {
this.urllib = this.options.urllib;
} else {
this.urllib = urllib;
this.agent = this.options.agent || globalHttpAgent;
}
};
var proto = STS.prototype;
/**
* STS opertaions
*/
proto.assumeRole = function* assumeRole(role, policy, expiration, session, options) {
var opts = this.options;
var params = {
'Action': 'AssumeRole',
'RoleArn': role,
'RoleSessionName': session || 'app',
'DurationSeconds': expiration || 3600,
'Format': opts.format,
'Version': opts.apiVersion,
'AccessKeyId': opts.accessKeyId,
'SignatureMethod': opts.sigMethod,
'SignatureVersion': opts.sigVersion,
'SignatureNonce': Math.random(),
'Timestamp': new Date().toISOString()
};
if (policy) {
var policyStr;
if (is.string(policy)) {
try {
policyStr = JSON.stringify(JSON.parse(policy));
} catch (err) {
throw new Error('Policy string is not a valid JSON: ' + err.message);
}
} else {
policyStr = JSON.stringify(policy);
}
params.Policy = policyStr;
}
var signature = this._getSignature('POST', params, opts.accessKeySecret);
params.Signature = signature;
var reqUrl = opts.endpoint;
var reqParams = {
agent: this.agent,
timeout: ms(options && options.timeout || opts.timeout),
method: 'POST',
content: querystring.stringify(params),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
ctx: options && options.ctx,
};
var result = yield this.urllib.request(reqUrl, reqParams);
debug('response %s %s, got %s, headers: %j',
reqParams.method, reqUrl, result.status, result.headers);
if (Math.floor(result.status / 100) !== 2) {
var err = yield this._requestError(result);
err.params = reqParams;
throw err;
}
result.data = JSON.parse(result.data);
return {
res: result.res,
credentials: result.data.Credentials
};
};
proto._requestError = function* _requestError(result) {
var err = new Error();
err.status = result.status;
try {
var resp = yield JSON.parse(result.data) || {};
err.code = resp.Code;
err.message = resp.Code + ': ' + resp.Message;
err.requestId = resp.RequestId;
} catch (e) {
err.message = 'UnknownError: ' + String(result.data);
}
return err;
};
proto._getSignature = function _getSignature(method, params, key) {
var that = this;
var canoQuery = Object.keys(params).sort().map(function (key) {
return that._escape(key) + '=' + that._escape(params[key])
}).join('&');
var stringToSign =
method.toUpperCase() +
'&' + this._escape('/') +
'&' + this._escape(canoQuery);
debug('string to sign: %s', stringToSign);
var signature = crypto.createHmac('sha1', key + '&');
signature = signature.update(stringToSign).digest('base64');
debug('signature: %s', signature);
return signature;
};
/**
* Since `encodeURIComponent` doesn't encode '*', which causes
* 'SignatureDoesNotMatch'. We need do it ourselves.
*/
proto._escape = function _escape(str) {
return encodeURIComponent(str).replace(/\*/g, '%2A');
};