@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.
Files changed (119) hide show
  1. package/LICENSE +202 -0
  2. package/MAINTAINERS.md +62 -0
  3. package/README.md +262 -0
  4. package/README_zh_CN.md +192 -0
  5. package/dist/esm/AssumeRoleProvider.d.mts +86 -0
  6. package/dist/esm/AssumeRoleProvider.mjs +183 -0
  7. package/dist/esm/CredentialProvider.d.mts +22 -0
  8. package/dist/esm/CredentialProvider.mjs +48 -0
  9. package/dist/esm/Credentials.d.mts +22 -0
  10. package/dist/esm/Credentials.mjs +38 -0
  11. package/dist/esm/IamAwsProvider.d.mts +27 -0
  12. package/dist/esm/IamAwsProvider.mjs +189 -0
  13. package/dist/esm/errors.d.mts +82 -0
  14. package/dist/esm/errors.mjs +117 -0
  15. package/dist/esm/helpers.d.mts +156 -0
  16. package/dist/esm/helpers.mjs +218 -0
  17. package/dist/esm/internal/async.d.mts +9 -0
  18. package/dist/esm/internal/async.mjs +14 -0
  19. package/dist/esm/internal/callbackify.d.mts +1 -0
  20. package/dist/esm/internal/callbackify.mjs +15 -0
  21. package/dist/esm/internal/client.d.mts +394 -0
  22. package/dist/esm/internal/client.mjs +3007 -0
  23. package/dist/esm/internal/copy-conditions.d.mts +10 -0
  24. package/dist/esm/internal/copy-conditions.mjs +25 -0
  25. package/dist/esm/internal/extensions.d.mts +18 -0
  26. package/dist/esm/internal/extensions.mjs +114 -0
  27. package/dist/esm/internal/helper.d.mts +177 -0
  28. package/dist/esm/internal/helper.mjs +552 -0
  29. package/dist/esm/internal/join-host-port.d.mts +11 -0
  30. package/dist/esm/internal/join-host-port.mjs +23 -0
  31. package/dist/esm/internal/post-policy.d.mts +17 -0
  32. package/dist/esm/internal/post-policy.mjs +98 -0
  33. package/dist/esm/internal/request.d.mts +11 -0
  34. package/dist/esm/internal/request.mjs +75 -0
  35. package/dist/esm/internal/response.d.mts +8 -0
  36. package/dist/esm/internal/response.mjs +16 -0
  37. package/dist/esm/internal/s3-endpoints.d.mts +38 -0
  38. package/dist/esm/internal/s3-endpoints.mjs +68 -0
  39. package/dist/esm/internal/type.d.mts +482 -0
  40. package/dist/esm/internal/type.mjs +30 -0
  41. package/dist/esm/internal/xml-parser.d.mts +93 -0
  42. package/dist/esm/internal/xml-parser.mjs +819 -0
  43. package/dist/esm/notification.d.mts +58 -0
  44. package/dist/esm/notification.mjs +209 -0
  45. package/dist/esm/s3.d.mts +40 -0
  46. package/dist/esm/s3.mjs +86 -0
  47. package/dist/esm/signing.d.mts +5 -0
  48. package/dist/esm/signing.mjs +258 -0
  49. package/dist/main/AssumeRoleProvider.d.ts +86 -0
  50. package/dist/main/AssumeRoleProvider.js +191 -0
  51. package/dist/main/CredentialProvider.d.ts +22 -0
  52. package/dist/main/CredentialProvider.js +55 -0
  53. package/dist/main/Credentials.d.ts +22 -0
  54. package/dist/main/Credentials.js +45 -0
  55. package/dist/main/IamAwsProvider.d.ts +27 -0
  56. package/dist/main/IamAwsProvider.js +198 -0
  57. package/dist/main/errors.d.ts +82 -0
  58. package/dist/main/errors.js +138 -0
  59. package/dist/main/helpers.d.ts +156 -0
  60. package/dist/main/helpers.js +233 -0
  61. package/dist/main/internal/async.d.ts +9 -0
  62. package/dist/main/internal/async.js +24 -0
  63. package/dist/main/internal/callbackify.d.ts +1 -0
  64. package/dist/main/internal/callbackify.js +21 -0
  65. package/dist/main/internal/client.d.ts +394 -0
  66. package/dist/main/internal/client.js +3014 -0
  67. package/dist/main/internal/copy-conditions.d.ts +10 -0
  68. package/dist/main/internal/copy-conditions.js +31 -0
  69. package/dist/main/internal/extensions.d.ts +18 -0
  70. package/dist/main/internal/extensions.js +122 -0
  71. package/dist/main/internal/helper.d.ts +177 -0
  72. package/dist/main/internal/helper.js +608 -0
  73. package/dist/main/internal/join-host-port.d.ts +11 -0
  74. package/dist/main/internal/join-host-port.js +29 -0
  75. package/dist/main/internal/post-policy.d.ts +17 -0
  76. package/dist/main/internal/post-policy.js +107 -0
  77. package/dist/main/internal/request.d.ts +11 -0
  78. package/dist/main/internal/request.js +83 -0
  79. package/dist/main/internal/response.d.ts +8 -0
  80. package/dist/main/internal/response.js +24 -0
  81. package/dist/main/internal/s3-endpoints.d.ts +38 -0
  82. package/dist/main/internal/s3-endpoints.js +73 -0
  83. package/dist/main/internal/type.d.ts +482 -0
  84. package/dist/main/internal/type.js +42 -0
  85. package/dist/main/internal/xml-parser.d.ts +93 -0
  86. package/dist/main/internal/xml-parser.js +849 -0
  87. package/dist/main/notification.d.ts +58 -0
  88. package/dist/main/notification.js +230 -0
  89. package/dist/main/s3.d.ts +40 -0
  90. package/dist/main/s3.js +117 -0
  91. package/dist/main/signing.d.ts +5 -0
  92. package/dist/main/signing.js +269 -0
  93. package/package.json +146 -39
  94. package/src/AssumeRoleProvider.ts +262 -0
  95. package/src/CredentialProvider.ts +54 -0
  96. package/src/Credentials.ts +44 -0
  97. package/src/IamAwsProvider.ts +234 -0
  98. package/src/errors.ts +120 -0
  99. package/src/helpers.ts +354 -0
  100. package/src/internal/async.ts +14 -0
  101. package/src/internal/callbackify.ts +19 -0
  102. package/src/internal/client.ts +3412 -0
  103. package/src/internal/copy-conditions.ts +30 -0
  104. package/src/internal/extensions.ts +140 -0
  105. package/src/internal/helper.ts +606 -0
  106. package/src/internal/join-host-port.ts +23 -0
  107. package/src/internal/post-policy.ts +99 -0
  108. package/src/internal/request.ts +102 -0
  109. package/src/internal/response.ts +26 -0
  110. package/src/internal/s3-endpoints.ts +70 -0
  111. package/src/internal/type.ts +577 -0
  112. package/src/internal/xml-parser.ts +871 -0
  113. package/src/notification.ts +254 -0
  114. package/src/s3.ts +155 -0
  115. package/src/signing.ts +325 -0
  116. package/lib/index.js +0 -450
  117. package/lib/index.js.map +0 -7
  118. package/lib/perfTest.js +0 -91
  119. package/lib/perfTest.js.map +0 -7
