bridgetown-webfinger 0.1.0
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 +7 -0
- data/CHANGELOG.md +15 -0
- data/CONTRIBUTING.md +55 -0
- data/LICENSE.md +22 -0
- data/README.md +125 -0
- data/bridgetown-webfinger.gemspec +35 -0
- data/lib/bridgetown/webfinger/alias.rb +29 -0
- data/lib/bridgetown/webfinger/builder.rb +133 -0
- data/lib/bridgetown/webfinger/href.rb +29 -0
- data/lib/bridgetown/webfinger/initializer.rb +23 -0
- data/lib/bridgetown/webfinger/jrd.rb +164 -0
- data/lib/bridgetown/webfinger/link.rb +118 -0
- data/lib/bridgetown/webfinger/link_relation_type.rb +159 -0
- data/lib/bridgetown/webfinger/logging.rb +38 -0
- data/lib/bridgetown/webfinger/model.rb +13 -0
- data/lib/bridgetown/webfinger/parameters.rb +166 -0
- data/lib/bridgetown/webfinger/properties.rb +44 -0
- data/lib/bridgetown/webfinger/titles.rb +40 -0
- data/lib/bridgetown/webfinger/uri/acct.rb +138 -0
- data/lib/bridgetown/webfinger/version.rb +8 -0
- data/lib/bridgetown-webfinger.rb +49 -0
- data/lib/roda/plugins/bridgetown_webfinger.rb +149 -0
- metadata +131 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
# A module providing classes to handle Uniform Resource Identifiers ([RFC2396][1])
|
6
|
+
#
|
7
|
+
# [1]: https://datatracker.ietf.org/doc/html/rfc2396
|
8
|
+
module URI
|
9
|
+
# A URI for an account on a system
|
10
|
+
#
|
11
|
+
# @since 0.1.0
|
12
|
+
# @api public
|
13
|
+
#
|
14
|
+
# @example Parsing an acct URI from a string
|
15
|
+
#
|
16
|
+
# URI.parse("acct:bilbo@bagend.com")
|
17
|
+
#
|
18
|
+
# See [RFC7565](https://datatracker.ietf.org/doc/html/rfc7565) for more
|
19
|
+
# information.
|
20
|
+
class Acct < Generic
|
21
|
+
# The components of the URI
|
22
|
+
#
|
23
|
+
# @private
|
24
|
+
COMPONENT = [:scheme, :account, :host].freeze
|
25
|
+
|
26
|
+
# Builds a {URI::Acct} from components
|
27
|
+
#
|
28
|
+
# Note: Do not enter already percent-encoded data as a component for the
|
29
|
+
# account because the implementation will do this step for you. If you're
|
30
|
+
# building from components received externally, consider using
|
31
|
+
# `URI.decode_uri_component` before using it to build a URI with this
|
32
|
+
# method.
|
33
|
+
#
|
34
|
+
# @since 0.1.0
|
35
|
+
# @api public
|
36
|
+
#
|
37
|
+
# @example Building an acct URI from an array of attributes
|
38
|
+
#
|
39
|
+
# URI::Acct.build(["bilbo", "bagend.com"])
|
40
|
+
#
|
41
|
+
# @example Building an acct URI from a hash of attributes
|
42
|
+
#
|
43
|
+
# URI::Acct.build({ account: "bilbo", host: "bagend.com" })
|
44
|
+
#
|
45
|
+
# @param args [Hash<Symbol, String>, Array<String>] the components to use,
|
46
|
+
# either as a Hash of `{account: String, host: String}` or a positional,
|
47
|
+
# 2-element `Array` of the form `[account, host]`
|
48
|
+
# @return [URI::Acct] the built URI
|
49
|
+
# @raise [ArgumentError] when the components are incorrect
|
50
|
+
def self.build(args)
|
51
|
+
components = Util.make_components_hash(self, args)
|
52
|
+
|
53
|
+
components[:account] ||= ""
|
54
|
+
components[:account] &&= URI.encode_uri_component(components[:account])
|
55
|
+
components[:host] ||= ""
|
56
|
+
components[:opaque] = "#{components.delete(:account)}@#{components.delete(:host)}"
|
57
|
+
|
58
|
+
super(components)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Instantiates a new {URI::Acct} from a long list of components
|
62
|
+
#
|
63
|
+
# @since 0.1.0
|
64
|
+
# @api public
|
65
|
+
#
|
66
|
+
# @example Manually constructing a URI — with value checking — for the masochistic
|
67
|
+
# URI::Acct.new(["acct", nil, nil, nil, nil, nil, "bilbo@bagend.com", nil, nil, nil, true])
|
68
|
+
#
|
69
|
+
# @param args [Array<String>] the components to set for the URI
|
70
|
+
# @return [URI::Acct]
|
71
|
+
# @raise [InvalidComponentError] when the account name is malformed
|
72
|
+
def initialize(*args)
|
73
|
+
super
|
74
|
+
|
75
|
+
raise InvalidComponentError, "missing opaque part for acct URI" unless @opaque
|
76
|
+
|
77
|
+
account, _, host = @opaque.rpartition("@")
|
78
|
+
@opaque = nil
|
79
|
+
|
80
|
+
if args[10] # arg_check
|
81
|
+
self.account = account
|
82
|
+
self.host = host
|
83
|
+
else
|
84
|
+
@account = account
|
85
|
+
@host = host
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# The account part (also known as the userpart) of the URI
|
90
|
+
#
|
91
|
+
# @since 0.1.0
|
92
|
+
# @api public
|
93
|
+
#
|
94
|
+
# @example Checking the account for a URI
|
95
|
+
#
|
96
|
+
# URI.parse("acct:bilbo@bagend.com").account #=> "bilbo"
|
97
|
+
#
|
98
|
+
# @return [String]
|
99
|
+
attr_reader :account
|
100
|
+
|
101
|
+
# Sets the account part of the URI
|
102
|
+
#
|
103
|
+
# @since 0.1.0
|
104
|
+
# @api public
|
105
|
+
#
|
106
|
+
# @example Setting the account for a URI
|
107
|
+
#
|
108
|
+
# uri = URI.parse("acct:bilbo@bagend.com")
|
109
|
+
# uri.account = "frodo"
|
110
|
+
# uri.account #=> "frodo"
|
111
|
+
#
|
112
|
+
# @param value [String] a percent-encoded account name for the URI
|
113
|
+
# @return [void]
|
114
|
+
# @raise [InvalidComponentError] when the value is malformed
|
115
|
+
def account=(value)
|
116
|
+
raise InvalidComponentError, "missing account part for acct URI" if value.empty?
|
117
|
+
raise InvalidComponentError, "account part not percent-encoded for acct URI" if value.include?("@")
|
118
|
+
|
119
|
+
@account = URI.decode_uri_component(value)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Converts the URI into a string
|
123
|
+
#
|
124
|
+
# @since 0.1.0
|
125
|
+
# @api public
|
126
|
+
#
|
127
|
+
# @example Converting a URI to a string
|
128
|
+
#
|
129
|
+
# URI.parse("acct:bilbo@bagend.com").to_s
|
130
|
+
#
|
131
|
+
# @return [String] the formatted version of the {URI::Acct}
|
132
|
+
def to_s
|
133
|
+
@scheme + ":" + URI.encode_uri_component(account) + "@" + host
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
register_scheme "ACCT", Acct
|
138
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bridgetown"
|
4
|
+
require "zeitwerk"
|
5
|
+
|
6
|
+
require_relative "bridgetown/webfinger/uri/acct"
|
7
|
+
|
8
|
+
# A progressive site generator and fullstack framework
|
9
|
+
#
|
10
|
+
# @see https://www.bridgetownrb.com/
|
11
|
+
module Bridgetown
|
12
|
+
# A Bridgetown plugin that adds support for serving [Webfinger][1] requests
|
13
|
+
#
|
14
|
+
# The plugin handles either static or dynamic requests, served with data backed
|
15
|
+
# by your Bridgetown site's data.
|
16
|
+
#
|
17
|
+
# [1]: https://datatracker.ietf.org/doc/html/rfc7033
|
18
|
+
module Webfinger
|
19
|
+
# The Zeitwerk loader responsible for auto-loading constants
|
20
|
+
#
|
21
|
+
# @private
|
22
|
+
Loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false).tap do |loader|
|
23
|
+
loader.ignore(__FILE__)
|
24
|
+
loader.ignore(File.join(__dir__, "roda", "plugins", "bridgetown_webfinger"))
|
25
|
+
loader.ignore(File.join(__dir__, "bridgetown", "webfinger", "initializer"))
|
26
|
+
loader.ignore(File.join(__dir__, "bridgetown", "webfinger", "uri", "acct"))
|
27
|
+
loader.inflector.inflect("jrd" => "JRD")
|
28
|
+
loader.setup
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks whether a given string is a URI of a registered type
|
32
|
+
#
|
33
|
+
# This will not convert the item into a URI object and it is able to
|
34
|
+
# properly handle when there are multiple URIs in the string (by returning
|
35
|
+
# false).
|
36
|
+
#
|
37
|
+
# @since 0.1.0
|
38
|
+
# @api private
|
39
|
+
#
|
40
|
+
# @param uri [String] the string to check as a URI
|
41
|
+
# @return [Boolean] true when it is a URI, false when it is not
|
42
|
+
def self.uri?(uri)
|
43
|
+
uri.is_a?(String) &&
|
44
|
+
uri == URI.extract(uri).first
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
require_relative "bridgetown/webfinger/initializer"
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A routing tree for building Rack applications
|
4
|
+
#
|
5
|
+
# @see https://roda.jeremyevans.net/
|
6
|
+
class Roda
|
7
|
+
# The namespace Roda uses for loading plugins by convention
|
8
|
+
module RodaPlugins
|
9
|
+
# A plugin that integrates Webfinger behavior into a Bridgetown Roda app
|
10
|
+
#
|
11
|
+
# This plugin requires the Bridgetown SSR plugin to be enabled before it.
|
12
|
+
#
|
13
|
+
# It reads data from the `authors` data for the Bridgetown site to validate
|
14
|
+
# author accounts, then extracts the `webfinger` key from the author to
|
15
|
+
# build the JSON Resource Descriptor.
|
16
|
+
module BridgetownWebfinger
|
17
|
+
# The Roda hook for configuring the plugin
|
18
|
+
#
|
19
|
+
# @since 0.1.0
|
20
|
+
# @api private
|
21
|
+
#
|
22
|
+
# @param app [::Roda] the Roda application to configure
|
23
|
+
# @return [void]
|
24
|
+
def self.configure(app)
|
25
|
+
return unless app.opts[:bridgetown_site].nil?
|
26
|
+
|
27
|
+
# :nocov: Because it's difficult to set up multiple contexts
|
28
|
+
raise(
|
29
|
+
"Roda app failure: the bridgetown_ssr plugin must be registered before " \
|
30
|
+
"bridgetown_webfinger"
|
31
|
+
)
|
32
|
+
# :nocov:
|
33
|
+
end
|
34
|
+
|
35
|
+
# Methods included in to the Roda request
|
36
|
+
#
|
37
|
+
# @see http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Base/RequestMethods.html
|
38
|
+
module RequestMethods
|
39
|
+
# Builds the Webfinger route within the Roda application
|
40
|
+
#
|
41
|
+
# @since 0.1.0
|
42
|
+
# @api public
|
43
|
+
#
|
44
|
+
# @example Enabling the Webfinger route
|
45
|
+
#
|
46
|
+
# class RodaApp < Bridgetown::Rack::Roda
|
47
|
+
# plugin :bridgetown_ssr
|
48
|
+
# plugin :bridgetown_webfinger
|
49
|
+
#
|
50
|
+
# route do |r|
|
51
|
+
# r.bridgetown_webfinger
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @return [void]
|
56
|
+
def bridgetown_webfinger
|
57
|
+
bridgetown_site = roda_class.opts[:bridgetown_site]
|
58
|
+
|
59
|
+
on(".well-known/webfinger") do
|
60
|
+
is do
|
61
|
+
get do
|
62
|
+
params = Bridgetown::Webfinger::Parameters.from_query_string(query_string)
|
63
|
+
host = URI(bridgetown_site.config.url).host
|
64
|
+
response["Access-Control-Allow-Origin"] =
|
65
|
+
bridgetown_site.config.webfinger.allowed_origins
|
66
|
+
|
67
|
+
unless (resource = params.resource)
|
68
|
+
next response.webfinger_error("Missing required parameter: resource", status: 400)
|
69
|
+
end
|
70
|
+
|
71
|
+
if (jrd = webfinger_maybe_jrd(resource, host: host, params: params, site: bridgetown_site))
|
72
|
+
response.webfinger_success(jrd)
|
73
|
+
else
|
74
|
+
response.webfinger_error("Unknown resource: #{resource}", status: 404)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# Constructs a JRD for the appropriate account from author metadata
|
84
|
+
#
|
85
|
+
# @since 0.1.0
|
86
|
+
# @api private
|
87
|
+
#
|
88
|
+
# @param resource [String] the resource URI from the parameters
|
89
|
+
# @param host [String] the host domain from the site configuration
|
90
|
+
# @param params [Bridgetown::Webfinger::Parameters] the parameters for the request
|
91
|
+
# @param site [Bridgetown::Site] the site for the Bridgetown instance
|
92
|
+
# @return [Bridgetown::Webfinger::JRD, nil] the JRD when possible, nil otherwise
|
93
|
+
def webfinger_maybe_jrd(resource, host:, params:, site:)
|
94
|
+
return unless (uri = URI.parse(resource)).instance_of?(URI::Acct)
|
95
|
+
return unless (authors = site.data.authors)
|
96
|
+
|
97
|
+
webfinger =
|
98
|
+
if (acct = authors[uri.account])
|
99
|
+
acct.webfinger
|
100
|
+
elsif uri.account.empty? && uri.host == host && authors.length == 1
|
101
|
+
uri.account = uri.host
|
102
|
+
authors.values.first.webfinger
|
103
|
+
else
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
jrd = Bridgetown::Webfinger::JRD.parse(uri.to_s, webfinger)
|
108
|
+
jrd.links&.select! { |link| params.rel.include?(link.rel) } if params.rel
|
109
|
+
jrd
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Methods included in to the Roda response
|
114
|
+
#
|
115
|
+
# @see http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Base/ResponseMethods.html
|
116
|
+
module ResponseMethods
|
117
|
+
# Renders an error in the style of Bridgetown Webfinger
|
118
|
+
#
|
119
|
+
# @since 0.1.0
|
120
|
+
# @api private
|
121
|
+
#
|
122
|
+
# @param message [String] the error message for the response
|
123
|
+
# @param status [Integer] the status code for the response
|
124
|
+
# @return [String] the response body
|
125
|
+
def webfinger_error(message, status:)
|
126
|
+
self["Content-Type"] = "application/json"
|
127
|
+
self.status = status
|
128
|
+
|
129
|
+
JSON.pretty_generate({error: message})
|
130
|
+
end
|
131
|
+
|
132
|
+
# Renders a JSON Resource Descriptor for Webfinger
|
133
|
+
#
|
134
|
+
# @since 0.1.0
|
135
|
+
# @api private
|
136
|
+
#
|
137
|
+
# @param jrd [Bridgetown::Webfinger::JRD] the JRD to render
|
138
|
+
# @return [String] the response body
|
139
|
+
def webfinger_success(jrd)
|
140
|
+
self["Content-Type"] = "application/jrd+json"
|
141
|
+
|
142
|
+
JSON.pretty_generate(jrd.to_h)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
register_plugin :bridgetown_webfinger, BridgetownWebfinger
|
148
|
+
end
|
149
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bridgetown-webfinger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Herold
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-02-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bridgetown
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: uri
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.12.0
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.12.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: zeitwerk
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2'
|
75
|
+
description:
|
76
|
+
email: opensource@michaeljherold.com
|
77
|
+
executables: []
|
78
|
+
extensions: []
|
79
|
+
extra_rdoc_files: []
|
80
|
+
files:
|
81
|
+
- CHANGELOG.md
|
82
|
+
- CONTRIBUTING.md
|
83
|
+
- LICENSE.md
|
84
|
+
- README.md
|
85
|
+
- bridgetown-webfinger.gemspec
|
86
|
+
- lib/bridgetown-webfinger.rb
|
87
|
+
- lib/bridgetown/webfinger/alias.rb
|
88
|
+
- lib/bridgetown/webfinger/builder.rb
|
89
|
+
- lib/bridgetown/webfinger/href.rb
|
90
|
+
- lib/bridgetown/webfinger/initializer.rb
|
91
|
+
- lib/bridgetown/webfinger/jrd.rb
|
92
|
+
- lib/bridgetown/webfinger/link.rb
|
93
|
+
- lib/bridgetown/webfinger/link_relation_type.rb
|
94
|
+
- lib/bridgetown/webfinger/logging.rb
|
95
|
+
- lib/bridgetown/webfinger/model.rb
|
96
|
+
- lib/bridgetown/webfinger/parameters.rb
|
97
|
+
- lib/bridgetown/webfinger/properties.rb
|
98
|
+
- lib/bridgetown/webfinger/titles.rb
|
99
|
+
- lib/bridgetown/webfinger/uri/acct.rb
|
100
|
+
- lib/bridgetown/webfinger/version.rb
|
101
|
+
- lib/roda/plugins/bridgetown_webfinger.rb
|
102
|
+
homepage: https://github.com/michaelherold/bridgetown-webfinger
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
metadata:
|
106
|
+
bug_tracker_uri: https://github.com/michaelherold/bridgetown-webfinger/issues
|
107
|
+
changelog_uri: https://github.com/michaelherold/bridgetown-webfinger/blob/main/CHANGELOG.md
|
108
|
+
documentation_uri: https://rubydoc.info/gems/bridgetown-webfinger/0.1.0
|
109
|
+
homepage_uri: https://github.com/michaelherold/bridgetown-webfinger
|
110
|
+
rubygems_mfa_required: 'true'
|
111
|
+
source_code_uri: https://github.com/michaelherold/bridgetown-webfinger
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 3.0.0
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubygems_version: 3.2.32
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: Adds structured support for Webfinger to Bridgetown sites
|
131
|
+
test_files: []
|