Merge branch 'feature/password_login' into dev

This commit is contained in:
Alex
2022-09-05 12:24:53 +02:00
20 changed files with 7766 additions and 68 deletions

7361
assets/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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),

View File

@@ -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

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 %>

View File

@@ -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"},

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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"

View File

@@ -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