@@ -0,0 +1,262 @@
1
+ import * as http from 'node:http'
2
+ import * as https from 'node:https'
3
+ import { URL, URLSearchParams } from 'node:url'
4
+
5
+ import { CredentialProvider } from './CredentialProvider.ts'
6
+ import { Credentials } from './Credentials.ts'
7
+ import { makeDateLong, parseXml, toSha256 } from './internal/helper.ts'
8
+ import { request } from './internal/request.ts'
9
+ import { readAsString } from './internal/response.ts'
10
+ import type { Transport } from './internal/type.ts'
11
+ import { signV4ByServiceName } from './signing.ts'
12
+
13
+ /**
14
+ * @see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
15
+ */
16
+ type CredentialResponse = {
17
+ ErrorResponse?: {
18
+ Error?: {
19
+ Code?: string
20
+ Message?: string
21
+ }
22
+ }
23
+
24
+ AssumeRoleResponse: {
25
+ AssumeRoleResult: {
26
+ Credentials: {
27
+ AccessKeyId: string
28
+ SecretAccessKey: string
29
+ SessionToken: string
30
+ Expiration: string
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ export interface AssumeRoleProviderOptions {
37
+ stsEndpoint: string
38
+ accessKey: string
39
+ secretKey: string
40
+ durationSeconds?: number
41
+ sessionToken?: string
42
+ policy?: string
43
+ region?: string
44
+ roleArn?: string
45
+ roleSessionName?: string
46
+ externalId?: string
47
+ token?: string
48
+ webIdentityToken?: string
49
+ action?: string
50
+ transportAgent?: http.Agent
51
+ }
52
+
53
+ const defaultExpirySeconds = 900
54
+
55
+ export class AssumeRoleProvider extends CredentialProvider {
56
+ private readonly stsEndpoint: URL
57
+ private readonly accessKey: string
58
+ private readonly secretKey: string
59
+ private readonly durationSeconds: number
60
+ private readonly policy?: string
61
+ private readonly region: string
62
+ private readonly roleArn?: string
63
+ private readonly roleSessionName?: string
64
+ private readonly externalId?: string
65
+ private readonly token?: string
66
+ private readonly webIdentityToken?: string
67
+ private readonly action: string
68
+
69
+ private _credentials: Credentials | null
70
+ private readonly expirySeconds: number
71
+ private accessExpiresAt = ''
72
+ private readonly transportAgent?: http.Agent
73
+
74
+ private readonly transport: Transport
75
+
76
+ constructor({
77
+ stsEndpoint,
78
+ accessKey,
79
+ secretKey,
80
+ durationSeconds = defaultExpirySeconds,
81
+ sessionToken,
82
+ policy,
83
+ region = '',
84
+ roleArn,
85
+ roleSessionName,
86
+ externalId,
87
+ token,
88
+ webIdentityToken,
89
+ action = 'AssumeRole',
90
+ transportAgent = undefined,
91
+ }: AssumeRoleProviderOptions) {
92
+ super({ accessKey, secretKey, sessionToken })
93
+
94
+ this.stsEndpoint = new URL(stsEndpoint)
95
+ this.accessKey = accessKey
96
+ this.secretKey = secretKey
97
+ this.policy = policy
98
+ this.region = region
99
+ this.roleArn = roleArn
100
+ this.roleSessionName = roleSessionName
101
+ this.externalId = externalId
102
+ this.token = token
103
+ this.webIdentityToken = webIdentityToken
104
+ this.action = action
105
+
106
+ this.durationSeconds = parseInt(durationSeconds as unknown as string)
107
+
108
+ let expirySeconds = this.durationSeconds
109
+ if (this.durationSeconds < defaultExpirySeconds) {
110
+ expirySeconds = defaultExpirySeconds
111
+ }
112
+ this.expirySeconds = expirySeconds // for calculating refresh of credentials.
113
+
114
+ // By default, nodejs uses a global agent if the 'agent' property
115
+ // is set to undefined. Otherwise, it's okay to assume the users
116
+ // know what they're doing if they specify a custom transport agent.
117
+ this.transportAgent = transportAgent
118
+ const isHttp: boolean = this.stsEndpoint.protocol === 'http:'
119
+ this.transport = isHttp ? http : https
120
+
121
+ /**
122
+ * Internal Tracking variables
123
+ */
124
+ this._credentials = null
125
+ }
126
+
127
+ getRequestConfig(): {
128
+ requestOptions: http.RequestOptions
129
+ requestData: string
130
+ } {
131
+ const hostValue = this.stsEndpoint.hostname
132
+ const portValue = this.stsEndpoint.port
133
+ const qryParams = new URLSearchParams({ Action: this.action, Version: '2011-06-15' })
134
+
135
+ qryParams.set('DurationSeconds', this.expirySeconds.toString())
136
+
137
+ if (this.policy) {
138
+ qryParams.set('Policy', this.policy)
139
+ }
140
+ if (this.roleArn) {
141
+ qryParams.set('RoleArn', this.roleArn)
142
+ }
143
+
144
+ if (this.roleSessionName != null) {
145
+ qryParams.set('RoleSessionName', this.roleSessionName)
146
+ }
147
+ if (this.token != null) {
148
+ qryParams.set('Token', this.token)
149
+ }
150
+
151
+ if (this.webIdentityToken) {
152
+ qryParams.set('WebIdentityToken', this.webIdentityToken)
153
+ }
154
+
155
+ if (this.externalId) {
156
+ qryParams.set('ExternalId', this.externalId)
157
+ }
158
+
159
+ const urlParams = qryParams.toString()
160
+ const contentSha256 = toSha256(urlParams)
161
+
162
+ const date = new Date()
163
+
164
+ const requestOptions = {
165
+ hostname: hostValue,
166
+ port: portValue,
167
+ path: '/',
168
+ protocol: this.stsEndpoint.protocol,
169
+ method: 'POST',
170
+ headers: {
171
+ 'Content-Type': 'application/x-www-form-urlencoded',
172
+ 'content-length': urlParams.length.toString(),
173
+ host: hostValue,
174
+ 'x-amz-date': makeDateLong(date),
175
+ 'x-amz-content-sha256': contentSha256,
176
+ } as Record<string, string>,
177
+ agent: this.transportAgent,
178
+ } satisfies http.RequestOptions
179
+
180
+ requestOptions.headers.authorization = signV4ByServiceName(
181
+ requestOptions,
182
+ this.accessKey,
183
+ this.secretKey,
184
+ this.region,
185
+ date,
186
+ contentSha256,
187
+ 'sts',
188
+ )
189
+
190
+ return {
191
+ requestOptions,
192
+ requestData: urlParams,
193
+ }
194
+ }
195
+
196
+ async performRequest(): Promise<CredentialResponse> {
197
+ const { requestOptions, requestData } = this.getRequestConfig()
198
+
199
+ const res = await request(this.transport, requestOptions, requestData)
200
+
201
+ const body = await readAsString(res)
202
+
203
+ return parseXml(body)
204
+ }
205
+
206
+ parseCredentials(respObj: CredentialResponse): Credentials {
207
+ if (respObj.ErrorResponse) {
208
+ throw new Error(
209
+ `Unable to obtain credentials: ${respObj.ErrorResponse?.Error?.Code} ${respObj.ErrorResponse?.Error?.Message}`,
210
+ { cause: respObj },
211
+ )
212
+ }
213
+
214
+ const {
215
+ AssumeRoleResponse: {
216
+ AssumeRoleResult: {
217
+ Credentials: {
218
+ AccessKeyId: accessKey,
219
+ SecretAccessKey: secretKey,
220
+ SessionToken: sessionToken,
221
+ Expiration: expiresAt,
222
+ },
223
+ },
224
+ },
225
+ } = respObj
226
+
227
+ this.accessExpiresAt = expiresAt
228
+
229
+ return new Credentials({ accessKey, secretKey, sessionToken })
230
+ }
231
+
232
+ async refreshCredentials(): Promise<Credentials> {
233
+ try {
234
+ const assumeRoleCredentials = await this.performRequest()
235
+ this._credentials = this.parseCredentials(assumeRoleCredentials)
236
+ } catch (err) {
237
+ throw new Error(`Failed to get Credentials: ${err}`, { cause: err })
238
+ }
239
+
240
+ return this._credentials
241
+ }
242
+
243
+ async getCredentials(): Promise<Credentials> {
244
+ if (this._credentials && !this.isAboutToExpire()) {
245
+ return this._credentials
246
+ }
247
+
248
+ this._credentials = await this.refreshCredentials()
249
+ return this._credentials
250
+ }
251
+
252
+ isAboutToExpire() {
253
+ const expiresAt = new Date(this.accessExpiresAt)
254
+ const provisionalExpiry = new Date(Date.now() + 1000 * 10) // check before 10 seconds.
255
+ return provisionalExpiry > expiresAt
256
+ }
257
+ }
258
+
259
+ // deprecated default export, please use named exports.
260
+ // keep for backward compatibility.
261
+ // eslint-disable-next-line import/no-default-export
262
+ export default AssumeRoleProvider
@@ -0,0 +1,54 @@
1
+ import { Credentials } from './Credentials.ts'
2
+
3
+ export class CredentialProvider {
4
+ private credentials: Credentials
5
+
6
+ constructor({ accessKey, secretKey, sessionToken }: { accessKey: string; secretKey: string; sessionToken?: string }) {
7
+ this.credentials = new Credentials({
8
+ accessKey,
9
+ secretKey,
10
+ sessionToken,
11
+ })
12
+ }
13
+
14
+ async getCredentials(): Promise<Credentials> {
15
+ return this.credentials.get()
16
+ }
17
+
18
+ setCredentials(credentials: Credentials) {
19
+ if (credentials instanceof Credentials) {
20
+ this.credentials = credentials
21
+ } else {
22
+ throw new Error('Unable to set Credentials. it should be an instance of Credentials class')
23
+ }
24
+ }
25
+
26
+ setAccessKey(accessKey: string) {
27
+ this.credentials.setAccessKey(accessKey)
28
+ }
29
+
30
+ getAccessKey() {
31
+ return this.credentials.getAccessKey()
32
+ }
33
+
34
+ setSecretKey(secretKey: string) {
35
+ this.credentials.setSecretKey(secretKey)
36
+ }
37
+
38
+ getSecretKey() {
39
+ return this.credentials.getSecretKey()
40
+ }
41
+
42
+ setSessionToken(sessionToken: string) {
43
+ this.credentials.setSessionToken(sessionToken)
44
+ }
45
+
46
+ getSessionToken() {
47
+ return this.credentials.getSessionToken()
48
+ }
49
+ }
50
+
51
+ // deprecated default export, please use named exports.
52
+ // keep for backward compatibility.
53
+ // eslint-disable-next-line import/no-default-export
54
+ export default CredentialProvider
@@ -0,0 +1,44 @@
1
+ export class Credentials {
2
+ public accessKey: string
3
+ public secretKey: string
4
+ public sessionToken?: string
5
+
6
+ constructor({ accessKey, secretKey, sessionToken }: { accessKey: string; secretKey: string; sessionToken?: string }) {
7
+ this.accessKey = accessKey
8
+ this.secretKey = secretKey
9
+ this.sessionToken = sessionToken
10
+ }
11
+
12
+ setAccessKey(accessKey: string) {
13
+ this.accessKey = accessKey
14
+ }
15
+
16
+ getAccessKey() {
17
+ return this.accessKey
18
+ }
19
+
20
+ setSecretKey(secretKey: string) {
21
+ this.secretKey = secretKey
22
+ }
23
+
24
+ getSecretKey() {
25
+ return this.secretKey
26
+ }
27
+
28
+ setSessionToken(sessionToken: string) {
29
+ this.sessionToken = sessionToken
30
+ }
31
+
32
+ getSessionToken() {
33
+ return this.sessionToken
34
+ }
35
+
36
+ get(): Credentials {
37
+ return this
38
+ }
39
+ }
40
+
41
+ // deprecated default export, please use named exports.
42
+ // keep for backward compatibility.
43
+ // eslint-disable-next-line import/no-default-export
44
+ export default Credentials
@@ -0,0 +1,234 @@
1
+ import * as fs from 'node:fs/promises'
2
+ import * as http from 'node:http'
3
+ import * as https from 'node:https'
4
+ import { URL, URLSearchParams } from 'node:url'
5
+
6
+ import { CredentialProvider } from './CredentialProvider.ts'
7
+ import { Credentials } from './Credentials.ts'
8
+ import { parseXml } from './internal/helper.ts'
9
+ import { request } from './internal/request.ts'
10
+ import { readAsString } from './internal/response.ts'
11
+
12
+ interface AssumeRoleResponse {
13
+ AssumeRoleWithWebIdentityResponse: {
14
+ AssumeRoleWithWebIdentityResult: {
15
+ Credentials: {
16
+ AccessKeyId: string
17
+ SecretAccessKey: string
18
+ SessionToken: string
19
+ Expiration: string
20
+ }
21
+ }
22
+ }
23
+ }
24
+
25
+ interface EcsCredentials {
26
+ AccessKeyID: string
27
+ SecretAccessKey: string
28
+ Token: string
29
+ Expiration: string
30
+ Code: string
31
+ Message: string
32
+ }
33
+
34
+ export interface IamAwsProviderOptions {
35
+ customEndpoint?: string
36
+ transportAgent?: http.Agent
37
+ }
38
+
39
+ export class IamAwsProvider extends CredentialProvider {
40
+ private readonly customEndpoint?: string
41
+
42
+ private _credentials: Credentials | null
43
+ private readonly transportAgent?: http.Agent
44
+ private accessExpiresAt = ''
45
+
46
+ constructor({ customEndpoint = undefined, transportAgent = undefined }: IamAwsProviderOptions) {
47
+ super({ accessKey: '', secretKey: '' })
48
+
49
+ this.customEndpoint = customEndpoint
50
+ this.transportAgent = transportAgent
51
+
52
+ /**
53
+ * Internal Tracking variables
54
+ */
55
+ this._credentials = null
56
+ }
57
+
58
+ async getCredentials(): Promise<Credentials> {
59
+ if (!this._credentials || this.isAboutToExpire()) {
60
+ this._credentials = await this.fetchCredentials()
61
+ }
62
+ return this._credentials
63
+ }
64
+
65
+ private async fetchCredentials(): Promise<Credentials> {
66
+ try {
67
+ // check for IRSA (https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)
68
+ const tokenFile = process.env.AWS_WEB_IDENTITY_TOKEN_FILE
69
+ if (tokenFile) {
70
+ return await this.fetchCredentialsUsingTokenFile(tokenFile)
71
+ }
72
+
73
+ // try with IAM role for EC2 instances (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
74
+ let tokenHeader = 'Authorization'
75
+ let token = process.env.AWS_CONTAINER_AUTHORIZATION_TOKEN
76
+ const relativeUri = process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
77
+ const fullUri = process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI
78
+ let url: URL
79
+ if (relativeUri) {
80
+ url = new URL(relativeUri, 'http://169.254.170.2')
81
+ } else if (fullUri) {
82
+ url = new URL(fullUri)
83
+ } else {
84
+ token = await this.fetchImdsToken()
85
+ tokenHeader = 'X-aws-ec2-metadata-token'
86
+ url = await this.getIamRoleNamedUrl(token)
87
+ }
88
+
89
+ return this.requestCredentials(url, tokenHeader, token)
90
+ } catch (err) {
91
+ throw new Error(`Failed to get Credentials: ${err}`, { cause: err })
92
+ }
93
+ }
94
+
95
+ private async fetchCredentialsUsingTokenFile(tokenFile: string): Promise<Credentials> {
96
+ const token = await fs.readFile(tokenFile, { encoding: 'utf8' })
97
+ const region = process.env.AWS_REGION
98
+ const stsEndpoint = new URL(region ? `https://sts.${region}.amazonaws.com` : 'https://sts.amazonaws.com')
99
+
100
+ const hostValue = stsEndpoint.hostname
101
+ const portValue = stsEndpoint.port
102
+ const qryParams = new URLSearchParams({
103
+ Action: 'AssumeRoleWithWebIdentity',
104
+ Version: '2011-06-15',
105
+ })
106
+
107
+ const roleArn = process.env.AWS_ROLE_ARN
108
+ if (roleArn) {
109
+ qryParams.set('RoleArn', roleArn)
110
+ const roleSessionName = process.env.AWS_ROLE_SESSION_NAME
111
+ qryParams.set('RoleSessionName', roleSessionName ? roleSessionName : Date.now().toString())
112
+ }
113
+
114
+ qryParams.set('WebIdentityToken', token)
115
+ qryParams.sort()
116
+
117
+ const requestOptions = {
118
+ hostname: hostValue,
119
+ port: portValue,
120
+ path: `${stsEndpoint.pathname}?${qryParams.toString()}`,
121
+ protocol: stsEndpoint.protocol,
122
+ method: 'POST',
123
+ headers: {},
124
+ agent: this.transportAgent,
125
+ } satisfies http.RequestOptions
126
+
127
+ const transport = stsEndpoint.protocol === 'http:' ? http : https
128
+ const res = await request(transport, requestOptions, null)
129
+ const body = await readAsString(res)
130
+
131
+ const assumeRoleResponse: AssumeRoleResponse = parseXml(body)
132
+ const creds = assumeRoleResponse.AssumeRoleWithWebIdentityResponse.AssumeRoleWithWebIdentityResult.Credentials
133
+ this.accessExpiresAt = creds.Expiration
134
+ return new Credentials({
135
+ accessKey: creds.AccessKeyId,
136
+ secretKey: creds.SecretAccessKey,
137
+ sessionToken: creds.SessionToken,
138
+ })
139
+ }
140
+
141
+ private async fetchImdsToken() {
142
+ const endpoint = this.customEndpoint ? this.customEndpoint : 'http://169.254.169.254'
143
+ const url = new URL('/latest/api/token', endpoint)
144
+
145
+ const requestOptions = {
146
+ hostname: url.hostname,
147
+ port: url.port,
148
+ path: `${url.pathname}${url.search}`,
149
+ protocol: url.protocol,
150
+ method: 'PUT',
151
+ headers: {
152
+ 'X-aws-ec2-metadata-token-ttl-seconds': '21600',
153
+ },
154
+ agent: this.transportAgent,
155
+ } satisfies http.RequestOptions
156
+
157
+ const transport = url.protocol === 'http:' ? http : https
158
+ const res = await request(transport, requestOptions, null)
159
+ return await readAsString(res)
160
+ }
161
+
162
+ private async getIamRoleNamedUrl(token: string) {
163
+ const endpoint = this.customEndpoint ? this.customEndpoint : 'http://169.254.169.254'
164
+ const url = new URL('latest/meta-data/iam/security-credentials/', endpoint)
165
+
166
+ const roleName = await this.getIamRoleName(url, token)
167
+ return new URL(`${url.pathname}/${encodeURIComponent(roleName)}`, url.origin)
168
+ }
169
+
170
+ private async getIamRoleName(url: URL, token: string): Promise<string> {
171
+ const requestOptions = {
172
+ hostname: url.hostname,
173
+ port: url.port,
174
+ path: `${url.pathname}${url.search}`,
175
+ protocol: url.protocol,
176
+ method: 'GET',
177
+ headers: {
178
+ 'X-aws-ec2-metadata-token': token,
179
+ },
180
+ agent: this.transportAgent,
181
+ } satisfies http.RequestOptions
182
+
183
+ const transport = url.protocol === 'http:' ? http : https
184
+ const res = await request(transport, requestOptions, null)
185
+ const body = await readAsString(res)
186
+ const roleNames = body.split(/\r\n|[\n\r\u2028\u2029]/)
187
+ if (roleNames.length === 0) {
188
+ throw new Error(`No IAM roles attached to EC2 service ${url}`)
189
+ }
190
+ return roleNames[0] as string
191
+ }
192
+
193
+ private async requestCredentials(url: URL, tokenHeader: string, token: string | undefined): Promise<Credentials> {
194
+ const headers: Record<string, string> = {}
195
+ if (token) {
196
+ headers[tokenHeader] = token
197
+ }
198
+ const requestOptions = {
199
+ hostname: url.hostname,
200
+ port: url.port,
201
+ path: `${url.pathname}${url.search}`,
202
+ protocol: url.protocol,
203
+ method: 'GET',
204
+ headers: headers,
205
+ agent: this.transportAgent,
206
+ } satisfies http.RequestOptions
207
+
208
+ const transport = url.protocol === 'http:' ? http : https
209
+ const res = await request(transport, requestOptions, null)
210
+ const body = await readAsString(res)
211
+ const ecsCredentials = JSON.parse(body) as EcsCredentials
212
+ if (!ecsCredentials.Code || ecsCredentials.Code != 'Success') {
213
+ throw new Error(`${url} failed with code ${ecsCredentials.Code} and message ${ecsCredentials.Message}`)
214
+ }
215
+
216
+ this.accessExpiresAt = ecsCredentials.Expiration
217
+ return new Credentials({
218
+ accessKey: ecsCredentials.AccessKeyID,
219
+ secretKey: ecsCredentials.SecretAccessKey,
220
+ sessionToken: ecsCredentials.Token,
221
+ })
222
+ }
223
+
224
+ private isAboutToExpire() {
225
+ const expiresAt = new Date(this.accessExpiresAt)
226
+ const provisionalExpiry = new Date(Date.now() + 1000 * 10) // 10 seconds leeway
227
+ return provisionalExpiry > expiresAt
228
+ }
229
+ }
230
+
231
+ // deprecated default export, please use named exports.
232
+ // keep for backward compatibility.
233
+ // eslint-disable-next-line import/no-default-export
234
+ export default IamAwsProvider
package/src/errors.ts ADDED
@@ -0,0 +1,120 @@
1
+ /*
2
+ * Hanzo S3 Javascript Library for Amazon S3 Compatible Cloud Storage, (C) 2015 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
+ /// <reference lib="ES2022.Error" />
18
+
19
+ class ExtendableError extends Error {
20
+ constructor(message?: string, opt?: ErrorOptions) {
21
+ // error Option {cause?: unknown} is a 'nice to have',
22
+ // don't use it internally
23
+ super(message, opt)
24
+ // set error name, otherwise it's always 'Error'
25
+ this.name = this.constructor.name
26
+ }
27
+ }
28
+
29
+ /**
30
+ * AnonymousRequestError is generated for anonymous keys on specific
31
+ * APIs. NOTE: PresignedURL generation always requires access keys.
32
+ */
33
+ export class AnonymousRequestError extends ExtendableError {}
34
+
35
+ /**
36
+ * InvalidArgumentError is generated for all invalid arguments.
37
+ */
38
+ export class InvalidArgumentError extends ExtendableError {}
39
+
40
+ /**
41
+ * InvalidPortError is generated when a non integer value is provided
42
+ * for ports.
43
+ */
44
+ export class InvalidPortError extends ExtendableError {}
45
+
46
+ /**
47
+ * InvalidEndpointError is generated when an invalid end point value is
48
+ * provided which does not follow domain standards.
49
+ */
50
+ export class InvalidEndpointError extends ExtendableError {}
51
+
52
+ /**
53
+ * InvalidBucketNameError is generated when an invalid bucket name is
54
+ * provided which does not follow AWS S3 specifications.
55
+ * http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
56
+ */
57
+ export class InvalidBucketNameError extends ExtendableError {}
58
+
59
+ /**
60
+ * InvalidObjectNameError is generated when an invalid object name is
61
+ * provided which does not follow AWS S3 specifications.
62
+ * http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
63
+ */
64
+ export class InvalidObjectNameError extends ExtendableError {}
65
+
66
+ /**
67
+ * AccessKeyRequiredError generated by signature methods when access
68
+ * key is not found.
69
+ */
70
+ export class AccessKeyRequiredError extends ExtendableError {}
71
+
72
+ /**
73
+ * SecretKeyRequiredError generated by signature methods when secret
74
+ * key is not found.
75
+ */
76
+ export class SecretKeyRequiredError extends ExtendableError {}
77
+
78
+ /**
79
+ * ExpiresParamError generated when expires parameter value is not
80
+ * well within stipulated limits.
81
+ */
82
+ export class ExpiresParamError extends ExtendableError {}
83
+
84
+ /**
85
+ * InvalidDateError generated when invalid date is found.
86
+ */
87
+ export class InvalidDateError extends ExtendableError {}
88
+
89
+ /**
90
+ * InvalidPrefixError generated when object prefix provided is invalid
91
+ * or does not conform to AWS S3 object key restrictions.
92
+ */
93
+ export class InvalidPrefixError extends ExtendableError {}
94
+
95
+ /**
96
+ * InvalidBucketPolicyError generated when the given bucket policy is invalid.
97
+ */
98
+ export class InvalidBucketPolicyError extends ExtendableError {}
99
+
100
+ /**
101
+ * IncorrectSizeError generated when total data read mismatches with
102
+ * the input size.
103
+ */
104
+ export class IncorrectSizeError extends ExtendableError {}
105
+
106
+ /**
107
+ * InvalidXMLError generated when an unknown XML is found.
108
+ */
109
+ export class InvalidXMLError extends ExtendableError {}
110
+
111
+ /**
112
+ * S3Error is generated for errors returned from S3 server.
113
+ * see getErrorTransformer for details
114
+ */
115
+ export class S3Error extends ExtendableError {
116
+ code?: string
117
+ region?: string
118
+ }
119
+
120
+ export class IsValidBucketNameError extends ExtendableError {}