0

I am trying to use Ruby On Rails as my backend, and gems like ReactOnRails and Shrine, in addition, I use package Uppy to upload the image to AWS S3

So far everything working fine until I trying to upload the image through Uppy which should transfer the AWS credentials through the backend, so I don't expose my secret keys from the front end.

A snippet of React Component

.use(AwsS3, {
  limit: 5,
  // serverUrl: 'https://uppy-companion.my-app.com/',
  strings: {
    preparingUpload: 'Preparing upload...'
  },
  getUploadParameters(file) {
    return fetch('/presign?filename=' + file.name, {
      method: 'post',
      // Send and receive JSON.
      headers: {
        accept: 'application/json',
        'content-type': 'application/json'
      },
      body: JSON.stringify({
        filename: file.name,
        contentType: file.type
      })
    }).then((response) => {
      // Parse the JSON response.
      return response.json()
    }).then((data) => {
      // Return an object in the correct shape.
      return {
        method: data.method,
        url: data.url,
        fields: data.fields
      }
    }).catch((error) => console.log(error))
  }
});

My Routes are

mount Shrine.presign_endpoint(:cache) => '/presign'

and finally my shrine.rb file

require 'shrine'

if Rails.env.production?
  require 'shrine/storage/s3'

  s3_options = {
    access_key_id: Rails.application.credentials.aws[:s3_access_key_id],
    secret_access_key: Rails.application.credentials.aws[:s3_secret_access_key],
    region: Rails.application.credentials.aws[:s3_region],
    bucket: Rails.application.credentials.aws[:s3_bucket]
  }

  Shrine.storages = {
    cache: Shrine::Storage::S3.new(prefix: 'cache', **s3_options),
    store: Shrine::Storage::S3.new(prefix: "store", **s3_options)
  }
else
  require 'shrine/storage/file_system'

  Shrine.storages = {
    cache: Shrine::Storage::FileSystem.new('public', prefix: 'uploads/cache'),
    store: Shrine::Storage::FileSystem.new('public', prefix: 'uploads')
  }
end

Shrine.plugin :activerecord
Shrine.plugin :backgrounding
Shrine.plugin :logging
Shrine.plugin :determine_mime_type
Shrine.plugin :cached_attachment_data
Shrine.plugin :restore_cached_data
#TODO: docs
Shrine.plugin :presign_endpoint if Rails.env.production?
Shrine.plugin :upload_endpoint if !Rails.env.production?

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

my AWS S3 Cros configurations

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
    <AllowedHeader>x-amz-date</AllowedHeader>
    <AllowedHeader>x-amz-content-sha256</AllowedHeader>
    <AllowedHeader>content-type</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>

I just cannot figure out how to fix this issue when I upload file through the uppy.js.

ray
  • 5,454
  • 1
  • 18
  • 40
GYTO
  • 440
  • 1
  • 10
  • 23

1 Answers1

3

Shrine's presign endpoint responds to GET requests, not POST, so you need to do

getUploadParameters(file) {
  return fetch('/presign?filename=' + file.name, {
    method: 'get',
    ...

By the way, if you mount the presign endpoint on /s3/params:

Rails.application.routes.draw do
  mount Shrine.presign_endpoint(:cache) => "/s3/params"
end

you don't need any of this fetch() logic, you can just point Uppy to your app, and it will automatically use /s3/params and do all of the fetching work for you:

uppy.use(AwsS3, {
  companionUrl: '/',
  ...
})
Janko
  • 8,985
  • 7
  • 34
  • 51
  • thanks, janko-m, it works as you said, just use generic routes it will handle by itself. Also, I have a question to the content-type, should it be done by shrine or uppy? I am trying to set content-type to be image, so you can open the image from the browser, without downloading them. – GYTO Feb 02 '19 at 22:45
  • If you mount the presign endpoint to `/s3/params`, and let Uppy's `AwsS3` plugin do the requests for you by setting `serverUrl: '/'`, then Uppy will automatically send `filename` and `type` query parameters in the presign request (`type` will contain the file's MIME type, e.g. `image/jpeg`). That means you can configure the `presign_endpoint` [like this](https://github.com/shrinerb/shrine/blob/b1e5d8362a8204e287d1609b580ccb90f8f009d6/demo/config/shrine.rb#L45-L55), which will set the correct content-type for the S3 object. – Janko Feb 03 '19 at 09:23