Giter Site home page Giter Site logo

Comments (11)

coladarci avatar coladarci commented on May 3, 2024 3

Figured it out, but leaving for the record:
Pow.Plug.put_config(plug: Pow.Plug.Session, otp_app: :kyodo_web)

I found this closed issue when trying to wire up login/registration via absinthe. While this is super helpful, I am running into problems when attempting to use this method. Is there something special about session/registrations, by any chance?

        %Plug.Conn{}
        |> Pow.Plug.put_config(otp_app: :kyodo_web)
        |> IO.inspect()
        |> Pow.Plug.authenticate_user(%{"email" => input.email, "password" => input.password})

Results in:
# Pow plug was not found in config. Please use a Pow plug that puts the :plug in the Pow configuration.

I'm aiming to move all of this functionality into Absinthe and then I'd be into making a pow-absinthe package if things go well... Either way, maybe I can help w/ the guides if there are some good learnings.

UPDATE: In digging through the tests it seems this has to do w/ not properly setting up the session in the conn. I will keep digging but this is starting to get hairy for me :)

from pow.

danschultzer avatar danschultzer commented on May 3, 2024 2

I got another idea for something that can make the integration cleaner.

First we'll pass the conn to the context:

  pipeline :graphql do
    plug MyAppWeb.Plugs.AbsintheContext
  end
defmodule MyAppWeb.Plugs.AbsintheContext do
  import Plug.Conn

  def init(default), do: default

  def call(conn, _default), do: Absinthe.Plug.put_options(conn, context: %{conn: conn})
end

Then we'll remove render handling in the resolvers as it should only return data:

defmodule GolfWeb.Resolvers.Accounts do
  def create_reset_token(_parent, %{email: email_address}, _resolution) do
    email_address
    |> Context.get_by_email(otp_app: :golf)
    |> maybe_generate_token()
  end

  defp maybe_generate_token(nil) do
    # error response
  end
  defp maybe_generate_token(user) do
    uuid = Pow.UUID.generate()
    backend = Pow.Config.get(otp_app: :golf, :cache_store_backend, Pow.Store.Backend.EtsCache)
    PowResetPassword.Store.ResetTokenCache.put([backend: backend], uuid, user)

    {:ok, %{user: user, token: token}}
  end
end

And finally handle the mail delivery in the response layer (like controller):

    @desc "Resets a password"
    field :reset_password, :user_email do
      arg(:email, non_null(:string))
      resolve(&Resolvers.Accounts.create_reset_token/3)
      middleware &send_reset_password_email/2
    end

    def send_reset_password_email(%{value: %{user: user, token: token}, context: %{conn: conn}} = resolution, _) do
      url = Routes.pow_reset_password_reset_password_url(
        GolfWeb.Endpoint,
        :edit,
        token
      )
      email = PowResetPassword.Phoenix.Mailer.reset_password(conn, user, url)

      Pow.Phoenix.Mailer.deliver(conn, email)

      %{resolution | value: %{email: user.email}}
    end

from pow.

glennr avatar glennr commented on May 3, 2024 2

In case this helps someone, I was trying to figure out how to deliver an invitation mail from a LiveComponent, and this issue put me on the right track i.e.:

  defp deliver_invitation_email(socket, user, invited_by) do
    conn = conn(socket)
    token = PowInvitation.Plug.sign_invitation_token(conn, user)
    url = Routes.pow_invitation_invitation_url(conn, :show, token)
    email = Mailer.invitation(conn, user, invited_by, url)

    Pow.Phoenix.Mailer.deliver(conn, email)
  end

  defp conn(%{endpoint: endpoint} = _socket) do
    Conn
    |> struct!(%{
      private: %{phoenix_endpoint: endpoint},
      secret_key_base: endpoint.config(:secret_key_base)
    })
    |> Pow.Plug.put_config(otp_app: :my_app)
  end

from pow.

vadimshvetsov avatar vadimshvetsov commented on May 3, 2024 2

I just want to note, that it isn't proper to pass conninto absinthe context and rely on it because GraphQL API is transport agnostic. In my opinion building new conn inside a resolver is the best workaround nowadays.

from pow.

axelclark avatar axelclark commented on May 3, 2024

Here is my current approach which seems to be working. I send the email address back regardless of whether the email is sent.

