Skip to content

Commit 1e1ca11

Browse files
authored
Merge pull request rails#42410 from aki77/activestorage-expiring-url
Add support for ActiveStorage expiring URLs
2 parents 26c00cb + 209a79a commit 1e1ca11

File tree

7 files changed

+96
-4
lines changed

7 files changed

+96
-4
lines changed

activestorage/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
2+
* Add support for ActiveStorage expiring URLs.
3+
4+
```ruby
5+
rails_blob_path(user.avatar, disposition: "attachment", expires_in: 30.minutes)
6+
7+
<%= image_tag rails_blob_path(user.avatar.variant(resize: "100x100"), expires_in: 30.minutes) %>
8+
```
9+
10+
If you want to set default expiration time for ActiveStorage URLs throughout your application, set `config.active_storage.urls_expire_in`.
11+
12+
*aki77*
13+
114
* Allow to purge an attachment when record is not persisted for `has_many_attached`
215
316
*Jacopo Beschi*

activestorage/config/routes.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@
3131
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(ActiveStorage.resolve_model_to_route, attachment.blob, options) }
3232

3333
direct :rails_storage_proxy do |model, options|
34+
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
35+
3436
if model.respond_to?(:signed_id)
3537
route_for(
3638
:rails_service_blob_proxy,
37-
model.signed_id,
39+
model.signed_id(expires_in: expires_in),
3840
model.filename,
3941
options
4042
)
4143
else
42-
signed_blob_id = model.blob.signed_id
44+
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
4345
variation_key = model.variation.key
4446
filename = model.blob.filename
4547

@@ -54,15 +56,17 @@
5456
end
5557

5658
direct :rails_storage_redirect do |model, options|
59+
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
60+
5761
if model.respond_to?(:signed_id)
5862
route_for(
5963
:rails_service_blob,
60-
model.signed_id,
64+
model.signed_id(expires_in: expires_in),
6165
model.filename,
6266
options
6367
)
6468
else
65-
signed_blob_id = model.blob.signed_id
69+
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
6670
variation_key = model.variation.key
6771
filename = model.blob.filename
6872

activestorage/lib/active_storage.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ module ActiveStorage
6060
mattr_accessor :content_types_allowed_inline, default: []
6161

6262
mattr_accessor :service_urls_expire_in, default: 5.minutes
63+
mattr_accessor :urls_expire_in
6364

6465
mattr_accessor :routes_prefix, default: "/rails/active_storage"
6566
mattr_accessor :draw_routes, default: true

activestorage/lib/active_storage/engine.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class Engine < Rails::Engine # :nodoc:
9191
ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
9292
ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
9393
ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
94+
ActiveStorage.urls_expire_in = app.config.active_storage.urls_expire_in
9495
ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
9596
ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
9697

activestorage/test/controllers/blobs/proxy_controller_test.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,39 @@ class ActiveStorage::Blobs::ProxyControllerTest < ActionDispatch::IntegrationTes
2424
get rails_storage_proxy_url(create_blob(content_type: "application/zip"))
2525
assert_match(/^attachment; /, response.headers["Content-Disposition"])
2626
end
27+
28+
test "signed ID within expiration date" do
29+
get rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg"), expires_in: 1.minute)
30+
assert_response :success
31+
end
32+
33+
test "Expired signed ID" do
34+
url = rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg"), expires_in: 1.minute)
35+
travel 2.minutes
36+
get url
37+
assert_response :not_found
38+
end
39+
end
40+
41+
class ActiveStorage::Blobs::ExpiringProxyControllerTest < ActionDispatch::IntegrationTest
42+
setup do
43+
@old_urls_expire_in = ActiveStorage.urls_expire_in
44+
ActiveStorage.urls_expire_in = 1.minutes
45+
end
46+
47+
teardown do
48+
ActiveStorage.urls_expire_in = @old_urls_expire_in
49+
end
50+
51+
test "signed ID within expiration date" do
52+
get rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg"))
53+
assert_response :success
54+
end
55+
56+
test "Expired signed ID" do
57+
url = rails_storage_proxy_url(create_file_blob(filename: "racecar.jpg"))
58+
travel 2.minutes
59+
get url
60+
assert_response :not_found
61+
end
2762
end

activestorage/test/controllers/blobs/redirect_controller_test.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,40 @@ class ActiveStorage::Blobs::RedirectControllerTest < ActionDispatch::Integration
1818
assert_redirected_to(/racecar\.jpg/)
1919
assert_equal "max-age=300, private", response.headers["Cache-Control"]
2020
end
21+
22+
test "signed ID within expiration date" do
23+
get rails_storage_redirect_url(@blob, expires_in: 1.minute)
24+
assert_redirected_to(/racecar\.jpg/)
25+
end
26+
27+
test "Expired signed ID" do
28+
url = rails_storage_redirect_url(@blob, expires_in: 1.minute)
29+
travel 2.minutes
30+
get url
31+
assert_response :not_found
32+
end
33+
end
34+
35+
class ActiveStorage::Blobs::ExpiringRedirectControllerTest < ActionDispatch::IntegrationTest
36+
setup do
37+
@blob = create_file_blob filename: "racecar.jpg"
38+
@old_urls_expire_in = ActiveStorage.urls_expire_in
39+
ActiveStorage.urls_expire_in = 1.minutes
40+
end
41+
42+
teardown do
43+
ActiveStorage.urls_expire_in = @old_urls_expire_in
44+
end
45+
46+
test "signed ID within expiration date" do
47+
get rails_storage_redirect_url(@blob)
48+
assert_redirected_to(/racecar\.jpg/)
49+
end
50+
51+
test "Expired signed ID" do
52+
url = rails_storage_redirect_url(@blob)
53+
travel 2.minutes
54+
get url
55+
assert_response :not_found
56+
end
2157
end

guides/source/configuring.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,8 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla
10411041

10421042
The default is 5 minutes.
10431043

1044+
* `config.active_storage.urls_expire_in` determines the default expiry of URLs in the Rails application generated by Active Storage. The default is nil.
1045+
10441046
* `config.active_storage.routes_prefix` can be used to set the route prefix for the routes served by Active Storage. Accepts a string that will be prepended to the generated routes.
10451047

10461048
```ruby

0 commit comments

Comments
 (0)