]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Use private ACL for private videos in s3
authorChocobozzz <me@florianbigard.com>
Wed, 19 Oct 2022 08:43:53 +0000 (10:43 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Mon, 24 Oct 2022 12:48:24 +0000 (14:48 +0200)
46 files changed:
.github/workflows/test.yml
config/default.yaml
config/production.yaml.example
package.json
server.ts
server/controllers/download.ts
server/controllers/index.ts
server/controllers/object-storage-proxy.ts [new file with mode: 0644]
server/helpers/webtorrent.ts
server/initializers/checker-after-init.ts
server/initializers/config.ts
server/initializers/constants.ts
server/lib/live/live-segment-sha-store.ts
server/lib/object-storage/shared/object-storage-helpers.ts
server/lib/object-storage/urls.ts
server/lib/object-storage/videos.ts
server/lib/video-privacy.ts
server/middlewares/validators/shared/videos.ts
server/middlewares/validators/static.ts
server/models/video/video-file.ts
server/models/video/video-streaming-playlist.ts
server/models/video/video.ts
server/tests/api/object-storage/index.ts
server/tests/api/object-storage/live.ts
server/tests/api/object-storage/video-imports.ts
server/tests/api/object-storage/video-static-file-privacy.ts [new file with mode: 0644]
server/tests/api/object-storage/videos.ts
server/tests/api/server/proxy.ts
server/tests/api/transcoding/create-transcoding.ts
server/tests/api/transcoding/hls.ts
server/tests/api/transcoding/update-while-transcoding.ts
server/tests/api/transcoding/video-studio.ts
server/tests/api/videos/video-static-file-privacy.ts
server/tests/cli/create-import-video-file-job.ts
server/tests/cli/create-move-video-storage-job.ts
server/tests/cli/create-transcoding-job.ts
server/tests/shared/live.ts
server/tests/shared/mock-servers/mock-object-storage.ts
server/types/express.d.ts
shared/core-utils/common/env.ts
shared/core-utils/videos/common.ts [moved from shared/core-utils/videos/privacy.ts with 57% similarity]
shared/core-utils/videos/index.ts
shared/server-commands/miscs/sql-command.ts
shared/server-commands/server/object-storage-command.ts
shared/server-commands/videos/live-command.ts
yarn.lock

index 59840d07659caa7d89b27d141d20766cbcb4bb20..65e1acec60293ab594dddf849a661d14463d5d14 100644 (file)
@@ -46,6 +46,8 @@ jobs:
       PGHOST: localhost
       NODE_PENDING_JOB_WAIT: 250
       ENABLE_OBJECT_STORAGE_TESTS: true
+      OBJECT_STORAGE_SCALEWAY_KEY_ID: ${{ secrets.OBJECT_STORAGE_SCALEWAY_KEY_ID }}
+      OBJECT_STORAGE_SCALEWAY_ACCESS_KEY: ${{ secrets.OBJECT_STORAGE_SCALEWAY_ACCESS_KEY }}
 
     steps:
       - uses: actions/checkout@v3
index f94ec620977739f048214b980c94b0a18c30702c..7753821da339a6c1016f7f3599760fda00ff1c80 100644 (file)
@@ -148,8 +148,11 @@ object_storage:
 
   region: 'us-east-1'
 
-  # Set this ACL on each uploaded object
-  upload_acl: 'public-read'
+  upload_acl:
+    # Set this ACL on each uploaded object of public/unlisted videos
+    public: 'public-read'
+      # Set this ACL on each uploaded object of private/internal videos
+    private: 'private'
 
   credentials:
     # You can also use AWS_ACCESS_KEY_ID env variable
index 48613e1c323a2d1317b1f034cb18bb850c9f6955..167d23af849311eefd3f9f8f01ede34b771eba65 100644 (file)
@@ -146,8 +146,11 @@ object_storage:
 
   region: 'us-east-1'
 
-  # Set this ACL on each uploaded object
-  upload_acl: 'public-read'
+  upload_acl:
+    # Set this ACL on each uploaded object of public/unlisted videos
+    public: 'public-read'
+      # Set this ACL on each uploaded object of private/internal videos
+    private: 'private'
 
   credentials:
     # You can also use AWS_ACCESS_KEY_ID env variable
index 23cd9e11275e279470b3edfb9a6d6eb665c6ddc5..2494550689d134397f494de92de9570aeb4f904a 100644 (file)
@@ -78,9 +78,9 @@
     "jpeg-js": "0.4.4"
   },
   "dependencies": {
-    "@aws-sdk/client-s3": "^3.23.0",
-    "@aws-sdk/lib-storage": "^3.72.0",
-    "@aws-sdk/node-http-handler": "^3.82.0",
+    "@aws-sdk/client-s3": "^3.190.0",
+    "@aws-sdk/lib-storage": "^3.190.0",
+    "@aws-sdk/node-http-handler": "^3.190.0",
     "@babel/parser": "^7.17.8",
     "@node-oauth/oauth2-server": "^4.2.0",
     "@opentelemetry/api": "^1.1.0",
index a29b5e408f5e6c2aa066ee31a19a70acbe97827b..dd595e9512303e22a6f6e5fb4fcf1f5c0d0f5df2 100644 (file)
--- a/server.ts
+++ b/server.ts
@@ -107,6 +107,7 @@ import {
   wellKnownRouter,
   lazyStaticRouter,
   servicesRouter,
+  objectStorageProxyRouter,
   pluginsRouter,
   webfingerRouter,
   trackerRouter,
@@ -240,6 +241,7 @@ app.use('/', wellKnownRouter)
 app.use('/', miscRouter)
 app.use('/', downloadRouter)
 app.use('/', lazyStaticRouter)
+app.use('/', objectStorageProxyRouter)
 
 // Client files, last valid routes!
 const cliOptions = cli.opts<{ client: boolean, plugins: boolean }>()
index abd1df26fe14216bd827b0622896e9dc002b5866..d9f34109f360dc1cdf2477e1e56edff0af812b2c 100644 (file)
@@ -5,6 +5,7 @@ import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache
 import { Hooks } from '@server/lib/plugins/hooks'
 import { VideoPathManager } from '@server/lib/video-path-manager'
 import { MStreamingPlaylist, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
+import { addQueryParams } from '@shared/core-utils'
 import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models'
 import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants'
 import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares'
@@ -84,7 +85,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response) {
   if (!checkAllowResult(res, allowParameters, allowedResult)) return
 
   if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
-    return res.redirect(videoFile.getObjectStorageUrl())
+    return redirectToObjectStorage({ req, res, video, file: videoFile })
   }
 
   await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), path => {
@@ -120,7 +121,7 @@ async function downloadHLSVideoFile (req: express.Request, res: express.Response
   if (!checkAllowResult(res, allowParameters, allowedResult)) return
 
   if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
-    return res.redirect(videoFile.getObjectStorageUrl())
+    return redirectToObjectStorage({ req, res, video, file: videoFile })
   }
 
   await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(streamingPlaylist), path => {
@@ -174,3 +175,20 @@ function checkAllowResult (res: express.Response, allowParameters: any, result?:
 
   return true
 }
+
+function redirectToObjectStorage (options: {
+  req: express.Request
+  res: express.Response
+  video: MVideo
+  file: MVideoFile
+}) {
+  const { req, res, video, file } = options
+
+  const baseUrl = file.getObjectStorageUrl(video)
+
+  const url = video.hasPrivateStaticPath() && req.query.videoFileToken
+    ? addQueryParams(baseUrl, { videoFileToken: req.query.videoFileToken })
+    : baseUrl
+
+  return res.redirect(url)
+}
index 8574a9e7bb81b7aa8ed7f28df73c6b451bb228c4..eaa2dd7c82972b91047109e8397d56ca320bd6ae 100644 (file)
@@ -1,14 +1,15 @@
 export * from './activitypub'
 export * from './api'
+export * from './bots'
 export * from './client'
 export * from './download'
 export * from './feeds'
-export * from './services'
-export * from './static'
 export * from './lazy-static'
 export * from './misc'
-export * from './webfinger'
-export * from './tracker'
-export * from './bots'
+export * from './object-storage-proxy'
 export * from './plugins'
+export * from './services'
+export * from './static'
+export * from './tracker'
+export * from './webfinger'
 export * from './well-known'
diff --git a/server/controllers/object-storage-proxy.ts b/server/controllers/object-storage-proxy.ts
new file mode 100644 (file)
index 0000000..6fedcfd
--- /dev/null
@@ -0,0 +1,78 @@
+import cors from 'cors'
+import express from 'express'
+import { OBJECT_STORAGE_PROXY_PATHS } from '@server/initializers/constants'
+import { getHLSFileReadStream, getWebTorrentFileReadStream } from '@server/lib/object-storage'
+import {
+  asyncMiddleware,
+  ensureCanAccessPrivateVideoHLSFiles,
+  ensureCanAccessVideoPrivateWebTorrentFiles,
+  optionalAuthenticate
+} from '@server/middlewares'
+import { HttpStatusCode } from '@shared/models'
+
+const objectStorageProxyRouter = express.Router()
+
+objectStorageProxyRouter.use(cors())
+
+objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.PRIVATE_WEBSEED + ':filename',
+  optionalAuthenticate,
+  asyncMiddleware(ensureCanAccessVideoPrivateWebTorrentFiles),
+  asyncMiddleware(proxifyWebTorrent)
+)
+
+objectStorageProxyRouter.get(OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:filename',
+  optionalAuthenticate,
+  asyncMiddleware(ensureCanAccessPrivateVideoHLSFiles),
+  asyncMiddleware(proxifyHLS)
+)
+
+// ---------------------------------------------------------------------------
+
+export {
+  objectStorageProxyRouter
+}
+
+async function proxifyWebTorrent (req: express.Request, res: express.Response) {
+  const filename = req.params.filename
+
+  try {
+    const stream = await getWebTorrentFileReadStream({
+      filename,
+      rangeHeader: req.header('range')
+    })
+
+    return stream.pipe(res)
+  } catch (err) {
+    return handleObjectStorageFailure(res, err)
+  }
+}
+
+async function proxifyHLS (req: express.Request, res: express.Response) {
+  const playlist = res.locals.videoStreamingPlaylist
+  const video = res.locals.onlyVideo
+  const filename = req.params.filename
+
+  try {
+    const stream = await getHLSFileReadStream({
+      playlist: playlist.withVideo(video),
+      filename,
+      rangeHeader: req.header('range')
+    })
+
+    return stream.pipe(res)
+  } catch (err) {
+    return handleObjectStorageFailure(res, err)
+  }
+}
+
+function handleObjectStorageFailure (res: express.Response, err: Error) {
+  if (err.name === 'NoSuchKey') {
+    return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
+  }
+
+  return res.fail({
+    status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
+    message: err.message,
+    type: err.name
+  })
+}
index 6d87c74f7ec8b9a31dc8a5fcce653d017634acbf..b458e86d2a0dc1a671962f03a1ef744c4d745210 100644 (file)
@@ -165,7 +165,7 @@ function generateMagnetUri (
   const xs = videoFile.getTorrentUrl()
   const announce = trackerUrls
 
-  let urlList = video.requiresAuth(video.uuid)
+  let urlList = video.hasPrivateStaticPath()
     ? []
     : [ videoFile.getFileUrl(video) ]
 
@@ -243,7 +243,7 @@ function buildAnnounceList () {
 }
 
 function buildUrlList (video: MVideo, videoFile: MVideoFile) {
-  if (video.requiresAuth(video.uuid)) return []
+  if (video.hasPrivateStaticPath()) return []
 
   return [ videoFile.getFileUrl(video) ]
 }
index c83fef425af8fc4ddd46ade9745000e54fff27e3..09e878eee27df0b42f610d838b23aa9dd5d36587 100644 (file)
@@ -278,6 +278,14 @@ function checkObjectStorageConfig () {
         'Object storage bucket prefixes should be set to different values when the same bucket is used for both types of video.'
       )
     }
+
+    if (!CONFIG.OBJECT_STORAGE.UPLOAD_ACL.PUBLIC) {
+      throw new Error('object_storage.upload_acl.public must be set')
+    }
+
+    if (!CONFIG.OBJECT_STORAGE.UPLOAD_ACL.PRIVATE) {
+      throw new Error('object_storage.upload_acl.private must be set')
+    }
   }
 }
 
