mirror of
https://github.com/ClaperCo/Claper.git
synced 2025-12-29 00:25:02 +01:00
Merge branch 'feature/password_login' into dev
This commit is contained in:
7361
assets/package-lock.json
generated
Normal file
7361
assets/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,24 @@ defmodule Claper.Accounts do
|
||||
Repo.get_by(User, email: email)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a user by email and password.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_user_by_email_and_password("foo@example.com", "correct_password")
|
||||
%User{}
|
||||
|
||||
iex> get_user_by_email_and_password("foo@example.com", "invalid_password")
|
||||
nil
|
||||
|
||||
"""
|
||||
def get_user_by_email_and_password(email, password)
|
||||
when is_binary(email) and is_binary(password) do
|
||||
user = Repo.get_by(User, email: email)
|
||||
if User.valid_password?(user, password), do: user
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single user.
|
||||
|
||||
@@ -135,6 +153,30 @@ defmodule Claper.Accounts do
|
||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, [context]))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the user password.
|
||||
## Examples
|
||||
iex> update_user_password(user, "valid password", %{password: ...})
|
||||
{:ok, %User{}}
|
||||
iex> update_user_password(user, "invalid password", %{password: ...})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
"""
|
||||
def update_user_password(user, password, attrs) do
|
||||
changeset =
|
||||
user
|
||||
|> User.password_changeset(attrs)
|
||||
|> User.validate_current_password(password)
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, changeset)
|
||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{user: user}} -> {:ok, user}
|
||||
{:error, :user, changeset, _} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Delivers the magic link email to the given user.
|
||||
|
||||
@@ -169,6 +211,47 @@ defmodule Claper.Accounts do
|
||||
UserNotifier.deliver_update_email_instructions(user, update_email_url_fun.(encoded_token))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for changing the user password.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_user_password(user)
|
||||
%Ecto.Changeset{data: %User{}}
|
||||
|
||||
"""
|
||||
def change_user_password(user, attrs \\ %{}) do
|
||||
User.password_changeset(user, attrs)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the user password.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_user_password(user, "valid password", %{password: ...})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> update_user_password(user, "invalid password", %{password: ...})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_user_password(user, %{"current_password" => curr_pw} = attrs) do
|
||||
changeset =
|
||||
user
|
||||
|> User.password_changeset(attrs)
|
||||
|> User.validate_current_password(curr_pw)
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, changeset)
|
||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{user: user}} -> {:ok, user}
|
||||
{:error, :user, changeset, _} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
|
||||
## Session
|
||||
|
||||
@doc """
|
||||
@@ -229,6 +312,68 @@ defmodule Claper.Accounts do
|
||||
end
|
||||
end
|
||||
|
||||
## Reset password
|
||||
|
||||
@doc """
|
||||
Delivers the reset password email to the given user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> deliver_user_reset_password_instructions(user, &Routes.user_reset_password_url(conn, :edit, &1))
|
||||
{:ok, %{to: ..., body: ...}}
|
||||
|
||||
"""
|
||||
def deliver_user_reset_password_instructions(%User{} = user, reset_password_url_fun)
|
||||
when is_function(reset_password_url_fun, 1) do
|
||||
{encoded_token, user_token} = UserToken.build_email_token(user, "reset_password")
|
||||
Repo.insert!(user_token)
|
||||
UserNotifier.deliver_reset_password_instructions(user, reset_password_url_fun.(encoded_token))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the user by reset password token.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_user_by_reset_password_token("validtoken")
|
||||
%User{}
|
||||
|
||||
iex> get_user_by_reset_password_token("invalidtoken")
|
||||
nil
|
||||
|
||||
"""
|
||||
def get_user_by_reset_password_token(token) do
|
||||
with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"),
|
||||
%User{} = user <- Repo.one(query) do
|
||||
user
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resets the user password.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reset_user_password(user, %{password: "new long password", password_confirmation: "new long password"})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> reset_user_password(user, %{password: "valid", password_confirmation: "not the same"})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def reset_user_password(user, attrs) do
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, User.password_changeset(user, attrs))
|
||||
|> Ecto.Multi.delete_all(:tokens, UserToken.user_and_contexts_query(user, :all))
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{user: user}} -> {:ok, user}
|
||||
{:error, :user, changeset, _} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Confirms a user by the given token.
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ defmodule Claper.Accounts.User do
|
||||
schema "users" do
|
||||
field :uuid, :binary_id
|
||||
field :email, :string
|
||||
field :password, :string, virtual: true, redact: true
|
||||
field :hashed_password, :string, redact: true
|
||||
field :is_admin, :boolean
|
||||
field :confirmed_at, :naive_datetime
|
||||
|
||||
@@ -14,10 +16,11 @@ defmodule Claper.Accounts.User do
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def registration_changeset(user, attrs, _opts \\ []) do
|
||||
def registration_changeset(user, attrs, opts \\ []) do
|
||||
user
|
||||
|> cast(attrs, [:email, :confirmed_at])
|
||||
|> cast(attrs, [:email, :confirmed_at, :password])
|
||||
|> validate_email()
|
||||
|> validate_password(opts)
|
||||
end
|
||||
|
||||
defp validate_email(changeset) do
|
||||
@@ -29,6 +32,26 @@ defmodule Claper.Accounts.User do
|
||||
|> unique_constraint(:email)
|
||||
end
|
||||
|
||||
defp validate_password(changeset, opts) do
|
||||
changeset
|
||||
|> validate_required([:password])
|
||||
|> validate_length(:password, min: 6, max: 72)
|
||||
|> maybe_hash_password(opts)
|
||||
end
|
||||
|
||||
defp maybe_hash_password(changeset, opts) do
|
||||
hash_password? = Keyword.get(opts, :hash_password, true)
|
||||
password = get_change(changeset, :password)
|
||||
|
||||
if hash_password? && password && changeset.valid? do
|
||||
changeset
|
||||
|> put_change(:hashed_password, Bcrypt.hash_pwd_salt(password))
|
||||
|> delete_change(:password)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
A user changeset for changing the email.
|
||||
|
||||
@@ -44,6 +67,32 @@ defmodule Claper.Accounts.User do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
A user changeset for changing the password.
|
||||
"""
|
||||
def password_changeset(user, attrs, opts \\ []) do
|
||||
user
|
||||
|> cast(attrs, [:password])
|
||||
|> validate_confirmation(:password, message: "does not match password")
|
||||
|> validate_password(opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Verifies the password.
|
||||
|
||||
If there is no user or the user doesn't have a password, we call
|
||||
`Bcrypt.no_user_verify/0` to avoid timing attacks.
|
||||
"""
|
||||
def valid_password?(%Claper.Accounts.User{hashed_password: hashed_password}, password)
|
||||
when is_binary(hashed_password) and byte_size(password) > 0 do
|
||||
Bcrypt.verify_pass(password, hashed_password)
|
||||
end
|
||||
|
||||
def valid_password?(_, _) do
|
||||
Bcrypt.no_user_verify()
|
||||
false
|
||||
end
|
||||
|
||||
@doc """
|
||||
Confirms the account by setting `confirmed_at`.
|
||||
"""
|
||||
@@ -52,4 +101,15 @@ defmodule Claper.Accounts.User do
|
||||
change(user, confirmed_at: now)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates the current password otherwise adds an error to the changeset.
|
||||
"""
|
||||
def validate_current_password(changeset, password) do
|
||||
if valid_password?(changeset.data, password) do
|
||||
changeset
|
||||
else
|
||||
add_error(changeset, :current_password, "is not valid")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -11,6 +11,7 @@ defmodule Claper.Accounts.UserToken do
|
||||
@change_email_validity_in_days 7
|
||||
@session_validity_in_days 60
|
||||
@confirm_magic_in_minutes 5
|
||||
@reset_password_validity_in_days 1
|
||||
|
||||
schema "users_tokens" do
|
||||
field :uuid, :binary_id
|
||||
@@ -158,6 +159,7 @@ defmodule Claper.Accounts.UserToken do
|
||||
|
||||
defp minutes_for_context("magic"), do: @confirm_magic_in_minutes
|
||||
defp days_for_context("confirm"), do: @confirm_validity_in_days
|
||||
defp days_for_context("reset_password"), do: @reset_password_validity_in_days
|
||||
|
||||
@doc """
|
||||
Checks if the token is valid and returns its underlying lookup query.
|
||||
|
||||
@@ -133,7 +133,7 @@ defmodule ClaperWeb.UserAuth do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> redirect(to: Routes.user_registration_path(conn, :confirm))
|
||||
#|> redirect(to: Routes.user_registration_path(conn, :confirm))
|
||||
end
|
||||
else
|
||||
conn
|
||||
|
||||
@@ -11,7 +11,7 @@ defmodule ClaperWeb.UserConfirmationController do
|
||||
if user = Accounts.get_user_by_email(email) do
|
||||
Accounts.deliver_user_confirmation_instructions(
|
||||
user,
|
||||
&Routes.user_confirmation_url(conn, :edit, &1)
|
||||
&Routes.user_confirmation_url(conn, :update, &1)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -25,10 +25,6 @@ defmodule ClaperWeb.UserConfirmationController do
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def edit(conn, %{"token" => token}) do
|
||||
render(conn, "edit.html", token: token)
|
||||
end
|
||||
|
||||
# Do not log in the user after confirmation to avoid a
|
||||
# leaked token giving the user access to the account.
|
||||
def update(conn, %{"token" => token}) do
|
||||
|
||||
@@ -20,7 +20,7 @@ defmodule ClaperWeb.UserRegistrationController do
|
||||
{:ok, _} =
|
||||
Accounts.deliver_user_confirmation_instructions(
|
||||
user,
|
||||
&Routes.user_confirmation_url(conn, :edit, &1)
|
||||
&Routes.user_confirmation_url(conn, :update, &1)
|
||||
)
|
||||
|
||||
conn
|
||||
|
||||
46
lib/claper_web/controllers/user_reset_password_controller.ex
Normal file
46
lib/claper_web/controllers/user_reset_password_controller.ex
Normal file
@@ -0,0 +1,46 @@
|
||||
defmodule ClaperWeb.UserResetPasswordController do
|
||||
use ClaperWeb, :controller
|
||||
import Phoenix.LiveView.Controller
|
||||
|
||||
alias Claper.Accounts
|
||||
|
||||
plug(:get_user_by_reset_password_token when action in [:edit])
|
||||
|
||||
def new(conn, _params) do
|
||||
render(conn, "new.html")
|
||||
end
|
||||
|
||||
def create(conn, %{"user" => %{"email" => email}}) do
|
||||
if user = Accounts.get_user_by_email(email) do
|
||||
Accounts.deliver_user_reset_password_instructions(
|
||||
user,
|
||||
&Routes.user_reset_password_url(conn, :edit, &1)
|
||||
)
|
||||
end
|
||||
|
||||
# Regardless of the outcome, show an impartial success/error message.
|
||||
conn
|
||||
|> put_flash(
|
||||
:info,
|
||||
"If your email is in our system, you'll receive instructions to reset your password shortly."
|
||||
)
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def edit(conn, _params) do
|
||||
live_render(conn, ClaperWeb.UserLive.ResetPassword)
|
||||
end
|
||||
|
||||
defp get_user_by_reset_password_token(conn, _opts) do
|
||||
%{"token" => token} = conn.params
|
||||
|
||||
if user = Accounts.get_user_by_reset_password_token(token) do
|
||||
put_session(conn, "user_id", user.id)
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, "Reset password link is invalid or it has expired.")
|
||||
|> redirect(to: "/")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,11 +9,20 @@ defmodule ClaperWeb.UserSessionController do
|
||||
|> render("new.html", error_message: nil)
|
||||
end
|
||||
|
||||
def create(conn, %{"user" => %{"email" => email}} = _user_params) do
|
||||
Accounts.deliver_magic_link(email, &Routes.user_confirmation_url(conn, :confirm_magic, &1))
|
||||
#def create(conn, %{"user" => %{"email" => email}} = _user_params) do
|
||||
# Accounts.deliver_magic_link(email, &Routes.user_confirmation_url(conn, :confirm_magic, &1))
|
||||
|
||||
conn
|
||||
|> redirect(to: Routes.user_registration_path(conn, :confirm, %{email: email}))
|
||||
# conn
|
||||
# |> redirect(to: Routes.user_registration_path(conn, :confirm, %{email: email}))
|
||||
#end
|
||||
def create(conn, %{"user" => user_params}) do
|
||||
%{"email" => email, "password" => password} = user_params
|
||||
|
||||
if user = Accounts.get_user_by_email_and_password(email, password) do
|
||||
UserAuth.log_in_user(conn, user, user_params)
|
||||
else
|
||||
render(conn, "new.html", error_message: "Invalid email or password")
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, _params) do
|
||||
|
||||
@@ -3,18 +3,24 @@ defmodule ClaperWeb.UserLiveAuth do
|
||||
alias ClaperWeb.Router.Helpers, as: Routes
|
||||
|
||||
def on_mount(:default, _params, %{"current_user" => current_user} = _session, socket) do
|
||||
if current_user.confirmed_at do
|
||||
socket =
|
||||
socket
|
||||
|> assign_new(:current_user, fn -> current_user end)
|
||||
socket =
|
||||
socket
|
||||
|> assign_new(:current_user, fn -> current_user end)
|
||||
|
||||
{:cont, socket}
|
||||
else
|
||||
{:halt,
|
||||
redirect(socket,
|
||||
to: Routes.user_registration_path(socket, :confirm, %{email: current_user.email})
|
||||
)}
|
||||
end
|
||||
{:cont, socket}
|
||||
|
||||
# if current_user.confirmed_at do
|
||||
# socket =
|
||||
# socket
|
||||
# |> assign_new(:current_user, fn -> current_user end)
|
||||
|
||||
# {:cont, socket}
|
||||
# else
|
||||
# {:halt,
|
||||
# redirect(socket,
|
||||
# to: Routes.user_registration_path(socket, :confirm, %{email: current_user.email})
|
||||
# )}
|
||||
# end
|
||||
end
|
||||
|
||||
def on_mount(:default, _params, _session, socket),
|
||||
|
||||
@@ -95,10 +95,15 @@ defmodule ClaperWeb.Router do
|
||||
scope "/", ClaperWeb do
|
||||
pipe_through([:browser, :redirect_if_user_is_authenticated])
|
||||
|
||||
get("/users/register", UserRegistrationController, :new)
|
||||
post("/users/register", UserRegistrationController, :create)
|
||||
get("/users/register/confirm", UserRegistrationController, :confirm)
|
||||
get("/users/log_in", UserSessionController, :new)
|
||||
post("/users/log_in", UserSessionController, :create)
|
||||
get("/users/magic/:token", UserConfirmationController, :confirm_magic)
|
||||
get("/users/reset_password", UserResetPasswordController, :new)
|
||||
post("/users/reset_password", UserResetPasswordController, :create)
|
||||
get("/users/reset_password/:token", UserResetPasswordController, :edit)
|
||||
end
|
||||
|
||||
scope "/", ClaperWeb do
|
||||
|
||||
15
lib/claper_web/templates/user_confirmation/new.html.heex
Normal file
15
lib/claper_web/templates/user_confirmation/new.html.heex
Normal file
@@ -0,0 +1,15 @@
|
||||
<h1>Resend confirmation instructions</h1>
|
||||
|
||||
<%= form_for :user, Routes.user_confirmation_path(@conn, :create), fn f -> %>
|
||||
<%= label f, :email %>
|
||||
<%= email_input f, :email, required: true %>
|
||||
|
||||
<div>
|
||||
<%= submit "Resend confirmation instructions" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
<%= link "Register", to: Routes.user_registration_path(@conn, :new) %> |
|
||||
<%= link "Log in", to: Routes.user_session_path(@conn, :new) %>
|
||||
</p>
|
||||
@@ -19,11 +19,12 @@
|
||||
<ClaperWeb.Component.Alert.error message={gettext "Oops, check that all fields are filled in correctly."} stick={true} />
|
||||
<% end %>
|
||||
<input type="hidden" name="remember" value="true">
|
||||
<ClaperWeb.Component.Input.email form={f} key={:email} labelClass="text-white" fieldClass="bg-gray-700" name={gettext "Email"} required="true" />
|
||||
<ClaperWeb.Component.Input.email form={f} key={:email} labelClass="text-white" fieldClass="bg-gray-700 text-white" name={gettext "Email"} required="true" />
|
||||
<ClaperWeb.Component.Input.password form={f} key={:password} labelClass="text-white" fieldClass="bg-gray-700 text-white" name={gettext "Password"} required="true" />
|
||||
|
||||
<div>
|
||||
<button type="submit" class="w-full flex justify-center text-white p-4 rounded-full tracking-wide font-bold focus:outline-none focus:shadow-outline shadow-lg bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500">
|
||||
Create account
|
||||
<%= gettext("Create account") %>
|
||||
</button>
|
||||
</div>
|
||||
</.form>
|
||||
|
||||
@@ -31,14 +31,21 @@
|
||||
<ClaperWeb.Component.Alert.error message={@error_message} stick={true} />
|
||||
<% end %>
|
||||
<input type="hidden" name="remember" value="true">
|
||||
<ClaperWeb.Component.Input.email form={f} key={:email} labelClass="text-white sr-only" placeholder={gettext "Enter your address email..."} fieldClass="bg-gray-700 text-white" name={gettext "Email"} autofocus="true" required="true" />
|
||||
<ClaperWeb.Component.Input.email form={f} key={:email} labelClass="text-white sr-only" placeholder={gettext "Your email address"} fieldClass="bg-gray-700 text-white" name={gettext "Email"} autofocus="true" required="true" />
|
||||
<ClaperWeb.Component.Input.password form={f} key={:password} labelClass="text-white sr-only" placeholder={gettext "Your password"} fieldClass="bg-gray-700 text-white" name={gettext "Password"} required="true" />
|
||||
|
||||
<div class="pt-5">
|
||||
<button type="submit" class="w-full flex justify-center text-white p-4 rounded-md tracking-wide font-bold focus:outline-none focus:shadow-outline shadow-lg bg-gradient-to-tl from-primary-500 to-secondary-500 bg-size-200 bg-pos-0 hover:bg-pos-100 transition-all duration-500">
|
||||
<%= gettext "Send link by email" %>
|
||||
<%= gettext "Login" %>
|
||||
</button>
|
||||
</div>
|
||||
</.form>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<%= if System.get_env("ENABLE_ACCOUNT_CREATION") == "true" do %>
|
||||
<%= link gettext("Create account"), to: Routes.user_registration_path(@conn, :new), class: "text-white text-sm text-center" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -215,18 +215,18 @@ defmodule ClaperWeb.Component.Input do
|
||||
assigns
|
||||
|> assign_new(:required, fn -> false end)
|
||||
|> assign_new(:autofocus, fn -> false end)
|
||||
|
||||
assigns =
|
||||
if Map.has_key?(assigns, :dark),
|
||||
do: assign(assigns, :containerTheme, "text-white"),
|
||||
else: assign(assigns, :containerTheme, "text-black")
|
||||
|> assign_new(:placeholder, fn -> false end)
|
||||
|> assign_new(:labelClass, fn -> "text-gray-700" end)
|
||||
|> assign_new(:fieldClass, fn -> "bg-white" end)
|
||||
|
||||
value = Map.get(assigns.form.data, assigns.key, "")
|
||||
|
||||
~H"""
|
||||
<div class="relative" x-data={"{input: '#{value}'}"} x-on:click="$refs.input.focus()">
|
||||
<%= password_input @form, @key, required: @required, autofocus: @autofocus, autocomplete: @key, class: "transition-all bg-transparent w-full #{@containerTheme} rounded px-3 border border-gray-500 focus:border-2 focus:border-primary-500 pt-5 pb-2 focus:outline-none input active:outline-none", "x-model": "input", "x-ref": "input" %>
|
||||
<%= label @form, @key, @name, class: "label absolute mb-0 -mt-2 pt-5 pl-3 leading-tighter text-gray-500 mt-2 cursor-text transition-all left-0", "x-bind:class": "input.length > 0 ? 'text-sm -top-1.5' : 'top-1'", "x-on:click": "$refs.input.focus()" %>
|
||||
<div class="relative" x-data={"{input: '#{value}'}"}>
|
||||
<%= label @form, @key, @name, class: "block text-sm font-medium #{@labelClass}" %>
|
||||
<div class="mt-1">
|
||||
<%= password_input @form, @key, required: @required, autofocus: @autofocus, placeholder: @placeholder, class: "#{@fieldClass} shadow-base block w-full text-lg focus:ring-primary-500 focus:ring-2 outline-none rounded-md py-4 px-3", "x-model": "input", "x-ref": "input" %>
|
||||
</div>
|
||||
<%= if Keyword.has_key?(@form.errors, @key) do %>
|
||||
<p class="text-supporting-red-500 text-sm"><%= error_tag @form, @key %></p>
|
||||
<% end %>
|
||||
|
||||
4
mix.lock
4
mix.lock
@@ -1,13 +1,13 @@
|
||||
%{
|
||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
|
||||
"castore": {:hex, :castore, "0.1.16", "2675f717adc700475345c5512c381ef9273eb5df26bdd3f8c13e2636cf4cc175", [:mix], [], "hexpm", "28ed2c43d83b5c25d35c51bc0abf229ac51359c170cba76171a462ced2e4b651"},
|
||||
"castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"},
|
||||
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
|
||||
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
|
||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
||||
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
||||
"dart_sass": {:hex, :dart_sass, "0.4.0", "50a0898faaa0b6584ee1a690f3aa02069b7aad7e873ddc4517a482ea39f3b476", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "27b7f4624fafdeb419d283f62425f6c623654f312455f53515ee6ef2144bf8de"},
|
||||
"dart_sass": {:hex, :dart_sass, "0.5.0", "c52ad951d9bf611399b6d5efbf404f58dc3a699753662081fbcd4752c6ddeed0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "bb76938174af8e047e855d3bb83e84ceca17e9acbf1ac6429f9d18171da4a911"},
|
||||
"db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"},
|
||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.26", "f4291134583f373c7d8755566122908eb9662df4c4b63caa66a0eabe06569b0a", [:mix], [], "hexpm", "48d460899f8a0c52c5470676611c01f64f3337bad0b26ddab43648428d94aabc"},
|
||||
|
||||
@@ -114,11 +114,6 @@ msgstr ""
|
||||
msgid "Host"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:38
|
||||
msgid "Send link by email"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/live/event_live/show.html.heex:117
|
||||
msgid "seconds"
|
||||
@@ -129,11 +124,6 @@ msgstr ""
|
||||
msgid "Create your first presentation"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:34
|
||||
msgid "Enter your address email..."
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:46
|
||||
#: lib/claper_web/live/event_live/manage.html.heex:29
|
||||
@@ -710,6 +700,7 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/live/event_live/join.html.heex:17
|
||||
#: lib/claper_web/live/event_live/join.html.heex:28
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:39
|
||||
msgid "Login"
|
||||
msgstr ""
|
||||
|
||||
@@ -723,3 +714,24 @@ msgstr ""
|
||||
#: lib/claper_web/live/event_live/show.html.heex:127
|
||||
msgid "Or use the code:"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:46
|
||||
msgid "Create account"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_registration/new.html.heex:23
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:35
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:34
|
||||
msgid "Your email address"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:35
|
||||
msgid "Your password"
|
||||
msgstr ""
|
||||
|
||||
@@ -115,11 +115,6 @@ msgstr ""
|
||||
msgid "Host"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:38
|
||||
msgid "Send link by email"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#: lib/claper_web/live/event_live/show.html.heex:117
|
||||
msgid "seconds"
|
||||
@@ -130,11 +125,6 @@ msgstr ""
|
||||
msgid "Create your first presentation"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:34
|
||||
msgid "Enter your address email..."
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:46
|
||||
#: lib/claper_web/live/event_live/manage.html.heex:29
|
||||
@@ -711,6 +701,7 @@ msgstr ""
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/live/event_live/join.html.heex:17
|
||||
#: lib/claper_web/live/event_live/join.html.heex:28
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:39
|
||||
msgid "Login"
|
||||
msgstr ""
|
||||
|
||||
@@ -724,3 +715,24 @@ msgstr ""
|
||||
#: lib/claper_web/live/event_live/show.html.heex:127
|
||||
msgid "Or use the code:"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:46
|
||||
msgid "Create account"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_registration/new.html.heex:23
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:35
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:34
|
||||
msgid "Your email address"
|
||||
msgstr ""
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:35
|
||||
msgid "Your password"
|
||||
msgstr ""
|
||||
|
||||
@@ -115,11 +115,6 @@ msgstr "Tableau de bord"
|
||||
msgid "Host"
|
||||
msgstr "Animateur"
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:38
|
||||
msgid "Send link by email"
|
||||
msgstr "Envoyer le lien par email"
|
||||
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#: lib/claper_web/live/event_live/show.html.heex:117
|
||||
msgid "seconds"
|
||||
@@ -130,11 +125,6 @@ msgstr "secondes"
|
||||
msgid "Create your first presentation"
|
||||
msgstr "Créez votre première présentation"
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:34
|
||||
msgid "Enter your address email..."
|
||||
msgstr "Entrez votre adresse email..."
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/live/event_live/event_card_component.ex:46
|
||||
#: lib/claper_web/live/event_live/manage.html.heex:29
|
||||
@@ -711,6 +701,7 @@ msgstr "A propos"
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/live/event_live/join.html.heex:17
|
||||
#: lib/claper_web/live/event_live/join.html.heex:28
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:39
|
||||
msgid "Login"
|
||||
msgstr "Connexion"
|
||||
|
||||
@@ -724,3 +715,24 @@ msgstr "Connectez-vous à votre compte"
|
||||
#: lib/claper_web/live/event_live/show.html.heex:127
|
||||
msgid "Or use the code:"
|
||||
msgstr "Ou utilisez le code:"
|
||||
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:46
|
||||
msgid "Create account"
|
||||
msgstr "Créer un compte"
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_registration/new.html.heex:23
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:35
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:34
|
||||
msgid "Your email address"
|
||||
msgstr "Adresse email"
|
||||
|
||||
#, elixir-autogen, elixir-format
|
||||
#: lib/claper_web/templates/user_session/new.html.heex:35
|
||||
msgid "Your password"
|
||||
msgstr "Votre mot de passe"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
defmodule Claper.Repo.Migrations.AddHashedPasswordToUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
add :hashed_password, :string, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user