Skip to content

Commit

Permalink
BC Support (#135)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrew Hebert <[email protected]>
  • Loading branch information
macifell and ah-s76 authored Dec 4, 2023
1 parent 483b663 commit 7d4d3e0
Show file tree
Hide file tree
Showing 22 changed files with 442 additions and 58 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ npm-debug.log

/.elixir_ls/
/log/

# local environment
.env*
9 changes: 9 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,12 @@ config :hammer,
pool_size: 4,
pool_max_overflow: 2
]}

config :recognizer, Recognizer.BigCommerce,
client_id: "bc_id",
client_secret: "bc_secret",
access_token: "bc_access_token",
store_hash: "bc_store_hash",
login_uri: "http://localhost/",
http_client: HTTPoison,
enabled?: true
9 changes: 9 additions & 0 deletions config/releases.exs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,12 @@ config :hammer,
pool_size: 4,
pool_max_overflow: 2
]}

config :recognizer, Recognizer.BigCommerce,
client_id: recognizer_config["BIGCOMMERCE_CLIENT_ID"],
client_secret: recognizer_config["BIGCOMMERCE_CLIENT_SECRET"],
access_token: recognizer_config["BIGCOMMERCE_ACCESS_TOKEN"],
store_hash: recognizer_config["BIGCOMMERCE_STORE_HASH"],
login_uri: recognizer_config["BIGCOMMERCE_LOGIN_URI"],
http_client: HTTPoison,
enabled?: false
9 changes: 9 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ config :hammer,
pool_size: 4,
pool_max_overflow: 2
]}

