made changes to how roles are set on registering new users
This commit is contained in:
parent
2358b6adfb
commit
42b2ea9d78
5 changed files with 135 additions and 62 deletions
|
@ -10,6 +10,19 @@ defmodule Bones73k.Accounts do
|
|||
|
||||
## Database getters
|
||||
|
||||
@doc """
|
||||
Returns the list of users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_users()
|
||||
[%User{}, ...]
|
||||
|
||||
"""
|
||||
def list_users do
|
||||
Repo.all(User)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a user by email.
|
||||
|
||||
|
@ -60,6 +73,22 @@ defmodule Bones73k.Accounts do
|
|||
"""
|
||||
def get_user!(id), do: Repo.get!(User, id)
|
||||
|
||||
@doc """
|
||||
Gets a single user.
|
||||
|
||||
Returns nil if the User does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_user(123)
|
||||
%User{}
|
||||
|
||||
iex> get_user(456)
|
||||
nil
|
||||
|
||||
"""
|
||||
def get_user(id), do: Repo.get(User, id)
|
||||
|
||||
## User registration
|
||||
|
||||
@doc """
|
||||
|
@ -81,22 +110,11 @@ defmodule Bones73k.Accounts do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Registers an admin.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> register_admin(%{field: value})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> register_admin(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
Returns role for user registration. First user to register gets :admin role.
|
||||
Following users get :user role. Used by registration controller/liveview.
|
||||
Admins can choose role for new users they create, and modify other uses.
|
||||
"""
|
||||
def register_admin(attrs) do
|
||||
%User{}
|
||||
|> User.admin_registration_changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
def registration_role, do: (Repo.exists?(User) && :user) || :admin
|
||||
|
||||
def logout_user(%User{} = user) do
|
||||
# Delete all user tokens
|
||||
|
|
|
@ -5,9 +5,14 @@ defmodule Bones73k.Accounts.User do
|
|||
|
||||
defenum(RolesEnum, :role, [
|
||||
:user,
|
||||
:manager,
|
||||
:admin
|
||||
])
|
||||
|
||||
@max_email 254
|
||||
@min_password 6
|
||||
@max_password 80
|
||||
|
||||
@derive {Inspect, except: [:password]}
|
||||
schema "users" do
|
||||
field :email, :string
|
||||
|
@ -19,6 +24,10 @@ defmodule Bones73k.Accounts.User do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
def max_email, do: @max_email
|
||||
def min_password, do: @min_password
|
||||
def max_password, do: @max_password
|
||||
|
||||
@doc """
|
||||
A user changeset for registration.
|
||||
|
||||
|
@ -26,53 +35,71 @@ defmodule Bones73k.Accounts.User do
|
|||
Otherwise databases may truncate the email without warnings, which
|
||||
could lead to unpredictable or insecure behaviour. Long passwords may
|
||||
also be very expensive to hash for certain algorithms.
|
||||
|
||||
## Options
|
||||
|
||||
* `:hash_password` - Hashes the password so it can be stored securely
|
||||
in the database and ensures the password field is cleared to prevent
|
||||
leaks in the logs. If password hashing is not needed and clearing the
|
||||
password field is not desired (like when using this changeset for
|
||||
validations on a LiveView form), this option can be set to `false`.
|
||||
Defaults to `true`.
|
||||
"""
|
||||
def registration_changeset(user, attrs) do
|
||||
def registration_changeset(user, attrs, opts \\ []) do
|
||||
user
|
||||
|> cast(attrs, [:email, :password])
|
||||
|> cast(attrs, [:email, :password, :role])
|
||||
|> validate_role()
|
||||
|> validate_email()
|
||||
|> validate_password()
|
||||
|> validate_password(opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
A user changeset for registering admins.
|
||||
"""
|
||||
def admin_registration_changeset(user, attrs) do
|
||||
user
|
||||
|> registration_changeset(attrs)
|
||||
|> prepare_changes(&set_admin_role/1)
|
||||
defp role_validator(:role, role) do
|
||||
(RolesEnum.valid_value?(role) && []) || [role: "invalid user role"]
|
||||
end
|
||||
|
||||
defp validate_role(changeset) do
|
||||
changeset
|
||||
|> validate_required([:role])
|
||||
|> validate_change(:role, &role_validator/2)
|
||||
end
|
||||
|
||||
defp validate_email_format(changeset) do
|
||||
r_email = ~r/^[\w.!#$%&’*+\-\/=?\^`{|}~]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i
|
||||
|
||||
changeset
|
||||
|> validate_required([:email])
|
||||
|> validate_format(:email, r_email, message: "must be a valid email address")
|
||||
|> validate_length(:email, max: @max_email)
|
||||
end
|
||||
|
||||
defp validate_email(changeset) do
|
||||
changeset
|
||||
|> validate_required([:email])
|
||||
|> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
|
||||
|> validate_length(:email, max: 160)
|
||||
|> validate_email_format()
|
||||
|> unsafe_validate_unique(:email, Bones73k.Repo)
|
||||
|> unique_constraint(:email)
|
||||
end
|
||||
|
||||
defp validate_password(changeset) do
|
||||
defp validate_password(changeset, opts) do
|
||||
changeset
|
||||
|> validate_required([:password])
|
||||
|> validate_length(:password, min: 12, max: 80)
|
||||
|> validate_length(:password, min: @min_password, max: @max_password)
|
||||
# |> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character")
|
||||
# |> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character")
|
||||
# |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character")
|
||||
|> prepare_changes(&hash_password/1)
|
||||
|> maybe_hash_password(opts)
|
||||
end
|
||||
|
||||
defp hash_password(changeset) do
|
||||
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)
|
||||
end
|
||||
|
||||
defp set_admin_role(changeset) do
|
||||
else
|
||||
changeset
|
||||
|> put_change(:role, :admin)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -93,11 +120,11 @@ defmodule Bones73k.Accounts.User do
|
|||
@doc """
|
||||
A user changeset for changing the password.
|
||||
"""
|
||||
def password_changeset(user, attrs) do
|
||||
def password_changeset(user, attrs, opts \\ []) do
|
||||
user
|
||||
|> cast(attrs, [:password])
|
||||
|> validate_confirmation(:password, message: "does not match password")
|
||||
|> validate_password()
|
||||
|> validate_password(opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -6,12 +6,15 @@ defmodule Bones73kWeb.UserRegistrationController do
|
|||
alias Bones73kWeb.UserAuth
|
||||
|
||||
def new(conn, _params) do
|
||||
changeset = Accounts.change_user_registration(%User{})
|
||||
changeset = Accounts.change_user_registration(%User{}, %{role: Accounts.registration_role()})
|
||||
render(conn, "new.html", changeset: changeset)
|
||||
end
|
||||
|
||||
def create(conn, %{"user" => user_params}) do
|
||||
case Accounts.register_user(user_params) do
|
||||
user_params
|
||||
|> Map.put_new("role", Accounts.registration_role())
|
||||
|> Accounts.register_user()
|
||||
|> case do
|
||||
{:ok, user} ->
|
||||
%Bamboo.Email{} =
|
||||
Accounts.deliver_user_confirmation_instructions(
|
||||
|
|
|
@ -1,22 +1,37 @@
|
|||
<h1>Register</h1>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-sm-9 col-md-7 col-lg-5 col-xl-4 ">
|
||||
|
||||
<%= form_for @changeset, Routes.user_registration_path(@conn, :create), fn f -> %>
|
||||
<h3>Register</h3>
|
||||
<p class="lead">Registration gains additional features, like remembering your song request history.</p>
|
||||
|
||||
<%= form_for @changeset, Routes.user_registration_path(@conn, :create), form_opts(@changeset, novalidate: true), fn f -> %>
|
||||
<%= if @changeset.action do %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Oops, something went wrong! Please check the errors below.</p>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
Ope — please check the errors below.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= label f, :email %>
|
||||
<%= email_input f, :email, required: true %>
|
||||
<%= error_tag f, :email %>
|
||||
<%= label f, :email, class: "form-label" %>
|
||||
<div class="input-group has-validation mb-3">
|
||||
<span class="input-group-text" id="basic-addon1">
|
||||
<%= icon_div @conn, "bi-at", [class: "icon fs-5"] %>
|
||||
</span>
|
||||
<%= email_input f, :email, class: error_class(f, :email, "form-control"), required: true %>
|
||||
</div>
|
||||
<%= error_tag f, :email, class: "d-block mt-n3 mb-3" %>
|
||||
|
||||
<%= label f, :password %>
|
||||
<%= password_input f, :password, required: true %>
|
||||
<%= error_tag f, :password %>
|
||||
<%= label f, :password, class: "form-label" %>
|
||||
<div class="input-group has-validation mb-3">
|
||||
<span class="input-group-text" id="basic-addon1">
|
||||
<%= icon_div @conn, "bi-key", [class: "icon fs-5"] %>
|
||||
</span>
|
||||
<%= password_input f, :password, class: "form-control", required: true %>
|
||||
</div>
|
||||
<%= error_tag f, :password, class: "d-block mt-n3 mb-3" %>
|
||||
|
||||
<div>
|
||||
<%= submit "Register" %>
|
||||
<div class="mb-3">
|
||||
<%= submit "Register", class: "btn btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
@ -25,6 +40,12 @@
|
|||
<%= link "Forgot your password?", to: Routes.user_reset_password_path(@conn, :new) %>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h2>Lorem</h2>
|
||||
<p>
|
||||
|
|
|
@ -9,26 +9,30 @@
|
|||
#
|
||||
# We recommend using the bang functions (`insert!`, `update!`
|
||||
# and so on) as they will fail if something goes wrong.
|
||||
alias Bones73k.Accounts
|
||||
|
||||
{:ok, admin} =
|
||||
Bones73k.Accounts.register_admin(%{
|
||||
Accounts.register_user(%{
|
||||
email: "admin@company.com",
|
||||
password: "123456789abc",
|
||||
password_confirmation: "123456789abc"
|
||||
password_confirmation: "123456789abc",
|
||||
role: Accounts.registration_role()
|
||||
})
|
||||
|
||||
{:ok, user_1} =
|
||||
Bones73k.Accounts.register_user(%{
|
||||
Accounts.register_user(%{
|
||||
email: "user1@company.com",
|
||||
password: "123456789abc",
|
||||
password_confirmation: "123456789abc"
|
||||
password_confirmation: "123456789abc",
|
||||
role: Accounts.registration_role()
|
||||
})
|
||||
|
||||
{:ok, user_2} =
|
||||
Bones73k.Accounts.register_user(%{
|
||||
Accounts.register_user(%{
|
||||
email: "user2@company.com",
|
||||
password: "123456789abc",
|
||||
password_confirmation: "123456789abc"
|
||||
password_confirmation: "123456789abc",
|
||||
role: Accounts.registration_role()
|
||||
})
|
||||
|
||||
Enum.each(1..10, fn i ->
|
||||
|
|
Loading…
Reference in a new issue