stavro / arc_ecto

An integration with Arc and Ecto.
255 stars 149 forks source link

Uploading to S3 from a remote URL returns a SignatureDoesNotMatch error #118

Open MarbilleJuntado opened 5 years ago

MarbilleJuntado commented 5 years ago

In arc_ecto, why do I get a SignatureDoesNotMatch error when uploading a file from an external URL to my S3 bucket (e.g. File.upload_file(%{"attachment" => "http://sample.com/file.pdf"}) ? I already configured my File model's changeset to allow paths when casting attachments.

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, @required_attrs ++ @optional_attrs)
  |> cast_attachments(params, [:attachment], allow_paths: true)
  |> validate_required(@required_attrs)
end

If attachment is set as a Plug.Upload, it works:

File.upload_file(%{      
  "attachment" => %Plug.Upload{
    content_type: "application/pdf",
    filename: "sample.pdf",
    path: "/tmp/plug-1565/multipart-1565172398-694450657366843-1"
  }
})

but not if it's a URL: File.upload_file(%{"attachment" => "http://sample.com/file.pdf"}). Here's how I set up my uploader:

defmodule MyApp.Uploaders.Attachment do
  use Arc.Definition
  use Arc.Ecto.Definition
  alias Phoenix.Naming

  @acl :private

  # Whitelist file extensions:
  def validate({file, _}) do
    ext =
      file
      |> get_file_name
      |> Path.extname()
      |> String.downcase()

    Enum.member?(~w(.jpg .jpeg .gif .png .pdf .docx .doc .odt), ext)
  end

  # Override the storage directory:
  def storage_dir(_version, {_file, scope}) do
    base_path = "uploads/attachments/#{scope_dir(scope)}/#{scope.id}"

    if __storage() == Arc.Storage.Local do
      "priv/static/" <> base_path
    else
      base_path
    end
  end

  # Specify custom headers for s3 objects
  # Available options are [:cache_control, :content_disposition,
  #    :content_encoding, :content_length, :content_type,
  #    :expect, :expires, :storage_class, :website_redirect_location]
  #
  def s3_object_headers(_version, {file, _scope}) do
    filename = get_file_name(file)
    [content_type: MIME.from_path(filename)]
  end

  defp scope_dir(scope) do
    scope.__struct__
    |> Atom.to_string()
    |> String.split(".")
    |> List.last()
    |> Naming.underscore()
  end

  defp get_file_name(%{file_name: file_name}), do: file_name
  defp get_file_name(%{filename: filename}), do: filename
end