Search code examples
ruby-on-railsaws-sdkdigital-oceanshrine

error on uploading to digitalocean spaces using shrine and aws-sdk


I am using shrine to directly upload to aws-s3 bucket and is working fine. Now I want to use DigitalOcean spaces instead. So I changed some settings for shrine like below(only changed the env variables relevant to spaces).

require "shrine"
require "shrine/storage/s3"

s3_options = {
    access_key_id: ENV["SPACES_ACCESS_KEY_ID"],
    secret_access_key: ENV["SPACES_SECRET_ACCESS_KEY"],
    region: ENV["SPACES_REGION"],
    bucket: ENV["SPACES_BUCKET"],
    endpoint: ENV["SPACES_ENDPOINT"]
}

Shrine.storages = {
    cache: Shrine::Storage::S3.new(prefix: "cache", upload_options: {acl: "public-read"}, **s3_options),
    store: Shrine::Storage::S3.new(prefix: "store", upload_options: {acl: "public-read"}, **s3_options),
}

Shrine.plugin :activerecord
Shrine.plugin :presign_endpoint
Shrine.plugin :restore_cached_data
Shrine.plugin :backgrounding

Shrine::Attacher.promote {|data| UploadJob.perform_async(data)}
Shrine::Attacher.delete {|data| DeleteJob.perform_async(data)}

I also added cors in spaces to allow all requests like below

Cors settings

But when I upload I am getting this error.

<Error><Code>AccessDenied</Code><Message>Policy missing condition: Content-Type</Message><BucketName>testing-dev</BucketName><RequestId>tx0000000036366363370-3663t37373-33883-sgp1a</RequestId><HostId>349494-sgp1a-sgp</HostId></Error>

What could be the issue here? I can see that the error telling content-type missing in policy. But how can I add that if thats the case?


Solution

  • It seems that DigitalOcean Spaces now requires the presign policy to include contentType condition. The presign policy is generated in the presign endpoint, so you can tell it to add :content_type based on the filename query parameter:

    Shrine.plugin :presign_endpoint, presign_options: -> (request) do
      filename     = request.params["filename"]
      extension    = File.extname(filename)
      content_type = Rack::Mime.mime_type(extension)
    
      { content_type: content_type }
    end
    

    This should make :content_type always present, because if the filename has no extension or the extension is unrecognised, Rack::Mime.mime_type will return application/octet-stream, which is the content type S3 assigns to objects by default anyway.

    Just make sure you're passing the filename query parameter on the client side when sending the presign request. E.g. using window.fetch it would be:

    fetch('/presign?filename=' + file.name)