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]).
-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", 26).
?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", 42).
-spec with_region(signer(), binary()) -> signer().
with_region(Signer, Region) ->
_record = Signer,
{signer,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
Region,
erlang:element(6, _record),
erlang:element(7, _record)}.
-file("src/aws4_request.gleam", 46).
-spec with_service(signer(), binary()) -> signer().
with_service(Signer, Service) ->
_record = Signer,
{signer,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
Service,
erlang:element(7, _record)}.
-file("src/aws4_request.gleam", 53).
?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) ->
_record = Signer,
{signer,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
{some, Session_token}}.
-file("src/aws4_request.gleam", 60).
?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) ->
_record = Signer,
{signer,
{some, Date_time},
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record)}.
-file("src/aws4_request.gleam", 207).
-spec add_session_token_header(list({binary(), HBF}), gleam@option:option(HBF)) -> list({binary(),
HBF}).
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", 203).
-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", 75).
?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 = gleam@string:lowercase(
binary:encode_hex(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 = gleam@string:concat(
[gleam@string:pad_left(gleam@int:to_string(Year), 4, <<"0"/utf8>>),
gleam@string:pad_left(gleam@int:to_string(Month), 2, <<"0"/utf8>>),
gleam@string:pad_left(gleam@int:to_string(Day), 2, <<"0"/utf8>>)]
),
Date_time = gleam@string:concat(
[Date,
<<"T"/utf8>>,
gleam@string:pad_left(gleam@int:to_string(Hour), 2, <<"0"/utf8>>),
gleam@string:pad_left(gleam@int:to_string(Minute), 2, <<"0"/utf8>>),
gleam@string:pad_left(gleam@int:to_string(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,
(gleam@int:to_string(Port))/binary>>
end
),
Method = gleam@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) ->
{gleam@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,
Path@1 = case erlang:element(8, Request@1) of
<<""/utf8>> ->
<<"/"/utf8>>;
Path ->
Path
end,
Canonical_request = gleam@string:concat(
[Method,
<<"\n"/utf8>>,
Path@1,
<<"\n"/utf8>>,
gleam@option:unwrap(erlang:element(9, Request@1), <<""/utf8>>),
<<"\n"/utf8>>,
begin
_pipe@7 = Headers,
_pipe@8 = gleam@list:map(
_pipe@7,
fun(H@1) ->
<<<<(erlang:element(1, H@1))/binary, ":"/utf8>>/binary,
(erlang:element(2, H@1))/binary>>
end
),
gleam@string:join(_pipe@8, <<"\n"/utf8>>)
end,
<<"\n"/utf8>>,
<<"\n"/utf8>>,
Header_names,
<<"\n"/utf8>>,
Payload_hash]
),
Scope = gleam@string:concat(
[Date,
<<"/"/utf8>>,
erlang:element(5, Signer),
<<"/"/utf8>>,
erlang:element(6, Signer),
<<"/aws4_request"/utf8>>]
),
To_sign = gleam@string:concat(
[<<"AWS4-HMAC-SHA256"/utf8>>,
<<"\n"/utf8>>,
Date_time,
<<"\n"/utf8>>,
Scope,
<<"\n"/utf8>>,
gleam@string:lowercase(
binary:encode_hex(
crypto:hash(sha256, <<Canonical_request/binary>>)
)
)]
),
Key = begin
_pipe@9 = <<"AWS4"/utf8, (erlang:element(4, Signer))/binary>>,
_pipe@10 = gleam@crypto:hmac(<<Date/binary>>, sha256, _pipe@9),
_pipe@11 = gleam@crypto:hmac(
<<(erlang:element(5, Signer))/binary>>,
sha256,
_pipe@10
),
_pipe@12 = gleam@crypto:hmac(
<<(erlang:element(6, Signer))/binary>>,
sha256,
_pipe@11
),
gleam@crypto:hmac(<<"aws4_request"/utf8>>, sha256, _pipe@12)
end,
Signature = begin
_pipe@13 = <<To_sign/binary>>,
_pipe@14 = gleam@crypto:hmac(_pipe@13, sha256, Key),
_pipe@15 = binary:encode_hex(_pipe@14),
gleam@string:lowercase(_pipe@15)
end,
Authorization = gleam@string:concat(
[<<"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],
_record = Request@1,
{request,
erlang:element(2, _record),
Headers@1,
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record),
erlang:element(8, _record),
erlang:element(9, _record)}.
-file("src/aws4_request.gleam", 66).
?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)
).