Skip to content

Commit

Permalink
Update user accounts, add user sign-in and auth for admin dashboard (#…
Browse files Browse the repository at this point in the history
…148)

* Update deps

* Start adding user account using Ash, auth

* Add working register/sign in

* Add sign in page, auth for users

* Redirect to sign-in

* Add logo png

* Update compilation of phoenix assets

* Try root phx dir

* Add echo

* Try compilation again, hopefully 404 is transient

* Move some endpoint env to compiled, some to runtime

* Remove extra comma

* Move check_origin back to compiled config

* Remove explicit check_origin on sockets. Docs say it defaults to the Endpoint's config

* Update server/compile

Co-authored-by: Paul Cretu <[email protected]>

---------

Co-authored-by: Paul Cretu <[email protected]>
  • Loading branch information
skanderm and paulcretu authored Jul 18, 2023
1 parent 61c72e6 commit d7c5bbd
Show file tree
Hide file tree
Showing 28 changed files with 437 additions and 134 deletions.
1 change: 0 additions & 1 deletion server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
/deps
/*.ez

/priv/static/
/rel

node_modules/
Expand Down
3 changes: 3 additions & 0 deletions server/assets/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ module.exports = {
'../lib/*_web/**/*.*ex',
"../deps/ash_authentication_phoenix/**/*.ex"
],
// Enable dark mode with a 'dark' class on html element
// See: https://tailwindcss.com/docs/dark-mode
darkMode: 'class',
theme: {
extend: {},
},
Expand Down
9 changes: 9 additions & 0 deletions server/compile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@

source $phoenix_dir/load_env.sh

echo "---- Building UI, running: npm run build"

cd $phoenix_dir/ui
npm run build

echo "---- UI build done, running: mix assets.deploy"

cd $phoenix_dir
mix assets.deploy

echo "---- mix assets.deploy done"
3 changes: 1 addition & 2 deletions server/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ config :orcasite,

