Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lib/recognizer/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -652,8 +652,16 @@ defmodule Recognizer.Accounts do
settings.
"""
def check_two_factor_notification_time(user) do
{:ok, attrs} = get_new_two_factor_settings(user)
check_two_factor_notification_time(attrs, 100)
case get_new_two_factor_settings(user) do
{:ok, attrs} when not is_nil(attrs) ->
check_two_factor_notification_time(attrs, 100)

{:ok, nil} ->
{:error, :no_two_factor_settings}

{:error, reason} ->
{:error, reason}
end
end

def check_two_factor_notification_time(attrs, two_factor_issue_time) do
Expand Down
83 changes: 69 additions & 14 deletions lib/recognizer/bigcommerce.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,70 @@ defmodule Recognizer.BigCommerce do

def create_customer(user) do
case Client.create_customer(user) do
{:ok, bc_id} ->
Repo.insert(%Customer{user_id: user.id, bc_id: bc_id})
{:ok, user}
{:ok, :email_already_exists} ->
handle_email_already_exists(user)

{:ok, bc_id} when is_integer(bc_id) ->
handle_new_customer_created(user, bc_id)

{:error, e} ->
Logger.error("error creating bigcommerce customer: #{inspect(e)}")
{:error, e}
handle_customer_creation_error(user, e)
end
end

defp handle_email_already_exists(user) do
Logger.info("BigCommerce customer email already exists for user #{user.id}, attempting to link existing customer")

case Client.get_customers(emails: [user.email]) do
{:ok, [customer_id | _]} ->
link_existing_customer(user, customer_id)

{:ok, []} ->
handle_customer_not_found_error(user)

{:error, e} ->
handle_get_customers_error(user, e)
end
end

defp link_existing_customer(user, customer_id) do
case Repo.insert(%Customer{user_id: user.id, bc_id: customer_id}) do
{:ok, _} ->
Logger.info("Successfully linked existing BigCommerce customer #{customer_id} to user #{user.id}")
{:ok, user}

{:error, changeset} ->
Logger.error("Failed to link BigCommerce customer to user: #{inspect(changeset)}")
{:error, {:bigcommerce_link_failed, changeset}}
end
end

defp handle_customer_not_found_error(user) do
Logger.error("BigCommerce reported email exists but customer not found via API for user #{user.id}")
{:error, {:bigcommerce_customer_not_found, "Email exists but customer not found"}}
end

defp handle_get_customers_error(user, e) do
Logger.error("Failed to get existing BigCommerce customer for user #{user.id}: #{inspect(e)}")
{:error, {:bigcommerce_api_error, e}}
end

defp handle_new_customer_created(user, bc_id) do
case Repo.insert(%Customer{user_id: user.id, bc_id: bc_id}) do
{:ok, _} ->
{:ok, user}

{:error, changeset} ->
Logger.error("Failed to save BigCommerce customer ID to database: #{inspect(changeset)}")
{:ok, user}
end
end

defp handle_customer_creation_error(user, e) do
Logger.error("BigCommerce customer creation failed for user #{user.id}: #{inspect(e)}")
{:error, e}
end

def get_or_create_customer(%{email: email, id: id} = user) do
Logger.info("Starting BigCommerce get_or_create_customer for user #{id} with email #{email}")

Expand Down Expand Up @@ -54,17 +108,13 @@ defmodule Recognizer.BigCommerce do

{:error, e} ->
Logger.error("Error while getting BigCommerce customer: #{inspect(e)}")
# Don't fail account creation due to BigCommerce API errors
# This ensures verification emails are still sent
Logger.info("Continuing account creation process despite BigCommerce error")
{:ok, user}
# Apply strict approach: fail account creation for BigCommerce API errors
{:error, e}

e ->
Logger.error("Unexpected error while getting or creating BigCommerce customer: #{inspect(e)}")
# Don't fail account creation due to BigCommerce errors
# This ensures verification emails are still sent
Logger.info("Continuing account creation process despite unexpected BigCommerce error")
{:ok, user}
# Apply strict approach: fail account creation for unexpected errors
{:error, "Unexpected BigCommerce error"}
end
end

Expand All @@ -75,11 +125,16 @@ defmodule Recognizer.BigCommerce do

def update_customer(user) do
case Client.update_customer(Repo.preload(user, :bigcommerce_user)) do
{:ok, :email_already_exists} ->
# When updating, email already exists is actually a success condition
Logger.info("BigCommerce customer update: email already exists for user #{user.id}, treating as success")
{:ok, user}

{:ok, _} ->
{:ok, user}

{:error, e} ->
Logger.error("error creating bigcommerce customer: #{inspect(e)}")
Logger.error("BigCommerce customer update failed: #{inspect(e)}")
{:error, e}
end
end
Expand Down
51 changes: 47 additions & 4 deletions lib/recognizer/bigcommerce/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ defmodule Recognizer.BigCommerce.Client do
{:ok, %Response{body: response, status_code: 200}} ->
{:ok, response}

{:ok, %Response{status_code: 422, body: body}} ->
handle_422_error(body, "create")

{:ok, %Response{status_code: 429, headers: headers}} ->
sleep_for_rate_limit(headers)
post_customer(customer_json)
Expand All @@ -65,6 +68,9 @@ defmodule Recognizer.BigCommerce.Client do
{:ok, %Response{body: response, status_code: 200}} ->
{:ok, response}

{:ok, %Response{status_code: 422, body: body}} ->
handle_422_error(body, "update")

{:ok, %Response{status_code: 429, headers: headers}} ->
sleep_for_rate_limit(headers)
put_customer(customer_json)
Expand All @@ -77,6 +83,36 @@ defmodule Recognizer.BigCommerce.Client do
end
end

# Precisely analyze 422 errors to determine if it's email duplication
defp handle_422_error(body, operation) do
case Jason.decode(body) do
{:ok, %{"errors" => %{".customer_update" => error}}} when is_binary(error) ->
if String.contains?(String.downcase(error), "email") and
String.contains?(String.downcase(error), "already in use") do
Logger.info("BigCommerce customer #{operation}: email already exists, treating as success - #{error}")
{:ok, :email_already_exists}
else
Logger.error("BigCommerce customer #{operation} failed with validation error: #{error}")
{:error, {:validation_error, error}}
end

{:ok, %{"errors" => errors}} ->
Logger.error("BigCommerce customer #{operation} failed with errors: #{inspect(errors)}")
{:error, {:validation_errors, errors}}

{:ok, decoded} ->
Logger.error("BigCommerce customer #{operation} failed with unexpected 422 response: #{inspect(decoded)}")
{:error, {:unexpected_422, decoded}}

{:error, json_error} ->
Logger.error(
"BigCommerce customer #{operation} failed with unparseable 422 response: #{body}, JSON error: #{inspect(json_error)}"
)

{:error, {:unparseable_422, body}}
end
end

def get_customers(queries \\ []) do
with params <- customer_queries_as_params(queries),
full_uri <- customers_uri(),
Expand Down Expand Up @@ -125,10 +161,17 @@ defmodule Recognizer.BigCommerce.Client do
end

defp get_id(response) do
case Jason.decode(response) do
{:ok, %{"data" => [%{"id" => id}]}} -> {:ok, id}
{:error, e} -> {:error, e}
e -> {:error, e}
case response do
:email_already_exists ->
# If email already exists, return special response for upper level handling
{:ok, :email_already_exists}

_ ->
case Jason.decode(response) do
{:ok, %{"data" => [%{"id" => id}]}} -> {:ok, id}
{:error, e} -> {:error, e}
e -> {:error, e}
end
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,24 @@ defmodule RecognizerWeb.Accounts.Prompt.TwoFactorController do

def edit(conn, _params) do
user = conn.assigns.user
{:ok, %{two_factor_seed: seed}} = Accounts.get_new_two_factor_settings(user)

render(conn, "confirm.html",
barcode: Authentication.generate_totp_barcode(user, seed),
totp_app_url: Authentication.get_totp_app_url(user, seed)
)
case Accounts.get_new_two_factor_settings(user) do
{:ok, %{two_factor_seed: seed}} ->
render(conn, "confirm.html",
barcode: Authentication.generate_totp_barcode(user, seed),
totp_app_url: Authentication.get_totp_app_url(user, seed)
)

{:ok, nil} ->
conn
|> put_flash(:error, "Two factor setup not found. Please set up two factor authentication first.")
|> redirect(to: Routes.prompt_two_factor_path(conn, :new))

{:error, _reason} ->
conn
|> put_flash(:error, "Error retrieving two factor settings. Please try again.")
|> redirect(to: Routes.prompt_two_factor_path(conn, :new))
end
end

def update(conn, params) do
Expand Down
Loading