Add username as login option

This commit is contained in:
Ondřej 2024-08-09 11:32:42 +02:00
parent f540a6bb4d
commit e82c9c0bd4
7 changed files with 67 additions and 33 deletions

View file

@ -48,9 +48,15 @@ defmodule ChoreTracker.Accounts do
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)
def get_user_by_login_and_password(login, password)
when is_binary(login) and is_binary(password) do
user =
if String.contains?(login, "@") do
Repo.get_by(User, email: login)
else
Repo.get_by(User, username: login)
end
if User.valid_password?(user, password), do: user
end
@ -84,7 +90,7 @@ defmodule ChoreTracker.Accounts do
{:error, %Ecto.Changeset{}}
"""
def register_user(attrs, opts) do
def register_user(attrs, opts \\ []) do
%User{}
|> User.registration_changeset(attrs, opts)
|> Repo.insert()

View file

@ -3,6 +3,7 @@ defmodule ChoreTracker.Accounts.User do
import Ecto.Changeset
schema "users" do
field :username, :string
field :email, :string
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
@ -38,11 +39,23 @@ defmodule ChoreTracker.Accounts.User do
"""
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password, :display_name])
|> cast(attrs, [:username, :display_name, :email, :password])
|> validate_username()
|> validate_email(opts)
|> validate_password(opts)
end
defp validate_username(changeset) do
changeset
|> validate_required([:username])
|> validate_length(:username, min: 3, max: 30)
|> validate_format(:username, ~r/^[a-zA-Z0-9_]+$/,
message: "username can only include letters, numbers and underscores"
)
|> unsafe_validate_unique(:username, ChoreTracker.Repo)
|> unique_constraint(:username)
end
defp validate_email(changeset, opts) do
changeset
|> validate_required([:email])

View file

@ -19,9 +19,10 @@ defmodule ChoreTrackerWeb.UserSessionController do
end
defp create(conn, %{"user" => user_params}, info) do
%{"email" => email, "password" => password} = user_params
%{"password" => password} = user_params
login = user_params["login"] || user_params["username"] || user_params["email"]
if user = Accounts.get_user_by_email_and_password(email, password) do
if user = Accounts.get_user_by_login_and_password(login, password) do
conn
|> put_flash(:info, info)
|> UserAuth.log_in_user(user, user_params)
@ -29,7 +30,7 @@ defmodule ChoreTrackerWeb.UserSessionController do
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
conn
|> put_flash(:error, "Invalid email or password")
|> put_flash(:email, String.slice(email, 0, 160))
|> put_flash(:login, String.slice(login, 0, 160))
|> redirect(to: ~p"/users/log_in")
end
end

View file

@ -16,7 +16,7 @@ defmodule ChoreTrackerWeb.UserLoginLive do
</.header>
<.simple_form for={@form} id="login_form" action={~p"/users/log_in"} phx-update="ignore">
<.input field={@form[:email]} type="email" label="Email" required />
<.input field={@form[:login]} label="Username or email" required />
<.input field={@form[:password]} type="password" label="Password" required />
<:actions>

View file

@ -31,6 +31,8 @@ defmodule ChoreTrackerWeb.UserRegistrationLive do
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={@form[:username]} label="Username" required />
<.input field={@form[:display_name]} label="Display name" required />
<.input field={@form[:email]} type="email" label="Email" required />
<.input field={@form[:password]} type="password" label="Password" required />

View file

@ -5,6 +5,7 @@ defmodule ChoreTracker.Repo.Migrations.CreateUsersAuthTables do
execute "CREATE EXTENSION IF NOT EXISTS citext", ""
create table(:users) do
add :username, :citext, null: false
add :email, :citext, null: false
add :hashed_password, :string, null: false
add :confirmed_at, :utc_datetime
@ -13,6 +14,7 @@ defmodule ChoreTracker.Repo.Migrations.CreateUsersAuthTables do
timestamps(type: :utc_datetime)
end
create unique_index(:users, [:username])
create unique_index(:users, [:email])
create table(:users_tokens) do

View file

@ -10,35 +10,38 @@
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.
admin_password = "secret"
{:ok, admin} =
ondrej =
ChoreTracker.Accounts.register_user(
%{
username: "ondrej",
email: "ondrej@jednota.party",
password: admin_password,
password: "secret",
display_name: "Ondřej"
},
disable_password_validation: true
)
ChoreTracker.Accounts.register_user(
matej =
ChoreTracker.Accounts.register_user(
%{
username: "matej",
email: "matej@jednota.party",
password: "secret",
display_name: "Matěj"
},
disable_password_validation: true
)
)
ChoreTracker.Accounts.register_user(
valentyna =
ChoreTracker.Accounts.register_user(
%{
username: "valentyna",
email: "valentyna@jednota.party",
password: "secret",
display_name: "Valentýna"
},
disable_password_validation: true
)
)
ChoreTracker.Chores.create_chore(%{
name: "Uklidit kuchyň",
@ -50,7 +53,12 @@ ChoreTracker.Chores.create_chore(%{
- Umýt lednici
- Vytřít podlahu
""",
starts_at: Date.utc_today()
starts_at: Date.utc_today(),
assignees: [
ondrej,
matej,
valentyna
]
})
ChoreTracker.Chores.create_chore(%{
@ -62,7 +70,9 @@ ChoreTracker.Chores.create_chore(%{
- Vyluxovat
- Vytřít podlahu
""",
starts_at: Date.utc_today()
starts_at: Date.utc_today(),
assignees: [
ondrej,
matej
]
})
IO.puts(["✓ Admin user created\n", " login: ", admin.email, "\n password: ", admin_password])