config :recognizer, Recognizer.BigCommerce,
client_id: "bc_id",
client_secret: "bc_secret",
access_token: "bc_access_token",
store_hash: "bc_store_hash",
login_uri: "http://localhost/",
http_client: HTTPoisonMock,
enabled?: true
29 changes: 26 additions & 3 deletions lib/recognizer/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule Recognizer.Accounts do
alias Recognizer.Accounts.RecoveryCode
alias Recognizer.Accounts.User
alias Recognizer.Accounts.VerificationCode
alias Recognizer.BigCommerce
alias Recognizer.Notifications.Account, as: Notification
alias Recognizer.Guardian
alias Recognizer.Repo
Expand Down Expand Up @@ -166,20 +167,29 @@ defmodule Recognizer.Accounts do
{:error, %Ecto.Changeset{}}
"""
def register_user(attrs, opts \\ [])
def register_user(attrs, opts \\ []) do
Repo.transaction(fn ->
case do_register_user(attrs, opts) do
{:ok, user} -> user
{:error, e} -> Repo.rollback(e)
end
end)
end

def register_user(attrs, verify_account_url_fun: verify_account_url_fun) do
defp do_register_user(attrs, verify_account_url_fun: verify_account_url_fun) do
%User{}
|> User.registration_changeset(attrs)
|> insert_user_and_notification_preferences()
|> maybe_create_big_commerce_customer()
|> maybe_generate_verification_code(verify_account_url_fun)
|> maybe_send_newsletter(attrs)
end

def register_user(attrs, opts) do
defp do_register_user(attrs, opts) do
%User{}
|> User.registration_changeset(attrs, opts)
|> insert_user_and_notification_preferences()
|> maybe_create_big_commerce_customer()
|> maybe_mark_user_verified()
|> maybe_notify_new_user()
|> maybe_send_newsletter(attrs)
Expand All @@ -203,6 +213,7 @@ defmodule Recognizer.Accounts do
%User{}
|> User.oauth_registration_changeset(attrs)
|> insert_user_and_notification_preferences()
|> maybe_create_big_commerce_customer()
|> maybe_notify_new_user()
end

Expand Down Expand Up @@ -705,4 +716,16 @@ defmodule Recognizer.Accounts do

Repo.delete_all(user_codes)
end

def maybe_create_big_commerce_customer({:ok, user}) do
if BigCommerce.enabled?() do
BigCommerce.create_customer(user)
else
{:ok, user}
end
end

def maybe_create_big_commerce_customer(error) do
error
end
end
15 changes: 15 additions & 0 deletions lib/recognizer/accounts/bc_customer_user.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Recognizer.Accounts.BCCustomerUser do
@moduledoc false

use Ecto.Schema

alias Recognizer.Accounts.User

@primary_key false
schema "bigcommerce_customer_users" do
field :bc_id, :integer
belongs_to :user, User, primary_key: true

timestamps()
end
end
8 changes: 7 additions & 1 deletion lib/recognizer/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ defmodule Recognizer.Accounts.User do

import Ecto.Changeset

alias Recognizer.Accounts.{NotificationPreference, OAuth, Organization, RecoveryCode, Role}
alias Recognizer.Accounts.BCCustomerUser, as: BcUser
alias Recognizer.Accounts.NotificationPreference
alias Recognizer.Accounts.OAuth
alias Recognizer.Accounts.Organization
alias Recognizer.Accounts.RecoveryCode
alias Recognizer.Accounts.Role
alias Recognizer.Repo
alias __MODULE__

Expand Down Expand Up @@ -41,6 +46,7 @@ defmodule Recognizer.Accounts.User do
field :verified_at, :utc_datetime

has_one :notification_preference, NotificationPreference, on_replace: :update
has_one :bigcommerce_user, BcUser

belongs_to :organization, Organization

Expand Down
61 changes: 61 additions & 0 deletions lib/recognizer/bigcommerce.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule Recognizer.BigCommerce do
@moduledoc """
BigCommerce context.
"""

require Logger

alias Recognizer.Accounts.BCCustomerUser, as: Customer
alias Recognizer.BigCommerce.Client
alias Recognizer.BigCommerce.Token
alias Recognizer.Repo

def enabled?() do
config(:enabled?)
end

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}

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

def generate_login_jwt(user) do
{:ok, token, _claims} =
user
|> Recognizer.Repo.preload(:bigcommerce_user)
|> jwt_claims()
|> Token.generate_and_sign(jwt_signer())

token
end

def login_redirect_uri(jwt) do
config(:login_uri) <> jwt
end

defp jwt_claims(user) do
%{
"aud" => "BigCommerce",
"iss" => config(:client_id),
"jti" => Ecto.UUID.generate(),
"operation" => "customer_login",
"store_hash" => config(:store_hash),
"customer_id" => user.bigcommerce_user.bc_id
}
end

defp jwt_signer() do
Joken.Signer.create("HS256", config(:client_secret))
end

defp config(key) do
Application.get_env(:recognizer, __MODULE__)[key]
end
end
98 changes: 98 additions & 0 deletions lib/recognizer/bigcommerce/client.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
defmodule Recognizer.BigCommerce.Client do
@moduledoc """
BigCommerce v3
"""

require Logger

alias Recognizer.Accounts.User

alias HTTPoison.Response

@default_retry_ms 5000

def create_customer(user) do
with {:ok, customer_params} <- user_as_customer_params(user),
{:ok, customer_json} <- Jason.encode(customer_params),
{:ok, response} <- post_customer(customer_json) do
get_id(response)
else
{:error, e} ->
Logger.error("cannot create customer with error: #{inspect(e)}")
{:error, e}

e ->
Logger.error("cannot create customer with error: #{inspect(e)}")
{:error, e}
end
end

defp user_as_customer_params(%User{email: email, first_name: first_name, last_name: last_name}) do
{:ok, [%{"email" => email, "first_name" => first_name, "last_name" => last_name}]}
end

defp user_as_customer_params(_user) do
{:error, :invalid_user}
end

defp post_customer(customer_json) do
case http_client().post(customers_uri(), customer_json, default_headers()) do
{:ok, %Response{body: response, status_code: 200}} ->
{:ok, response}

{:ok, %Response{status_code: 429, headers: headers}} ->
sleep_for_rate_limit(headers)
post_customer(customer_json)

{:error, e} ->
{:error, e}

e ->
{:error, e}
end
end

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

defp default_headers() do
[
{"Content-Type", "application/json"},
{"Accept", "application/json"},
{"Authorization", "Bearer #{config(:access_token)}"},
{"x-Auth-Token", config(:access_token)}
]
end

defp customers_uri() do
uri("/v3/customers")
end

defp uri(path) do
"https://api.bigcommerce.com/stores/#{config(:store_hash)}#{path}"
end

defp http_client() do
config(:http_client)
end

defp config(key) do
Application.get_env(:recognizer, Recognizer.BigCommerce)[key]
end

defp sleep_for_rate_limit(headers) do
retry_ms =
case List.keyfind(headers, "x-rate-limit-time-reset-ms", 0) do
nil -> @default_retry_ms
{_, retry_value} -> String.to_integer(retry_value)
end

Logger.warn("Rate limited, sleeping for ms: #{inspect(retry_ms)}")
Process.sleep(retry_ms)
end
end
5 changes: 5 additions & 0 deletions lib/recognizer/bigcommerce/token.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Recognizer.BigCommerce.Token do
@moduledoc false

use Joken.Config
end
27 changes: 20 additions & 7 deletions lib/recognizer_web/authentication.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ defmodule RecognizerWeb.Authentication do
import Phoenix.Controller

alias Guardian.DB, as: GuardianDB
alias RecognizerWeb.Router.Helpers, as: Routes
alias Recognizer.BigCommerce
alias Recognizer.Guardian
alias RecognizerWeb.Router.Helpers, as: Routes

@doc """
Logs the user in.
Expand All @@ -31,15 +32,27 @@ defmodule RecognizerWeb.Authentication do
|> redirect(to: Routes.prompt_two_factor_path(conn, :new))

{:ok, _user} ->
redirect_opts = login_redirect(conn)

conn
|> clear_session()
|> Guardian.Plug.sign_in(user, params)
|> redirect(redirect_opts)
if BigCommerce.enabled?() && get_session(conn, :bc) do
log_in_bc_user(conn, user)
else
redirect_opts = login_redirect(conn)

conn
|> clear_session()
|> Guardian.Plug.sign_in(user, params)
|> redirect(redirect_opts)
end
end
end

defp log_in_bc_user(conn, user) do
jwt = BigCommerce.generate_login_jwt(user)

conn
|> clear_session()
|> redirect(external: BigCommerce.login_redirect_uri(jwt))
end

@doc """
Logs the user in via the API.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ defmodule RecognizerWeb.Accounts.UserOAuthController do
conn
|> put_flash(:error, friendly_error_message(changeset))
|> redirect(to: Routes.user_session_path(conn, :new))

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

Expand Down Expand Up @@ -83,8 +86,8 @@ defmodule RecognizerWeb.Accounts.UserOAuthController do
{:ok, _oauth} <- Accounts.create_oauth(user, provider, uid) do
user
else
{:error, changeset} ->
Repo.rollback(changeset)
{:error, e} ->
Repo.rollback(e)
end
end)
end
Expand Down
Loading

0 comments on commit 7d4d3e0

Please sign in to comment.