@hanzo/s3 0.6.3 → 8.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/MAINTAINERS.md +62 -0
- package/README.md +262 -0
- package/README_zh_CN.md +192 -0
- package/dist/esm/AssumeRoleProvider.d.mts +86 -0
- package/dist/esm/AssumeRoleProvider.mjs +183 -0
- package/dist/esm/CredentialProvider.d.mts +22 -0
- package/dist/esm/CredentialProvider.mjs +48 -0
- package/dist/esm/Credentials.d.mts +22 -0
- package/dist/esm/Credentials.mjs +38 -0
- package/dist/esm/IamAwsProvider.d.mts +27 -0
- package/dist/esm/IamAwsProvider.mjs +189 -0
- package/dist/esm/errors.d.mts +82 -0
- package/dist/esm/errors.mjs +117 -0
- package/dist/esm/helpers.d.mts +156 -0
- package/dist/esm/helpers.mjs +218 -0
- package/dist/esm/internal/async.d.mts +9 -0
- package/dist/esm/internal/async.mjs +14 -0
- package/dist/esm/internal/callbackify.d.mts +1 -0
- package/dist/esm/internal/callbackify.mjs +15 -0
- package/dist/esm/internal/client.d.mts +394 -0
- package/dist/esm/internal/client.mjs +3007 -0
- package/dist/esm/internal/copy-conditions.d.mts +10 -0
- package/dist/esm/internal/copy-conditions.mjs +25 -0
- package/dist/esm/internal/extensions.d.mts +18 -0
- package/dist/esm/internal/extensions.mjs +114 -0
- package/dist/esm/internal/helper.d.mts +177 -0
- package/dist/esm/internal/helper.mjs +552 -0
- package/dist/esm/internal/join-host-port.d.mts +11 -0
- package/dist/esm/internal/join-host-port.mjs +23 -0
- package/dist/esm/internal/post-policy.d.mts +17 -0
- package/dist/esm/internal/post-policy.mjs +98 -0
- package/dist/esm/internal/request.d.mts +11 -0
- package/dist/esm/internal/request.mjs +75 -0
- package/dist/esm/internal/response.d.mts +8 -0
- package/dist/esm/internal/response.mjs +16 -0
- package/dist/esm/internal/s3-endpoints.d.mts +38 -0
- package/dist/esm/internal/s3-endpoints.mjs +68 -0
- package/dist/esm/internal/type.d.mts +482 -0
- package/dist/esm/internal/type.mjs +30 -0
- package/dist/esm/internal/xml-parser.d.mts +93 -0
- package/dist/esm/internal/xml-parser.mjs +819 -0
- package/dist/esm/notification.d.mts +58 -0
- package/dist/esm/notification.mjs +209 -0
- package/dist/esm/s3.d.mts +40 -0
- package/dist/esm/s3.mjs +86 -0
- package/dist/esm/signing.d.mts +5 -0
- package/dist/esm/signing.mjs +258 -0
- package/dist/main/AssumeRoleProvider.d.ts +86 -0
- package/dist/main/AssumeRoleProvider.js +191 -0
- package/dist/main/CredentialProvider.d.ts +22 -0
- package/dist/main/CredentialProvider.js +55 -0
- package/dist/main/Credentials.d.ts +22 -0
- package/dist/main/Credentials.js +45 -0
- package/dist/main/IamAwsProvider.d.ts +27 -0
- package/dist/main/IamAwsProvider.js +198 -0
- package/dist/main/errors.d.ts +82 -0
- package/dist/main/errors.js +138 -0
- package/dist/main/helpers.d.ts +156 -0
- package/dist/main/helpers.js +233 -0
- package/dist/main/internal/async.d.ts +9 -0
- package/dist/main/internal/async.js +24 -0
- package/dist/main/internal/callbackify.d.ts +1 -0
- package/dist/main/internal/callbackify.js +21 -0
- package/dist/main/internal/client.d.ts +394 -0
- package/dist/main/internal/client.js +3014 -0
- package/dist/main/internal/copy-conditions.d.ts +10 -0
- package/dist/main/internal/copy-conditions.js +31 -0
- package/dist/main/internal/extensions.d.ts +18 -0
- package/dist/main/internal/extensions.js +122 -0
- package/dist/main/internal/helper.d.ts +177 -0
- package/dist/main/internal/helper.js +608 -0
- package/dist/main/internal/join-host-port.d.ts +11 -0
- package/dist/main/internal/join-host-port.js +29 -0
- package/dist/main/internal/post-policy.d.ts +17 -0
- package/dist/main/internal/post-policy.js +107 -0
- package/dist/main/internal/request.d.ts +11 -0
- package/dist/main/internal/request.js +83 -0
- package/dist/main/internal/response.d.ts +8 -0
- package/dist/main/internal/response.js +24 -0
- package/dist/main/internal/s3-endpoints.d.ts +38 -0
- package/dist/main/internal/s3-endpoints.js +73 -0
- package/dist/main/internal/type.d.ts +482 -0
- package/dist/main/internal/type.js +42 -0
- package/dist/main/internal/xml-parser.d.ts +93 -0
- package/dist/main/internal/xml-parser.js +849 -0
- package/dist/main/notification.d.ts +58 -0
- package/dist/main/notification.js +230 -0
- package/dist/main/s3.d.ts +40 -0
- package/dist/main/s3.js +117 -0
- package/dist/main/signing.d.ts +5 -0
- package/dist/main/signing.js +269 -0
- package/package.json +146 -39
- package/src/AssumeRoleProvider.ts +262 -0
- package/src/CredentialProvider.ts +54 -0
- package/src/Credentials.ts +44 -0
- package/src/IamAwsProvider.ts +234 -0
- package/src/errors.ts +120 -0
- package/src/helpers.ts +354 -0
- package/src/internal/async.ts +14 -0
- package/src/internal/callbackify.ts +19 -0
- package/src/internal/client.ts +3412 -0
- package/src/internal/copy-conditions.ts +30 -0
- package/src/internal/extensions.ts +140 -0
- package/src/internal/helper.ts +606 -0
- package/src/internal/join-host-port.ts +23 -0
- package/src/internal/post-policy.ts +99 -0
- package/src/internal/request.ts +102 -0
- package/src/internal/response.ts +26 -0
- package/src/internal/s3-endpoints.ts +70 -0
- package/src/internal/type.ts +577 -0
- package/src/internal/xml-parser.ts +871 -0
- package/src/notification.ts +254 -0
- package/src/s3.ts +155 -0
- package/src/signing.ts +325 -0
- package/lib/index.js +0 -450
- package/lib/index.js.map +0 -7
- package/lib/perfTest.js +0 -91
- package/lib/perfTest.js.map +0 -7
package/src/signing.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hanzo S3 Javascript Library for Amazon S3 Compatible Cloud Storage, (C) 2016 Hanzo AI, Inc.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as crypto from 'node:crypto'
|
|
18
|
+
|
|
19
|
+
import * as errors from './errors.ts'
|
|
20
|
+
import { PRESIGN_EXPIRY_DAYS_MAX } from './helpers.ts'
|
|
21
|
+
import { getScope, isNumber, isObject, isString, makeDateLong, makeDateShort, uriEscape } from './internal/helper.ts'
|
|
22
|
+
import type { ICanonicalRequest, IRequest, RequestHeaders } from './internal/type.ts'
|
|
23
|
+
|
|
24
|
+
const signV4Algorithm = 'AWS4-HMAC-SHA256'
|
|
25
|
+
|
|
26
|
+
// getCanonicalRequest generate a canonical request of style.
|
|
27
|
+
//
|
|
28
|
+
// canonicalRequest =
|
|
29
|
+
// <HTTPMethod>\n
|
|
30
|
+
// <CanonicalURI>\n
|
|
31
|
+
// <CanonicalQueryString>\n
|
|
32
|
+
// <CanonicalHeaders>\n
|
|
33
|
+
// <SignedHeaders>\n
|
|
34
|
+
// <HashedPayload>
|
|
35
|
+
//
|
|
36
|
+
function getCanonicalRequest(
|
|
37
|
+
method: string,
|
|
38
|
+
path: string,
|
|
39
|
+
headers: RequestHeaders,
|
|
40
|
+
signedHeaders: string[],
|
|
41
|
+
hashedPayload: string,
|
|
42
|
+
): ICanonicalRequest {
|
|
43
|
+
if (!isString(method)) {
|
|
44
|
+
throw new TypeError('method should be of type "string"')
|
|
45
|
+
}
|
|
46
|
+
if (!isString(path)) {
|
|
47
|
+
throw new TypeError('path should be of type "string"')
|
|
48
|
+
}
|
|
49
|
+
if (!isObject(headers)) {
|
|
50
|
+
throw new TypeError('headers should be of type "object"')
|
|
51
|
+
}
|
|
52
|
+
if (!Array.isArray(signedHeaders)) {
|
|
53
|
+
throw new TypeError('signedHeaders should be of type "array"')
|
|
54
|
+
}
|
|
55
|
+
if (!isString(hashedPayload)) {
|
|
56
|
+
throw new TypeError('hashedPayload should be of type "string"')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const headersArray = signedHeaders.reduce((acc, i) => {
|
|
60
|
+
// Trim spaces from the value (required by V4 spec)
|
|
61
|
+
const val = `${headers[i]}`.replace(/ +/g, ' ')
|
|
62
|
+
acc.push(`${i.toLowerCase()}:${val}`)
|
|
63
|
+
return acc
|
|
64
|
+
}, [] as string[])
|
|
65
|
+
|
|
66
|
+
const requestResource = path.split('?')[0]
|
|
67
|
+
let requestQuery = path.split('?')[1]
|
|
68
|
+
if (!requestQuery) {
|
|
69
|
+
requestQuery = ''
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (requestQuery) {
|
|
73
|
+
requestQuery = requestQuery
|
|
74
|
+
.split('&')
|
|
75
|
+
.sort()
|
|
76
|
+
.map((element) => (!element.includes('=') ? element + '=' : element))
|
|
77
|
+
.join('&')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
method.toUpperCase(),
|
|
82
|
+
requestResource,
|
|
83
|
+
requestQuery,
|
|
84
|
+
headersArray.join('\n') + '\n',
|
|
85
|
+
signedHeaders.join(';').toLowerCase(),
|
|
86
|
+
hashedPayload,
|
|
87
|
+
].join('\n')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// generate a credential string
|
|
91
|
+
function getCredential(accessKey: string, region: string, requestDate?: Date, serviceName = 's3') {
|
|
92
|
+
if (!isString(accessKey)) {
|
|
93
|
+
throw new TypeError('accessKey should be of type "string"')
|
|
94
|
+
}
|
|
95
|
+
if (!isString(region)) {
|
|
96
|
+
throw new TypeError('region should be of type "string"')
|
|
97
|
+
}
|
|
98
|
+
if (!isObject(requestDate)) {
|
|
99
|
+
throw new TypeError('requestDate should be of type "object"')
|
|
100
|
+
}
|
|
101
|
+
return `${accessKey}/${getScope(region, requestDate, serviceName)}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Returns signed headers array - alphabetically sorted
|
|
105
|
+
function getSignedHeaders(headers: RequestHeaders): string[] {
|
|
106
|
+
if (!isObject(headers)) {
|
|
107
|
+
throw new TypeError('request should be of type "object"')
|
|
108
|
+
}
|
|
109
|
+
// Excerpts from @lsegal - https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258
|
|
110
|
+
//
|
|
111
|
+
// User-Agent:
|
|
112
|
+
//
|
|
113
|
+
// This is ignored from signing because signing this causes problems with generating pre-signed URLs
|
|
114
|
+
// (that are executed by other agents) or when customers pass requests through proxies, which may
|
|
115
|
+
// modify the user-agent.
|
|
116
|
+
//
|
|
117
|
+
// Content-Length:
|
|
118
|
+
//
|
|
119
|
+
// This is ignored from signing because generating a pre-signed URL should not provide a content-length
|
|
120
|
+
// constraint, specifically when vending a S3 pre-signed PUT URL. The corollary to this is that when
|
|
121
|
+
// sending regular requests (non-pre-signed), the signature contains a checksum of the body, which
|
|
122
|
+
// implicitly validates the payload length (since changing the number of bytes would change the checksum)
|
|
123
|
+
// and therefore this header is not valuable in the signature.
|
|
124
|
+
//
|
|
125
|
+
// Content-Type:
|
|
126
|
+
//
|
|
127
|
+
// Signing this header causes quite a number of problems in browser environments, where browsers
|
|
128
|
+
// like to modify and normalize the content-type header in different ways. There is more information
|
|
129
|
+
// on this in https://github.com/aws/aws-sdk-js/issues/244. Avoiding this field simplifies logic
|
|
130
|
+
// and reduces the possibility of future bugs
|
|
131
|
+
//
|
|
132
|
+
// Authorization:
|
|
133
|
+
//
|
|
134
|
+
// Is skipped for obvious reasons
|
|
135
|
+
|
|
136
|
+
const ignoredHeaders = ['authorization', 'content-length', 'content-type', 'user-agent']
|
|
137
|
+
return Object.keys(headers)
|
|
138
|
+
.filter((header) => !ignoredHeaders.includes(header))
|
|
139
|
+
.sort()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// returns the key used for calculating signature
|
|
143
|
+
function getSigningKey(date: Date, region: string, secretKey: string, serviceName = 's3') {
|
|
144
|
+
if (!isObject(date)) {
|
|
145
|
+
throw new TypeError('date should be of type "object"')
|
|
146
|
+
}
|
|
147
|
+
if (!isString(region)) {
|
|
148
|
+
throw new TypeError('region should be of type "string"')
|
|
149
|
+
}
|
|
150
|
+
if (!isString(secretKey)) {
|
|
151
|
+
throw new TypeError('secretKey should be of type "string"')
|
|
152
|
+
}
|
|
153
|
+
const dateLine = makeDateShort(date)
|
|
154
|
+
const hmac1 = crypto
|
|
155
|
+
.createHmac('sha256', 'AWS4' + secretKey)
|
|
156
|
+
.update(dateLine)
|
|
157
|
+
.digest(),
|
|
158
|
+
hmac2 = crypto.createHmac('sha256', hmac1).update(region).digest(),
|
|
159
|
+
hmac3 = crypto.createHmac('sha256', hmac2).update(serviceName).digest()
|
|
160
|
+
return crypto.createHmac('sha256', hmac3).update('aws4_request').digest()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// returns the string that needs to be signed
|
|
164
|
+
function getStringToSign(canonicalRequest: ICanonicalRequest, requestDate: Date, region: string, serviceName = 's3') {
|
|
165
|
+
if (!isString(canonicalRequest)) {
|
|
166
|
+
throw new TypeError('canonicalRequest should be of type "string"')
|
|
167
|
+
}
|
|
168
|
+
if (!isObject(requestDate)) {
|
|
169
|
+
throw new TypeError('requestDate should be of type "object"')
|
|
170
|
+
}
|
|
171
|
+
if (!isString(region)) {
|
|
172
|
+
throw new TypeError('region should be of type "string"')
|
|
173
|
+
}
|
|
174
|
+
const hash = crypto.createHash('sha256').update(canonicalRequest).digest('hex')
|
|
175
|
+
const scope = getScope(region, requestDate, serviceName)
|
|
176
|
+
const stringToSign = [signV4Algorithm, makeDateLong(requestDate), scope, hash]
|
|
177
|
+
|
|
178
|
+
return stringToSign.join('\n')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// calculate the signature of the POST policy
|
|
182
|
+
export function postPresignSignatureV4(region: string, date: Date, secretKey: string, policyBase64: string): string {
|
|
183
|
+
if (!isString(region)) {
|
|
184
|
+
throw new TypeError('region should be of type "string"')
|
|
185
|
+
}
|
|
186
|
+
if (!isObject(date)) {
|
|
187
|
+
throw new TypeError('date should be of type "object"')
|
|
188
|
+
}
|
|
189
|
+
if (!isString(secretKey)) {
|
|
190
|
+
throw new TypeError('secretKey should be of type "string"')
|
|
191
|
+
}
|
|
192
|
+
if (!isString(policyBase64)) {
|
|
193
|
+
throw new TypeError('policyBase64 should be of type "string"')
|
|
194
|
+
}
|
|
195
|
+
const signingKey = getSigningKey(date, region, secretKey)
|
|
196
|
+
return crypto.createHmac('sha256', signingKey).update(policyBase64).digest('hex').toLowerCase()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Returns the authorization header
|
|
200
|
+
export function signV4(
|
|
201
|
+
request: IRequest,
|
|
202
|
+
accessKey: string,
|
|
203
|
+
secretKey: string,
|
|
204
|
+
region: string,
|
|
205
|
+
requestDate: Date,
|
|
206
|
+
sha256sum: string,
|
|
207
|
+
serviceName = 's3',
|
|
208
|
+
) {
|
|
209
|
+
if (!isObject(request)) {
|
|
210
|
+
throw new TypeError('request should be of type "object"')
|
|
211
|
+
}
|
|
212
|
+
if (!isString(accessKey)) {
|
|
213
|
+
throw new TypeError('accessKey should be of type "string"')
|
|
214
|
+
}
|
|
215
|
+
if (!isString(secretKey)) {
|
|
216
|
+
throw new TypeError('secretKey should be of type "string"')
|
|
217
|
+
}
|
|
218
|
+
if (!isString(region)) {
|
|
219
|
+
throw new TypeError('region should be of type "string"')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!accessKey) {
|
|
223
|
+
throw new errors.AccessKeyRequiredError('accessKey is required for signing')
|
|
224
|
+
}
|
|
225
|
+
if (!secretKey) {
|
|
226
|
+
throw new errors.SecretKeyRequiredError('secretKey is required for signing')
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const signedHeaders = getSignedHeaders(request.headers)
|
|
230
|
+
const canonicalRequest = getCanonicalRequest(request.method, request.path, request.headers, signedHeaders, sha256sum)
|
|
231
|
+
const serviceIdentifier = serviceName || 's3'
|
|
232
|
+
const stringToSign = getStringToSign(canonicalRequest, requestDate, region, serviceIdentifier)
|
|
233
|
+
const signingKey = getSigningKey(requestDate, region, secretKey, serviceIdentifier)
|
|
234
|
+
const credential = getCredential(accessKey, region, requestDate, serviceIdentifier)
|
|
235
|
+
const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex').toLowerCase()
|
|
236
|
+
|
|
237
|
+
return `${signV4Algorithm} Credential=${credential}, SignedHeaders=${signedHeaders
|
|
238
|
+
.join(';')
|
|
239
|
+
.toLowerCase()}, Signature=${signature}`
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function signV4ByServiceName(
|
|
243
|
+
request: IRequest,
|
|
244
|
+
accessKey: string,
|
|
245
|
+
secretKey: string,
|
|
246
|
+
region: string,
|
|
247
|
+
requestDate: Date,
|
|
248
|
+
contentSha256: string,
|
|
249
|
+
serviceName = 's3',
|
|
250
|
+
): string {
|
|
251
|
+
return signV4(request, accessKey, secretKey, region, requestDate, contentSha256, serviceName)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// returns a presigned URL string
|
|
255
|
+
export function presignSignatureV4(
|
|
256
|
+
request: IRequest,
|
|
257
|
+
accessKey: string,
|
|
258
|
+
secretKey: string,
|
|
259
|
+
sessionToken: string | undefined,
|
|
260
|
+
region: string,
|
|
261
|
+
requestDate: Date,
|
|
262
|
+
expires: number | undefined,
|
|
263
|
+
) {
|
|
264
|
+
if (!isObject(request)) {
|
|
265
|
+
throw new TypeError('request should be of type "object"')
|
|
266
|
+
}
|
|
267
|
+
if (!isString(accessKey)) {
|
|
268
|
+
throw new TypeError('accessKey should be of type "string"')
|
|
269
|
+
}
|
|
270
|
+
if (!isString(secretKey)) {
|
|
271
|
+
throw new TypeError('secretKey should be of type "string"')
|
|
272
|
+
}
|
|
273
|
+
if (!isString(region)) {
|
|
274
|
+
throw new TypeError('region should be of type "string"')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!accessKey) {
|
|
278
|
+
throw new errors.AccessKeyRequiredError('accessKey is required for presigning')
|
|
279
|
+
}
|
|
280
|
+
if (!secretKey) {
|
|
281
|
+
throw new errors.SecretKeyRequiredError('secretKey is required for presigning')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (expires && !isNumber(expires)) {
|
|
285
|
+
throw new TypeError('expires should be of type "number"')
|
|
286
|
+
}
|
|
287
|
+
if (expires && expires < 1) {
|
|
288
|
+
throw new errors.ExpiresParamError('expires param cannot be less than 1 seconds')
|
|
289
|
+
}
|
|
290
|
+
if (expires && expires > PRESIGN_EXPIRY_DAYS_MAX) {
|
|
291
|
+
throw new errors.ExpiresParamError('expires param cannot be greater than 7 days')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const iso8601Date = makeDateLong(requestDate)
|
|
295
|
+
const signedHeaders = getSignedHeaders(request.headers)
|
|
296
|
+
const credential = getCredential(accessKey, region, requestDate)
|
|
297
|
+
const hashedPayload = 'UNSIGNED-PAYLOAD'
|
|
298
|
+
|
|
299
|
+
const requestQuery: string[] = []
|
|
300
|
+
requestQuery.push(`X-Amz-Algorithm=${signV4Algorithm}`)
|
|
301
|
+
requestQuery.push(`X-Amz-Credential=${uriEscape(credential)}`)
|
|
302
|
+
requestQuery.push(`X-Amz-Date=${iso8601Date}`)
|
|
303
|
+
requestQuery.push(`X-Amz-Expires=${expires}`)
|
|
304
|
+
requestQuery.push(`X-Amz-SignedHeaders=${uriEscape(signedHeaders.join(';').toLowerCase())}`)
|
|
305
|
+
if (sessionToken) {
|
|
306
|
+
requestQuery.push(`X-Amz-Security-Token=${uriEscape(sessionToken)}`)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const resource = request.path.split('?')[0]
|
|
310
|
+
let query = request.path.split('?')[1]
|
|
311
|
+
if (query) {
|
|
312
|
+
query = query + '&' + requestQuery.join('&')
|
|
313
|
+
} else {
|
|
314
|
+
query = requestQuery.join('&')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const path = resource + '?' + query
|
|
318
|
+
|
|
319
|
+
const canonicalRequest = getCanonicalRequest(request.method, path, request.headers, signedHeaders, hashedPayload)
|
|
320
|
+
|
|
321
|
+
const stringToSign = getStringToSign(canonicalRequest, requestDate, region)
|
|
322
|
+
const signingKey = getSigningKey(requestDate, region, secretKey)
|
|
323
|
+
const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex').toLowerCase()
|
|
324
|
+
return request.protocol + '//' + request.headers.host + path + `&X-Amz-Signature=${signature}`
|
|
325
|
+
}
|