Pow: Robust, modular, extendable user authentication and management system

Yes I know, that is what I am interested in :slight_smile:

It is not 30 seconds but 30 minutes If I read correctly, and I guess you could change that value to something like 24hours, and refresh it when you start the game.

Ah, right you are - itā€™s 30 minutes; however that is still short and I donā€™t want to weaken the session token strategy. The one-time ā€œremember meā€ cookie is the right tool for the job.

There is nothing public to share on the game server, but itā€™s not fancy - Iā€™m just storing account information for now. In the future, it will help with cloud services and multi-player access to game data.

Then yes I would accept and send cookies. But really there is no difference between a cookie and a token, both are headers here.

Right, so thatā€™s back to my question: whatā€™s the name of the cookie?

Sorry I do not have a running app at the moment to check but there is always the docs:

  • :persistent_session_cookie_key - session key name. This defaults to
    ā€œpersistent_sessionā€. If :otp_app is used itā€™ll automatically prepend
    the key with the :otp_app value.
1 Like

The API guide uses the PowPersistentSession.Store.PersistentSessionCache for renewal tokens which by default lasts 30 days. So all you have to do is to set up your client logic to renew the session with the renewal token when the session token no longer works (as is needed in any case, since the session token only lives for 30 minutes). You are able to renew the session for up to 30 days with the renewal token.

1 Like

Thank you very much, @danschultzer. That sounds perfect.

Iā€™d like to have a test in my app that creates an unconfirmed user (I have successfully enabled the confirmation module and have manually checked that my setup is working) and then tries to sign in with that. Pow.Plug.assign_current_user and Pow.Operations.authenticate donā€™t reject it so I am wondering what does?

Code:

# in another file (the ex_machina factory):

  def unconfirmed_user_factory do
    %User{
      email: Faker.Internet.email(),
      password: password()
    }
  end

# in the test file:

  @pow_config Application.fetch_env!(:my_app_name, :pow) # the app name is not a typo, just obfuscated after pasting it here.

  @email1 "u1@whatever.org"
  @pass1 "abcdefghi"

    test "sign in with unconfirmed user fails", %{conn: conn} do
      conn = Pow.Plug.put_config(conn, @pow_config)

      params =
        params_for(:unconfirmed_user, %{email: @email1, password: @pass1})
        |> Map.merge(%{password_confirmation: @pass1})

      {:ok, user} = Pow.Ecto.Context.create(params, [repo: Repo, user: User])

      Pow.Operations.authenticate(%{"email" => @email1, "password" => @pass1}, @pow_config)
  end

Whatā€™s even more puzzling is that the created user has unconfirmed_email equalling nil. email_confirmed_at is nil at least (and there is a value in the email_confirmation_token).

@danschultzer What am I doing wrong? I simply want to have one sanity test in place to catch me if I break the auth system in the future. How would you write a persistent ā€“ and non-persistent as well ā€“ test that creates an unconfirmed user, tries to sign in with it and properly receives an error?

Oh well, after some docs and sources reading, the test correctly detects a problem when trying to sign in with an unconfirmed user. Pasting the code for posterity:

  @pow_config Application.fetch_env!(:my_app_name, :pow)
  @email1 "u1@whatever.org"
  @pass1 "asdfghjk"

  test "trying to sign in with an unconfirmed user fails", %{conn: conn} do
    {:ok, _user} =
      Pow.Operations.create(
        %{email: @email1, password: @pass1, password_confirmation: @pass1},
        @pow_config
      )

    params = %{"user" => %{"email" => @email1, "password" => @pass1}}
    conn = Pow.Plug.put_config(conn, @pow_config)
    conn = post(conn, Routes.pow_session_path(conn, :create, params))
    err = get_flash(conn, "error")
    refute is_nil(err)
  end

Basically, donā€™t forget to put the Pow config in the Plug.Conn. And then search the Phoenix error flash for error(s).

