defmodule GitGudWeb.StorageController do
@moduledoc """
Serves the local-disk equivalent of an S3 presigned URL.
Only used when `GitGud.Storage.Disk` is the active backend. Disk
doesn't have native presigning, so the impl mints a token URL that
this controller decodes + validates before reading the file off
disk or writing the request body to disk.
Auth is exclusively via the token in the query string — the URL is
the capability. Tokens have a short expiry and embed the bucket +
key they authorize.
"""
use GitGudWeb, :controller
alias GitGud.Storage.Disk
# GET /_storage/:bucket/*key
def show(conn, %{"bucket" => bucket, "key" => key_parts}) do
key = Enum.join(key_parts, "/")
token = conn.query_params["token"] || ""
with {:ok, %{bucket: ^bucket, key: ^key}} <- Disk.verify_token(token, :get),
path = Disk.object_path(bucket, key),
true <- File.exists?(path) do
conn
|> put_resp_content_type("application/octet-stream")
|> send_file(200, path)
else
false ->
send_resp(conn, 404, "")
{:error, :expired} ->
send_resp(conn, 410, "expired token")
_ ->
send_resp(conn, 403, "")
end
end
# PUT /_storage/:bucket/*key
def upload(conn, %{"bucket" => bucket, "key" => key_parts}) do
key = Enum.join(key_parts, "/")
token = conn.query_params["token"] || ""
with {:ok, %{bucket: ^bucket, key: ^key}} <- Disk.verify_token(token, :put),
path = Disk.object_path(bucket, key) do
File.mkdir_p!(Path.dirname(path))
case stream_body_to_file(conn, path) do
{:ok, conn} ->
send_resp(conn, 200, "")
{:error, _, conn} ->
_ = File.rm(path)
send_resp(conn, 500, "write_failed")
end
else
{:error, :expired} ->
send_resp(conn, 410, "expired token")
_ ->
send_resp(conn, 403, "")
end
end
defp stream_body_to_file(conn, path) do
{:ok, io} = File.open(path, [:write, :binary])
result =
do_read(conn, io)
File.close(io)
result
end
defp do_read(conn, io) do
case Plug.Conn.read_body(conn, length: 50_000_000) do
{:ok, body, conn} ->
:ok = IO.binwrite(io, body)
{:ok, conn}
{:more, body, conn} ->
:ok = IO.binwrite(io, body)
do_read(conn, io)
{:error, reason} ->
{:error, reason, conn}
end
end
end
2.4 KiB · text
5af55d9