12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
- import { GaxiosOptions } from 'gaxios' ;
15
+ import { Gaxios , GaxiosOptions } from 'gaxios' ;
16
16
17
- import { Headers } from './authclient' ;
17
+ import { HeadersInit } from './authclient' ;
18
18
import { Crypto , createCrypto , fromArrayBufferToHex } from '../crypto/crypto' ;
19
19
20
- type HttpMethod =
21
- | 'GET'
22
- | 'POST'
23
- | 'PUT'
24
- | 'PATCH'
25
- | 'HEAD'
26
- | 'DELETE'
27
- | 'CONNECT'
28
- | 'OPTIONS'
29
- | 'TRACE' ;
30
-
31
20
/** Interface defining the AWS authorization header map for signed requests. */
32
21
interface AwsAuthHeaderMap {
33
22
amzDate ?: string ;
@@ -60,15 +49,15 @@ interface GenerateAuthHeaderMapOptions {
60
49
// The AWS service URL query string.
61
50
canonicalQuerystring : string ;
62
51
// The HTTP method used to call this API.
63
- method : HttpMethod ;
52
+ method : string ;
64
53
// The AWS region.
65
54
region : string ;
66
55
// The AWS security credentials.
67
56
securityCredentials : AwsSecurityCredentials ;
68
57
// The optional request payload if available.
69
58
requestPayload ?: string ;
70
59
// The optional additional headers needed for the requested AWS API.
71
- additionalAmzHeaders ?: Headers ;
60
+ additionalAmzHeaders ?: HeadersInit ;
72
61
}
73
62
74
63
/** AWS Signature Version 4 signing algorithm identifier. */
@@ -113,7 +102,7 @@ export class AwsRequestSigner {
113
102
*/
114
103
async getRequestOptions ( amzOptions : GaxiosOptions ) : Promise < GaxiosOptions > {
115
104
if ( ! amzOptions . url ) {
116
- throw new Error ( '"url" is required in "amzOptions"' ) ;
105
+ throw new RangeError ( '"url" is required in "amzOptions"' ) ;
117
106
}
118
107
// Stringify JSON requests. This will be set in the request body of the
119
108
// generated signed request.
@@ -127,19 +116,26 @@ export class AwsRequestSigner {
127
116
const additionalAmzHeaders = amzOptions . headers ;
128
117
const awsSecurityCredentials = await this . getCredentials ( ) ;
129
118
const uri = new URL ( url ) ;
119
+
120
+ if ( typeof requestPayload !== 'string' && requestPayload !== undefined ) {
121
+ throw new TypeError (
122
+ `'requestPayload' is expected to be a string if provided. Got: ${ requestPayload } `
123
+ ) ;
124
+ }
125
+
130
126
const headerMap = await generateAuthenticationHeaderMap ( {
131
127
crypto : this . crypto ,
132
128
host : uri . host ,
133
129
canonicalUri : uri . pathname ,
134
- canonicalQuerystring : uri . search . substr ( 1 ) ,
130
+ canonicalQuerystring : uri . search . slice ( 1 ) ,
135
131
method,
136
132
region : this . region ,
137
133
securityCredentials : awsSecurityCredentials ,
138
134
requestPayload,
139
135
additionalAmzHeaders,
140
136
} ) ;
141
137
// Append additional optional headers, eg. X-Amz-Target, Content-Type, etc.
142
- const headers : { [ key : string ] : string } = Object . assign (
138
+ const headers = Gaxios . mergeHeaders (
143
139
// Add x-amz-date if available.
144
140
headerMap . amzDate ? { 'x-amz-date' : headerMap . amzDate } : { } ,
145
141
{
@@ -149,7 +145,7 @@ export class AwsRequestSigner {
149
145
additionalAmzHeaders || { }
150
146
) ;
151
147
if ( awsSecurityCredentials . token ) {
152
- Object . assign ( headers , {
148
+ Gaxios . mergeHeaders ( headers , {
153
149
'x-amz-security-token' : awsSecurityCredentials . token ,
154
150
} ) ;
155
151
}
@@ -159,7 +155,7 @@ export class AwsRequestSigner {
159
155
headers,
160
156
} ;
161
157
162
- if ( typeof requestPayload !== ' undefined' ) {
158
+ if ( requestPayload !== undefined ) {
163
159
awsSignedReq . body = requestPayload ;
164
160
}
165
161
@@ -223,7 +219,9 @@ async function getSigningKey(
223
219
async function generateAuthenticationHeaderMap (
224
220
options : GenerateAuthHeaderMapOptions
225
221
) : Promise < AwsAuthHeaderMap > {
226
- const additionalAmzHeaders = options . additionalAmzHeaders || { } ;
222
+ const additionalAmzHeaders = Gaxios . mergeHeaders (
223
+ options . additionalAmzHeaders
224
+ ) ;
227
225
const requestPayload = options . requestPayload || '' ;
228
226
// iam.amazonaws.com host => iam service.
229
227
// sts.us-east-2.amazonaws.com => sts service.
@@ -237,38 +235,38 @@ async function generateAuthenticationHeaderMap(
237
235
// Format: '%Y%m%d'.
238
236
const dateStamp = now . toISOString ( ) . replace ( / [ - ] / g, '' ) . replace ( / T .* / , '' ) ;
239
237
240
- // Change all additional headers to be lower case.
241
- const reformattedAdditionalAmzHeaders : Headers = { } ;
242
- Object . keys ( additionalAmzHeaders ) . forEach ( key => {
243
- reformattedAdditionalAmzHeaders [ key . toLowerCase ( ) ] =
244
- additionalAmzHeaders [ key ] ;
245
- } ) ;
246
238
// Add AWS token if available.
247
239
if ( options . securityCredentials . token ) {
248
- reformattedAdditionalAmzHeaders [ 'x-amz-security-token' ] =
249
- options . securityCredentials . token ;
240
+ additionalAmzHeaders . set (
241
+ 'x-amz-security-token' ,
242
+ options . securityCredentials . token
243
+ ) ;
250
244
}
251
245
// Header keys need to be sorted alphabetically.
252
- const amzHeaders = Object . assign (
246
+ const amzHeaders = Gaxios . mergeHeaders (
253
247
{
254
248
host : options . host ,
255
249
} ,
256
250
// Previously the date was not fixed with x-amz- and could be provided manually.
257
251
// https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req
258
- reformattedAdditionalAmzHeaders . date ? { } : { 'x-amz-date' : amzDate } ,
259
- reformattedAdditionalAmzHeaders
252
+ additionalAmzHeaders . has ( ' date' ) ? { } : { 'x-amz-date' : amzDate } ,
253
+ additionalAmzHeaders
260
254
) ;
261
255
let canonicalHeaders = '' ;
262
- const signedHeadersList = Object . keys ( amzHeaders ) . sort ( ) ;
256
+
257
+ // TypeScript is missing `Headers#keys` at the time of writing
258
+ const signedHeadersList = [
259
+ ...( amzHeaders as Headers & { keys : ( ) => string [ ] } ) . keys ( ) ,
260
+ ] . sort ( ) ;
263
261
signedHeadersList . forEach ( key => {
264
- canonicalHeaders += `${ key } :${ amzHeaders [ key ] } \n` ;
262
+ canonicalHeaders += `${ key } :${ amzHeaders . get ( key ) } \n` ;
265
263
} ) ;
266
264
const signedHeaders = signedHeadersList . join ( ';' ) ;
267
265
268
266
const payloadHash = await options . crypto . sha256DigestHex ( requestPayload ) ;
269
267
// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
270
268
const canonicalRequest =
271
- `${ options . method } \n` +
269
+ `${ options . method . toUpperCase ( ) } \n` +
272
270
`${ options . canonicalUri } \n` +
273
271
`${ options . canonicalQuerystring } \n` +
274
272
`${ canonicalHeaders } \n` +
@@ -298,7 +296,7 @@ async function generateAuthenticationHeaderMap(
298
296
299
297
return {
300
298
// Do not return x-amz-date if date is available.
301
- amzDate : reformattedAdditionalAmzHeaders . date ? undefined : amzDate ,
299
+ amzDate : additionalAmzHeaders . has ( ' date' ) ? undefined : amzDate ,
302
300
authorizationHeader,
303
301
canonicalQuerystring : options . canonicalQuerystring ,
304
302
} ;
0 commit comments