2 Likes

Hey @danschultzer,

Please can you help again with this? I have a plug based on your suggestion that used to work fine untilā€¦ now Iā€™m having some dyalizer warning:

Function call/2 has no local return.ElixirLS Dialyzer

This is occuring on the line we have

config = Pow.Plug.fetch_config(conn)

After some search I found that Pow.Plug.fetch_config(conn) can return no_config_error() sometimes which result in a no_return() type.

Please, any suggestion on how to get rid of this warning?

Below my custom Plug:

defmodule MyApp.LoadClientProfilePlug do
  @doc false

  @spec init(any()) :: any()
  def init(opts), do: opts

  @doc false
  def call(conn = %{assigns: %{current_user: user = %{role: "client"}}}, _opts) do
    config = Pow.Plug.fetch_config(conn)
    preloaded_user = Core.Repo.preload(user, :profile)

    conn
    |> Pow.Plug.assign_current_user(preloaded_user, config)
  end
end

You can let dialyzer know with changing @spec call(Conn.t(), atom()) :: Conn.t()

To @spec call(Conn.t(), atom()) :: no_return() | Conn.t()

no_return | ā€¦ doesnā€™t make sense.

Either a function is no_return or it is not. But it canā€™t be both.

Iā€™m not an expert on dialyzer at all, but in the past when I had dialyzer warnings with any function that could raise then ā€˜no_return()ā€™ has always worked.
But there might be a specific type that encapsule a function that would raise on error or return a value?

Iā€™m intrested in hearing how you have solved these dialyzer warnings on function no_return?

1 Like

no_return means, this function will never return.

Never returning means, it will recurse infinitely.

Raising is always allowed, you do not need to annotate it, we are not doing Java here.

Dialyzer will never complain about a raise here or there, as long as it can proove you will return a value of the correct type at least sometimes.

It will complain though, if it does not find a way to properly return a value of the correct type, it will tell you something like ā€œfunction foo does not have a local returnā€.

Just adding no_return as one of the possible return types, tells dialyzer ā€œignore everything else, this function does not return everā€.

So instead of just adding no_return you should think about if that is right, and why dialyzer might think it is that way and then fix the code.

3 Likes

What version of ElixirLS are you using? I just tested with the plug and it doesnā€™t print any dialyzer warning for me (I also ran mix dialyzer just to be sure.). Using ElixirLS 0.2.25.

1 Like

Iā€™m using the vscode extension and itā€™s marked ElixirLS v0.5.0.

Iā€™m working in an umbrella project and running mix dyalizer at the root of the umbrealla or in its child app shows:

** (Mix) The task ā€œdyalizerā€ could not be found

Should I add any dependency?

My initial warning message
Function call/2 has no local return.ElixirLS Dialyzer
is shown only in vscode editor. When compiling the project in terminal and running Phoenix server I donā€™t see any warning. I have to say also that I just installed the ElixirLs vscode plugin but didnā€™t add any configuration.

Thank you all for your replies and for your time.

I think the issue has nothing to do with Pow. I just replaced the LoadProfilePlug with another Plug code that does not show any warning in its original context, and the warning remains in the LoadPlugProfile. That is to say no matter the code I put in it, the warning remains. The issue comes probably from the outside of the Plug, maybe because of how I call it? I will be checking that.

I feel really sorry for wasting you some time on this.
Thanks

The LS uses a bundled/vendored version of dialyxir, just to translate errors that have been found by dialyzer into elixir syntax.

If you want to run dialyzer from the terminal on your own, you will need to add the dialyxir package as a :dev dependency to your project.

A ā€œhas no local returnā€ is often hard to debug and can require knowledge of the full program context and its libraries. More than often, this errors origin was in a library that had some types of its own wrong, or a NIF-stub was not set up correctly.

1 Like

Fortunately erasing the .elixir_ls folder and let it be rebuilt made the warning gone. Thanks.