diff --git a/spec/providers/twitter_spec.cr b/spec/providers/twitter_spec.cr new file mode 100644 index 0000000..b8e6fa1 --- /dev/null +++ b/spec/providers/twitter_spec.cr @@ -0,0 +1,59 @@ +require "../spec_helper" + +describe MultiAuth::Provider::Twitter do + request_token_params = { + oauth_token: "NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0", + oauth_token_secret: "veNRnAWe6inFuo8o2u8SLLZLjolYDmDP7SzL0YfYI", + oauth_callback_confirmed: "true", + } + + access_token_params = { + oauth_token: "7588892-kagSNqWge8gB1WwE3plnFsJHAZVfxWD7Vb57p0b4", + oauth_token_secret: "PbKfYqSryyeKDWz4ebtY3o5ogNLG11WJuZBc9fQrQo", + } + + verify_credentials_params = { + id: 38895958, + name: "Sean Cook", + screen_name: "theSeanCook", + location: "San Francisco", + url: "http://twitter.com", + description: "I taught your phone that thing you like. The Mobile Partner Engineer @Twitter.", + profile_image_url: "http://a0.twimg.com/profile_images/1751506047/dead_sexy_normal.JPG", + email: "me@twitter.com", + } + + describe "#authorize_uri" do + it "generates authorize uri" do + WebMock.stub(:post, "https://api.twitter.com/oauth/request_token") + .to_return(body: HTTP::Params.encode request_token_params) + uri = MultiAuth.make("twitter", "/callback").authorize_uri + uri.should eq "https://api.twitter.com/oauth/authorize?oauth_token=NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0&oauth_callback=%2Fcallback" + end + end + + describe "#fetch_tw_user" do + it "successfully fetches user params" do + WebMock.stub(:post, "https://api.twitter.com/oauth/access_token") + .to_return(body: HTTP::Params.encode request_token_params) + + WebMock.stub(:get, "https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true") + .to_return(body: verify_credentials_params.to_json) + + user = MultiAuth.make("twitter", "/callback").user({"oauth_token" => "token", "oauth_verifier" => "verifier"}) + + user.uid.should eq verify_credentials_params[:id].to_s + user.email.should eq verify_credentials_params[:email] + user.name.should eq verify_credentials_params[:name] + user.nickname.should eq verify_credentials_params[:screen_name] + user.location.should eq verify_credentials_params[:location] + user.description.should eq verify_credentials_params[:description] + user.image.should eq verify_credentials_params[:profile_image_url] + user.urls.should eq({"twitter" => verify_credentials_params[:url]}) + + user.provider.should eq "twitter" + user.raw_json.should_not be_nil + user.access_token.should_not be_nil + end + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 42728e0..47d4449 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -6,3 +6,4 @@ MultiAuth.config("google", "google_id", "google_secret") MultiAuth.config("github", "github_id", "github_secret") MultiAuth.config("facebook", "facebook_id", "facebook_secret") MultiAuth.config("vk", "vk_id", "vk_secret") +MultiAuth.config("twitter", "twitter_consumer_key", "twitter_consumer_secret") diff --git a/src/multi_auth.cr b/src/multi_auth.cr index 3894c0e..464ec35 100644 --- a/src/multi_auth.cr +++ b/src/multi_auth.cr @@ -1,3 +1,4 @@ +require "oauth" require "oauth2" require "./multi_auth/**" @@ -12,7 +13,7 @@ module MultiAuth @@configuration end - def self.config(provider, client_id, client_secret) - @@configuration[provider] = [client_id, client_secret] + def self.config(provider, key, secret) + @@configuration[provider] = [key, secret] end end diff --git a/src/multi_auth/engine.cr b/src/multi_auth/engine.cr index 3b3441c..943cded 100644 --- a/src/multi_auth/engine.cr +++ b/src/multi_auth/engine.cr @@ -5,12 +5,13 @@ class MultiAuth::Engine when "github" then Provider::Github when "facebook" then Provider::Facebook when "vk" then Provider::Vk + when "twitter" then Provider::Twitter else raise "Provider #{provider} not implemented" end - client_id, client_secret = MultiAuth.configuration[provider] - @provider = provider_class.new(redirect_uri, client_id, client_secret) + key, secret = MultiAuth.configuration[provider] + @provider = provider_class.new(redirect_uri, key, secret) end getter provider : Provider diff --git a/src/multi_auth/provider.cr b/src/multi_auth/provider.cr index a6c56d8..17b707e 100644 --- a/src/multi_auth/provider.cr +++ b/src/multi_auth/provider.cr @@ -1,11 +1,11 @@ abstract class MultiAuth::Provider getter redirect_uri : String - getter client_id : String - getter client_secret : String - - def initialize(@redirect_uri : String, @client_id : String, @client_secret : String) - end + getter key : String + getter secret : String abstract def authorize_uri(scope = nil) abstract def user(params : Hash(String, String)) + + def initialize(@redirect_uri : String, @key : String, @secret : String) + end end diff --git a/src/multi_auth/providers/facebook.cr b/src/multi_auth/providers/facebook.cr index 14a695d..a7c5d03 100644 --- a/src/multi_auth/providers/facebook.cr +++ b/src/multi_auth/providers/facebook.cr @@ -62,8 +62,8 @@ class MultiAuth::Provider::Facebook < MultiAuth::Provider private def client OAuth2::Client.new( "www.facebook.com", - client_id, - client_secret, + key, + secret, redirect_uri: redirect_uri, authorize_uri: "/v2.9/dialog/oauth", ) @@ -72,8 +72,8 @@ class MultiAuth::Provider::Facebook < MultiAuth::Provider private def token_client OAuth2::Client.new( "graph.facebook.com", - client_id, - client_secret, + key, + secret, redirect_uri: redirect_uri, token_uri: "/v2.9/oauth/access_token" ) diff --git a/src/multi_auth/providers/github.cr b/src/multi_auth/providers/github.cr index dc31503..8cc8a8b 100644 --- a/src/multi_auth/providers/github.cr +++ b/src/multi_auth/providers/github.cr @@ -62,8 +62,8 @@ class MultiAuth::Provider::Github < MultiAuth::Provider private def client OAuth2::Client.new( "github.com", - client_id, - client_secret, + key, + secret, authorize_uri: "/login/oauth/authorize", token_uri: "/login/oauth/access_token" ) diff --git a/src/multi_auth/providers/google.cr b/src/multi_auth/providers/google.cr index c7758e0..5c4d885 100644 --- a/src/multi_auth/providers/google.cr +++ b/src/multi_auth/providers/google.cr @@ -12,8 +12,8 @@ class MultiAuth::Provider::Google < MultiAuth::Provider client = OAuth2::Client.new( "accounts.google.com", - client_id, - client_secret, + key, + secret, authorize_uri: "/o/oauth2/v2/auth", redirect_uri: redirect_uri ) @@ -24,8 +24,8 @@ class MultiAuth::Provider::Google < MultiAuth::Provider def user(params : Hash(String, String)) client = OAuth2::Client.new( "www.googleapis.com", - client_id, - client_secret, + key, + secret, token_uri: "/oauth2/v4/token", redirect_uri: redirect_uri ) diff --git a/src/multi_auth/providers/twitter.cr b/src/multi_auth/providers/twitter.cr new file mode 100644 index 0000000..93e8c82 --- /dev/null +++ b/src/multi_auth/providers/twitter.cr @@ -0,0 +1,62 @@ +class MultiAuth::Provider::Twitter < MultiAuth::Provider + def authorize_uri(scope = nil) + request_token = consumer.get_request_token(redirect_uri) + consumer.get_authorize_uri(request_token, redirect_uri) + end + + def user(params : Hash(String, String)) + tw_user = fetch_tw_user(params["oauth_token"], params["oauth_verifier"]) + + User.new( + "twitter", + tw_user.id, + tw_user.name, + tw_user.raw_json.to_s, + tw_user.access_token.not_nil! + ).tap do |user| + user.email = tw_user.email + user.nickname = tw_user.screen_name + user.location = tw_user.location + user.description = tw_user.description + user.image = tw_user.profile_image_url + if url = tw_user.url + user.urls = {"twitter" => url} + end + end + end + + private class TwUser + property raw_json : String? + property access_token : OAuth::AccessToken? + + JSON.mapping( + id: {type: String, converter: String::RawConverter}, + name: String, + screen_name: String, + location: String?, + description: String?, + url: String?, + profile_image_url: String?, + email: String? + ) + end + + private def fetch_tw_user(oauth_token, oauth_verifier) + request_token = OAuth::RequestToken.new oauth_token, "" + access_token = consumer.get_access_token(request_token, oauth_verifier) + + client = HTTP::Client.new("api.twitter.com", tls: true) + access_token.authenticate(client, key, secret) + + raw_json = client.get("/1.1/account/verify_credentials.json?include_email=true").body + + TwUser.from_json(raw_json).tap do |user| + user.access_token = access_token + user.raw_json = raw_json + end + end + + private def consumer + @consumer ||= OAuth::Consumer.new("api.twitter.com", key, secret) + end +end diff --git a/src/multi_auth/providers/vk.cr b/src/multi_auth/providers/vk.cr index 899c792..5c06e3d 100644 --- a/src/multi_auth/providers/vk.cr +++ b/src/multi_auth/providers/vk.cr @@ -95,8 +95,8 @@ class MultiAuth::Provider::Vk < MultiAuth::Provider private def client OAuth2::Client.new( "oauth.vk.com", - client_id, - client_secret, + key, + secret, redirect_uri: redirect_uri, authorize_uri: "/authorize", token_uri: "/access_token" diff --git a/src/multi_auth/user.cr b/src/multi_auth/user.cr index 7211dfc..b15d1f8 100644 --- a/src/multi_auth/user.cr +++ b/src/multi_auth/user.cr @@ -6,7 +6,7 @@ class MultiAuth::User getter uid : String getter name : String getter raw_json : String - getter access_token : OAuth2::AccessToken + getter access_token : OAuth::AccessToken | OAuth2::AccessToken property email : String? property nickname : String?