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

Web auth to authenticate CLI from browser #1024

Open
Benjamin-Philip opened this issue May 7, 2021 · 9 comments · May be fixed by #1058
Open

Web auth to authenticate CLI from browser #1024

Benjamin-Philip opened this issue May 7, 2021 · 9 comments · May be fixed by #1058

Comments

@Benjamin-Philip
Copy link
Contributor

Benjamin-Philip commented May 7, 2021

José mentioned that you (the hexpm team) are interested in creating a means of authenticating the CLI from the browser. Could I get some more info on this please ?

/cc @josevalim

@wojtekmach
Copy link
Member

wojtekmach commented May 7, 2021

Hey @Benjamin-Philip, thank you for your interest in this feature.

Currently, we authenticate with Hex using the command line:

~% mix hex.user auth
Username: wojtekmach
Account password: ******

It has a couple of issues:

By pushing more logic to the server side, we avoid having to duplicate the logic between clients (Hex, rebar3, etc). For authenticating with passwords in particular, using the Web will make it easier to leverage password managers like 1Password, etc.

In a nutshell we want to achieve something like this:

Screen.Recording.2021-05-07.at.10.10.47.mov

(I happened to be already logged-in on GitHub, otherwise I'd have to log in on that screen)

We need to add:

  • an endpoint to hex.pm
  • a function to hex_core to open up the website and after the authentication flow is over, save off the API token we got from the website
  • a mix hex.user auth --web option to Hex that uses the function from hex_core
  • same for rebar3_hex

This list of tasks roughly mirrors what we discussed on the WG: erlef/build-and-packaging-wg#21

I hope this gives you some context. I guess it is still low on details how exactly it is going to work on the technical side so if you decide to work on it, you'd need to do some experimentation (e.g. with GitHub flow to see how exactly they do it)

I think the next step would be to build a proof-of-concept, just adding an endpoint to hexpm and a mix hex.user auth --web task that does the simplest possible thing (I'd skip the 8-letter code for now, etc) and afterwards filling in the missing details and extracting code to hex_core.

@Benjamin-Philip
Copy link
Contributor Author

Benjamin-Philip commented May 7, 2021

@wojtekmach

I hope this gives you some context I guess it is still low on details how exactly it is going to work on the technical side so if you decide to work on it, you'd need to do some experimentation (e.g. with GitHub flow to see how exactly they do it)

Yes it does. Even though it is low on details, I was planning to look at the Github implementation as well.

I think the next step would be to build a proof-of-concept, just adding an endpoint to hexpm and a mix hex.user auth --web task that does the simplest possible thing (I'd skip the 8-letter code for now, etc) and afterwards filling in the missing details and extracting code to hex_core.

Ok will do. I'll notify you by email if I get it ready.

I'm unfamiliar with how hex manages authentication. So here are some questions:

  1. How do you save authenticated users on the website? Do you have a cookie that you update regularly ? (Just out of curiosity)
  2. In the web auth flow, what happens if a user is not authenticated even on his browser ? What if he has not even signed up ? Do we redirect him to the Login/Signup page, or do you have something else in mind ?
  3. Alternatively, do you plan to support logging in using OAuth with a connected google/github/facebook acount ?
  4. Is the API token saved by the current authentication method, or do you do something else ?

PS: Starting next week, (10th May) I'm going to get very busy, so updates are gonna be slow.

@wojtekmach
Copy link
Member

How do you save authenticated users on the website? Do you have a cookie that you update regularly ? (Just out of curiosity)

We have a cookie, see AuthHelpers module.

In the web auth flow, what happens if a user is not authenticated even on his browser ? What if he has not even signed up ? Do we redirect him to the Login/Signup page, or do you have something else in mind ?

If they are nit logged in they should be redirected to a login screen and upon logging in, they should be presented with the option to finish authorizing the CLI.

If the user doesn’t even have an account we could somehow explicitly handle that but I also think it’s ok if they'd restart the whole flow after signing up.

Alternatively, do you plan to support logging in using OAuth with a connected google/github/facebook acount ?

I think that is completely orthogonal so let’s not worry about that for now. But no, we have no plans for OAuth integration at the moment.

Is the API token saved by the current authentication method, or do you do something else ?

Yes, currently upon successful auth, the API token is saved to ~/.hex/hex.config and we’d continue to do so.

@Benjamin-Philip
Copy link
Contributor Author

@wojtekmach I was browsing through hex and rebar3_hex, and I noticed that a lot of logic is duplicated ( a good example of which is the API key generation). Why don't you write the tasks in hex_core, and write wrappers for mix and rebar ?

@wojtekmach
Copy link
Member

Yeah things like that can for sure be improved, please send PRs.

Benjamin-Philip added a commit to Benjamin-Philip/hexpm that referenced this issue Oct 4, 2021
The Hexpm team is looking for a means of authenticating a hex client
from the browser. The plan is to default to this new means of
authentication and deprecate the Username/Password authentication.

The github cli (https://github.com/cli/cli) had a similar problem. What
they did was implement the device OAuth
flow (https://datatracker.ietf.org/doc/html/rfc8628). I decided that a
flow of that sort would work very well for Hexpm.

However, Hexpm has no support for OAuth. So, I can only "mock" the
device flow. This commit does the following:

- Adds all the endpoints at the router for such a flow
- Creates a controller
- Add support for step 1 of the device flow
- Adds tests for the above functionality

See also: hexpm#1024, erlef/build-and-packaging-wg#21
@Benjamin-Philip
Copy link
Contributor Author

@wojtekmach and @ericmj, I am having some trouble with mocking logged in users in tests and key generation.

I am implementing an OAuth device flow like flow, with the only difference being that I am not bothering with a client_id.
There reason I chose to go with it is simply because, for token or key generation I haven't been able to find a better flow.
(There might be a more suitable flow, just that I am not aware of it.)

Now, I am currently implementing step 2 of the device flow where the user verifies a web_auth request by typing the user_code
in the website and a key is generated.

To generate a key, I am using Hexpm.Accounts.Keys.create like how its done in api/keys_controller. Keys.create needs audit data, which I obtain using audit_data(conn) in the WebAuth controller. I am currently having issues with passing the audit data to Keys.create.

When testing, I receive the following error:

1) test POST /web_auth/submit redirects to sucess page on valid user code (HexpmWeb.WebAuthControllerTest)
     test/hexpm_web/controllers/web_auth_controller.exs:71
     ** (KeyError) key :organization not found in: %{auth_source: nil, current_organization: nil, current_user: nil, email: nil, key: nil, user_agent: "missing"}
     code: |> post(Routes.web_auth_path(c.conn, :submit, request))
     stacktrace:
       (hexpm 0.0.1) lib/hexpm_web/controllers/web_auth_controller.ex:36: HexpmWeb.WebAuthController.submit/2
       (hexpm 0.0.1) lib/hexpm_web/controllers/web_auth_controller.ex:1: HexpmWeb.WebAuthController.action/2
       (hexpm 0.0.1) lib/hexpm_web/controllers/web_auth_controller.ex:1: HexpmWeb.WebAuthController.phoenix_controller_pipeline/2
       (phoenix 1.5.7) lib/phoenix/router.ex:352: Phoenix.Router.__call__/2
       (hexpm 0.0.1) lib/plug/error_handler.ex:65: HexpmWeb.Router.call/2
       (hexpm 0.0.1) lib/hexpm_web/endpoint.ex:1: HexpmWeb.Endpoint.plug_builder_call/2
       (hexpm 0.0.1) lib/hexpm_web/endpoint.ex:1: HexpmWeb.Endpoint.call/2
       (phoenix 1.5.7) lib/phoenix/test/conn_test.ex:225: Phoenix.ConnTest.dispatch/5
       test/hexpm_web/controllers/web_auth_controller.exs:82: (test)

For the following function call in the GenServer:

Keys.create(user_id, key_params, audit: audit)

Initially I felt something was wrong with the audit_data, but all the key_controller tests passed, which made me think that something was wrong with the way I am mocking logged in users. To mock users, I have I have the following setup function in my tests:

def setup_users(context) do
    user = insert(:user)
    organization = insert(:organization)
    insert(:organization_user, organization: organization, user: user)
    Map.merge(context, %{user: user, organization: organization})
end

with the following test:

  describe "POST /web_auth/submit" do
    setup :setup_users

    test "redirects to sucess page on valid user code", c do
      {:ok, user_code} =
        post(build_conn(), Routes.web_auth_path(build_conn(), :code, @test))
        |> json_response(200)
        |> Map.fetch("user_code")

      request = %{"user_code" => user_code, "user_id" => c.user.id}

      conn =
        c.conn
        |> test_login(c.user)
        |> post(Routes.web_auth_path(c.conn, :submit, request))

      assert redirected_to(conn, 200) =~ Routes.web_auth_path(conn, :success)
      assert html_response(conn, 200) =~ "Congratulations, you're all set!"
    end

    # more tests

  end

I am using test_login to mock logins as that's what used in the tfa_controller tests

The fact test_login isn't really doing anything was reinforced when I made the following IO.inspect calls in the controller:

_ = IO.inspect(conn.assigns.organization, label: "Conn OID")
_ = IO.inspect(conn.assigns.current_user, label: "Conn UID")

There was no printed output.

Now my question is this:

  1. How should I mock logged in users?
  2. Am I generating new keys correctly? If not, how should I do so?

(Note: the actual business logic is in a GenServer in lib/hexpm.)

@wojtekmach
Copy link
Member

test_login/2 is definitely the way to go. Keys.create is also good choice. Not sure off the top of my head what is wrong. feel free to send a work-in-progress pr to debug this issue.

@Benjamin-Philip
Copy link
Contributor Author

Ok. I will commit my files. Just note that the code is still in a mess and some function might be incomplete.

@Benjamin-Philip
Copy link
Contributor Author

@wojtekmach, I have created a draft PR at #1058.

Benjamin-Philip added a commit to Benjamin-Philip/hexpm that referenced this issue May 4, 2022
The Hexpm team is looking for a means of authenticating a hex client
from the browser. The plan is to default to this new means of
authentication and deprecate the Username/Password authentication.

The github cli (https://github.com/cli/cli) had a similar problem. What
they did was implement the device OAuth
flow (https://datatracker.ietf.org/doc/html/rfc8628). I decided that a
flow of that sort would work very well for Hexpm.

However, Hexpm has no support for OAuth. So, I can only "mock" the
device flow. This commit does the following:

- Adds all the endpoints at the router for such a flow
- Creates a controller
- Add support for step 1 of the device flow
- Adds tests for the above functionality

See also: hexpm#1024, erlef/build-and-packaging-wg#21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants