Skip to content

Commit

Permalink
Ensure Users Confirm Their Accounts After Sign Up (#2364)
Browse files Browse the repository at this point in the history
* Render banner when account is not confirmed

* Account confirmation modal

* Resend confirmation email using banner

* Module doc and tests

* Email sent successful alert inside modal content

* Change email

* Put flash on conn

* CSS tweaks

* Testing the controller functions

* Cleanup

closes #2353, closes #2368

* Move message for account confirmation into heex

* Format date differently

---------

Co-authored-by: Taylor Downs <[email protected]>
Co-authored-by: Stuart Corbishley <[email protected]>
  • Loading branch information
3 people authored Aug 15, 2024
1 parent 95c8aa7 commit b99e718
Show file tree
Hide file tree
Showing 21 changed files with 420 additions and 26 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ and this project adheres to

### Added

- Ensure that all users in an instance have a confirmed email address within 48
hours [#2389](https://github.com/OpenFn/lightning/issues/2389)

### Changed

### Fixed
Expand Down
8 changes: 4 additions & 4 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
border-color: #bce8f1;
}
.alert-warning {
color: #8a6d3b;
color: #8B5F0D;
background-color: #fcf8e3;
border-color: #faebcc;
}
.alert-danger {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
color: #DC2626;
background-color: #FDEBEB;
border-color: #FDEBEB;
}
.alert p {
margin-bottom: 0;
Expand Down
13 changes: 13 additions & 0 deletions lib/lightning/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,13 @@ defmodule Lightning.Accounts do
end
end

def remind_account_confirmation(%User{} = user) do
UserNotifier.remind_account_confirmation(
user,
build_email_token(user)
)
end

@doc """
Confirms a user by the given token.
Expand Down Expand Up @@ -952,4 +959,10 @@ defmodule Lightning.Accounts do
)
|> Repo.all()
end

def confirmation_required?(%User{confirmed_at: nil, inserted_at: inserted_at}) do
DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >= 48
end

def confirmation_required?(_user), do: false
end
14 changes: 14 additions & 0 deletions lib/lightning/accounts/user_notifier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ defmodule Lightning.Accounts.UserNotifier do
""")
end

def remind_account_confirmation(user, token) do
deliver(user.email, "Confirm your OpenFn account", """
Hello #{user.first_name},
Please confirm your OpenFn account by clicking on the URL below:
#{url(LightningWeb.Endpoint, ~p"/users/confirm/#{token}")}
If you have not requested an account confirmation email, please ignore this.
OpenFn
""")
end

@doc """
Deliver email to notify user of his addition of a project.
"""
Expand Down
7 changes: 6 additions & 1 deletion lib/lightning_web/components/icons.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ defmodule LightningWeb.Components.Icons do
def icon(%{name: "hero-" <> _} = assigns) do
~H"""
<span
class={[@naked && "text-gray-500 hover:text-primary-400", @name, @class]}
class={[
@naked && "text-gray-500 hover:text-primary-400",
"align-top",
@name,
@class
]}
{@rest}
/>
"""
Expand Down
14 changes: 14 additions & 0 deletions lib/lightning_web/components/layout_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ defmodule LightningWeb.LayoutComponents do
slot :inner_block

def header(assigns) do
# TODO - remove title_height once we confirm that :description is unused
title_height =
if Enum.any?(assigns[:description]) do
"mt-4 h-10"
Expand All @@ -80,6 +81,19 @@ defmodule LightningWeb.LayoutComponents do
)

~H"""
<LightningWeb.Components.Common.banner
:if={@current_user && !@current_user.confirmed_at}
id="account-confirmation-alert"
type="danger"
centered
message={"Please confirm your account before #{@current_user.inserted_at |> DateTime.add(48, :hour) |> Timex.format!("%A, %d %B @ %H:%M UTC", :strftime)} to continue using OpenFn."}
action={
%{
text: "Resend confirmation email",
target: "/users/send-confirmation-email"
}
}
/>
<div class="flex-none bg-white shadow-sm">
<div class={[@title_class, @title_height]}>
<%= if @current_user do %>
Expand Down
14 changes: 13 additions & 1 deletion lib/lightning_web/components/layouts/live.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,22 @@
<div class="flex-auto">
<Common.flash flash={@flash} kind={:info} />
<Common.flash flash={@flash} kind={:error} />
<.live_nav_block flash={@flash}>
<.live_nav_block
:if={
!assigns[:account_confirmation_required?] or
@socket.view == LightningWeb.ProfileLive.Edit
}
flash={@flash}
>
<%= @inner_content %>
</.live_nav_block>
<.live_component module={LightningWeb.ModalPortal} id="modal-portal" />
<.live_component
:if={assigns[:account_confirmation_required?]}
module={LightningWeb.AccountConfirmationModal}
id="account-confirmation-modal"
current_user={@current_user}
/>
</div>
</div>
</main>
6 changes: 5 additions & 1 deletion lib/lightning_web/controllers/user_auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,11 @@ defmodule LightningWeb.UserAuth do
def fetch_current_user(conn, _opts) do
{user_token, conn} = ensure_user_token(conn)
user = user_token && Accounts.get_user_by_session_token(user_token)
assign(conn, :current_user, user)
confirmation_required? = Accounts.confirmation_required?(user)

conn
|> assign(:current_user, user)
|> assign(:account_confirmation_required?, confirmation_required?)
end

defp ensure_user_token(conn) do
Expand Down
25 changes: 25 additions & 0 deletions lib/lightning_web/controllers/user_confirmation_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,31 @@ defmodule LightningWeb.UserConfirmationController do
end
end

def send_email(
%{assigns: %{current_user: %{confirmed_at: nil}}} = conn,
_params
) do
Lightning.Accounts.remind_account_confirmation(conn.assigns.current_user)

conn
|> put_flash(:info, "Confirmation email sent successfully")
|> redirect(to: get_referer(conn))
end

def send_email(conn, _params) do
redirect(conn, to: get_referer(conn))
end

defp get_referer(conn) do
conn
|> Plug.Conn.get_req_header("referer")
|> List.first()
|> case do
nil -> "/projects"
referer -> URI.parse(referer) |> Map.get(:path)
end
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
Expand Down
11 changes: 9 additions & 2 deletions lib/lightning_web/init_assigns.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ defmodule LightningWeb.InitAssigns do
alias Lightning.Accounts

def on_mount(:default, _params, session, socket) do
current_user = Accounts.get_user_by_session_token(session["user_token"])
confirmation_required? = Accounts.confirmation_required?(current_user)

{:cont,
assign_new(socket, :current_user, fn ->
Accounts.get_user_by_session_token(session["user_token"])
socket
|> assign_new(:current_user, fn ->
current_user
end)
|> assign_new(:account_confirmation_required?, fn ->
confirmation_required?
end)}
end
end
112 changes: 112 additions & 0 deletions lib/lightning_web/live/account_confirmation_modal.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
defmodule LightningWeb.AccountConfirmationModal do
@moduledoc """
A LiveView component for displaying an account confirmation modal.
This component is responsible for informing users that access to their
accounts, projects, and workflows is restricted until they confirm
their account. It provides functionality to resend the confirmation
email and allows users to update their email address if needed.
## Features
- Displays a modal with instructions for account confirmation.
- Allows users to resend the confirmation email.
- Provides feedback when the confirmation email is successfully sent.
- Allows users to navigate to the profile page to update their email address.
## Usage
Include this component in your LiveView template where you need to prompt
users to confirm their account. The component determines whether the modal
should be shown based on the current view context.
## Examples
<.live_component
module={LightningWeb.AccountConfirmationModal}
id="account-confirmation-modal"
current_user={@current_user}
/>
The component uses assigns to manage its state, including:
- `:show_modal` - Determines if the modal should be visible.
- `:email_sent` - Indicates if the confirmation email was successfully sent.
"""
use LightningWeb, :live_component

@impl true
def update(assigns, socket) do
show_modal =
case socket.view do
LightningWeb.ProfileLive.Edit -> false
_ -> true
end

{:ok,
socket
|> assign(assigns)
|> assign(:show_modal, show_modal)
|> assign(:email_sent, false)}
end

@impl true
def handle_event("resend-confirmation-email", _, socket) do
Lightning.Accounts.remind_account_confirmation(socket.assigns.current_user)

{:noreply, assign(socket, :email_sent, true)}
end

@impl true
def render(assigns) do
~H"""
<div class="text-xs">
<.modal
id={@id}
show={@show_modal}
close_on_keydown={false}
close_on_click_away={false}
width="xl:min-w-1/3 min-w-1/2 w-1/3"
>
<:title>
<div class="flex justify-between">
<span class="font-bold">Confirm your account</span>
</div>
</:title>
<div class="container mx-auto px-6 space-y-6 text-base text-gray-600">
<div>
This account has been blocked pending email confirmation. Please
check your email for a confirmation link, request that a new link be
sent, or update your email address to keep using OpenFn.
</div>
<Common.alert :if={@email_sent} type="info">
<:message>
A new link has been sent. Please check your email.
</:message>
</Common.alert>
</div>
<.modal_footer class="mt-6 mx-6">
<div class="sm:flex sm:flex-row-reverse">
<button
id="resend-confirmation-email-button"
type="button"
phx-click="resend-confirmation-email"
phx-target={@myself}
disabled={@email_sent}
class="ml-3 inline-flex justify-center rounded-md disabled:bg-primary-300 bg-primary-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-primary-500 sm:w-auto"
>
Resend confirmation email
</button>
<.link
href={~p"/profile"}
class="inline-flex justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:w-auto"
>
Update email address
</.link>
</div>
</.modal_footer>
</.modal>
</div>
"""
end
end
Loading

0 comments on commit b99e718

Please sign in to comment.