mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 18:18:55 +00:00
fix(ux): Rename Magic Link to Email (OTP) (#5939)
Fixes #5927 See https://www.firezone.dev/kb/authenticate/email <img width="1258" alt="Screenshot 2024-07-21 at 11 29 59 AM" src="https://github.com/user-attachments/assets/07d5596f-b74c-4bc7-91df-3565ae552f15">
This commit is contained in:
@@ -404,13 +404,13 @@ Interactive Elixir (1.15.2) - press Ctrl+C to exit (type h() ENTER for help)
|
||||
iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)1> {:ok, account} = Domain.Accounts.create_account(%{name: "Firezone", slug: "firezone"})
|
||||
{:ok, ...}
|
||||
|
||||
iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)2> {:ok, magic_link_provider} = Domain.Auth.create_provider(account, %{name: "Email", adapter: :email, adapter_config: %{}})
|
||||
iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)2> {:ok, email_provider} = Domain.Auth.create_provider(account, %{name: "Email (OTP)", adapter: :email, adapter_config: %{}})
|
||||
{:ok, ...}
|
||||
|
||||
iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)3> {:ok, actor} = Domain.Actors.create_actor(account, %{type: :account_admin_user, name: "Andrii Dryga"})
|
||||
{:ok, ...}
|
||||
|
||||
iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)4> {:ok, identity} = Domain.Auth.upsert_identity(actor, magic_link_provider, %{provider_identifier: "a@firezone.dev", provider_identifier_confirmation: "a@firezone.dev"})
|
||||
iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)4> {:ok, identity} = Domain.Auth.upsert_identity(actor, email_provider, %{provider_identifier: "a@firezone.dev", provider_identifier_confirmation: "a@firezone.dev"})
|
||||
...
|
||||
|
||||
iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)5> context = %Domain.Auth.Context{type: :browser, user_agent: "User-Agent: iOS/12.7 (iPhone) connlib/0.7.412", remote_ip: {127, 0, 0, 1}}
|
||||
|
||||
@@ -72,7 +72,7 @@ defmodule Domain.Auth do
|
||||
alias Domain.Auth.Identity
|
||||
|
||||
# This session duration is used when IdP doesn't return the token expiration date,
|
||||
# or no IdP is used (eg. sign in via magic link or userpass).
|
||||
# or no IdP is used (eg. sign in via email or userpass).
|
||||
@default_session_duration_hours [
|
||||
browser: [
|
||||
account_admin_user: 10,
|
||||
@@ -310,7 +310,7 @@ defmodule Domain.Auth do
|
||||
end)
|
||||
end
|
||||
|
||||
# used during magic link auth flow
|
||||
# used during email auth flow
|
||||
def fetch_active_identity_by_provider_and_identifier(
|
||||
%Provider{adapter: :email} = provider,
|
||||
provider_identifier,
|
||||
|
||||
@@ -267,9 +267,9 @@ defmodule Domain.Billing.EventHandler do
|
||||
membership_rules: [%{operator: true}]
|
||||
})
|
||||
|
||||
{:ok, magic_link_provider} =
|
||||
{:ok, email_provider} =
|
||||
Domain.Auth.create_provider(account, %{
|
||||
name: "Email",
|
||||
name: "Email (OTP)",
|
||||
adapter: :email,
|
||||
adapter_config: %{}
|
||||
})
|
||||
@@ -281,7 +281,7 @@ defmodule Domain.Billing.EventHandler do
|
||||
})
|
||||
|
||||
{:ok, _identity} =
|
||||
Domain.Auth.upsert_identity(actor, magic_link_provider, %{
|
||||
Domain.Auth.upsert_identity(actor, email_provider, %{
|
||||
provider_identifier: metadata["account_admin_email"] || account_email,
|
||||
provider_identifier_confirmation: metadata["account_admin_email"] || account_email
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ defmodule Domain.Ops do
|
||||
membership_rules: [%{operator: true}]
|
||||
})
|
||||
|
||||
{:ok, magic_link_provider} =
|
||||
{:ok, email_provider} =
|
||||
Domain.Auth.create_provider(account, %{
|
||||
name: "Email",
|
||||
adapter: :email,
|
||||
@@ -41,12 +41,12 @@ defmodule Domain.Ops do
|
||||
})
|
||||
|
||||
{:ok, identity} =
|
||||
Domain.Auth.upsert_identity(actor, magic_link_provider, %{
|
||||
Domain.Auth.upsert_identity(actor, email_provider, %{
|
||||
provider_identifier: account_admin_email,
|
||||
provider_identifier_confirmation: account_admin_email
|
||||
})
|
||||
|
||||
%{account: account, provider: magic_link_provider, actor: actor, identity: identity}
|
||||
%{account: account, provider: email_provider, actor: actor, identity: identity}
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -76,13 +76,13 @@ defmodule Domain.Ops do
|
||||
Domain.Repo.transaction(fn ->
|
||||
{:ok, account} = Domain.Accounts.fetch_account_by_id_or_slug(account_slug)
|
||||
providers = Domain.Auth.all_active_providers_for_account!(account)
|
||||
magic_link_provider = Enum.find(providers, fn provider -> provider.adapter == :email end)
|
||||
email_provider = Enum.find(providers, fn provider -> provider.adapter == :email end)
|
||||
|
||||
{:ok, actor} =
|
||||
Domain.Actors.create_actor(account, %{type: :account_admin_user, name: "Firezone Support"})
|
||||
|
||||
{:ok, identity} =
|
||||
Domain.Auth.upsert_identity(actor, magic_link_provider, %{
|
||||
Domain.Auth.upsert_identity(actor, email_provider, %{
|
||||
provider_identifier: "ent-support@firezone.dev",
|
||||
provider_identifier_confirmation: "ent-support@firezone.dev"
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
defmodule Domain.Repo.Migrations.RenameMagicLinkToEmailOTP do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
"""
|
||||
UPDATE auth_providers
|
||||
SET name = 'Email (OTP)'
|
||||
WHERE adapter = 'email';
|
||||
"""
|
||||
|> execute("")
|
||||
end
|
||||
end
|
||||
@@ -62,7 +62,7 @@ defmodule Web.AuthController do
|
||||
@doc """
|
||||
This is a callback for the Email provider which sends login link.
|
||||
"""
|
||||
def request_magic_link(
|
||||
def request_email_otp(
|
||||
conn,
|
||||
%{
|
||||
"account_id_or_slug" => account_id_or_slug,
|
||||
@@ -76,7 +76,7 @@ defmodule Web.AuthController do
|
||||
|
||||
with true <- String.contains?(provider_identifier, "@"),
|
||||
{:ok, provider} <- Domain.Auth.fetch_active_provider_by_id(provider_id) do
|
||||
conn = maybe_send_magic_link_email(conn, provider, provider_identifier, redirect_params)
|
||||
conn = maybe_send_email_otp(conn, provider, provider_identifier, redirect_params)
|
||||
|
||||
signed_provider_identifier =
|
||||
Plug.Crypto.sign(
|
||||
@@ -110,7 +110,7 @@ defmodule Web.AuthController do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_send_magic_link_email(conn, provider, provider_identifier, redirect_params) do
|
||||
defp maybe_send_email_otp(conn, provider, provider_identifier, redirect_params) do
|
||||
context_type = Web.Auth.fetch_auth_context_type!(redirect_params)
|
||||
context = Web.Auth.get_auth_context(conn, context_type)
|
||||
|
||||
@@ -125,7 +125,7 @@ defmodule Web.AuthController do
|
||||
),
|
||||
{:ok, identity} <-
|
||||
Domain.Auth.Adapters.Email.request_sign_in_token(identity, context),
|
||||
{:ok, fragment} <- send_magic_link_email(conn, identity, redirect_params) do
|
||||
{:ok, fragment} <- send_email_otp(conn, identity, redirect_params) do
|
||||
fragment
|
||||
else
|
||||
_ ->
|
||||
@@ -144,7 +144,7 @@ defmodule Web.AuthController do
|
||||
put_auth_state(conn, provider.id, {fragment, provider_identifier, redirect_params})
|
||||
end
|
||||
|
||||
defp send_magic_link_email(conn, identity, redirect_params) do
|
||||
defp send_email_otp(conn, identity, redirect_params) do
|
||||
# Nonce is the short part that is sent to the user in the email
|
||||
nonce = identity.provider_virtual_state.nonce
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ defmodule Web.Settings.IdentityProviders.GoogleWorkspace.Show do
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Identity Provider <code><%= @provider.name %></code>
|
||||
Identity Provider: <code><%= @provider.name %></code>
|
||||
<span :if={not is_nil(@provider.disabled_at)} class="text-primary-600">(disabled)</span>
|
||||
<span :if={not is_nil(@provider.deleted_at)} class="text-red-600">(deleted)</span>
|
||||
</:title>
|
||||
|
||||
@@ -41,7 +41,7 @@ defmodule Web.Settings.IdentityProviders.JumpCloud.Show do
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Identity Provider <code><%= @provider.name %></code>
|
||||
Identity Provider: <code><%= @provider.name %></code>
|
||||
<span :if={not is_nil(@provider.disabled_at)} class="text-primary-600">(disabled)</span>
|
||||
<span :if={not is_nil(@provider.deleted_at)} class="text-red-600">(deleted)</span>
|
||||
</:title>
|
||||
|
||||
@@ -38,7 +38,7 @@ defmodule Web.Settings.IdentityProviders.MicrosoftEntra.Show do
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Identity Provider <code><%= @provider.name %></code>
|
||||
Identity Provider: <code><%= @provider.name %></code>
|
||||
<span :if={not is_nil(@provider.disabled_at)} class="text-primary-600">(disabled)</span>
|
||||
<span :if={not is_nil(@provider.deleted_at)} class="text-red-600">(deleted)</span>
|
||||
</:title>
|
||||
|
||||
@@ -38,7 +38,7 @@ defmodule Web.Settings.IdentityProviders.Okta.Show do
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Identity Provider <code><%= @provider.name %></code>
|
||||
Identity Provider: <code><%= @provider.name %></code>
|
||||
<span :if={not is_nil(@provider.disabled_at)} class="text-primary-600">(disabled)</span>
|
||||
<span :if={not is_nil(@provider.deleted_at)} class="text-red-600">(deleted)</span>
|
||||
</:title>
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule Web.Settings.IdentityProviders.OpenIDConnect.Show do
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Identity Provider <code><%= @provider.name %></code>
|
||||
Identity Provider: <code><%= @provider.name %></code>
|
||||
<span :if={not is_nil(@provider.disabled_at)} class="text-primary-600">(disabled)</span>
|
||||
<span :if={not is_nil(@provider.deleted_at)} class="text-red-600">(deleted)</span>
|
||||
</:title>
|
||||
|
||||
@@ -33,7 +33,7 @@ defmodule Web.Settings.IdentityProviders.System.Show do
|
||||
|
||||
<.section>
|
||||
<:title>
|
||||
Identity Provider <code><%= @provider.name %></code>
|
||||
Identity Provider: <code><%= @provider.name %></code>
|
||||
<span :if={not is_nil(@provider.disabled_at)} class="text-primary-600">(disabled)</span>
|
||||
<span :if={not is_nil(@provider.deleted_at)} class="text-red-600">(deleted)</span>
|
||||
</:title>
|
||||
|
||||
@@ -204,7 +204,7 @@ defmodule Web.SignIn do
|
||||
~H"""
|
||||
<.form
|
||||
for={@email_form}
|
||||
action={~p"/#{@account}/sign_in/providers/#{@provider.id}/request_magic_link"}
|
||||
action={~p"/#{@account}/sign_in/providers/#{@provider.id}/request_email_otp"}
|
||||
class="space-y-4 lg:space-y-6"
|
||||
id="email_form"
|
||||
phx-update="ignore"
|
||||
|
||||
@@ -158,7 +158,7 @@ defmodule Web.SignIn.Email do
|
||||
as={:email}
|
||||
class="inline"
|
||||
action={
|
||||
~p"/#{@account_id_or_slug}/sign_in/providers/#{@provider_id}/request_magic_link?resend=true"
|
||||
~p"/#{@account_id_or_slug}/sign_in/providers/#{@provider_id}/request_email_otp?resend=true"
|
||||
}
|
||||
method="post"
|
||||
>
|
||||
|
||||
@@ -180,7 +180,7 @@ defmodule Web.SignUp do
|
||||
id="resend-email"
|
||||
as={:email}
|
||||
class="inline"
|
||||
action={~p"/#{@account}/sign_in/providers/#{@provider}/request_magic_link"}
|
||||
action={~p"/#{@account}/sign_in/providers/#{@provider}/request_email_otp"}
|
||||
method="post"
|
||||
>
|
||||
<.input
|
||||
|
||||
@@ -93,7 +93,7 @@ defmodule Web.Router do
|
||||
post "/verify_credentials", AuthController, :verify_credentials
|
||||
|
||||
# Email
|
||||
post "/request_magic_link", AuthController, :request_magic_link
|
||||
post "/request_email_otp", AuthController, :request_email_otp
|
||||
get "/verify_sign_in_token", AuthController, :verify_sign_in_token
|
||||
|
||||
# IdP
|
||||
|
||||
@@ -75,7 +75,7 @@ defmodule Web.ConnCase do
|
||||
|> Plug.Conn.assign(:subject, subject)
|
||||
end
|
||||
|
||||
def put_magic_link_auth_state(
|
||||
def put_email_auth_state(
|
||||
conn,
|
||||
account,
|
||||
%{adapter: :email} = provider,
|
||||
@@ -86,7 +86,7 @@ defmodule Web.ConnCase do
|
||||
Map.merge(%{"email" => %{"provider_identifier" => identity.provider_identifier}}, params)
|
||||
|
||||
redirected_conn =
|
||||
post(conn, ~p"/#{account}/sign_in/providers/#{provider.id}/request_magic_link", params)
|
||||
post(conn, ~p"/#{account}/sign_in/providers/#{provider.id}/request_email_otp", params)
|
||||
|
||||
assert_received {:email, email}
|
||||
[_match, secret] = Regex.run(~r/secret=([^&\n]*)/, email.text_body)
|
||||
@@ -136,7 +136,7 @@ defmodule Web.ConnCase do
|
||||
)
|
||||
|
||||
redirected_conn =
|
||||
post(conn, ~p"/#{account}/sign_in/providers/#{provider.id}/request_magic_link", params)
|
||||
post(conn, ~p"/#{account}/sign_in/providers/#{provider.id}/request_email_otp", params)
|
||||
|
||||
assert_received {:email, email}
|
||||
[_match, secret] = Regex.run(~r/secret=([^&\n]*)/, email.text_body)
|
||||
|
||||
@@ -323,7 +323,7 @@ defmodule Web.AuthControllerTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "request_magic_link/2" do
|
||||
describe "request_email_otp/2" do
|
||||
test "sends a login link to the user email", %{conn: conn} do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
provider = Fixtures.Auth.create_email_provider(account: account)
|
||||
@@ -332,7 +332,7 @@ defmodule Web.AuthControllerTest do
|
||||
conn =
|
||||
post(
|
||||
conn,
|
||||
~p"/#{provider.account_id}/sign_in/providers/#{provider.id}/request_magic_link",
|
||||
~p"/#{provider.account_id}/sign_in/providers/#{provider.id}/request_email_otp",
|
||||
%{
|
||||
"email" => %{
|
||||
"provider_identifier" => identity.provider_identifier
|
||||
@@ -362,7 +362,7 @@ defmodule Web.AuthControllerTest do
|
||||
identity = Fixtures.Auth.create_identity(account: account, provider: provider)
|
||||
|
||||
for _ <- 1..3 do
|
||||
post(conn, ~p"/#{account}/sign_in/providers/#{provider}/request_magic_link", %{
|
||||
post(conn, ~p"/#{account}/sign_in/providers/#{provider}/request_email_otp", %{
|
||||
"email" => %{
|
||||
"provider_identifier" => identity.provider_identifier
|
||||
}
|
||||
@@ -373,7 +373,7 @@ defmodule Web.AuthControllerTest do
|
||||
end)
|
||||
end
|
||||
|
||||
post(conn, ~p"/#{account}/sign_in/providers/#{provider}/request_magic_link", %{
|
||||
post(conn, ~p"/#{account}/sign_in/providers/#{provider}/request_email_otp", %{
|
||||
"email" => %{
|
||||
"provider_identifier" => identity.provider_identifier
|
||||
}
|
||||
@@ -390,7 +390,7 @@ defmodule Web.AuthControllerTest do
|
||||
conn =
|
||||
post(
|
||||
conn,
|
||||
~p"/#{provider.account_id}/sign_in/providers/#{provider.id}/request_magic_link",
|
||||
~p"/#{provider.account_id}/sign_in/providers/#{provider.id}/request_email_otp",
|
||||
%{
|
||||
"as" => "client",
|
||||
"nonce" => "NONCE",
|
||||
@@ -425,7 +425,7 @@ defmodule Web.AuthControllerTest do
|
||||
conn =
|
||||
post(
|
||||
conn,
|
||||
~p"/#{account.id}/sign_in/providers/#{provider_id}/request_magic_link",
|
||||
~p"/#{account.id}/sign_in/providers/#{provider_id}/request_email_otp",
|
||||
%{"email" => %{"provider_identifier" => "foo@bar.com"}}
|
||||
)
|
||||
|
||||
@@ -440,7 +440,7 @@ defmodule Web.AuthControllerTest do
|
||||
conn =
|
||||
post(
|
||||
conn,
|
||||
~p"/#{account.id}/sign_in/providers/#{provider_id}/request_magic_link",
|
||||
~p"/#{account.id}/sign_in/providers/#{provider_id}/request_email_otp",
|
||||
%{"email" => %{"provider_identifier" => "foo"}}
|
||||
)
|
||||
|
||||
@@ -455,7 +455,7 @@ defmodule Web.AuthControllerTest do
|
||||
conn =
|
||||
post(
|
||||
conn,
|
||||
~p"/#{account.id}/sign_in/providers/#{provider.id}/request_magic_link",
|
||||
~p"/#{account.id}/sign_in/providers/#{provider.id}/request_email_otp",
|
||||
%{"email" => %{"provider_identifier" => "foo@bar"}}
|
||||
)
|
||||
|
||||
@@ -480,7 +480,7 @@ defmodule Web.AuthControllerTest do
|
||||
actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
identity = Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor)
|
||||
|
||||
{conn_with_cookie, secret} = put_magic_link_auth_state(conn, account, provider, identity)
|
||||
{conn_with_cookie, secret} = put_email_auth_state(conn, account, provider, identity)
|
||||
|
||||
%{
|
||||
account: account,
|
||||
@@ -596,7 +596,7 @@ defmodule Web.AuthControllerTest do
|
||||
}
|
||||
|
||||
{conn_with_cookie, _secret} =
|
||||
put_magic_link_auth_state(conn, account, provider, identity, redirect_params)
|
||||
put_email_auth_state(conn, account, provider, identity, redirect_params)
|
||||
|
||||
conn =
|
||||
conn_with_cookie
|
||||
@@ -679,7 +679,7 @@ defmodule Web.AuthControllerTest do
|
||||
}
|
||||
|
||||
{conn_with_cookie, secret} =
|
||||
put_magic_link_auth_state(conn, account, provider, identity, redirect_params)
|
||||
put_email_auth_state(conn, account, provider, identity, redirect_params)
|
||||
|
||||
conn =
|
||||
conn_with_cookie
|
||||
@@ -1233,7 +1233,7 @@ defmodule Web.AuthControllerTest do
|
||||
provider: provider
|
||||
)
|
||||
|
||||
{conn, secret} = put_magic_link_auth_state(conn, account, provider, identity)
|
||||
{conn, secret} = put_email_auth_state(conn, account, provider, identity)
|
||||
|
||||
authorized_conn =
|
||||
conn
|
||||
|
||||
@@ -27,7 +27,7 @@ defmodule Web.SignIn.EmailTest do
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
{conn, _secret} = put_magic_link_auth_state(conn, account, provider, identity)
|
||||
{conn, _secret} = put_email_auth_state(conn, account, provider, identity)
|
||||
|
||||
signed_provider_identifier =
|
||||
Plug.Crypto.sign(
|
||||
@@ -53,7 +53,7 @@ defmodule Web.SignIn.EmailTest do
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
{conn, secret} = put_magic_link_auth_state(conn, account, provider, identity)
|
||||
{conn, secret} = put_email_auth_state(conn, account, provider, identity)
|
||||
|
||||
signed_provider_identifier =
|
||||
Plug.Crypto.sign(
|
||||
@@ -99,7 +99,7 @@ defmodule Web.SignIn.EmailTest do
|
||||
"redirect_to" => "/foo"
|
||||
}
|
||||
|
||||
{conn, secret} = put_magic_link_auth_state(conn, account, provider, identity, redirect_params)
|
||||
{conn, secret} = put_email_auth_state(conn, account, provider, identity, redirect_params)
|
||||
|
||||
signed_provider_identifier =
|
||||
Plug.Crypto.sign(
|
||||
@@ -139,7 +139,7 @@ defmodule Web.SignIn.EmailTest do
|
||||
identity: identity,
|
||||
conn: conn
|
||||
} do
|
||||
{conn, _secret} = put_magic_link_auth_state(conn, account, provider, identity)
|
||||
{conn, _secret} = put_email_auth_state(conn, account, provider, identity)
|
||||
|
||||
signed_provider_identifier =
|
||||
Plug.Crypto.sign(
|
||||
|
||||
@@ -714,7 +714,7 @@ export default function Page() {
|
||||
<li className="flex space-x-2.5">
|
||||
<HiCheck className="flex-shrink-0 w-5 h-5 text-neutral-900" />
|
||||
<span className="leading-tight text-lg text-neutral-900 ">
|
||||
Authenticate with Magic link or OIDC
|
||||
Authenticate with Email OTP or OIDC
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex space-x-2.5">
|
||||
|
||||
Reference in New Issue
Block a user