# Configures the endpoint
config :orcasite, OrcasiteWeb.Endpoint,
url: [host: "localhost"],
render_errors: [
formats: [html: OrcasiteWeb.ErrorHTML, json: OrcasiteWeb.ErrorJSON],
layout: false
Expand Down Expand Up @@ -74,7 +73,7 @@ config :orcasite, OrcasiteWeb.Auth.AuthAccessPipeline,
error_handler: OrcasiteWeb.Auth.AuthErrorHandler

config :ash, :use_all_identities_in_manage_relationship?, false
config :orcasite, :ash_apis, [Orcasite.Notifications]
config :orcasite, :ash_apis, [Orcasite.Notifications, Orcasite.Accounts]
config :orcasite, :ecto_repos, [Orcasite.Repo]

config :orcasite, Oban,
Expand Down
1 change: 1 addition & 0 deletions server/config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Config
# watchers to your application. For example, we use it
# with brunch.io to recompile .js and .css sources.
config :orcasite, OrcasiteWeb.Endpoint,
url: [host: "localhost"],
http: [port: 4000],
debug_errors: true,
code_reloader: true,
Expand Down
2 changes: 0 additions & 2 deletions server/config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,3 @@ if config_env() == :prod do

config :swoosh, :api_client, Swoosh.ApiClient.Finch
end

config :mailchimp, api_key: System.get_env("ORCASOUND_MAILCHIMP_API_KEY")
3 changes: 2 additions & 1 deletion server/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import Config
config :orcasite, OrcasiteWeb.Endpoint,
http: [port: 4001],
server: false,
secret_key_base: "99+mOEo5AOlhlQi4sJJd5MWnAdFFzm64"
secret_key_base: "99+mOEo5AOlhlQi4sJJd5MWnAdFFzm64",
check_origin: false

# Print only warnings and errors during test
config :logger, level: :warning
Expand Down
66 changes: 5 additions & 61 deletions server/lib/orcasite/accounts/accounts.ex
Original file line number Diff line number Diff line change
@@ -1,67 +1,11 @@
defmodule Orcasite.Accounts do
import Ecto.Query, warn: false
use Ash.Api, extensions: [AshAdmin.Api]

alias Orcasite.Repo
alias Orcasite.Accounts.User

def get_user!(id), do: Repo.get!(User, id)

def find_user_by_auth_token(auth_token),
do: User |> where(auth_token: ^auth_token) |> Repo.one()

def create_user(attrs \\ %{}) do
%User{}
|> User.create_changeset(attrs)
|> Repo.insert()
end

def list_users(params \\ %{pagination: %{page: 1, page_size: 10}}) do
User
|> order_by(desc: :inserted_at)
|> Repo.paginate(page: params.pagination.page, page_size: params.pagination.page_size)
end

def update_user(%User{id: id} = user, attrs, %User{admin: admin, id: current_user_id})
when (is_boolean(admin) and admin) or current_user_id == id do
user
|> User.changeset(attrs)
|> Repo.update()
end

def update_password(user, password) do
user
|> User.password_changeset(%{password: password})
|> Repo.update()
end

def login_user(user) do
{:ok, jwt, _} = OrcasiteWeb.Guardian.encode_and_sign(user)
{:ok, _} = store_token(user, jwt)
end

def store_token(%User{} = user, auth_token) do
user
|> User.store_token_changeset(%{auth_token: auth_token})
|> Repo.update()
end

def revoke_token(%User{} = user) do
user
|> User.store_token_changeset(%{auth_token: nil})
|> Repo.update()
resources do
registry Orcasite.Accounts.Registry
end

def authenticate(%{email: email, password: password}) do
User
|> Repo.get_by(email: String.downcase(email))
|> case do
nil ->
# Take up time
Bcrypt.no_user_verify()
{:error, :wrong_credentials}

user ->
Bcrypt.check_pass(user, password)
end
admin do
show? true
end
end
8 changes: 8 additions & 0 deletions server/lib/orcasite/accounts/registry.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule Orcasite.Accounts.Registry do
use Ash.Registry, extensions: [Ash.Registry.ResourceValidations]

entries do
entry Orcasite.Accounts.User
entry Orcasite.Accounts.Token
end
end
21 changes: 21 additions & 0 deletions server/lib/orcasite/accounts/token.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule Orcasite.Accounts.Token do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication.TokenResource]

token do
api Orcasite.Accounts
end

postgres do
table "tokens"
repo Orcasite.Repo
end

# If using policies, add the following bypass:
# policies do
# bypass AshAuthentication.Checks.AshAuthenticationInteraction do
# authorize_if always()
# end
# end
end
75 changes: 35 additions & 40 deletions server/lib/orcasite/accounts/user.ex
Original file line number Diff line number Diff line change
@@ -1,54 +1,49 @@
defmodule Orcasite.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication]

alias Orcasite.Accounts.User

schema "users" do
field(:email, :string)
field(:password_hash, :string)
field(:first_name, :string)
field(:last_name, :string)
field(:admin, :boolean)
field(:auth_token, :string)
attributes do
uuid_primary_key :id
attribute :email, :ci_string, allow_nil?: false
attribute :hashed_password, :string, allow_nil?: false, sensitive?: true
attribute :first_name, :string
attribute :last_name, :string
attribute :admin, :boolean

field(:password, :string, virtual: true)

timestamps()
end

def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:email, :first_name, :last_name])
|> validate_required([:email])
|> update_change(:email, &String.downcase/1)
|> validate_format(:email, ~r/^.+@.+$/)
|> unique_constraint(:email, name: :users_lower_email_index)
end

def create_changeset(%User{} = user, attrs) do
user
|> changeset(attrs)
|> password_changeset(attrs)
create_timestamp :inserted_at
update_timestamp :updated_at
end

def password_changeset(user_or_changeset, attrs) do
user_or_changeset
|> cast(attrs, [:password])
|> validate_length(:password, min: 6, max: 100)
|> hash_password
authentication do
api Orcasite.Accounts

strategies do
password :password do
identity_field :email
end
end

