Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Uploading dropzone, S3, carrierwave, not working in Safari, but works in Google Chrome

I'm using dropzone with S3 and carrierwave. I'm able to upload images through Google Chrome, but I can't get it to work with Safari, which is weird.

This is my form

= nested_form_for @trip, html: { multipart: true, id: 'fileupload', class: 'directUpload', data: { 'form-data' => (@s3_direct_post.fields), 'url' => @s3_direct_post.url, 'host' => URI.parse(@s3_direct_post.url).host } } do |f|
  .dropzone#imageUpload
    = f.simple_fields_for :trip_images, TripImage.new, child_index: TripImage.new.object_id do |ff|
    = ff.file_field :photo, class: 'hide form-fields'
    = f.button :submit, id: "submit-data"

This is in the Trip controller

def set_s3_direct_post
  @s3_direct_post = S3_BUCKET.presigned_post(key: "/uploads/temporary/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'public-read', content_type: 'image/jpeg')
end

This is TripImage model

class TripImage < ActiveRecord::Base
  belongs_to :resource, :polymorphic => true
  mount_uploader :photo, PhotoUploader
  after_create :process_async

  def to_jq_upload
    {
      'name' => read_attribute(:attachment_file_name),
      'size' => read_attribute(:attachment_file_size),
      'url' => attachment.url(:original),
      'thumbnail_url' => attachment.url(:thumb),
      'delete_url' => "/photos/#{id}",
      'delete_type' => 'DELETE'
    }
  end

  private

  def process_async
    PhotoVersioningJob.set( wait: 5.seconds ).perform_later(self.id)
  end

end

This is js

$(function(){
  $('.directUpload').find(".dropzone").each(function(i, elem) {
    s3ImageUpload(elem);
  });
})

function s3ImageUpload(elem){
  var fileInput    = $(elem);
  var form         = $(fileInput.parents('form:first'));
  var form_url = form.data('url');
  var form_data = form.data('form-data');
  Dropzone.options.imageUpload = {
    url: form_url,
    params: form_data,
    uploadMultiple: false,
    addRemoveLinks: true,
    removedfile: function(file){
      //some codes
    },
    success: function(file, serverResponse, event){
      //some codes
    },
    error: function(data){
      //some codes
    }
  };
}

EDIT: Current CORS configuration

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*.example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
        <AllowedHeader>origin</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

Tested and doesn't work

EDIT: I also have S3 direct upload, not sure if this affects it too?

S3DirectUpload.config do |c|
  c.access_key_id = Rails::AWS.config['access_key_id']       # your access key id
  c.secret_access_key = Rails::AWS.config['secret_access_key']   # your secret access key
  c.bucket = Rails::AWS.config['bucket_name']              # your bucket name
  c.region = 's3'             # region prefix of your bucket url. This is _required_ for the non-default AWS region, eg. "s3-eu-west-1"
end
like image 609
hellomello Avatar asked Dec 11 '15 04:12

hellomello


1 Answers

I have run into a similar issue with Safari recently, and discovered that it is sending an extra Access-Control-Request-Header that Chrome does not -- specifically 'origin'. To address this difference I needed to update my AWS CORS config on the destination bucket.

AWS Documentation on the necessity of request headers matching an allowed header config. The third bullet point makes this requirement explicit:

Every header listed in the request's Access-Control-Request-Headers header on the preflight request must match an AllowedHeader element.

This helpful StackOverflow answer gives an example config:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
    <AllowedHeader>x-requested-with</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

And what needed to be added to get it working in Safari:

    <AllowedHeader>origin</AllowedHeader>
like image 124
craigts Avatar answered Nov 17 '22 07:11

craigts