From 63309f0857573ef3beabef145781d37ab36ce78f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 22 Oct 2016 14:54:20 +0200 Subject: [PATCH] Working commit --- .gitignore | 4 +++ README.md | 37 +++++++++++++++++++ lib/omniauth/mastodon/version.rb | 4 +-- lib/omniauth/strategies/mastodon.rb | 56 +++++++++++++++++++++++++++-- 4 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46054c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +coverage +doc +.yardoc +*.gem diff --git a/README.md b/README.md index c1995c9..103862e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ OmniAuth::Mastodon ================== + +[![Gem Version](http://img.shields.io/gem/v/omniauth-mastodon.svg)][gem] + +[gem]: https://rubygems.org/gems/omniauth-mastodon + +Authentication strategy for federated Mastodon instances. This is just slightly more complicated than a traditional OAuth2 flow: We do not know the URL of the OAuth end-points in advance, nor can we be sure that +we already have client credentials for that Mastodon instance. + +## Installation + + gem 'mastodon-api', require: 'mastodon' + gem 'omniauth-mastodon' + gem 'omniauth' + +## Configuration + +Example: + +```ruby +Rails.application.config.middleware.use OmniAuth::Builder do + provider :mastodon, credentials: lambda { |domain, callback_url| + Rails.logger.info "Requested credentials for #{domain} with callback URL #{callback_url}" + + existing = MastodonClient.find_by(domain: domain) + return [existing.client_id, existing.client_secret] unless existing.nil? + + client = Mastodon::REST::Client.new(base_url: "https://#{domain}") + app = client.create_app('OmniAuth Test Harness', callback_url) + + MastodonClient.create!(domain: domain, client_id: app.client_id, client_secret: app.client_secret) + + [app.client_id, app.client_secret] + } +end +``` + +The only configuration key you need to set is a lambda for `:credentials`. That lambda will be called whenever we need to get client credentials for OAuth2 requests. The example above uses an ActiveRecord model to store client credentials for different Mastodon domains, and uses the `mastodon-api` gem to fetch them dynamically if they're not stored yet. diff --git a/lib/omniauth/mastodon/version.rb b/lib/omniauth/mastodon/version.rb index 22d0095..e29b7c4 100644 --- a/lib/omniauth/mastodon/version.rb +++ b/lib/omniauth/mastodon/version.rb @@ -8,11 +8,11 @@ module OmniAuth end def minor - 0 + 9 end def patch - 1 + 0 end def pre diff --git a/lib/omniauth/strategies/mastodon.rb b/lib/omniauth/strategies/mastodon.rb index 58a4aa3..5204bd8 100644 --- a/lib/omniauth/strategies/mastodon.rb +++ b/lib/omniauth/strategies/mastodon.rb @@ -5,9 +5,15 @@ module OmniAuth class Mastodon < OmniAuth::Strategies::OAuth2 option :name, 'mastodon' - option :domain + option :credentials + option :identifier - uid { [raw_info['username'], '@', options.domain].join } + option :client_options, { + authorize_url: '/oauth/authorize', + token_url: '/oauth/token' + } + + uid { identifier } info do { @@ -22,9 +28,55 @@ module OmniAuth { raw_info: raw_info } end + # Before we can redirect the user to authorize access, we must know where the user is from + # If the identifier param is not already present, a form will be shown for entering it + def request_phase + identifier ? start_oauth : get_identifier + end + + def callback_phase + set_options_from_identifier + super + end + def raw_info @raw_info ||= access_token.get('api/v1/accounts/verify_credentials').parsed end + + def callback_url + full_host + script_name + callback_path + end + + private + + def get_identifier + form = OmniAuth::Form.new(title: 'Mastodon Login') + form.text_field 'Your full Mastodon identifier', 'identifier' + form.button 'Login' + form.to_response + end + + def start_oauth + set_options_from_identifier + redirect client.auth_code.authorize_url({:redirect_uri => callback_url}.merge(authorize_params)) + end + + def identifier + i = options.identifier || request.params['identifier'] || (env['omniauth.params'].is_a?(Hash) ? env['omniauth.params']['identifier'] : nil) + i = i.downcase.strip unless i.nil? + i = nil if i == '' + i + end + + def set_options_from_identifier + username, domain = identifier.split('@') + client_id, client_secret = options.credentials.call(domain, callback_url) + + options.identifier = identifier + options.client_options[:site] = "https://#{domain}" + options.client_id = client_id + options.client_secret = client_secret + end end end end