defmodule GolfWeb.Resolvers.Accounts do
  alias GolfWeb.Router.Helpers, as: Routes
  alias PowResetPassword.Phoenix.Mailer

  def create_reset_token(_parent, %{email: email_address}, _resolution) do
    config = Application.get_env(:golf, :pow, [])
    conn = Pow.Plug.put_config(%Plug.Conn{}, config)
    params = %{"email" => email_address}

    conn
    |> PowResetPassword.Plug.create_reset_token(params)
    |> respond_create(email_address)
  end

  defp respond_create({:ok, %{token: token, user: user}, conn}, email_address) do
    url =
      Routes.pow_reset_password_reset_password_url(
        GolfWeb.Endpoint,
        :edit,
        token
      )

    email = Mailer.reset_password(conn, user, url)
    Pow.Phoenix.Mailer.deliver(conn, email)

    {:ok, %{email: email_address}}
  end

  defp respond_create({:error, _, _}, email_address) do
    {:ok, %{email: email_address}}
  end
end

from pow.

danschultzer avatar danschultzer commented on May 3, 2024

The plug modules handles plug connections, so nearly all plug methods will be focused on conn transformations. This also includes using configuration from the connection which could be set in the endpoint rather than env config. With absinthe plug all of that will be bypassed. I've not worked with GraphQL/absinthe before, but I'll take a look at the absinthe and absinthe plug libraries to see if there is a cleaner way of handling this. It would be ideal if the connection could be passed along, but I'll also take a look at the API in Pow and see if it makes sense to separate some of the reset password method logic to make it easier for this type of customization.

But your approach looks great. Instead of loading the app env you could set it to load the :otp_app config the same way as in your endpoint:

    params = %{"email" => email_address}

    %Plug.Conn{}
    |> Pow.Plug.put_config(otp_app: :golf)
    |> PowResetPassword.Plug.create_reset_token(params)
    |> respond_create(email_address)

from pow.

danschultzer avatar danschultzer commented on May 3, 2024

Ok, I have given it some thought and your solution is probably the best one. Since this logic is spread over a controller and plug module, I don't think there's much that can be done to make a simpler integration.

I would probably try write some of these bits into smaller methods so refactoring will be easier if there'll be any breaking changes in Pow in the future:

defmodule GolfWeb.Resolvers.Accounts do
  alias GolfWeb.Router.Helpers, as: Routes

  def create_reset_token(_parent, %{email: email_address}, _resolution) do
    conn()
    |> PowResetPassword.Plug.create_reset_token(%{email: email_address})
    |> maybe_send_email(email_address)
  end

  defp maybe_send_email({:ok, %{token: token, user: user}, conn}, email_address) do
    deliver_email(conn, user, token)

    {:ok, %{email: email_address}}
  end
  defp maybe_send_email({:error, _, _}, email_address) do
    {:ok, %{email: email_address}}
  end

  defp conn(), do: Pow.Plug.put_config(%Plug.Conn{}, otp_app: :golf)

  defp deliver_email(conn, user, token) do
    url = reset_password_url(token)
    email = PowResetPassword.Phoenix.Mailer.reset_password(conn, user, url)

    Pow.Phoenix.Mailer.deliver(conn, email)
  end

  defp reset_password_url(token) do
    Routes.pow_reset_password_reset_password_url(
      GolfWeb.Endpoint,
      :edit,
      token
    )
  end
end

This is all about your preference though, the way you have written it is good as it is 😄

from pow.

dlobo avatar dlobo commented on May 3, 2024

This worked for me, though this line

defp conn(), do: Pow.Plug.put_config(%Plug.Conn{}, otp_app: :golf)

is giving me dialyzer errors about success typing and the contract. If I had to guess based on previous dialyzer battles, this is because the Plug.Conn typespec has keys which need a value (like pid), and the argument we are passing is primarily empty

I suppressed dialyzer for that file to make progress

from pow.

danschultzer avatar danschultzer commented on May 3, 2024

@dlobo good call, should probably do something similar to Plug.Adapters.Test.Conn: https://github.com/elixir-plug/plug/blob/f24af6a5b00dba4d32d44c6f2fb4423f6a5d8e3b/lib/plug/adapters/test/conn.ex#L34-L50

from pow.

dlobo avatar dlobo commented on May 3, 2024

In my case, the Pow calls just needed the config object (basically I wanted to delete the existing tokens in case the user updated the roles, so deleting the token in persistent and credentials cache)

so I just sent in the config as: [otp_app: :glific] and did not need the conn object (and hence did not need to get in via absinthe either)

from pow.

amitvchaudhary avatar amitvchaudhary commented on May 3, 2024

I am still getting this error "Pow plug was not found in config. Please use a Pow plug that puts the :plug in the Pow configuration."

config = Application.get_env(:myapp_api, :pow, [])
conn = Pow.Plug.put_config(%Plug.Conn{}, config)

def signin(conn, attrs \ %{}) do
user = pow_authenticate(%{"email" => attrs.email, "password" => attrs.password})

if user != nil && user.is_disabled do
  nil
else
  {result, connection} = conn
  |> Pow.Plug.authenticate_user(%{"email" => attrs.email, "password" => attrs.password})

  {result, connection, user}
end

end

from pow.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.