janko / uppy-s3_multipart

Provides Ruby endpoints for AWS S3 multipart uploads in Uppy
https://uppy.io/docs/aws-s3/
MIT License
65 stars 21 forks source link

How to replace the host for signing during multipart upload in docker compose. #34

Open pocari opened 7 months ago

pocari commented 7 months ago

Docker compose, and I’m using Minio as a middleware equivalent to s3 in my local environment. In this configuration, when I want to achieve multipart direct upload, if I use this gem normally, the signature generated by presgined_post is based on the host inside the container, but Actually, I upload from the host os side to localhost:xxxx (=forward port), so the host is different from the signature and becomes localhost, resulting in 403.

As a workaround for this, I replaced the host with localhost when signing and avoided it. However, since this extension became a redundant response, is there a way to flexibly replace Aws::S3::Presigner used in Uppy::S3Multipart::Client?

For now, I’ll share the implementation where I replaced that part.

# config/initializer/shrine.rb

Shrine.plugin :uppy_s3_multipart
if Rails.env.development?
  module ShrineUppyS3MultipartEx
    def uppy_s3_multipart(storage_key, **options) # rubocop:disable Metrics/CyclomaticComplexity
      s3 = find_storage(storage_key)

      raise Error, "expected storage to be a Shrine::Storage::S3, but was #{s3.inspect}" unless defined?(Shrine::Storage::S3) && s3.is_a?(Shrine::Storage::S3)

      options[:bucket]  ||= s3.bucket
      options[:prefix]  ||= s3.prefix
      options[:public]  ||= s3.public if s3.respond_to?(:public)
      options[:options] ||= opts[:uppy_s3_multipart_options]

      MyUppyMultipartApp.new(**options)
    end
  end

  Shrine.singleton_class.prepend(ShrineUppyS3MultipartEx)

  class MyUppyMultipartApp < Uppy::S3Multipart::App
    def initialize(bucket:, prefix: nil, public: nil, options: {})
      super
      @router.opts[:client] = MyUppyMultipartClient.new(bucket:)
    end
  end

  class MyUppyMultipartClient < Uppy::S3Multipart::Client
    def presigner
      MyPresigner.new(client:)
    end
  end

  class MyPresigner < Aws::S3::Presigner
    def initialize(*, **)
      super
    end

    def handle_presigned_url_context(req)
      super.tap do
        handle_host_context(req)
      end
    end

    def handle_host_context(req)
      req.handle(priority: 10) do |context|
        uri = context.http_request.endpoint
        uri.host = 'localhost' # replace host for sign
        @handler.call(context)
      end
    end
  end
end