Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Google authentication #24

Merged
merged 98 commits into from
Dec 11, 2019
Merged

Google authentication #24

merged 98 commits into from
Dec 11, 2019

Conversation

SimonLab
Copy link
Member

ref: dwyl/app#247
Use elixir_auth_google to allow users to signin using OAuth2 Google.

@SimonLab SimonLab added the in-progress An issue or pull request that is being worked on by the assigned person label Nov 21, 2019
@SimonLab SimonLab self-assigned this Nov 21, 2019
@SimonLab
Copy link
Member Author

SimonLab commented Nov 21, 2019

Error on Travis due to a conflict of elixir versions. I think refreshing the mix.lock should fix the error (or rebuilding the application)
Edit: I need to update the elixir version in travis.yml configuration file

@nelsonic nelsonic temporarily deployed to production-google-authe-oif4on November 21, 2019 11:00 Inactive
@nelsonic nelsonic temporarily deployed to production-google-authe-oif4on November 21, 2019 11:03 Inactive
add :refresh_token, :string
add :key_id, :integer

add :person_id, references(:people, on_delete: :nothing)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


def change do
alter table("people") do
remove :key_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonLab :key_id is for storing the id of the encryption key used to encrypt the data in the same row as the data so that it's clear which key to use to decrypt it.
Are you planning to store the encryption key id somewhere else? 💭

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the key_id has been added to the sessions table
see dwyl/app#247 (comment)

Copy link
Member

@nelsonic nelsonic Nov 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonLab yeah, I saw that 👍
However I expect the data in these two tables will be encrypted with different encryption keys. If someone registers to use the App using just an email address and password dwyl/app#237 (a use-case we will 100% be adding to support people who don't have/use Google Accounts) Then we will need to have key_id in the people table to know which encryption key was used to encrypt their data on sign-up. On each subsequent login the session data (e.g: IP Address and Browser User Agent) will be encrypted and stored in the sessions table possibly with a different encryption key because of our key rotation system. 💭

Apologies that this is not clearly mapped out ... I don't expect you to guess what's in my head. 🙄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes makes sense, sorry I was focused on the google auth and hasn't thought of keeping the specific key_id for email/password authentication. I'll add it back 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad with the new solution for this. in-lining the key_id is more "user-friendly" (to devs) 👍

@nelsonic
Copy link
Member

@SimonLab do you have time to do a couple of pomodoros of pairing on this?
I want to run the branch on my localhost but I don't want to step on your toes while you are working.
Are you working on fixing the build? https://travis-ci.com/dwyl/app-mvp-phoenix/builds/138393162#L307

@SimonLab
Copy link
Member Author

SimonLab commented Nov 27, 2019

I was a bit lost on how the associations works with Ecto so I'm working on this at the moment, ie creating people and sessions. I haven't looked at the failing build yet. I'll be on zoom

@nelsonic
Copy link
Member

@SimonLab OK. happy to pair on any part of the work if you are.
LMK if you want to join the Zoom (standup) call earlier e.g: 16:40

lib/app/ctx.ex Outdated
def create_session(attrs \\ %{}) do
%Session{}
|> Session.changeset(attrs)
|> Ecto.build_ass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean Ecto.build_assoc ? 💭

