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
1 change: 1 addition & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ config :hexdocs,
session_signing_salt: "QftsNdJO",
session_encryption_salt: "QftsNdJO",
host: "localhost",
private_host: "localhost",
gcs_put_debounce: 0,
special_packages: %{
"eex" => "elixir-lang/elixir",
Expand Down
3 changes: 2 additions & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ if config_env() == :prod do
queue_concurrency: String.to_integer(System.fetch_env!("HEXDOCS_QUEUE_CONCURRENCY")),
github_user: System.fetch_env!("HEXDOCS_GITHUB_USER"),
github_token: System.fetch_env!("HEXDOCS_GITHUB_TOKEN"),
host: System.fetch_env!("HEXDOCS_HOST")
host: System.fetch_env!("HEXDOCS_HOST"),
private_host: System.fetch_env!("HEXDOCS_PRIVATE_HOST")

config :hexdocs, :repo_bucket, name: System.fetch_env!("HEXDOCS_REPO_BUCKET")

Expand Down
3 changes: 2 additions & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ config :hexdocs,
cdn_impl: Hexdocs.CDN.Local,
search_impl: Hexdocs.Search.Local,
source_repo_impl: Hexdocs.SourceRepo.Mock,
hex_repo_impl: Hexdocs.HexRepo.Mock
hex_repo_impl: Hexdocs.HexRepo.Mock,
private_host: "localhost"

config :logger, level: :warning
2 changes: 1 addition & 1 deletion lib/hexdocs/file_rewriter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ defmodule Hexdocs.FileRewriter do

@noindex_hook ~s|<meta name="robots" content="noindex">|

@official_domains ~w(hex.pm hexdocs.pm elixir-lang.org erlang.org)
@official_domains ~w(hex.pm hexdocs.pm hexorgs.pm elixir-lang.org erlang.org)

def run(path, content) do
content
Expand Down
52 changes: 36 additions & 16 deletions lib/hexdocs/plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -58,25 +58,43 @@ defmodule Hexdocs.Plug do
end

defp run(conn, _opts) do
subdomain = subdomain(conn.host)

cond do
!subdomain ->
case subdomain(conn.host) do
:error ->
send_resp(conn, 400, "")

# OAuth callback - exchange code for tokens
conn.request_path == "/oauth/callback" ->
handle_oauth_callback(conn, subdomain)
{:redirect, subdomain} ->
redirect_to_private_host(conn, subdomain)

# OAuth access token in session
access_token = get_session(conn, "access_token") ->
try_serve_page_oauth(conn, subdomain, access_token)
{:ok, subdomain} ->
cond do
# OAuth callback - exchange code for tokens
conn.request_path == "/oauth/callback" ->
handle_oauth_callback(conn, subdomain)

true ->
redirect_oauth(conn, subdomain)
# OAuth access token in session
access_token = get_session(conn, "access_token") ->
try_serve_page_oauth(conn, subdomain, access_token)

true ->
redirect_oauth(conn, subdomain)
end
end
end

defp redirect_to_private_host(conn, subdomain) do
scheme = Application.get_env(:hexdocs, :scheme)
host = Application.get_env(:hexdocs, :private_host)
url = "#{scheme}://#{subdomain}.#{host}#{conn.request_path}"

html = Plug.HTML.html_escape(url)
body = "<html><body>You are being <a href=\"#{html}\">redirected</a>.</body></html>"

conn
|> put_resp_header("location", url)
|> put_resp_header("content-type", "text/html")
|> send_resp(301, body)
end

defp redirect_oauth(conn, organization) do
code_verifier = Hexdocs.OAuth.generate_code_verifier()
code_challenge = Hexdocs.OAuth.generate_code_challenge(code_verifier)
Expand All @@ -103,7 +121,7 @@ defmodule Hexdocs.Plug do

defp build_oauth_redirect_uri(_conn, organization) do
scheme = Application.get_env(:hexdocs, :scheme)
host = Application.get_env(:hexdocs, :host)
host = Application.get_env(:hexdocs, :private_host)
"#{scheme}://#{organization}.#{host}/oauth/callback"
end

Expand Down Expand Up @@ -258,11 +276,13 @@ defmodule Hexdocs.Plug do
end

defp subdomain(host) do
app_host = Application.get_env(:hexdocs, :host)
public_host = Application.get_env(:hexdocs, :host)
private_host = Application.get_env(:hexdocs, :private_host)

case String.split(host, ".", parts: 2) do
[subdomain, ^app_host] -> subdomain
_ -> nil
[subdomain, ^private_host] -> {:ok, subdomain}
[subdomain, ^public_host] -> {:redirect, subdomain}
_ -> :error
end
end

Expand Down
14 changes: 10 additions & 4 deletions lib/hexdocs/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ defmodule Hexdocs.Utils do

def hexdocs_url(repository, path) do
"/" <> _ = path
host = Application.get_env(:hexdocs, :host)
scheme = if host == "hexdocs.pm", do: "https", else: "http"
subdomain = if repository == "hexpm", do: "", else: "#{repository}."
URI.encode("#{scheme}://#{subdomain}#{host}#{path}")

if repository == "hexpm" do
host = Application.get_env(:hexdocs, :host)
scheme = if host == "hexdocs.pm", do: "https", else: "http"
URI.encode("#{scheme}://#{host}#{path}")
else
host = Application.get_env(:hexdocs, :private_host)
scheme = if host in ["hexdocs.pm", "hexorgs.pm"], do: "https", else: "http"
URI.encode("#{scheme}://#{repository}.#{host}#{path}")
end
end

def latest_version(versions) do
Expand Down
34 changes: 34 additions & 0 deletions test/hexdocs/plug_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,40 @@ defmodule Hexdocs.PlugTest do
end
end

describe "redirect from public host to private host" do
setup do
original_host = Application.get_env(:hexdocs, :host)
original_private_host = Application.get_env(:hexdocs, :private_host)
Application.put_env(:hexdocs, :host, "hexdocs.test")
Application.put_env(:hexdocs, :private_host, "hexorgs.test")

on_exit(fn ->
Application.put_env(:hexdocs, :host, original_host)
Application.put_env(:hexdocs, :private_host, original_private_host)
end)
end

test "301 redirects from *.hexdocs.test to *.hexorgs.test" do
conn = conn(:get, "http://myorg.hexdocs.test:5002/my_package/index.html") |> call()
assert conn.status == 301
[location] = get_resp_header(conn, "location")
assert location == "http://myorg.hexorgs.test/my_package/index.html"
end

test "serves docs on private host" do
conn = conn(:get, "http://myorg.hexorgs.test:5002/foo") |> call()
assert conn.status == 302

[location] = get_resp_header(conn, "location")
assert String.starts_with?(location, "http://localhost:5000/oauth/authorize?")
end

test "returns 400 for unrecognized host" do
conn = conn(:get, "http://other.example.com:5002/foo") |> call()
assert conn.status == 400
end
end

defp call(conn) do
Hexdocs.Plug.call(conn, [])
end
Expand Down