defmodule HonuWeb.Storage.S3 do
alias Honu.Attachments.Blob
alias HonuWeb.Storage
@behaviour Storage
@impl true
def url(_conn, blob = %Blob{}, opts \\ []) do
private_url(blob, opts)
end
#defp public_url(blob, opts) do
#end
defp private_url(blob, opts) do
presigned_url(blob, opts)
end
defp presigned_url(blob, _opts) do
metadata = AWS.S3.metadata()
client =
Honu.Storage.S3.client()
|> Map.put(:service, metadata.signing_name)
headers = []
query = []
host = build_host(client, metadata, headers)
path = blob.key
url =
client
|> build_uri(host, path)
|> add_query(query, client)
|> to_string()
#headers = AWS.Signature.sign_v4(client, now(), "get", url, headers, "")
now = now()
:aws_signature.sign_v4_query_params(
client.access_key_id,
client.secret_access_key,
client.region,
client.service,
{{now.year, now.month, now.day}, {now.hour, now.minute, now.second}},
"get",
url,
[
uri_encode_path: false,
body_digest: "UNSIGNED-PAYLOAD",
ttl: ttl()
]
)
end
defp ttl do
case HonuWeb.Storage.config(:service_urls_expire_in) do
{:ok, value} when is_integer(value) -> value
_ -> 300
end
end
# https://github.com/aws-beam/aws-elixir/blob/master/lib/aws/request.ex
defp now do
NaiveDateTime.utc_now()
|> NaiveDateTime.truncate(:second)
end
#defp build_host(%AWS.Client{} = client, %AWS.ServiceMetadata{} = metadata, headers) do
defp build_host(client, metadata, headers) do
build_options = %{
region: client.region,
endpoint: client.endpoint,
service: metadata.signing_name,
global?: metadata.global?,
endpoint_prefix: metadata.endpoint_prefix,
account_id: :proplists.get_value("x-amz-account-id", headers, nil)
}
case build_options do
%{region: "local", endpoint: nil} ->
"localhost"
%{endpoint: endpoint} when is_binary(endpoint) ->
endpoint
%{endpoint: endpoint_fun} when is_function(endpoint_fun, 1) ->
endpoint_fun.(build_options)
%{global?: true, endpoint: endpoint} ->
endpoint = resolve_endpoint_sufix(endpoint)
build_final_endpoint([metadata.endpoint_prefix, endpoint], build_options)
%{endpoint: endpoint} ->
endpoint = resolve_endpoint_sufix(endpoint)
build_final_endpoint([metadata.endpoint_prefix, client.region, endpoint], build_options)
end
end
defp resolve_endpoint_sufix({:keep_prefixes, sufix}) when is_binary(sufix) do
sufix
end
defp resolve_endpoint_sufix(nil), do: AWS.Client.default_endpoint()
defp build_final_endpoint(parts, options) do
parts =
if options.endpoint_prefix == "s3-control" do
account_id = options.account_id || raise "missing account_id"
[account_id | parts]
else
parts
end
Enum.join(parts, ".")
end
#defp build_uri(%AWS.Client{} = client, host, path) do
defp build_uri(client, host, path) do
bucket = Honu.Storage.config!(:s3_bucket)
URI.merge("#{client.proto}://#{bucket}.#{host}:#{client.port}", path)
end
defp add_query(uri = %URI{}, [], _client) do
uri
end
defp add_query(uri = %URI{}, query, client) do
querystring = AWS.Client.encode!(client, query, :query)
if is_binary(uri.query) do
%{uri | query: uri.query <> "&" <> querystring}
else
%{uri | query: querystring}
end
end
end