-
Notifications
You must be signed in to change notification settings - Fork 97
S3 Server Setup
The server side is very straightforward. /sign
takes a filename, type, and size
then returns some JSON.
NOTE: if the client side uploader uploads multiple files, you'll have to sign each file separately.
/sign?name=5854315625_2275398b48_o.jpg&size=1269959&type=image%2Fjpeg
or the more idiomatic:
GET /sign
{
"name": "5854315625_2275398b48_o.jpg",
"size": 1269959,
"type": "image/jpeg"
}
{
"Expires": "Wed, 03 Oct 2018 09:24:24 GMT",
"key": "5854315625_2275398b48_o.jpg",
"success_action_status": "201",
"Content-Disposition": "attachment",
"Content-Type": "image/png",
"Cache-Control": "max-age=630720000, public",
"policy": "eyJleHBpcmF0aW9uIjoiMjAxOC0xMC0wM1QwMDoyNDoyNFoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJ0ZXN0LXVwbG9hZHMtY2FyZWVyanNtIn0seyJFeHBpcmVzIjoiV2VkLCAwMyBPY3QgMjAxOCAwOToyNDoyNCBHTVQifSx7ImtleSI6InN0YWdpbmcvdXNlci1yZWdpc3RyYXRpb25zLzMxMzUvZG9jdW1lbnRzL1NjcmVlbi1TaG90LTIwMTgtMTAtMDItYXQtMy41MS4xNS1QTS5wbmcifSx7InN1Y2Nlc3NfYWN0aW9uX3N0YXR1cyI6IjIwMSJ9LHsiQ29udGVudC1EaXNwb3NpdGlvbiI6ImF0dGFjaG1lbnQifSx7IkNvbnRlbnQtVHlwZSI6ImltYWdlL3BuZyJ9LHsiQ2FjaGUtQ29udHJvbCI6Im1heC1hZ2U9NjMwNzIwMDAwLCBwdWJsaWMifSx7IngtYW16LWNyZWRlbnRpYWwiOiJBS0lBSlFNSllGRTZJSFYzTFVDUS8yMDE4MTAwMi9jYS1jZW50cmFsLTEvczMvYXdzNF9yZXF1ZXN0In0seyJ4LWFtei1hbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In0seyJ4LWFtei1kYXRlIjoiMjAxODEwMDJUMjMyNDI0WiJ9XX0=",
"x-amz-credential": "AKIAJFFJYFE6IHV3LUCQ/87181002/ca-central-1/s3/aws4_request",
"x-amz-algorithm": "AWS4-HMAC-SHA256",
"x-amz-date": "20181002T232424Z",
"x-amz-signature": "e6414e6dbda4988wwfa7ada51febcb777dq05eebf89ca3c0352233d2446fb",
"bucket": "test-uploads"
}
These values tell the client-side where to post to, in order to make a successful request to S3.
NOTE: If you are using Pundit library to handle authorization in your app, rename method policy()
listed below to something else (e. g. s3_policy
), because policy()
is already defined in Pundit and it will cause authorization error (making policy()
private doesn't help either).
def sign
@expires = 10.hours.from_now.utc
render json: signature, status: :ok
end
Signature uses the AWS Ruby SDK to generate a pre-signed post request. Follow instructions here for setup and config: https://github.com/aws/aws-sdk-ruby.
def signature
client = Aws::S3::Client.new
resource = Aws::S3::Resource.new(client: client)
bucket = resource.bucket ENV['AWS_BUCKET_NAME']
presigned_post = bucket.presigned_post(
acl: 'public-read',
expires: @expires,
key: upload_path,
policy: policy,
success_action_status: '201',
content_disposition: 'attachment', # indicate this should be downloaded no opened in the browser
content_type: params[:type],
cache_control: 'max-age=630720000, public'
)
fields = presigned_post.fields
fields['bucket'] = ENV['AWS_BUCKET_NAME']
fields
end
Policy is base64-encoded JSON that is used by S3 to validate that the file is the same as the one attached.
def policy(options = {})
Base64.strict_encode64(
{
expiration: @expires,
conditions: [
{ bucket: 'sandbox' },
{ acl: 'public-read' },
{ expires: @expires },
{ success_action_status: '201' },
[ 'starts-with', '$key', '' ],
[ 'starts-with', '$Content-Type', '' ],
[ 'starts-with', '$Cache-Control', '' ],
[ 'content-length-range', 0, 524288000 ]
]
}.to_json
)
end