Skip to content

Commit

Permalink
Add expires_at option to ActiveStorage::Blob#signed_id
Browse files Browse the repository at this point in the history
  • Loading branch information
aki77 authored and rafaelfranca committed Sep 26, 2023
1 parent 5fcc610 commit 1f9fbbe
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 10 deletions.
9 changes: 9 additions & 0 deletions activestorage/CHANGELOG.md
@@ -1,3 +1,12 @@
* Add `expires_at` option to `ActiveStorage::Blob#signed_id`.

```ruby
rails_blob_path(user.avatar, disposition: "attachment", expires_at: 30.minutes.from_now)
<%= image_tag rails_blob_path(user.avatar.variant(resize: "100x100"), expires_at: 30.minutes.from_now) %>
```
*Aki*
* Allow attaching File and Pathname when assigning attributes, e.g.
```ruby
Expand Down
2 changes: 1 addition & 1 deletion activestorage/app/models/active_storage/blob.rb
Expand Up @@ -158,7 +158,7 @@ def compose(blobs, filename:, content_type: nil, metadata: nil)
end

# Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
def signed_id(purpose: :blob_id, expires_in: nil)
def signed_id(purpose: :blob_id, expires_in: nil, expires_at: nil)
super
end

Expand Down
10 changes: 6 additions & 4 deletions activestorage/config/routes.rb
Expand Up @@ -32,16 +32,17 @@

direct :rails_storage_proxy do |model, options|
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
expires_at = options.delete(:expires_at)

if model.respond_to?(:signed_id)
route_for(
:rails_service_blob_proxy,
model.signed_id(expires_in: expires_in),
model.signed_id(expires_in: expires_in, expires_at: expires_at),
model.filename,
options
)
else
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
signed_blob_id = model.blob.signed_id(expires_in: expires_in, expires_at: expires_at)
variation_key = model.variation.key
filename = model.blob.filename

Expand All @@ -57,16 +58,17 @@

direct :rails_storage_redirect do |model, options|
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
expires_at = options.delete(:expires_at)

if model.respond_to?(:signed_id)
route_for(
:rails_service_blob,
model.signed_id(expires_in: expires_in),
model.signed_id(expires_in: expires_in, expires_at: expires_at),
model.filename,
options
)
else
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
signed_blob_id = model.blob.signed_id(expires_in: expires_in, expires_at: expires_at)
variation_key = model.variation.key
filename = model.blob.filename

Expand Down
16 changes: 14 additions & 2 deletions activestorage/test/controllers/blobs/proxy_controller_test.rb
Expand Up @@ -32,18 +32,30 @@ class ActiveStorage::Blobs::ProxyControllerTest < ActionDispatch::IntegrationTes
assert_match(/^attachment; /, response.headers["Content-Disposition"])
end

test "signed ID within expiration date" do
test "signed ID within expiration duration" do
get rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg"), expires_in: 1.minute)
assert_response :success
end

test "Expired signed ID" do
test "Expired signed ID within expiration duration" do
url = rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg"), expires_in: 1.minute)
travel 2.minutes
get url
assert_response :not_found
end

test "signed ID within expiration time" do
get rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg"), expires_at: 1.minute.from_now)
assert_response :success
end

test "Expired signed ID within expiration time" do
url = rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg"), expires_at: 1.minute.from_now)
travel 2.minutes
get url
assert_response :not_found
end

test "single Byte Range" do
get rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg")), headers: { "Range" => "bytes=5-9" }
assert_response :partial_content
Expand Down
16 changes: 14 additions & 2 deletions activestorage/test/controllers/blobs/redirect_controller_test.rb
Expand Up @@ -19,17 +19,29 @@ class ActiveStorage::Blobs::RedirectControllerTest < ActionDispatch::Integration
assert_equal "max-age=300, private", response.headers["Cache-Control"]
end

test "signed ID within expiration date" do
test "signed ID within expiration duration" do
get rails_storage_redirect_url(@blob, expires_in: 1.minute)
assert_redirected_to(/racecar\.jpg/)
end

test "Expired signed ID" do
test "Expired signed ID within expiration duration" do
url = rails_storage_redirect_url(@blob, expires_in: 1.minute)
travel 2.minutes
get url
assert_response :not_found
end

test "signed ID within expiration time" do
get rails_storage_redirect_url(@blob, expires_at: 1.minute.from_now)
assert_redirected_to(/racecar\.jpg/)
end

test "Expired signed ID within expiration time" do
url = rails_storage_redirect_url(@blob, expires_at: 1.minute.from_now)
travel 2.minutes
get url
assert_response :not_found
end
end

class ActiveStorage::Blobs::ExpiringRedirectControllerTest < ActionDispatch::IntegrationTest
Expand Down
19 changes: 18 additions & 1 deletion activestorage/test/models/attachment_test.rb
Expand Up @@ -108,7 +108,7 @@ class ActiveStorage::AttachmentTest < ActiveSupport::TestCase
assert_equal blob, ActiveStorage::Blob.find_signed!(signed_id)
end

test "fail to find blob within expiration date" do
test "fail to find blob within expiration duration" do
blob = create_blob
@user.avatar.attach(blob)

Expand All @@ -117,6 +117,23 @@ class ActiveStorage::AttachmentTest < ActiveSupport::TestCase
assert_nil ActiveStorage::Blob.find_signed(signed_id)
end

test "getting a signed blob ID from an attachment with a expires_at" do
blob = create_blob
@user.avatar.attach(blob)

signed_id = @user.avatar.signed_id(expires_at: 1.minute.from_now)
assert_equal blob, ActiveStorage::Blob.find_signed!(signed_id)
end

test "fail to find blob within expiration time" do
blob = create_blob
@user.avatar.attach(blob)

signed_id = @user.avatar.signed_id(expires_at: 1.minute.from_now)
travel 2.minutes
assert_nil ActiveStorage::Blob.find_signed(signed_id)
end

test "signed blob ID backwards compatibility" do
blob = create_blob
@user.avatar.attach(blob)
Expand Down

0 comments on commit 1f9fbbe

Please sign in to comment.