Packages

An AWS Signature Version 4 client implementation, used for S3 auth, etc

Current section

Files

Jump to
aws4_request src aws4_request.erl
Raw

src/aws4_request.erl

-module(aws4_request).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws4_request.gleam").
-export([signer/4, with_region/2, with_service/2, with_session_token/2, with_date_time/2, sign_bits/2, sign_string/2]).
-export_type([signer/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type signer() :: {signer,
gleam@option:option({{integer(), integer(), integer()},
{integer(), integer(), integer()}}),
binary(),
binary(),
binary(),
binary(),
gleam@option:option(binary())}.
-file("src/aws4_request.gleam", 28).
?DOC(" Create a new request signer for the given credentials, service, and region.\n").
-spec signer(binary(), binary(), binary(), binary()) -> signer().
signer(Access_key_id, Secret_access_key, Region, Service) ->
{signer, none, Access_key_id, Secret_access_key, Region, Service, none}.
-file("src/aws4_request.gleam", 44).
-spec with_region(signer(), binary()) -> signer().
with_region(Signer, Region) ->
{signer,
erlang:element(2, Signer),
erlang:element(3, Signer),
erlang:element(4, Signer),
Region,
erlang:element(6, Signer),
erlang:element(7, Signer)}.
-file("src/aws4_request.gleam", 48).
-spec with_service(signer(), binary()) -> signer().
with_service(Signer, Service) ->
{signer,
erlang:element(2, Signer),
erlang:element(3, Signer),
erlang:element(4, Signer),
erlang:element(5, Signer),
Service,
erlang:element(7, Signer)}.
-file("src/aws4_request.gleam", 55).
?DOC(
" Set a session token when using temporary security credentials.\n"
" This sets the `x-amz-security-token` header.\n"
).
-spec with_session_token(signer(), binary()) -> signer().
with_session_token(Signer, Session_token) ->
{signer,
erlang:element(2, Signer),
erlang:element(3, Signer),
erlang:element(4, Signer),
erlang:element(5, Signer),
erlang:element(6, Signer),
{some, Session_token}}.
-file("src/aws4_request.gleam", 62).
?DOC(
" Set a specific time to use for request signing, overriding the default\n"
" behaviour of using the current time.\n"
).
-spec with_date_time(
signer(),
{{integer(), integer(), integer()}, {integer(), integer(), integer()}}
) -> signer().
with_date_time(Signer, Date_time) ->
{signer,
{some, Date_time},
erlang:element(3, Signer),
erlang:element(4, Signer),
erlang:element(5, Signer),
erlang:element(6, Signer),
erlang:element(7, Signer)}.
-file("src/aws4_request.gleam", 223).
?DOC(" AWS SigV4 requires query params to be sorted and URI encoded\n").
-spec encode_query_string(gleam@option:option(binary())) -> binary().
encode_query_string(Query) ->
_pipe = Query,
_pipe@1 = gleam@option:unwrap(_pipe, <<""/utf8>>),
_pipe@2 = gleam_stdlib:parse_query(_pipe@1),
_pipe@3 = gleam@result:unwrap(_pipe@2, []),
_pipe@4 = gleam@list:sort(
_pipe@3,
fun(A, B) ->
gleam@string:compare(erlang:element(1, A), erlang:element(1, B))
end
),
gleam@uri:query_to_string(_pipe@4).
-file("src/aws4_request.gleam", 215).
?DOC(" URI encodes the path while preserving path separators\n").
-spec uri_encode(binary()) -> binary().
uri_encode(Path) ->
_pipe = Path,
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
_pipe@2 = gleam@list:map(_pipe@1, fun gleam_stdlib:percent_encode/1),
gleam@string:join(_pipe@2, <<"/"/utf8>>).
-file("src/aws4_request.gleam", 205).
-spec get_canonical_path(binary()) -> binary().
get_canonical_path(Path) ->
case Path of
<<""/utf8>> ->
<<"/"/utf8>>;
_ ->
Path
end.
-file("src/aws4_request.gleam", 237).
-spec add_session_token_header(list({binary(), ERN}), gleam@option:option(ERN)) -> list({binary(),
ERN}).
add_session_token_header(Headers, Maybe_session_token) ->
case Maybe_session_token of
none ->
Headers;
{some, Session_token} ->
gleam@list:prepend(
Headers,
{<<"x-amz-security-token"/utf8>>, Session_token}
)
end.
-file("src/aws4_request.gleam", 233).
-spec now() -> {{integer(), integer(), integer()},
{integer(), integer(), integer()}}.
now() ->
_pipe = os:system_time(1000),
calendar:system_time_to_universal_time(_pipe, 1000).
-file("src/aws4_request.gleam", 77).
?DOC(" Sign a request that has a bit array body.\n").
-spec sign_bits(signer(), gleam@http@request:request(bitstring())) -> gleam@http@request:request(bitstring()).
sign_bits(Signer, Request) ->
Payload_hash = string:lowercase(
gleam_stdlib:base16_encode(
gleam@crypto:hash(sha256, erlang:element(4, Request))
)
),
{{Year, Month, Day}, {Hour, Minute, Second}} = gleam@option:lazy_unwrap(
erlang:element(2, Signer),
fun now/0
),
Date = erlang:list_to_binary(
[gleam@string:pad_start(erlang:integer_to_binary(Year), 4, <<"0"/utf8>>),
gleam@string:pad_start(
erlang:integer_to_binary(Month),
2,
<<"0"/utf8>>
),
gleam@string:pad_start(
erlang:integer_to_binary(Day),
2,
<<"0"/utf8>>
)]
),
Date_time = erlang:list_to_binary(
[Date,
<<"T"/utf8>>,
gleam@string:pad_start(
erlang:integer_to_binary(Hour),
2,
<<"0"/utf8>>
),
gleam@string:pad_start(
erlang:integer_to_binary(Minute),
2,
<<"0"/utf8>>
),
gleam@string:pad_start(
erlang:integer_to_binary(Second),
2,
<<"0"/utf8>>
),
<<"Z"/utf8>>]
),
Request@1 = gleam@http@request:set_header(
Request,
<<"host"/utf8>>,
case erlang:element(7, Request) of
none ->
erlang:element(6, Request);
{some, Port} ->
<<<<(erlang:element(6, Request))/binary, ":"/utf8>>/binary,
(erlang:integer_to_binary(Port))/binary>>
end
),
Method = string:uppercase(
gleam@http:method_to_string(erlang:element(2, Request@1))
),
Headers = begin
_pipe = erlang:element(3, Request@1),
_pipe@1 = add_session_token_header(_pipe, erlang:element(7, Signer)),
_pipe@2 = gleam@list:prepend(
_pipe@1,
{<<"x-amz-date"/utf8>>, Date_time}
),
_pipe@3 = gleam@list:prepend(
_pipe@2,
{<<"x-amz-content-sha256"/utf8>>, Payload_hash}
),
_pipe@4 = gleam@list:map(
_pipe@3,
fun(Header) ->
{string:lowercase(erlang:element(1, Header)),
erlang:element(2, Header)}
end
),
gleam@list:sort(
_pipe@4,
fun(A, B) ->
gleam@string:compare(erlang:element(1, A), erlang:element(1, B))
end
)
end,
Header_names = begin
_pipe@5 = Headers,
_pipe@6 = gleam@list:map(_pipe@5, fun(H) -> erlang:element(1, H) end),
gleam@string:join(_pipe@6, <<";"/utf8>>)
end,
Canonical_uri = begin
_pipe@7 = get_canonical_path(erlang:element(8, Request@1)),
uri_encode(_pipe@7)
end,
Canonical_query = encode_query_string(erlang:element(9, Request@1)),
Canonical_request = erlang:list_to_binary(
[Method,
<<"\n"/utf8>>,
Canonical_uri,
<<"\n"/utf8>>,
Canonical_query,
<<"\n"/utf8>>,
begin
_pipe@8 = Headers,
_pipe@9 = gleam@list:map(
_pipe@8,
fun(H@1) ->
<<<<(erlang:element(1, H@1))/binary, ":"/utf8>>/binary,
(gleam@string:trim(erlang:element(2, H@1)))/binary>>
end
),
gleam@string:join(_pipe@9, <<"\n"/utf8>>)
end,
<<"\n"/utf8>>,
<<"\n"/utf8>>,
Header_names,
<<"\n"/utf8>>,
Payload_hash]
),
Scope = erlang:list_to_binary(
[Date,
<<"/"/utf8>>,
erlang:element(5, Signer),
<<"/"/utf8>>,
erlang:element(6, Signer),
<<"/aws4_request"/utf8>>]
),
To_sign = erlang:list_to_binary(
[<<"AWS4-HMAC-SHA256"/utf8>>,
<<"\n"/utf8>>,
Date_time,
<<"\n"/utf8>>,
Scope,
<<"\n"/utf8>>,
string:lowercase(
gleam_stdlib:base16_encode(
gleam@crypto:hash(sha256, <<Canonical_request/binary>>)
)
)]
),
Key = begin
_pipe@10 = <<"AWS4"/utf8, (erlang:element(4, Signer))/binary>>,
_pipe@11 = gleam_crypto_ffi:hmac(<<Date/binary>>, sha256, _pipe@10),
_pipe@12 = gleam_crypto_ffi:hmac(
<<(erlang:element(5, Signer))/binary>>,
sha256,
_pipe@11
),
_pipe@13 = gleam_crypto_ffi:hmac(
<<(erlang:element(6, Signer))/binary>>,
sha256,
_pipe@12
),
gleam_crypto_ffi:hmac(<<"aws4_request"/utf8>>, sha256, _pipe@13)
end,
Signature = begin
_pipe@14 = <<To_sign/binary>>,
_pipe@15 = gleam_crypto_ffi:hmac(_pipe@14, sha256, Key),
_pipe@16 = gleam_stdlib:base16_encode(_pipe@15),
string:lowercase(_pipe@16)
end,
Authorization = erlang:list_to_binary(
[<<"AWS4-HMAC-SHA256 Credential="/utf8>>,
erlang:element(3, Signer),
<<"/"/utf8>>,
Scope,
<<<<",SignedHeaders="/utf8, Header_names/binary>>/binary,
",Signature="/utf8>>,
Signature]
),
Headers@1 = [{<<"authorization"/utf8>>, Authorization} | Headers],
{request,
erlang:element(2, Request@1),
Headers@1,
erlang:element(4, Request@1),
erlang:element(5, Request@1),
erlang:element(6, Request@1),
erlang:element(7, Request@1),
erlang:element(8, Request@1),
erlang:element(9, Request@1)}.
-file("src/aws4_request.gleam", 68).
?DOC(" Sign a request that has a string body.\n").
-spec sign_string(signer(), gleam@http@request:request(binary())) -> gleam@http@request:request(bitstring()).
sign_string(Signer, Request) ->
sign_bits(
Signer,
gleam@http@request:map(Request, fun gleam_stdlib:identity/1)
).