Preview for Amazon S3 Client-Side Encrypted Active Storage files
In our project, we are using Active Storage as our upload solution, and Amazon S3 for Storage service. Recently because of a requirement in the project, we decided to encrypt files before uploading to S3 and decrypting them after downloading (client-side encryption). Amazon S3 Gem meets this requirement and we can find the documentation through this link.
And the following greate article shows how we can implement it in a transparent way:
This blog post has described what you need, BUT if you want to have a preview for the uploaded files, so you are the one who should read this article.
Preview for Uploaded Files (image, PDF, movie, …)
Unfortunately, Active Storage does not completely support preview for client-side encrypted files, if we use the default previewer path which is created by Active Storage itself, it will give us a URL that refers directly to the encrypted thumbnail image on the S3, and it’s not previewable by an HTML image tag.
NOTE: We should not forget that we need a transparent way that works with any configuration of Active Storage. For example, config.active_storage.service = :local
should work properly in your development environment while config.active_storage.service = :amazon
works on the production server.
Assume that we have an Attachment
model that is associated with on document:
class Attachment < ApplicationRecord
has_one_attached :document
end
Now, we will go through the following steps:
- The blog post in the previous section has missed an important line of code that is used in the Active Storage Preview, and we need to change the download method like the following code:
def download(key, &block)
binary_data = instrument :download, key: key do
encryption_client.get_object(
bucket: bucket.name,
key: key
).body.string.force_encoding(Encoding::BINARY)
end yield binary_data if block binary_data
end
The bold line is the missing line. If you want to know why we need to add this, check out this line of the Active Storage source code.
2. Then we are going to create a service object in order to process and download preview blob:
module Attachments
class Preview
def initialize(attachment)
@attachment = attachment
end def call
document_preview =
@attachment.document
.blob
.representation(resize: '200x200')
.processed if document_preview.is_a?(ActiveStorage::Variant)
variant = document_preview
else
variant = ActiveStorage::Variant.new(
document_preview.image,
document_preview.variation
)
end
variant_preview = variant.processed
ActiveStorage::Blob.service.download(variant_preview.key)
end
end
end
3. Now we need to create a controller to serve preview file:
class AttachmentsController < ApplicationController
def preview
preview_data = ::Attachments::Preview.new(attachment).call
send_data(
preview_data,
filename: attachment.filename,
type: 'image/png'
)
end private def attachment
@attachment ||= ::Attachment.find params[:id]
end
end
4. And that’s it, now you just need to use the proper route to implemented action in your <img />
tags:
<img src=<%= preview_attachment_path(attachment) %> />
Wrapping Up
If you want to use the Preview feature of the Active Storage you need to know that it generates a thumbnail beside the original file, but when you are using client-side encryption, you can not directly refer to the thumbnail file (because it’s encrypted too). In this case, you need to decrypt the thumbnail file, before previewing. We have explained how you can meet this requirement by creating a custom controller and use it in your image tag instead of the URL which is generated by the Active Storage.