Harden authentication email delivery
This commit is contained in:
parent
41b37ccf37
commit
f5c0d1d9b2
|
|
@ -211,6 +211,15 @@ one `INSERT ... ON CONFLICT` statement. Failed transactional-email delivery
|
|||
removes only the token created for that failed attempt. Expired buckets and
|
||||
tokens are pruned by the maintenance worker.
|
||||
|
||||
Authentication delivery also reuses the validity window of an existing
|
||||
one-time link as an idempotency window: a repeated request for the same account
|
||||
and flow does not create or send another link while the previous one remains
|
||||
valid. The account row is locked only while reserving the token, so concurrent
|
||||
requests cannot produce duplicate messages and SMTP work does not run inside a
|
||||
database transaction. Google verification flows include a non-reversible hash
|
||||
of the pending flow in the token context, so a new OAuth flow is not suppressed
|
||||
by an older one.
|
||||
|
||||
The Compose and kind scripts finish by subscribing on one live BEAM node,
|
||||
broadcasting through a different connected node, and failing if the PubSub
|
||||
probe is not received.
|
||||
|
|
|
|||
|
|
@ -1067,9 +1067,10 @@ defmodule WhoNeedHelp.Accounts do
|
|||
end
|
||||
|
||||
@doc "Delivers a one-time local-account verification link before connecting Google."
|
||||
def deliver_google_link_instructions(%User{} = user, magic_link_url_fun)
|
||||
when is_function(magic_link_url_fun, 1) do
|
||||
{encoded_token, user_token} = UserToken.build_email_token(user, "login")
|
||||
def deliver_google_link_instructions(%User{} = user, delivery_key, magic_link_url_fun)
|
||||
when is_binary(delivery_key) and is_function(magic_link_url_fun, 1) do
|
||||
context = "login:google:#{short_hash(delivery_key)}"
|
||||
{encoded_token, user_token} = UserToken.build_email_token(user, context)
|
||||
|
||||
persist_email_token_and_deliver(user_token, fn ->
|
||||
UserNotifier.deliver_google_link_instructions(user, magic_link_url_fun.(encoded_token))
|
||||
|
|
@ -1093,8 +1094,54 @@ defmodule WhoNeedHelp.Accounts do
|
|||
## Token helper
|
||||
|
||||
defp persist_email_token_and_deliver(user_token, deliver_fun) do
|
||||
persisted_token = Repo.insert!(user_token)
|
||||
case reserve_email_token(user_token) do
|
||||
{:ok, :already_sent} ->
|
||||
{:ok, :already_sent}
|
||||
|
||||
{:ok, persisted_token} ->
|
||||
deliver_reserved_email_token(persisted_token, deliver_fun)
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp reserve_email_token(%UserToken{context: "login"} = user_token),
|
||||
do: reserve_auth_email_token(user_token)
|
||||
|
||||
defp reserve_email_token(%UserToken{context: "login:google:" <> _} = user_token),
|
||||
do: reserve_auth_email_token(user_token)
|
||||
|
||||
defp reserve_email_token(user_token), do: {:ok, Repo.insert!(user_token)}
|
||||
|
||||
defp reserve_auth_email_token(user_token) do
|
||||
cutoff =
|
||||
DateTime.utc_now(:second)
|
||||
|> DateTime.add(-UserToken.magic_link_validity_in_minutes(), :minute)
|
||||
|
||||
Repo.transact(fn ->
|
||||
# Serialize reservations per account so concurrent requests cannot both send.
|
||||
Repo.one!(
|
||||
from user in User,
|
||||
where: user.id == ^user_token.user_id,
|
||||
select: user.id,
|
||||
lock: "FOR UPDATE"
|
||||
)
|
||||
|
||||
recent_token? =
|
||||
Repo.exists?(
|
||||
from token in UserToken,
|
||||
where: token.user_id == ^user_token.user_id,
|
||||
where: token.context == ^user_token.context,
|
||||
where: token.sent_to == ^user_token.sent_to,
|
||||
where: token.inserted_at > ^cutoff
|
||||
)
|
||||
|
||||
if recent_token?, do: {:ok, :already_sent}, else: Repo.insert(user_token)
|
||||
end)
|
||||
end
|
||||
|
||||
defp deliver_reserved_email_token(persisted_token, deliver_fun) do
|
||||
case deliver_fun.() do
|
||||
{:ok, _email} = delivered ->
|
||||
delivered
|
||||
|
|
@ -1105,6 +1152,12 @@ defmodule WhoNeedHelp.Accounts do
|
|||
end
|
||||
end
|
||||
|
||||
defp short_hash(value) do
|
||||
:crypto.hash(:sha256, value)
|
||||
|> Base.url_encode64(padding: false)
|
||||
|> binary_part(0, 22)
|
||||
end
|
||||
|
||||
defp before_moderation_user(query, nil), do: query
|
||||
|
||||
defp before_moderation_user(query, {inserted_at, id}) do
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ defmodule WhoNeedHelp.Accounts.UserNotifier do
|
|||
alias WhoNeedHelp.Accounts.User
|
||||
|
||||
# Delivers the email using the application mailer.
|
||||
defp deliver(recipient, subject, body) do
|
||||
defp deliver(recipient, subject, text_body, html_body) do
|
||||
from = Application.fetch_env!(:who_need_help, :mailer_from)
|
||||
|
||||
email =
|
||||
|
|
@ -15,7 +15,8 @@ defmodule WhoNeedHelp.Accounts.UserNotifier do
|
|||
|> to(recipient)
|
||||
|> from({from[:name], from[:address]})
|
||||
|> subject(subject)
|
||||
|> text_body(body)
|
||||
|> text_body(text_body)
|
||||
|> html_body(html_body)
|
||||
|
||||
with {:ok, _metadata} <- Mailer.deliver(email) do
|
||||
{:ok, email}
|
||||
|
|
@ -27,14 +28,18 @@ defmodule WhoNeedHelp.Accounts.UserNotifier do
|
|||
"""
|
||||
def deliver_update_email_instructions(user, url) do
|
||||
with_user_locale(user, fn ->
|
||||
deliver(
|
||||
user.email,
|
||||
gettext("Update email instructions"),
|
||||
gettext(
|
||||
"Hi %{email},\n\nYou can change your email by visiting the URL below:\n\n%{url}\n\nIf you didn't request this change, please ignore this.",
|
||||
email: user.email,
|
||||
url: url
|
||||
)
|
||||
deliver_action_email(user,
|
||||
subject: gettext("Confirm your Who Need Help email change"),
|
||||
heading: gettext("Confirm your new email address"),
|
||||
introduction:
|
||||
gettext("Use the secure link below to confirm this email address for your account."),
|
||||
action_label: gettext("Confirm email address"),
|
||||
url: url,
|
||||
expiry_note: gettext("This confirmation link expires in 7 days."),
|
||||
security_note:
|
||||
gettext(
|
||||
"If you did not request this change, ignore this email. Your address will not change."
|
||||
)
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
|
@ -52,46 +57,143 @@ defmodule WhoNeedHelp.Accounts.UserNotifier do
|
|||
@doc "Delivers instructions for verifying a local account before connecting Google sign-in."
|
||||
def deliver_google_link_instructions(user, url) do
|
||||
with_user_locale(user, fn ->
|
||||
deliver(
|
||||
user.email,
|
||||
gettext("Confirm Google sign-in"),
|
||||
gettext(
|
||||
"Hi %{email},\n\nUse the secure link below to sign in and connect Google to your Who Need Help account:\n\n%{url}\n\nIf you did not request this, ignore this email. Google will not be connected.",
|
||||
email: user.email,
|
||||
url: url
|
||||
)
|
||||
deliver_action_email(user,
|
||||
subject: gettext("Confirm Google sign-in for Who Need Help"),
|
||||
heading: gettext("Confirm Google sign-in"),
|
||||
introduction:
|
||||
gettext("Use the secure link below to sign in and connect Google to your account."),
|
||||
action_label: gettext("Confirm Google sign-in"),
|
||||
url: url,
|
||||
expiry_note: gettext("This one-time link expires in 15 minutes."),
|
||||
security_note:
|
||||
gettext("If you did not request this, ignore this email. Google will not be connected.")
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp deliver_magic_link_instructions(user, url) do
|
||||
with_user_locale(user, fn ->
|
||||
deliver(
|
||||
user.email,
|
||||
gettext("Log in instructions"),
|
||||
gettext(
|
||||
"Hi %{email},\n\nYou can log into your account by visiting the URL below:\n\n%{url}\n\nIf you didn't request this email, please ignore this.",
|
||||
email: user.email,
|
||||
url: url
|
||||
)
|
||||
deliver_action_email(user,
|
||||
subject: gettext("Your Who Need Help sign-in link"),
|
||||
heading: gettext("Sign in to Who Need Help"),
|
||||
introduction: gettext("Use the secure link below to sign in to your account."),
|
||||
action_label: gettext("Sign in to Who Need Help"),
|
||||
url: url,
|
||||
expiry_note: gettext("This one-time link expires in 15 minutes."),
|
||||
security_note:
|
||||
gettext("If you did not request this sign-in, you can safely ignore this email.")
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp deliver_confirmation_instructions(user, url) do
|
||||
with_user_locale(user, fn ->
|
||||
deliver(
|
||||
user.email,
|
||||
gettext("Confirmation instructions"),
|
||||
gettext(
|
||||
"Hi %{email},\n\nYou can confirm your account by visiting the URL below:\n\n%{url}\n\nIf you didn't create an account with us, please ignore this.",
|
||||
email: user.email,
|
||||
url: url
|
||||
)
|
||||
deliver_action_email(user,
|
||||
subject: gettext("Confirm your Who Need Help account"),
|
||||
heading: gettext("Confirm your account"),
|
||||
introduction: gettext("Use the secure link below to confirm your Who Need Help account."),
|
||||
action_label: gettext("Confirm account"),
|
||||
url: url,
|
||||
expiry_note: gettext("This one-time link expires in 15 minutes."),
|
||||
security_note:
|
||||
gettext("If you did not create this account, you can safely ignore this email.")
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
defp deliver_action_email(user, content) do
|
||||
subject = Keyword.fetch!(content, :subject)
|
||||
heading = Keyword.fetch!(content, :heading)
|
||||
introduction = Keyword.fetch!(content, :introduction)
|
||||
action_label = Keyword.fetch!(content, :action_label)
|
||||
url = Keyword.fetch!(content, :url)
|
||||
expiry_note = Keyword.fetch!(content, :expiry_note)
|
||||
security_note = Keyword.fetch!(content, :security_note)
|
||||
|
||||
text = """
|
||||
#{heading}
|
||||
|
||||
#{introduction}
|
||||
|
||||
#{action_label}: #{url}
|
||||
|
||||
#{expiry_note}
|
||||
|
||||
#{security_note}
|
||||
|
||||
Who Need Help
|
||||
"""
|
||||
|
||||
html = action_email_html(heading, introduction, action_label, url, expiry_note, security_note)
|
||||
|
||||
deliver(user.email, subject, text, html)
|
||||
end
|
||||
|
||||
defp action_email_html(heading, introduction, action_label, url, expiry_note, security_note) do
|
||||
escaped_heading = escape_html(heading)
|
||||
escaped_introduction = escape_html(introduction)
|
||||
escaped_action_label = escape_html(action_label)
|
||||
escaped_url = escape_html(url)
|
||||
escaped_expiry_note = escape_html(expiry_note)
|
||||
escaped_security_note = escape_html(security_note)
|
||||
|
||||
"""
|
||||
<!doctype html>
|
||||
<html lang="#{escape_html(Gettext.get_locale(WhoNeedHelpWeb.Gettext))}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light">
|
||||
<title>#{escaped_heading}</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:0;background:#f5f7f6;color:#1d1d20;font-family:Arial,Helvetica,sans-serif;">
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background:#f5f7f6;">
|
||||
<tr>
|
||||
<td align="center" style="padding:32px 16px;">
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="max-width:560px;background:#ffffff;border:1px solid #dfe5e2;border-radius:16px;">
|
||||
<tr>
|
||||
<td style="padding:28px 32px 12px;font-size:18px;font-weight:700;color:#007d6b;">Who Need Help</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 32px 0;">
|
||||
<h1 style="margin:0;font-size:26px;line-height:1.25;color:#1d1d20;">#{escaped_heading}</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:16px 32px 0;font-size:16px;line-height:1.6;color:#555b58;">#{escaped_introduction}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:24px 32px;">
|
||||
<a href="#{escaped_url}" style="display:inline-block;padding:13px 20px;border-radius:8px;background:#a93612;color:#ffffff;text-decoration:none;font-size:16px;font-weight:700;">#{escaped_action_label}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 32px 8px;font-size:14px;line-height:1.55;color:#555b58;">#{escaped_expiry_note}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:8px 32px 0;font-size:14px;line-height:1.55;color:#555b58;">#{escaped_security_note}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:20px 32px 8px;font-size:12px;line-height:1.5;color:#747a77;">#{escape_html(gettext("If the button does not work, copy and paste this address into your browser:"))}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:0 32px 28px;font-size:12px;line-height:1.5;word-break:break-all;"><a href="#{escaped_url}" style="color:#007d6b;">#{escaped_url}</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
end
|
||||
|
||||
defp escape_html(value) do
|
||||
value
|
||||
|> Phoenix.HTML.html_escape()
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
end
|
||||
|
||||
defp with_user_locale(%User{locale: locale}, fun) do
|
||||
Gettext.with_locale(WhoNeedHelpWeb.Gettext, WhoNeedHelp.Locales.normalize(locale), fun)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ defmodule WhoNeedHelp.Accounts.UserToken do
|
|||
@change_email_validity_in_days 7
|
||||
@session_validity_in_days 14
|
||||
|
||||
def magic_link_validity_in_minutes, do: @magic_link_validity_in_minutes
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
schema "users_tokens" do
|
||||
|
|
@ -113,7 +115,9 @@ defmodule WhoNeedHelp.Accounts.UserToken do
|
|||
hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
|
||||
|
||||
query =
|
||||
from token in by_token_and_context_query(hashed_token, "login"),
|
||||
from token in UserToken,
|
||||
where: token.token == ^hashed_token,
|
||||
where: token.context == "login" or like(token.context, "login:google:%"),
|
||||
join: user in assoc(token, :user),
|
||||
where: token.inserted_at > ago(^@magic_link_validity_in_minutes, "minute"),
|
||||
where: token.sent_to == user.email,
|
||||
|
|
@ -160,7 +164,7 @@ defmodule WhoNeedHelp.Accounts.UserToken do
|
|||
|
||||
from token in UserToken,
|
||||
where:
|
||||
(token.context == "login" and token.inserted_at <= ^magic_link_cutoff) or
|
||||
(like(token.context, "login%") and token.inserted_at <= ^magic_link_cutoff) or
|
||||
(like(token.context, "change:%") and token.inserted_at <= ^change_email_cutoff) or
|
||||
(token.context == "session" and token.inserted_at <= ^session_cutoff)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -525,6 +525,7 @@ defmodule WhoNeedHelpWeb.GoogleAuthController do
|
|||
|
||||
Accounts.deliver_google_link_instructions(
|
||||
user,
|
||||
pending_token,
|
||||
&"#{login_url}?google_link=#{URI.encode_www_form(pending_token)}#token=#{URI.encode_www_form(&1)}"
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
else: gettext("Sign-in request processed")}
|
||||
<:subtitle>
|
||||
<%= if @email_flow == "register" do %>
|
||||
{gettext("Use the newest confirmation link if it arrives.")}
|
||||
{gettext("Use the most recent confirmation link that arrived.")}
|
||||
<% else %>
|
||||
{gettext("Signing in does not create a new account.")}
|
||||
<% end %>
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
<div>
|
||||
<h2 class="text-lg font-bold">
|
||||
<%= if @email_flow == "register" do %>
|
||||
{gettext("Open the newest Who Need Help email")}
|
||||
{gettext("Open the most recent Who Need Help email")}
|
||||
<% else %>
|
||||
{gettext("Already registered? Check your email")}
|
||||
<% end %>
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
|
||||
<div class="mt-6 rounded-2xl bg-base-200 p-4 text-sm leading-6 text-base-content/70">
|
||||
{gettext(
|
||||
"If the message is not in your inbox after a minute, check Spam. Older secure links may have been replaced by the newest one."
|
||||
"If the message is not in your inbox after a minute, check Spam. Repeated requests do not send another email while a recent secure link is still valid."
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -4087,7 +4087,7 @@ msgstr ""
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:71
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "If the message is not in your inbox after a minute, check Spam. Older secure links may have been replaced by the newest one."
|
||||
msgid "If the message is not in your inbox after a minute, check Spam. Repeated requests do not send another email while a recent secure link is still valid."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:36
|
||||
|
|
@ -4166,7 +4166,7 @@ msgstr ""
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:51
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open the newest Who Need Help email"
|
||||
msgid "Open the most recent Who Need Help email"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:105
|
||||
|
|
@ -4380,7 +4380,7 @@ msgstr ""
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:15
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Use the newest confirmation link if it arrives."
|
||||
msgid "Use the most recent confirmation link that arrived."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1658
|
||||
|
|
@ -6828,3 +6828,83 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgid "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your Who Need Help email change"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your new email address"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to confirm this email address for your account."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm email address"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "This confirmation link expires in 7 days."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this change, ignore this email. Your address will not change."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm Google sign-in for Who Need Help"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to sign in and connect Google to your account."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "This one-time link expires in 15 minutes."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this, ignore this email. Google will not be connected."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Your Who Need Help sign-in link"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Sign in to Who Need Help"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to sign in to your account."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this sign-in, you can safely ignore this email."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your Who Need Help account"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your account"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to confirm your Who Need Help account."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm account"
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not create this account, you can safely ignore this email."
|
||||
msgstr ""
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If the button does not work, copy and paste this address into your browser:"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -4087,8 +4087,8 @@ msgstr ""
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:71
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "If the message is not in your inbox after a minute, check Spam. Older secure links may have been replaced by the newest one."
|
||||
msgstr ""
|
||||
msgid "If the message is not in your inbox after a minute, check Spam. Repeated requests do not send another email while a recent secure link is still valid."
|
||||
msgstr "If the message is not in your inbox after a minute, check Spam. Repeated requests do not send another email while a recent secure link is still valid."
|
||||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:36
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -4166,8 +4166,8 @@ msgstr ""
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:51
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open the newest Who Need Help email"
|
||||
msgstr ""
|
||||
msgid "Open the most recent Who Need Help email"
|
||||
msgstr "Open the most recent Who Need Help email"
|
||||
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:105
|
||||
#, elixir-autogen, elixir-format, fuzzy
|
||||
|
|
@ -4380,8 +4380,8 @@ msgstr ""
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:15
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Use the newest confirmation link if it arrives."
|
||||
msgstr ""
|
||||
msgid "Use the most recent confirmation link that arrived."
|
||||
msgstr "Use the most recent confirmation link that arrived."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1658
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -6828,3 +6828,83 @@ msgstr "Pending email verification"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgid "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
|
||||
msgstr "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your Who Need Help email change"
|
||||
msgstr "Confirm your Who Need Help email change"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your new email address"
|
||||
msgstr "Confirm your new email address"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to confirm this email address for your account."
|
||||
msgstr "Use the secure link below to confirm this email address for your account."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm email address"
|
||||
msgstr "Confirm email address"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "This confirmation link expires in 7 days."
|
||||
msgstr "This confirmation link expires in 7 days."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this change, ignore this email. Your address will not change."
|
||||
msgstr "If you did not request this change, ignore this email. Your address will not change."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm Google sign-in for Who Need Help"
|
||||
msgstr "Confirm Google sign-in for Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to sign in and connect Google to your account."
|
||||
msgstr "Use the secure link below to sign in and connect Google to your account."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "This one-time link expires in 15 minutes."
|
||||
msgstr "This one-time link expires in 15 minutes."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this, ignore this email. Google will not be connected."
|
||||
msgstr "If you did not request this, ignore this email. Google will not be connected."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Your Who Need Help sign-in link"
|
||||
msgstr "Your Who Need Help sign-in link"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Sign in to Who Need Help"
|
||||
msgstr "Sign in to Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to sign in to your account."
|
||||
msgstr "Use the secure link below to sign in to your account."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this sign-in, you can safely ignore this email."
|
||||
msgstr "If you did not request this sign-in, you can safely ignore this email."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your Who Need Help account"
|
||||
msgstr "Confirm your Who Need Help account"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your account"
|
||||
msgstr "Confirm your account"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to confirm your Who Need Help account."
|
||||
msgstr "Use the secure link below to confirm your Who Need Help account."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm account"
|
||||
msgstr "Confirm account"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not create this account, you can safely ignore this email."
|
||||
msgstr "If you did not create this account, you can safely ignore this email."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If the button does not work, copy and paste this address into your browser:"
|
||||
msgstr "If the button does not work, copy and paste this address into your browser:"
|
||||
|
|
|
|||
|
|
@ -4227,8 +4227,8 @@ msgstr "Помощники увидят только указанный выше
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:71
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "If the message is not in your inbox after a minute, check Spam. Older secure links may have been replaced by the newest one."
|
||||
msgstr "Если письмо не появилось через минуту, проверьте папку «Спам». Новая защищённая ссылка могла заменить предыдущие."
|
||||
msgid "If the message is not in your inbox after a minute, check Spam. Repeated requests do not send another email while a recent secure link is still valid."
|
||||
msgstr "Если письмо не появилось через минуту, проверьте папку «Спам». Повторный запрос не отправляет ещё одно письмо, пока недавняя защищённая ссылка остаётся действительной."
|
||||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:36
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -4306,8 +4306,8 @@ msgstr "Открыть ссылку для благодарности"
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:51
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open the newest Who Need Help email"
|
||||
msgstr "Откройте последнее письмо Who Need Help"
|
||||
msgid "Open the most recent Who Need Help email"
|
||||
msgstr "Откройте последнее полученное письмо Who Need Help"
|
||||
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:105
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -4520,8 +4520,8 @@ msgstr "Перейдите по ссылке подтверждения в пи
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:15
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Use the newest confirmation link if it arrives."
|
||||
msgstr "Если пришла новая ссылка подтверждения, используйте её."
|
||||
msgid "Use the most recent confirmation link that arrived."
|
||||
msgstr "Используйте последнюю полученную ссылку подтверждения."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1658
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -6971,3 +6971,83 @@ msgstr "Ожидает подтверждения email"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgid "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
|
||||
msgstr "Мы сохранили ожидающую заявку %{reference}. Она ещё не попала в очередь поддержки. Откройте приватную ссылку из письма, чтобы подтвердить адрес и отправить заявку на рассмотрение."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your Who Need Help email change"
|
||||
msgstr "Подтвердите изменение email в Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your new email address"
|
||||
msgstr "Подтвердите новый адрес электронной почты"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to confirm this email address for your account."
|
||||
msgstr "Перейдите по защищённой ссылке ниже, чтобы подтвердить этот адрес электронной почты для своей учётной записи."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm email address"
|
||||
msgstr "Подтвердить email"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "This confirmation link expires in 7 days."
|
||||
msgstr "Ссылка для подтверждения действует 7 дней."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this change, ignore this email. Your address will not change."
|
||||
msgstr "Если вы не запрашивали это изменение, проигнорируйте письмо. Ваш адрес не изменится."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm Google sign-in for Who Need Help"
|
||||
msgstr "Подтвердите вход через Google в Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to sign in and connect Google to your account."
|
||||
msgstr "Перейдите по защищённой ссылке ниже, чтобы войти и подключить Google к своей учётной записи."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "This one-time link expires in 15 minutes."
|
||||
msgstr "Эта одноразовая ссылка действует 15 минут."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this, ignore this email. Google will not be connected."
|
||||
msgstr "Если вы этого не запрашивали, проигнорируйте письмо. Google не будет подключён."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Your Who Need Help sign-in link"
|
||||
msgstr "Ваша ссылка для входа в Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Sign in to Who Need Help"
|
||||
msgstr "Войти в Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to sign in to your account."
|
||||
msgstr "Перейдите по защищённой ссылке ниже, чтобы войти в свою учётную запись."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this sign-in, you can safely ignore this email."
|
||||
msgstr "Если вы не запрашивали вход, просто проигнорируйте это письмо."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your Who Need Help account"
|
||||
msgstr "Подтвердите учётную запись Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your account"
|
||||
msgstr "Подтвердите учётную запись"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to confirm your Who Need Help account."
|
||||
msgstr "Перейдите по защищённой ссылке ниже, чтобы подтвердить учётную запись Who Need Help."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm account"
|
||||
msgstr "Подтвердить учётную запись"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not create this account, you can safely ignore this email."
|
||||
msgstr "Если вы не создавали эту учётную запись, просто проигнорируйте письмо."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If the button does not work, copy and paste this address into your browser:"
|
||||
msgstr "Если кнопка не работает, скопируйте этот адрес и вставьте его в браузер:"
|
||||
|
|
|
|||
|
|
@ -4221,8 +4221,8 @@ msgstr "Помічники бачитимуть лише вказаний вищ
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:71
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "If the message is not in your inbox after a minute, check Spam. Older secure links may have been replaced by the newest one."
|
||||
msgstr "Якщо лист не з’явився за хвилину, перевірте папку «Спам». Нове захищене посилання могло замінити попередні."
|
||||
msgid "If the message is not in your inbox after a minute, check Spam. Repeated requests do not send another email while a recent secure link is still valid."
|
||||
msgstr "Якщо лист не з’явився за хвилину, перевірте папку «Спам». Повторний запит не надсилає ще одного листа, доки нещодавнє захищене посилання залишається чинним."
|
||||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:36
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -4300,8 +4300,8 @@ msgstr "Відкрити посилання для подяки"
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:51
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Open the newest Who Need Help email"
|
||||
msgstr "Відкрийте останній лист Who Need Help"
|
||||
msgid "Open the most recent Who Need Help email"
|
||||
msgstr "Відкрийте останній отриманий лист Who Need Help"
|
||||
|
||||
#: lib/who_need_help_web/live/public_profile_live.ex:105
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -4514,8 +4514,8 @@ msgstr "Перейдіть за посиланням підтвердження
|
|||
|
||||
#: lib/who_need_help_web/controllers/user_registration_html/sent.html.heex:15
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "Use the newest confirmation link if it arrives."
|
||||
msgstr "Якщо надійшло нове посилання підтвердження, використайте його."
|
||||
msgid "Use the most recent confirmation link that arrived."
|
||||
msgstr "Скористайтеся останнім отриманим посиланням підтвердження."
|
||||
|
||||
#: lib/who_need_help_web/live/request_live/show.ex:1658
|
||||
#, elixir-autogen, elixir-format
|
||||
|
|
@ -6965,3 +6965,83 @@ msgstr "Очікує підтвердження email"
|
|||
#, elixir-autogen, elixir-format
|
||||
msgid "We saved pending request %{reference}. It is not in the support queue yet. Open the private link sent to the contact email to verify the address and submit the request for review."
|
||||
msgstr "Ми зберегли запит %{reference}, що очікує підтвердження. Він ще не потрапив до черги підтримки. Відкрийте приватне посилання з листа, щоб підтвердити адресу та надіслати запит на розгляд."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your Who Need Help email change"
|
||||
msgstr "Підтвердьте зміну email у Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your new email address"
|
||||
msgstr "Підтвердьте нову адресу електронної пошти"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to confirm this email address for your account."
|
||||
msgstr "Перейдіть за захищеним посиланням нижче, щоб підтвердити цю адресу електронної пошти для свого облікового запису."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm email address"
|
||||
msgstr "Підтвердити email"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "This confirmation link expires in 7 days."
|
||||
msgstr "Посилання для підтвердження діє 7 днів."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this change, ignore this email. Your address will not change."
|
||||
msgstr "Якщо ви не запитували цю зміну, проігноруйте лист. Ваша адреса не зміниться."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm Google sign-in for Who Need Help"
|
||||
msgstr "Підтвердьте вхід через Google у Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to sign in and connect Google to your account."
|
||||
msgstr "Перейдіть за захищеним посиланням нижче, щоб увійти й підключити Google до свого облікового запису."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "This one-time link expires in 15 minutes."
|
||||
msgstr "Це одноразове посилання діє 15 хвилин."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this, ignore this email. Google will not be connected."
|
||||
msgstr "Якщо ви цього не запитували, проігноруйте лист. Google не буде підключено."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Your Who Need Help sign-in link"
|
||||
msgstr "Ваше посилання для входу в Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Sign in to Who Need Help"
|
||||
msgstr "Увійти в Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to sign in to your account."
|
||||
msgstr "Перейдіть за захищеним посиланням нижче, щоб увійти до свого облікового запису."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not request this sign-in, you can safely ignore this email."
|
||||
msgstr "Якщо ви не запитували вхід, просто проігноруйте цей лист."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your Who Need Help account"
|
||||
msgstr "Підтвердьте обліковий запис Who Need Help"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm your account"
|
||||
msgstr "Підтвердьте обліковий запис"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Use the secure link below to confirm your Who Need Help account."
|
||||
msgstr "Перейдіть за захищеним посиланням нижче, щоб підтвердити обліковий запис Who Need Help."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "Confirm account"
|
||||
msgstr "Підтвердити обліковий запис"
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If you did not create this account, you can safely ignore this email."
|
||||
msgstr "Якщо ви не створювали цей обліковий запис, просто проігноруйте лист."
|
||||
|
||||
#: lib/who_need_help/accounts/user_notifier.ex
|
||||
msgid "If the button does not work, copy and paste this address into your browser:"
|
||||
msgstr "Якщо кнопка не працює, скопіюйте цю адресу та вставте її у браузер:"
|
||||
|
|
|
|||
72
test/who_need_help/accounts/user_notifier_test.exs
Normal file
72
test/who_need_help/accounts/user_notifier_test.exs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
defmodule WhoNeedHelp.Accounts.UserNotifierTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias WhoNeedHelp.Accounts.{User, UserNotifier}
|
||||
|
||||
test "magic-link email is recognizable, multipart, and explicit about expiry" do
|
||||
user = %User{
|
||||
email: "person@example.com",
|
||||
display_name: "Helpful neighbor",
|
||||
locale: "en",
|
||||
confirmed_at: DateTime.utc_now(:second)
|
||||
}
|
||||
|
||||
url = "https://whoneedhelp.com/users/log-in#token=one-time-token"
|
||||
|
||||
assert {:ok, email} = UserNotifier.deliver_login_instructions(user, url)
|
||||
|
||||
assert email.subject == "Your Who Need Help sign-in link"
|
||||
assert email.text_body =~ "This one-time link expires in 15 minutes."
|
||||
assert email.text_body =~ url
|
||||
refute email.text_body =~ user.email
|
||||
|
||||
assert email.html_body =~ "<title>Sign in to Who Need Help</title>"
|
||||
assert email.html_body =~ ~s(href="#{url}")
|
||||
assert email.html_body =~ "copy and paste this address into your browser"
|
||||
refute email.html_body =~ "<img"
|
||||
refute email.html_body =~ user.email
|
||||
end
|
||||
|
||||
test "account email HTML escapes translated content and action URLs" do
|
||||
user = %User{email: "person@example.com", locale: "en"}
|
||||
url = ~s(https://whoneedhelp.com/users/log-in#token=a&next="unsafe")
|
||||
|
||||
assert {:ok, email} = UserNotifier.deliver_login_instructions(user, url)
|
||||
|
||||
assert email.text_body =~ url
|
||||
assert email.html_body =~ "a&next="unsafe""
|
||||
refute email.html_body =~ ~s(href="#{url}")
|
||||
end
|
||||
|
||||
test "new-account confirmation uses an account-specific subject" do
|
||||
user = %User{email: "person@example.com", locale: "en", confirmed_at: nil}
|
||||
|
||||
assert {:ok, email} =
|
||||
UserNotifier.deliver_login_instructions(
|
||||
user,
|
||||
"https://whoneedhelp.com/users/log-in#token=confirmation-token"
|
||||
)
|
||||
|
||||
assert email.subject == "Confirm your Who Need Help account"
|
||||
assert email.text_body =~ "Confirm your account"
|
||||
assert email.html_body =~ "Confirm account"
|
||||
end
|
||||
|
||||
test "Google-link verification has a distinct recognizable subject" do
|
||||
user = %User{
|
||||
email: "person@example.com",
|
||||
locale: "en",
|
||||
confirmed_at: DateTime.utc_now(:second)
|
||||
}
|
||||
|
||||
assert {:ok, email} =
|
||||
UserNotifier.deliver_google_link_instructions(
|
||||
user,
|
||||
"https://whoneedhelp.com/users/log-in?google_link=pending#token=one-time-token"
|
||||
)
|
||||
|
||||
assert email.subject == "Confirm Google sign-in for Who Need Help"
|
||||
assert email.text_body =~ "Google will not be connected"
|
||||
assert email.html_body =~ "Confirm Google sign-in"
|
||||
end
|
||||
end
|
||||
|
|
@ -703,6 +703,72 @@ defmodule WhoNeedHelp.AccountsTest do
|
|||
where: token.user_id == ^user.id and token.context == "login"
|
||||
)
|
||||
end
|
||||
|
||||
test "does not send another login email while the previous link is valid", %{user: user} do
|
||||
assert {:ok, first_email} =
|
||||
Accounts.deliver_login_instructions(user, &"https://example.test/#{&1}")
|
||||
|
||||
assert {:ok, :already_sent} =
|
||||
Accounts.deliver_login_instructions(user, &"https://example.test/#{&1}")
|
||||
|
||||
assert first_email.subject == "Confirm your Who Need Help account"
|
||||
|
||||
assert Repo.aggregate(
|
||||
from(token in UserToken,
|
||||
where: token.user_id == ^user.id and token.context == "login"
|
||||
),
|
||||
:count
|
||||
) == 1
|
||||
end
|
||||
|
||||
test "allows a new login email after the existing link expires", %{user: user} do
|
||||
assert {:ok, _email} =
|
||||
Accounts.deliver_login_instructions(user, &"https://example.test/#{&1}")
|
||||
|
||||
Repo.update_all(
|
||||
from(token in UserToken,
|
||||
where: token.user_id == ^user.id and token.context == "login"
|
||||
),
|
||||
set: [inserted_at: DateTime.add(DateTime.utc_now(:second), -16, :minute)]
|
||||
)
|
||||
|
||||
assert {:ok, _email} =
|
||||
Accounts.deliver_login_instructions(user, &"https://example.test/#{&1}")
|
||||
|
||||
assert Repo.aggregate(
|
||||
from(token in UserToken,
|
||||
where: token.user_id == ^user.id and token.context == "login"
|
||||
),
|
||||
:count
|
||||
) == 2
|
||||
end
|
||||
|
||||
test "deduplicates only the same pending Google verification flow", %{user: user} do
|
||||
url = &"https://example.test/google/[TOKEN]#{&1}[TOKEN]"
|
||||
|
||||
assert {:ok, first_email} =
|
||||
Accounts.deliver_google_link_instructions(user, "pending-flow-one", url)
|
||||
|
||||
[_, token | _] = String.split(first_email.text_body, "[TOKEN]")
|
||||
assert %User{id: user_id} = Accounts.get_user_by_magic_link_token(token)
|
||||
assert user_id == user.id
|
||||
|
||||
assert {:ok, :already_sent} =
|
||||
Accounts.deliver_google_link_instructions(user, "pending-flow-one", url)
|
||||
|
||||
assert {:ok, _email} =
|
||||
Accounts.deliver_google_link_instructions(user, "pending-flow-two", url)
|
||||
|
||||
contexts =
|
||||
UserToken
|
||||
|> where([token], token.user_id == ^user.id)
|
||||
|> select([token], token.context)
|
||||
|> Repo.all()
|
||||
|
||||
assert length(contexts) == 2
|
||||
assert Enum.all?(contexts, &String.starts_with?(&1, "login:google:"))
|
||||
assert contexts |> MapSet.new() |> MapSet.size() == 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "inspect/2 for the User module" do
|
||||
|
|
|
|||
|
|
@ -506,8 +506,8 @@ defmodule WhoNeedHelpWeb.GoogleAuthControllerTest do
|
|||
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "finish connecting Google"
|
||||
|
||||
assert_email_sent(fn email ->
|
||||
email.subject == "Confirm Google sign-in" and
|
||||
email.text_body =~ "connect Google to your Who Need Help account" and
|
||||
email.subject == "Confirm Google sign-in for Who Need Help" and
|
||||
email.text_body =~ "connect Google to your account" and
|
||||
email.text_body =~ "google_link=" and email.text_body =~ "#token="
|
||||
end)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user