From d429ebdfe30eaafc34f50336512e58962837142f Mon Sep 17 00:00:00 2001 From: Sergey Makridenkov Date: Sat, 10 Jun 2017 08:11:23 +0200 Subject: [PATCH] Add google provider --- README.md | 1 + spec/providers/facebook_spec.cr | 66 ++- spec/providers/github_spec.cr | 89 ++-- spec/providers/google_spec.cr | 67 +++ spec/providers/vk_spec.cr | 70 ++-- spec/support/google.json | 484 ++++++++++++++++++++++ src/crystal_std_patch/access_token.cr | 2 +- src/crystal_std_patch/access_token_mac.cr | 2 +- src/multi_auth/engine.cr | 2 +- src/multi_auth/providers/facebook.cr | 3 +- src/multi_auth/providers/github.cr | 3 +- src/multi_auth/providers/google.cr | 156 ++++--- src/multi_auth/providers/vk.cr | 2 +- 13 files changed, 757 insertions(+), 190 deletions(-) create mode 100644 spec/providers/google_spec.cr create mode 100644 spec/support/google.json diff --git a/README.md b/README.md index c8d6b60..ce1088c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ MultiAuth is a library that standardizes multi-provider authentication for web a - Github.com - Facebook.com - Vk.com +- Google.com ## Installation diff --git a/spec/providers/facebook_spec.cr b/spec/providers/facebook_spec.cr index 11f5751..73fdac2 100644 --- a/spec/providers/facebook_spec.cr +++ b/spec/providers/facebook_spec.cr @@ -1,43 +1,41 @@ require "../spec_helper" -describe MultiAuth do - context "facebook" do - it "generates authorize_uri" do - uri = MultiAuth.make("facebook", "/callback").authorize_uri - uri.should eq("https://www.facebook.com/v2.9/dialog/oauth?client_id=facebook_id&redirect_uri=%2Fcallback&response_type=code&scope=email") - end - - it "fetch user" do - WebMock.wrap do - WebMock - .stub(:post, "https://graph.facebook.com/v2.9/oauth/access_token") - .with( - body: "client_id=facebook_id&client_secret=facebook_secret&redirect_uri=%2Fcallback&grant_type=authorization_code&code=123", - headers: {"Accept" => "application/json", "Content-type" => "application/x-www-form-urlencoded"}) - .to_return( - body: %({ - "access_token" : "1111", - "token_type" : "Bearer", - "expires_in" : 899, - "refresh_token" : null, - "scope" : "user" - }) - ) +describe MultiAuth::Provider::Facebook do + it "generates authorize_uri" do + uri = MultiAuth.make("facebook", "/callback").authorize_uri + uri.should eq("https://www.facebook.com/v2.9/dialog/oauth?client_id=facebook_id&redirect_uri=%2Fcallback&response_type=code&scope=email") + end - WebMock - .stub(:get, "https://graph.facebook.com/v2.9/me?fields=id,name,last_name,first_name,email,location,about,website") - .to_return( - body: %({ - "name" : "Sergey", - "id" : "3333" + it "fetch user" do + WebMock.wrap do + WebMock + .stub(:post, "https://graph.facebook.com/v2.9/oauth/access_token") + .with( + body: "client_id=facebook_id&client_secret=facebook_secret&redirect_uri=%2Fcallback&grant_type=authorization_code&code=123", + headers: {"Accept" => "application/json", "Content-type" => "application/x-www-form-urlencoded"}) + .to_return( + body: %({ + "access_token" : "1111", + "token_type" : "Bearer", + "expires_in" : 899, + "refresh_token" : null, + "scope" : "user" }) - ) + ) + + WebMock + .stub(:get, "https://graph.facebook.com/v2.9/me?fields=id,name,last_name,first_name,email,location,about,website") + .to_return( + body: %({ + "name" : "Sergey", + "id" : "3333" + }) + ) - user = MultiAuth.make("facebook", "/callback").user({"code" => "123"}).as(MultiAuth::User) + user = MultiAuth.make("facebook", "/callback").user({"code" => "123"}).as(MultiAuth::User) - user.name.should eq("Sergey") - user.uid.should eq("3333") - end + user.name.should eq("Sergey") + user.uid.should eq("3333") end end end diff --git a/spec/providers/github_spec.cr b/spec/providers/github_spec.cr index 2c2532d..f80ab6f 100644 --- a/spec/providers/github_spec.cr +++ b/spec/providers/github_spec.cr @@ -1,69 +1,34 @@ require "../spec_helper" -describe MultiAuth do - context "github" do - it "generates authorize_uri" do - uri = MultiAuth.make("github", "/callback").authorize_uri - uri.should eq("https://github.com/login/oauth/authorize?client_id=github_id&redirect_uri=&response_type=code&scope=user%3Aemail") - end - - it "fetch user" do - WebMock.wrap do - WebMock.stub(:post, "https://github.com/login/oauth/access_token") - .with( - body: "client_id=github_id&client_secret=github_secret&redirect_uri=&grant_type=authorization_code&code=123", - headers: {"Accept" => "application/json", "Content-type" => "application/x-www-form-urlencoded"} - ) - .to_return( - body: %({ - "access_token" : "1111", - "token_type" : "Bearer", - "expires_in" : 899, - "refresh_token" : null, - "scope" : "user" - }) - ) - - WebMock.stub(:get, "https://api.github.com/user") - .to_return(body: File.read("spec/support/github.json")) - - user = MultiAuth.make("github", "/callback").user({"code" => "123"}).as(MultiAuth::User) - - user.email.should eq("hi@msa7.ru") - end - end +describe MultiAuth::Provider::Github do + it "generates authorize_uri" do + uri = MultiAuth.make("github", "/callback").authorize_uri + uri.should eq("https://github.com/login/oauth/authorize?client_id=github_id&redirect_uri=&response_type=code&scope=user%3Aemail") end - context "facebook" do - it "generates authorize_uri" do - uri = MultiAuth.make("github", "/callback").authorize_uri - uri.should eq("https://github.com/login/oauth/authorize?client_id=github_id&redirect_uri=&response_type=code&scope=user%3Aemail") - end - - it "fetch user" do - WebMock.wrap do - WebMock.stub(:post, "https://github.com/login/oauth/access_token") - .with( - body: "client_id=github_id&client_secret=github_secret&redirect_uri=&grant_type=authorization_code&code=123", - headers: {"Accept" => "application/json", "Content-type" => "application/x-www-form-urlencoded"} - ) - .to_return( - body: %({ - "access_token" : "1111", - "token_type" : "Bearer", - "expires_in" : 899, - "refresh_token" : null, - "scope" : "user" - }) - ) - - WebMock.stub(:get, "https://api.github.com/user") - .to_return(body: File.read("spec/support/github.json")) - - user = MultiAuth.make("github", "/callback").user({"code" => "123"}).as(MultiAuth::User) - - user.email.should eq("hi@msa7.ru") - end + it "fetch user" do + WebMock.wrap do + WebMock.stub(:post, "https://github.com/login/oauth/access_token") + .with( + body: "client_id=github_id&client_secret=github_secret&redirect_uri=&grant_type=authorization_code&code=123", + headers: {"Accept" => "application/json", "Content-type" => "application/x-www-form-urlencoded"} + ) + .to_return( + body: %({ + "access_token" : "1111", + "token_type" : "Bearer", + "expires_in" : 899, + "refresh_token" : null, + "scope" : "user" + }) + ) + + WebMock.stub(:get, "https://api.github.com/user") + .to_return(body: File.read("spec/support/github.json")) + + user = MultiAuth.make("github", "/callback").user({"code" => "123"}).as(MultiAuth::User) + + user.email.should eq("hi@msa7.ru") end end end diff --git a/spec/providers/google_spec.cr b/spec/providers/google_spec.cr new file mode 100644 index 0000000..533daab --- /dev/null +++ b/spec/providers/google_spec.cr @@ -0,0 +1,67 @@ +require "../spec_helper" + +describe MultiAuth::Provider::Google do + it "generates authorize_uri" do + uri = MultiAuth.make("google", "/callback").authorize_uri + uri.should eq("https://accounts.google.com/o/oauth2/v2/auth?client_id=google_id&redirect_uri=%2Fcallback&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuser.emails.read+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuser.phonenumbers.read+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuser.addresses.read+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.login+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcontacts.readonly") + end + + it "fetch user" do + WebMock.wrap do + WebMock.stub(:post, "https://www.googleapis.com/oauth2/v4/token") + .with( + body: "client_id=google_id&client_secret=google_secret&redirect_uri=%2Fcallback&grant_type=authorization_code&code=123", + headers: {"Accept" => "application/json", "Content-Length" => "111", "Host" => "www.googleapis.com", "Content-type" => "application/x-www-form-urlencoded"} + ) + .to_return( + body: %({ + "access_token" : "1111", + "token_type" : "Bearer", + "expires_in" : 899, + "refresh_token" : null, + "scope" : "user" + }) + ) + + WebMock.stub(:get, "https://people.googleapis.com/v1/people/me?requestMask.includeField=person.addresses,person.biographies,person.bragging_rights,person.cover_photos,person.email_addresses,person.im_clients,person.interests,person.names,person.nicknames,person.phone_numbers,person.photos,person.urls") + .to_return(body: File.read("spec/support/google.json")) + + user = MultiAuth.make("google", "/callback").user({"code" => "123"}).as(MultiAuth::User) + + user.email.should eq("smkrbr@gmail.com") + end + end +end + +context "facebook" do + it "generates authorize_uri" do + uri = MultiAuth.make("github", "/callback").authorize_uri + uri.should eq("https://github.com/login/oauth/authorize?client_id=github_id&redirect_uri=&response_type=code&scope=user%3Aemail") + end + + it "fetch user" do + WebMock.wrap do + WebMock.stub(:post, "https://github.com/login/oauth/access_token") + .with( + body: "client_id=github_id&client_secret=github_secret&redirect_uri=&grant_type=authorization_code&code=123", + headers: {"Accept" => "application/json", "Content-type" => "application/x-www-form-urlencoded"} + ) + .to_return( + body: %({ + "access_token" : "1111", + "token_type" : "Bearer", + "expires_in" : 899, + "refresh_token" : null, + "scope" : "user" + }) + ) + + WebMock.stub(:get, "https://api.github.com/user") + .to_return(body: File.read("spec/support/github.json")) + + user = MultiAuth.make("github", "/callback").user({"code" => "123"}).as(MultiAuth::User) + + user.email.should eq("hi@msa7.ru") + end + end +end diff --git a/spec/providers/vk_spec.cr b/spec/providers/vk_spec.cr index a067711..1b2eaee 100644 --- a/spec/providers/vk_spec.cr +++ b/spec/providers/vk_spec.cr @@ -1,45 +1,43 @@ require "../spec_helper" -describe MultiAuth do - context "vk" do - it "generates authorize_uri" do - uri = MultiAuth.make("vk", "/callback").authorize_uri - uri.should eq("https://oauth.vk.com/authorize?client_id=vk_id&redirect_uri=%2Fcallback&response_type=code&scope=email") - end +describe MultiAuth::Provider::Vk do + it "generates authorize_uri" do + uri = MultiAuth.make("vk", "/callback").authorize_uri + uri.should eq("https://oauth.vk.com/authorize?client_id=vk_id&redirect_uri=%2Fcallback&response_type=code&scope=email") + end - it "fetch user" do - WebMock.wrap do - WebMock - .stub(:post, "https://oauth.vk.com/access_token") - .with( - body: "client_id=vk_id&client_secret=vk_secret&redirect_uri=%2Fcallback&grant_type=authorization_code&code=123", - headers: {"Accept" => "application/json", "Content-Length" => "103", "Host" => "oauth.vk.com", "Content-type" => "application/x-www-form-urlencoded"}) - .to_return( - body: %({ - "access_token" : "1111", - "expires_in" : 899, - "refresh_token" : null, - "scope" : "email", - "user_id" : "3333", - "email" : "s@msa7.ru" - }) - ) + it "fetch user" do + WebMock.wrap do + WebMock + .stub(:post, "https://oauth.vk.com/access_token") + .with( + body: "client_id=vk_id&client_secret=vk_secret&redirect_uri=%2Fcallback&grant_type=authorization_code&code=123", + headers: {"Accept" => "application/json", "Content-Length" => "103", "Host" => "oauth.vk.com", "Content-type" => "application/x-www-form-urlencoded"}) + .to_return( + body: %({ + "access_token" : "1111", + "expires_in" : 899, + "refresh_token" : null, + "scope" : "email", + "user_id" : "3333", + "email" : "s@msa7.ru" + }) + ) - WebMock - .stub(:get, %(https://api.vk.com/method/users.get?fields=about,photo_max_orig,city,country,domain,contacts,site&user_id="3333"&v=5.52)) - .to_return( - body: %({"response": [{ - "first_name" : "Sergey", - "last_name" : "Makridenkov", - "id" : 3333 - }]}) - ) + WebMock + .stub(:get, %(https://api.vk.com/method/users.get?fields=about,photo_max_orig,city,country,domain,contacts,site&user_id="3333"&v=5.52)) + .to_return( + body: %({"response": [{ + "first_name" : "Sergey", + "last_name" : "Makridenkov", + "id" : 3333 + }]}) + ) - user = MultiAuth.make("vk", "/callback").user({"code" => "123"}).as(MultiAuth::User) + user = MultiAuth.make("vk", "/callback").user({"code" => "123"}).as(MultiAuth::User) - user.name.should eq("Makridenkov Sergey") - user.uid.should eq("3333") - end + user.name.should eq("Makridenkov Sergey") + user.uid.should eq("3333") end end end diff --git a/spec/support/google.json b/spec/support/google.json new file mode 100644 index 0000000..64c438c --- /dev/null +++ b/spec/support/google.json @@ -0,0 +1,484 @@ +{ + "resourceName": "people/107107280077356483059", + "etag": "%EhIBEAUXGQkUIiUuAgoNDAsDGAY=", + "metadata": { + "sources": [ + { + "type": "CONTACT", + "id": "c8734cc8cdfacf3", + "etag": "#TW9Lu5USlf4=" + }, + { + "type": "CONTACT", + "id": "15ea8fcc8a89f4de", + "etag": "#qpol+eC6bLA=" + }, + { + "type": "CONTACT", + "id": "93", + "etag": "#538/l64v70s=" + }, + { + "type": "CONTACT", + "id": "7f57a1b18d6a6aaa", + "etag": "#PC96tiMIRRk=" + }, + { + "type": "CONTACT", + "id": "95", + "etag": "#0NOQmzfvzLw=" + }, + { + "type": "PROFILE", + "id": "107107280077356483059", + "etag": "#4eZz2/IuMFw=", + "profileMetadata": { + "objectType": "PERSON" + } + } + ], + "objectType": "PERSON" + }, + "locales": [ + { + "metadata": { + "primary": true, + "source": { + "type": "ACCOUNT", + "id": "107107280077356483059" + } + }, + "value": "en" + } + ], + "names": [ + { + "metadata": { + "primary": true, + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "displayName": "Sergey Makridenkov", + "familyName": "Makridenkov", + "givenName": "Sergey", + "displayNameLastFirst": "Makridenkov, Sergey" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "displayName": "Iam Я", + "familyName": "Я", + "givenName": "Iam", + "displayNameLastFirst": "Я, Iam" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "15ea8fcc8a89f4de" + } + }, + "displayName": "Sergey Makridenkov", + "familyName": "Makridenkov", + "givenName": "Sergey", + "displayNameLastFirst": "Makridenkov, Sergey" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "93" + } + }, + "displayName": "Sergey", + "givenName": "Sergey", + "displayNameLastFirst": "Sergey" + } + ], + "nicknames": [ + { + "metadata": { + "primary": true, + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "value": "SergXIIIth" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "15ea8fcc8a89f4de" + } + }, + "value": "Sergey" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "93" + } + }, + "value": "Sergey" + } + ], + "coverPhotos": [ + { + "metadata": { + "primary": true, + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "url": "https://lh3.googleusercontent.com/c5dqxl-2uHZ82ah9p7yxrVF1ZssrJNSV_15Nu0TUZwzCWqmtoLxCUJgEzLGtxsrJ6-v6R6rKU_-FYm881TTiMCJ_=s1600", + "default": true + } + ], + "photos": [ + { + "metadata": { + "primary": true, + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "url": "https://lh4.googleusercontent.com/-AD281PNjUXU/AAAAAAAAAAI/AAAAAAAALYQ/3WAUiy8jbHE/s100/photo.jpg" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "url": "https://lh4.googleusercontent.com/-CXdSX83lhoc/V-4T6R40_VI/AAAAAAAAAAA/T5JE4AgDpgU/s100/photo.jpg" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "15ea8fcc8a89f4de" + } + }, + "url": "https://lh5.googleusercontent.com/-0KHl0_KdiM4/V-4TSCrGNhI/AAAAAAAAAAA/8oI0NMAJz1c/s100/photo.jpg" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "93" + } + }, + "url": "https://lh3.googleusercontent.com/-ehsvcWTPv6Q/V-4TXpPyIRI/AAAAAAAAAAA/az30Nif1z_o/s100/photo.jpg" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "7f57a1b18d6a6aaa" + } + }, + "url": "https://lh6.googleusercontent.com/-xiYsuxbe4n4/V-4TRQn2AlI/AAAAAAAAAAA/Vl5kcSh4ajQ/s100/photo.jpg" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "95" + } + }, + "url": "https://lh4.googleusercontent.com/-DV151mfKFes/V-4TQJidLSI/AAAAAAAAAAA/YH54nPKjiyo/s100/photo.jpg" + } + ], + "addresses": [ + { + "metadata": { + "primary": true, + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "formattedValue": "Konětopy č.p. 17\n440 01 Pnětluky\nokres Louny\nkraj Ústecký", + "type": "other", + "formattedType": "Other", + "streetAddress": "Konětopy č.p. 17", + "city": "Pnětluky", + "region": "okres Louny\nkraj Ústecký", + "postalCode": "440 01" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "formattedValue": "187351\nРоссия, Ленинградская обл., Кировский район, село Путилово, ул. Братьев Пожарских, д.21, кв.10", + "type": "home", + "formattedType": "Home", + "streetAddress": "187351\nРоссия, Ленинградская обл., Кировский район, село Путилово, ул. Братьев Пожарских, д.21, кв.10" + } + ], + "emailAddresses": [ + { + "metadata": { + "primary": true, + "verified": true, + "source": { + "type": "ACCOUNT", + "id": "107107280077356483059" + } + }, + "value": "smkrbr@gmail.com" + }, + { + "metadata": { + "verified": true, + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "value": "smkrbr@gmail.com", + "type": "home", + "formattedType": "Home" + }, + { + "metadata": { + "verified": true, + "source": { + "type": "ACCOUNT", + "id": "107107280077356483059" + } + }, + "value": "smkrbr@mail.ru" + }, + { + "metadata": { + "verified": true, + "source": { + "type": "ACCOUNT", + "id": "107107280077356483059" + } + }, + "value": "sergey@makridenkov.com" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "smkrbr@gmail.com", + "type": "home", + "formattedType": "Home" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "makridenkov@gmail.com", + "type": "work", + "formattedType": "Work" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "sergey@makridenkov.com", + "type": "work", + "formattedType": "Work" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "smkrbr@free.kindle.com", + "type": "Kindle", + "formattedType": "Kindle" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "s@msa7.ru", + "type": "other", + "formattedType": "Other" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "15ea8fcc8a89f4de" + } + }, + "value": "smkrbr@gmail.com", + "type": "home", + "formattedType": "Home" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "93" + } + }, + "value": "smkrbr@gmail.com", + "type": "other", + "formattedType": "Other" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "7f57a1b18d6a6aaa" + } + }, + "value": "sergey@makridenkov.com" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "95" + } + }, + "value": "smkrbr@mail.ru" + } + ], + "phoneNumbers": [ + { + "metadata": { + "primary": true, + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "+7 953 354-06-92", + "canonicalForm": "+79533540692", + "type": "work", + "formattedType": "Work" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "+420775357809", + "canonicalForm": "+420775357809", + "type": "mobile", + "formattedType": "Mobile" + }, + { + "metadata": { + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "+79051621936", + "canonicalForm": "+79051621936", + "type": "home", + "formattedType": "Home" + }, + { + "metadata": { + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "value": "+420774668079", + "type": "mobile", + "formattedType": "Mobile" + } + ], + "biographies": [ + { + "metadata": { + "primary": true, + "source": { + "type": "CONTACT", + "id": "c8734cc8cdfacf3" + } + }, + "value": "2600987645 / 2010 fio income\n\n\nmBank mKonto \nČíslo účtu 670100-2209502078/6210 \nČíslo účtu ve formátu IBAN CZ61 6210 6701 0022 0950 2078 \nČíslo BIC BREXCZPP \n\nIC 01311131\nDIC CZ8409272630", + "contentType": "TEXT_PLAIN" + } + ], + "urls": [ + { + "metadata": { + "primary": true, + "verified": true, + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "value": "https://plus.google.com/107107280077356483059", + "type": "profile", + "formattedType": "Profile" + } + ], + "organizations": [ + { + "metadata": { + "primary": true, + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "type": "work", + "formattedType": "Work", + "endDate": { + "year": 2011, + "month": 1, + "day": 1 + }, + "current": true, + "title": "Programmer, Web developer" + } + ], + "occupations": [ + { + "metadata": { + "primary": true, + "source": { + "type": "PROFILE", + "id": "107107280077356483059" + } + }, + "value": "Programmer, Web developer" + } + ] +} diff --git a/src/crystal_std_patch/access_token.cr b/src/crystal_std_patch/access_token.cr index 8e24083..9b65d8e 100644 --- a/src/crystal_std_patch/access_token.cr +++ b/src/crystal_std_patch/access_token.cr @@ -1,5 +1,5 @@ # TODO remove hack after release -# https://github.com/crystal-lang/crystal/commit/31099237c87a3851c8cb2a78df5ff00bac7364c6 +# https://github.com/crystal-lang/crystal/commit/a3b77d32f1896e996230fbdd07328761b27f6000 # Base class for the two possible access tokens: Bearer and Mac. # diff --git a/src/crystal_std_patch/access_token_mac.cr b/src/crystal_std_patch/access_token_mac.cr index 998ac6c..7d30907 100644 --- a/src/crystal_std_patch/access_token_mac.cr +++ b/src/crystal_std_patch/access_token_mac.cr @@ -1,5 +1,5 @@ # TODO remove hack after release -# https://github.com/crystal-lang/crystal/commit/31099237c87a3851c8cb2a78df5ff00bac7364c6 +# https://github.com/crystal-lang/crystal/commit/a3b77d32f1896e996230fbdd07328761b27f6000 require "secure_random" require "openssl/hmac" diff --git a/src/multi_auth/engine.cr b/src/multi_auth/engine.cr index c2173c1..3b3441c 100644 --- a/src/multi_auth/engine.cr +++ b/src/multi_auth/engine.cr @@ -1,7 +1,7 @@ class MultiAuth::Engine def initialize(provider : String, redirect_uri : String) provider_class = case provider - # when "google" then Provider::Google + when "google" then Provider::Google when "github" then Provider::Github when "facebook" then Provider::Facebook when "vk" then Provider::Vk diff --git a/src/multi_auth/providers/facebook.cr b/src/multi_auth/providers/facebook.cr index 21e5d3e..f31ab51 100644 --- a/src/multi_auth/providers/facebook.cr +++ b/src/multi_auth/providers/facebook.cr @@ -1,6 +1,7 @@ class MultiAuth::Provider::Facebook < MultiAuth::Provider def authorize_uri(scope = nil) - client.get_authorize_uri("email") + scope ||= "email" + client.get_authorize_uri(scope) end def user(params : Hash(String, String)) diff --git a/src/multi_auth/providers/github.cr b/src/multi_auth/providers/github.cr index b54fe44..9db1cc1 100644 --- a/src/multi_auth/providers/github.cr +++ b/src/multi_auth/providers/github.cr @@ -1,6 +1,7 @@ class MultiAuth::Provider::Github < MultiAuth::Provider def authorize_uri(scope = nil) - client.get_authorize_uri("user:email") + scope ||= "user:email" + client.get_authorize_uri(scope) end def user(params : Hash(String, String)) diff --git a/src/multi_auth/providers/google.cr b/src/multi_auth/providers/google.cr index ba97549..62e0212 100644 --- a/src/multi_auth/providers/google.cr +++ b/src/multi_auth/providers/google.cr @@ -1,52 +1,104 @@ -# class MultiAuth::Provider::Google < MultiAuth::Provider -# def authorize_uri(scope = nil) -# oauth2_client = OAuth2::Client.new( -# "accounts.google.com", -# client_id, -# client_secret, -# authorize_uri: "/o/oauth2/v2/auth", -# redirect_uri: redirect_uri -# ) - -# oauth2_client.get_authorize_uri("profile email openid") -# end - -# def user(params : Hash(String, String)) -# access_token = get_access_token(params["code"]) -# p "access_token #{access_token}" -# nil - -# # code=4/Eyr6K3sxsLPiN28Z9SPkAqdJRSJhOcV5N8TKu8qBAvc -# # &authuser=0 -# # &session_state=e55994130393dbe0607b7c1dbc62fd716e806b75..487e -# # &prompt=consent - -# # access_token = OAuth2::AccessToken::Bearer.new(params_hash["code"], 172_800) - -# # client = HTTP::Client.new("accounts.google.com", tls: true) - -# # # Prepare it for using OAuth2 authentication -# # access_token.authenticate(client) - -# # # Execute requests as usual: they will be authenticated -# # client.get("/some_path") - -# # User.new(params) -# end - -# # def raw_info -# # @raw_info ||= access_token.get('https://www.googleapis.com/plus/v1/people/me/openIdConnect').parsed -# # end - -# private def get_access_token(authorization_code) -# oauth2_client = OAuth2::Client.new( -# "www.googleapis.com", -# client_id, -# client_secret, -# token_uri: "/oauth2/v4/token", -# redirect_uri: redirect_uri -# ) - -# oauth2_client.get_access_token_using_authorization_code(authorization_code) -# end -# end +class MultiAuth::Provider::Google < MultiAuth::Provider + def authorize_uri(scope = nil) + defaults = [ + "https://www.googleapis.com/auth/user.emails.read", + "https://www.googleapis.com/auth/user.phonenumbers.read", + "https://www.googleapis.com/auth/user.addresses.read", + "https://www.googleapis.com/auth/plus.login", + "https://www.googleapis.com/auth/contacts.readonly", + ] + + scope ||= defaults.join(" ") + + client = OAuth2::Client.new( + "accounts.google.com", + client_id, + client_secret, + authorize_uri: "/o/oauth2/v2/auth", + redirect_uri: redirect_uri + ) + + client.get_authorize_uri(scope) + end + + def user(params : Hash(String, String)) + client = OAuth2::Client.new( + "www.googleapis.com", + client_id, + client_secret, + token_uri: "/oauth2/v4/token", + redirect_uri: redirect_uri + ) + + access_token = client.get_access_token_using_authorization_code(params["code"]) + + # https://developers.google.com/people/api/rest/v1/people/get + # enable Google People API + + api = HTTP::Client.new("people.googleapis.com", tls: true) + access_token.authenticate(api) + + fields = [ + "person.addresses", + "person.biographies", + "person.bragging_rights", + "person.cover_photos", + "person.email_addresses", + "person.im_clients", + "person.interests", + "person.names", + "person.nicknames", + "person.phone_numbers", + "person.photos", + "person.urls", + ].join(",") + + raw_json = api.get("/v1/people/me?requestMask.includeField=#{fields}").body + + build_user(raw_json, access_token) + end + + private def primary(field) + primary = primary?(field) + raise "No primary in #{json[field]}" unless primary + primary + end + + private def primary?(field) + json[field].each do |item| + return item if item["metadata"]["primary"].as_bool? + end + nil + end + + private def json + @json.as(JSON::Any) + end + + private def build_user(raw_json, access_token) + @json = JSON.parse(raw_json) + + name = primary("names") + user = User.new("github", json["resourceName"].as_s, name["displayName"].as_s, raw_json) + user.access_token = access_token + + user.first_name = name["givenName"].as_s? + user.last_name = name["familyName"].as_s? + + user.nickname = primary("nicknames")["value"].as_s if primary?("nicknames") + user.image = primary("photos")["url"].as_s if primary?("photos") + user.location = primary("addresses")["formattedValue"].as_s if primary?("addresses") + user.email = primary("emailAddresses")["value"].as_s if primary?("emailAddresses") + user.phone = primary("phoneNumbers")["canonicalForm"].as_s if primary?("phoneNumbers") + user.description = primary("biographies")["value"].as_s if primary?("biographies") + + json["urls"].each do |url| + urls = {} of String => String + urls[url["type"].as_s] = url["value"].as_s + + user.urls = urls + end + + user + end +end diff --git a/src/multi_auth/providers/vk.cr b/src/multi_auth/providers/vk.cr index 57f4911..f2c85b0 100644 --- a/src/multi_auth/providers/vk.cr +++ b/src/multi_auth/providers/vk.cr @@ -1,6 +1,6 @@ class MultiAuth::Provider::Vk < MultiAuth::Provider def authorize_uri(scope = nil) - @scope = scope || "email" + @scope = "email" client.get_authorize_uri(@scope) end