diff --git a/README.md b/README.md index 2a4d2a3..867781f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ The _easiest_ way to add Google OAuth authentication to your Elixir Apps. [![Hex.pm](https://img.shields.io/hexpm/v/elixir_auth_google?color=brightgreen&style=flat-square)](https://hex.pm/packages/elixir_auth_google) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/elixir-auth-google/issues) [![HitCount](http://hits.dwyl.com/dwyl/elixir-auth-google.svg)](http://hits.dwyl.com/dwyl/elixir-auth-google) + @@ -31,29 +32,27 @@ Following best practices for security & privacy and avoiding complexity by having sensible defaults for all settings. - > We built a lightweight solution -that only does _one_ thing -and is easy for complete beginners to understand/use.
-There were already _several_ available options -for adding Google Auth to apps on -[hex.pm/packages?search=google](https://hex.pm/packages?search=google)
-that all added _far_ too many implementation steps (complexity) -and had incomplete docs (**`@doc false`**) and tests.
-e.g: -[github.com/googleapis/elixir-google-api](https://github.com/googleapis/elixir-google-api) -which is a -["_generated_"](https://github.com/googleapis/elixir-google-api/blob/master/scripts/generate_client.sh) -client and is considered "experimental".
-We have drawn inspiration from several sources -including code from other programming languages to build this package. -This result is _much_ simpler -than anything else -and has both step-by-step instructions -and a _complete working example_ App -including how to encrypt tokens for secure storage -to help you ship your app _fast_. - +> that only does _one_ thing +> and is easy for complete beginners to understand/use.
+> There were already _several_ available options +> for adding Google Auth to apps on +> [hex.pm/packages?search=google](https://hex.pm/packages?search=google)
+> that all added _far_ too many implementation steps (complexity) +> and had incomplete docs (**`@doc false`**) and tests.
+> e.g: +> [github.com/googleapis/elixir-google-api](https://github.com/googleapis/elixir-google-api) +> which is a +> ["_generated_"](https://github.com/googleapis/elixir-google-api/blob/master/scripts/generate_client.sh) +> client and is considered "experimental".
+> We have drawn inspiration from several sources +> including code from other programming languages to build this package. +> This result is _much_ simpler +> than anything else +> and has both step-by-step instructions +> and a _complete working example_ App +> including how to encrypt tokens for secure storage +> to help you ship your app _fast_. # _Who_? 👥 @@ -67,7 +66,6 @@ of auth "schemes" or "strategies".
Just follow the detailed instructions and you'll be up-and running in 5 minutes. - # _How_? ✅ You can add Google Authentication to your Elixir App @@ -81,19 +79,19 @@ Open your project's **`mix.exs`** file and locate the **`deps`** (dependencies) section.
Add a line for **`:elixir_auth_google`** in the **`deps`** list: +:exclamation: the Github action should update the OTP -> 25 and Elixir versions -> 1.14 ? + +:exclamation: this version is disruptive as it exposes only one function in the controller + ```elixir def deps do [ - {:elixir_auth_google, "~> 1.6.3"} + {:elixir_auth_google, "~> 2.0"} ] end ``` -Once you have added the line to your **`mix.exs`**, -remember to run the **`mix deps.get`** command -in your terminal -to _download_ the dependencies. - +Once you have added the line to your **`mix.exs`** remember to run the **`mix deps.get`** command in your terminal to _download_ the dependencies. ## 2. Create Google APIs Application OAuth2 Credentials 🆕 @@ -103,15 +101,14 @@ and save the credentials as environment variables accessible by your app, or put them in your config file. > **Note**: There are a few steps for creating a set of Google APIs credentials, -so if you don't already have a Google App, -we created the following step-by-step guide -to make it quick and _relatively_ painless: -[create-google-app-guide.md](https://github.com/dwyl/elixir-auth-google/blob/master/create-google-app-guide.md)
-Don't be intimidated by all the buzz-words; -it's quite straightforward. -And if you get stuck, ask for -[help!](https://github.com/dwyl/elixir-auth-google/issues) - +> so if you don't already have a Google App, +> we created the following step-by-step guide +> to make it quick and _relatively_ painless: +> [create-google-app-guide.md](https://github.com/dwyl/elixir-auth-google/blob/master/create-google-app-guide.md)
+> Don't be intimidated by all the buzz-words; +> it's quite straightforward. +> And if you get stuck, ask for +> [help!](https://github.com/dwyl/elixir-auth-google/issues) ## 3. Setup CLIENT_ID and CLIENT_SECRET in your project @@ -121,6 +118,7 @@ You may either add those keys as environment variables or put them in the config export GOOGLE_CLIENT_ID=631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com export GOOGLE_CLIENT_SECRET=MHxv6-RGF5nheXnxh1b0LNDq ``` + Or add the following in the config file: ```elixir @@ -129,9 +127,9 @@ config :elixir_auth_google, client_secret: "MHxv6-RGF5nheXnxh1b0LNDq" ``` -> ⚠️ Don't worry, these keys aren't valid. -They are just here for illustration purposes. +> ⚠️ Don't worry, these keys aren't valid. +> They are just here for illustration purposes. ## 4. Create a `GoogleAuthController` in your Project 📝 @@ -140,27 +138,58 @@ Create a new file called and add the following code: ```elixir +# lib/app_web/controllers/google_auth_controller.ex + defmodule AppWeb.GoogleAuthController do use AppWeb, :controller + action_fallback LoginErrorController @doc """ `index/2` handles the callback from Google Auth API redirect. """ def index(conn, %{"code" => code}) do - {:ok, token} = ElixirAuthGoogle.get_token(code, conn) - {:ok, profile} = ElixirAuthGoogle.get_user_profile(token.access_token) - conn - |> render(:welcome, profile: profile) + with {:ok, profile} <- ElixirAuthGoogle.get_profile(code, conn) do + conn + |> put_view(AppWeb.PageView) + |> render(:welcome, profile: profile) + end end end ``` + This code does 3 things: -+ Create a one-time auth `token` based on the response `code` sent by Google -after the person authenticates. -+ Request the person's profile data from Google based on the `access_token` -+ Render a `:welcome` view displaying some profile data -to confirm that login with Google was successful. +- Requests the person's profile data from Google based on the response `code` sent by Google after the person authenticates. +- Renders a `:welcome` view displaying some profile data + to confirm that login with Google was successful. +- [Falls back](https://hexdocs.pm/phoenix/Phoenix.Controller.html#action_fallback/1) to the initial page whenever `get_profile/1` fails and sends a message `{:error, :bad_request}`. + +## 4.bis Optional: create an fallback controller + +In case the login process returns an error (because of wrong credentials, call didn't succeed), you may want ot capture and handle it nicely. This is what the `action_fallback` does: pass the flow to this controller. + +```elixir +# lib/app_web/login_error_controller.ex + +defmodule AppWeb.LoginErrorController do + use AppWeb, :controller + require Logger + + @moduledoc """ + An example of a fallback. Returns to "/" and displays flash error + """ + def call(conn, {:error, message}) do + oauth_google_url = ElixirAuthGoogle.generate_oauth_url(conn) + + conn + |> fetch_session() + |> fetch_flash() + |> put_flash(:error, message) + |> put_view(AppWeb.PageView) + |> render("index.html", oauth_google_url: oauth_google_url) + end +end +``` ## 5. Create the `/auth/google/callback` Endpoint 📍 @@ -170,16 +199,36 @@ and locate the section that looks like `scope "/", AppWeb do` Add the following line: ```elixir -get "/auth/google/callback", GoogleAuthController, :index +# router.ex + +scope "/", AppWeb do + pipe_through :browser + [...] + get "/auth/google/callback", GoogleAuthController, :index +end ``` Sample: [lib/app_web/router.ex#L20](https://github.com/dwyl/elixir-auth-google-demo/blob/4bb616dd134f498b84f079104c0f3345769517c4/lib/app_web/router.ex#L20) +> **Note**: Google recommends to set a [Content Security Policy](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid#content_security_policy) to prevent XSS attacks. +> This can be this way by adding the CSP in the router as a plug in the `:borwser` pipeline. We set is as "report-only" below. + +```elixir +# router +@csp "script-src https://accounts.google.com/gsi/client;" <> + "frame-src https://accounts.google.com/gsi/;" <> + "connect-src https://accounts.google.com/gsi/;" + +pipeline :browser + [...] + plug(:put_secure_browser_headers, %{"content-security-policy-report-only" => @csp}) +``` + ### Different callback url? You can specify the env var -``` +```bash export GOOGLE_CALLBACK_PATH=/myauth/google_callback ``` @@ -203,6 +252,7 @@ Open the `lib/app_web/controllers/page_controller.ex` file and update the `index` function: From: + ```elixir def index(conn, _params) do render(conn, "index.html") @@ -210,7 +260,10 @@ end ``` To: + ```elixir +# lib/app_web/controllers/page_controller.ex + def index(conn, _params) do oauth_google_url = ElixirAuthGoogle.generate_oauth_url(conn) render(conn, "index.html",[oauth_google_url: oauth_google_url]) @@ -223,12 +276,16 @@ Open the `/lib/app_web/templates/page/index.html.eex` file and type the following code: ```html +# lib/app_web/templates/page/index.html.heex +

Welcome to Awesome App!

-

To get started, login to your Google Account:

- - Sign in with Google - +

To get started, login to your Google Account:

+

+ + Sign in with Google + +

``` @@ -245,7 +302,6 @@ where you can display a "login success" message: ![welcome](https://user-images.githubusercontent.com/194400/70201692-494db880-170f-11ea-9776-0ffd1fdf5a72.png) - ### _Optional_: Scopes Most of the time you will only want/need @@ -263,7 +319,7 @@ simply define them using an environment variable, e.g: GOOGLE_SCOPE=email contacts photoslibrary ``` -***or*** you can set them as a config variable if you prefer: +**_or_** you can set them as a config variable if you prefer: ``` config :elixir_auth_google, @@ -275,13 +331,12 @@ once the person authenticates/authorizes.

- ## _Optimised_ SVG+CSS Button In **step 6.1** above, we suggest using an `` for the `Sign in with GitHub` button. -But even though this image appears small **`389 × 93 px`** +But even though this image appears small **`389 × 93 px`** https://i.imgur.com/Kagbzkq.png it is "only" **`8kb`**: ![google-button-8kb](https://user-images.githubusercontent.com/194400/73607428-cd0c1000-45ad-11ea-8639-ffc3e9a0e0a2.png) @@ -290,43 +345,64 @@ We could spend some time in a graphics editor optimising the image, but we _know_ we can do better by using our `CSS` skills! 💡 > **Note**: This is the _official_ button provided by Google: -[developers.google.com/identity/images/signin-assets.zip](developers.google.com/identity/images/signin-assets.zip)
-So if there was any optimisation they could squeeze out of it, -they probably would have done it before publishing the zip! +> [developers.google.com/identity/images/signin-assets.zip](developers.google.com/identity/images/signin-assets.zip)
+> So if there was any optimisation they could squeeze out of it, +> they probably would have done it before publishing the zip! The following code re-creates the `` using the GitHub logo **`SVG`** and `CSS` for layout/style: ```html -
- +
+ - -
- - - - - + margin-top: 12px" + > +
+ + + + +
-
- Sign in with Google -
+
Sign in with Google
``` > We created this from scratch using the SVG of the Google logo -and some basic CSS.
-For the "making of" journey see: -https://github.com/dwyl/elixir-auth-google/issues/25 +> and some basic CSS.
+> For the "making of" journey see: +> https://github.com/dwyl/elixir-auth-google/issues/25 The result looks _better_ than the `` button: @@ -336,7 +412,6 @@ It can be scaled to any screen size so it will _always_ look great!
Using http://bytesizematters.com we see that our SVG+CSS button is only **`1kb`**: ![bytesize-matters-google-button](https://user-images.githubusercontent.com/194400/73607378-4fe09b00-45ad-11ea-9ab1-3b383c1d4516.png) - That is an **87.5%** bandwidth saving on the **`8kb`** of the [**`.png`** button](https://github.com/dwyl/elixir-auth-google/issues/25). @@ -346,7 +421,6 @@ which means the page loads _even_ faster. This is used in the Demo app: [`lib/app_web/templates/page/index.html.eex`](https://github.com/dwyl/elixir-auth-google-demo/blob/4fdbeada2f13f4dd27d2372a916764ec7aad24e7/lib/app_web/templates/page/index.html.eex#L5-L26) - ### `i18n` The _biggest_ advantage of having an SVG+CSS button @@ -377,7 +451,6 @@ which is a lot better than nothing, the `SVG+CSS` button can be re-interpreted by a non-screen device and more easily transformed. -

## _Even_ More Detail 💡 @@ -398,27 +471,25 @@ including creating sessions and saving profile data to a database, take a look at our MVP: https://github.com/dwyl/app-mvp-phoenix -

## Notes 📝 -+ Official Docs for Google Identity Platform: -https://developers.google.com/identity/choose-auth - + Web specific sample code (JS): - https://developers.google.com/identity/sign-in/web -+ Google Sign-In for server-side apps: -https://developers.google.com/identity/sign-in/web/server-side-flow -+ Using OAuth 2.0 for Web Server Applications: -https://developers.google.com/identity/protocols/OAuth2WebServer -+ Google Auth Branding Guidelines: -https://developers.google.com/identity/branding-guidelines
-Only two colors are permitted for the button: -**white** `#FFFFFF` and **blue** `#4285F4` +- Official Docs for Google Identity Platform: + https://developers.google.com/identity/choose-auth + - Web specific sample code (JS): + https://developers.google.com/identity/sign-in/web +- Google Sign-In for server-side apps: + https://developers.google.com/identity/sign-in/web/server-side-flow +- Using OAuth 2.0 for Web Server Applications: + https://developers.google.com/identity/protocols/OAuth2WebServer +- Google Auth Branding Guidelines: + https://developers.google.com/identity/branding-guidelines
+ Only two colors are permitted for the button: + **white** `#FFFFFF` and **blue** `#4285F4` ![two-colors-of-google-auth-button](https://user-images.githubusercontent.com/194400/69634312-d9be3600-1049-11ea-9354-cdaa53f5c42b.png) - ### Fun Facts 📈📊 Unlike other "social media" companies, @@ -431,29 +502,29 @@ The following is a quick list of facts that make adding Google Auth to your App a compelling business case: -+ As of May 2019, there are over -[2.5 Billion](https://www.theverge.com/2019/5/7/18528297/google-io-2019-android-devices-play-store-total-number-statistic-keynote) -_active_ Android devices; -[87%](https://www.idc.com/promo/smartphone-market-share/os) global market share. -All these people have Google Accounts in order to use Google services. -+ YouTube has -[2 billion](https://www.businessofapps.com/data/youtube-statistics/) -monthly active YouTube users (_signed in with a Google Account_). -+ Gmail has -[1.5 Billion](https://www.thenewsminute.com/article/googles-gmail-turns-15-now-has-over-15-billion-monthly-active-users-99275) -monthly active users a -[27% share](https://seotribunal.com/blog/google-stats-and-facts) - of the global email client market. -+ [65%](https://techjury.net/stats-about/gmail-statistics) -of Small and Medium sized businesses use Google Apps for business. -+ [90%+](https://techjury.net/stats-about/gmail-statistics) -of startups use Gmail. This is a good _proxy_ for "early adopters". -+ [68%](https://eu.azcentral.com/story/opinion/op-ed/joannaallhands/2017/10/09/google-classroom-changing-teachers-students-education/708246001/) -of schools in the US use Google Classroom and related G-suite products.
-So the _next_ generation of internet/app users have Google accounts. -+ Google has -[90.46%](https://seotribunal.com/blog/google-stats-and-facts/) -of the search engine market share worldwide. 95.4% on Mobile. +- As of May 2019, there are over + [2.5 Billion](https://www.theverge.com/2019/5/7/18528297/google-io-2019-android-devices-play-store-total-number-statistic-keynote) + _active_ Android devices; + [87%](https://www.idc.com/promo/smartphone-market-share/os) global market share. + All these people have Google Accounts in order to use Google services. +- YouTube has + [2 billion](https://www.businessofapps.com/data/youtube-statistics/) + monthly active YouTube users (_signed in with a Google Account_). +- Gmail has + [1.5 Billion](https://www.thenewsminute.com/article/googles-gmail-turns-15-now-has-over-15-billion-monthly-active-users-99275) + monthly active users a + [27% share](https://seotribunal.com/blog/google-stats-and-facts) + of the global email client market. +- [65%](https://techjury.net/stats-about/gmail-statistics) + of Small and Medium sized businesses use Google Apps for business. +- [90%+](https://techjury.net/stats-about/gmail-statistics) + of startups use Gmail. This is a good _proxy_ for "early adopters". +- [68%](https://eu.azcentral.com/story/opinion/op-ed/joannaallhands/2017/10/09/google-classroom-changing-teachers-students-education/708246001/) + of schools in the US use Google Classroom and related G-suite products.
+ So the _next_ generation of internet/app users have Google accounts. +- Google has + [90.46%](https://seotribunal.com/blog/google-stats-and-facts/) + of the search engine market share worldwide. 95.4% on Mobile. Of the 4.5 billion internet users (58% of the world population), around 3.2 billion (72%) have a Google account. @@ -469,7 +540,7 @@ This is **`false`** and App developers have 100% control over what data is sent to (stored by) Google. An App can use Google Auth to _authenticate_ a person (_identify them and get read-only access - to their personal details like **first name** and **email address**_) +to their personal details like **first name** and **email address**_) without sending any data to Google. Yes, it will mean that Google "knows" that the person is _using_ your App, but it will not give Google any insight into _how_ they are using it diff --git a/config/test.exs b/config/test.exs index 0dd4ec4..3df37b5 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,4 +1,4 @@ -use Mix.Config +import Config config :elixir_auth_google, client_id: "631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com", diff --git a/create-google-app-guide.md b/create-google-app-guide.md index 557de78..d85c706 100644 --- a/create-google-app-guide.md +++ b/create-google-app-guide.md @@ -1,176 +1,102 @@ # Creating a Google Application for OAuth2 Authentication -This is a step-by-step guide -for creating a Google App from scratch -so that you can obtain the API keys -to add Google OAuth2 Authentication -to your Elixir App -and save the credentials to environment variables.
+This is a step-by-step guide for creating a Google App from scratch so that you can obtain the API keys to add Google OAuth2 Authentication to your Elixir App and save the credentials to environment variables. + Our guide follows the _official_ docs: -https://developers.google.com/identity/sign-in/web/server-side-flow
-We've added detail and screenshots to the steps -because some people have found the official Google API docs confusing.
-_This_ guide is checked periodically by the @dwyl team/community, -but Google are known to occasionally change their UI/Workflow, -so if anything has changed, or there are extra/fewer steps, -[please let us know!](https://github.com/dwyl/elixir-auth-google/issues) + + +We've added detail and screenshots to the steps because some people have found the official Google API docs confusing. + +_This_ guide is checked periodically by the @dwyl team/community, but Google are known to occasionally change their UI/Workflow, so if anything has changed, or there are extra/fewer steps, [please let us know!](https://github.com/dwyl/elixir-auth-google/issues) ## 1. Create a New Project -In your preferred web browser, -visit: -https://console.developers.google.com -and ensure you are authenticated with your Google Account -so you can see your "API & Services Dashboard": +In your preferred web browser, visit: + + + +and ensure you are authenticated with your Google Account so you can see your "API & Services Dashboard": elixir-auth-google-create-new-app If you don't already have a Google APIs project for your Elixir App, click the **CREATE** button on the dashboard. - ## 2. Define the Details for your New Project (App) -Enter the details for your App's **Project name** -and where appropriate input any additional/relevant info: +![new project](images/Screenshot%202022-10-27%20at%2011.24.10.png) -elixir-auth-google-app-details +![new project saved](images/Screenshot%202022-10-27%20at%2014.04.54.png) Click the **CREATE** button to create your project. - ## 3. OAuth Consent Screen -After creating the New Project, -the UI will return to the APIs dashboard -and the name of your app will appear in the top menu. - -Click the **OAuth Consent Screen** from the left side menu: - -elixir-auth-google-consent-screen +Click on "Google Cloud", then "Dashboard", then +**OAuth Consent Screen** from the left side menu: -Make the Application **`Public`** (_the default option_) and -input the same name as you used for your application in step 1. -Upload an image if you have one (_e.g: the icon/logo for your app_): +![Oauth consent](images/Screenshot%202022-10-27%20at%2014.18.31.png) -OAuth-consent-screen-1of2 +Set "external": -Leave the "**Scopes for Google APIs**" set to the default -**email**, **profile** and **openid**. +![external](images/Screenshot%202022-10-27%20at%2014.11.44.png) -No other data is required at this point, so skip the rest. +and enter the details for your App's **Project name** and where appropriate input any additional/relevant info: App name, the User support email and Developper contanct information. -Scroll down to the bottom and click "**Save**": -OAuth-consent-screen-2of2 +## 4. Create Credentials -This will take you to the API Credentials page. +Click on "Google Cloud", then "Dashboard", then "APIs and services" then "Credentials", then "+ CREATE CREDENTIALS", then "OAuth client ID". -## 4. Create Credentials +![credentials](images/Screenshot%202022-10-27%20at%2014.10.24.png) Click the **Create Credentials** button: -Screenshot 2019-11-26 at 23 24 28 -That will popup a menu from which you will select **OAuth Client ID**. - -You will see a form that allows you to specify -the details of your App for the credentials. - -Screenshot 2019-11-27 at 02 13 55 - -+ Application Type: Web application -+ Name: Elixir Auth Server -+ Authorized JavaScript origins: -http://localhost:4000 -(_the default for a Phoenix app on your local dev machine. - you can add your "production" URL later._) -+ Authorized redirect URIs: -http://localhost:4000/auth/google/callback -(_the endpoint to redirect to once authentication is successful. - again, add your production URL once you have auth working on `localhost`_) - -> Ensure you hit the enter key after pasting/typing -the URIs to ensure they are saved. -A common stumbling block is that URIs aren't saved. See: -https://stackoverflow.com/questions/24363041/redirect-uri-in-google-cloud-console-doesnt-save -Once you have input the relevant data click the **Create** button. +- Application Type: **Web application** +- Name: "_the one you want_" +- Authorized redirect URIs: **the same as the one yo udefined in your router**: + http://localhost:4000/auth/google/callback + (_the endpoint to redirect to once authentication is successful. + :exclamation: add your production URL once you have auth working on `localhost`_) + +Then save your credentials in `env. file: -> This form/step can be confusing at first, -but essentially you can have multiple credentials -for the same project, -e.g: if you had a Native Android App -you would create a new set of credentials -to ensure a separation of concerns between -server and client implementations. -For now just create the server (Elixir) credentials. - - -## 5. Download the OAuth Client Credentials - -After you click the **Create** button -in the **Create OAuth client ID** form (_step 4 above_), -you will be shown your OAuth client Credentials: - -elixir-auth-google-oauth-client-credentials - -Download the credentials, e.g: - -+ Client ID: 631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com -+ Client Secret: MHxv6-RGF5nheXnxh1b0LNDq - -> ⚠️ Don't worry, these keys aren't valid. -We deleted them immediately after capturing the screenshot -to avoid any security issues. -Obviously treat your credentials -like you would the username+password for your bank account; -never share a **real** Client ID or secret on GitHub -or any other public/insecure forum! - -You can also download the OAuth credentials as a json file: - -elixir-auth-google-json - -Example: -```json -{ - "web": { - "client_id": "631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com", - "project_id": "elixir-auth-demo", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_secret": "MHxv6-RGF5nheXnxh1b0LNDq", - "redirect_uris": [ - "http://localhost:4000/auth/google/callback" - ], - "javascript_origins": [ - "http://localhost:4000" - ] - } -} +```env +# .env +export GOOGLE_CLIENT_ID="935113971xxx.apps.googleusercontent.com" +export GOOGLE_CLIENT_SECRET="GOCSPX-5jxxx" ``` -> Again, for security reasons, -these credentials were -invalidated _immediately_ after downloading.
-But this is what the file looks like. +> **Note**: the "client-id" is acccessible but not the secret. In case you missed it, you can always generate another one with "RESET SECRET" + +![reset secret](images/Screenshot%202022-10-27%20at%2018.58.15.png) + +:red_circle: Authorized JavaScript origins: **with this library, you don't use Google's SDK so you don't need this**. + +> **Note**: if you want ot use **One tap**, then you need to pass an authorized url: in **DEV** mode, it is **AND** . +Then, don't forget to: -Return to step 3 of the -[README.md](https://github.com/dwyl/elixir-auth-google/blob/master/README.md) +![save](images/Screenshot%202022-10-27%20at%2018.45.46.png) +> A common stumbling block is that URIs aren't saved. See: +> https://stackoverflow.com/questions/24363041/redirect-uri-in-google-cloud-console-doesnt-save -
+Once you have input the relevant data click the **Create** button. + +> This form/step can be confusing at first, but essentially you can have multiple credentials for the same project, e.g: if you had a Native Android App you would create a new set of credentials to ensure a separation of concerns between server and client implementations. +> For now just create the server (Elixir) credentials. -# Note +## Update for production mode When you ship your app to your Production environment, -you will need to re-visit steps 3 & 4 -to update your app settings URL & callback -to reflect the URl where you are deploying your app e.g: +you will need to re-visit step 2 and 4 to update your app settings URL & callback to reflect the URl where you are deploying your app e.g: ![add-heroku-app](https://user-images.githubusercontent.com/194400/70204921-32f92a00-171a-11ea-83b2-34e5eeea777b.png) -In our case -https://elixir-auth-google-demo.herokuapp.com -and -https://elixir-auth-google-demo.herokuapp.com/auth/google/callback +In our case: + + + +and: + + diff --git a/images/Screenshot 2022-10-27 at 11.24.10.png b/images/Screenshot 2022-10-27 at 11.24.10.png new file mode 100644 index 0000000..3076c7e Binary files /dev/null and b/images/Screenshot 2022-10-27 at 11.24.10.png differ diff --git a/images/Screenshot 2022-10-27 at 11.25.41.png b/images/Screenshot 2022-10-27 at 11.25.41.png new file mode 100644 index 0000000..345ae4a Binary files /dev/null and b/images/Screenshot 2022-10-27 at 11.25.41.png differ diff --git a/images/Screenshot 2022-10-27 at 11.32.42.png b/images/Screenshot 2022-10-27 at 11.32.42.png new file mode 100644 index 0000000..b3d43f7 Binary files /dev/null and b/images/Screenshot 2022-10-27 at 11.32.42.png differ diff --git a/images/Screenshot 2022-10-27 at 14.04.54.png b/images/Screenshot 2022-10-27 at 14.04.54.png new file mode 100644 index 0000000..926613e Binary files /dev/null and b/images/Screenshot 2022-10-27 at 14.04.54.png differ diff --git a/images/Screenshot 2022-10-27 at 14.06.37.png b/images/Screenshot 2022-10-27 at 14.06.37.png new file mode 100644 index 0000000..7cb280f Binary files /dev/null and b/images/Screenshot 2022-10-27 at 14.06.37.png differ diff --git a/images/Screenshot 2022-10-27 at 14.10.24.png b/images/Screenshot 2022-10-27 at 14.10.24.png new file mode 100644 index 0000000..2528ceb Binary files /dev/null and b/images/Screenshot 2022-10-27 at 14.10.24.png differ diff --git a/images/Screenshot 2022-10-27 at 14.11.44.png b/images/Screenshot 2022-10-27 at 14.11.44.png new file mode 100644 index 0000000..e741da3 Binary files /dev/null and b/images/Screenshot 2022-10-27 at 14.11.44.png differ diff --git a/images/Screenshot 2022-10-27 at 14.12.48.png b/images/Screenshot 2022-10-27 at 14.12.48.png new file mode 100644 index 0000000..73fd614 Binary files /dev/null and b/images/Screenshot 2022-10-27 at 14.12.48.png differ diff --git a/images/Screenshot 2022-10-27 at 14.18.31.png b/images/Screenshot 2022-10-27 at 14.18.31.png new file mode 100644 index 0000000..4d8eb12 Binary files /dev/null and b/images/Screenshot 2022-10-27 at 14.18.31.png differ diff --git a/images/Screenshot 2022-10-27 at 18.45.46.png b/images/Screenshot 2022-10-27 at 18.45.46.png new file mode 100644 index 0000000..436d726 Binary files /dev/null and b/images/Screenshot 2022-10-27 at 18.45.46.png differ diff --git a/images/Screenshot 2022-10-27 at 18.58.15.png b/images/Screenshot 2022-10-27 at 18.58.15.png new file mode 100644 index 0000000..2fed56f Binary files /dev/null and b/images/Screenshot 2022-10-27 at 18.58.15.png differ diff --git a/lib/elixir_auth_google.ex b/lib/elixir_auth_google.ex index d544342..3ce8975 100644 --- a/lib/elixir_auth_google.ex +++ b/lib/elixir_auth_google.ex @@ -2,6 +2,7 @@ defmodule ElixirAuthGoogle do @moduledoc """ Minimalist Google OAuth Authentication for Elixir Apps. Extensively tested, documented, maintained and in active use in production. + It exposes two functions, `generate_oauth_url/2` and `get_profile/1`. """ @google_auth_url "https://accounts.google.com/o/oauth2/v2/auth?response_type=code" @google_token_url "https://oauth2.googleapis.com/token" @@ -36,18 +37,25 @@ defmodule ElixirAuthGoogle do @doc """ `generate_redirect_uri/1` generates the Google redirect uri based on conn """ - @spec generate_redirect_uri(conn) :: String.t() def generate_redirect_uri(conn) do get_baseurl_from_conn(conn) <> get_app_callback_url() end + @doc """ + Same as `generate_oauth_url/2` with `state` query parameter + """ + + # def generate_oauth_url(conn, state) when is_binary(state) do + # params = URI.encode_query(%{state: state}, :rfc3986) + # generate_oauth_url(conn) <> "&#{params}" + # end + @doc """ `generate_oauth_url/1` creates the Google OAuth2 URL with client_id, scope and redirect_uri which is the URL Google will redirect to when auth is successful. This is the URL you need to use for your "Login with Google" button. See step 5 of the instructions. """ - @spec generate_oauth_url(conn) :: String.t() def generate_oauth_url(conn) do query = %{ client_id: google_client_id(), @@ -56,39 +64,82 @@ defmodule ElixirAuthGoogle do } params = URI.encode_query(query, :rfc3986) - "#{@google_auth_url}&#{params}" end - @doc """ - Same as `generate_oauth_url/1` with `state` query parameter - """ - def generate_oauth_url(conn, state) when is_binary(state) do - params = URI.encode_query(%{state: state}, :rfc3986) - generate_oauth_url(conn) <> "&#{params}" - end - @doc """ `get_token/2` encodes the secret keys and authorization code returned by Google and issues an HTTP request to get a person's profile data. **TODO**: we still need to handle the various failure conditions >> issues/16 """ - @spec get_token(String.t(), conn) :: {:ok, map} | {:error, any} - def get_token(code, conn) do - body = - Jason.encode!(%{ - client_id: google_client_id(), - client_secret: google_client_secret(), - redirect_uri: generate_redirect_uri(conn), - grant_type: "authorization_code", - code: code - }) - - inject_poison().post(@google_token_url, body) - |> parse_body_response() + # <----- RM + # @spec get_token(String.t(), conn) :: {:ok, map} | {:error, any} + # def get_token(code, conn) do + # body = + # Jason.encode!(%{ + # client_id: google_client_id(), + # client_secret: google_client_secret(), + # redirect_uri: generate_redirect_uri(conn), + # grant_type: "authorization_code", + # code: code + # }) + + # inject_poison().post(@google_token_url, body) + # |> parse_body_response() + # end + + # <---- + + def get_profile(code, conn) do + Jason.encode!(%{ + client_id: google_client_id(), + client_secret: google_client_secret(), + redirect_uri: generate_redirect_uri(conn), + grant_type: "authorization_code", + code: code + }) + |> then(fn body -> + inject_poison().post(@google_token_url, body) + |> parse_status() + |> parse_response() + end) + end + + # Note: failure status are 400, 401, 404 depending on which input is corrupted. + # all tested: client_id, secret, redirect_uri, code, @google_token_url, @google-user_profile, params + def parse_status({:ok, %{status_code: 200}} = response) do + parse_body_response(response) + end + + def parse_status({:ok, _}), do: {:error, :bad_request} + # or more verbose with error status + # def parse_status({:ok, status}) do + # case status do + # %{status_code: 404} -> {:error, :wrong_url} + # %{status_code: 401} -> {:error, :unauthorized} + # %{status_code: 400} -> {:error, :wrong_code} + # _ -> {:error, :unknown_error} + # end + # end + + # def parse_body_response({:error, err}), do: {:error, err} + + def parse_body_response({:ok, %{body: nil}}), do: {:error, :no_body} + + def parse_body_response({:ok, %{body: body}}) do + {:ok, + body + |> Jason.decode!() + |> convert()} end + def convert(str_key_map) do + for {key, val} <- str_key_map, into: %{}, do: {String.to_atom(key), val} + end + + def parse_response({:error, response}), do: {:error, response} + def parse_response({:ok, response}), do: get_user_profile(response.access_token) + @doc """ `get_user_profile/1` requests the Google User's userinfo profile data providing the access_token received in the `get_token/1` above. @@ -97,51 +148,67 @@ defmodule ElixirAuthGoogle do **TODO**: we still need to handle the various failure conditions >> issues/16 At this point the types of errors we expect are HTTP 40x/50x responses. """ - @spec get_user_profile(String.t()) :: {:ok, map} | {:error, any} - def get_user_profile(token) do - params = URI.encode_query(%{access_token: token}, :rfc3986) - "#{@google_user_profile}?#{params}" - |> inject_poison().get() - |> parse_body_response() + # @spec get_user_profile(String.t()) :: {:ok, map} | {:error, any} + # def get_user_profile(token) do + # params = URI.encode_query(%{access_token: token}, :rfc3986) + + # "#{@google_user_profile}?#{params}" + # |> inject_poison().get() + # |> parse_body_response() + # end + + # <---- CHANGED + def get_user_profile(access_token) do + access_token + |> encode() + |> then(fn params -> + (@google_user_profile <> "?" <> params) + |> inject_poison().get() + |> parse_status() + end) end + # <---- + + def encode(token), do: URI.encode_query(%{access_token: token}, :rfc3986) + @doc """ `parse_body_response/1` parses the response returned by Google so your app can use the resulting JSON. """ - @spec parse_body_response({atom, String.t()} | {:error, any}) :: {:ok, map} | {:error, any} - def parse_body_response({:error, err}), do: {:error, err} - - def parse_body_response({:ok, response}) do - body = Map.get(response, :body) - # make keys of map atoms for easier access in templates - if body == nil do - {:error, :no_body} - else - {:ok, str_key_map} = Jason.decode(body) - atom_key_map = for {key, val} <- str_key_map, into: %{}, do: {String.to_atom(key), val} - {:ok, atom_key_map} - end - - # https://stackoverflow.com/questions/31990134 - end - defp google_client_id do + # @spec parse_body_response({atom, String.t()} | {:error, any}) :: {:ok, map} | {:error, any} + # def parse_body_response({:error, err}), do: {:error, err} + + # def parse_body_response({:ok, response}) do + # body = Map.get(response, :body) + # # make keys of map atoms for easier access in templates + # if body == nil do + # {:error, :no_body} + # else + # {:ok, str_key_map} = Jason.decode(body) + # atom_key_map = for {key, val} <- str_key_map, into: %{}, do: {String.to_atom(key), val} + # {:ok, atom_key_map} + # end + + # # https://stackoverflow.com/questions/31990134 + # end + + def google_client_id do System.get_env("GOOGLE_CLIENT_ID") || Application.get_env(:elixir_auth_google, :client_id) end - defp google_client_secret do + def google_client_secret do System.get_env("GOOGLE_CLIENT_SECRET") || Application.get_env(:elixir_auth_google, :client_secret) end - defp google_scope do + def google_scope do System.get_env("GOOGLE_SCOPE") || Application.get_env(:elixir_auth_google, :google_scope) || @default_scope end - defp get_app_callback_url do + def get_app_callback_url do System.get_env("GOOGLE_CALLBACK_PATH") || Application.get_env(:elixir_auth_google, :callback_path) || @default_callback_path end diff --git a/lib/httpoison_mock.ex b/lib/httpoison_mock.ex index bf29853..4933c5d 100644 --- a/lib/httpoison_mock.ex +++ b/lib/httpoison_mock.ex @@ -8,14 +8,21 @@ defmodule ElixirAuthGoogle.HTTPoisonMock do get/1 passing in the wrong_token is used to test failure in the auth process. Obviously, don't invoke it from your App unless you want people to see fails. """ - def get("https://www.googleapis.com/oauth2/v3/userinfo?access_token=wrong_token") do - {:error, :bad_request} + @wrong_url "https://www.googleapis.com/oauth2/v3/userinfo?access_token=wrong_token" + def get(@wrong_url) do + {:ok, %{status_code: 400}} + end + + @bad_profile "https://www.googleapis.com/oauth2/v3/userinfo?access_token=bad_profile" + def get(@bad_profile) do + {:ok, {:error, :bad_request}} end # get/1 using a dummy _url to test body decoding. def get(_url) do {:ok, %{ + status_code: 200, body: Jason.encode!(%{ email: "nelson@gmail.com", @@ -33,7 +40,24 @@ defmodule ElixirAuthGoogle.HTTPoisonMock do @doc """ post/2 passing in dummy _url & _body to test return of access_token. """ - def post(_url, _body) do - {:ok, %{body: Jason.encode!(%{access_token: "token1"})}} + + def post(_url, body) do + case Jason.decode!(body) do + %{"code" => "body_nil"} -> + {:ok, %{status_code: 200, body: nil}} + + %{"code" => "bad_profile"} -> + {:ok, %{status_code: 200, body: Jason.encode!(%{access_token: "bad_profile"})}} + + %{"code" => "123"} -> + {:ok, + %{ + status_code: 200, + body: Jason.encode!(%{access_token: "token1"}) + }} + + %{"code" => "wrong_token"} -> + {:ok, %{status_code: 400}} + end end end diff --git a/mix.exs b/mix.exs index 5b6670f..55dad5c 100644 --- a/mix.exs +++ b/mix.exs @@ -2,13 +2,13 @@ defmodule ElixirAuthGoogle.MixProject do use Mix.Project @description "Minimalist Google OAuth Authentication for Elixir Apps" - @version "1.6.3" + @version "1.6.4" def project do [ app: :elixir_auth_google, version: @version, - elixir: ">= 1.11.0", + elixir: ">= 1.14.1", start_permanent: Mix.env() == :prod, deps: deps(), description: @description, @@ -33,17 +33,17 @@ defmodule ElixirAuthGoogle.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:credo, "~> 1.5", only: [:dev, :test], runtime: false}, + {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, {:httpoison, "~> 1.8.0"}, - {:jason, "~> 1.2"}, + {:jason, "~> 1.4"}, # tracking test coverage - {:excoveralls, "~> 0.14.1", only: [:test, :dev]}, + {:excoveralls, "~> 0.15", only: [:test, :dev]}, # mock stuffs in test - {:mock, "~> 0.3.0", only: :test}, + {:mock, "~> 0.3.7", only: :test}, # documentation - {:ex_doc, "~> 0.25.3", only: :dev} + {:ex_doc, "~> 0.29", only: :dev} ] end diff --git a/mix.lock b/mix.lock index 6979baa..f75fd83 100644 --- a/mix.lock +++ b/mix.lock @@ -1,24 +1,24 @@ %{ - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"}, - "credo": {:hex, :credo, "1.6.2", "2f82b29a47c0bb7b72f023bf3a34d151624f1cbe1e6c4e52303b05a11166a701", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "ae9dc112bc368e7b145c547bec2ed257ef88955851c15057c7835251a17211c6"}, + "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, + "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, "earmark": {:hex, :earmark, "1.4.9", "837e4c1c5302b3135e9955f2bbf52c6c52e950c383983942b68b03909356c0d9", [:mix], [{:earmark_parser, ">= 1.4.9", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "0d72df7d13a3dc8422882bed5263fdec5a773f56f7baeb02379361cb9e5b0d8e"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.15", "b29e8e729f4aa4a00436580dcc2c9c5c51890613457c193cc8525c388ccb2f06", [:mix], [], "hexpm", "044523d6438ea19c1b8ec877ec221b008661d3c27e3b848f4c879f500421ca5c"}, - "ex_doc": {:hex, :ex_doc, "0.25.3", "3edf6a0d70a39d2eafde030b8895501b1c93692effcbd21347296c18e47618ce", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "9ebebc2169ec732a38e9e779fd0418c9189b3ca93f4a676c961be6c1527913f5"}, - "excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, + "ex_doc": {:hex, :ex_doc, "0.29.0", "4a1cb903ce746aceef9c1f9ae8a6c12b742a5461e6959b9d3b24d813ffbea146", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "f096adb8bbca677d35d278223361c7792d496b3fc0d0224c9d4bc2f651af5db1"}, + "excoveralls": {:hex, :excoveralls, "0.15.0", "ac941bf85f9f201a9626cc42b2232b251ad8738da993cf406a4290cacf562ea4", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9631912006b27eca30a2f3c93562bc7ae15980afb014ceb8147dc5cdd8f376f1"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"}, - "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"}, + "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, - "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, diff --git a/test/elixir_auth_google_test.exs b/test/elixir_auth_google_test.exs index 50a9c4e..e28d1ec 100644 --- a/test/elixir_auth_google_test.exs +++ b/test/elixir_auth_google_test.exs @@ -16,8 +16,8 @@ defmodule ElixirAuthGoogleTest do test "get_baseurl_from_conn(conn) detects the URL for production" do conn = %{ - host: "dwyl.com", - port: 80 + host: "dwyl.com" + # port: 80 } assert ElixirAuthGoogle.get_baseurl_from_conn(conn) == "https://dwyl.com" @@ -44,63 +44,53 @@ defmodule ElixirAuthGoogleTest do assert url =~ "http%3A%2F%2Flocalhost%3A4000" end - test "get Google login url with state" do + test "get_profile/1" do conn = %{ host: "localhost", port: 4000 } - url = ElixirAuthGoogle.generate_oauth_url(conn, "state1") - id = System.get_env("GOOGLE_CLIENT_ID") - id_from_config = Application.get_env(:elixir_auth_google, :client_id) - - assert id == id_from_config - - expected = - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=" <> - id <> - "&redirect_uri=http%3A%2F%2Flocalhost%3A4000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&state=state1" + res = %{ + email: "nelson@gmail.com", + email_verified: true, + family_name: "Correia", + given_name: "Nelson", + locale: "en", + name: "Nelson Correia", + picture: "https://lh3.googleusercontent.com/a-/AAuE7mApnYb260YC1JY7a", + sub: "940732358705212133793" + } - assert url == expected + assert ElixirAuthGoogle.get_profile("123", conn) == {:ok, res} end - test "get Google token" do + test "no_body" do conn = %{ host: "localhost", port: 4000 } - {:ok, res} = ElixirAuthGoogle.get_token("ok_code", conn) - assert res == %{access_token: "token1"} + assert ElixirAuthGoogle.get_profile("body_nil", conn) == {:error, :no_body} end - test "get Google token (config redirect uri)" do + test "return error with incorrect token" do conn = %{ host: "localhost", port: 4000 } - {:ok, res} = ElixirAuthGoogle.get_token("ok_code", conn) - assert res == %{access_token: "token1"} - end + # fails at HTTP.GET for get_user_profile + assert ElixirAuthGoogle.get_profile("bad_profile", conn) == + {:error, :bad_request} - test "get_user_profile/1" do - res = %{ - email: "nelson@gmail.com", - email_verified: true, - family_name: "Correia", - given_name: "Nelson", - locale: "en", - name: "Nelson Correia", - picture: "https://lh3.googleusercontent.com/a-/AAuE7mApnYb260YC1JY7a", - sub: "940732358705212133793" - } - - assert ElixirAuthGoogle.get_user_profile("123") == {:ok, res} + # fails at HTTP.POST + assert ElixirAuthGoogle.get_profile("wrong_token", conn) == + {:error, :bad_request} end - test "return error with incorrect token" do - assert ElixirAuthGoogle.get_user_profile("wrong_token") == {:error, :bad_request} + test "get_user_profile return error with incorrect token for the Mock tests" do + assert ElixirAuthGoogle.get_user_profile("wrong_token") == + {:error, :bad_request} end test "generate_redirect_uri(conn) generate correct callback url" do @@ -121,7 +111,7 @@ defmodule ElixirAuthGoogleTest do mock_get_env = fn :elixir_auth_google, :callback_path -> "/special/callback" end - with_mock Application, [get_env: mock_get_env] do + with_mock Application, get_env: mock_get_env do assert ElixirAuthGoogle.generate_redirect_uri(conn) == "https://foobar.com/special/callback" end @@ -135,7 +125,7 @@ defmodule ElixirAuthGoogleTest do mock_get_env = fn "GOOGLE_CALLBACK_PATH" -> "/very/special/callback" end - with_mock System, [get_env: mock_get_env] do + with_mock System, get_env: mock_get_env do assert ElixirAuthGoogle.generate_redirect_uri(conn) == "https://foobar.com/very/special/callback" end