opengraphplus 0.1.0 → 0.1.1
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 +4 -4
- data/README.md +2 -2
- data/lib/generators/opengraphplus/install/install_generator.rb +32 -0
- data/lib/generators/opengraphplus/install/templates/README +46 -0
- data/lib/generators/opengraphplus/install/templates/initializer.rb +8 -0
- data/lib/open_graph_plus.rb +1 -6
- data/lib/opengraphplus/api_key.rb +62 -0
- data/lib/opengraphplus/configuration.rb +29 -0
- data/lib/opengraphplus/image_generator.rb +16 -0
- data/lib/opengraphplus/rails/controller.rb +35 -0
- data/lib/opengraphplus/rails/helper.rb +24 -0
- data/lib/opengraphplus/rails/railtie.rb +19 -0
- data/lib/opengraphplus/rails/signature/routes.rb +35 -0
- data/lib/opengraphplus/rails/signature/scope.rb +58 -0
- data/lib/opengraphplus/rails/signature.rb +9 -0
- data/lib/opengraphplus/rails.rb +8 -0
- data/lib/opengraphplus/signature/generator.rb +24 -0
- data/lib/opengraphplus/signature/url.rb +63 -0
- data/lib/opengraphplus/signature/verifier.rb +47 -0
- data/lib/opengraphplus/signature.rb +12 -0
- data/lib/opengraphplus/tag.rb +39 -0
- data/lib/opengraphplus/tags/renderer.rb +78 -0
- data/lib/opengraphplus/tags.rb +64 -0
- data/lib/{open_graph_plus → opengraphplus}/version.rb +1 -1
- data/lib/opengraphplus.rb +14 -1
- metadata +39 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8dd205094f9939db4ce18af7cae036bf08dbee2a31405a1b862fb7660974a68e
|
|
4
|
+
data.tar.gz: 258cc8781b405cb3b396123889a1a2c2b86bf913d12ad14081b2eae1e1ee3e95
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f932e0b431882a31941d22ca4b30cfbf01ecec4ee7facd177d1e3009995b29ce866944fc549671e156ae4d1986762e35d11fb06af63738f2e5de627ea4e988fe
|
|
7
|
+
data.tar.gz: 1a1d51479377852b0794ef4e6884700fcec16662034574db964df7cfa74da90d048019b7b4a8cce21095b12ca45f156dc1ef7d3447a2bedb108c075ce8fd63ed
|
data/README.md
CHANGED
|
@@ -28,7 +28,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
28
28
|
|
|
29
29
|
## Contributing
|
|
30
30
|
|
|
31
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/opengraphplus/
|
|
31
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/opengraphplus/ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/opengraphplus/ruby/blob/main/CODE_OF_CONDUCT.md).
|
|
32
32
|
|
|
33
33
|
## License
|
|
34
34
|
|
|
@@ -36,4 +36,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
36
36
|
|
|
37
37
|
## Code of Conduct
|
|
38
38
|
|
|
39
|
-
Everyone interacting in the OpenGraphPlus project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/opengraphplus/
|
|
39
|
+
Everyone interacting in the OpenGraphPlus project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/opengraphplus/ruby/blob/main/CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Opengraphplus
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Creates an OpenGraphPlus initializer file and adds default Open Graph tags to ApplicationController"
|
|
11
|
+
|
|
12
|
+
def copy_initializer
|
|
13
|
+
template "initializer.rb", "config/initializers/opengraphplus.rb"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def inject_into_application_controller
|
|
17
|
+
inject_into_class "app/controllers/application_controller.rb", "ApplicationController", <<-RUBY
|
|
18
|
+
|
|
19
|
+
open_graph do |og|
|
|
20
|
+
og.type = "website"
|
|
21
|
+
og.url = request.original_url
|
|
22
|
+
og.site_name = Rails.application.class.module_parent_name.titleize
|
|
23
|
+
end
|
|
24
|
+
RUBY
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def show_readme
|
|
28
|
+
readme "README" if behavior == :invoke
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
OpenGraphPlus has been installed!
|
|
3
|
+
|
|
4
|
+
Next steps:
|
|
5
|
+
|
|
6
|
+
1. Add your API key to config/initializers/opengraphplus.rb
|
|
7
|
+
Get your API key at: https://opengraphplus.com/dashboard
|
|
8
|
+
|
|
9
|
+
2. Add the helper to your layout file (e.g., app/views/layouts/application.html.erb):
|
|
10
|
+
|
|
11
|
+
<head>
|
|
12
|
+
<%%= open_graph_meta_tags %>
|
|
13
|
+
</head>
|
|
14
|
+
|
|
15
|
+
3. Customize the default Open Graph tags in ApplicationController:
|
|
16
|
+
|
|
17
|
+
class ApplicationController < ActionController::Base
|
|
18
|
+
open_graph do |og|
|
|
19
|
+
og.type = "website"
|
|
20
|
+
og.url = request.original_url
|
|
21
|
+
og.site_name = "Your Site Name"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
4. Override tags in specific controllers (inherits from parent):
|
|
26
|
+
|
|
27
|
+
class ArticlesController < ApplicationController
|
|
28
|
+
before_action { @article = Article.find(params[:id]) }
|
|
29
|
+
|
|
30
|
+
open_graph do |og|
|
|
31
|
+
og.title = @article.title
|
|
32
|
+
og.description = @article.excerpt
|
|
33
|
+
og.type = "article"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
5. Set tags in views:
|
|
38
|
+
|
|
39
|
+
<%% open_graph do |og|
|
|
40
|
+
og.title = "My Page Title"
|
|
41
|
+
og.description = "My Page Description"
|
|
42
|
+
end %>
|
|
43
|
+
|
|
44
|
+
6. Verify it works by checking your page source for og:image meta tags,
|
|
45
|
+
or use the preview tool at: https://opengraphplus.com/previews/new
|
|
46
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
OpenGraphPlus.configure do |config|
|
|
4
|
+
# Get your API key at https://opengraphplus.com/dashboard
|
|
5
|
+
config.api_key = Rails.application.credentials.opengraphplus_api_key
|
|
6
|
+
# Or use ENV:
|
|
7
|
+
# config.api_key = ENV["OPENGRAPHPLUS_API_KEY"]
|
|
8
|
+
end
|
data/lib/open_graph_plus.rb
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
|
|
7
|
+
module OpenGraphPlus
|
|
8
|
+
class APIKey
|
|
9
|
+
NAMESPACE = "ogp"
|
|
10
|
+
PUBLIC_KEY_BYTES = 16 # 22 characters when base64 encoded
|
|
11
|
+
SECRET_KEY_BYTES = 32 # 43 characters when base64 encoded
|
|
12
|
+
|
|
13
|
+
attr_reader :public_key, :secret_key, :environment
|
|
14
|
+
|
|
15
|
+
def initialize(public_key:, secret_key:, environment: :live)
|
|
16
|
+
@public_key = public_key
|
|
17
|
+
@secret_key = secret_key
|
|
18
|
+
@environment = environment.to_sym
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
{ pk: public_key, sk: secret_key }
|
|
23
|
+
.to_json
|
|
24
|
+
.then { |json| Base64.urlsafe_encode64(json, padding: false) }
|
|
25
|
+
.then { |payload| [NAMESPACE, environment, payload].join("_") }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def live?
|
|
29
|
+
environment == :live
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test?
|
|
33
|
+
environment == :test
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
def generate(environment: :live)
|
|
38
|
+
new(
|
|
39
|
+
public_key: SecureRandom.urlsafe_base64(PUBLIC_KEY_BYTES),
|
|
40
|
+
secret_key: SecureRandom.urlsafe_base64(SECRET_KEY_BYTES),
|
|
41
|
+
environment: environment
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse(encoded_key)
|
|
46
|
+
return nil unless encoded_key.is_a?(String)
|
|
47
|
+
|
|
48
|
+
case encoded_key.split("_", 3)
|
|
49
|
+
in [NAMESPACE, env, payload]
|
|
50
|
+
payload
|
|
51
|
+
.then { |p| Base64.urlsafe_decode64(p) }
|
|
52
|
+
.then { |json| JSON.parse(json) }
|
|
53
|
+
.then { |data| new(public_key: data["pk"], secret_key: data["sk"], environment: env) }
|
|
54
|
+
else
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
rescue ArgumentError, JSON::ParserError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenGraphPlus
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_reader :api_key
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@api_key = nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def api_key=(value)
|
|
12
|
+
@api_key = value.is_a?(APIKey) ? value : APIKey.parse(value)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def configuration
|
|
18
|
+
@configuration ||= Configuration.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def configure
|
|
22
|
+
yield(configuration)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reset_configuration!
|
|
26
|
+
@configuration = Configuration.new
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenGraphPlus
|
|
4
|
+
class ImageGenerator
|
|
5
|
+
attr_reader :request
|
|
6
|
+
|
|
7
|
+
def initialize(request)
|
|
8
|
+
@request = request
|
|
9
|
+
@signature_url = Signature::URL.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def url
|
|
13
|
+
@signature_url.build("/opengraph", url: request.original_url)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenGraphPlus
|
|
4
|
+
module Rails
|
|
5
|
+
module Controller
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
helper_method :open_graph, :open_graph_tags, :open_graph_meta_tags
|
|
10
|
+
append_before_action :set_default_open_graph_image
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class_methods do
|
|
14
|
+
def open_graph(&block)
|
|
15
|
+
before_action { instance_exec(open_graph, &block) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def open_graph_image_generator
|
|
20
|
+
ImageGenerator.new(request)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def set_default_open_graph_image
|
|
26
|
+
return if open_graph.image.url
|
|
27
|
+
|
|
28
|
+
generated_url = open_graph_image_generator.url
|
|
29
|
+
open_graph.image.url = generated_url if generated_url
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
include Helper
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenGraphPlus
|
|
4
|
+
module Rails
|
|
5
|
+
module Helper
|
|
6
|
+
def open_graph(**kwargs)
|
|
7
|
+
@open_graph_root ||= Tags::Root.new
|
|
8
|
+
@open_graph_root.update(**kwargs) if kwargs.any?
|
|
9
|
+
yield(@open_graph_root) if block_given?
|
|
10
|
+
@open_graph_root
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def open_graph_tags
|
|
14
|
+
root = @open_graph_root || Tags::Root.new
|
|
15
|
+
Tags::Renderer.new(root).tags
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def open_graph_meta_tags
|
|
19
|
+
result = open_graph_tags.map(&:to_s).join("\n")
|
|
20
|
+
result.respond_to?(:html_safe) ? result.html_safe : result
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module OpenGraphPlus
|
|
6
|
+
module Rails
|
|
7
|
+
class Railtie < ::Rails::Railtie
|
|
8
|
+
initializer "opengraphplus.helpers" do
|
|
9
|
+
ActiveSupport.on_load(:action_controller) do
|
|
10
|
+
include OpenGraphPlus::Rails::Controller
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
ActiveSupport.on_load(:action_view) do
|
|
14
|
+
include OpenGraphPlus::Rails::Helper
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenGraphPlus
|
|
4
|
+
module Rails
|
|
5
|
+
module Signature
|
|
6
|
+
module Routes
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
# Access the signature verifier set up by Signature::Scope
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# class ScreenshotsController < ApplicationController
|
|
13
|
+
# include OpenGraphPlus::Rails::Signature::Routes
|
|
14
|
+
#
|
|
15
|
+
# def show
|
|
16
|
+
# if signature_verifier&.public_key
|
|
17
|
+
# api_key = ApiKey.find_by(public_key: signature_verifier.public_key)
|
|
18
|
+
# if api_key && signature_verifier.valid?(api_key.secret_key)
|
|
19
|
+
# # success
|
|
20
|
+
# else
|
|
21
|
+
# # invalid signature
|
|
22
|
+
# end
|
|
23
|
+
# else
|
|
24
|
+
# # malformed signature
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
|
|
29
|
+
def signature_verifier
|
|
30
|
+
request.env[ENV_KEY]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenGraphPlus
|
|
4
|
+
module Rails
|
|
5
|
+
module Signature
|
|
6
|
+
class Scope
|
|
7
|
+
# Rails route constraint that sets up signature verification
|
|
8
|
+
# for controller to handle. Always matches so controller can
|
|
9
|
+
# decide how to handle errors (e.g., show fallback image).
|
|
10
|
+
#
|
|
11
|
+
# Usage in routes.rb:
|
|
12
|
+
# scope "signed/:signature", constraints: OpenGraphPlus::Rails::Signature::Scope.new do
|
|
13
|
+
# get "opengraph", to: "screenshots#show"
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# Then in controller:
|
|
17
|
+
# verifier = request.env["opengraphplus.verifier"]
|
|
18
|
+
# if verifier&.public_key
|
|
19
|
+
# api_key = ApiKey.find_by(public_key: verifier.public_key)
|
|
20
|
+
# if api_key && verifier.valid?(api_key.secret_key)
|
|
21
|
+
# # success
|
|
22
|
+
# else
|
|
23
|
+
# # invalid signature
|
|
24
|
+
# end
|
|
25
|
+
# else
|
|
26
|
+
# # malformed signature
|
|
27
|
+
# end
|
|
28
|
+
|
|
29
|
+
def initialize(param: :signature)
|
|
30
|
+
@param = param
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def matches?(request)
|
|
34
|
+
signature = request.params[@param]
|
|
35
|
+
return true unless signature
|
|
36
|
+
|
|
37
|
+
path_and_query = build_path_and_query(request, signature)
|
|
38
|
+
verifier = OpenGraphPlus::Signature::Verifier.new(signature: signature, path_and_query: path_and_query)
|
|
39
|
+
|
|
40
|
+
request.env[ENV_KEY] = verifier
|
|
41
|
+
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def build_path_and_query(request, signature)
|
|
48
|
+
# Find signature in path and take everything after it
|
|
49
|
+
request.path
|
|
50
|
+
.split(signature, 2)
|
|
51
|
+
.last
|
|
52
|
+
.then { |path| path.empty? ? "/" : path }
|
|
53
|
+
.then { |path| request.query_string.empty? ? path : "#{path}?#{request.query_string}" }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "base64"
|
|
5
|
+
|
|
6
|
+
module OpenGraphPlus
|
|
7
|
+
module Signature
|
|
8
|
+
class Generator
|
|
9
|
+
attr_reader :api_key
|
|
10
|
+
|
|
11
|
+
def initialize(api_key)
|
|
12
|
+
@api_key = api_key
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def generate(path_and_query)
|
|
16
|
+
path_and_query
|
|
17
|
+
.then { |data| OpenSSL::HMAC.digest(DIGEST_ALGORITHM, api_key.secret_key, data) }
|
|
18
|
+
.then { |hmac| hmac.byteslice(0, HMAC_BYTES) }
|
|
19
|
+
.then { |truncated| "#{api_key.public_key}:#{truncated}" }
|
|
20
|
+
.then { |payload| Base64.urlsafe_encode64(payload, padding: false) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module OpenGraphPlus
|
|
6
|
+
module Signature
|
|
7
|
+
class URL
|
|
8
|
+
DEFAULT_BASE_URL = "https://opengraphplus.com"
|
|
9
|
+
DEFAULT_PATH_PREFIX = "/v2/:signature"
|
|
10
|
+
|
|
11
|
+
attr_reader :base_uri, :path_prefix
|
|
12
|
+
|
|
13
|
+
def initialize(api_key: nil, generator: nil, base_url: nil, path_prefix: DEFAULT_PATH_PREFIX)
|
|
14
|
+
@api_key = api_key
|
|
15
|
+
@generator = generator
|
|
16
|
+
@base_uri = URI.parse(base_url || ENV.fetch("OPENGRAPHPLUS_URL", DEFAULT_BASE_URL))
|
|
17
|
+
@path_prefix = path_prefix
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build(path, **params)
|
|
21
|
+
return nil unless generator
|
|
22
|
+
|
|
23
|
+
path_and_query = build_path_and_query(path, params)
|
|
24
|
+
signature = generator.generate(path_and_query)
|
|
25
|
+
|
|
26
|
+
base_uri.dup.tap do |uri|
|
|
27
|
+
uri.path = signed_path(signature, path)
|
|
28
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
29
|
+
end.to_s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def generator
|
|
33
|
+
@generator ||= begin
|
|
34
|
+
api_key = @api_key || OpenGraphPlus.configuration.api_key
|
|
35
|
+
if api_key
|
|
36
|
+
Generator.new(api_key)
|
|
37
|
+
else
|
|
38
|
+
warn_missing_api_key
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def build_path_and_query(path, params)
|
|
47
|
+
normalized_path = File.join("/", path)
|
|
48
|
+
params.empty? ? normalized_path : "#{normalized_path}?#{URI.encode_www_form(params)}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def signed_path(signature, path)
|
|
52
|
+
File.join(path_prefix.gsub(":signature", signature), path)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def warn_missing_api_key
|
|
56
|
+
return if @warned
|
|
57
|
+
|
|
58
|
+
warn "[OpenGraphPlus] API key not configured. Set OpenGraphPlus.configuration.api_key to enable automatic Open Graph image generation."
|
|
59
|
+
@warned = true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
require "base64"
|
|
5
|
+
|
|
6
|
+
module OpenGraphPlus
|
|
7
|
+
module Signature
|
|
8
|
+
class Verifier
|
|
9
|
+
attr_reader :signature, :path_and_query
|
|
10
|
+
|
|
11
|
+
def initialize(signature:, path_and_query:)
|
|
12
|
+
@signature = signature
|
|
13
|
+
@path_and_query = path_and_query
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def public_key
|
|
17
|
+
parsed[:public_key]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def valid?(secret_key)
|
|
21
|
+
expected_hmac = OpenSSL::HMAC.digest(DIGEST_ALGORITHM, secret_key, path_and_query)
|
|
22
|
+
truncated_expected = expected_hmac.byteslice(0, HMAC_BYTES)
|
|
23
|
+
actual_hmac = parsed[:hmac]
|
|
24
|
+
|
|
25
|
+
secure_compare(truncated_expected, actual_hmac)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def parsed
|
|
31
|
+
@parsed ||= signature
|
|
32
|
+
.then { |sig| Base64.urlsafe_decode64(sig) }
|
|
33
|
+
.then { |decoded| decoded.split(":", 2) }
|
|
34
|
+
.then { |public_key, hmac| { public_key: public_key, hmac: hmac } }
|
|
35
|
+
rescue ArgumentError
|
|
36
|
+
{ public_key: nil, hmac: nil }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def secure_compare(a, b)
|
|
40
|
+
return false if a.nil? || b.nil?
|
|
41
|
+
return false if a.bytesize != b.bytesize
|
|
42
|
+
|
|
43
|
+
OpenSSL.fixed_length_secure_compare(a, b)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenGraphPlus
|
|
4
|
+
module Signature
|
|
5
|
+
DIGEST_ALGORITHM = "SHA256"
|
|
6
|
+
HMAC_BYTES = 16 # Truncated HMAC length for shorter URLs
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative "signature/generator"
|
|
11
|
+
require_relative "signature/verifier"
|
|
12
|
+
require_relative "signature/url"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module OpenGraphPlus
|
|
6
|
+
class Tag
|
|
7
|
+
attr_reader :property, :content
|
|
8
|
+
|
|
9
|
+
def initialize(property, content)
|
|
10
|
+
@property = property
|
|
11
|
+
@content = content
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def meta
|
|
15
|
+
%(<meta property="#{escape property}" content="#{escape content}">)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def render_in(view_context = nil)
|
|
19
|
+
result = meta
|
|
20
|
+
if view_context.respond_to?(:raw)
|
|
21
|
+
view_context.raw(result)
|
|
22
|
+
elsif result.respond_to?(:html_safe)
|
|
23
|
+
result.html_safe
|
|
24
|
+
else
|
|
25
|
+
result
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_s
|
|
30
|
+
meta
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def escape(content)
|
|
36
|
+
CGI.escapeHTML(content.to_s)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenGraphPlus
|
|
4
|
+
module Tags
|
|
5
|
+
class Renderer
|
|
6
|
+
attr_reader :root
|
|
7
|
+
|
|
8
|
+
def initialize(root)
|
|
9
|
+
@root = root
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def tags
|
|
13
|
+
[*og_tags, *twitter_tags].compact
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def og
|
|
19
|
+
root.og
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def twitter
|
|
23
|
+
root.twitter
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def tag(property, content)
|
|
27
|
+
return nil if content.nil?
|
|
28
|
+
|
|
29
|
+
Tag.new(property, content)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def og_tags
|
|
33
|
+
[
|
|
34
|
+
tag("og:title", og.title),
|
|
35
|
+
tag("og:description", og.description),
|
|
36
|
+
tag("og:url", og.url),
|
|
37
|
+
tag("og:type", og.type),
|
|
38
|
+
tag("og:site_name", og.site_name),
|
|
39
|
+
tag("og:locale", og.locale),
|
|
40
|
+
tag("og:determiner", og.determiner),
|
|
41
|
+
tag("og:audio", og.audio),
|
|
42
|
+
tag("og:video", og.video),
|
|
43
|
+
*og_image_tags
|
|
44
|
+
]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def og_image_tags
|
|
48
|
+
return [] unless og.image
|
|
49
|
+
|
|
50
|
+
image = og.image
|
|
51
|
+
[
|
|
52
|
+
tag("og:image", image.url),
|
|
53
|
+
tag("og:image:secure_url", image.secure_url),
|
|
54
|
+
tag("og:image:type", image.type),
|
|
55
|
+
tag("og:image:width", image.width),
|
|
56
|
+
tag("og:image:height", image.height),
|
|
57
|
+
tag("og:image:alt", image.alt)
|
|
58
|
+
]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def twitter_tags
|
|
62
|
+
[
|
|
63
|
+
tag("twitter:card", twitter.card),
|
|
64
|
+
tag("twitter:site", twitter.site),
|
|
65
|
+
tag("twitter:creator", twitter.creator),
|
|
66
|
+
tag("twitter:title", twitter.title || og.title),
|
|
67
|
+
tag("twitter:description", twitter.description || og.description),
|
|
68
|
+
tag("twitter:image", twitter_image_url),
|
|
69
|
+
tag("twitter:image:alt", twitter.image_alt || og.image&.alt)
|
|
70
|
+
]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def twitter_image_url
|
|
74
|
+
twitter.image || og.image&.url
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
module OpenGraphPlus
|
|
6
|
+
module Tags
|
|
7
|
+
class Base
|
|
8
|
+
def update(**kwargs)
|
|
9
|
+
kwargs.each { |key, value| public_send(:"#{key}=", value) }
|
|
10
|
+
self
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Image < Base
|
|
15
|
+
attr_accessor :url, :width, :height, :type, :alt, :secure_url
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Twitter < Base
|
|
19
|
+
attr_accessor :card, :site, :creator, :title, :description, :image, :image_alt
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@card = "summary_large_image"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class OpenGraph < Base
|
|
27
|
+
attr_accessor :title, :description, :url, :type, :site_name, :locale, :determiner, :audio, :video
|
|
28
|
+
attr_reader :image
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@type = "website"
|
|
32
|
+
@image = Image.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def image_url=(url)
|
|
36
|
+
@image.url = url
|
|
37
|
+
@image.secure_url = url
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Root < Base
|
|
42
|
+
extend Forwardable
|
|
43
|
+
|
|
44
|
+
attr_reader :og, :twitter
|
|
45
|
+
|
|
46
|
+
def_delegators :@og,
|
|
47
|
+
:title, :title=,
|
|
48
|
+
:description, :description=,
|
|
49
|
+
:url, :url=,
|
|
50
|
+
:type, :type=,
|
|
51
|
+
:site_name, :site_name=,
|
|
52
|
+
:locale, :locale=,
|
|
53
|
+
:determiner, :determiner=,
|
|
54
|
+
:audio, :audio=,
|
|
55
|
+
:video, :video=,
|
|
56
|
+
:image, :image_url=
|
|
57
|
+
|
|
58
|
+
def initialize
|
|
59
|
+
@og = OpenGraph.new
|
|
60
|
+
@twitter = Twitter.new
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/opengraphplus.rb
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "opengraphplus/version"
|
|
4
|
+
require_relative "opengraphplus/api_key"
|
|
5
|
+
require_relative "opengraphplus/configuration"
|
|
6
|
+
require_relative "opengraphplus/signature"
|
|
7
|
+
require_relative "opengraphplus/tag"
|
|
8
|
+
require_relative "opengraphplus/tags"
|
|
9
|
+
require_relative "opengraphplus/tags/renderer"
|
|
10
|
+
require_relative "opengraphplus/image_generator"
|
|
11
|
+
|
|
12
|
+
module OpenGraphPlus
|
|
13
|
+
class Error < StandardError; end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
require_relative "opengraphplus/rails" if defined?(::Rails::Railtie)
|
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opengraphplus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brad Gessler
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-12-
|
|
11
|
-
dependencies:
|
|
10
|
+
date: 2025-12-17 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
12
26
|
description: Add Open Graph meta tags to your Rails application
|
|
13
27
|
email:
|
|
14
28
|
- brad@opengraphplus.com
|
|
@@ -20,9 +34,29 @@ files:
|
|
|
20
34
|
- LICENSE.txt
|
|
21
35
|
- README.md
|
|
22
36
|
- Rakefile
|
|
37
|
+
- lib/generators/opengraphplus/install/install_generator.rb
|
|
38
|
+
- lib/generators/opengraphplus/install/templates/README
|
|
39
|
+
- lib/generators/opengraphplus/install/templates/initializer.rb
|
|
23
40
|
- lib/open_graph_plus.rb
|
|
24
|
-
- lib/open_graph_plus/version.rb
|
|
25
41
|
- lib/opengraphplus.rb
|
|
42
|
+
- lib/opengraphplus/api_key.rb
|
|
43
|
+
- lib/opengraphplus/configuration.rb
|
|
44
|
+
- lib/opengraphplus/image_generator.rb
|
|
45
|
+
- lib/opengraphplus/rails.rb
|
|
46
|
+
- lib/opengraphplus/rails/controller.rb
|
|
47
|
+
- lib/opengraphplus/rails/helper.rb
|
|
48
|
+
- lib/opengraphplus/rails/railtie.rb
|
|
49
|
+
- lib/opengraphplus/rails/signature.rb
|
|
50
|
+
- lib/opengraphplus/rails/signature/routes.rb
|
|
51
|
+
- lib/opengraphplus/rails/signature/scope.rb
|
|
52
|
+
- lib/opengraphplus/signature.rb
|
|
53
|
+
- lib/opengraphplus/signature/generator.rb
|
|
54
|
+
- lib/opengraphplus/signature/url.rb
|
|
55
|
+
- lib/opengraphplus/signature/verifier.rb
|
|
56
|
+
- lib/opengraphplus/tag.rb
|
|
57
|
+
- lib/opengraphplus/tags.rb
|
|
58
|
+
- lib/opengraphplus/tags/renderer.rb
|
|
59
|
+
- lib/opengraphplus/version.rb
|
|
26
60
|
- sig/open_graph_plus.rbs
|
|
27
61
|
homepage: https://opengraphplus.com/ruby
|
|
28
62
|
licenses:
|
|
@@ -30,7 +64,7 @@ licenses:
|
|
|
30
64
|
metadata:
|
|
31
65
|
allowed_push_host: https://rubygems.org
|
|
32
66
|
homepage_uri: https://opengraphplus.com/ruby
|
|
33
|
-
source_code_uri: https://github.com/opengraphplus/
|
|
67
|
+
source_code_uri: https://github.com/opengraphplus/ruby
|
|
34
68
|
rdoc_options: []
|
|
35
69
|
require_paths:
|
|
36
70
|
- lib
|