Skip to content

Commit 107a4c0

Browse files
authored
fix(auth): complete invited account setup (#38)
1 parent 69336de commit 107a4c0

8 files changed

Lines changed: 268 additions & 27 deletions

File tree

lib/craftplan/accounts/emails.ex

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,32 @@ defmodule Craftplan.Accounts.Emails do
4545
""")
4646
end
4747

48+
def deliver_invitation_email(user, url) do
49+
if !url do
50+
raise "Cannot deliver invitation instructions without a url"
51+
end
52+
53+
deliver(user.email, "Set up your Craftplan account", """
54+
<html>
55+
<p>
56+
Hi #{user.email},
57+
</p>
58+
59+
<p>
60+
You've been invited to join Craftplan.
61+
</p>
62+
63+
<p>
64+
<a href="#{url}">Set your password</a> to finish setting up your account.
65+
</p>
66+
67+
<p>
68+
This link expires in 3 days. If it expires, request a new password link from the sign-in page.
69+
</p>
70+
</html>
71+
""")
72+
end
73+
4874
# For simplicity, this module simply logs messages to the terminal.
4975
# You should replace it by a proper email or notification tool, such as:
5076
#

lib/craftplan/accounts/user.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ defmodule Craftplan.Accounts.User do
2525
identity_field :email
2626

2727
resettable do
28+
token_lifetime {3, :days}
2829
sender Craftplan.Accounts.User.Senders.SendPasswordResetEmail
2930
end
3031

@@ -38,7 +39,7 @@ defmodule Craftplan.Accounts.User do
3839
require_interaction? true
3940
confirm_on_create? true
4041
confirm_on_update? false
41-
auto_confirm_actions [:sign_in_with_magic_link, :reset_password_with_password]
42+
auto_confirm_actions [:sign_in_with_magic_link, :password_reset_with_password]
4243
sender Craftplan.Accounts.User.Senders.SendNewUserConfirmationEmail
4344
end
4445
end
Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
defmodule Craftplan.Accounts.User.Senders.SendNewUserConfirmationEmail do
22
@moduledoc """
3-
Sends an email for a new user to confirm their email address.
3+
Sends email confirmation or invited-account setup instructions for a new user.
44
"""
55

66
use AshAuthentication.Sender
77
use CraftplanWeb, :verified_routes
88

9+
alias AshAuthentication.Info
10+
alias AshAuthentication.Strategy.Password
11+
alias Craftplan.Accounts.Emails
12+
alias Craftplan.Accounts.User
13+
914
@impl true
10-
def send(user, token, _) do
11-
Craftplan.Accounts.Emails.deliver_new_user_confirmation_email(
15+
def send(user, confirmation_token, opts) do
16+
send_for_action(user, confirmation_token, Keyword.get(opts, :changeset))
17+
end
18+
19+
defp send_for_action(user, _confirmation_token, %Ash.Changeset{action: %{name: :invite}}) do
20+
# Invited users start with an unknowable generated password. Setting a password through the
21+
# reset action also confirms the email, so they cannot be stranded after their first sign-out.
22+
password_strategy = Info.strategy!(User, :password)
23+
{:ok, reset_token} = Password.reset_token_for(password_strategy, user)
24+
25+
Emails.deliver_invitation_email(
26+
user,
27+
url(~p"/password-reset/#{reset_token}")
28+
)
29+
end
30+
31+
defp send_for_action(user, confirmation_token, _changeset) do
32+
Emails.deliver_new_user_confirmation_email(
1233
user,
13-
url(~p"/auth/user/confirm_new_user?#{[confirm: token]}")
34+
url(~p"/auth/user/confirm_new_user?#{[confirm: confirmation_token]}")
1435
)
1536
end
1637
end

lib/craftplan_web/auth_overrides.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ defmodule CraftplanWeb.AuthOverrides do
5555
set :root_class, "w-full flex justify-center py-2"
5656
set :href_class, nil
5757
set :href_url, "/"
58-
set :image_class, "block dark:hidden"
59-
set :dark_image_class, "hidden dark:block"
60-
set :image_url, "https://ash-hq.org/images/ash-framework-light.png"
61-
set :dark_image_url, "https://ash-hq.org/images/ash-framework-dark.png"
58+
set :image_class, "block h-20 w-20 dark:hidden"
59+
set :dark_image_class, "hidden h-20 w-20 dark:block"
60+
set :image_url, "/images/favicon.svg"
61+
set :dark_image_url, "/images/favicon.svg"
6262
set :text_class, nil
6363
set :text, nil
6464
end

lib/craftplan_web/components/layouts/root.html.heex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
<link rel="icon" type="image/svg" sizes="32x32" href={~p"/images/favicon.svg"} />
1414
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
1515
</script>
16-
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
1716
</head>
1817
<body class="craftplan-web bg-stone-50 text-stone-800">
1918
{@inner_content}

lib/craftplan_web/controllers/auth_controller.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@ defmodule CraftplanWeb.AuthController do
22
use CraftplanWeb, :controller
33
use AshAuthentication.Phoenix.Controller
44

5+
alias AshAuthentication.Info
6+
alias AshAuthentication.Strategy.Password
7+
alias Craftplan.Accounts.User
8+
9+
def success(conn, {:confirm_new_user, :confirm}, %{role: role} = user, _token) when role in [:staff, :admin] do
10+
password_strategy = Info.strategy!(User, :password)
11+
12+
case Password.reset_token_for(password_strategy, user) do
13+
{:ok, reset_token} ->
14+
conn
15+
|> put_flash(:info, "Your email has been confirmed. Set a password to finish setup.")
16+
|> redirect(to: ~p"/password-reset/#{reset_token}")
17+
18+
:error ->
19+
conn
20+
|> put_flash(:error, "Your email was confirmed, but password setup could not be started.")
21+
|> redirect(to: ~p"/reset")
22+
end
23+
end
24+
525
def success(conn, activity, user, _token) do
626
return_to = get_session(conn, :return_to) || ~p"/manage/overview"
727

@@ -35,6 +55,11 @@ defmodule CraftplanWeb.AuthController do
3555
You can confirm your account using the link we sent to you, or by resetting your password.
3656
"""
3757

58+
{{:password, :reset}, _reason} ->
59+
"""
60+
Your password could not be changed. The link may be invalid or expired; request a new one and try again.
61+
"""
62+
3863
_ ->
3964
"Incorrect email or password"
4065
end

lib/craftplan_web/router.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,19 @@ defmodule CraftplanWeb.Router do
2525
)
2626

2727
def put_session_timezone(conn, _opts) do
28-
timezone = conn.cookies["timezone"]
28+
timezone = valid_timezone_or_utc(conn.cookies["timezone"])
2929
put_session(conn, "timezone", timezone)
3030
end
3131

32+
defp valid_timezone_or_utc(timezone) when is_binary(timezone) do
33+
case DateTime.now(timezone) do
34+
{:ok, _datetime} -> timezone
35+
{:error, _reason} -> "Etc/UTC"
36+
end
37+
end
38+
39+
defp valid_timezone_or_utc(_timezone), do: "Etc/UTC"
40+
3241
#
3342
# Pipelines
3443
#

0 commit comments

Comments
 (0)