fix(portal): Ship hotfixes for various crash reports discovered in logs (#4538)

I'll merge and push this right away to prevent users from hitting edge
cases and our monitoring from triggering alerts.
This commit is contained in:
Andrew Dryga
2024-04-08 02:20:26 -06:00
committed by GitHub
parent cb56d768f4
commit 1078c292d3
12 changed files with 130 additions and 6 deletions

View File

@@ -34,6 +34,10 @@ defmodule API.Client.Channel do
defp schedule_expiration(%{assigns: %{subject: %{expires_at: expires_at}}} = socket) do
expires_in = DateTime.diff(expires_at, DateTime.utc_now(), :millisecond)
# Protect from race conditions where the token might have expired during code execution
expires_in = max(0, expires_in)
# Expiration time is capped at 31 days even if IdP returns really long lived tokens
expires_in = min(expires_in, 2_678_400_000)
if expires_in > 0 do
Process.send_after(self(), :token_expired, expires_in)

View File

@@ -113,6 +113,31 @@ defmodule API.Client.ChannelTest do
assert is_number(online_at)
end
test "does not crash when subject expiration is too large", %{
client: client,
subject: subject
} do
expires_at = DateTime.utc_now() |> DateTime.add(100_000_000_000, :millisecond)
subject = %{subject | expires_at: expires_at}
# We need to trap exits to avoid test process termination
# because it is linked to the created test channel process
Process.flag(:trap_exit, true)
{:ok, _reply, _socket} =
API.Client.Socket
|> socket("client:#{client.id}", %{
opentelemetry_ctx: OpenTelemetry.Ctx.new(),
opentelemetry_span_ctx: OpenTelemetry.Tracer.start_span("test"),
client: client,
subject: subject
})
|> subscribe_and_join(API.Client.Channel, "client")
refute_receive {:EXIT, _pid, _}
refute_receive {:socket_close, _pid, _}
end
test "expires the channel when token is expired", %{client: client, subject: subject} do
expires_at = DateTime.utc_now() |> DateTime.add(25, :millisecond)
subject = %{subject | expires_at: expires_at}

View File

@@ -97,7 +97,7 @@ defmodule Domain.Flows.Flow.Query do
@impl Domain.Repo.Query
def cursor_fields,
do: [
{:flows, :asc, :inserted_at},
{:flows, :desc, :inserted_at},
{:flows, :asc, :id}
]
end

View File

@@ -32,7 +32,7 @@ defmodule Web.Flows.Show do
{:ok, socket}
else
{:error, _reason} -> raise Web.LiveErrors.NotFoundError
_other -> raise Web.LiveErrors.NotFoundError
end
end

View File

@@ -20,6 +20,7 @@ defmodule Web.Resources.Show do
socket,
page_title: "Resource #{resource.name}",
traffic_filters_enabled?: Accounts.traffic_filters_enabled?(socket.assigns.account),
flow_activities_enabled?: Accounts.flow_activities_enabled?(socket.assigns.account),
resource: resource,
actor_groups_peek: Map.fetch!(actor_groups_peek, resource.id),
params: Map.take(params, ["site_id"])
@@ -298,7 +299,7 @@ defmodule Web.Resources.Show do
</.link>
(<%= flow.gateway_remote_ip %>)
</:col>
<:col :let={flow} label="ACTIVITY">
<:col :let={flow} :if={@flow_activities_enabled?} label="ACTIVITY">
<.link navigate={~p"/#{@account}/flows/#{flow.id}"} class={[link_style()]}>
Show
</.link>

View File

@@ -28,6 +28,10 @@ defmodule Web.SignIn.Email do
{:ok, socket, temporary_assigns: [form: %Phoenix.HTML.Form{}]}
end
def mount(_params, _session, _socket) do
raise Web.LiveErrors.NotFoundError
end
def render(assigns) do
~H"""
<section>

View File

@@ -1,7 +1,16 @@
defmodule Web.SignIn.Success do
use Web, {:live_view, layout: {Web.Layouts, :public}}
def mount(params, _session, socket) do
def mount(
%{
"fragment" => _,
"state" => _,
"actor_name" => _,
"identity_provider_identifier" => _
} = params,
_session,
socket
) do
if connected?(socket) do
Process.send_after(self(), :redirect_client, 100)
end
@@ -16,6 +25,10 @@ defmodule Web.SignIn.Success do
{:ok, socket}
end
def mount(_params, _session, _socket) do
raise Web.LiveErrors.InvalidParamsError
end
def render(assigns) do
~H"""
<section>

View File

@@ -22,7 +22,8 @@ defmodule Web.SignUp do
%Registration{}
|> Ecto.Changeset.cast(attrs, [:email])
|> Ecto.Changeset.validate_required([:email])
|> Ecto.Changeset.validate_format(:email, ~r/.+@.+/)
|> Domain.Repo.Changeset.trim_change(:email)
|> Domain.Repo.Changeset.validate_email(:email)
|> validate_email_allowed(whitelisted_domains)
|> Ecto.Changeset.validate_confirmation(:email,
required: true,

View File

@@ -7,4 +7,15 @@ defmodule Web.LiveErrors do
def actions(_exception), do: []
end
end
# this is not a styled error because only security scanners that
# try to manipulate the request will see it
defmodule InvalidParamsError do
defexception message: "Unprocessable Entity"
defimpl Plug.Exception do
def status(_exception), do: 422
def actions(_exception), do: []
end
end
end

View File

@@ -36,6 +36,26 @@ defmodule Web.Live.Flows.ShowTest do
}}}
end
test "renders 404 error when flow activities are not enabled", %{
account: account,
identity: identity,
flow: flow,
conn: conn
} do
{:ok, account} =
Domain.Accounts.update_account(account, %{
features: %{
flow_activities: false
}
})
assert_raise Web.LiveErrors.NotFoundError, fn ->
conn
|> authorize_conn(identity)
|> live(~p"/#{account}/flows/#{flow}") =~ "404"
end
end
test "renders breadcrumbs item", %{
account: account,
flow: flow,

View File

@@ -34,4 +34,13 @@ defmodule Web.SignIn.SuccessTest do
uri = URI.parse(path)
assert URI.decode_query(uri.query) == expected_query_params
end
test "returns 422 error when params are missing", %{
account: account,
conn: conn
} do
assert_raise Web.LiveErrors.InvalidParamsError, fn ->
live(conn, ~p"/#{account}/sign_in/success")
end
end
end

View File

@@ -75,6 +75,42 @@ defmodule Web.Live.SignUpTest do
end)
end
test "trims the user email", %{conn: conn} do
Domain.Config.put_env_override(:outbound_email_adapter_configured?, true)
account_name = "FooBar"
{:ok, lv, _html} = live(conn, ~p"/sign_up")
email = Fixtures.Auth.email()
attrs = %{
account: %{name: account_name},
actor: %{name: "John Doe"},
email: " " <> email <> " "
}
Bypass.open()
|> Domain.Mocks.Stripe.mock_create_customer_endpoint(%{
id: Ecto.UUID.generate(),
name: account_name
})
|> Domain.Mocks.Stripe.mock_create_subscription_endpoint()
lv
|> form("form", registration: attrs)
|> render_submit()
account = Repo.one(Domain.Accounts.Account)
assert account.name == account_name
assert account.metadata.stripe.customer_id
assert account.metadata.stripe.billing_email == email
identity = Repo.one(Domain.Auth.Identity)
assert identity.account_id == account.id
assert identity.provider_identifier == email
end
test "allows whitelisted domains to create new account", %{conn: conn} do
whitelisted_domain = "example.com"
Domain.Config.put_env_override(:outbound_email_adapter_configured?, true)
@@ -175,7 +211,7 @@ defmodule Web.Live.SignUpTest do
|> form("form", registration: attrs)
|> render_submit()
|> form_validation_errors() == %{
"registration[email]" => ["has invalid format"]
"registration[email]" => ["is an invalid email address"]
}
end