createSignedURL.test.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { describe, it, beforeEach, afterEach } from 'vitest'
  2. import assert from 'node:assert'
  3. import {
  4. S3Client,
  5. UploadPartCommand,
  6. PutObjectCommand,
  7. } from '@aws-sdk/client-s3'
  8. import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
  9. import createSignedURL from './createSignedURL.ts'
  10. const bucketName = 'some-bucket.with.dots'
  11. const s3ClientOptions = {
  12. region: 'us-bar-1',
  13. credentials: {
  14. accessKeyId: 'foo',
  15. secretAccessKey: 'bar',
  16. sessionToken: 'foobar',
  17. },
  18. }
  19. const { Date: OriginalDate } = globalThis
  20. describe('createSignedURL', () => {
  21. beforeEach(() => {
  22. const now_ms = OriginalDate.now()
  23. // @ts-expect-error we're touching globals for test purposes.
  24. globalThis.Date = function Date() {
  25. if (new.target) {
  26. return Reflect.construct(OriginalDate, [now_ms])
  27. }
  28. return Reflect.apply(OriginalDate, this, [now_ms])
  29. }
  30. globalThis.Date.now = function now() {
  31. return now_ms
  32. }
  33. })
  34. afterEach(() => {
  35. globalThis.Date = OriginalDate
  36. })
  37. it('should be able to sign non-multipart upload', async () => {
  38. const client = new S3Client(s3ClientOptions)
  39. assert.strictEqual(
  40. (
  41. await createSignedURL({
  42. accountKey: s3ClientOptions.credentials.accessKeyId,
  43. accountSecret: s3ClientOptions.credentials.secretAccessKey,
  44. sessionToken: s3ClientOptions.credentials.sessionToken,
  45. bucketName,
  46. Key: 'some/key',
  47. Region: s3ClientOptions.region,
  48. expires: 900,
  49. })
  50. ).searchParams.get('X-Amz-Signature'),
  51. new URL(
  52. await getSignedUrl(
  53. client,
  54. new PutObjectCommand({
  55. Bucket: bucketName,
  56. Key: 'some/key',
  57. }),
  58. { expiresIn: 900 },
  59. ),
  60. ).searchParams.get('X-Amz-Signature'),
  61. )
  62. })
  63. it('should be able to sign multipart upload', async () => {
  64. const client = new S3Client(s3ClientOptions)
  65. const partNumber = 99
  66. const uploadId = 'dummyUploadId'
  67. assert.strictEqual(
  68. (
  69. await createSignedURL({
  70. accountKey: s3ClientOptions.credentials.accessKeyId,
  71. accountSecret: s3ClientOptions.credentials.secretAccessKey,
  72. sessionToken: s3ClientOptions.credentials.sessionToken,
  73. uploadId,
  74. partNumber,
  75. bucketName,
  76. Key: 'some/key',
  77. Region: s3ClientOptions.region,
  78. expires: 900,
  79. })
  80. ).searchParams.get('X-Amz-Signature'),
  81. new URL(
  82. await getSignedUrl(
  83. client,
  84. new UploadPartCommand({
  85. Bucket: bucketName,
  86. UploadId: uploadId,
  87. PartNumber: partNumber,
  88. Key: 'some/key',
  89. }),
  90. { expiresIn: 900 },
  91. ),
  92. ).searchParams.get('X-Amz-Signature'),
  93. )
  94. })
  95. it('should escape path and query as restricted to RFC 3986', async () => {
  96. const client = new S3Client(s3ClientOptions)
  97. const partNumber = 99
  98. const specialChars = ";?:@&=+$,#!'()"
  99. const uploadId = `Upload${specialChars}Id`
  100. // '.*' chars of path should be encoded
  101. const Key = `${specialChars}.*/${specialChars}.*.ext`
  102. const implResult = await createSignedURL({
  103. accountKey: s3ClientOptions.credentials.accessKeyId,
  104. accountSecret: s3ClientOptions.credentials.secretAccessKey,
  105. sessionToken: s3ClientOptions.credentials.sessionToken,
  106. uploadId,
  107. partNumber,
  108. bucketName,
  109. Key,
  110. Region: s3ClientOptions.region,
  111. expires: 900,
  112. })
  113. const sdkResult = new URL(
  114. await getSignedUrl(
  115. client,
  116. new UploadPartCommand({
  117. Bucket: bucketName,
  118. UploadId: uploadId,
  119. PartNumber: partNumber,
  120. Key,
  121. }),
  122. { expiresIn: 900 },
  123. ),
  124. )
  125. assert.strictEqual(implResult.pathname, sdkResult.pathname)
  126. const extractUploadId = /([?&])uploadId=([^&]+?)(&|$)/
  127. const extractSignature = /([?&])X-Amz-Signature=([^&]+?)(&|$)/
  128. assert.strictEqual(
  129. implResult.search.match(extractUploadId)![2],
  130. sdkResult.search.match(extractUploadId)![2],
  131. )
  132. assert.strictEqual(
  133. implResult.search.match(extractSignature)![2],
  134. sdkResult.search.match(extractSignature)![2],
  135. )
  136. })
  137. })