imprint-image 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1844ab246c97a048a3ffd9a5f97ef4194b96c44ced2986f2c7ec7de1c96f872c
4
- data.tar.gz: dae5536d6035f67546136f12ffe1fc52d602bdffc8dd5eeba120fec2313c4cd7
3
+ metadata.gz: 74fc0a692977e7e71b4b60c67b5a4df6f031cf3a1ab300d7b101f64eace13870
4
+ data.tar.gz: 610281c24277f365da0c123fae869a1bcb58ccd1c26e3231e77bb6e684e4104a
5
5
  SHA512:
6
- metadata.gz: ba77ada64c1f0fb91755aa766dbbdb330eee4ec7c630fedcd467a858569f751ac546c5a823c9857e4ae67e737a911bf686a2fe2a26b4a25e427450766ed3e3d4
7
- data.tar.gz: 8cf8d948c812075b068479e25737a0d43999831f2dea87a018d6d62fe8c1049d96bb27076f88173ed91d4bd4ae1939669d768402ee9e8ce8e5c215c14014d909
6
+ metadata.gz: 7b03cabfb248058c3d74cc786eb13bc7db0e9047e02d410ae5e61a1aef532fb294fd4a7c2c3056d39279014fe93160d3fdc84bc13e04b746710d68fa38c42c7e
7
+ data.tar.gz: 258bd180f84820402c8427f478c06032a0d9ea16282c1408a2a9f7611b1e27e7a17bd8fc0188dae4a16f95dcf4844cfb3e39c442c96d635cb7d6328949332051
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## Changelog.md
2
+
3
+ ## 0.1.1 - 2026-01-21
4
+
5
+ - Optional Rails integration via Railtie
6
+ - Rails view helper for rendering signed images
7
+ - Documentation for Rails usage
8
+
9
+ ## [0.1.0] - 2026-01-20
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Giménez Silva Germán Alberto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # Imprint Image
2
+
3
+ **Signed, expiring image watermark rendering for Ruby**
4
+
5
+ `imprint-image` is a Ruby library for generating **signed, time-limited image renders** with **dynamic text watermarks**.
6
+ It allows you to securely distribute images using **expiring tokens**, preventing unauthorized reuse, hotlinking, or permanent access.
7
+
8
+ The library is **framework-agnostic** and works as pure Ruby, with **optional Rails integration**.
9
+
10
+ Image rendering is powered by the **GD graphics library**.
11
+
12
+ ---
13
+
14
+ ## Features
15
+
16
+ - 🔐 Signed tokens with expiration
17
+ - 🖼 Dynamic text watermarks rendered on the fly
18
+ - ⏱ Time-limited image access
19
+ - 🚫 Prevents hotlinking and unauthorized reuse
20
+ - 🧩 Pure Ruby core
21
+ - 🚆 Optional Rails integration (no forced dependency)
22
+
23
+ ---
24
+
25
+ ## Installation
26
+
27
+ ### RubyGems
28
+
29
+ ```bash
30
+ gem install imprint-image
31
+ ```
32
+
33
+ or in your `Gemfile`:
34
+
35
+ ```ruby
36
+ gem 'imprint-image'
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Requirements
42
+
43
+ - Ruby ≥ 3.3
44
+ - GD graphics library (`libgd`)
45
+ - The `ruby-libgd` gem (installed automatically)
46
+
47
+ On Debian / Ubuntu:
48
+
49
+ ```bash
50
+ sudo apt install libgd-dev
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Basic Usage (Pure Ruby)
56
+
57
+ ### Generate a signed token
58
+
59
+ ```ruby
60
+ token = Imprint.sign(
61
+ source: '/path/to/image.png',
62
+ watermark: 'CONFIDENTIAL',
63
+ expires_in: 60
64
+ )
65
+ ```
66
+
67
+ ---
68
+
69
+ ### Render an image from a token
70
+
71
+ ```ruby
72
+ output_path = Imprint.render_from_token(token)
73
+
74
+ if output_path
75
+ puts "Rendered image at: #{output_path}"
76
+ else
77
+ puts "Token expired or invalid"
78
+ end
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Rails Integration (Optional)
84
+
85
+ Imprint does **not** force routes or controllers.
86
+
87
+ ### Example Route
88
+
89
+ ```ruby
90
+ get '/imprint/:token', to: 'imprint#show'
91
+ ```
92
+
93
+ ---
94
+
95
+ ### Example Controller
96
+
97
+ ```ruby
98
+ class ImprintController < ApplicationController
99
+ def show
100
+ path = Imprint.render_from_token(params[:token])
101
+ return head :not_found unless path
102
+ send_file path, type: 'image/png', disposition: 'inline'
103
+ end
104
+ end
105
+ ```
106
+
107
+ ---
108
+
109
+ ### Example View
110
+
111
+ ```erb
112
+ <img src="/imprint/<%= token.split('/').last %>" />
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Rails View Helper
118
+
119
+ ```erb
120
+ <%= imprint_image_tag(token, class: "watermarked") %>
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Security Model
126
+
127
+ - Tokens are signed and tamper-proof
128
+ - Tokens include an expiration timestamp
129
+ - Expired tokens cannot be rendered
130
+
131
+ ---
132
+
133
+ ## Temporary Files
134
+
135
+ Rendered images are written to `Dir.tmpdir`.
136
+ You are responsible for cleaning them if needed.
137
+
138
+ ---
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ bundle install
144
+ bundle exec rake
145
+ ```
146
+
147
+ ---
148
+
149
+ ## License
150
+
151
+ MIT License.
152
+
153
+ ---
154
+
155
+ ## Author
156
+
157
+ Germán Giménez Silva
158
+ https://github.com/ggerman
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Imprint::Engine.routes.draw do
4
+ get 'render/:token', to: 'renders#show'
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Imprint
4
+ class RendersController < ActionController::Base
5
+ def show
6
+ path = Imprint.render_from_token(params[:token])
7
+
8
+ return head :not_found unless path && File.exist?(path)
9
+
10
+ send_file(
11
+ path,
12
+ type: 'image/png',
13
+ disposition: 'inline',
14
+ cache_control: 'no-store'
15
+ )
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ module Imprint
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace Imprint
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Imprint
4
+ module Rails
5
+ module Helper
6
+ def imprint_image_tag(token, **)
7
+ image_tag(
8
+ imprint_render_path(token: token),
9
+ **
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require 'imprint-image'
3
+ require 'rails/railtie'
4
+
5
+ module Imprint
6
+ class Railtie < Rails::Railtie
7
+ initializer 'imprint.helpers' do
8
+ ActiveSupport.on_load(:action_view) do
9
+ include Imprint::Rails::Helper
10
+ end
11
+ end
12
+
13
+ initializer 'imprint.configure' do
14
+ # Punto de extensión futuro
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gd'
4
+ require 'securerandom'
5
+ require 'tmpdir'
6
+
7
+ module Imprint
8
+ class Renderer
9
+ DEFAULT_FONT_SIZE = 24
10
+
11
+ def self.render(source:, watermark:)
12
+ raise ArgumentError, 'source image not found' unless File.exist?(source)
13
+
14
+ image = GD::Image.open(source)
15
+
16
+ apply_watermark(image, watermark)
17
+
18
+ output = File.join(
19
+ Dir.tmpdir,
20
+ "imprint-#{SecureRandom.hex(8)}.png"
21
+ )
22
+
23
+ image.save(output)
24
+ output
25
+ end
26
+
27
+ def self.apply_watermark(image, text)
28
+ color = GD::Color.rgba(0, 116, 139, 225)
29
+
30
+ raise "Font missing" unless File.exist?(default_font)
31
+
32
+ image.text(
33
+ text,
34
+ x: 20,
35
+ y: image.height - 20,
36
+ size: DEFAULT_FONT_SIZE,
37
+ color: color,
38
+ font: default_font
39
+ )
40
+ end
41
+
42
+ def self.default_font
43
+ @default_font ||= begin
44
+ font = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'
45
+ raise 'Font not found' unless File.exist?(font)
46
+
47
+ font
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "json"
5
+ require "base64"
6
+
7
+ module Imprint
8
+ class Signer
9
+ ALGORITHM = "HS256"
10
+
11
+ def self.sign(payload, secret:)
12
+ data = Base64.urlsafe_encode64(payload.to_json)
13
+ signature = OpenSSL::HMAC.hexdigest(
14
+ OpenSSL::Digest.new("sha256"),
15
+ secret,
16
+ data
17
+ )
18
+
19
+ "#{data}.#{signature}"
20
+ end
21
+
22
+ def self.verify(token, secret:)
23
+ data, signature = token.split(".", 2)
24
+ return nil unless data && signature
25
+
26
+ expected = OpenSSL::HMAC.hexdigest(
27
+ OpenSSL::Digest.new("sha256"),
28
+ secret,
29
+ data
30
+ )
31
+
32
+ return nil unless secure_compare(expected, signature)
33
+
34
+ JSON.parse(Base64.urlsafe_decode64(data))
35
+ rescue JSON::ParserError, ArgumentError
36
+ nil
37
+ end
38
+
39
+ def self.secure_compare(a, b)
40
+ return false unless a.bytesize == b.bytesize
41
+
42
+ OpenSSL.fixed_length_secure_compare(a, b)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Imprint
4
+ VERSION = "0.1.6"
5
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'imprint/version'
4
+ require 'imprint/signer'
5
+ require 'imprint/renderer'
6
+ require 'imprint/engine' if defined?(Rails)
7
+ require 'imprint/railtie' if defined?(Rails)
8
+
9
+ module Imprint
10
+ def self.sign(source:, watermark:, expires_in:)
11
+ payload = {
12
+ source: source,
13
+ watermark: watermark,
14
+ exp: Time.now.to_i + expires_in.to_i
15
+ }
16
+
17
+ token = Signer.sign(payload, secret: secret_key)
18
+ "/imprint/render/#{token}"
19
+ end
20
+
21
+ def self.verify(token)
22
+ payload = Signer.verify(token, secret: secret_key)
23
+ return nil unless payload.is_a?(Hash)
24
+
25
+ return nil if payload['exp'].to_i < Time.now.to_i
26
+
27
+ payload
28
+ end
29
+
30
+ def self.render_from_token(token)
31
+ payload = verify(token)
32
+ return nil unless payload
33
+
34
+ Renderer.render(
35
+ source: payload['source'],
36
+ watermark: payload['watermark']
37
+ )
38
+ end
39
+
40
+ def self.secret_key
41
+ # sin engine todavía; Rails solo si existe
42
+ if defined?(Rails)
43
+ Rails.application.secret_key_base
44
+ else
45
+ ENV['IMPRINT_SECRET'] || 'imprint-dev-secret'
46
+ end
47
+ end
48
+ end
data/lib/imprint.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'imprint'
4
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imprint-image
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giménez Silva Germán Alberto
@@ -35,7 +35,20 @@ email:
35
35
  executables: []
36
36
  extensions: []
37
37
  extra_rdoc_files: []
38
- files: []
38
+ files:
39
+ - CHANGELOG.md
40
+ - LICENSE
41
+ - README.md
42
+ - lib/imprint-image.rb
43
+ - lib/imprint.rb
44
+ - lib/imprint/config/routes.rb
45
+ - lib/imprint/controllers/imprint/renders_controller.rb
46
+ - lib/imprint/engine.rb
47
+ - lib/imprint/rails/helper.rb
48
+ - lib/imprint/railtie.rb
49
+ - lib/imprint/renderer.rb
50
+ - lib/imprint/signer.rb
51
+ - lib/imprint/version.rb
39
52
  homepage: https://github.com/ggerman/imprint
40
53
  licenses:
41
54
  - MIT