mix.exs Outdated
@@ -44,7 +44,8 @@ defmodule App.MixProject do
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:elixir_auth_google, git: "https://github.com/dwyl/elixir-auth-google.git", branch: "master" },
{:fields, git: "https://github.com/dwyl/fields.git", branch: "update-ecto"}
{:fields, path: "/home/simon/Documents/dwyl/fields"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't work very well on CI ... 😜
(but I get it that you're testing ...) 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arf yes, well spotted, I'll add back the github link.
The Fields PR is now merged

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonLab thanks for merging the PR. 👍
I will add "enough" docs to fields to publish it to Hex.pm 📦
so we no longer have to use the GitHub link. 🙄

@nelsonic
Copy link
Member

@SimonLab it appears we are "suffering" from not having fields published on Hex.pm ... 😞
If you can take a look at my PR: dwyl/fields#51 🔍 🙏
I will work on documenting fields this morning so that we can ship it. 📦

@nelsonic
Copy link
Member

@SimonLab please ensure you have pushed your latest changes on this branch/PR so I can attempt to take a look at it over the weekend. (obvs you shouldn't be working on weekends, but I have to "catch up"...)
Thanks! ✨

@SimonLab
Copy link
Member Author

I think this PR is ready for review now that the email/password authentication has been added. I've had a looked and removed unnecessary config and logs but otherwise it looks good to me.

@SimonLab SimonLab assigned nelsonic and unassigned SimonLab Dec 11, 2019
@SimonLab SimonLab added awaiting-review An issue or pull request that needs to be reviewed and removed in-progress An issue or pull request that is being worked on by the assigned person labels Dec 11, 2019
google_client_id: System.get_env("GOOGLE_CLIENT_ID"),
google_client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
google_scope: "profile email",
google_redirect_uri: "http://localhost:4000/google/auth/callback"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@nelsonic nelsonic added in-review Issue or pull request that is currently being reviewed by the assigned person and removed awaiting-review An issue or pull request that needs to be reviewed labels Dec 11, 2019
@@ -16,3 +16,6 @@ config :app, AppWeb.Endpoint,

# Print only warnings and errors during test
config :logger, level: :warn

# mock elixir_auth_google
config :app, :elixir_auth_google, App.ElixirAuthGoogle.Mock
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth documenting how this works somewhere?

def get_person!(id), do: Repo.get!(Person, id)
def get_person!(id) do
Repo.get!(Person, id)
# |> Repo.preload(sessions: :person)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't need to preload the sessions, this function can be restored to def get_person!(id), do: Repo.get!(Person, id)
Not urgent.

@@ -581,4 +614,23 @@ defmodule App.Ctx do
def change_timer(%Timer{} = timer) do
Timer.changeset(timer, %{})
end

alias App.Ctx.Session
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm increasingly not a fan of the "Contexts" in Phoenix.
They don't help to organise code, they simply dump all code in to Ctx
that should be in the same file as the schema or controller.

Not a problem with this PR more something that I'm going to fix in a future refactor. 💭

end

def logout(conn) do
configure_session(conn, drop: true)
Copy link
Member

@nelsonic nelsonic Dec 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reading of this function is that it will only drop the session for one browser tab.
If the user has multiple and only performs logout in one, will their session still be valid in the others?
OR will the cookie be destroyed for all tabs and thus invalidate the session?
(That still does not mitigate against a cookie replay though...) 💭

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that the function will drop the session for the browser (ie all the tabs). I'll double check this on my localhost.

person = person_id && App.Ctx.get_person!(person_id) ->
assign(conn, :current_person, person)

true ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true catch all assigns :current_person as nil i.e. not authenticated. 👍

create_basic_session(conn, person)

{:error, %Ecto.Changeset{} = changeset} ->
url_oauth_google = ElixirAuthGoogle.generate_oauth_url(conn)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks very similar to the index function above. could we just invoke that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we redirect to the index page we will loose the errors information contained in the changeset and the error won't be displayed to the user

<section class="tc">
<%= form_for @changeset, Routes.page_path(@conn, :register), fn f -> %>
<div class="pa2">
<%= text_input f, :email, placeholder: "email" %>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Should be <input type="email" ... > to allow browser to auto-complete email address.
https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#email_input/3

Co-Authored-By: Nelson <nelson+github@dwyl.io>
@@ -17,10 +17,6 @@
<%= text_input f, :email %>
<%= error_tag f, :email %>

<%= label f, :email_hash %>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. thanks for removing. 👍

@nelsonic
Copy link
Member

on my localhost I ran the following commands:

git pull 
# branch google-authentication-#247
mix deps.get
mix ecto.migrate
mix phx.server

Server runs fine:
image

But when I attempt to login using Google I see the error:

image

When I run the following command to reset the DB:

mix ecto.reset
mix phx.server

I see the following error:
image

[debug] ** (Ecto.NoResultsError) expected at least one result but got none in query:

from p0 in App.Ctx.Person,
  where: p0.id == ^1

    (ecto) lib/ecto/repo/queryable.ex:107: Ecto.Repo.Queryable.one!/3
    (app) lib/app_web/controllers/auth_controller.ex:15: AppWeb.Auth.call/2
    (app) AppWeb.Router.browser/2
    (app) lib/app_web/router.ex:1: AppWeb.Router.__pipe_through0__/1
    (phoenix) lib/phoenix/router.ex:283: Phoenix.Router.__call__/2
    (app) lib/app_web/endpoint.ex:1: AppWeb.Endpoint.plug_builder_call/2
    (app) lib/plug/debugger.ex:122: AppWeb.Endpoint."call (overridable 3)"/2
    (app) lib/app_web/endpoint.ex:1: AppWeb.Endpoint.call/2
    (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
    (cowboy) /Users/n/code/app-mvp-phoenix/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
    (cowboy) /Users/n/code/app-mvp-phoenix/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3
    (cowboy) /Users/n/code/app-mvp-phoenix/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Apparently invoking App.Ctx.get_person!(person_id) on controllers/auth_controller.ex:15
fails catastrophically if there are zero people in the DB ... 😞

@SimonLab want to pair on resolving this?

@SimonLab
Copy link
Member Author

Yes I had the same issue on localhost. When you reset your database you will also need to delete the cookie session manually. I think you must have manage to login and your session still exists but now the database doesn't contains the user. This case shouldn't happen in "real life" but we should still had a condition to check for it


## Example

iex> transform_profile_data_to_person(%{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to update this example to use :atoms as keys in the Map.
Not necessary for this PR. but good to do in next iteration. 💭

|> Map.put(:picture, profile.picture)
end

@doc false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to avoid @doc false wherever possible. 🚫
It's the equivalent of this road (pointless) sign:
image

Copy link
Member

@nelsonic nelsonic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonLab PR looks good, thanks! 👍

@nelsonic nelsonic merged commit bd284db into master Dec 11, 2019
@nelsonic nelsonic deleted the google-authentication-#247 branch December 11, 2019 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in-review Issue or pull request that is currently being reviewed by the assigned person
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants