Skip to content

Commit

Permalink
Merge branch 'account-verification' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
macifell committed Jul 28, 2023
2 parents 0ff6e70 + 5f482a6 commit 11e0051
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 19 deletions.
29 changes: 15 additions & 14 deletions lib/recognizer/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -630,15 +630,6 @@ defmodule Recognizer.Accounts do

## Account Verification

def deliver_account_verification_instructions(%User{} = user, verify_account_url_fun) do
%{code: code} = Repo.get_by(VerificationCode, user_id: user.id)

Notification.deliver_account_verification_instructions(
user,
verify_account_url_fun.(code)
)
end

def get_user_by_verification_code(code) do
case Repo.get_by(VerificationCode, code: code) do
nil -> {:error, :code_not_found}
Expand All @@ -647,20 +638,30 @@ defmodule Recognizer.Accounts do
end

def generate_verification_code({:ok, user}, verify_account_url_fun) do
%VerificationCode{}
|> VerificationCode.changeset(%{code: VerificationCode.generate_code(), user_id: user.id})
|> Repo.insert()
{:ok, code} =
%VerificationCode{}
|> VerificationCode.changeset(%{code: VerificationCode.generate_code(), user_id: user.id})
|> Repo.insert()

deliver_account_verification_instructions(user, verify_account_url_fun)
Notification.deliver_account_verification_instructions(user, verify_account_url_fun.(code))

{:ok, user}
end

def generate_verification_code(error, _verify_account_url_fun) do
# TODO refactor pipeline
error
end

def resend_verification_code(user, verify_account_url_fun) do
case Repo.get_by(VerificationCode, user_id: user.id) do
nil ->
generate_verification_code({:ok, user}, verify_account_url_fun)

code ->
Notification.deliver_account_verification_instructions(user, verify_account_url_fun.(code))
end
end

def verify_user(code) do
case get_user_by_verification_code(code) do
{:ok, user} ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Recognizer.VerificationCodeCleanupTask do
defmodule Recognizer.Accounts.VerificationCodeCleanupTask do
@moduledoc """
A scheduled task that cleans up old verification codes.
"""
Expand Down
4 changes: 2 additions & 2 deletions lib/recognizer/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ defmodule Recognizer.Application do
RecognizerWeb.Endpoint,
# Start a worker by calling: Recognizer.Worker.start_link(arg)
{Redix, name: :redix, host: Application.get_env(:recognizer, :redis_host)},
# Start the task for removing expired verification tokens
Recognizer.VerificationCodeCleanupTask
# Start the task for removing expired verification codes
Recognizer.Accounts.VerificationCodeCleanupTask
]

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
defmodule RecognizerWeb.Accounts.Prompt.VerificationController do
use RecognizerWeb, :controller

alias Recognizer.Accounts
alias RecognizerWeb.Authentication

@one_minute 60_000

plug :ensure_user

def new(conn, _params) do
render(conn, "new.html", user: conn.assigns.user)
plug Hammer.Plug,
[
rate_limit: {"user:verification", @one_minute, 3},
by: {:conn, &get_user_id_from_unverified_request/1}
]
when action in [:resend]

def new(%{assigns: %{user: %{verified_at: nil}}} = conn, _params) do
render(conn, "new.html", resend?: false)
end

def new(%{assigns: %{user: user}} = conn, _params) do
Authentication.log_in_user(conn, user)
end

def resend(%{assigns: %{user: user}} = conn, _params) do
Accounts.resend_verification_code(user, &Routes.verification_code_url(conn, :new, &1))
render(conn, "new.html", resend?: true)
end
end
4 changes: 4 additions & 0 deletions lib/recognizer_web/controllers/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ defmodule RecognizerWeb.Controllers.Helpers do
Authentication.fetch_current_user(conn).id
end

def get_user_id_from_unverified_request(conn) do
conn.assigns.user.id
end

def ensure_user(conn, _opts) do
user_id = get_session(conn, :prompt_user_id)

Expand Down
1 change: 1 addition & 0 deletions lib/recognizer_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ defmodule RecognizerWeb.Router do
post "/prompt/setup-two-factor/confirm", TwoFactorController, :update

get "/prompt/verification", VerificationController, :new
post "/prompt/verification", VerificationController, :resend
end

scope "/", RecognizerWeb.Accounts do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,16 @@

<div class="content">
<p>Please click on the link provided in the verification email to activate your account.</p>
<%= if @resend? do %>
<p>We've sent you another copy of the verification email.</p>
<% else %>
<%= form_for @conn, Routes.prompt_verification_path(@conn, :resend), fn _f -> %>
<div class="buttons is-right mt-5">
<div class="control">
<%= submit "Resend Verification Code", class: "button is-secondary" %>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule RecognizerWeb.Accounts.Prompt.VerificationCodeControllerTest do
use RecognizerWeb.ConnCase

import Recognizer.AccountFactory

setup %{conn: conn} do
user = insert(:user, verified_at: nil)
verified_user = insert(:user)

%{
unverified_conn:
Phoenix.ConnTest.init_test_session(conn, %{
prompt_user_id: user.id
}),
verified_conn:
Phoenix.ConnTest.init_test_session(conn, %{
prompt_user_id: verified_user.id
}),
empty_conn: conn
}
end

describe "GET /prompt/verification" do
test "renders the verification required page for an unverified user", %{unverified_conn: conn} do
conn = get(conn, Routes.prompt_verification_path(conn, :new))
response = html_response(conn, 200)
assert response =~ "Account Verification Pending</h2>"
end

test "redirects verified user", %{verified_conn: conn} do
conn = get(conn, Routes.prompt_verification_path(conn, :new))
assert redirected_to(conn) =~ "/settings"
end

test "redirects anonymous user", %{empty_conn: conn} do
conn = get(conn, Routes.prompt_verification_path(conn, :new))
assert redirected_to(conn) =~ "/login"
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
defmodule RecognizerWeb.Accounts.UserVerificationCodeControllerTest do
use RecognizerWeb.ConnCase

import Recognizer.AccountFactory

setup do
%{user: insert(:user, verified_at: nil)}
end

describe "GET /verify" do
test "shows message for an expired code", %{conn: conn} do
conn = get(conn, Routes.verification_code_path(conn, :new, "expired"))
response = html_response(conn, 200)
assert response =~ "Expired Verification Code</h2>"
end

test "verifies a valid code", %{conn: conn, user: user} do
verification = insert(:verification_code, user: user)
conn = get(conn, Routes.verification_code_path(conn, :new, verification.code))
assert redirected_to(conn) =~ "/settings"
end

test "verifies a valid code multiple times", %{conn: conn, user: user} do
verification = insert(:verification_code, user: user)
conn = get(conn, Routes.verification_code_path(conn, :new, verification.code))
conn = get(conn, Routes.verification_code_path(conn, :new, verification.code))
conn = get(conn, Routes.verification_code_path(conn, :new, verification.code))
assert redirected_to(conn) =~ "/settings"
end
end
end

0 comments on commit 11e0051

Please sign in to comment.