Skip to content

Commit 127311a

Browse files
Add domain mapping feature with email validation (#1460)
Co-authored-by: Jake Laderman <[email protected]>
1 parent 1d28476 commit 127311a

35 files changed

+6448
-228
lines changed

apps/core/lib/core.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ defmodule Core do
44

55
@chars String.codepoints("abcdefghijklmnopqrstuvwxyz")
66

7+
def priv_file!(filename) do
8+
:code.priv_dir(:core)
9+
|> Path.join(filename)
10+
|> File.read!()
11+
end
12+
713
def conf(key), do: Application.get_env(:core, key)
814

915
def env(var, :int, default) do

apps/core/lib/core/schema/domain_mapping.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,17 @@ defmodule Core.Schema.DomainMapping do
2424
from(dm in query, where: dm.workos_connection_id == ^conn_id)
2525
end
2626

27-
@restricted ~w(gmail.com outlook.com hotmail.com yahoo.com)
27+
@restricted Core.priv_file!("generic_emails.txt") |> String.split(~r/\s+/)
28+
29+
def restricted(), do: @restricted
2830

2931
@valid ~w(domain account_id enable_sso workos_connection_id)a
3032

3133
def changeset(model, attrs \\ %{}) do
3234
model
3335
|> cast(attrs, @valid)
3436
|> unique_constraint(:domain)
35-
|> validate_exclusion(:domain, @restricted, message: "cannot reserve any of [#{Enum.join(@restricted, ",")}]")
37+
|> validate_exclusion(:domain, @restricted, message: "cannot create domain mappings for consumer email domains")
3638
|> foreign_key_constraint(:account_id)
3739
|> validate_required([:domain])
3840
end

apps/core/lib/core/schema/user.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ defmodule Core.Schema.User do
9090
timestamps()
9191
end
9292

93+
def domain(%__MODULE__{email: email}) do
94+
case String.split(email, "@") do
95+
[_, domain] -> domain
96+
_ -> nil
97+
end
98+
end
99+
93100
def mark_url_safe(), do: Process.put({__MODULE__, :url_safe}, true)
94101

95102
def url_safe?(), do: !!Process.get({__MODULE__, :url_safe})

apps/core/lib/core/services/accounts.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ defmodule Core.Services.Accounts do
112112
do: Stripe.Customer.update(cid, stripe)
113113
%{db: db} -> {:ok, db}
114114
end)
115+
|> add_operation(:domain_mappings, fn
116+
%{db: %Account{domain_mappings: [_ | _] = mappings} = account} ->
117+
email_domain = User.domain(user)
118+
case {Enum.all?(mappings, fn %DomainMapping{domain: domain} -> domain == email_domain end), !!user.email_confirmed} do
119+
{true, false} -> {:error, "you must confirm your email to modify domain mappings"}
120+
{true, _} -> {:ok, account}
121+
{_, _} -> {:error, "you cannot create a domain mapping that does not match your email domain"}
122+
end
123+
%{db: db} -> {:ok, db}
124+
end)
115125
|> execute(extract: :db)
116126
end
117127

apps/core/lib/core/services/users.ex

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ defmodule Core.Services.Users do
66
alias Core.Clients.ZeroSSL
77
alias Core.PubSub
88
alias Core.Schema.{
9+
Account,
910
PersistedToken,
1011
User,
1112
Publisher,
@@ -354,8 +355,15 @@ defmodule Core.Services.Users do
354355
|> Core.Repo.insert()
355356
end)
356357
|> add_operation(:user, fn %{pre: user} ->
357-
with {:ok, %{user: user}} <- Accounts.create_account(Map.get(attrs, :account, %{}), user),
358-
do: {:ok, user}
358+
case Accounts.get_mapping_for_email(user.email) do
359+
%DomainMapping{account: %Account{id: aid}} ->
360+
user
361+
|> Ecto.Changeset.change(%{account_id: aid})
362+
|> Core.Repo.update()
363+
_ ->
364+
Accounts.create_account(Map.get(attrs, :account, %{}), user)
365+
|> when_ok(& &1.user)
366+
end
359367
end)
360368
|> execute(extract: :user)
361369
|> notify(:create)

0 commit comments

Comments
 (0)