index a5a0d4e46dd385b5af1a6ced51ac2c2c879184f1..ab5e645ad3714ab2c96c2c77f9ef4b2f7c3310ae 100644 (file)
@@ -118,7 +118,10 @@ const CONFIG = {
     MAX_UPLOAD_PART: bytes.parse(config.get<string>('object_storage.max_upload_part')),
     ENDPOINT: config.get<string>('object_storage.endpoint'),
     REGION: config.get<string>('object_storage.region'),
-    UPLOAD_ACL: config.get<string>('object_storage.upload_acl'),
+    UPLOAD_ACL: {
+      PUBLIC: config.get<string>('object_storage.upload_acl.public'),
+      PRIVATE: config.get<string>('object_storage.upload_acl.private')
+    },
     CREDENTIALS: {
       ACCESS_KEY_ID: config.get<string>('object_storage.credentials.access_key_id'),
       SECRET_ACCESS_KEY: config.get<string>('object_storage.credentials.secret_access_key')
index 88bdd07fea9358f2315cd1540a4b310805780c82..66eb31230b5cde07552cf0f3237f3fe2ecf31f95 100644 (file)
@@ -685,6 +685,13 @@ const LAZY_STATIC_PATHS = {
   VIDEO_CAPTIONS: '/lazy-static/video-captions/',
   TORRENTS: '/lazy-static/torrents/'
 }
+const OBJECT_STORAGE_PROXY_PATHS = {
+  PRIVATE_WEBSEED: '/object-storage-proxy/webseed/private/',
+
+  STREAMING_PLAYLISTS: {
+    PRIVATE_HLS: '/object-storage-proxy/streaming-playlists/hls/private/'
+  }
+}
 
 // Cache control
 const STATIC_MAX_AGE = {
@@ -995,6 +1002,7 @@ export {
   VIDEO_LIVE,
   PEERTUBE_VERSION,
   LAZY_STATIC_PATHS,
+  OBJECT_STORAGE_PROXY_PATHS,
   SEARCH_INDEX,
   DIRECTORIES,
   RESUMABLE_UPLOAD_SESSION_LIFETIME,
index faf03dccfebcd23f4c17b90274040198854c7495..4d03754a9dc3944b782ee117005325736cc9d545 100644 (file)
@@ -5,6 +5,7 @@ import { logger, loggerTagsFactory } from '@server/helpers/logger'
 import { MStreamingPlaylistVideo } from '@server/types/models'
 import { buildSha256Segment } from '../hls'
 import { storeHLSFileFromPath } from '../object-storage'
+import PQueue from 'p-queue'
 
 const lTags = loggerTagsFactory('live')
 
@@ -16,6 +17,7 @@ class LiveSegmentShaStore {
   private readonly sha256Path: string
   private readonly streamingPlaylist: MStreamingPlaylistVideo
   private readonly sendToObjectStorage: boolean
+  private readonly writeQueue = new PQueue({ concurrency: 1 })
 
   constructor (options: {
     videoUUID: string
@@ -37,7 +39,11 @@ class LiveSegmentShaStore {
     const segmentName = basename(segmentPath)
     this.segmentsSha256.set(segmentName, shaResult)
 
-    await this.writeToDisk()
+    try {
+      await this.writeToDisk()
+    } catch (err) {
+      logger.error('Cannot write sha segments to disk.', { err })
+    }
   }
 
   async removeSegmentSha (segmentPath: string) {
@@ -55,19 +61,20 @@ class LiveSegmentShaStore {
     await this.writeToDisk()
   }
 
-  private async writeToDisk () {
-    await writeJson(this.sha256Path, mapToJSON(this.segmentsSha256))
+  private writeToDisk () {
+    return this.writeQueue.add(async () => {
+      await writeJson(this.sha256Path, mapToJSON(this.segmentsSha256))
 
-    if (this.sendToObjectStorage) {
-      const url = await storeHLSFileFromPath(this.streamingPlaylist, this.sha256Path)
+      if (this.sendToObjectStorage) {
+        const url = await storeHLSFileFromPath(this.streamingPlaylist, this.sha256Path)
 
-      if (this.streamingPlaylist.segmentsSha256Url !== url) {
-        this.streamingPlaylist.segmentsSha256Url = url
-        await this.streamingPlaylist.save()
+        if (this.streamingPlaylist.segmentsSha256Url !== url) {
+          this.streamingPlaylist.segmentsSha256Url = url
+          await this.streamingPlaylist.save()
+        }
       }
-    }
+    })
   }
-
 }
 
 export {
index c131977e8c622684018d00e77bc492c492571e57..05b52f412aadc2ce5533715d75f07052b938cdb5 100644 (file)
@@ -2,18 +2,21 @@ import { createReadStream, createWriteStream, ensureDir, ReadStream } from 'fs-e
 import { dirname } from 'path'
 import { Readable } from 'stream'
 import {
+  _Object,
   CompleteMultipartUploadCommandOutput,
   DeleteObjectCommand,
   GetObjectCommand,
   ListObjectsV2Command,
-  PutObjectCommandInput
+  PutObjectAclCommand,
+  PutObjectCommandInput,
+  S3Client
 } from '@aws-sdk/client-s3'
 import { Upload } from '@aws-sdk/lib-storage'
 import { pipelinePromise } from '@server/helpers/core-utils'
 import { isArray } from '@server/helpers/custom-validators/misc'
 import { logger } from '@server/helpers/logger'
 import { CONFIG } from '@server/initializers/config'
-import { getPrivateUrl } from '../urls'
+import { getInternalUrl } from '../urls'
 import { getClient } from './client'
 import { lTags } from './logger'
 
@@ -44,69 +47,91 @@ async function storeObject (options: {
   inputPath: string
   objectStorageKey: string
   bucketInfo: BucketInfo
+  isPrivate: boolean
 }): Promise<string> {
-  const { inputPath, objectStorageKey, bucketInfo } = options
+  const { inputPath, objectStorageKey, bucketInfo, isPrivate } = options
 
   logger.debug('Uploading file %s to %s%s in bucket %s', inputPath, bucketInfo.PREFIX, objectStorageKey, bucketInfo.BUCKET_NAME, lTags())
 
   const fileStream = createReadStream(inputPath)
 
-  return uploadToStorage({ objectStorageKey, content: fileStream, bucketInfo })
+  return uploadToStorage({ objectStorageKey, content: fileStream, bucketInfo, isPrivate })
 }
 
 // ---------------------------------------------------------------------------
 
-async function removeObject (filename: string, bucketInfo: BucketInfo) {
-  const command = new DeleteObjectCommand({
+function updateObjectACL (options: {
+  objectStorageKey: string
+  bucketInfo: BucketInfo
+  isPrivate: boolean
+}) {
+  const { objectStorageKey, bucketInfo, isPrivate } = options
+
+  const key = buildKey(objectStorageKey, bucketInfo)
+
+  logger.debug('Updating ACL file %s in bucket %s', key, bucketInfo.BUCKET_NAME, lTags())
+
+  const command = new PutObjectAclCommand({
     Bucket: bucketInfo.BUCKET_NAME,
-    Key: buildKey(filename, bucketInfo)
+    Key: key,
+    ACL: getACL(isPrivate)
   })
 
   return getClient().send(command)
 }
 
-async function removePrefix (prefix: string, bucketInfo: BucketInfo) {
-  const s3Client = getClient()
-
-  const commandPrefix = bucketInfo.PREFIX + prefix
-  const listCommand = new ListObjectsV2Command({
-    Bucket: bucketInfo.BUCKET_NAME,
-    Prefix: commandPrefix
+function updatePrefixACL (options: {
+  prefix: string
+  bucketInfo: BucketInfo
+  isPrivate: boolean
+}) {
+  const { prefix, bucketInfo, isPrivate } = options
+
+  logger.debug('Updating ACL of files in prefix %s in bucket %s', prefix, bucketInfo.BUCKET_NAME, lTags())
+
+  return applyOnPrefix({
+    prefix,
+    bucketInfo,
+    commandBuilder: obj => {
+      return new PutObjectAclCommand({
+        Bucket: bucketInfo.BUCKET_NAME,
+        Key: obj.Key,
+        ACL: getACL(isPrivate)
+      })
+    }
   })
+}
 
-  const listedObjects = await s3Client.send(listCommand)
+// ---------------------------------------------------------------------------
 
-  // FIXME: use bulk delete when s3ninja will support this operation
-  // const deleteParams = {
-  //   Bucket: bucketInfo.BUCKET_NAME,
-  //   Delete: { Objects: [] }
-  // }
+function removeObject (objectStorageKey: string, bucketInfo: BucketInfo) {
+  const key = buildKey(objectStorageKey, bucketInfo)
 
-  if (isArray(listedObjects.Contents) !== true) {
-    const message = `Cannot remove ${commandPrefix} prefix in bucket ${bucketInfo.BUCKET_NAME}: no files listed.`
+  logger.debug('Removing file %s in bucket %s', key, bucketInfo.BUCKET_NAME, lTags())
 
-    logger.error(message, { response: listedObjects, ...lTags() })
-    throw new Error(message)
-  }
-
-  for (const object of listedObjects.Contents) {
-    const command = new DeleteObjectCommand({
-      Bucket: bucketInfo.BUCKET_NAME,
-      Key: object.Key
-    })
-
-    await s3Client.send(command)
+  const command = new DeleteObjectCommand({
+    Bucket: bucketInfo.BUCKET_NAME,
+    Key: key
+  })
 
-    // FIXME: use bulk delete when s3ninja will support this operation
-    // deleteParams.Delete.Objects.push({ Key: object.Key })
-  }
+  return getClient().send(command)
+}
 
+function removePrefix (prefix: string, bucketInfo: BucketInfo) {
   // FIXME: use bulk delete when s3ninja will support this operation
-  // const deleteCommand = new DeleteObjectsCommand(deleteParams)
-  // await s3Client.send(deleteCommand)
 
-  // Repeat if not all objects could be listed at once (limit of 1000?)
-  if (listedObjects.IsTruncated) await removePrefix(prefix, bucketInfo)
+  logger.debug('Removing prefix %s in bucket %s', prefix, bucketInfo.BUCKET_NAME, lTags())
+
+  return applyOnPrefix({
+    prefix,
+    bucketInfo,
+    commandBuilder: obj => {
+      return new DeleteObjectCommand({
+        Bucket: bucketInfo.BUCKET_NAME,
+        Key: obj.Key
+      })
+    }
+  })
 }
 
 // ---------------------------------------------------------------------------
@@ -138,14 +163,42 @@ function buildKey (key: string, bucketInfo: BucketInfo) {
 
 // ---------------------------------------------------------------------------
 
+async function createObjectReadStream (options: {
+  key: string
+  bucketInfo: BucketInfo
+  rangeHeader: string
+}) {
+  const { key, bucketInfo, rangeHeader } = options
+
+  const command = new GetObjectCommand({
+    Bucket: bucketInfo.BUCKET_NAME,
+    Key: buildKey(key, bucketInfo),
+    Range: rangeHeader
+  })
+
+  const response = await getClient().send(command)
+
+  return response.Body as Readable
+}
+
+// ---------------------------------------------------------------------------
+
 export {
   BucketInfo,
   buildKey,
+
   storeObject,
+
   removeObject,
   removePrefix,
+
   makeAvailable,
-  listKeysOfPrefix
+
+  updateObjectACL,
+  updatePrefixACL,
+
+  listKeysOfPrefix,
+  createObjectReadStream
 }
 
 // ---------------------------------------------------------------------------
@@ -154,17 +207,15 @@ async function uploadToStorage (options: {
   content: ReadStream
   objectStorageKey: string
   bucketInfo: BucketInfo
+  isPrivate: boolean
 }) {
-  const { content, objectStorageKey, bucketInfo } = options
+  const { content, objectStorageKey, bucketInfo, isPrivate } = options
 
   const input: PutObjectCommandInput = {
     Body: content,
     Bucket: bucketInfo.BUCKET_NAME,
-    Key: buildKey(objectStorageKey, bucketInfo)
-  }
-
-  if (CONFIG.OBJECT_STORAGE.UPLOAD_ACL) {
-    input.ACL = CONFIG.OBJECT_STORAGE.UPLOAD_ACL
+    Key: buildKey(objectStorageKey, bucketInfo),
+    ACL: getACL(isPrivate)
   }
 
   const parallelUploads3 = new Upload({
@@ -194,5 +245,50 @@ async function uploadToStorage (options: {
     bucketInfo.PREFIX, objectStorageKey, bucketInfo.BUCKET_NAME, lTags()
   )
 
-  return getPrivateUrl(bucketInfo, objectStorageKey)
+  return getInternalUrl(bucketInfo, objectStorageKey)
+}
+
+async function applyOnPrefix (options: {
+  prefix: string
+  bucketInfo: BucketInfo
+  commandBuilder: (obj: _Object) => Parameters<S3Client['send']>[0]
+
+  continuationToken?: string
+}) {
+  const { prefix, bucketInfo, commandBuilder, continuationToken } = options
+
+  const s3Client = getClient()
+
+  const commandPrefix = bucketInfo.PREFIX + prefix
+  const listCommand = new ListObjectsV2Command({
+    Bucket: bucketInfo.BUCKET_NAME,
+    Prefix: commandPrefix,
+    ContinuationToken: continuationToken
+  })
+
+  const listedObjects = await s3Client.send(listCommand)
+
+  if (isArray(listedObjects.Contents) !== true) {
+    const message = `Cannot apply function on ${commandPrefix} prefix in bucket ${bucketInfo.BUCKET_NAME}: no files listed.`
+
+    logger.error(message, { response: listedObjects, ...lTags() })
+    throw new Error(message)
+  }
+
+  for (const object of listedObjects.Contents) {
+    const command = commandBuilder(object)
+
+    await s3Client.send(command)
+  }
+
+  // Repeat if not all objects could be listed at once (limit of 1000?)
+  if (listedObjects.IsTruncated) {
+    await applyOnPrefix({ ...options, continuationToken: listedObjects.ContinuationToken })
+  }
+}
+
+function getACL (isPrivate: boolean) {
+  return isPrivate
+    ? CONFIG.OBJECT_STORAGE.UPLOAD_ACL.PRIVATE
+    : CONFIG.OBJECT_STORAGE.UPLOAD_ACL.PUBLIC
 }
index 2a889190b9f5bfe1c97a674426f65c2ffeb0b9ca..a47a98b9833b9e1be97912bac0f216f35d3a1bdd 100644 (file)
@@ -1,10 +1,14 @@
 import { CONFIG } from '@server/initializers/config'
+import { OBJECT_STORAGE_PROXY_PATHS, WEBSERVER } from '@server/initializers/constants'
+import { MVideoUUID } from '@server/types/models'
 import { BucketInfo, buildKey, getEndpointParsed } from './shared'
 
-function getPrivateUrl (config: BucketInfo, keyWithoutPrefix: string) {
+function getInternalUrl (config: BucketInfo, keyWithoutPrefix: string) {
   return getBaseUrl(config) + buildKey(keyWithoutPrefix, config)
 }
 
+// ---------------------------------------------------------------------------
+
 function getWebTorrentPublicFileUrl (fileUrl: string) {
   const baseUrl = CONFIG.OBJECT_STORAGE.VIDEOS.BASE_URL
   if (!baseUrl) return fileUrl
@@ -19,11 +23,28 @@ function getHLSPublicFileUrl (fileUrl: string) {
   return replaceByBaseUrl(fileUrl, baseUrl)
 }
 
+// ---------------------------------------------------------------------------
+
+function getHLSPrivateFileUrl (video: MVideoUUID, filename: string) {
+  return WEBSERVER.URL + OBJECT_STORAGE_PROXY_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + video.uuid + `/${filename}`
+}
+
+function getWebTorrentPrivateFileUrl (filename: string) {
+  return WEBSERVER.URL + OBJECT_STORAGE_PROXY_PATHS.PRIVATE_WEBSEED + filename
+}
+
+// ---------------------------------------------------------------------------
+
 export {
-  getPrivateUrl,
+  getInternalUrl,
+
   getWebTorrentPublicFileUrl,
-  replaceByBaseUrl,
-  getHLSPublicFileUrl
+  getHLSPublicFileUrl,
+
+  getHLSPrivateFileUrl,
+  getWebTorrentPrivateFileUrl,
+
+  replaceByBaseUrl
 }
 
 // ---------------------------------------------------------------------------
index e323baaa2f619916df62e2bce817534b037623e4..003807826ccc10d72da78dc4877e00c17cfc0297 100644 (file)
@@ -5,7 +5,17 @@ import { MStreamingPlaylistVideo, MVideo, MVideoFile } from '@server/types/model
 import { getHLSDirectory } from '../paths'
 import { VideoPathManager } from '../video-path-manager'
 import { generateHLSObjectBaseStorageKey, generateHLSObjectStorageKey, generateWebTorrentObjectStorageKey } from './keys'
-import { listKeysOfPrefix, lTags, makeAvailable, removeObject, removePrefix, storeObject } from './shared'
+import {
+  createObjectReadStream,
+  listKeysOfPrefix,
+  lTags,
+  makeAvailable,
+  removeObject,
+  removePrefix,
+  storeObject,
+  updateObjectACL,
+  updatePrefixACL
+} from './shared'
 
 function listHLSFileKeysOf (playlist: MStreamingPlaylistVideo) {
   return listKeysOfPrefix(generateHLSObjectBaseStorageKey(playlist), CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS)
@@ -17,7 +27,8 @@ function storeHLSFileFromFilename (playlist: MStreamingPlaylistVideo, filename:
   return storeObject({
     inputPath: join(getHLSDirectory(playlist.Video), filename),
     objectStorageKey: generateHLSObjectStorageKey(playlist, filename),
-    bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS
+    bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS,
+    isPrivate: playlist.Video.hasPrivateStaticPath()
   })
 }
 
@@ -25,7 +36,8 @@ function storeHLSFileFromPath (playlist: MStreamingPlaylistVideo, path: string)
   return storeObject({
     inputPath: path,
     objectStorageKey: generateHLSObjectStorageKey(playlist, basename(path)),
-    bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS
+    bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS,
+    isPrivate: playlist.Video.hasPrivateStaticPath()
   })
 }
 
@@ -35,7 +47,26 @@ function storeWebTorrentFile (video: MVideo, file: MVideoFile) {
   return storeObject({
     inputPath: VideoPathManager.Instance.getFSVideoFileOutputPath(video, file),
     objectStorageKey: generateWebTorrentObjectStorageKey(file.filename),
-    bucketInfo: CONFIG.OBJECT_STORAGE.VIDEOS
+    bucketInfo: CONFIG.OBJECT_STORAGE.VIDEOS,
+    isPrivate: video.hasPrivateStaticPath()
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+function updateWebTorrentFileACL (video: MVideo, file: MVideoFile) {
+  return updateObjectACL({
+    objectStorageKey: generateWebTorrentObjectStorageKey(file.filename),
+    bucketInfo: CONFIG.OBJECT_STORAGE.VIDEOS,
+    isPrivate: video.hasPrivateStaticPath()
+  })
+}
+
+function updateHLSFilesACL (playlist: MStreamingPlaylistVideo) {
+  return updatePrefixACL({
+    prefix: generateHLSObjectBaseStorageKey(playlist),
+    bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS,
+    isPrivate: playlist.Video.hasPrivateStaticPath()
   })
 }
 
@@ -87,6 +118,39 @@ async function makeWebTorrentFileAvailable (filename: string, destination: strin
 
 // ---------------------------------------------------------------------------
 
+function getWebTorrentFileReadStream (options: {
+  filename: string
+  rangeHeader: string
+}) {
+  const { filename, rangeHeader } = options
+
+  const key = generateWebTorrentObjectStorageKey(filename)
+
+  return createObjectReadStream({
+    key,
+    bucketInfo: CONFIG.OBJECT_STORAGE.VIDEOS,
+    rangeHeader
+  })
+}
+
+function getHLSFileReadStream (options: {
+  playlist: MStreamingPlaylistVideo
+  filename: string
+  rangeHeader: string
+}) {
+  const { playlist, filename, rangeHeader } = options
+
+  const key = generateHLSObjectStorageKey(playlist, filename)
+
+  return createObjectReadStream({
+    key,
+    bucketInfo: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS,
+    rangeHeader
+  })
+}
+
+// ---------------------------------------------------------------------------
+
 export {
   listHLSFileKeysOf,
 
@@ -94,10 +158,16 @@ export {
   storeHLSFileFromFilename,
   storeHLSFileFromPath,
 
+  updateWebTorrentFileACL,
+  updateHLSFilesACL,
+
   removeHLSObjectStorage,
   removeHLSFileObjectStorage,
   removeWebTorrentObjectStorage,
 
   makeWebTorrentFileAvailable,
-  makeHLSFileAvailable
+  makeHLSFileAvailable,
+
+  getWebTorrentFileReadStream,
+  getHLSFileReadStream
 }
index 1a4a5a22dcf78f5505929a6db3af68562889e7f8..41f9d62b3c514ce0b0938b8513cc31fb06fa58f9 100644 (file)
@@ -2,8 +2,9 @@ import { move } from 'fs-extra'
 import { join } from 'path'
 import { logger } from '@server/helpers/logger'
 import { DIRECTORIES } from '@server/initializers/constants'
-import { MVideo, MVideoFullLight } from '@server/types/models'
-import { VideoPrivacy } from '@shared/models'
+import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
+import { VideoPrivacy, VideoStorage } from '@shared/models'
+import { updateHLSFilesACL, updateWebTorrentFileACL } from './object-storage'
 
 function setVideoPrivacy (video: MVideo, newPrivacy: VideoPrivacy) {
   if (video.privacy === VideoPrivacy.PRIVATE && newPrivacy !== VideoPrivacy.PRIVATE) {
@@ -50,47 +51,77 @@ export {
 
 // ---------------------------------------------------------------------------
 
+type MoveType = 'private-to-public' | 'public-to-private'
+
 async function moveFiles (options: {
-  type: 'private-to-public' | 'public-to-private'
+  type: MoveType
   video: MVideoFullLight
 }) {
   const { type, video } = options
 
-  const directories = type === 'private-to-public'
-    ? {
-      webtorrent: { old: DIRECTORIES.VIDEOS.PRIVATE, new: DIRECTORIES.VIDEOS.PUBLIC },
-      hls: { old: DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, new: DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC }
+  for (const file of video.VideoFiles) {
+    if (file.storage === VideoStorage.FILE_SYSTEM) {
+      await moveWebTorrentFileOnFS(type, video, file)
+    } else {
+      await updateWebTorrentFileACL(video, file)
     }
-    : {
-      webtorrent: { old: DIRECTORIES.VIDEOS.PUBLIC, new: DIRECTORIES.VIDEOS.PRIVATE },
-      hls: { old: DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC, new: DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE }
+  }
+
+  const hls = video.getHLSPlaylist()
+
+  if (hls) {
+    if (hls.storage === VideoStorage.FILE_SYSTEM) {
+      await moveHLSFilesOnFS(type, video)
+    } else {
+      await updateHLSFilesACL(hls)
     }
+  }
+}
 
-  for (const file of video.VideoFiles) {
-    const source = join(directories.webtorrent.old, file.filename)
-    const destination = join(directories.webtorrent.new, file.filename)
+async function moveWebTorrentFileOnFS (type: MoveType, video: MVideo, file: MVideoFile) {
+  const directories = getWebTorrentDirectories(type)
 
-    try {
-      logger.info('Moving WebTorrent files of %s after privacy change (%s -> %s).', video.uuid, source, destination)
+  const source = join(directories.old, file.filename)
+  const destination = join(directories.new, file.filename)
 
-      await move(source, destination)
-    } catch (err) {
-      logger.error('Cannot move webtorrent file %s to %s after privacy change', source, destination, { err })
-    }
+  try {
+    logger.info('Moving WebTorrent files of %s after privacy change (%s -> %s).', video.uuid, source, destination)
+
+    await move(source, destination)
+  } catch (err) {
+    logger.error('Cannot move webtorrent file %s to %s after privacy change', source, destination, { err })
+  }
+}
+
+function getWebTorrentDirectories (moveType: MoveType) {
+  if (moveType === 'private-to-public') {
+    return { old: DIRECTORIES.VIDEOS.PRIVATE, new: DIRECTORIES.VIDEOS.PUBLIC }
   }
 
-  const hls = video.getHLSPlaylist()
+  return { old: DIRECTORIES.VIDEOS.PUBLIC, new: DIRECTORIES.VIDEOS.PRIVATE }
+}
 
-  if (hls) {
-    const source = join(directories.hls.old, video.uuid)
-    const destination = join(directories.hls.new, video.uuid)
+// ---------------------------------------------------------------------------
 
-    try {
-      logger.info('Moving HLS files of %s after privacy change (%s -> %s).', video.uuid, source, destination)
+async function moveHLSFilesOnFS (type: MoveType, video: MVideo) {
+  const directories = getHLSDirectories(type)
 
-      await move(source, destination)
-    } catch (err) {
-      logger.error('Cannot move HLS file %s to %s after privacy change', source, destination, { err })
-    }
+  const source = join(directories.old, video.uuid)
+  const destination = join(directories.new, video.uuid)
+
+  try {
+    logger.info('Moving HLS files of %s after privacy change (%s -> %s).', video.uuid, source, destination)
+
+    await move(source, destination)
+  } catch (err) {
+    logger.error('Cannot move HLS file %s to %s after privacy change', source, destination, { err })
+  }
+}
+
+function getHLSDirectories (moveType: MoveType) {
+  if (moveType === 'private-to-public') {
+    return { old: DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, new: DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC }
   }
+
+  return { old: DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC, new: DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE }
 }
index c29751eca875b4047c3a79b8f9ecb81d224dd8ef..ebbfc0a0a9242232c349dd070c612b4ac5d51549 100644 (file)
@@ -111,7 +111,7 @@ async function checkCanSeeVideo (options: {
 }) {
   const { req, res, video, paramId } = options
 
-  if (video.requiresAuth(paramId)) {
+  if (video.requiresAuth({ urlParamId: paramId, checkBlacklist: true })) {
     return checkCanSeeAuthVideo(req, res, video)
   }
 
@@ -174,13 +174,13 @@ async function checkCanAccessVideoStaticFiles (options: {
   res: Response
   paramId: string
 }) {
-  const { video, req, res, paramId } = options
+  const { video, req, res } = options
 
   if (res.locals.oauth?.token.User) {
     return checkCanSeeVideo(options)
   }
 
-  if (!video.requiresAuth(paramId)) return true
+  if (!video.hasPrivateStaticPath()) return true
 
   const videoFileToken = req.query.videoFileToken
   if (!videoFileToken) {
index ff9e6ae6e34738ae589902bdb0f8faf742b06c24..13fde6dd13e2904db0ef728a3f15b97b56e67e4c 100644 (file)
@@ -7,10 +7,17 @@ import { logger } from '@server/helpers/logger'
 import { LRU_CACHE } from '@server/initializers/constants'
 import { VideoModel } from '@server/models/video/video'
 import { VideoFileModel } from '@server/models/video/video-file'
+import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models'
 import { HttpStatusCode } from '@shared/models'
 import { areValidationErrors, checkCanAccessVideoStaticFiles } from './shared'
 
-const staticFileTokenBypass = new LRUCache<string, boolean>({
+type LRUValue = {
+  allowed: boolean
+  video?: MVideoThumbnail
+  file?: MVideoFile
+  playlist?: MStreamingPlaylist }
+
+const staticFileTokenBypass = new LRUCache<string, LRUValue>({
   max: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.MAX_SIZE,
   ttl: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.TTL
 })
@@ -27,18 +34,26 @@ const ensureCanAccessVideoPrivateWebTorrentFiles = [
     const cacheKey = token + '-' + req.originalUrl
 
     if (staticFileTokenBypass.has(cacheKey)) {
-      const allowedFromCache = staticFileTokenBypass.get(cacheKey)
+      const { allowed, file, video } = staticFileTokenBypass.get(cacheKey)
+
+      if (allowed === true) {
+        res.locals.onlyVideo = video
+        res.locals.videoFile = file
 
-      if (allowedFromCache === true) return next()
+        return next()
+      }
 
       return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
     }
 
-    const allowed = await isWebTorrentAllowed(req, res)
+    const result = await isWebTorrentAllowed(req, res)
+
+    staticFileTokenBypass.set(cacheKey, result)
 
-    staticFileTokenBypass.set(cacheKey, allowed)
+    if (result.allowed !== true) return
 
-    if (allowed !== true) return
+    res.locals.onlyVideo = result.video
+    res.locals.videoFile = result.file
 
     return next()
   }
@@ -64,18 +79,28 @@ const ensureCanAccessPrivateVideoHLSFiles = [
     const cacheKey = token + '-' + videoUUID
 
     if (staticFileTokenBypass.has(cacheKey)) {
-      const allowedFromCache = staticFileTokenBypass.get(cacheKey)
+      const { allowed, file, playlist, video } = staticFileTokenBypass.get(cacheKey)
 
-      if (allowedFromCache === true) return next()
+      if (allowed === true) {
+        res.locals.onlyVideo = video
+        res.locals.videoFile = file
+        res.locals.videoStreamingPlaylist = playlist
+
+        return next()
+      }
 
       return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
     }
 
-    const allowed = await isHLSAllowed(req, res, videoUUID)
+    const result = await isHLSAllowed(req, res, videoUUID)
+
+    staticFileTokenBypass.set(cacheKey, result)
 
-    staticFileTokenBypass.set(cacheKey, allowed)
+    if (result.allowed !== true) return
 
-    if (allowed !== true) return
+    res.locals.onlyVideo = result.video
+    res.locals.videoFile = result.file
+    res.locals.videoStreamingPlaylist = result.playlist
 
     return next()
   }
@@ -96,25 +121,38 @@ async function isWebTorrentAllowed (req: express.Request, res: express.Response)
     logger.debug('Unknown static file %s to serve', req.originalUrl, { filename })
 
     res.sendStatus(HttpStatusCode.FORBIDDEN_403)
-    return false
+    return { allowed: false }
   }
 
-  const video = file.getVideo()
+  const video = await VideoModel.load(file.getVideo().id)
 
-  return checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
+  return {
+    file,
+    video,
+    allowed: await checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
+  }
 }
 
 async function isHLSAllowed (req: express.Request, res: express.Response, videoUUID: string) {
-  const video = await VideoModel.load(videoUUID)
+  const filename = basename(req.path)
+
+  const video = await VideoModel.loadWithFiles(videoUUID)
 
   if (!video) {
     logger.debug('Unknown static file %s to serve', req.originalUrl, { videoUUID })
 
     res.sendStatus(HttpStatusCode.FORBIDDEN_403)
-    return false
+    return { allowed: false }
   }
 
-  return checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
+  const file = await VideoFileModel.loadByFilename(filename)
+
+  return {
+    file,
+    video,
+    playlist: video.getHLSPlaylist(),
+    allowed: await checkCanAccessVideoStaticFiles({ req, res, video, paramId: video.uuid })
+  }
 }
 
 function extractTokenOrDie (req: express.Request, res: express.Response) {
index 1a608932f82ee7c993cb577b3e4e7bdc6daa4cec..c20c90c1b2b61fafbdcc5bda4d3176e2293573ac 100644 (file)
@@ -22,7 +22,12 @@ import validator from 'validator'
 import { logger } from '@server/helpers/logger'
 import { extractVideo } from '@server/helpers/video'
 import { buildRemoteVideoBaseUrl } from '@server/lib/activitypub/url'
-import { getHLSPublicFileUrl, getWebTorrentPublicFileUrl } from '@server/lib/object-storage'
+import {
+  getHLSPrivateFileUrl,
+  getHLSPublicFileUrl,
+  getWebTorrentPrivateFileUrl,
+  getWebTorrentPublicFileUrl
+} from '@server/lib/object-storage'
 import { getFSTorrentFilePath } from '@server/lib/paths'
 import { isVideoInPrivateDirectory } from '@server/lib/video-privacy'
 import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoWithHost } from '@server/types/models'
@@ -503,7 +508,25 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
     return !!this.videoStreamingPlaylistId
   }
 
-  getObjectStorageUrl () {
+  // ---------------------------------------------------------------------------
+
+  getObjectStorageUrl (video: MVideo) {
+    if (video.hasPrivateStaticPath()) {
+      return this.getPrivateObjectStorageUrl(video)
+    }
+
+    return this.getPublicObjectStorageUrl()
+  }
+
+  private getPrivateObjectStorageUrl (video: MVideo) {
+    if (this.isHLS()) {
+      return getHLSPrivateFileUrl(video, this.filename)
+    }
+
+    return getWebTorrentPrivateFileUrl(this.filename)
+  }
+
+  private getPublicObjectStorageUrl () {
     if (this.isHLS()) {
       return getHLSPublicFileUrl(this.fileUrl)
     }
@@ -511,26 +534,29 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
     return getWebTorrentPublicFileUrl(this.fileUrl)
   }
 
+  // ---------------------------------------------------------------------------
+
   getFileUrl (video: MVideo) {
-    if (this.storage === VideoStorage.OBJECT_STORAGE) {
-      return this.getObjectStorageUrl()
-    }
+    if (video.isOwned()) {
+      if (this.storage === VideoStorage.OBJECT_STORAGE) {
+        return this.getObjectStorageUrl(video)
+      }
 
-    if (!this.Video) this.Video = video as VideoModel
-    if (video.isOwned()) return WEBSERVER.URL + this.getFileStaticPath(video)
+      return WEBSERVER.URL + this.getFileStaticPath(video)
+    }
 
     return this.fileUrl
   }
 
+  // ---------------------------------------------------------------------------
+
   getFileStaticPath (video: MVideo) {
-    if (this.isHLS()) {
-      if (isVideoInPrivateDirectory(video.privacy)) {
-        return join(STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS, video.uuid, this.filename)
-      }
+    if (this.isHLS()) return this.getHLSFileStaticPath(video)
 
-      return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, video.uuid, this.filename)
-    }
+    return this.getWebTorrentFileStaticPath(video)
+  }
 
+  private getWebTorrentFileStaticPath (video: MVideo) {
     if (isVideoInPrivateDirectory(video.privacy)) {
       return join(STATIC_PATHS.PRIVATE_WEBSEED, this.filename)
     }
@@ -538,6 +564,16 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
     return join(STATIC_PATHS.WEBSEED, this.filename)
   }
 
+  private getHLSFileStaticPath (video: MVideo) {
+    if (isVideoInPrivateDirectory(video.privacy)) {
+      return join(STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS, video.uuid, this.filename)
+    }
+
+    return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, video.uuid, this.filename)
+  }
+
+  // ---------------------------------------------------------------------------
+
   getFileDownloadUrl (video: MVideoWithHost) {
     const path = this.isHLS()
       ? join(STATIC_DOWNLOAD_PATHS.HLS_VIDEOS, `${video.uuid}-${this.resolution}-fragmented${this.extname}`)
index b919046ed02e9d1f3dd6aacd9d7b3f4b99d47f92..1318a4daea5ab4d8b1956a02f5867d8a1ae266bf 100644 (file)
@@ -15,7 +15,7 @@ import {
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
-import { getHLSPublicFileUrl } from '@server/lib/object-storage'
+import { getHLSPrivateFileUrl, getHLSPublicFileUrl } from '@server/lib/object-storage'
 import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename } from '@server/lib/paths'
 import { isVideoInPrivateDirectory } from '@server/lib/video-privacy'
 import { VideoFileModel } from '@server/models/video/video-file'
@@ -245,10 +245,12 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
     this.p2pMediaLoaderInfohashes = VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(masterPlaylistUrl, files)
   }
 
+  // ---------------------------------------------------------------------------
+
   getMasterPlaylistUrl (video: MVideo) {
     if (video.isOwned()) {
       if (this.storage === VideoStorage.OBJECT_STORAGE) {
-        return getHLSPublicFileUrl(this.playlistUrl)
+        return this.getMasterPlaylistObjectStorageUrl(video)
       }
 
       return WEBSERVER.URL + this.getMasterPlaylistStaticPath(video)
@@ -257,10 +259,20 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
     return this.playlistUrl
   }
 
+  private getMasterPlaylistObjectStorageUrl (video: MVideo) {
+    if (video.hasPrivateStaticPath()) {
+      return getHLSPrivateFileUrl(video, this.playlistFilename)
+    }
+
+    return getHLSPublicFileUrl(this.playlistUrl)
+  }
+
+  // ---------------------------------------------------------------------------
+
   getSha256SegmentsUrl (video: MVideo) {
     if (video.isOwned()) {
       if (this.storage === VideoStorage.OBJECT_STORAGE) {
-        return getHLSPublicFileUrl(this.segmentsSha256Url)
+        return this.getSha256SegmentsObjectStorageUrl(video)
       }
 
       return WEBSERVER.URL + this.getSha256SegmentsStaticPath(video)
@@ -269,6 +281,16 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
     return this.segmentsSha256Url
   }
 
+  private getSha256SegmentsObjectStorageUrl (video: MVideo) {
+    if (video.hasPrivateStaticPath()) {
+      return getHLSPrivateFileUrl(video, this.segmentsSha256Filename)
+    }
+
+    return getHLSPublicFileUrl(this.segmentsSha256Url)
+  }
+
+  // ---------------------------------------------------------------------------
+
   getStringType () {
     if (this.type === VideoStreamingPlaylistType.HLS) return 'hls'
 
index 82362917ec605d71514036657754cf2edf24b78e..c568075d8b2a55a95dee670470e841c42d27f709 100644 (file)
@@ -30,6 +30,7 @@ import { removeHLSFileObjectStorage, removeHLSObjectStorage, removeWebTorrentObj
 import { tracer } from '@server/lib/opentelemetry/tracing'
 import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from '@server/lib/paths'
 import { VideoPathManager } from '@server/lib/video-path-manager'
+import { isVideoInPrivateDirectory } from '@server/lib/video-privacy'
 import { getServerActor } from '@server/models/application/application'
 import { ModelCache } from '@server/models/model-cache'
 import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
@@ -1764,9 +1765,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     const playlist = this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
     if (!playlist) return undefined
 
-    playlist.Video = this
-
-    return playlist
+    return playlist.withVideo(this)
   }
 
   setHLSPlaylist (playlist: MStreamingPlaylist) {
@@ -1868,16 +1867,39 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     return setAsUpdated('video', this.id, transaction)
   }
 
-  requiresAuth (paramId: string) {
+  // ---------------------------------------------------------------------------
+
+  requiresAuth (options: {
+    urlParamId: string
+    checkBlacklist: boolean
+  }) {
+    const { urlParamId, checkBlacklist } = options
+
+    if (this.privacy === VideoPrivacy.PRIVATE || this.privacy === VideoPrivacy.INTERNAL) {
+      return true
+    }
+
     if (this.privacy === VideoPrivacy.UNLISTED) {
-      if (!isUUIDValid(paramId)) return true
+      if (urlParamId && !isUUIDValid(urlParamId)) return true
 
       return false
     }
 
-    return this.privacy === VideoPrivacy.PRIVATE || this.privacy === VideoPrivacy.INTERNAL || !!this.VideoBlacklist
+    if (checkBlacklist && this.VideoBlacklist) return true
+
+    if (this.privacy !== VideoPrivacy.PUBLIC) {
+      throw new Error(`Unknown video privacy ${this.privacy} to know if the video requires auth`)
+    }
+
+    return false
   }
 
+  hasPrivateStaticPath () {
+    return isVideoInPrivateDirectory(this.privacy)
+  }
+
+  // ---------------------------------------------------------------------------
+
   async setNewState (newState: VideoState, isNewVideo: boolean, transaction: Transaction) {
     if (this.state === newState) throw new Error('Cannot use same state ' + newState)
 
index f319d6ef58d6988ce8b844d7ff3fa9f3e4ad1693..1f4489fa30b7419d211e6868605eb7d9e831800e 100644 (file)
@@ -1,3 +1,4 @@
 export * from './live'
 export * from './video-imports'
+export * from './video-static-file-privacy'
 export * from './videos'
index 77f3a8066ce485ac0f06be3960eb6f216f665713..ad2b554b7b9dc28b9cd56809f6859e5ebf672f65 100644 (file)
@@ -2,7 +2,7 @@
 
 import { expect } from 'chai'
 import { expectStartWith, testVideoResolutions } from '@server/tests/shared'
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode, LiveVideoCreate, VideoPrivacy } from '@shared/models'
 import {
   createMultipleServers,
@@ -46,7 +46,7 @@ async function checkFilesExist (servers: PeerTubeServer[], videoUUID: string, nu
     expect(files).to.have.lengthOf(numberOfFiles)
 
     for (const file of files) {
-      expectStartWith(file.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl())
+      expectStartWith(file.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
 
       await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
     }
@@ -75,16 +75,16 @@ async function checkFilesCleanup (server: PeerTubeServer, videoUUID: string, res
 }
 
 describe('Object storage for lives', function () {
-  if (areObjectStorageTestsDisabled()) return
+  if (areMockObjectStorageTestsDisabled()) return
 
   let servers: PeerTubeServer[]
 
   before(async function () {
     this.timeout(120000)
 
-    await ObjectStorageCommand.prepareDefaultBuckets()
+    await ObjectStorageCommand.prepareDefaultMockBuckets()
 
-    servers = await createMultipleServers(2, ObjectStorageCommand.getDefaultConfig())
+    servers = await createMultipleServers(2, ObjectStorageCommand.getDefaultMockConfig())
 
     await setAccessTokensToServers(servers)
     await setDefaultVideoChannel(servers)
index 90988ea9a1d0c052f08c59b928cf268d30619f67..11c86641105b8cc7ee3c719f20ade28a46034e3d 100644 (file)
@@ -2,7 +2,7 @@
 
 import { expect } from 'chai'
 import { expectStartWith, FIXTURE_URLS } from '@server/tests/shared'
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode, VideoPrivacy } from '@shared/models'
 import {
   createSingleServer,
@@ -29,16 +29,16 @@ async function importVideo (server: PeerTubeServer) {
 }
 
 describe('Object storage for video import', function () {
-  if (areObjectStorageTestsDisabled()) return
+  if (areMockObjectStorageTestsDisabled()) return
 
   let server: PeerTubeServer
 
   before(async function () {
     this.timeout(120000)
 
-    await ObjectStorageCommand.prepareDefaultBuckets()
+    await ObjectStorageCommand.prepareDefaultMockBuckets()
 
-    server = await createSingleServer(1, ObjectStorageCommand.getDefaultConfig())
+    server = await createSingleServer(1, ObjectStorageCommand.getDefaultMockConfig())
 
     await setAccessTokensToServers([ server ])
     await setDefaultVideoChannel([ server ])
@@ -64,7 +64,7 @@ describe('Object storage for video import', function () {
       expect(video.streamingPlaylists).to.have.lengthOf(0)
 
       const fileUrl = video.files[0].fileUrl
-      expectStartWith(fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
+      expectStartWith(fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
 
       await makeRawRequest({ url: fileUrl, expectedStatus: HttpStatusCode.OK_200 })
     })
@@ -89,13 +89,13 @@ describe('Object storage for video import', function () {
       expect(video.streamingPlaylists[0].files).to.have.lengthOf(5)
 
       for (const file of video.files) {
-        expectStartWith(file.fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
+        expectStartWith(file.fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
 
         await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
       }
 
       for (const file of video.streamingPlaylists[0].files) {
-        expectStartWith(file.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl())
+        expectStartWith(file.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
 
         await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
       }
diff --git a/server/tests/api/object-storage/video-static-file-privacy.ts b/server/tests/api/object-storage/video-static-file-privacy.ts
new file mode 100644 (file)
index 0000000..981bbaa
--- /dev/null
@@ -0,0 +1,336 @@
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
+import { expect } from 'chai'
+import { basename } from 'path'
+import { expectStartWith } from '@server/tests/shared'
+import { areScalewayObjectStorageTestsDisabled, getAllFiles, getHLS } from '@shared/core-utils'
+import { HttpStatusCode, LiveVideo, VideoDetails, VideoPrivacy } from '@shared/models'
+import {
+  cleanupTests,
+  createSingleServer,
+  findExternalSavedVideo,
+  makeRawRequest,
+  ObjectStorageCommand,
+  PeerTubeServer,
+  sendRTMPStream,
+  setAccessTokensToServers,
+  setDefaultVideoChannel,
+  stopFfmpeg,
+  waitJobs
+} from '@shared/server-commands'
+
+describe('Object storage for video static file privacy', function () {
+  // We need real world object storage to check ACL
+  if (areScalewayObjectStorageTestsDisabled()) return
+
+  let server: PeerTubeServer
+  let userToken: string
+
+  before(async function () {
+    this.timeout(120000)
+
+    server = await createSingleServer(1, ObjectStorageCommand.getDefaultScalewayConfig(1))
+    await setAccessTokensToServers([ server ])
+    await setDefaultVideoChannel([ server ])
+
+    await server.config.enableMinimumTranscoding()
+
+    userToken = await server.users.generateUserAndToken('user1')
+  })
+
+  describe('VOD', function () {
+    let privateVideoUUID: string
+    let publicVideoUUID: string
+    let userPrivateVideoUUID: string
+
+    async function checkPrivateFiles (uuid: string) {
+      const video = await server.videos.getWithToken({ id: uuid })
+
+      for (const file of video.files) {
+        expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/webseed/private/')
+
+        await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+      }
+
+      for (const file of getAllFiles(video)) {
+        const internalFileUrl = await server.sql.getInternalFileUrl(file.id)
+        expectStartWith(internalFileUrl, ObjectStorageCommand.getScalewayBaseUrl())
+        await makeRawRequest({ url: internalFileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+      }
+
+      const hls = getHLS(video)
+
+      if (hls) {
+        for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
+          expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
+        }
+
+        await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+        await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+
+        for (const file of hls.files) {
+          expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
+
+          await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+        }
+      }
+    }
+
+    async function checkPublicFiles (uuid: string) {
+      const video = await server.videos.getWithToken({ id: uuid })
+
+      for (const file of getAllFiles(video)) {
+        expectStartWith(file.fileUrl, ObjectStorageCommand.getScalewayBaseUrl())
+
+        await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
+      }
+
+      const hls = getHLS(video)
+
+      if (hls) {
+        expectStartWith(hls.playlistUrl, ObjectStorageCommand.getScalewayBaseUrl())
+        expectStartWith(hls.segmentsSha256Url, ObjectStorageCommand.getScalewayBaseUrl())
+
+        await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
+        await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
+      }
+    }
+
+    async function getSampleFileUrls (videoId: string) {
+      const video = await server.videos.getWithToken({ id: videoId })
+
+      return {
+        webTorrentFile: video.files[0].fileUrl,
+        hlsFile: getHLS(video).files[0].fileUrl
+      }
+    }
+
+    it('Should upload a private video and have appropriate object storage ACL', async function () {
+      this.timeout(60000)
+
+      {
+        const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
+        privateVideoUUID = uuid
+      }
+
+      {
+        const { uuid } = await server.videos.quickUpload({ name: 'user video', token: userToken, privacy: VideoPrivacy.PRIVATE })
+        userPrivateVideoUUID = uuid
+      }
+
+      await waitJobs([ server ])
+
+      await checkPrivateFiles(privateVideoUUID)
+    })
+
+    it('Should upload a public video and have appropriate object storage ACL', async function () {
+      this.timeout(60000)
+
+      const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.UNLISTED })
+      await waitJobs([ server ])
+
+      publicVideoUUID = uuid
+
+      await checkPublicFiles(publicVideoUUID)
+    })
+
+    it('Should not get files without appropriate OAuth token', async function () {
+      this.timeout(60000)
+
+      const { webTorrentFile, hlsFile } = await getSampleFileUrls(privateVideoUUID)
+
+      await makeRawRequest({ url: webTorrentFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+      await makeRawRequest({ url: webTorrentFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+
+      await makeRawRequest({ url: hlsFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+      await makeRawRequest({ url: hlsFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+    })
+
+    it('Should not get HLS file of another video', async function () {
+      this.timeout(60000)
+
+      const privateVideo = await server.videos.getWithToken({ id: privateVideoUUID })
+      const hlsFilename = basename(getHLS(privateVideo).files[0].fileUrl)
+
+      const badUrl = server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + userPrivateVideoUUID + '/' + hlsFilename
+      const goodUrl = server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + privateVideoUUID + '/' + hlsFilename
+
+      await makeRawRequest({ url: badUrl, token: server.accessToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
+      await makeRawRequest({ url: goodUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+    })
+
+    it('Should correctly check OAuth or video file token', async function () {
+      this.timeout(60000)
+
+      const badVideoFileToken = await server.videoToken.getVideoFileToken({ token: userToken, videoId: userPrivateVideoUUID })
+      const goodVideoFileToken = await server.videoToken.getVideoFileToken({ videoId: privateVideoUUID })
+
+      const { webTorrentFile, hlsFile } = await getSampleFileUrls(privateVideoUUID)
+
+      for (const url of [ webTorrentFile, hlsFile ]) {
+        await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+
+        await makeRawRequest({ url, query: { videoFileToken: badVideoFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({ url, query: { videoFileToken: goodVideoFileToken }, expectedStatus: HttpStatusCode.OK_200 })
+      }
+    })
+
+    it('Should update public video to private', async function () {
+      this.timeout(60000)
+
+      await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.INTERNAL } })
+
+      await checkPrivateFiles(publicVideoUUID)
+    })
+
+    it('Should update private video to public', async function () {
+      this.timeout(60000)
+
+      await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.PUBLIC } })
+
+      await checkPublicFiles(publicVideoUUID)
+    })
+
+    after(async function () {
+      this.timeout(30000)
+
+      if (privateVideoUUID) await server.videos.remove({ id: privateVideoUUID })
+      if (publicVideoUUID) await server.videos.remove({ id: publicVideoUUID })
+      if (userPrivateVideoUUID) await server.videos.remove({ id: userPrivateVideoUUID })
+
+      await waitJobs([ server ])
+    })
+  })
+
+  describe('Live', function () {
+    let normalLiveId: string
+    let normalLive: LiveVideo
+
+    let permanentLiveId: string
+    let permanentLive: LiveVideo
+
+    let unrelatedFileToken: string
+
+    async function checkLiveFiles (live: LiveVideo, liveId: string) {
+      const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
+      await server.live.waitUntilPublished({ videoId: liveId })
+
+      const video = await server.videos.getWithToken({ id: liveId })
+      const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
+
+      const hls = video.streamingPlaylists[0]
+
+      for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
+        expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
+
+        await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+        await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+
+        await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+        await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
+
+        await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+      }
+
+      await stopFfmpeg(ffmpegCommand)
+    }
+
+    async function checkReplay (replay: VideoDetails) {
+      const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid })
+
+      const hls = replay.streamingPlaylists[0]
+      expect(hls.files).to.not.have.lengthOf(0)
+
+      for (const file of hls.files) {
+        await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+        await makeRawRequest({ url: file.fileUrl, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
+
+        await makeRawRequest({ url: file.fileUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({
+          url: file.fileUrl,
+          query: { videoFileToken: unrelatedFileToken },
+          expectedStatus: HttpStatusCode.FORBIDDEN_403
+        })
+      }
+
+      for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
+        expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
+
+        await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
+        await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
+
+        await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+        await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
+      }
+    }
+
+    before(async function () {
+      await server.config.enableMinimumTranscoding()
+
+      const { uuid } = await server.videos.quickUpload({ name: 'another video' })
+      unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
+
+      await server.config.enableLive({
+        allowReplay: true,
+        transcoding: true,
+        resolutions: 'min'
+      })
+
+      {
+        const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: false, privacy: VideoPrivacy.PRIVATE })
+        normalLiveId = video.uuid
+        normalLive = live
+      }
+
+      {
+        const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: true, privacy: VideoPrivacy.PRIVATE })
+        permanentLiveId = video.uuid
+        permanentLive = live
+      }
+    })
+
+    it('Should create a private normal live and have a private static path', async function () {
+      this.timeout(240000)
+
+      await checkLiveFiles(normalLive, normalLiveId)
+    })
+
+    it('Should create a private permanent live and have a private static path', async function () {
+      this.timeout(240000)
+
+      await checkLiveFiles(permanentLive, permanentLiveId)
+    })
+
+    it('Should have created a replay of the normal live with a private static path', async function () {
+      this.timeout(240000)
+
+      await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId })
+
+      const replay = await server.videos.getWithToken({ id: normalLiveId })
+      await checkReplay(replay)
+    })
+
+    it('Should have created a replay of the permanent live with a private static path', async function () {
+      this.timeout(240000)
+
+      await server.live.waitUntilWaiting({ videoId: permanentLiveId })
+      await waitJobs([ server ])
+
+      const live = await server.videos.getWithToken({ id: permanentLiveId })
+      const replayFromList = await findExternalSavedVideo(server, live)
+      const replay = await server.videos.getWithToken({ id: replayFromList.id })
+
+      await checkReplay(replay)
+    })
+  })
+
+  after(async function () {
+    await cleanupTests([ server ])
+  })
+})
index 63f5179c75c177a0ff30c2a69ef5d1e3310024c1..af3db9334d44da616e558f2ef5e1f4da74f1764e 100644 (file)
@@ -11,7 +11,7 @@ import {
   generateHighBitrateVideo,
   MockObjectStorage
 } from '@server/tests/shared'
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode, VideoDetails } from '@shared/models'
 import {
   cleanupTests,
@@ -52,7 +52,7 @@ async function checkFiles (options: {
   for (const file of video.files) {
     const baseUrl = baseMockUrl
       ? `${baseMockUrl}/${webtorrentBucket}/`
-      : `http://${webtorrentBucket}.${ObjectStorageCommand.getEndpointHost()}/`
+      : `http://${webtorrentBucket}.${ObjectStorageCommand.getMockEndpointHost()}/`
 
     const prefix = webtorrentPrefix || ''
     const start = baseUrl + prefix
@@ -73,7 +73,7 @@ async function checkFiles (options: {
 
     const baseUrl = baseMockUrl
       ? `${baseMockUrl}/${playlistBucket}/`
-      : `http://${playlistBucket}.${ObjectStorageCommand.getEndpointHost()}/`
+      : `http://${playlistBucket}.${ObjectStorageCommand.getMockEndpointHost()}/`
 
     const prefix = playlistPrefix || ''
     const start = baseUrl + prefix
@@ -141,16 +141,16 @@ function runTestSuite (options: {
     const port = await mockObjectStorage.initialize()
     baseMockUrl = options.useMockBaseUrl ? `http://localhost:${port}` : undefined
 
-    await ObjectStorageCommand.createBucket(options.playlistBucket)
-    await ObjectStorageCommand.createBucket(options.webtorrentBucket)
+    await ObjectStorageCommand.createMockBucket(options.playlistBucket)
+    await ObjectStorageCommand.createMockBucket(options.webtorrentBucket)
 
     const config = {
       object_storage: {
         enabled: true,
-        endpoint: 'http://' + ObjectStorageCommand.getEndpointHost(),
-        region: ObjectStorageCommand.getRegion(),
+        endpoint: 'http://' + ObjectStorageCommand.getMockEndpointHost(),
+        region: ObjectStorageCommand.getMockRegion(),
 
-        credentials: ObjectStorageCommand.getCredentialsConfig(),
+        credentials: ObjectStorageCommand.getMockCredentialsConfig(),
 
         max_upload_part: options.maxUploadPart || '5MB',
 
@@ -261,7 +261,7 @@ function runTestSuite (options: {
 }
 
 describe('Object storage for videos', function () {
-  if (areObjectStorageTestsDisabled()) return
+  if (areMockObjectStorageTestsDisabled()) return
 
   describe('Test config', function () {
     let server: PeerTubeServer
@@ -269,17 +269,17 @@ describe('Object storage for videos', function () {
     const baseConfig = {
       object_storage: {
         enabled: true,
-        endpoint: 'http://' + ObjectStorageCommand.getEndpointHost(),
-        region: ObjectStorageCommand.getRegion(),
+        endpoint: 'http://' + ObjectStorageCommand.getMockEndpointHost(),
+        region: ObjectStorageCommand.getMockRegion(),
 
-        credentials: ObjectStorageCommand.getCredentialsConfig(),
+        credentials: ObjectStorageCommand.getMockCredentialsConfig(),
 
         streaming_playlists: {
-          bucket_name: ObjectStorageCommand.DEFAULT_PLAYLIST_BUCKET
+          bucket_name: ObjectStorageCommand.DEFAULT_PLAYLIST_MOCK_BUCKET
         },
 
         videos: {
-          bucket_name: ObjectStorageCommand.DEFAULT_WEBTORRENT_BUCKET
+          bucket_name: ObjectStorageCommand.DEFAULT_WEBTORRENT_MOCK_BUCKET
         }
       }
     }
@@ -310,7 +310,7 @@ describe('Object storage for videos', function () {
     it('Should fail with bad credentials', async function () {
       this.timeout(60000)
 
-      await ObjectStorageCommand.prepareDefaultBuckets()
+      await ObjectStorageCommand.prepareDefaultMockBuckets()
 
       const config = merge({}, baseConfig, {
         object_storage: {
@@ -334,7 +334,7 @@ describe('Object storage for videos', function () {
     it('Should succeed with credentials from env', async function () {
       this.timeout(60000)
 
-      await ObjectStorageCommand.prepareDefaultBuckets()
+      await ObjectStorageCommand.prepareDefaultMockBuckets()
 
       const config = merge({}, baseConfig, {
         object_storage: {
@@ -345,7 +345,7 @@ describe('Object storage for videos', function () {
         }
       })
 
-      const goodCredentials = ObjectStorageCommand.getCredentialsConfig()
+      const goodCredentials = ObjectStorageCommand.getMockCredentialsConfig()
 
       server = await createSingleServer(1, config, {
         env: {
@@ -361,7 +361,7 @@ describe('Object storage for videos', function () {
       await waitJobs([ server ], true)
       const video = await server.videos.get({ id: uuid })
 
-      expectStartWith(video.files[0].fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
+      expectStartWith(video.files[0].fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
     })
 
     after(async function () {
index a4151ebdda26d9afd0d639e0f237888137ee1978..3700b07462052ee940c5fc835e0e538410d6c6a3 100644 (file)
@@ -2,7 +2,7 @@
 
 import { expect } from 'chai'
 import { expectNotStartWith, expectStartWith, FIXTURE_URLS, MockProxy } from '@server/tests/shared'
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode, VideoPrivacy } from '@shared/models'
 import {
   cleanupTests,
@@ -120,40 +120,40 @@ describe('Test proxy', function () {
   })
 
   describe('Object storage', function () {
-    if (areObjectStorageTestsDisabled()) return
+    if (areMockObjectStorageTestsDisabled()) return
 
     before(async function () {
       this.timeout(30000)
 
-      await ObjectStorageCommand.prepareDefaultBuckets()
+      await ObjectStorageCommand.prepareDefaultMockBuckets()
     })
 
     it('Should succeed to upload to object storage with the appropriate proxy config', async function () {
       this.timeout(120000)
 
       await servers[0].kill()
-      await servers[0].run(ObjectStorageCommand.getDefaultConfig(), { env: goodEnv })
+      await servers[0].run(ObjectStorageCommand.getDefaultMockConfig(), { env: goodEnv })
 
       const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
       await waitJobs(servers)
 
       const video = await servers[0].videos.get({ id: uuid })
 
-      expectStartWith(video.files[0].fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
+      expectStartWith(video.files[0].fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
     })
 
     it('Should fail to upload to object storage with a wrong proxy config', async function () {
       this.timeout(120000)
 
       await servers[0].kill()
-      await servers[0].run(ObjectStorageCommand.getDefaultConfig(), { env: badEnv })
+      await servers[0].run(ObjectStorageCommand.getDefaultMockConfig(), { env: badEnv })
 
       const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
       await waitJobs(servers)
 
       const video = await servers[0].videos.get({ id: uuid })
 
-      expectNotStartWith(video.files[0].fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
+      expectNotStartWith(video.files[0].fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
     })
   })
 
index 372f5332ada3fe745a4b6bac31e2b53129a4df89..85389a9493150f7c0f021dc827e15377aff6c495 100644 (file)
@@ -2,7 +2,7 @@
 
 import { expect } from 'chai'
 import { checkResolutionsInMasterPlaylist, expectStartWith } from '@server/tests/shared'
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode, VideoDetails } from '@shared/models'
 import {
   cleanupTests,
@@ -19,7 +19,7 @@ import {
 
 async function checkFilesInObjectStorage (video: VideoDetails) {
   for (const file of video.files) {
-    expectStartWith(file.fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
+    expectStartWith(file.fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
     await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
   }
 
@@ -27,14 +27,14 @@ async function checkFilesInObjectStorage (video: VideoDetails) {
 
   const hlsPlaylist = video.streamingPlaylists[0]
   for (const file of hlsPlaylist.files) {
-    expectStartWith(file.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl())
+    expectStartWith(file.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
     await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
   }
 
-  expectStartWith(hlsPlaylist.playlistUrl, ObjectStorageCommand.getPlaylistBaseUrl())
+  expectStartWith(hlsPlaylist.playlistUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
   await makeRawRequest({ url: hlsPlaylist.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
 
-  expectStartWith(hlsPlaylist.segmentsSha256Url, ObjectStorageCommand.getPlaylistBaseUrl())
+  expectStartWith(hlsPlaylist.segmentsSha256Url, ObjectStorageCommand.getMockPlaylistBaseUrl())
   await makeRawRequest({ url: hlsPlaylist.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
 }
 
@@ -49,7 +49,7 @@ function runTests (objectStorage: boolean) {
     this.timeout(120000)
 
     const config = objectStorage
-      ? ObjectStorageCommand.getDefaultConfig()
+      ? ObjectStorageCommand.getDefaultMockConfig()
       : {}
 
     // Run server 2 to have transcoding enabled
@@ -60,7 +60,7 @@ function runTests (objectStorage: boolean) {
 
     await doubleFollow(servers[0], servers[1])
 
-    if (objectStorage) await ObjectStorageCommand.prepareDefaultBuckets()
+    if (objectStorage) await ObjectStorageCommand.prepareDefaultMockBuckets()
 
     const { shortUUID } = await servers[0].videos.quickUpload({ name: 'video' })
     videoUUID = shortUUID
@@ -256,7 +256,7 @@ describe('Test create transcoding jobs from API', function () {
   })
 
   describe('On object storage', function () {
-    if (areObjectStorageTestsDisabled()) return
+    if (areMockObjectStorageTestsDisabled()) return
 
     runTests(true)
   })
index 7b5492cd461184ff5059040f1d90a8802b3711d6..84a53c0bdacd1e4562f7ace5ce4c4ea130369a86 100644 (file)
@@ -2,7 +2,7 @@
 
 import { join } from 'path'
 import { checkDirectoryIsEmpty, checkTmpIsEmpty, completeCheckHlsPlaylist } from '@server/tests/shared'
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode } from '@shared/models'
 import {
   cleanupTests,
@@ -150,19 +150,19 @@ describe('Test HLS videos', function () {
   })
 
   describe('With object storage enabled', function () {
-    if (areObjectStorageTestsDisabled()) return
+    if (areMockObjectStorageTestsDisabled()) return
 
     before(async function () {
       this.timeout(120000)
 
-      const configOverride = ObjectStorageCommand.getDefaultConfig()
-      await ObjectStorageCommand.prepareDefaultBuckets()
+      const configOverride = ObjectStorageCommand.getDefaultMockConfig()
+      await ObjectStorageCommand.prepareDefaultMockBuckets()
 
       await servers[0].kill()
       await servers[0].run(configOverride)
     })
 
-    runTestSuite(true, ObjectStorageCommand.getPlaylistBaseUrl())
+    runTestSuite(true, ObjectStorageCommand.getMockPlaylistBaseUrl())
   })
 
   after(async function () {
index 5ca9233929a673b8b7e47ac4f604dc90e53ac9ba..8e32ea069d3eaad7e2adde21b0548576e62d5deb 100644 (file)
@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
 
 import { completeCheckHlsPlaylist } from '@server/tests/shared'
-import { areObjectStorageTestsDisabled, wait } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled, wait } from '@shared/core-utils'
 import { VideoPrivacy } from '@shared/models'
 import {
   cleanupTests,
@@ -130,19 +130,19 @@ describe('Test update video privacy while transcoding', function () {
   })
 
   describe('With object storage enabled', function () {
-    if (areObjectStorageTestsDisabled()) return
+    if (areMockObjectStorageTestsDisabled()) return
 
     before(async function () {
       this.timeout(120000)
 
-      const configOverride = ObjectStorageCommand.getDefaultConfig()
-      await ObjectStorageCommand.prepareDefaultBuckets()
+      const configOverride = ObjectStorageCommand.getDefaultMockConfig()
+      await ObjectStorageCommand.prepareDefaultMockBuckets()
 
       await servers[0].kill()
       await servers[0].run(configOverride)
     })
 
-    runTestSuite(true, ObjectStorageCommand.getPlaylistBaseUrl())
+    runTestSuite(true, ObjectStorageCommand.getMockPlaylistBaseUrl())
   })
 
   after(async function () {
index 9613111b52ebf438745d529814b8c00cefd0abca..ab08e8fb64be578d774c077c52e68052387972c5 100644 (file)
@@ -1,6 +1,6 @@
 import { expect } from 'chai'
 import { expectStartWith } from '@server/tests/shared'
-import { areObjectStorageTestsDisabled, getAllFiles } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled, getAllFiles } from '@shared/core-utils'
 import { VideoStudioTask } from '@shared/models'
 import {
   cleanupTests,
@@ -315,13 +315,13 @@ describe('Test video studio', function () {
   })
 
   describe('Object storage video edition', function () {
-    if (areObjectStorageTestsDisabled()) return
+    if (areMockObjectStorageTestsDisabled()) return
 
     before(async function () {
-      await ObjectStorageCommand.prepareDefaultBuckets()
+      await ObjectStorageCommand.prepareDefaultMockBuckets()
 
       await servers[0].kill()
-      await servers[0].run(ObjectStorageCommand.getDefaultConfig())
+      await servers[0].run(ObjectStorageCommand.getDefaultMockConfig())
 
       await servers[0].config.enableMinimumTranscoding()
     })
@@ -344,11 +344,11 @@ describe('Test video studio', function () {
         }
 
         for (const webtorrentFile of video.files) {
-          expectStartWith(webtorrentFile.fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
+          expectStartWith(webtorrentFile.fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
         }
 
         for (const hlsFile of video.streamingPlaylists[0].files) {
-          expectStartWith(hlsFile.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl())
+          expectStartWith(hlsFile.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
         }
 
         await checkDuration(server, 9)
index e38fdec6e98ae0ab4f1f18016b022d9c5527b81f..bdbe85127bc13902ac3e6e06ec5b51f6e5da1c03 100644 (file)
@@ -37,7 +37,7 @@ describe('Test video static file privacy', function () {
 
     function runSuite () {
 
-      async function checkPrivateWebTorrentFiles (uuid: string) {
+      async function checkPrivateFiles (uuid: string) {
         const video = await server.videos.getWithToken({ id: uuid })
 
         for (const file of video.files) {
@@ -63,7 +63,7 @@ describe('Test video static file privacy', function () {
         }
       }
 
-      async function checkPublicWebTorrentFiles (uuid: string) {
+      async function checkPublicFiles (uuid: string) {
         const video = await server.videos.get({ id: uuid })
 
         for (const file of getAllFiles(video)) {
@@ -98,7 +98,7 @@ describe('Test video static file privacy', function () {
           const { uuid } = await server.videos.quickUpload({ name: 'video', privacy })
           await waitJobs([ server ])
 
-          await checkPrivateWebTorrentFiles(uuid)
+          await checkPrivateFiles(uuid)
         }
       })
 
@@ -112,7 +112,7 @@ describe('Test video static file privacy', function () {
           await server.videos.update({ id: uuid, attributes: { privacy } })
           await waitJobs([ server ])
 
-          await checkPrivateWebTorrentFiles(uuid)
+          await checkPrivateFiles(uuid)
         }
       })
 
@@ -125,7 +125,7 @@ describe('Test video static file privacy', function () {
         await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.UNLISTED } })
         await waitJobs([ server ])
 
-        await checkPublicWebTorrentFiles(uuid)
+        await checkPublicFiles(uuid)
       })
 
       it('Should upload an internal video and update it to public to have a public static path', async function () {
@@ -137,7 +137,7 @@ describe('Test video static file privacy', function () {
         await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
         await waitJobs([ server ])
 
-        await checkPublicWebTorrentFiles(uuid)
+        await checkPublicFiles(uuid)
       })
 
       it('Should upload an internal video and schedule a public publish', async function () {
@@ -160,7 +160,7 @@ describe('Test video static file privacy', function () {
 
         await waitJobs([ server ])
 
-        await checkPublicWebTorrentFiles(uuid)
+        await checkPublicFiles(uuid)
       })
     }
 
index a4aa5f69984ebd6a3219d2d88f75ed77da11126e..43f53035b947fe5f23655aa60e736448e41c2b03 100644 (file)
@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
 
 import { expect } from 'chai'
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode, VideoDetails, VideoFile, VideoInclude } from '@shared/models'
 import {
   cleanupTests,
@@ -27,7 +27,7 @@ function assertVideoProperties (video: VideoFile, resolution: number, extname: s
 
 async function checkFiles (video: VideoDetails, objectStorage: boolean) {
   for (const file of video.files) {
-    if (objectStorage) expectStartWith(file.fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
+    if (objectStorage) expectStartWith(file.fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
 
     await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
   }
@@ -43,7 +43,7 @@ function runTests (objectStorage: boolean) {
     this.timeout(90000)
 
     const config = objectStorage
-      ? ObjectStorageCommand.getDefaultConfig()
+      ? ObjectStorageCommand.getDefaultMockConfig()
       : {}
 
     // Run server 2 to have transcoding enabled
@@ -52,7 +52,7 @@ function runTests (objectStorage: boolean) {
 
     await doubleFollow(servers[0], servers[1])
 
-    if (objectStorage) await ObjectStorageCommand.prepareDefaultBuckets()
+    if (objectStorage) await ObjectStorageCommand.prepareDefaultMockBuckets()
 
     // Upload two videos for our needs
     {
@@ -157,7 +157,7 @@ describe('Test create import video jobs', function () {
   })
 
   describe('On object storage', function () {
-    if (areObjectStorageTestsDisabled()) return
+    if (areMockObjectStorageTestsDisabled()) return
 
     runTests(true)
   })
index ecdd75b760f9ac6e8bf9051d2cb05c8c92a4f4ee..c357f501b4e622facc7fbdc63dc004ed99117a0b 100644 (file)
@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
 
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode, VideoDetails } from '@shared/models'
 import {
   cleanupTests,
@@ -17,7 +17,7 @@ import { expectStartWith } from '../shared'
 async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObjectStorage: boolean) {
   for (const file of video.files) {
     const start = inObjectStorage
-      ? ObjectStorageCommand.getWebTorrentBaseUrl()
+      ? ObjectStorageCommand.getMockWebTorrentBaseUrl()
       : origin.url
 
     expectStartWith(file.fileUrl, start)
@@ -26,7 +26,7 @@ async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObject
   }
 
   const start = inObjectStorage
-    ? ObjectStorageCommand.getPlaylistBaseUrl()
+    ? ObjectStorageCommand.getMockPlaylistBaseUrl()
     : origin.url
 
   const hls = video.streamingPlaylists[0]
@@ -41,7 +41,7 @@ async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObject
 }
 
 describe('Test create move video storage job', function () {
-  if (areObjectStorageTestsDisabled()) return
+  if (areMockObjectStorageTestsDisabled()) return
 
   let servers: PeerTubeServer[] = []
   const uuids: string[] = []
@@ -55,7 +55,7 @@ describe('Test create move video storage job', function () {
 
     await doubleFollow(servers[0], servers[1])
 
-    await ObjectStorageCommand.prepareDefaultBuckets()
+    await ObjectStorageCommand.prepareDefaultMockBuckets()
 
     await servers[0].config.enableTranscoding()
 
@@ -67,14 +67,14 @@ describe('Test create move video storage job', function () {
     await waitJobs(servers)
 
     await servers[0].kill()
-    await servers[0].run(ObjectStorageCommand.getDefaultConfig())
+    await servers[0].run(ObjectStorageCommand.getDefaultMockConfig())
   })
 
   it('Should move only one file', async function () {
     this.timeout(120000)
 
     const command = `npm run create-move-video-storage-job -- --to-object-storage -v ${uuids[1]}`
-    await servers[0].cli.execWithEnv(command, ObjectStorageCommand.getDefaultConfig())
+    await servers[0].cli.execWithEnv(command, ObjectStorageCommand.getDefaultMockConfig())
     await waitJobs(servers)
 
     for (const server of servers) {
@@ -94,7 +94,7 @@ describe('Test create move video storage job', function () {
     this.timeout(120000)
 
     const command = `npm run create-move-video-storage-job -- --to-object-storage --all-videos`
-    await servers[0].cli.execWithEnv(command, ObjectStorageCommand.getDefaultConfig())
+    await servers[0].cli.execWithEnv(command, ObjectStorageCommand.getDefaultMockConfig())
     await waitJobs(servers)
 
     for (const server of servers) {
index 51bf04a800cb443d04fd00d7d61f53447618812a..38b737829129541d4ae6051ad854d744b224a7d8 100644 (file)
@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
 
 import { expect } from 'chai'
-import { areObjectStorageTestsDisabled } from '@shared/core-utils'
+import { areMockObjectStorageTestsDisabled } from '@shared/core-utils'
 import { HttpStatusCode, VideoFile } from '@shared/models'
 import {
   cleanupTests,
@@ -18,8 +18,8 @@ import { checkResolutionsInMasterPlaylist, expectStartWith } from '../shared'
 async function checkFilesInObjectStorage (files: VideoFile[], type: 'webtorrent' | 'playlist') {
   for (const file of files) {
     const shouldStartWith = type === 'webtorrent'
-      ? ObjectStorageCommand.getWebTorrentBaseUrl()
-      : ObjectStorageCommand.getPlaylistBaseUrl()
+      ? ObjectStorageCommand.getMockWebTorrentBaseUrl()
+      : ObjectStorageCommand.getMockPlaylistBaseUrl()
 
     expectStartWith(file.fileUrl, shouldStartWith)
 
@@ -36,7 +36,7 @@ function runTests (objectStorage: boolean) {
     this.timeout(120000)
 
     const config = objectStorage
-      ? ObjectStorageCommand.getDefaultConfig()
+      ? ObjectStorageCommand.getDefaultMockConfig()
       : {}
 
     // Run server 2 to have transcoding enabled
@@ -47,7 +47,7 @@ function runTests (objectStorage: boolean) {
 
     await doubleFollow(servers[0], servers[1])
 
-    if (objectStorage) await ObjectStorageCommand.prepareDefaultBuckets()
+    if (objectStorage) await ObjectStorageCommand.prepareDefaultMockBuckets()
 
     for (let i = 1; i <= 5; i++) {
       const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'video' + i } })
@@ -255,7 +255,7 @@ describe('Test create transcoding jobs', function () {
   })
 
   describe('On object storage', function () {
-    if (areObjectStorageTestsDisabled()) return
+    if (areMockObjectStorageTestsDisabled()) return
 
     runTests(true)
   })
index da3691711282429f1cf64897a941be1212b4f051..78e29f575cb1a9347e8bcb4b88a01f6863da7533 100644 (file)
@@ -50,7 +50,7 @@ async function testVideoResolutions (options: {
     })
 
     if (objectStorage) {
-      expect(hlsPlaylist.playlistUrl).to.contain(ObjectStorageCommand.getPlaylistBaseUrl())
+      expect(hlsPlaylist.playlistUrl).to.contain(ObjectStorageCommand.getMockPlaylistBaseUrl())
     }
 
     for (let i = 0; i < resolutions.length; i++) {
@@ -65,11 +65,11 @@ async function testVideoResolutions (options: {
       })
 
       const baseUrl = objectStorage
-        ? ObjectStorageCommand.getPlaylistBaseUrl() + 'hls'
+        ? ObjectStorageCommand.getMockPlaylistBaseUrl() + 'hls'
         : originServer.url + '/static/streaming-playlists/hls'
 
       if (objectStorage) {
-        expect(hlsPlaylist.segmentsSha256Url).to.contain(ObjectStorageCommand.getPlaylistBaseUrl())
+        expect(hlsPlaylist.segmentsSha256Url).to.contain(ObjectStorageCommand.getMockPlaylistBaseUrl())
       }
 
       const subPlaylist = await originServer.streamingPlaylists.get({
index 99d68e014596fc1adf9e52e827b104b78b60f012..8c325bf119908bc007f4bb21cc038886587909f9 100644 (file)
@@ -12,7 +12,7 @@ export class MockObjectStorage {
     const app = express()
 
     app.get('/:bucketName/:path(*)', (req: express.Request, res: express.Response, next: express.NextFunction) => {
-      const url = `http://${req.params.bucketName}.${ObjectStorageCommand.getEndpointHost()}/${req.params.path}`
+      const url = `http://${req.params.bucketName}.${ObjectStorageCommand.getMockEndpointHost()}/${req.params.path}`
 
       if (process.env.DEBUG) {
         console.log('Receiving request on mocked server %s.', req.url)
index 27d60da72663464481f9770d19460ad4c5543cfc..3738ffc47dd8d716078831e17cbff1405a072347 100644 (file)
@@ -97,7 +97,7 @@ declare module 'express' {
 
       title?: string
       status?: number
-      type?: ServerErrorCode
+      type?: ServerErrorCode | string
       instance?: string
 
       data?: PeerTubeProblemDocumentData
index 38c96b15207fffcd28404654dd068038c7d02376..973f895d4e8f4100758ae075346ac47002f293ff 100644 (file)
@@ -14,7 +14,7 @@ function areHttpImportTestsDisabled () {
   return disabled
 }
 
-function areObjectStorageTestsDisabled () {
+function areMockObjectStorageTestsDisabled () {
   const disabled = process.env.ENABLE_OBJECT_STORAGE_TESTS !== 'true'
 
   if (disabled) console.log('ENABLE_OBJECT_STORAGE_TESTS env is not set to "true" so object storage tests are disabled')
@@ -22,9 +22,25 @@ function areObjectStorageTestsDisabled () {
   return disabled
 }
 
+function areScalewayObjectStorageTestsDisabled () {
+  if (areMockObjectStorageTestsDisabled()) return true
+
+  const enabled = process.env.OBJECT_STORAGE_SCALEWAY_KEY_ID && process.env.OBJECT_STORAGE_SCALEWAY_ACCESS_KEY
+  if (!enabled) {
+    console.log(
+      'OBJECT_STORAGE_SCALEWAY_KEY_ID and/or OBJECT_STORAGE_SCALEWAY_ACCESS_KEY are not set, so scaleway object storage tests are disabled'
+    )
+
+    return true
+  }
+
+  return false
+}
+
 export {
   parallelTests,
   isGithubCI,
   areHttpImportTestsDisabled,
-  areObjectStorageTestsDisabled
+  areMockObjectStorageTestsDisabled,
+  areScalewayObjectStorageTestsDisabled
 }
similarity index 57%
rename from shared/core-utils/videos/privacy.ts
rename to shared/core-utils/videos/common.ts
index f33487b490c5363a2a928231becfaa8f8f911ff0..2c6efdb7f38bad9ee2130826307b2e896f490d45 100644 (file)
@@ -1,5 +1,6 @@
-import { VideoDetails } from '../../models/videos/video.model'
+import { VideoStreamingPlaylistType } from '@shared/models'
 import { VideoPrivacy } from '../../models/videos/video-privacy.enum'
+import { VideoDetails } from '../../models/videos/video.model'
 
 function getAllPrivacies () {
   return [ VideoPrivacy.PUBLIC, VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED ]
@@ -8,14 +9,18 @@ function getAllPrivacies () {
 function getAllFiles (video: Partial<Pick<VideoDetails, 'files' | 'streamingPlaylists'>>) {
   const files = video.files
 
-  if (video.streamingPlaylists[0]) {
-    return files.concat(video.streamingPlaylists[0].files)
-  }
+  const hls = getHLS(video)
+  if (hls) return files.concat(hls.files)
 
   return files
 }
 
+function getHLS (video: Partial<Pick<VideoDetails, 'streamingPlaylists'>>) {
+  return video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
+}
+
 export {
   getAllPrivacies,
-  getAllFiles
+  getAllFiles,
+  getHLS
 }
index 620e3a716fd42f576b37b5189f1fe9a7997b30b7..2cf319395efcac5854eca93b9ff4f3c8db2b322c 100644 (file)
@@ -1,2 +1,2 @@
 export * from './bitrate'
-export * from './privacy'
+export * from './common'
index 09a99f8340066bdf2621d26b109ccc7a6b277ecf..b0d9ce56d734df4f171726c6a2c9f849c06577b9 100644 (file)
@@ -23,6 +23,11 @@ export class SQLCommand extends AbstractCommand {
     return parseInt(total, 10)
   }
 
+  async getInternalFileUrl (fileId: number) {
+    return this.selectQuery(`SELECT "fileUrl" FROM "videoFile" WHERE id = ${fileId}`)
+      .then(rows => rows[0].fileUrl as string)
+  }
+
   setActorField (to: string, field: string, value: string) {
     const seq = this.getSequelize()
 
index b4de8f4cbbbab7d42c0054a09e9ecb350b10c6b8..405e1b043d84fd14222e35e7e0886ec9e396385d 100644 (file)
@@ -4,74 +4,121 @@ import { makePostBodyRequest } from '../requests'
 import { AbstractCommand } from '../shared'
 
 export class ObjectStorageCommand extends AbstractCommand {
-  static readonly DEFAULT_PLAYLIST_BUCKET = 'streaming-playlists'
-  static readonly DEFAULT_WEBTORRENT_BUCKET = 'videos'
+  static readonly DEFAULT_PLAYLIST_MOCK_BUCKET = 'streaming-playlists'
+  static readonly DEFAULT_WEBTORRENT_MOCK_BUCKET = 'videos'
 
-  static getDefaultConfig () {
+  static readonly DEFAULT_SCALEWAY_BUCKET = 'peertube-ci-test'
+
+  // ---------------------------------------------------------------------------
+
+  static getDefaultMockConfig () {
     return {
       object_storage: {
         enabled: true,
-        endpoint: 'http://' + this.getEndpointHost(),
-        region: this.getRegion(),
+        endpoint: 'http://' + this.getMockEndpointHost(),
+        region: this.getMockRegion(),
 
-        credentials: this.getCredentialsConfig(),
+        credentials: this.getMockCredentialsConfig(),
 
         streaming_playlists: {
-          bucket_name: this.DEFAULT_PLAYLIST_BUCKET
+          bucket_name: this.DEFAULT_PLAYLIST_MOCK_BUCKET
         },
 
         videos: {
-          bucket_name: this.DEFAULT_WEBTORRENT_BUCKET
+          bucket_name: this.DEFAULT_WEBTORRENT_MOCK_BUCKET
         }
       }
     }
   }
 
-  static getCredentialsConfig () {
+  static getMockCredentialsConfig () {
     return {
       access_key_id: 'AKIAIOSFODNN7EXAMPLE',
       secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
     }
   }
 
-  static getEndpointHost () {
+  static getMockEndpointHost () {
     return 'localhost:9444'
   }
 
-  static getRegion () {
+  static getMockRegion () {
     return 'us-east-1'
   }
 
-  static getWebTorrentBaseUrl () {
-    return `http://${this.DEFAULT_WEBTORRENT_BUCKET}.${this.getEndpointHost()}/`
+  static getMockWebTorrentBaseUrl () {
+    return `http://${this.DEFAULT_WEBTORRENT_MOCK_BUCKET}.${this.getMockEndpointHost()}/`
   }
 
-  static getPlaylistBaseUrl () {
-    return `http://${this.DEFAULT_PLAYLIST_BUCKET}.${this.getEndpointHost()}/`
+  static getMockPlaylistBaseUrl () {
+    return `http://${this.DEFAULT_PLAYLIST_MOCK_BUCKET}.${this.getMockEndpointHost()}/`
   }
 
-  static async prepareDefaultBuckets () {
-    await this.createBucket(this.DEFAULT_PLAYLIST_BUCKET)
-    await this.createBucket(this.DEFAULT_WEBTORRENT_BUCKET)
+  static async prepareDefaultMockBuckets () {
+    await this.createMockBucket(this.DEFAULT_PLAYLIST_MOCK_BUCKET)
+    await this.createMockBucket(this.DEFAULT_WEBTORRENT_MOCK_BUCKET)
   }
 
-  static async createBucket (name: string) {
+  static async createMockBucket (name: string) {
     await makePostBodyRequest({
-      url: this.getEndpointHost(),
+      url: this.getMockEndpointHost(),
       path: '/ui/' + name + '?delete',
       expectedStatus: HttpStatusCode.TEMPORARY_REDIRECT_307
     })
 
     await makePostBodyRequest({
-      url: this.getEndpointHost(),
+      url: this.getMockEndpointHost(),
       path: '/ui/' + name + '?create',
       expectedStatus: HttpStatusCode.TEMPORARY_REDIRECT_307
     })
 
     await makePostBodyRequest({
-      url: this.getEndpointHost(),
+      url: this.getMockEndpointHost(),
       path: '/ui/' + name + '?make-public',
       expectedStatus: HttpStatusCode.TEMPORARY_REDIRECT_307
     })
   }
+
+  // ---------------------------------------------------------------------------
+
+  static getDefaultScalewayConfig (serverNumber: number) {
+    return {
+      object_storage: {
+        enabled: true,
+        endpoint: this.getScalewayEndpointHost(),
+        region: this.getScalewayRegion(),
+
+        credentials: this.getScalewayCredentialsConfig(),
+
+        streaming_playlists: {
+          bucket_name: this.DEFAULT_SCALEWAY_BUCKET,
+          prefix: `test:server-${serverNumber}-streaming-playlists:`
+        },
+
+        videos: {
+          bucket_name: this.DEFAULT_SCALEWAY_BUCKET,
+          prefix: `test:server-${serverNumber}-videos:`
+        }
+      }
+    }
+  }
+
+  static getScalewayCredentialsConfig () {
+    return {
+      access_key_id: process.env.OBJECT_STORAGE_SCALEWAY_KEY_ID,
+      secret_access_key: process.env.OBJECT_STORAGE_SCALEWAY_ACCESS_KEY
+    }
+  }
+
+  static getScalewayEndpointHost () {
+    return 's3.fr-par.scw.cloud'
+  }
+
+  static getScalewayRegion () {
+    return 'fr-par'
+  }
+
+  static getScalewayBaseUrl () {
+    return `https://${this.DEFAULT_SCALEWAY_BUCKET}.${this.getScalewayEndpointHost()}/`
+  }
 }
index de193fa49666a8d0333bdb427283c7895949f030..cc9502c6f1efe9735092dd84b1cece35eba90857 100644 (file)
@@ -197,7 +197,7 @@ export class LiveCommand extends AbstractCommand {
 
     const segmentName = `${playlistNumber}-00000${segment}.ts`
     const baseUrl = objectStorage
-      ? ObjectStorageCommand.getPlaylistBaseUrl() + 'hls'
+      ? ObjectStorageCommand.getMockPlaylistBaseUrl() + 'hls'
       : server.url + '/static/streaming-playlists/hls'
 
     let error = true
@@ -253,7 +253,7 @@ export class LiveCommand extends AbstractCommand {
 
     const segmentName = `${playlistNumber}-00000${segment}.ts`
     const baseUrl = objectStorage
-      ? ObjectStorageCommand.getPlaylistBaseUrl()
+      ? ObjectStorageCommand.getMockPlaylistBaseUrl()
       : `${this.server.url}/static/streaming-playlists/hls`
 
     const url = `${baseUrl}/${videoUUID}/${segmentName}`
@@ -275,7 +275,7 @@ export class LiveCommand extends AbstractCommand {
     const { playlistName, videoUUID, objectStorage = false } = options
 
     const baseUrl = objectStorage
-      ? ObjectStorageCommand.getPlaylistBaseUrl()
+      ? ObjectStorageCommand.getMockPlaylistBaseUrl()
       : `${this.server.url}/static/streaming-playlists/hls`
 
     const url = `${baseUrl}/${videoUUID}/${playlistName}`
index 64966f808dcfd5ed70ff3304df9ec35e497f3cbe..0dde0a362a2693eb1036aa0634ae6fef46e463fc 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
     "@aws-sdk/util-utf8-browser" "^3.0.0"
     tslib "^1.11.1"
 
-"@aws-sdk/abort-controller@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.162.0.tgz#c49c08e61e87c1e6bcba8214177228ecf4007535"
-  integrity sha512-8j1f/g+pNny3HkOojl+6phwd1yQE0FmM6EdssRJPA/IpR+SE0qTva2psKfZA9DivAg+/iTBozVCQU5GUJY1F2A==
+"@aws-sdk/abort-controller@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.190.0.tgz#284263db7ba051f31dda64e077b68e45cca7a7b3"
+  integrity sha512-M6qo2exTzEfHT5RuW7K090OgesUojhb2JyWiV4ulu7ngY4DWBUBMKUqac696sHRUZvGE5CDzSi0606DMboM+kA==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/chunked-blob-reader-native@3.109.0":
-  version "3.109.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/chunked-blob-reader-native/-/chunked-blob-reader-native-3.109.0.tgz#4db2ec81faf38fe33cf9dd6f75641afe0826dcfd"
-  integrity sha512-Ybn3vDZ3CqGyprL2qdF6QZqoqlx8lA3qOJepobjuKKDRw+KgGxjUY4NvWe0R2MdRoduyaDj6uvhIay0S1MOSJQ==
+"@aws-sdk/chunked-blob-reader-native@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/chunked-blob-reader-native/-/chunked-blob-reader-native-3.188.0.tgz#a5c3a778b23af761703317ef286a083a43fb510f"
+  integrity sha512-WielYjaAHfT/HAOW7Tj6yVeNdaOtts3aUm9Sf/3D+ElbCTGyaaMNfE4x0a+qn6dJZXewf1eAxybOIU5ftIeSGw==
   dependencies:
-    "@aws-sdk/util-base64-browser" "3.109.0"
+    "@aws-sdk/util-base64-browser" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/chunked-blob-reader@3.55.0":
-  version "3.55.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.55.0.tgz#db240c78e7c4c826e707f0ca32a4d221c41cf6a0"
-  integrity sha512-o/xjMCq81opAjSBjt7YdHJwIJcGVG5XIV9+C2KXcY5QwVimkOKPybWTv0mXPvSwSilSx+EhpLNhkcJuXdzhw4w==
+"@aws-sdk/chunked-blob-reader@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.188.0.tgz#18181b27511ab512e56b9f2cef30d2abbef639dc"
+  integrity sha512-zkPRFZZPL3eH+kH86LDYYXImiClA1/sW60zYOjse9Pgka+eDJlvBN6hcYxwDEKjcwATYiSRR1aVQHcfCinlGXg==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/client-s3@^3.23.0":
-  version "3.165.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.165.0.tgz#62555e00ca5df1736c36682e39da272a9d6cf013"
-  integrity sha512-vVTIxf6OJoX0t2Ur64r0gQXKfSeK7VnHq4m5O6QM8IDmOuVP7jyeEt5sw1PaDC4amarVa215u/IZWsaIEDlrsw==
+"@aws-sdk/client-s3@^3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.190.0.tgz#7d97da023d68fd19c1b2f9a8a6a162a75ff32daf"
+  integrity sha512-ngF92/X+odFt5Zbs5AWTozI+35fwFkHghbDABi5uSWNtgs2alSc/A94/yexbDdvVrVnuUa/FAMkvYX8duGZ9ig==
   dependencies:
     "@aws-crypto/sha1-browser" "2.0.0"
     "@aws-crypto/sha256-browser" "2.0.0"
     "@aws-crypto/sha256-js" "2.0.0"
-    "@aws-sdk/client-sts" "3.165.0"
-    "@aws-sdk/config-resolver" "3.163.0"
-    "@aws-sdk/credential-provider-node" "3.165.0"
-    "@aws-sdk/eventstream-serde-browser" "3.162.0"
-    "@aws-sdk/eventstream-serde-config-resolver" "3.162.0"
-    "@aws-sdk/eventstream-serde-node" "3.162.0"
-    "@aws-sdk/fetch-http-handler" "3.162.0"
-    "@aws-sdk/hash-blob-browser" "3.162.0"
-    "@aws-sdk/hash-node" "3.162.0"
-    "@aws-sdk/hash-stream-node" "3.162.0"
-    "@aws-sdk/invalid-dependency" "3.162.0"
-    "@aws-sdk/md5-js" "3.162.0"
-    "@aws-sdk/middleware-bucket-endpoint" "3.162.0"
-    "@aws-sdk/middleware-content-length" "3.162.0"
-    "@aws-sdk/middleware-expect-continue" "3.162.0"
-    "@aws-sdk/middleware-flexible-checksums" "3.162.0"
-    "@aws-sdk/middleware-host-header" "3.162.0"
-    "@aws-sdk/middleware-location-constraint" "3.162.0"
-    "@aws-sdk/middleware-logger" "3.162.0"
-    "@aws-sdk/middleware-recursion-detection" "3.162.0"
-    "@aws-sdk/middleware-retry" "3.162.0"
-    "@aws-sdk/middleware-sdk-s3" "3.165.0"
-    "@aws-sdk/middleware-serde" "3.162.0"
-    "@aws-sdk/middleware-signing" "3.163.0"
-    "@aws-sdk/middleware-ssec" "3.162.0"
-    "@aws-sdk/middleware-stack" "3.162.0"
-    "@aws-sdk/middleware-user-agent" "3.162.0"
-    "@aws-sdk/node-config-provider" "3.162.0"
-    "@aws-sdk/node-http-handler" "3.162.0"
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/signature-v4-multi-region" "3.163.0"
-    "@aws-sdk/smithy-client" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/url-parser" "3.162.0"
-    "@aws-sdk/util-base64-browser" "3.109.0"
-    "@aws-sdk/util-base64-node" "3.55.0"
-    "@aws-sdk/util-body-length-browser" "3.154.0"
-    "@aws-sdk/util-body-length-node" "3.55.0"
-    "@aws-sdk/util-defaults-mode-browser" "3.162.0"
-    "@aws-sdk/util-defaults-mode-node" "3.163.0"
-    "@aws-sdk/util-stream-browser" "3.162.0"
-    "@aws-sdk/util-stream-node" "3.162.0"
-    "@aws-sdk/util-user-agent-browser" "3.162.0"
-    "@aws-sdk/util-user-agent-node" "3.162.0"
-    "@aws-sdk/util-utf8-browser" "3.109.0"
-    "@aws-sdk/util-utf8-node" "3.109.0"
-    "@aws-sdk/util-waiter" "3.162.0"
-    "@aws-sdk/xml-builder" "3.142.0"
-    entities "2.2.0"
-    fast-xml-parser "3.19.0"
+    "@aws-sdk/client-sts" "3.190.0"
+    "@aws-sdk/config-resolver" "3.190.0"
+    "@aws-sdk/credential-provider-node" "3.190.0"
+    "@aws-sdk/eventstream-serde-browser" "3.190.0"
+    "@aws-sdk/eventstream-serde-config-resolver" "3.190.0"
+    "@aws-sdk/eventstream-serde-node" "3.190.0"
+    "@aws-sdk/fetch-http-handler" "3.190.0"
+    "@aws-sdk/hash-blob-browser" "3.190.0"
+    "@aws-sdk/hash-node" "3.190.0"
+    "@aws-sdk/hash-stream-node" "3.190.0"
+    "@aws-sdk/invalid-dependency" "3.190.0"
+    "@aws-sdk/md5-js" "3.190.0"
+    "@aws-sdk/middleware-bucket-endpoint" "3.190.0"
+    "@aws-sdk/middleware-content-length" "3.190.0"
+    "@aws-sdk/middleware-expect-continue" "3.190.0"
+    "@aws-sdk/middleware-flexible-checksums" "3.190.0"
+    "@aws-sdk/middleware-host-header" "3.190.0"
+    "@aws-sdk/middleware-location-constraint" "3.190.0"
+    "@aws-sdk/middleware-logger" "3.190.0"
+    "@aws-sdk/middleware-recursion-detection" "3.190.0"
+    "@aws-sdk/middleware-retry" "3.190.0"
+    "@aws-sdk/middleware-sdk-s3" "3.190.0"
+    "@aws-sdk/middleware-serde" "3.190.0"
+    "@aws-sdk/middleware-signing" "3.190.0"
+    "@aws-sdk/middleware-ssec" "3.190.0"
+    "@aws-sdk/middleware-stack" "3.190.0"
+    "@aws-sdk/middleware-user-agent" "3.190.0"
+    "@aws-sdk/node-config-provider" "3.190.0"
+    "@aws-sdk/node-http-handler" "3.190.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/signature-v4-multi-region" "3.190.0"
+    "@aws-sdk/smithy-client" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/url-parser" "3.190.0"
+    "@aws-sdk/util-base64-browser" "3.188.0"
+    "@aws-sdk/util-base64-node" "3.188.0"
+    "@aws-sdk/util-body-length-browser" "3.188.0"
+    "@aws-sdk/util-body-length-node" "3.188.0"
+    "@aws-sdk/util-defaults-mode-browser" "3.190.0"
+    "@aws-sdk/util-defaults-mode-node" "3.190.0"
+    "@aws-sdk/util-stream-browser" "3.190.0"
+    "@aws-sdk/util-stream-node" "3.190.0"
+    "@aws-sdk/util-user-agent-browser" "3.190.0"
+    "@aws-sdk/util-user-agent-node" "3.190.0"
+    "@aws-sdk/util-utf8-browser" "3.188.0"
+    "@aws-sdk/util-utf8-node" "3.188.0"
+    "@aws-sdk/util-waiter" "3.190.0"
+    "@aws-sdk/xml-builder" "3.188.0"
+    fast-xml-parser "4.0.11"
     tslib "^2.3.1"
 
-"@aws-sdk/client-sso@3.165.0":
-  version "3.165.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.165.0.tgz#655e74e4673b79ab167d228a0ad9de8d08a88845"
-  integrity sha512-Cizf03z6UFgHWOIQYOjZdNwUhoh6yhT5B2dH+mh1q+Naq1zsLmD2PUvx7SM+0fZsN9MhOICLyBSE/nSSE7E8Kg==
+"@aws-sdk/client-sso@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.190.0.tgz#d3435bf406bd7cedf705e9e876da49dd5a2bc17f"
+  integrity sha512-joEKRjJEzgvXnEih/x2UDDCPlvXWCO3MAHmqi44yJ36Ph4YsFS299mOjPdVLuzUtpQ+cST1nRO7hXNFrulW2jQ==
   dependencies:
     "@aws-crypto/sha256-browser" "2.0.0"
     "@aws-crypto/sha256-js" "2.0.0"
-    "@aws-sdk/config-resolver" "3.163.0"
-    "@aws-sdk/fetch-http-handler" "3.162.0"
-    "@aws-sdk/hash-node" "3.162.0"
-    "@aws-sdk/invalid-dependency" "3.162.0"
-    "@aws-sdk/middleware-content-length" "3.162.0"
-    "@aws-sdk/middleware-host-header" "3.162.0"
-    "@aws-sdk/middleware-logger" "3.162.0"
-    "@aws-sdk/middleware-recursion-detection" "3.162.0"
-    "@aws-sdk/middleware-retry" "3.162.0"
-    "@aws-sdk/middleware-serde" "3.162.0"
-    "@aws-sdk/middleware-stack" "3.162.0"
-    "@aws-sdk/middleware-user-agent" "3.162.0"
-    "@aws-sdk/node-config-provider" "3.162.0"
-    "@aws-sdk/node-http-handler" "3.162.0"
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/smithy-client" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/url-parser" "3.162.0"
-    "@aws-sdk/util-base64-browser" "3.109.0"
-    "@aws-sdk/util-base64-node" "3.55.0"
-    "@aws-sdk/util-body-length-browser" "3.154.0"
-    "@aws-sdk/util-body-length-node" "3.55.0"
-    "@aws-sdk/util-defaults-mode-browser" "3.162.0"
-    "@aws-sdk/util-defaults-mode-node" "3.163.0"
-    "@aws-sdk/util-user-agent-browser" "3.162.0"
-    "@aws-sdk/util-user-agent-node" "3.162.0"
-    "@aws-sdk/util-utf8-browser" "3.109.0"
-    "@aws-sdk/util-utf8-node" "3.109.0"
+    "@aws-sdk/config-resolver" "3.190.0"
+    "@aws-sdk/fetch-http-handler" "3.190.0"
+    "@aws-sdk/hash-node" "3.190.0"
+    "@aws-sdk/invalid-dependency" "3.190.0"
+    "@aws-sdk/middleware-content-length" "3.190.0"
+    "@aws-sdk/middleware-host-header" "3.190.0"
+    "@aws-sdk/middleware-logger" "3.190.0"
+    "@aws-sdk/middleware-recursion-detection" "3.190.0"
+    "@aws-sdk/middleware-retry" "3.190.0"
+    "@aws-sdk/middleware-serde" "3.190.0"
+    "@aws-sdk/middleware-stack" "3.190.0"
+    "@aws-sdk/middleware-user-agent" "3.190.0"
+    "@aws-sdk/node-config-provider" "3.190.0"
+    "@aws-sdk/node-http-handler" "3.190.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/smithy-client" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/url-parser" "3.190.0"
+    "@aws-sdk/util-base64-browser" "3.188.0"
+    "@aws-sdk/util-base64-node" "3.188.0"
+    "@aws-sdk/util-body-length-browser" "3.188.0"
+    "@aws-sdk/util-body-length-node" "3.188.0"
+    "@aws-sdk/util-defaults-mode-browser" "3.190.0"
+    "@aws-sdk/util-defaults-mode-node" "3.190.0"
+    "@aws-sdk/util-user-agent-browser" "3.190.0"
+    "@aws-sdk/util-user-agent-node" "3.190.0"
+    "@aws-sdk/util-utf8-browser" "3.188.0"
+    "@aws-sdk/util-utf8-node" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/client-sts@3.165.0":
-  version "3.165.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.165.0.tgz#1d9824aba899af5c9e6712543916fa63bfb3a2bf"
-  integrity sha512-pizXLuaIRDdC1zseB0v/YQ3IKxzJfD/bVBgOhQNCNVr4d7O7/oDXU3I+GhESmKpWopQADg1s1Q3NGtkUoJJUew==
+"@aws-sdk/client-sts@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.190.0.tgz#835a471daec37aa12e073f425aeab60fc1b3f9e5"
+  integrity sha512-s5MqfxqWxHAl1RhfQ6swjawsVPBqmq5F+SfzZcGLBCnVv03GaSL8J9K42O1Cc0+HwPQH2miY+Avy0zAr6VpY+Q==
   dependencies:
     "@aws-crypto/sha256-browser" "2.0.0"
     "@aws-crypto/sha256-js" "2.0.0"
-    "@aws-sdk/config-resolver" "3.163.0"
-    "@aws-sdk/credential-provider-node" "3.165.0"
-    "@aws-sdk/fetch-http-handler" "3.162.0"
-    "@aws-sdk/hash-node" "3.162.0"
-    "@aws-sdk/invalid-dependency" "3.162.0"
-    "@aws-sdk/middleware-content-length" "3.162.0"
-    "@aws-sdk/middleware-host-header" "3.162.0"
-    "@aws-sdk/middleware-logger" "3.162.0"
-    "@aws-sdk/middleware-recursion-detection" "3.162.0"
-    "@aws-sdk/middleware-retry" "3.162.0"
-    "@aws-sdk/middleware-sdk-sts" "3.163.0"
-    "@aws-sdk/middleware-serde" "3.162.0"
-    "@aws-sdk/middleware-signing" "3.163.0"
-    "@aws-sdk/middleware-stack" "3.162.0"
-    "@aws-sdk/middleware-user-agent" "3.162.0"
-    "@aws-sdk/node-config-provider" "3.162.0"
-    "@aws-sdk/node-http-handler" "3.162.0"
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/smithy-client" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/url-parser" "3.162.0"
-    "@aws-sdk/util-base64-browser" "3.109.0"
-    "@aws-sdk/util-base64-node" "3.55.0"
-    "@aws-sdk/util-body-length-browser" "3.154.0"
-    "@aws-sdk/util-body-length-node" "3.55.0"
-    "@aws-sdk/util-defaults-mode-browser" "3.162.0"
-    "@aws-sdk/util-defaults-mode-node" "3.163.0"
-    "@aws-sdk/util-user-agent-browser" "3.162.0"
-    "@aws-sdk/util-user-agent-node" "3.162.0"
-    "@aws-sdk/util-utf8-browser" "3.109.0"
-    "@aws-sdk/util-utf8-node" "3.109.0"
-    entities "2.2.0"
-    fast-xml-parser "3.19.0"
+    "@aws-sdk/config-resolver" "3.190.0"
+    "@aws-sdk/credential-provider-node" "3.190.0"
+    "@aws-sdk/fetch-http-handler" "3.190.0"
+    "@aws-sdk/hash-node" "3.190.0"
+    "@aws-sdk/invalid-dependency" "3.190.0"
+    "@aws-sdk/middleware-content-length" "3.190.0"
+    "@aws-sdk/middleware-host-header" "3.190.0"
+    "@aws-sdk/middleware-logger" "3.190.0"
+    "@aws-sdk/middleware-recursion-detection" "3.190.0"
+    "@aws-sdk/middleware-retry" "3.190.0"
+    "@aws-sdk/middleware-sdk-sts" "3.190.0"
+    "@aws-sdk/middleware-serde" "3.190.0"
+    "@aws-sdk/middleware-signing" "3.190.0"
+    "@aws-sdk/middleware-stack" "3.190.0"
+    "@aws-sdk/middleware-user-agent" "3.190.0"
+    "@aws-sdk/node-config-provider" "3.190.0"
+    "@aws-sdk/node-http-handler" "3.190.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/smithy-client" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/url-parser" "3.190.0"
+    "@aws-sdk/util-base64-browser" "3.188.0"
+    "@aws-sdk/util-base64-node" "3.188.0"
+    "@aws-sdk/util-body-length-browser" "3.188.0"
+    "@aws-sdk/util-body-length-node" "3.188.0"
+    "@aws-sdk/util-defaults-mode-browser" "3.190.0"
+    "@aws-sdk/util-defaults-mode-node" "3.190.0"
+    "@aws-sdk/util-user-agent-browser" "3.190.0"
+    "@aws-sdk/util-user-agent-node" "3.190.0"
+    "@aws-sdk/util-utf8-browser" "3.188.0"
+    "@aws-sdk/util-utf8-node" "3.188.0"
+    fast-xml-parser "4.0.11"
     tslib "^2.3.1"
 
-"@aws-sdk/config-resolver@3.163.0":
-  version "3.163.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.163.0.tgz#65ae429a02d65a8a9d2fe321cc88fa9117c6ecae"
-  integrity sha512-iBl5Zc3+VRGJy6n+aMcg++7tzYi4G1bHia6v/eF93SvdKxtRv40M9QnqoNfaNUuw9U2ltwKOHepw7J3bkOA8cQ==
+"@aws-sdk/config-resolver@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.190.0.tgz#cb068fef70360c227698b670a2f1224467b523b4"
+  integrity sha512-K+VnDtjTgjpf7yHEdDB0qgGbHToF0pIL0pQMSnmk2yc8BoB3LGG/gg1T0Ki+wRlrFnDCJ6L+8zUdawY2qDsbyw==
   dependencies:
-    "@aws-sdk/signature-v4" "3.163.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-config-provider" "3.109.0"
-    "@aws-sdk/util-middleware" "3.162.0"
+    "@aws-sdk/signature-v4" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-config-provider" "3.188.0"
+    "@aws-sdk/util-middleware" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/credential-provider-env@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.162.0.tgz#be2bf9587d9e7b2a10b1c2530c5984b60efc11bd"
-  integrity sha512-yzCJXiAAbZZHB4iThi4I+rs+gTYwBSetdU4Z1D89a2xjcOjCa8IhdQKm3GO/uJMScy4VtW3EEFG4/zZ7dVQPOw==
+"@aws-sdk/credential-provider-env@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.190.0.tgz#b0be7325508529ec1d910b1f18c5a6cc98186dcd"
+  integrity sha512-GTY7l3SJhTmRGFpWddbdJOihSqoMN8JMo3CsCtIjk4/h3xirBi02T4GSvbrMyP7FP3Fdl4NAdT+mHJ4q2Bvzxw==
   dependencies:
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/credential-provider-imds@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.162.0.tgz#bb8002a37c42e6f2f42fcafff3a8e4262b6aac4a"
-  integrity sha512-ohrHMIu2MNauPjbE8mEMKtlEQH/VZdpNswPigaEejUGVumz0NSft9PlIn2X79sNX5Y+uXopynMQF4MZj773hTw==
+"@aws-sdk/credential-provider-imds@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.190.0.tgz#15aee396b321e01ede0f0aa88f643e43c42ad879"
+  integrity sha512-gI5pfBqGYCKdmx8igPvq+jLzyE2kuNn9Q5u73pdM/JZxiq7GeWYpE/MqqCubHxPtPcTFgAwxCxCFoXlUTBh/2g==
   dependencies:
-    "@aws-sdk/node-config-provider" "3.162.0"
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/url-parser" "3.162.0"
+    "@aws-sdk/node-config-provider" "3.190.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/url-parser" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/credential-provider-ini@3.165.0":
-  version "3.165.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.165.0.tgz#eee4131ef1b8a0f815f53e6be5452b547e839d04"
-  integrity sha512-NrLe29bdhmpNPsEsYxUxb0hTxNMXCmz5pH2l/T9COT6SMxom1wpbB/aKwf9897Z1xvhoFi6flDQjmu//599BZw==
-  dependencies:
-    "@aws-sdk/credential-provider-env" "3.162.0"
-    "@aws-sdk/credential-provider-imds" "3.162.0"
-    "@aws-sdk/credential-provider-sso" "3.165.0"
-    "@aws-sdk/credential-provider-web-identity" "3.162.0"
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/shared-ini-file-loader" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+"@aws-sdk/credential-provider-ini@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.190.0.tgz#b12b9620aeb30c87d99dd234ba7c80b983688167"
+  integrity sha512-Z7NN/evXJk59hBQlfOSWDfHntwmxwryu6uclgv7ECI6SEVtKt1EKIlPuCLUYgQ4lxb9bomyO5lQAl/1WutNT5w==
+  dependencies:
+    "@aws-sdk/credential-provider-env" "3.190.0"
+    "@aws-sdk/credential-provider-imds" "3.190.0"
+    "@aws-sdk/credential-provider-sso" "3.190.0"
+    "@aws-sdk/credential-provider-web-identity" "3.190.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/shared-ini-file-loader" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/credential-provider-node@3.165.0":
-  version "3.165.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.165.0.tgz#ff3aa22df468ded2d370955f0b2c0548860583d4"
-  integrity sha512-emUd3kqAvV8Qydn5pJ+YKo47UJ+B5RXNyQXMasNQsw1jxrB60j8QAIL9JGM019SzZBnHZuW3DrHClA17OVC+xQ==
-  dependencies:
-    "@aws-sdk/credential-provider-env" "3.162.0"
-    "@aws-sdk/credential-provider-imds" "3.162.0"
-    "@aws-sdk/credential-provider-ini" "3.165.0"
-    "@aws-sdk/credential-provider-process" "3.162.0"
-    "@aws-sdk/credential-provider-sso" "3.165.0"
-    "@aws-sdk/credential-provider-web-identity" "3.162.0"
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/shared-ini-file-loader" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+"@aws-sdk/credential-provider-node@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.190.0.tgz#7de69d65c694e26191414ddde978df929bd632b7"
+  integrity sha512-ctCG5+TsIK2gVgvvFiFjinPjc5nGpSypU3nQKCaihtPh83wDN6gCx4D0p9M8+fUrlPa5y+o/Y7yHo94ATepM8w==
+  dependencies:
+    "@aws-sdk/credential-provider-env" "3.190.0"
+    "@aws-sdk/credential-provider-imds" "3.190.0"
+    "@aws-sdk/credential-provider-ini" "3.190.0"
+    "@aws-sdk/credential-provider-process" "3.190.0"
+    "@aws-sdk/credential-provider-sso" "3.190.0"
+    "@aws-sdk/credential-provider-web-identity" "3.190.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/shared-ini-file-loader" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/credential-provider-process@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.162.0.tgz#e54db5f3ac5a5200a6f04803b05278c028a54447"
-  integrity sha512-KtmYjlCMAa0XF3IJo4dxSF+OWmRoHbrdEHGEZw+j6iCZ3Nz6Y6xCsdxun5rAKdom1QRNMDR4wX0hRAdPYobW2w==
+"@aws-sdk/credential-provider-process@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.190.0.tgz#b1a4aa9fc83984bf2219cfc027a78deaca417c59"
+  integrity sha512-sIJhICR80n5XY1kW/EFHTh5ZzBHb5X+744QCH3StcbKYI44mOZvNKfFdeRL2fQ7yLgV7npte2HJRZzQPWpZUrw==
   dependencies:
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/shared-ini-file-loader" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/shared-ini-file-loader" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/credential-provider-sso@3.165.0":
-  version "3.165.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.165.0.tgz#7da83675149983e25345c05ee1113e64485b5b19"
-  integrity sha512-b2BuYyUSmnfChhz5ZbnqOaLSAsnzYcwpEPEUbQUdNGPSE3QcMd0SPl3woH82095WYlXTFjwgxlOPn5ad5hdBpA==
+"@aws-sdk/credential-provider-sso@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.190.0.tgz#285895dc38c09033154906ce253ff6f0bdca86ab"
+  integrity sha512-uarU9vk471MHHT+GJj3KWFSmaaqLNL5n1KcMer2CCAZfjs+mStAi8+IjZuuKXB4vqVs5DxdH8cy5aLaJcBlXwQ==
   dependencies:
-    "@aws-sdk/client-sso" "3.165.0"
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/shared-ini-file-loader" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/client-sso" "3.190.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/shared-ini-file-loader" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/credential-provider-web-identity@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.162.0.tgz#0b06aee91777b16c362ad565e9f1fdf77de7e8f6"
-  integrity sha512-vy86OS5/h+Vfk1bxvWjbayyUtFNdwU+mfALin3zxJbFqneSxRBydNBomt/guJjapZE+h865lkteyOsqsYMskzQ==
+"@aws-sdk/credential-provider-web-identity@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.190.0.tgz#c41fe6f1ebb814581b010c0f82e5232da85c90b7"
+  integrity sha512-nlIBeK9hGHKWC874h+ITAfPZ9Eaok+x/ydZQVKsLHiQ9PH3tuQ8AaGqhuCwBSH0hEAHZ/BiKeEx5VyWAE8/x+Q==
   dependencies:
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/eventstream-codec@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-codec/-/eventstream-codec-3.162.0.tgz#c105a2d3081b0b74a3d1be6c49bd489a677102ad"
-  integrity sha512-DllkOwvOFO28nuFyY+Klu31aRwHp1Db6G6BfMfRFACId5RIE9LNVgEoNnR/uluZqQ9IN7k7kyp7ycztyn18tUA==
+"@aws-sdk/eventstream-codec@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-codec/-/eventstream-codec-3.190.0.tgz#7e9705ed5ffd2b7b77c0eca868c1c64a8a2d2941"
+  integrity sha512-F1ux94fMKUrlOfJR/1x8p5+lisfemKizNSRtMnj0kcR+Y9vIt9bUodFyuDF8tJl/7iwnop6EPiGc3QA1IKjBKA==
   dependencies:
     "@aws-crypto/crc32" "2.0.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-hex-encoding" "3.109.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-hex-encoding" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/eventstream-serde-browser@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.162.0.tgz#d84952d5c65b247b3e01c4ee59c42a716338ce38"
-  integrity sha512-99xPbUs3JS+uwdKe+pZu3UOc/GeI9qPk4epramssgaLKzQGkO4ylN3CD9ts4xeMXkE+VS0p0lEKeg80/fkzqzA==
+"@aws-sdk/eventstream-serde-browser@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-browser/-/eventstream-serde-browser-3.190.0.tgz#cb53132a6340742c2dd1a767da4442d3b5a6c866"
+  integrity sha512-aUsbLLPi3dtafnpIJtGbpCe8fhABIsVV49V1jJx6gQDAs6HG5IhO3LfndF05H6UbMyt8sDS6223wSt6HFUSHjQ==
   dependencies:
-    "@aws-sdk/eventstream-serde-universal" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/eventstream-serde-universal" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/eventstream-serde-config-resolver@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.162.0.tgz#2c5f749262575d44b8ebebff96e243d9c0bbcf38"
-  integrity sha512-1G4IUPrcONzZ0o60CLbv3dOZT9UhT1iOCSRXzdy7XiTQ4rNLpqZVLG8GxNdu2ZpuDoW0ZdpjRDfmnzX+IXAILA==
+"@aws-sdk/eventstream-serde-config-resolver@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.190.0.tgz#9c05e4bd73157d0933bfa7b834ad34138cbac9c1"
+  integrity sha512-etlsbvG8KSFQpGzoNazJoNPnN3P/i+IgIo/5D8pEl4R7gkjLAHZYnbk299xfv59olDWn9SVdrZfVx8lS+lUWHA==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/eventstream-serde-node@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.162.0.tgz#06676c14b9cea5f179586d47bdbac9c60966efb4"
-  integrity sha512-bXfgIR/6ZBdu022sTr0dRjEWMI1Z0UKCARYuywygwQ7GtAzhyHaRKrLk59foDX9NkLTExlQCfIRcsW6rMjL+hA==
+"@aws-sdk/eventstream-serde-node@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-node/-/eventstream-serde-node-3.190.0.tgz#de910debb967258be370e10fc8f2b970d936de8e"
+  integrity sha512-CmZ1jZJPar1uZAbfMrv00fzGk4NIc+1w4lRxOc0oow18pPSXsUAmefJKcW5ETX609xaH47nO5b7pTuHU8eGgzg==
   dependencies:
-    "@aws-sdk/eventstream-serde-universal" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/eventstream-serde-universal" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/eventstream-serde-universal@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.162.0.tgz#cf515746715baed3efc9bdac9e28ed29e8546142"
-  integrity sha512-EnZGRWUkZ8d07OuoN+WG1u+ta42b6szf06F+SE2pKhsiAfnHHN3MsfxsnUN/ZYsgSXv6COxdp+acHR8CnjzRMQ==
+"@aws-sdk/eventstream-serde-universal@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-serde-universal/-/eventstream-serde-universal-3.190.0.tgz#28eb59946db104e15e63a117b9b3327b21c4da03"
+  integrity sha512-pioZzWDjjhfCfRIUr5oQdghUMdQV6KtP3lBrOO6rBCFCBOQjnQafqH+a+xRjEw19YvBpMX3tFUrVrUytCN9TDQ==
   dependencies:
-    "@aws-sdk/eventstream-codec" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/eventstream-codec" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/fetch-http-handler@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.162.0.tgz#d90d3f179ff11bedf69b971d22a7c00abba650d9"
-  integrity sha512-DZLxxEqSMXqKteYohO4w6uoORabpETWso6wBdIFMul1BbEseqLjub1594D5RA18cqkcM2dV4ttw+boPPzOjSAw==
+"@aws-sdk/fetch-http-handler@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.190.0.tgz#9943c8c9ec3bd9eb9121d3a6c1d356f24f0931a9"
+  integrity sha512-5riRpKydARXAPLesTZm6eP6QKJ4HJGQ3k0Tepi3nvxHVx3UddkRNoX0pLS3rvbajkykWPNC2qdfRGApWlwOYsA==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/querystring-builder" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-base64-browser" "3.109.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/querystring-builder" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-base64-browser" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/hash-blob-browser@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.162.0.tgz#75d1ff18dc3181231b35a131adadfe85cc227e63"
-  integrity sha512-m6d9jSQvV0eNWUOaUEHeP5N5AgjKs+wag+sY+Obr1zQlX1hp5boIauCEhTnm2sJxL5Z2M0bSleb/I0V8RFvYHw==
+"@aws-sdk/hash-blob-browser@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.190.0.tgz#75e7e69bbab4cda4b9f69ef208fc7e1d5f695ca0"
+  integrity sha512-qRIoUGsy6S6kmqi0LQ+Ma9L5HKm8lIt002z+Yhb4kRTf6PFBWa/A0mDyrcj6L3MV71QGKfAEacV5nrVMF/aIhw==
   dependencies:
-    "@aws-sdk/chunked-blob-reader" "3.55.0"
-    "@aws-sdk/chunked-blob-reader-native" "3.109.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/chunked-blob-reader" "3.188.0"
+    "@aws-sdk/chunked-blob-reader-native" "3.188.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/hash-node@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.162.0.tgz#72eeba4fe923fc96103740600bcb5a217dc8d338"
-  integrity sha512-lfyyAb0Cd084QnUNLTkYowD8RW3L5Tb9lNnIMH6HY7uSE/obw1j/OnLUPqpey628WJ5DPyyvNBah3Vu+JVZ5Mw==
+"@aws-sdk/hash-node@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.190.0.tgz#0ecad888142e8c097e13701f7bafc69e3e7ce91e"
+  integrity sha512-DNwVT3O8zc9Jk/bXiXcN0WsD98r+JJWryw9F1/ZZbuzbf6rx2qhI8ZK+nh5X6WMtYPU84luQMcF702fJt/1bzg==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-buffer-from" "3.55.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-buffer-from" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/hash-stream-node@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/hash-stream-node/-/hash-stream-node-3.162.0.tgz#cfb1e8cc36df4772a339416a5c4c5138064a8648"
-  integrity sha512-WnaKxaK+RIl+MOm3oohyKy1rDduNRTxMhSuLpo9NsZVc6tqZBXQ4Y4fTfND80YP6iyAcao1Gv96Ph8H6wY7yzA==
+"@aws-sdk/hash-stream-node@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/hash-stream-node/-/hash-stream-node-3.190.0.tgz#3173e1febfccfcbc63e581dd355df26f94de2ec9"
+  integrity sha512-Itdqn0tRx5bS9A1vy/GzvAtg+KeBwg1MTeiefJQrGsIzh7KitvbFpcDlHZBvId6HPLI0c0aIORucwi9ayEOqjQ==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/invalid-dependency@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.162.0.tgz#a75b29aae362854041ebc4d282746041231109e4"
-  integrity sha512-ENZ7Jf2EcxMMdAX9/sRrt/1rzeA2WwqAKrjIacKGT9KEGQNU+omWF/h+8stLCu0Uxcg0XswFXgQCXcI1IQuZjg==
+"@aws-sdk/invalid-dependency@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.190.0.tgz#a4caa91ce50554f99fbfcba062eca233bb79e280"
+  integrity sha512-crCh63e8d/Uw9y3dQlVTPja7+IZiXpNXyH6oSuAadTDQwMq6KK87Av1/SDzVf6bAo2KgAOo41MyO2joaCEk0dQ==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/is-array-buffer@3.55.0":
-  version "3.55.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/is-array-buffer/-/is-array-buffer-3.55.0.tgz#c46122c5636f01d5895e5256a587768c3425ea7a"
-  integrity sha512-NbiPHVYuPxdqdFd6FxzzN3H1BQn/iWA3ri3Ry7AyLeP/tGs1yzEWMwf8BN8TSMALI0GXT6Sh0GDWy3Ok5xB6DA==
+"@aws-sdk/is-array-buffer@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/is-array-buffer/-/is-array-buffer-3.188.0.tgz#2e969b2e799490e3bbd5008554aa346c58e3a9b6"
+  integrity sha512-n69N4zJZCNd87Rf4NzufPzhactUeM877Y0Tp/F3KiHqGeTnVjYUa4Lv1vLBjqtfjYb2HWT3NKlYn5yzrhaEwiQ==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/lib-storage@^3.72.0":
-  version "3.165.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.165.0.tgz#810dec9b649656ae663ef14b7e1a5a5032363044"
-  integrity sha512-6eM4xQgq4VfIFI8hLnrGXhnynFDnTdIGksE9ejOb6sloOVcqhsrffnWuhGRc4AkK2/LuExY8E+jKbiQjM0h3aQ==
+"@aws-sdk/lib-storage@^3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.190.0.tgz#a7550ab703dc3e45421ad41599c59fb72df81802"
+  integrity sha512-dmjtkMZlIncDOeYHPXEv/wxqT2V8z3l7eYHoILSnT/T1238dYOk06yGH6SyCdIdDxTJq5f06NQIzSCRtzN48EA==
   dependencies:
-    "@aws-sdk/smithy-client" "3.162.0"
+    "@aws-sdk/middleware-endpoint" "3.190.0"
+    "@aws-sdk/smithy-client" "3.190.0"
     buffer "5.6.0"
     events "3.3.0"
     stream-browserify "3.0.0"
     tslib "^2.3.1"
 
-"@aws-sdk/md5-js@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/md5-js/-/md5-js-3.162.0.tgz#f469e9dca0eea77ce9d87cf157dc034b834f1144"
-  integrity sha512-8rODeZjopffWtnvGOOn3JZghztvxZBAF9a8I6rAUFUveOvyZbxhDZRxdqfGbnNesYBKxZRjU4hjt9dvfmRVSvw==
+"@aws-sdk/md5-js@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/md5-js/-/md5-js-3.190.0.tgz#6d6992a5f2012d36473c39356c89f499e87d33f0"
+  integrity sha512-zeyHnFzKYOgpAS1O+RfLyTRTjo0yLTw3Hooh/GjyuScfjwYG4UqP9kgZHnNLjHS9gXBahtskBOHaBCBGNTVLdw==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-utf8-browser" "3.109.0"
-    "@aws-sdk/util-utf8-node" "3.109.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-utf8-browser" "3.188.0"
+    "@aws-sdk/util-utf8-node" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-bucket-endpoint@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.162.0.tgz#7930aa6ecc2248c578996e43b00b4cddd77b8904"
-  integrity sha512-WkT8BqBN1GzAAzQnkhmyIIhRrZucCY2LdLgXfArYNgzdxFphUJXk19JuKlT8xQHBNENMnCiX+OHupn4prKXQaA==
+"@aws-sdk/middleware-bucket-endpoint@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.190.0.tgz#ddb703cdfaf5780351a4f7657bd3fa0e27899c8a"
+  integrity sha512-p1ItehR+NEEBpfPxRpq6VB12qIMWvs/D9ROLRltnIPC6Cf5H0rqWFf2ewT36PkkpL17+rjbt89N7AXLkBgU9NA==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-arn-parser" "3.55.0"
-    "@aws-sdk/util-config-provider" "3.109.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-arn-parser" "3.188.0"
+    "@aws-sdk/util-config-provider" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-content-length@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.162.0.tgz#987d77a843598aa52d1045745dbae7e79e763464"
-  integrity sha512-gwuxHPBNNkr9Ah9gTNHqJ3uIp3zeY+VC2H810+RqkG5QrxU1bGAN/zezIIbcAlXjMM9vTSfO0rxGI04nhTx0BQ==
+"@aws-sdk/middleware-content-length@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.190.0.tgz#640d2dfeeb1c715a93f8a03048a8458aaf153973"
+  integrity sha512-sSU347SuC6I8kWum1jlJlpAqeV23KP7enG+ToWcEcgFrJhm3AvuqB//NJxDbkKb2DNroRvJjBckBvrwNAjQnBQ==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-expect-continue@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.162.0.tgz#4d3695f82cf950c6b47d227d8382a145e6992053"
-  integrity sha512-6AVKtODhV0mfwAONc67haulkPVlrxNkcgw4gEc4iXTT9ZQiyz4WHUU8hELB0Bkj+7or94BU15fCpUD3rK1VXeQ==
+"@aws-sdk/middleware-endpoint@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.190.0.tgz#dfee34aece9a67598d2aebed1fd0565f740eb806"
+  integrity sha512-jpsfQ1MuXVOBXtlMAkM/Dy5Qrv70K1V0FkRPYI6zftqfhDSf2sWb5Uc887m904bWwwQE4hIlkQJIPByZVkT0+g==
+  dependencies:
+    "@aws-sdk/middleware-serde" "3.190.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/signature-v4" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/url-parser" "3.190.0"
+    "@aws-sdk/util-config-provider" "3.188.0"
+    "@aws-sdk/util-middleware" "3.190.0"
+    tslib "^2.3.1"
+
+"@aws-sdk/middleware-expect-continue@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.190.0.tgz#09167fa3206f663e6e605e11125529b162211df7"
+  integrity sha512-ZE3WY6RQ1uNuFgDn2KHfJXiLbwYgK/a4LX3SWNsO5gCrf9qchHTotkAc8PjFS5x4WV7TkddvhL2i57eikf1cYg==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-flexible-checksums@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.162.0.tgz#f218775cf65c4b28705a72a6f4968df7a48ca171"
-  integrity sha512-KMOOJB+ntqSBVbsNln55Cm+wrPxP0+6dWdRcjuq1NnQqsTHoADiJ+o9+BBYgAFUQR4Q/zWcvxZQ7gTan0W+PLQ==
+"@aws-sdk/middleware-flexible-checksums@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.190.0.tgz#e453d92c95b594b05c9515387daa80d09368599f"
+  integrity sha512-YcZC2KX5G00RPhZpzFHBru2t27OelKYFG96Tc4cNOnjFmi++t7p5m5ZItFgovv9gUI9vTiI/XBQ+kc1XQ769Xw==
   dependencies:
     "@aws-crypto/crc32" "2.0.0"
     "@aws-crypto/crc32c" "2.0.0"
-    "@aws-sdk/is-array-buffer" "3.55.0"
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/is-array-buffer" "3.188.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-host-header@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.162.0.tgz#c59015c83f6c574bde72534919022b3a90c3cd2a"
-  integrity sha512-gw5xe22P62N9yZPvrVXewM2vp70w9mLRWC1vh6pRDs0hEudAlsbXoWjB/z6jpG6ucA4Y1IOuXy5yGr9lND+zhg==
+"@aws-sdk/middleware-host-header@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.190.0.tgz#47a01bd9b7db526114097db4659ef7e578881b62"
+  integrity sha512-cL7Vo/QSpGx/DDmFxjeV0Qlyi1atvHQDPn3MLBBmi1icu+3GKZkCMAJwzsrV3U4+WoVoDYT9FJ9yMQf2HaIjeQ==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-location-constraint@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.162.0.tgz#201f3f97099e6d771f7456cec526c235b608c7d3"
-  integrity sha512-CnCx4TA8wZa4fnXYiyj37CIkOnppBKokZFu+mbiG6shxXb4tVA8p4PgptOnDRYJHLzgeD4v7B/zMFpUP3o7a8g==
+"@aws-sdk/middleware-location-constraint@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.190.0.tgz#bd624370daadb0b47982ea83b7e0ce001286654c"
+  integrity sha512-dJ9u7Jb51z9YZLlpvT61og4bzsArJ81cox1t1P990Bbbi+pvJWoiwhwTdLDZhGqTYYg9ZuPd1ajmM2d9NAG/CQ==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-logger@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.162.0.tgz#10b2d552d7aa2c48b91061a418224cc47984272c"
-  integrity sha512-3YysLwpTZdfZkve2ytKFIwEc/WqDkxoI5kUXQq2hjsHAjLW7pEhUV00o+LJbgKjNxh38eSmmKeFlr5jnIjXHiQ==
+"@aws-sdk/middleware-logger@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.190.0.tgz#022c1c6da76d95b3d04e32179c5b1bdfb3944295"
+  integrity sha512-rrfLGYSZCBtiXNrIa8pJ2uwUoUMyj6Q82E8zmduTvqKWviCr6ZKes0lttGIkWhjvhql2m4CbjG5MPBnY7RXL4A==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-recursion-detection@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.162.0.tgz#6c13e90ec9cc7b7adcb98bcb3d02eb37cef37b5c"
-  integrity sha512-AqoTnSX0JgoFuKPyWy0S+WUJqgfkVz6Os50azi32snjHmluEgLOmfeF0ixfxGFUVGxZp8WDuu/JVhwgTRKVuUA==
+"@aws-sdk/middleware-recursion-detection@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.190.0.tgz#6b8480ff62d141312f10940b0a0fe44f651e3f8a"
+  integrity sha512-5tc1AIIZe5jDNdyuJW+7vIFmQOxz3q031ZVrEtUEIF7cz2ySho2lkOWziz+v+UGSLhjHGKMz3V26+aN1FLZNxQ==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-retry@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.162.0.tgz#0601882f9105fc440b902a937f630da65914bd99"
-  integrity sha512-9ZuTim8tnTgP7wNgj+RIdYzGhNgou6QBBX85qMIvngksRUgsd1CGR17HQTyYDZTKlZs7GvLt/L5FaJcOlpPUxA==
+"@aws-sdk/middleware-retry@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.190.0.tgz#935d4097d5785ae14b98272af69aed7ff066786b"
+  integrity sha512-h1bPopkncf2ue/erJdhqvgR2AEh0bIvkNsIHhx93DckWKotZd/GAVDq0gpKj7/f/7B+teHH8Fg5GDOwOOGyKcg==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/service-error-classification" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-middleware" "3.162.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/service-error-classification" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-middleware" "3.190.0"
     tslib "^2.3.1"
     uuid "^8.3.2"
 
-"@aws-sdk/middleware-sdk-s3@3.165.0":
-  version "3.165.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.165.0.tgz#7181f9f2efec56fd85d98c845bf996d8d109d5e3"
-  integrity sha512-OaCQqrgvJLdCVmBwSvQ/woYyDfYuOHUY4i7hl/DDH20QGq1n/CE8g7BBvFVqEaptin0dunFVHOOgzopmVpmOUQ==
+"@aws-sdk/middleware-sdk-s3@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.190.0.tgz#27f5d760896d76ea66e9fa4531b7cb3fb80654d4"
+  integrity sha512-ixN3OOnF95Pla2Q9c4EyUpZk0TR5yYGbV1/eZqz5M9n2Iy1t6H11ZYSmK0BH3YjWG6Tdqva0AIwCfuvoUXWGZg==
   dependencies:
-    "@aws-sdk/middleware-bucket-endpoint" "3.162.0"
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-arn-parser" "3.55.0"
+    "@aws-sdk/middleware-bucket-endpoint" "3.190.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-arn-parser" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-sdk-sts@3.163.0":
-  version "3.163.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.163.0.tgz#362eedb0739c3b4e98aa1154cc5c6f38d7e18f68"
-  integrity sha512-3UV0O5D3HPF5xHsIrDiygs06wmGVnoAXsyrqcZSPkYig9eGP6lTL1I8YS8JIG9/VojQDKjDUpQZMC3Om0+C7hA==
+"@aws-sdk/middleware-sdk-sts@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.190.0.tgz#4606c41658f6d0ff2ff9b11bd2fc3a35f8ebd1ea"
+  integrity sha512-eUHrsr35mkD/cAFKoYuFYz0zE7QPBWvSzMzqmEJC+YXzAF6IABP3FL/htAFpWkje5XDl5dQ6dAxzV+ebK8RNXQ==
   dependencies:
-    "@aws-sdk/middleware-signing" "3.163.0"
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/signature-v4" "3.163.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/middleware-signing" "3.190.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/signature-v4" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-serde@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.162.0.tgz#e83201bffd46500ed4ea71bb3ceda63505f31f96"
-  integrity sha512-Vdgxbl7/o99CjeljQx3mTpY4cX7rc8YQykD49L2S61D6+Gkk9Zc4DMvaJDcxvR7ZUzRwjMTcMHlxbopcp1+UBA==
+"@aws-sdk/middleware-serde@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.190.0.tgz#85f17432facfa8453564a5b5cd2d24e722eeff9f"
+  integrity sha512-S132hEOK4jwbtZ1bGAgSuQ0DMFG4TiD4ulAwbQRBYooC7tiWZbRiR0Pkt2hV8d7WhOHgUpg7rvqlA7/HXXBAsA==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-signing@3.163.0":
-  version "3.163.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.163.0.tgz#9ecc4bdeb1e2b67ab777808818f86ebde9e64578"
-  integrity sha512-Uj5vdejYUJOCJAx5u/SHWQTp75bd7GVqnXazO3QFHRjhSCMT55wsm8D+7LKRNbp4SjavO49m9kubXf+pLpqlJQ==
+"@aws-sdk/middleware-signing@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.190.0.tgz#32fc668c6ea7e632c1dd0eaacd78c71ff522dc77"
+  integrity sha512-Xj5MmXZq8UIpY8wOwyqei5q6MgqKxsO9Plo2zUgW7JR0w7R21MlqY2kzzvcM9R0FFhi9dqhniFT2ZMRUb1v73w==
   dependencies:
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/signature-v4" "3.163.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/signature-v4" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-middleware" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-ssec@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.162.0.tgz#2fcda524b9582a3fcec4124037b3cc3e2d81c453"
-  integrity sha512-hCPivyliCIQ3nncVJmwxhLiWiuMpk3aQYclhhg//KW8xke7UFr9Q6WD8wzINMWFnBOmsNq1guRi3uVjz6K9m2g==
+"@aws-sdk/middleware-ssec@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.190.0.tgz#8519077c258ed503953220bb004b2e338ecdd19d"
+  integrity sha512-nwggMCfXBmhcDIrV/oPeSeuE1FRk0xvi/2PBuYYhM+xnkfUYovYUcdJhGkNqt7U1Mk0+CR2VVwozreigfcLesg==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-stack@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.162.0.tgz#26b37e6241ca5c1a5e50bb2b1367a39eabd2b5ad"
-  integrity sha512-e/by4QvDl9qMQHdBnLz6n8PRglswPb3eS23qT2Wt32KVLUehMUGAf1cdns6YmYSHATK/ivFmT2QHHEnNIc+n5w==
+"@aws-sdk/middleware-stack@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.190.0.tgz#15572c938bf3bbe9d275870e541360fdc7997fab"
+  integrity sha512-h1mqiWNJdi1OTSEY8QovpiHgDQEeRG818v8yShpqSYXJKEqdn54MA3Z1D2fg/Wv/8ZJsFrBCiI7waT1JUYOmCg==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/middleware-user-agent@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.162.0.tgz#da374bd9c66efc563ee9a2f6d1fff29c5ebea965"
-  integrity sha512-aSCQk+oQbMPVHdncuend4jmd4MguLWjvi67OwKqdZjIKsSQfObCO8vwlfDM+ED3HcOfA0LwSxsFeSfQxC+WHxA==
+"@aws-sdk/middleware-user-agent@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.190.0.tgz#791eb451b01846184140eeda63dd51153f911c2c"
+  integrity sha512-y/2cTE1iYHKR0nkb3DvR3G8vt12lcTP95r/iHp8ZO+Uzpc25jM/AyMHWr2ZjqQiHKNlzh8uRw1CmQtgg4sBxXQ==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/node-config-provider@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.162.0.tgz#9637996fd5ef18ce4619b927744727d2aff0d508"
-  integrity sha512-PgaekXCCyz/gKkbukt9zYLBJDEVgmCm0l78q5J84yJbu0FhcZY4LaAgCHdzhsgEYWTX497hokzNc3rgLdVu46A==
+"@aws-sdk/node-config-provider@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.190.0.tgz#a6005c5d3393970e1682c2561c5622d36d05c35d"
+  integrity sha512-TJPUchyeK5KeEXWrwb6oW5/OkY3STCSGR1QIlbPcaTGkbo4kXAVyQmmZsY4KtRPuDM6/HlfUQV17bD716K65rQ==
   dependencies:
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/shared-ini-file-loader" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/shared-ini-file-loader" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/node-http-handler@3.162.0", "@aws-sdk/node-http-handler@^3.82.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.162.0.tgz#771370fe09cc3b030666a6654cf1e461e6fe3cf1"
-  integrity sha512-9jNk9SU3nNLZ1OW+fd6zHGdByUDm0FEO3Hy+J62DvbFe16x09TnVnPAoHfZ69kjz5ZNS7Gg0wmdKjUHi9T3lJQ==
+"@aws-sdk/node-http-handler@3.190.0", "@aws-sdk/node-http-handler@^3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.190.0.tgz#db14e265a5d42587b3edca0e71fa47f530d8c81b"
+  integrity sha512-3Klkr73TpZkCzcnSP+gmFF0Baluzk3r7BaWclJHqt2LcFUWfIJzYlnbBQNZ4t3EEq7ZlBJX85rIDHBRlS+rUyA==
   dependencies:
-    "@aws-sdk/abort-controller" "3.162.0"
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/querystring-builder" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/abort-controller" "3.190.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/querystring-builder" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/property-provider@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.162.0.tgz#45cdaadfba54bb1c275972f12f33f72c8eb22333"
-  integrity sha512-kQLpibZRIrF58axcKY4Pr17YGoVBKBOWKol8jI8vlDhbFJqn14pVLohv4wZ8TzG2kKhWCF+t25YQCefWz2/lkg==
+"@aws-sdk/property-provider@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.190.0.tgz#99372b7e2fba63d3f47ea368c9659ef003733e57"
+  integrity sha512-uzdKjHE2blbuceTC5zeBgZ0+Uo/hf9pH20CHpJeVNtrrtF3GALtu4Y1Gu5QQVIQBz8gjHnqANx0XhfYzorv69Q==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/protocol-http@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.162.0.tgz#3657cb6627576180d8c5a9a37d6c3f2984d70f92"
-  integrity sha512-xMFFxwcO+x5QoQX/LRGb3BpLCIBWC9cBOULm34rYGBySd/zQqebVJOhiKTPzaRL02WZTDNxsEEQHg97Lpe8CNw==
+"@aws-sdk/protocol-http@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.190.0.tgz#6f777f4f5193fc83402fdce29d8fc2bd0c93fb05"
+  integrity sha512-s5MVfeONpfZYRzCSbqQ+wJ3GxKED+aSS7+CQoeaYoD6HDTDxaMGNv9aiPxVCzW02sgG7py7f29Q6Vw+5taZXZA==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/querystring-builder@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.162.0.tgz#c1667eebeb0e15ac6cae2523afa5ea3a8ca8aa00"
-  integrity sha512-3urwxCEpnQwa1B6fKmcr8R2Qmzr8VDttRSay5CgD/stbZ4XUzNsA6G1V36+EL1Vq4vMr1aZhriARioLDlhcz+g==
+"@aws-sdk/querystring-builder@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.190.0.tgz#5c115eb30343cc28ea8fc6ab1ca945907533403a"
+  integrity sha512-w9mTKkCsaLIBC8EA4RAHrqethNGbf60CbpPzN/QM7yCV3ZZJAXkppFfjTVVOMbPaI8GUEOptJtzgqV68CRB7ow==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-uri-escape" "3.55.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-uri-escape" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/querystring-parser@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.162.0.tgz#d4f0522cfe2a44d2f242a2e5c9c9cf928930938e"
-  integrity sha512-0ccaGsR1O7e3BsprdYBMwGf8gmycTv1Dfz2EB5R6MiTqzcuQJ/lxpIcRh3jhUJaD1TPlUziyrBEAxtLka3HDDQ==
+"@aws-sdk/querystring-parser@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.190.0.tgz#e96fab0ac834ab57777d736790eb2509491bd3fa"
+  integrity sha512-vCKP0s33VtS47LSYzEWRRr2aTbi3qNkUuQyIrc5LMqBfS5hsy79P1HL4Q7lCVqZB5fe61N8fKzOxDxWRCF0sXg==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/service-error-classification@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.162.0.tgz#b07ed4ef541ec4d3d3cfe93efae054e3d39b5a1b"
-  integrity sha512-AD9XL3CHFzwVWNEzdTo9aRnJl1ImqrRLlJ5zR/5ihTIJ68ZTYEiYP4vNKSCV6UfQ+vaaRNgLwiAx7JXzY54awg==
+"@aws-sdk/service-error-classification@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.190.0.tgz#b1e232abfdc98fcf6f12dcbe50f9b9141fe53d42"
+  integrity sha512-g+s6xtaMa5fCMA2zJQC4BiFGMP7FN5/L1V/UwxCnKy8skCwaN0K5A1tFffBjjbYiPI7Gu7LVorWD2A0Y4xl01Q==
 
-"@aws-sdk/shared-ini-file-loader@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.162.0.tgz#53bc40d774f4411200104e415cbe27a3dea110f7"
-  integrity sha512-AGxISXns+1o6Pw+SPizFJDTw4Lwm+JSwhycCNhFU3JfdLsKfLY08JV4JHlcc+TyY4a8HhnGvE3r5t2f2dPLIsA==
+"@aws-sdk/shared-ini-file-loader@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.190.0.tgz#23efb053ae56f7cb96cb1cb64e8afeffafac963c"
+  integrity sha512-CZC/xsGReUEl5w+JgfancrxfkaCbEisyIFy6HALUYrioWQe80WMqLAdUMZSXHWjIaNK9mH0J/qvcSV2MuIoMzQ==
   dependencies:
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/signature-v4-multi-region@3.163.0":
-  version "3.163.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.163.0.tgz#e1d58d6dd40c4530d6ae83f8ebc0ad33a64f5d7c"
-  integrity sha512-SIb6e1dFNchvWziKudh7+BbYjUDW7WAEWWz419pKj/WDGdZNaA7dj7rr4IAB9vbi6QqzkECGDszdMqriWbJrDg==
+"@aws-sdk/signature-v4-multi-region@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.190.0.tgz#f3971e238362b3128dcf536142277f4123ddafed"
+  integrity sha512-i9BpqlY3kAF8l8amqJ4skyjrlBy4B3hhO4RPu1D1wF4+GsJ5JGZ638Y5bYMH90k7A7ycTDDgFWHJcAsm1b0fEQ==
   dependencies:
-    "@aws-sdk/protocol-http" "3.162.0"
-    "@aws-sdk/signature-v4" "3.163.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-arn-parser" "3.55.0"
+    "@aws-sdk/protocol-http" "3.190.0"
+    "@aws-sdk/signature-v4" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-arn-parser" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/signature-v4@3.163.0":
-  version "3.163.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.163.0.tgz#6a083fd0fe285b07ca71fc88990c7a9440b7c0b2"
-  integrity sha512-1iein+7iAHKcRIXaZhl/lG6JrOR/Qmk27zMqfARzxDF7o/W5arSs3DHIKytO1sOEn9zV6Mqm21dRAumD21VCCg==
+"@aws-sdk/signature-v4@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.190.0.tgz#ed5a408465723021648fd95440234898e114e2f6"
+  integrity sha512-L/R/1X2T+/Kg2k/sjoYyDFulVUGrVcRfyEKKVFIUNg0NwUtw5UKa1/gS7geTKcg4q8M2pd/v+OCBrge2X7phUw==
   dependencies:
-    "@aws-sdk/is-array-buffer" "3.55.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-hex-encoding" "3.109.0"
-    "@aws-sdk/util-middleware" "3.162.0"
-    "@aws-sdk/util-uri-escape" "3.55.0"
+    "@aws-sdk/is-array-buffer" "3.188.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-hex-encoding" "3.188.0"
+    "@aws-sdk/util-middleware" "3.190.0"
+    "@aws-sdk/util-uri-escape" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/smithy-client@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.162.0.tgz#2646b05c287a609a1abb7d28a7009f3314c26803"
-  integrity sha512-o7CwdhPvzYMvHY5dTzL2kqN8Zsl2D8pZ1mG2dPdQW9hYnutLOFK1HVv5dIzoSkp3jUwVGh6AXd1i4ZSb2d0LrA==
+"@aws-sdk/smithy-client@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.190.0.tgz#4a951a7f3470fe148330e3a3e68cf9020b148e64"
+  integrity sha512-f5EoCwjBLXMyuN491u1NmEutbolL0cJegaJbtgK9OJw2BLuRHiBknjDF4OEVuK/WqK0kz2JLMGi9xwVPl4BKCA==
   dependencies:
-    "@aws-sdk/middleware-stack" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/middleware-stack" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/types@3.162.0", "@aws-sdk/types@^3.1.0", "@aws-sdk/types@^3.110.0":
+"@aws-sdk/types@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.190.0.tgz#ef22549c81ea6a7dd2c57e5869e787fea40c4434"
+  integrity sha512-mkeZ+vJZzElP6OdRXvuLKWHSlDQxZP9u8BjQB9N0Rw0pCXTzYS0vzIhN1pL0uddWp5fMrIE68snto9xNR6BQuA==
+
+"@aws-sdk/types@^3.1.0", "@aws-sdk/types@^3.110.0":
   version "3.162.0"
   resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.162.0.tgz#e908ff1a5de6bd06d7d6b88a648b384592acf7e2"
   integrity sha512-NBmuwVujH8fURDMvBHkHrYu/JAfG6Js/Bu0mC4o2Kdo5mRa3fD/N9kK0dEAxU1Rxp4wY2E++V9j2ZCw1KBGrSg==
 
-"@aws-sdk/url-parser@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.162.0.tgz#ce341d3b6dbcaf52f00b80aff56b24b5ab8c1e82"
-  integrity sha512-aJQ2awXYDceLAzPMQETpvI1XQd8oYuqH1EriFzXHqoJTNmYnHb7awtKSqwaS8pq48x1rS/eVtJAi85BG93fXyw==
+"@aws-sdk/url-parser@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.190.0.tgz#d3c40dd0d01fb97c2c7f610baf1be2f045ae5582"
+  integrity sha512-FKFDtxA9pvHmpfWmNVK5BAVRpDgkWMz3u4Sg9UzB+WAFN6UexRypXXUZCFAo8S04FbPKfYOR3O0uVlw7kzmj9g==
   dependencies:
-    "@aws-sdk/querystring-parser" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/querystring-parser" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-arn-parser@3.55.0":
-  version "3.55.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.55.0.tgz#6672eb2975e798a460bedfaf6b5618d4e4b262e1"
-  integrity sha512-76KJxp4MRWufHYWys7DFl64znr5yeJ3AIQNAPCKKw1sP0hzO7p6Kx0PaJnw9x+CPSzOrT4NbuApL6/srYhKDGg==
+"@aws-sdk/util-arn-parser@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.188.0.tgz#4df0144c00dce3490666da7d55e6e13c9a3f21b2"
+  integrity sha512-q4nZzt/g3sRY9a3sj1PaNFwql5bXfKSW4fRy0zLdbZHcYdgq2oQfVsJTIlL9lUNjifkXiIsmk61Q16JExtrLyw==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-base64-browser@3.109.0":
-  version "3.109.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-browser/-/util-base64-browser-3.109.0.tgz#e7faf5c4cbb88bc39b9c1c5a1a79e4c869e9f645"
-  integrity sha512-lAZ6fyDGiRLaIsKT9qh7P9FGuNyZ4gAbr1YOSQk/5mHtaTuUvxlPptZuInNM/0MPQm6lpcot00D8IWTucn4PbA==
+"@aws-sdk/util-base64-browser@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-browser/-/util-base64-browser-3.188.0.tgz#581c85dc157aff88ca81e42d9c79d87c95db8d03"
+  integrity sha512-qlH+5NZBLiyKziL335BEPedYxX6j+p7KFRWXvDQox9S+s+gLCayednpK+fteOhBenCcR9fUZOVuAPScy1I8qCg==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-base64-node@3.55.0":
-  version "3.55.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-node/-/util-base64-node-3.55.0.tgz#da9a3fd6752be49163572144793e6b23d0186ff4"
-  integrity sha512-UQ/ZuNoAc8CFMpSiRYmevaTsuRKzLwulZTnM8LNlIt9Wx1tpNvqp80cfvVj7yySKROtEi20wq29h31dZf1eYNQ==
+"@aws-sdk/util-base64-node@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64-node/-/util-base64-node-3.188.0.tgz#1d2413f68c8ad1cca0903fc11d92af88ba70e14d"
+  integrity sha512-r1dccRsRjKq+OhVRUfqFiW3sGgZBjHbMeHLbrAs9jrOjU2PTQ8PSzAXLvX/9lmp7YjmX17Qvlsg0NCr1tbB9OA==
   dependencies:
-    "@aws-sdk/util-buffer-from" "3.55.0"
+    "@aws-sdk/util-buffer-from" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-body-length-browser@3.154.0":
-  version "3.154.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.154.0.tgz#8c4c5d08c1923deeedf46006dc4db820ca606f56"
-  integrity sha512-TUuy7paVkBRQrB/XFCsL8iTW6g/ma0S3N8dYOiIMJdeTqTFryeyOGkBpYBgYFQL6zRMZpyu0jOM7GYEffGFOXw==
+"@aws-sdk/util-body-length-browser@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz#e1d949318c10a621b38575a9ef01e39f9857ddb0"
+  integrity sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-body-length-node@3.55.0":
-  version "3.55.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-node/-/util-body-length-node-3.55.0.tgz#67049bbb6c62d794a1bb5a13b9a678988c925489"
-  integrity sha512-lU1d4I+9wJwydduXs0SxSfd+mHKjxeyd39VwOv6i2KSwWkPbji9UQqpflKLKw+r45jL7+xU/zfeTUg5Tt/3Gew==
+"@aws-sdk/util-body-length-node@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-node/-/util-body-length-node-3.188.0.tgz#3fc2a820b9be0efcbdf962d8f980b9000b98ddba"
+  integrity sha512-XwqP3vxk60MKp4YDdvDeCD6BPOiG2e+/Ou4AofZOy5/toB6NKz2pFNibQIUg2+jc7mPMnGnvOW3MQEgSJ+gu/Q==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-buffer-from@3.55.0":
-  version "3.55.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-buffer-from/-/util-buffer-from-3.55.0.tgz#e7c927974b07a29502aa1ad58509b91d0d7cf0f7"
-  integrity sha512-uVzKG1UgvnV7XX2FPTylBujYMKBPBaq/qFBxfl0LVNfrty7YjpfieQxAe6yRLD+T0Kir/WDQwGvYC+tOYG3IGA==
+"@aws-sdk/util-buffer-from@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-buffer-from/-/util-buffer-from-3.188.0.tgz#a062ccd990571df4353990e8b78aebec5a14547d"
+  integrity sha512-NX1WXZ8TH20IZb4jPFT2CnLKSqZWddGxtfiWxD9M47YOtq/SSQeR82fhqqVjJn4P8w2F5E28f+Du4ntg/sGcxA==
   dependencies:
-    "@aws-sdk/is-array-buffer" "3.55.0"
+    "@aws-sdk/is-array-buffer" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-config-provider@3.109.0":
-  version "3.109.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-config-provider/-/util-config-provider-3.109.0.tgz#7828b8894b2b23c289ffc5c106cbced7a5d6ee86"
-  integrity sha512-GrAZl/aBv0A28LkyNyq8SPJ5fmViCwz80fWLMeWx/6q5AbivuILogjlWwEZSvZ9zrlHOcFC0+AnCa5pQrjaslw==
+"@aws-sdk/util-config-provider@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-config-provider/-/util-config-provider-3.188.0.tgz#f7a365e6cbfe728c1224f0b39926636619b669e0"
+  integrity sha512-LBA7tLbi7v4uvbOJhSnjJrxbcRifKK/1ZVK94JTV2MNSCCyNkFotyEI5UWDl10YKriTIUyf7o5cakpiDZ3O4xg==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-defaults-mode-browser@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.162.0.tgz#41625d5ea4d3d347df00cc5647a0808fcb059c94"
-  integrity sha512-BjhuriainNy0ezFqCK//380Wc4xsJJOnq1tmPlHScQxp3g8ucfClvjOUi96XQaTrEf8c8EsYp77+JNsvrHytmw==
+"@aws-sdk/util-defaults-mode-browser@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.190.0.tgz#b544c978ae4e5a7cf12bb7975c0199dbe3517c85"
+  integrity sha512-FKxTU4tIbFk2pdUbBNneStF++j+/pB4NYJ1HRSEAb/g4D2+kxikR/WKIv3p0JTVvAkwcuX/ausILYEPUyDZ4HQ==
   dependencies:
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     bowser "^2.11.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-defaults-mode-node@3.163.0":
-  version "3.163.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.163.0.tgz#9cb4d5b0b1beb9573b11289eee46c762d3a9c0bd"
-  integrity sha512-IM5BVeUXzLpb9m8qBSpUc2iO+topP1F7Ojq1GNrl5G/b22f7b1FCL5qkTem/UIXkkgI+efI7jr05xPRGiU73Hg==
+"@aws-sdk/util-defaults-mode-node@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.190.0.tgz#67acd5a3dba7f30ac2fb30084dd2b9ff77612c15"
+  integrity sha512-qBiIMjNynqAP7p6urG1+ZattYkFaylhyinofVcLEiDvM9a6zGt6GZsxru2Loq0kRAXXGew9E9BWGt45HcDc20g==
   dependencies:
-    "@aws-sdk/config-resolver" "3.163.0"
-    "@aws-sdk/credential-provider-imds" "3.162.0"
-    "@aws-sdk/node-config-provider" "3.162.0"
-    "@aws-sdk/property-provider" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/config-resolver" "3.190.0"
+    "@aws-sdk/credential-provider-imds" "3.190.0"
+    "@aws-sdk/node-config-provider" "3.190.0"
+    "@aws-sdk/property-provider" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-hex-encoding@3.109.0":
-  version "3.109.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.109.0.tgz#93b20acc27c0a1d7d80f653bf19d3dd01c2ccc65"
-  integrity sha512-s8CgTNrn3cLkrdiohfxLuOYPCanzvHn/aH5RW6DaMoeQiG5Hl9QUiP/WtdQ9QQx3xvpQFpmvxIaSBwSgFNLQxA==
+"@aws-sdk/util-hex-encoding@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.188.0.tgz#c2d8b02b952db58acbd5f53718109657c69c460f"
+  integrity sha512-QyWovTtjQ2RYxqVM+STPh65owSqzuXURnfoof778spyX4iQ4z46wOge1YV2ZtwS8w5LWd9eeVvDrLu5POPYOnA==
   dependencies:
     tslib "^2.3.1"
 
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-middleware@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-middleware/-/util-middleware-3.162.0.tgz#5e38b45b52b731ec4be7b3eb65e8097e70a8a844"
-  integrity sha512-jDqZZ5nst+NtzvAPIQBdQqGY14Z3HeGANGm5NUoxWp8IlHnEV7GhTFDjFgubf8mgTBCzHnvbuBY1bfkzAeXWBA==
+"@aws-sdk/util-middleware@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-middleware/-/util-middleware-3.190.0.tgz#9c594987f107af05b770f2ac2e70c0391d0cb5b5"
+  integrity sha512-qzTJ/qhFDzHZS+iXdHydQ/0sWAuNIB5feeLm55Io/I8Utv3l3TKYOhbgGwTsXY+jDk7oD+YnAi7hLN5oEBCwpg==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-stream-browser@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-stream-browser/-/util-stream-browser-3.162.0.tgz#9fbde6fee82bdef40b36f2529f99378f3899ec4e"
-  integrity sha512-1GCOvu6bIQNo3ql/ULobztaJTGxUsWBIgSxftATU0vuCI1Rf4ReluENvpXzdbTEi6mv+k1YUrBBLVzSmFwwxrw==
-  dependencies:
-    "@aws-sdk/fetch-http-handler" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-base64-browser" "3.109.0"
-    "@aws-sdk/util-hex-encoding" "3.109.0"
-    "@aws-sdk/util-utf8-browser" "3.109.0"
+"@aws-sdk/util-stream-browser@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-stream-browser/-/util-stream-browser-3.190.0.tgz#ebb14f63dffe05a09a670cf14a307920d736c803"
+  integrity sha512-BqHUD+Bz780LM6lD2YLYsX1PZ+BqwkRYtSaw+ch8IvrnMWeAB1hzQWXRh+2NhMpe2IBw18IrrYB3kVmcyGgUtg==
+  dependencies:
+    "@aws-sdk/fetch-http-handler" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-base64-browser" "3.188.0"
+    "@aws-sdk/util-hex-encoding" "3.188.0"
+    "@aws-sdk/util-utf8-browser" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-stream-node@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-stream-node/-/util-stream-node-3.162.0.tgz#fcc6e8db77c1785033f3f120a2700affee304b7b"
-  integrity sha512-MWHF8HIsEdircxXb00/y+SesLugetGiU47xOVQtg40yWdoJyPWzP6kBK6+SWT8uR0r1IYdW7fwyxMbc9TccYcQ==
+"@aws-sdk/util-stream-node@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-stream-node/-/util-stream-node-3.190.0.tgz#30f7a3a81e3ad379f9bbf60d197686ca6fa6138e"
+  integrity sha512-RJ7iQB43fSfGW+aPhdT5LfG5GtbenB7q8mQLiTdIoj4Hw+gwa90JagMX+NxZv6DEkT4IgbT4q9liPQ48M83/YA==
   dependencies:
-    "@aws-sdk/node-http-handler" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
-    "@aws-sdk/util-buffer-from" "3.55.0"
+    "@aws-sdk/node-http-handler" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
+    "@aws-sdk/util-buffer-from" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-uri-escape@3.55.0":
-  version "3.55.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-uri-escape/-/util-uri-escape-3.55.0.tgz#ee57743c628a1c9f942dfe73205ce890ec011916"
-  integrity sha512-mmdDLUpFCN2nkfwlLdOM54lTD528GiGSPN1qb8XtGLgZsJUmg3uJSFIN2lPeSbEwJB3NFjVas/rnQC48i7mV8w==
+"@aws-sdk/util-uri-escape@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-uri-escape/-/util-uri-escape-3.188.0.tgz#6dbd4322f6cdc3252a75c6f729e1082369c468c0"
+  integrity sha512-4Y6AYZMT483Tiuq8dxz5WHIiPNdSFPGrl6tRTo2Oi2FcwypwmFhqgEGcqxeXDUJktvaCBxeA08DLr/AemVhPCg==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-user-agent-browser@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.162.0.tgz#735a3611e65df0c4b8a416bbb096e56d0b43f5c9"
-  integrity sha512-FNmC2ywy1u3tbUSVCSkCwLvcbjIvj5EzAtF6I2wrMTI5PfaxVIQapKn2EecoVQgf4lsZqvGjyTxbl7SYvf9fxw==
+"@aws-sdk/util-user-agent-browser@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.190.0.tgz#efb7eda04b94b260ae8e8ff5f623eeb9318f2bfd"
+  integrity sha512-c074wjsD+/u9vT7DVrBLkwVhn28I+OEHuHaqpTVCvAIjpueZ3oms0e99YJLfpdpEgdLavOroAsNFtAuRrrTZZw==
   dependencies:
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/types" "3.190.0"
     bowser "^2.11.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-user-agent-node@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.162.0.tgz#3c589c1ad1ce2821c9568dd12bb5632b8eb776be"
-  integrity sha512-OIbZlccBFwITDQJoymU0V+yqqyPEbJUExJzeiP9bxJ58h7Jxj/da24cxCMaVDYvjhP/PoflOmC5Xblonaeg+oQ==
+"@aws-sdk/util-user-agent-node@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.190.0.tgz#b4bdc523d63ca418f5ca54e26f836db91fe55c43"
+  integrity sha512-R36BMvvPX8frqFhU4lAsrOJ/2PJEHH/Jz1WZzO3GWmVSEAQQdHmo8tVPE3KOM7mZWe5Hj1dZudFAIxWHHFYKJA==
   dependencies:
-    "@aws-sdk/node-config-provider" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/node-config-provider" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-utf8-browser@3.109.0", "@aws-sdk/util-utf8-browser@^3.0.0":
+"@aws-sdk/util-utf8-browser@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz#484762bd600401350e148277731d6744a4a92225"
+  integrity sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==
+  dependencies:
+    tslib "^2.3.1"
+
+"@aws-sdk/util-utf8-browser@^3.0.0":
   version "3.109.0"
   resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.109.0.tgz#d013272e1981b23a4c84ac06f154db686c0cf84e"
   integrity sha512-FmcGSz0v7Bqpl1SE8G1Gc0CtDpug+rvqNCG/szn86JApD/f5x8oByjbEiAyTU2ZH2VevUntx6EW68ulHyH+x+w==
   dependencies:
     tslib "^2.3.1"
 
-"@aws-sdk/util-utf8-node@3.109.0":
-  version "3.109.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-node/-/util-utf8-node-3.109.0.tgz#89e06d916f5b246c7265f59bac742973ac0767ac"
-  integrity sha512-Ti/ZBdvz2eSTElsucjzNmzpyg2MwfD1rXmxD0hZuIF8bPON/0+sZYnWd5CbDw9kgmhy28dmKue086tbZ1G0iLQ==
+"@aws-sdk/util-utf8-node@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-node/-/util-utf8-node-3.188.0.tgz#935bc58a71f2792ac6a4ec881f72bf9ceee008b4"
+  integrity sha512-hCgP4+C0Lekjpjt2zFJ2R/iHes5sBGljXa5bScOFAEkRUc0Qw0VNgTv7LpEbIOAwGmqyxBoCwBW0YHPW1DfmYQ==
   dependencies:
-    "@aws-sdk/util-buffer-from" "3.55.0"
+    "@aws-sdk/util-buffer-from" "3.188.0"
     tslib "^2.3.1"
 
-"@aws-sdk/util-waiter@3.162.0":
-  version "3.162.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.162.0.tgz#369499ec934a47aa8f0340876a9228e6d8989736"
-  integrity sha512-RDMimLdgffkBiFjXyGO0DYjUhDisBHx5OZIaj57bCWRqvMj4lSz5+eS3fpVEZaRDFo/P3i/86lvjPEqXStTlZA==
+"@aws-sdk/util-waiter@3.190.0":
+  version "3.190.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/util-waiter/-/util-waiter-3.190.0.tgz#01348830fbd6a9a1a8e1fb22e87a29185d8e69fc"
+  integrity sha512-wlc56EElap8ua+9VCSqXaC4QsJQecYmC0C6A5EfFDtFlFyyd+vjpiLMU34juzrqJfVCx6aAUD2c9f+ezvk1G5Q==
   dependencies:
-    "@aws-sdk/abort-controller" "3.162.0"
-    "@aws-sdk/types" "3.162.0"
+    "@aws-sdk/abort-controller" "3.190.0"
+    "@aws-sdk/types" "3.190.0"
     tslib "^2.3.1"
 
-"@aws-sdk/xml-builder@3.142.0":
-  version "3.142.0"
-  resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.142.0.tgz#55d03f1a1d1b3b02f07e26ec05d6855151f94c4e"
-  integrity sha512-e8rFjm5y9ngFc/cPwWMNn/CmMMrLx98CajWew9q7OzP6OOXQJ0H6TaRps2uQPM5XUv3/Ab5YQCV3NiaLJLqqNg==
+"@aws-sdk/xml-builder@3.188.0":
+  version "3.188.0"
+  resolved "https://registry.yarnpkg.com/@aws-sdk/xml-builder/-/xml-builder-3.188.0.tgz#d3f72f5e490be577670b6532343f5682e38ac000"
+  integrity sha512-/Hah3gAtrBpEaDInX3eSS0nXw/IUeb+rWiGspXxb5O8bh5kyjQqeu8/sVJQlpOtq4aPDbMDmloH4k696qTqgbw==
   dependencies:
     tslib "^2.3.1"
 
@@ -4145,7 +4172,7 @@ engine.io@~6.2.0:
     engine.io-parser "~5.0.3"
     ws "~8.2.3"
 
-entities@2.2.0, entities@^2.0.0:
+entities@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
   integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
@@ -4748,10 +4775,12 @@ fast-safe-stringify@^2.1.1:
   resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
   integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
 
-fast-xml-parser@3.19.0:
-  version "3.19.0"
-  resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz#cb637ec3f3999f51406dd8ff0e6fc4d83e520d01"
-  integrity sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==
+fast-xml-parser@4.0.11:
+  version "4.0.11"
+  resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz#42332a9aca544520631c8919e6ea871c0185a985"
+  integrity sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==
+  dependencies:
+    strnum "^1.0.5"
 
 fast-xml-parser@^4.0.0-beta.8:
   version "4.0.9"