My Koryto
My Koryto

Reputation: 667

Receiving a 'Bad Request (400)' on PUT request from AWS S3 when a user is trying to upload a song to my rails 6 app

I'm upgrading my rails 5 app to rails 6. During this update, I updated my aws-sdk from v1 to v3. I have included the aws-sdk-s3 gem in my gemfile as mentioned in the documentation.

One of the main features of my app is allowing users to upload songs. When I've tried to manually test this feature I've encountered the following error:

PUT https://bucketname.s3.amazonaws.com/ 400 (Bad Request)

Here is the attached XML:

<Error>
   <Code>AccessDenied</Code>
   <Message>Access Denied</Message>
   <RequestId>request id code</RequestId>  
   <HostId>host id code</HostId>
</Error>

I was trying to check how to solve it in this S3 documentation but there I found another confusing detail. While my console returned a 400 status code, 'AccessDenied' is actually written as 403 status code in the documentation.

I'm attaching the Upload model. The commented out lines are the original lines of code I had. I changed it according to what was mentioned on the S3 documentation.

class Upload
  attr_reader :filename
  # New lines of code: ####################################################
  require 'aws-sdk-s3'

  Aws.config.update({
    region: 'us-east-1',
    credentials: Aws::Credentials.new(Rails.application.credentials.dig(:aws, :access_key_id), Rails.application.credentials.dig(:aws, :secret_access_key)),
  })
  #########################################################################
  def initialize(filename)
    @filename = filename
  end

  def url
    #@url ||= bucket_file.url_for(:write, content_type: content_type, acl: :public_read).to_s
    @url ||= bucket_file.url().to_s
  end

  def content_type
    @content_type ||= MIME::Types.type_for(filename).first.content_type
  end

  def to_json(*args)
    { url: url, content_type: content_type }.to_json
  end

  private
  
  def bucket_file
    #@bucket_file ||= bucket.object("uploads/#{SecureRandom.uuid}/#{filename}")
    @bucket_file ||= bucket.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'public_read', content_type: content_type)
  end

  def bucket
    #@bucket ||= AWS::S3.new.buckets(ENV['S3_BUCKET_NAME'])  
    @bucket ||= Aws::S3::Resource.new(region: 'us-east-1').bucket(Rails.application.credentials.dig(:aws, :bucket))  
  end

end

It is important to mention that I made sure that the credentials are accessible and valid.

Upvotes: 1

Views: 1415

Answers (2)

Ygd0
Ygd0

Reputation: 23

Dealt with a similar problem as you described.

Note that despite the fact that you fixed up this problem, you will need to make further adjustment before moving to production environment as production env enforces a more thight csrf token regulations.

There is a further explanation here: https://docs.aws.amazon.com/AmazonS3/latest/API/archive-RESTObjectPUT.html

Upvotes: 1

My Koryto
My Koryto

Reputation: 667

The url_for function is deprecated and therefore had to be change to public_url.

This is a working version of my original code:

class Upload
  attr_reader :filename
  require 'aws-sdk-s3'

  Aws.config.update({
    region: 'us-east-1',
    credentials: Aws::Credentials.new(Rails.application.credentials.dig(:aws, :access_key_id), Rails.application.credentials.dig(:aws, :secret_access_key)),
  })

  def initialize(filename)
    @filename = filename
  end

  def url
    @url ||= bucket_file.public_url().to_s
  end

  def content_type
    @content_type ||= MIME::Types.type_for(filename).first.content_type
  end

  def to_json(*args)
    { url: url, content_type: content_type }.to_json
  end

  private
  
  def bucket_file
    @bucket_file ||= bucket.object("uploads/#{SecureRandom.uuid}/${filename}")
  end

  def bucket 
    @bucket ||= Aws::S3::Resource.new(region: 'us-east-1').bucket(Rails.application.credentials.dig(:aws, :bucket))  
  end

end

Upvotes: 3

Related Questions