-
Notifications
You must be signed in to change notification settings - Fork 0
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
refactor: Replace Addict with Guardian #126
Conversation
aion/config/dev.exs
Outdated
|
||
config :guardian, Guardian, | ||
allowed_algos: ["HS512"], | ||
secret_key: "@11H41LW35tT3x45", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats super experimental PR, please do not laugh 😠
Added TODO section for easier tracking of a PR |
Note that this PR's main goal is to swap the libraries.
|
I'll make login/register page as one view. Idea of this PR is to make it work just as it was before. I agree that logout is not necessary but our users need to be able to register/login just as before. |
This PR should contain whatever changes are needed to prove that addict was removed totally and guardian was successfully introduced. From that point we can begin to work paralelly on issues connected to that - logout for example (users didn't have an option to do that so far). |
Everything is done, I think this is enough for first version. Im not sure about the case when the token expires but lets leave this case for now. Branch is also synchronised with master. But I have another issues that are more important:
== Compilation error on file lib/aion/endpoint.ex ==
** (UndefinedFunctionError) function Aion.Router.init/1 is undefined (module Aion.Router is not available)
Aion.Router.init([])
(plug) lib/plug/builder.ex:193: Plug.Builder.init_module_plug/3
(plug) lib/plug/builder.ex:181: anonymous fn/4 in Plug.Builder.compile/3
(elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
(plug) lib/plug/builder.ex:181: Plug.Builder.compile/3
(phoenix) expanding macro: Phoenix.Endpoint.__before_compile__/1
lib/aion/endpoint.ex:1: Aion.Endpoint (module) So 2 important checks wont pass on CI (tests wont run and dialyzer also screams) |
Start with manual tests, let's fix the CI and then start reviewing the code. Please checkout to this branch and if everything works for you as before. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of them are to-do-now
issues, some of them are to be discussed. Fantastic job in general 👏 🔥 🎆 ✨
Too bad the PR is so big and the review takes so long 😆
aion/config/travis.exs
Outdated
pool: Ecto.Adapters.SQL.Sandbox | ||
|
||
config :guardian, Guardian, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be one Guardian config only in config.exs, it should import a proper key from the environment (which will vary depending on the environment).
aion/mix.exs
Outdated
] | ||
{:postgrex, ">= 0.0.0"}, | ||
{:simetric, "~> 0.1.0"}, | ||
{:exrm, "~> 1.0.8"},] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move the bracket to a new line.
user = %{ email: "[email protected]", name: "something" } | ||
conn = Plug.Test.init_test_session(build_conn(), %{current_user: user}) | ||
{:ok, conn: put_req_header(conn, "accept", "application/json")} | ||
user = %Aion.User{ email: "[email protected]", name: "something", password: "2131231", id: 1 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be no spaces after {
and before }
user = %{ email: "[email protected]", name: "something"} | ||
conn = Plug.Test. init_test_session(build_conn(), %{current_user: user}) | ||
{:ok, conn: put_req_header(conn, "accept", "application/json")} | ||
user = %Aion.User{ email: "[email protected]", name: "something", password: "2131231", id: 1 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ x }
user = %{ email: "[email protected]", name: "something" } | ||
conn = Plug.Test. init_test_session(build_conn(), %{current_user: user}) | ||
{:ok, conn: put_req_header(conn, "accept", "application/json")} | ||
user = %Aion.User{ email: "[email protected]", name: "something", password: "2131231", id: 1 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ samehere }
Forms.updateFormInput form name value | ||
|
||
|
||
unwrapToken : Maybe String -> String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
getToken maybeToken =
case maybeToken of
Just token -> token
Nothing -> ""
aion/web/elm/src/Update.elm
Outdated
oldDisplayLoginInsteadOfRegistration = | ||
oldAuthData.displayLoginInsteadOfRegistration | ||
in | ||
case oldDisplayLoginInsteadOfRegistration of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will get hugely simplified when we introduce setters, so I'm not going to ask you to fix it now.
aion/web/elm/src/View.elm
Outdated
includeNavbar = | ||
case currentRoute of | ||
AuthRoute -> | ||
\x y z -> x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seriously, though, any idea for a better naming here? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plus if y and z are not needed then I suppose you could do \betterName _ _ -> betterName
|> validate_required([:name, :email, :encrypted_password]) | ||
|> cast(params, [:name, :email, :password]) | ||
|> validate_required([:name, :email, :password]) | ||
|> validate_length(:email, min: 3, max: 30) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could move constants like this to a separate module or just out of this function and not hardcode them. (especially the latter one shouldn't be a lot of work, you could deal with that using the module @attributes
which are conventional way of dealing with module constants in Elixir.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is out of scope of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True
aion/web/views/user_view.ex
Outdated
use Aion.Web, :view | ||
|
||
def render("user.json", %{user: user}) do | ||
if user != nil do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not sure about conventional way of dealing with nils, but this answer and Jose's answer suggest some solutions. I think the second one could actually be better (but it's just my personal opinion because I'm not a die hard fan of multiple function clauses). Here's how the first one could work:
def render("user.json", %user: user}) when is_nil user, do: %{}
def render("user.json", %user: user}), do: Map.fetch(user, [:id, :name, :email])
Similarly to my suggestion ^ in the second one you should definitely use Map.fetch/2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this work?
def render("user.json", %{user: nil}), do: %{}
def render("user.json", %{user: user}), do: Map.fetch(user, [:id, :name, :email])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Map.take/2
*
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah right, was looking at the docs and eventually used the wrong one
lgtm
@@ -16,4 +16,13 @@ registrationErrorToast = | |||
|
|||
registrationSuccessfulToast : ( Model, Cmd Msg ) -> ( Model, Cmd Msg ) | |||
registrationSuccessfulToast = | |||
addToast (Toasty.Defaults.Success "Success!" "Account created successfuly - proceed to Login.") | |||
addToast (Toasty.Defaults.Success "Success!" "Account created successfuly.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
successfully
user = %{ email: "[email protected]", name: "something" } | ||
conn = Plug.Test. init_test_session(build_conn(), %{current_user: user}) | ||
{:ok, conn: put_req_header(conn, "accept", "application/json")} | ||
user = %Aion.User{ email: "[email protected]", name: "something", password: "2131231", id: 1 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this piece of code repeats many times ;) How about extracting it somewhere ;)
def unauthenticated(conn, _params) do | ||
conn | ||
|> put_status(401) | ||
|> render("error.json", message: "Authentication required") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree but I would prefer to put it in authorization related module
I extracted unauthenticated function to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move the rest of the common errors to the error file. As for the rest of the issues, if something is not to be fixed during this PR just make sure there is an existing issue for that, so that we don't forget to fix it in the future (like the request stuff in Elm) 😃
aion/test/test_helper.exs
Outdated
@@ -2,3 +2,18 @@ ExUnit.start | |||
|
|||
Ecto.Adapters.SQL.Sandbox.mode(Aion.Repo, :manual) | |||
|
|||
defmodule Aion.TestHelpers do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def login_by_username_and_pass(conn, email, given_pass, opts) do | ||
repo = Keyword.fetch!(opts, :repo) | ||
user = repo.get_by(Aion.User, email: email) | ||
cond do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, let's leave it this way then
SessionController.create(conn, %{"email" => user.email, "password" => user.password}) | ||
{:error, changeset} -> | ||
conn | ||
|> put_status(:unprocessable_entity) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this would also end up in errors.ex
|> render("login.json", jwt: jwt) | ||
{:error, _reason, conn} -> | ||
conn | ||
|> put_status(500) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump
createQuestionWithAnswersRequest url token decoder body = | ||
Http.request | ||
{ method = "POST" | ||
, headers = [ Http.header "Authorization" ("Bearer " ++ token) ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
%{"data": "failed to authorise"} | ||
end | ||
|
||
def render("login.json", %{jwt: jwt}) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The jwt
cannot be changed to token
cause it would require a change in Guardian's implementation, right?
aion/web/views/errors.ex
Outdated
|
||
def unauthenticated(conn) do | ||
conn | ||
|> put_status(401) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tab here is redundant
aion/web/views/errors.ex
Outdated
@@ -0,0 +1,12 @@ | |||
defmodule Aion.ErrorCodesViews do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be easier if the module names resembled the file names, just like they did so far (e.g. RoomChannel -> room_channel.ex, UserRecord -> user_record.ex)
It would be even better if their whole name would represent the filepath, but it's not doable so easily with Phoenix 1.2 architecture. But the file names can mirror the module names at least, it's easier to search through the repo then.
Also, this is part of a controller, not of a view. I think putting it in the same directory as all the controllers would be alright (the code wouldn't move that far in the whole project directory). You could name it then like Aion.ControllerErrors
or something similar
I've moved the errors stuff to a separate module in controllers directory. I think it looks better now. Also, the imports used in all of the controllers can be stored in the One last thing that needs to be done is moving Guardian logic to We've moved to a lib that is widely used so you shouldn't have any problems finding resources in this area like this or this |
@mrapacz Issue with I've also added tests for I think we are ready to merge if all issues are resolved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I understand right the AuthConnCase sets up a logged in conn for the tests and ConnCase doesn't? Just small nits, looks good to me apart from that, good job 👍
|
||
login_result = Aion.Auth.login_by_username_and_pass(conn, @user.email, @user.password, repo: Aion.Repo) | ||
|
||
assert tuple_size(login_result) == 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for the future, I think in general we could match the exact results (if it's possible), cause just checking for list's/tuple's length may not be the most accurate thing.
This is what we've been doing so far, so no need to fix it, just suggesting that we could take another approach in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to do it but it was not possible since authenticated conn contains things that depend on time (I think some kind of timestamp).
Second thing is that the result is a tuple that is {:ok, login(conn, user)}
and login
method was tested above. Also its the only result that provides tuple that has length 2 so I think its fine (although not that scalable).
@@ -1,19 +1,3 @@ | |||
ExUnit.start |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this file is not needed anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is
19:52:22.599 [error] Task #PID<0.314.0> started from #PID<0.73.0> terminating
** (stop) exited in: GenServer.call(ExUnit.Server, {:take_async_cases, 16}, 60000)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
Also, make sure you update the description of PR and its title/commit message. As for the latter one - pro tip: |
|
Summary
Replaces Addict with Guardian. Basically changes whole authentication model.
Related issues
#39
Test plan
Manual + additional unit tests. Register and login are not integrated in Elm as a part of single page app.
TODO
NOTE(mrapacz|12-10-2017):
What still needs to be done before merging:
System.get_env