Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified assets/resume/kyle-neal-resume.pdf
Binary file not shown.
7 changes: 7 additions & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ config :revstack, RevstackWeb.Endpoint,
http: [port: String.to_integer(System.get_env("PORT", "4000"))]

if config_env() == :prod do
fly_app_name = System.get_env("FLY_APP_NAME")

config :revstack, Revstack.Repo, Revstack.RepoConfig.prod_repo_config()

# The secret key base is used to sign/encrypt cookies and other secrets.
Expand Down Expand Up @@ -66,6 +68,11 @@ if config_env() == :prod do
System.get_env("ADMIN_PASSWORD") ||
raise("Missing environment variable `ADMIN_PASSWORD`!")

config :revstack, RevstackWeb.Plugs.FlyRedirect,
enabled: not is_nil(fly_app_name),
source_host: "revstack.fly.dev",
target_url: "https://revenuelink.net"

# Trust proxy headers (X-Forwarded-For) from Fly.io load balancer
config :revstack, trust_proxy: true

Expand Down
11 changes: 3 additions & 8 deletions fly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ app = "revstack"
primary_region = "dfw"
kill_signal = "SIGTERM"
kill_timeout = "5s"
swap_size_mb = 512

[build]

Expand All @@ -22,17 +23,11 @@ kill_timeout = "5s"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = "stop"
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1
min_machines_running = 0
processes = ["app"]

[http_service.concurrency]
type = "connections"
hard_limit = 1000
soft_limit = 1000

[[vm]]
memory = "1gb"
cpu_kind = "shared"
cpus = 1
2 changes: 2 additions & 0 deletions lib/revstack_web/endpoint.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ defmodule RevstackWeb.Endpoint do
websocket: [connect_info: [session: @session_options]],
longpoll: [connect_info: [session: @session_options]]

plug RevstackWeb.Plugs.FlyRedirect

# Serve at "/" the static files from "priv/static" directory.
#
# When code reloading is disabled (e.g., in production),
Expand Down
36 changes: 36 additions & 0 deletions lib/revstack_web/plugs/fly_redirect.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule RevstackWeb.Plugs.FlyRedirect do
@moduledoc """
Redirects requests from the Fly app domain to the canonical external site.

This plug is intended to run in production and is controlled via runtime config.
"""

@behaviour Plug

import Plug.Conn

@impl true
def init(opts), do: opts

@impl true
def call(conn, _opts) do
redirect_config = Application.get_env(:revstack, __MODULE__, [])

enabled? = Keyword.get(redirect_config, :enabled, false)
source_host = Keyword.get(redirect_config, :source_host, "revstack.fly.dev")
target_url = Keyword.get(redirect_config, :target_url, "https://revenuelink.net")

if enabled? and host_matches?(conn.host, source_host) do
conn
|> put_resp_header("location", target_url)
|> send_resp(301, "")
|> halt()
else
conn
end
end

defp host_matches?(host, source_host) do
String.downcase(host || "") == String.downcase(source_host || "")
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Revstack.MixProject do
def project do
[
app: :revstack,
version: "1.4.0",
version: "1.5.0",
elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
Expand Down
68 changes: 68 additions & 0 deletions test/revstack_web/plugs/fly_redirect_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule RevstackWeb.Plugs.FlyRedirectTest do
use RevstackWeb.ConnCase, async: true

alias RevstackWeb.Plugs.FlyRedirect

describe "call/2" do
setup do
original_config = Application.get_env(:revstack, FlyRedirect, [])

on_exit(fn ->
Application.put_env(:revstack, FlyRedirect, original_config)
end)

:ok
end

test "redirects when enabled and host matches source host", %{conn: conn} do
Application.put_env(:revstack, FlyRedirect,
enabled: true,
source_host: "revstack.fly.dev",
target_url: "https://revenuelink.net"
)

conn =
conn
|> Map.put(:host, "revstack.fly.dev")
|> FlyRedirect.call([])

assert conn.halted
assert conn.status == 301
assert get_resp_header(conn, "location") == ["https://revenuelink.net"]
end

test "does not redirect when disabled", %{conn: conn} do
Application.put_env(:revstack, FlyRedirect,
enabled: false,
source_host: "revstack.fly.dev",
target_url: "https://revenuelink.net"
)

conn =
conn
|> Map.put(:host, "revstack.fly.dev")
|> FlyRedirect.call([])

refute conn.halted
assert conn.status == nil
assert get_resp_header(conn, "location") == []
end

test "does not redirect for non-matching host", %{conn: conn} do
Application.put_env(:revstack, FlyRedirect,
enabled: true,
source_host: "revstack.fly.dev",
target_url: "https://revenuelink.net"
)

conn =
conn
|> Map.put(:host, "example.com")
|> FlyRedirect.call([])

refute conn.halted
assert conn.status == nil
assert get_resp_header(conn, "location") == []
end
end
end
Loading