Skip to main content

src/aws@internal@providers@sso.erl

-module(aws@internal@providers@sso).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/aws/internal/providers/sso.gleam").
-export([default_endpoint/1, fetch/2]).
-export_type([options/0, sso_credentials/0, error/0, raw_role_creds/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.

?MODULEDOC(
    " AWS SSO (IAM Identity Center) provider — the GetRoleCredentials portal\n"
    " call given an already-cached SSO access token. We don't implement the\n"
    " device-grant flow that produces the cached token in the first place; the\n"
    " AWS CLI's `aws sso login` does that and writes the token to\n"
    " `~/.aws/sso/cache/<hash>.json`. We just consume it.\n"
    "\n"
    " Endpoint shape:\n"
    "   GET https://portal.sso.<region>.amazonaws.com/federation/credentials\n"
    "       ?account_id=<id>&role_name=<name>\n"
    "   Header: x-amz-sso_bearer_token: <access_token>\n"
    "\n"
    " Response:\n"
    "   { \"roleCredentials\": { accessKeyId, secretAccessKey, sessionToken,\n"
    "                          expiration (millis since epoch) } }\n"
).

-type options() :: {options, binary(), binary(), binary(), binary(), binary()}.

-type sso_credentials() :: {sso_credentials,
        binary(),
        binary(),
        binary(),
        integer()}.

-type error() :: {failed, binary()} | {unreachable, binary()}.

-type raw_role_creds() :: {raw_role_creds,
        binary(),
        binary(),
        binary(),
        integer()}.

-file("src/aws/internal/providers/sso.gleam", 38).
-spec default_endpoint(binary()) -> binary().
default_endpoint(Region) ->
    <<<<"https://portal.sso."/utf8, Region/binary>>/binary,
        ".amazonaws.com"/utf8>>.

-file("src/aws/internal/providers/sso.gleam", 119).
-spec raw_decoder() -> gleam@dynamic@decode:decoder(raw_role_creds()).
raw_decoder() ->
    gleam@dynamic@decode:subfield(
        [<<"roleCredentials"/utf8>>, <<"accessKeyId"/utf8>>],
        {decoder, fun gleam@dynamic@decode:decode_string/1},
        fun(Access_key_id) ->
            gleam@dynamic@decode:subfield(
                [<<"roleCredentials"/utf8>>, <<"secretAccessKey"/utf8>>],
                {decoder, fun gleam@dynamic@decode:decode_string/1},
                fun(Secret_access_key) ->
                    gleam@dynamic@decode:subfield(
                        [<<"roleCredentials"/utf8>>, <<"sessionToken"/utf8>>],
                        {decoder, fun gleam@dynamic@decode:decode_string/1},
                        fun(Session_token) ->
                            gleam@dynamic@decode:subfield(
                                [<<"roleCredentials"/utf8>>,
                                    <<"expiration"/utf8>>],
                                {decoder, fun gleam@dynamic@decode:decode_int/1},
                                fun(Expiration_ms) ->
                                    gleam@dynamic@decode:success(
                                        {raw_role_creds,
                                            Access_key_id,
                                            Secret_access_key,
                                            Session_token,
                                            Expiration_ms}
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/aws/internal/providers/sso.gleam", 144).
-spec decode_credentials(bitstring()) -> {ok, sso_credentials()} |
    {error, error()}.
decode_credentials(Body) ->
    gleam@result:'try'(
        begin
            _pipe = gleam@bit_array:to_string(Body),
            gleam@result:replace_error(
                _pipe,
                {failed, <<"non-utf8 SSO response body"/utf8>>}
            )
        end,
        fun(Text) ->
            gleam@result:'try'(
                begin
                    _pipe@1 = gleam@json:parse(Text, raw_decoder()),
                    gleam@result:map_error(
                        _pipe@1,
                        fun(_) ->
                            {failed,
                                <<"SSO response is not the expected JSON shape"/utf8>>}
                        end
                    )
                end,
                fun(Raw) ->
                    {ok,
                        {sso_credentials,
                            erlang:element(2, Raw),
                            erlang:element(3, Raw),
                            erlang:element(4, Raw),
                            erlang:element(5, Raw) div 1000}}
                end
            )
        end
    ).

-file("src/aws/internal/providers/sso.gleam", 99).
-spec describe_http(aws@internal@http_send:http_error()) -> binary().
describe_http(Error) ->
    case Error of
        {connect_failed, Reason} ->
            <<"connect failed: "/utf8, Reason/binary>>;

        timeout ->
            <<"timeout"/utf8>>;

        {invalid_body, Reason@1} ->
            <<"invalid body: "/utf8, Reason@1/binary>>;

        {other, Reason@2} ->
            Reason@2
    end.

-file("src/aws/internal/providers/sso.gleam", 83).
-spec build_request(binary(), binary()) -> {ok,
        gleam@http@request:request(bitstring())} |
    {error, binary()}.
build_request(Url, Access_token) ->
    gleam@result:'try'(
        begin
            _pipe = gleam@http@request:to(Url),
            gleam@result:replace_error(
                _pipe,
                <<"invalid SSO URL: "/utf8, Url/binary>>
            )
        end,
        fun(Base) ->
            {ok,
                begin
                    _pipe@1 = Base,
                    _pipe@2 = gleam@http@request:set_method(_pipe@1, get),
                    _pipe@3 = gleam@http@request:set_body(
                        _pipe@2,
                        gleam_stdlib:identity(<<""/utf8>>)
                    ),
                    gleam@http@request:set_header(
                        _pipe@3,
                        <<"x-amz-sso_bearer_token"/utf8>>,
                        Access_token
                    )
                end}
        end
    ).

-file("src/aws/internal/providers/sso.gleam", 59).
-spec fetch(
    fun((gleam@http@request:request(bitstring())) -> {ok,
            gleam@http@response:response(bitstring())} |
        {error, aws@internal@http_send:http_error()}),
    options()
) -> {ok, sso_credentials()} | {error, error()}.
fetch(Send, Options) ->
    Url = <<<<<<<<(erlang:element(6, Options))/binary,
                    "/federation/credentials?account_id="/utf8>>/binary,
                (aws@internal@uri:encode_component(erlang:element(3, Options)))/binary>>/binary,
            "&role_name="/utf8>>/binary,
        (aws@internal@uri:encode_component(erlang:element(4, Options)))/binary>>,
    gleam@result:'try'(
        begin
            _pipe = build_request(Url, erlang:element(5, Options)),
            gleam@result:map_error(_pipe, fun(Reason) -> {failed, Reason} end)
        end,
        fun(Req) ->
            gleam@result:'try'(
                begin
                    _pipe@1 = Send(Req),
                    gleam@result:map_error(
                        _pipe@1,
                        fun(E) ->
                            {unreachable,
                                <<"SSO portal transport: "/utf8,
                                    (describe_http(E))/binary>>}
                        end
                    )
                end,
                fun(Resp) -> case erlang:element(2, Resp) of
                        Code when (Code >= 200) andalso (Code < 300) ->
                            decode_credentials(erlang:element(4, Resp));

                        Code@1 ->
                            {error,
                                {failed,
                                    <<"SSO portal returned status "/utf8,
                                        (erlang:integer_to_binary(Code@1))/binary>>}}
                    end end
            )
        end
    ).