UNPKG

4.14 kBJavaScriptView Raw
1
2const debug = require('debug')('ali-oss:sts');
3const crypto = require('crypto');
4const querystring = require('querystring');
5const copy = require('copy-to');
6const AgentKeepalive = require('agentkeepalive');
7const is = require('is-type-of');
8const ms = require('humanize-ms');
9const urllib = require('urllib');
10
11const globalHttpAgent = new AgentKeepalive();
12
13
14function STS(options) {
15 if (!(this instanceof STS)) {
16 return new STS(options);
17 }
18
19 if (!options
20 || !options.accessKeyId
21 || !options.accessKeySecret) {
22 throw new Error('require accessKeyId, accessKeySecret');
23 }
24
25 this.options = {
26 endpoint: options.endpoint || 'https://sts.aliyuncs.com',
27 format: 'JSON',
28 apiVersion: '2015-04-01',
29 sigMethod: 'HMAC-SHA1',
30 sigVersion: '1.0',
31 timeout: '60s'
32 };
33 copy(options).to(this.options);
34
35 // support custom agent and urllib client
36 if (this.options.urllib) {
37 this.urllib = this.options.urllib;
38 } else {
39 this.urllib = urllib;
40 this.agent = this.options.agent || globalHttpAgent;
41 }
42}
43
44module.exports = STS;
45
46const proto = STS.prototype;
47
48/**
49 * STS opertaions
50 */
51
52proto.assumeRole = async function assumeRole(role, policy, expiration, session, options) {
53 const opts = this.options;
54 const params = {
55 Action: 'AssumeRole',
56 RoleArn: role,
57 RoleSessionName: session || 'app',
58 DurationSeconds: expiration || 3600,
59
60 Format: opts.format,
61 Version: opts.apiVersion,
62 AccessKeyId: opts.accessKeyId,
63 SignatureMethod: opts.sigMethod,
64 SignatureVersion: opts.sigVersion,
65 SignatureNonce: Math.random(),
66 Timestamp: new Date().toISOString()
67 };
68
69 if (policy) {
70 let policyStr;
71 if (is.string(policy)) {
72 try {
73 policyStr = JSON.stringify(JSON.parse(policy));
74 } catch (err) {
75 throw new Error(`Policy string is not a valid JSON: ${err.message}`);
76 }
77 } else {
78 policyStr = JSON.stringify(policy);
79 }
80 params.Policy = policyStr;
81 }
82
83 const signature = this._getSignature('POST', params, opts.accessKeySecret);
84 params.Signature = signature;
85
86 const reqUrl = opts.endpoint;
87 const reqParams = {
88 agent: this.agent,
89 timeout: ms((options && options.timeout) || opts.timeout),
90 method: 'POST',
91 content: querystring.stringify(params),
92 headers: {
93 'Content-Type': 'application/x-www-form-urlencoded'
94 },
95 ctx: options && options.ctx
96 };
97
98 const result = await this.urllib.request(reqUrl, reqParams);
99 debug(
100 'response %s %s, got %s, headers: %j',
101 reqParams.method, reqUrl, result.status, result.headers
102 );
103
104 if (Math.floor(result.status / 100) !== 2) {
105 const err = await this._requestError(result);
106 err.params = reqParams;
107 throw err;
108 }
109 result.data = JSON.parse(result.data);
110
111 return {
112 res: result.res,
113 credentials: result.data.Credentials
114 };
115};
116
117proto._requestError = async function _requestError(result) {
118 const err = new Error();
119 err.status = result.status;
120
121 try {
122 const resp = await JSON.parse(result.data) || {};
123 err.code = resp.Code;
124 err.message = `${resp.Code}: ${resp.Message}`;
125 err.requestId = resp.RequestId;
126 } catch (e) {
127 err.message = `UnknownError: ${String(result.data)}`;
128 }
129
130 return err;
131};
132
133proto._getSignature = function _getSignature(method, params, key) {
134 const that = this;
135 const canoQuery = Object.keys(params).sort().map(k => `${that._escape(k)}=${that._escape(params[k])}`).join('&');
136
137 const stringToSign =
138 `${method.toUpperCase()
139 }&${this._escape('/')
140 }&${this._escape(canoQuery)}`;
141
142 debug('string to sign: %s', stringToSign);
143
144 let signature = crypto.createHmac('sha1', `${key}&`);
145 signature = signature.update(stringToSign).digest('base64');
146
147 debug('signature: %s', signature);
148
149 return signature;
150};
151
152/**
153 * Since `encodeURIComponent` doesn't encode '*', which causes
154 * 'SignatureDoesNotMatch'. We need do it ourselves.
155 */
156proto._escape = function _escape(str) {
157 return encodeURIComponent(str)
158 .replace(/!/g, '%21')
159 .replace(/'/g, '%27')
160 .replace(/\(/g, '%28')
161 .replace(/\)/g, '%29')
162 .replace(/\*/g, '%2A');
163};