clifinger

clifinger

Hello,

I need Help!

I am totally new with Ash and I want to do a login action with a password verification,
Should I do an action or a function in my module ?

Showing Posts 1 to 10

zachdaniel

zachdaniel

Creator of Ash

There are numerous examples in the ash documentation on defining custom actions, please take a look at those. As for authentication specifically, see README — ash_authentication v4.14.1

clifinger

clifinger OP

Yes for Ash authentication, but for now I like to understand what I am I doing for learning purpose.

So I made user ressource and a custom register function with Argon2 and it’s work fine.
My question is about login, should I make a custom read action?

For now I did something dirty but working :

defmodule VsAppWeb.Accounts.User.UserLoginLive do
  use VsAppWeb, :live_view
  alias AshPhoenix.Form
  alias VsApp.Accounts

  def mount(_params, _session, socket) do
    # Créer le formulaire de login
    form = Accounts.User.form_to_login() |> to_form()
    {:ok, assign(socket, form: form, error_message: nil)}
  end

  def render(assigns) do
    ~H"""
    <div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
      <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
        <.form for={@form} phx-change="validate" phx-submit="login" class="space-y-6">
          <div>
            <.input field={@form[:email]} type="email" required label="Email" />
          </div>

          <div>
            <.input field={@form[:password]} type="password" required label="Password" />
          </div>

          <%= if @error_message do %>
            <div class="text-red-600 text-sm" role="alert">
              {@error_message}
            </div>
          <% end %>

          <div>
            <.button phx-disable-with="Signing in..." class="w-full">
              Sign in
            </.button>
          </div>
        </.form>
      </div>
    </div>
    """
  end

  def handle_event("validate", %{"form" => form_params}, socket) do
    form = socket.assigns.form

    form =
      form
      |> Form.validate(form_params)

    {:noreply, assign(socket, form: form)}
  end

  def handle_event("login", %{"form" => %{"email" => email, "password" => password}}, socket) do
    case Accounts.Verifier.authenticate_user(email, password) do
      {:ok, _} ->
        {:noreply,
         socket
         |> clear_flash()
         |> assign(error_message: nil)
         |> redirect(to: "/")}

      {:error, :invalid_credentials} ->
        {:noreply,
         socket
         |> assign(error_message: "Invalid email or password")}
    end
  end
end
clifinger

clifinger OP

Again, I don’t want to use Ash_Authentication for now, this is the way I learn :wink:

zachdaniel

zachdaniel

Creator of Ash

Understood. Your original question had very little context. If you clarify more about your question up front you’ll get clearer answers in general.

In AshAuthentication what we do is make a read action with a preparation that adds an after action hook with Ash.Query.after_action if a user is returned then we check the password. If it doesn’t match then we set the result to {:ok, []} otherwise we return the result as is. When no users are returned we do a dummy password check to protect against timing attacks.

With that read action, if you pass in the username and password (which will be an argument) and a user is returned, you know that it’s safe to log them in.

clifinger

clifinger OP

Yes, sorry about the context, I will try to be more explicit next time :slight_smile:

defmodule VsApp.Accounts.Validations.ValidLogin do
  @moduledoc """
  Login validation preparation
  """
  use Ash.Resource.Preparation
  require Logger
  require Ash.Query

  @impl true
  def prepare(query, _opts, _context) do
    password = Ash.Query.get_argument(query, :password)
    email = Ash.Query.get_argument(query, :email)

    query
    |> Ash.Query.filter(email == ^email)
    |> Ash.Query.load([:hashed_password])
    |> Ash.Query.after_action(fn _, user ->
      case user do
        [user] ->
          if Argon2.verify_pass(password, user.hashed_password) do
            {:ok, [user]}
          else
            {:ok, []}
          end

        [] ->
          Argon2.no_user_verify()
          {:ok, []}

        _ ->
          {:ok, []}
      end
    end)
  end
end

This one work for login, but now I try to add the validation like the preparation documentation and I don’t see any error in my form.

Please, Can you give me a validation example in the preparation ?

zachdaniel

zachdaniel

Creator of Ash

If you want to see an error in your form, return an error with a field on it

prepare fn query, _ -> 
  Ash.Query.add_error(query, field: :email, message: "is invalid")
end
clifinger

clifinger OP

I love the work you did and I am sure for “normal” crud operation it’s so powerful.

But for a simple read action, it’s too much frustration…

If I do Ash.Query.add_error(query, field: :email, message: "is invalid"):

[warning] Unhandled error in form submission for VsApp.Accounts.User.login

This error was unhandled because Ash.Error.Unknown.UnknownError does not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Unknown.UnknownError)

Maybe I will wait the documentation is more mature, because it’s so hard to too do simple things like a simple read action with validation.

My suggestion is to have exactly the same options for read as the other actions, if I do create :login my form worked fine and also my login action. Just I don’t like to make a create action with no mutation …

clifinger

clifinger OP

I am slower than when I dev in Rust :sweat_smile:

zachdaniel

zachdaniel

Creator of Ash

Definitely not a suggestion to make a create action with no mutation. It wouldn’t work anyway :slight_smile:

I understand if you’d rather not use it, but my suggestion is not to let small hurdles like this put you off :slight_smile: We work every day based on conversations like this one to make the docs and UX better. Despite how it may seem, Ash is for far more than “normal” crud :slight_smile: It is a very expansive framework and there are lots of places that need some docs love, but its not an indicator of the utility of the framework itself :slight_smile: The common theme among our users is slow to get started, massively productive down the road. If you don’t have time to learn a new thing w/ new patterns then it may not be a good idea to use Ash. It isn’t for everyone, some users don’t enjoy working this way, to each their own, no judgement :hugs:

In this case, we added shorthand for creating errors on actions in the way that I described, I was just mistaken that the shorthand had also been applied to read actions.

I’ve released v3.4.56 addressing this, and now the example call to Ash.Query.add_error/2 should work.

clifinger

clifinger OP

I’m fully aware of the work you’ve accomplished, and I believe I’ve gone through almost all your talks on YouTube. I’m going to give myself a little more time, about two more days, before making a final decision. I’m not saying this out of ego, but because I need to make a decisive choice for the company where I work as CTO, which leans towards a more full TypeScript/Node.js stack.

What reassures me is your availability and, of course, all the hard work you’ve put in so far. However, to be completely transparent and honest, I genuinely think the documentation has gaps. I won’t hesitate to contribute once I’m ready myself.

Thank you again for your response!

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews