[Feature proposal] Allow ActionText::Content attachments to use configurable service

Currently, ActionText attachments use the default storage service.

If we want one ActionText has_rich_text on a model to use a public service, the whole app needs to be public first, rather than private first, which would be my preference.

Allowing a per case configuration as is possible with native-to-model attachables would allow the developer to choose their preference, and importantly allow that data-privacy-first approach.

I guess it should fall back to the default configured service if none is specified.

I would propose to use the same service parameter as is used on has_x_attached and pass it through to has_many_attached :embeds at https://github.com/rails/rails/blob/914caca2d31bd753f47f9168f2a375921d9e91cc/actiontext/app/models/action_text/rich_text.rb#L15

Not sure of the best way to go about passing that through though, so it’s configurable per rich text, rather than globally.

It’s that something that would be useful to others?

1 Like

This would definitely be useful. Like for Active Storage attachments where one can declare:

has_one_attached :cover_image, service: :public

for Action Text rich texts we could have:

has_rich_text :content, service: :public

@simmerz have you found any solution to make Action Text attachments use a non-default Active Storage service?

Posted as an issue here so it can be PR’d Allow configuration of embeddables storage service for ActionText via `has_rich_text` · Issue #45405 · rails/rails · GitHub

Thank you @simmerz. I came back to this topic after discovering that our Action Text embedded images stopped working, however images attached directly with Active Storage still work. One difference is that the former use the default service which is a private S3 storage, whereas the latter service is a public S3 storage. We recently rotated the credentials to access AWS, I wonder if it may be the cause of images breaking? I also note that if the secret_key_base changes, attachments (either direct or embedded in Action Text) will also break.

All of these reasons make me uncomfortable using a non-public storage for Action Text embeds, and before proceeding to fixing them I would like the embedded images to use the public storage service instead of the default. I’ll see if we can monkey-patch our app to do so.

Then in a second step we’d need to carefully investigate why the images broke and find better strategies for credential rotation, and document these.

credentials for accessing AWS shouldn’t matter at all. We’ve successfully transitioned between S3 and DigitalOcean Spaces (S3 compatible) which has meant different credentials. You’re right though that if your secret key changes then your attachments will no longer work as it provides the hashing key for the attachment.

Was the rotation the only change or did you also change the secret key base and perhaps did you change the configuration of the buckets in storage.yml which could also have an impact?

We’ve successfully transitioned between S3 and DigitalOcean Spaces (S3 compatible)

Do you also have embedded Action Text images using a private S3 service (not public: true)?

To the best of my knowledge, the secret key base hadn’t been changed, nor storage.yml. This is according to the git log and the Heroku activity history. However I discovered that the secret key base was ending with a spurious newline character, I wonder if this started to be handled differently when updating Rails (e.g. some trim added somewhere). Considering images are broken anyway, I will probably rotate the secret key base (and remove that trailing newline) and then repair the situation.

Here is one temporary solution that seems to work to ensure ActionText::RichText embeds are stored in a specific service:

EDIT: after a quick check the code below doesn’t seem to change the service at all, merely changing the service_name saved in the DB.

# config/initializers/action_text.rb
module ActionText
  Rails.application.reloader.to_prepare do
    unless RichText.included_modules.include?(RichTextMonkeyPatch)
      RichText.include(RichTextMonkeyPatch)
    end
  end
end

# lib/action_text/rich_text_monkey_patch.rb
module ActionText
  module RichTextMonkeyPatch
    extend ActiveSupport::Concern
    included do
      before_save do
        embeds&.each { |e| e.service_name = :public }
      end
    end
  end
end
1 Like

The previous comment didn’t work, but this hack does seem to work (use a specific service for all direct-uploaded attachments):

# config/initializers/active_storage.rb

Rails.application.reloader.to_prepare do
  ActiveStorage::DirectUploadsController.class_eval do
    def create # override previous definition
      blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args.merge(service_name: "public"))
      render json: direct_upload_json(blob)
    end
  end
end