tokens do
enabled? true
token_resource Orcasite.Accounts.Token
signing_secret fn _, _ ->
{:ok, Application.get_env(:orcasite, OrcasiteWeb.Endpoint)[:secret_key_base]}
end
end
end

def store_token_changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:auth_token])
postgres do
table "users"
repo Orcasite.Repo
end

defp hash_password(%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset) do
put_change(changeset, :password_hash, Bcrypt.hash_pwd_salt(password))
identities do
identity :unique_email, [:email]
end

defp hash_password(changeset) do
changeset
actions do
defaults [:read, :create, :update, :destroy]
end
end
4 changes: 2 additions & 2 deletions server/lib/orcasite/notifications/email.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule Orcasite.Notifications.Email do
assigns
|> Map.put(
:unsubscribe_url,
url(~p"/auth/subscription/unsubscribe?token=#{assigns.unsubscribe_token}")
url(~p"/s/auth/subscription/unsubscribe?token=#{assigns.unsubscribe_token}")
)

"""
Expand Down Expand Up @@ -59,7 +59,7 @@ defmodule Orcasite.Notifications.Email do
assigns
|> Map.put(
:unsubscribe_url,
url(~p"/auth/subscription/unsubscribe?token=#{assigns.unsubscribe_token}")
url(~p"/s/auth/subscription/unsubscribe?token=#{assigns.unsubscribe_token}")
)

"""
Expand Down
2 changes: 1 addition & 1 deletion server/lib/orcasite/notifications/resources/token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defmodule Orcasite.Notifications.Token do
end

postgres do
table "tokens"
table "subscriber_tokens"
repo Orcasite.Repo
end

Expand Down
2 changes: 1 addition & 1 deletion server/lib/orcasite_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defmodule OrcasiteWeb do
below. Instead, define additional modules and import
those modules here.
"""
def static_paths, do: ~w(css fonts images js favicon.ico robots.txt)
def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)

def router do
quote do
Expand Down
13 changes: 13 additions & 0 deletions server/lib/orcasite_web/auth_overrides.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule OrcasiteWeb.AuthOverrides do
use AshAuthentication.Phoenix.Overrides
alias AshAuthentication.Phoenix.Components

override Components.Banner do
set :image_url, "/images/logo.png"
set :image_class, "w-80"
end

override Components.MagicLink do
set :root_class, "hidden"
end
end
4 changes: 2 additions & 2 deletions server/lib/orcasite_web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<.live_title>
<%= assigns[:page_title] || "Orcasound" %>
</.live_title>
<link rel="stylesheet" href={~p"/css/app.css"}/>
<script defer type="text/javascript" src={~p"/js/app.js"}></script>
<link rel="stylesheet" href={~p"/assets/app.css"}/>
<script defer type="text/javascript" src={~p"/assets/app.js"}></script>
</head>
<body>
<%= @inner_content %>
Expand Down
36 changes: 36 additions & 0 deletions server/lib/orcasite_web/controllers/auth_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule OrcasiteWeb.AuthController do
use OrcasiteWeb, :controller
use AshAuthentication.Phoenix.Controller

def success(conn, _activity, nil = _user, _token) do
return_to = get_session(conn, :return_to) || ~p"/"

conn
|> clear_session()
|> redirect(to: return_to)
end

def success(conn, _activity, user, _token) do
return_to = get_session(conn, :return_to) || ~p"/"

conn
|> delete_session(:return_to)
|> store_in_session(user)
|> assign(:current_user, user)
|> redirect(to: return_to)
end

def failure(conn, _activity, _reason) do
conn
|> put_status(401)
|> render("failure.html")
end

def sign_out(conn, _params) do
return_to = get_session(conn, :return_to) || ~p"/"

conn
|> clear_session()
|> redirect(to: return_to)
end
end
5 changes: 5 additions & 0 deletions server/lib/orcasite_web/controllers/auth_html.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule OrcasiteWeb.AuthHTML do
use OrcasiteWeb, :html

embed_templates "auth_html/*"
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1 class="text-2xl">Authentication Error</h1>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Success! You've been signed in</h1>
Loading

0 comments on commit d7c5bbd

Please sign in to comment.