Packages

A small, focused OAuth 2.0 library for Gleam, built on top of Gleams HTTP requests and responses.

Current section

Files

Jump to
flwr_oauth2 src flwr_oauth2.erl
Raw

src/flwr_oauth2.erl

-module(flwr_oauth2).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/flwr_oauth2.gleam").
-export([secret_is_valid/1, parse_scope/1, parse_token_response/1, make_redirect_uri/1, to_http_request/1, random_state/1, random_state32/0]).
-export_type([response_type/0, client_id/0, secret/0, state/0, authorization_code_grant_redirect_uri/0, token_request/0, client_authentication/0, error/0, access_token_response/0, auth_location/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 response_type() :: code | token.
-type client_id() :: {client_id, binary()}.
-type secret() :: {secret, binary()} |
{secret_with_expiration, binary(), gleam@time@timestamp:timestamp()}.
-type state() :: {state, binary()}.
-type authorization_code_grant_redirect_uri() :: {authorization_code_grant_redirect_uri,
gleam@uri:uri(),
response_type(),
gleam@option:option(gleam@uri:uri()),
client_id(),
list(binary()),
gleam@option:option(state())} |
{authorization_code_grant_redirect_uri_with_p_k_c_e,
gleam@uri:uri(),
response_type(),
gleam@option:option(gleam@uri:uri()),
client_id(),
list(binary()),
gleam@option:option(state()),
binary()}.
-type token_request() :: {authorization_code_grant_token_request,
gleam@uri:uri(),
client_authentication(),
gleam@option:option(gleam@uri:uri()),
binary()} |
{authorization_code_grant_token_request_with_p_k_c_e,
gleam@uri:uri(),
client_authentication(),
gleam@option:option(gleam@uri:uri()),
binary(),
binary()} |
{resource_owner_credentials_grant_token_request,
gleam@uri:uri(),
client_authentication(),
binary(),
binary(),
list(binary())} |
{refresh_token_grant_request,
gleam@uri:uri(),
client_authentication(),
binary(),
list(binary())} |
{client_credentials_grant_token_request,
gleam@uri:uri(),
client_authentication(),
list(binary())}.
-type client_authentication() :: {client_secret_basic, client_id(), secret()} |
{client_secret_post, client_id(), secret()} |
{public_authentication, client_id()}.
-type error() :: secret_expired | invalid_uri.
-type access_token_response() :: {token_error_response,
integer(),
binary(),
gleam@option:option(binary()),
gleam@option:option(binary())} |
{access_token_response,
binary(),
binary(),
gleam@option:option(integer()),
gleam@option:option(binary()),
list(binary())}.
-type auth_location() :: {header, binary(), binary()} |
{body, list({binary(), binary()})}.
-file("src/flwr_oauth2.gleam", 397).
-spec parse_token_error_response(gleam@http@response:response(binary())) -> {ok,
access_token_response()} |
{error, gleam@json:decode_error()}.
parse_token_error_response(Response) ->
Error_decoder = begin
gleam@dynamic@decode:field(
<<"error"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Error) ->
gleam@dynamic@decode:optional_field(
<<"error_description"/utf8>>,
none,
gleam@dynamic@decode:optional(
{decoder, fun gleam@dynamic@decode:decode_string/1}
),
fun(Error_description) ->
gleam@dynamic@decode:optional_field(
<<"error_uri"/utf8>>,
none,
gleam@dynamic@decode:optional(
{decoder,
fun gleam@dynamic@decode:decode_string/1}
),
fun(Error_uri) ->
gleam@dynamic@decode:success(
{token_error_response,
erlang:element(2, Response),
Error,
Error_description,
Error_uri}
)
end
)
end
)
end
)
end,
gleam@json:parse(erlang:element(4, Response), Error_decoder).
-file("src/flwr_oauth2.gleam", 424).
?DOC(
" Checks if a given secret is not expired.\n"
" Returns always true for secrets that cannot expire.\n"
).
-spec secret_is_valid(secret()) -> boolean().
secret_is_valid(Secret) ->
_pipe = case Secret of
{secret, _} ->
false;
{secret_with_expiration, _, Expires_at} ->
gleam@time@timestamp:compare(
Expires_at,
gleam@time@timestamp:system_time()
)
=:= lt
end,
gleam@bool:negate(_pipe).
-file("src/flwr_oauth2.gleam", 319).
-spec create_authentication(client_authentication()) -> {ok, auth_location()} |
{error, error()}.
create_authentication(Auth) ->
case Auth of
{client_secret_basic, Client_id, Client_secret} ->
_pipe = Client_secret,
_pipe@1 = secret_is_valid(_pipe),
_pipe@2 = gleam@bool:negate(_pipe@1),
gleam@bool:guard(
_pipe@2,
{error, secret_expired},
fun() ->
Encoded = begin
_pipe@3 = (<<<<(erlang:element(2, Client_id))/binary,
":"/utf8>>/binary,
(erlang:element(2, Client_secret))/binary>>),
_pipe@4 = gleam_stdlib:identity(_pipe@3),
gleam_stdlib:bit_array_base64_encode(_pipe@4, false)
end,
_pipe@5 = {header,
<<"authentication"/utf8>>,
<<"Basic "/utf8, Encoded/binary>>},
{ok, _pipe@5}
end
);
{client_secret_post, Client_id@1, Client_secret@1} ->
_pipe@6 = Client_secret@1,
_pipe@7 = secret_is_valid(_pipe@6),
_pipe@8 = gleam@bool:negate(_pipe@7),
gleam@bool:guard(
_pipe@8,
{error, secret_expired},
fun() ->
_pipe@9 = {body,
[{<<"client_id"/utf8>>, erlang:element(2, Client_id@1)},
{<<"client_secret"/utf8>>,
erlang:element(2, Client_secret@1)}]},
{ok, _pipe@9}
end
);
{public_authentication, Client_id@2} ->
_pipe@10 = {body,
[{<<"client_id"/utf8>>, erlang:element(2, Client_id@2)}]},
{ok, _pipe@10}
end.
-file("src/flwr_oauth2.gleam", 290).
-spec setup_request(
gleam@uri:uri(),
list({binary(), binary()}),
client_authentication()
) -> {ok, gleam@http@request:request(binary())} | {error, error()}.
setup_request(Token_endpoint, Body, Client_auth) ->
Req = begin
_pipe = gleam@http@request:from_uri(Token_endpoint),
gleam@result:map_error(_pipe, fun(_) -> invalid_uri end)
end,
_pipe@7 = begin
gleam@result:map(
Req,
fun(Req@1) ->
Req@2 = begin
_pipe@1 = Req@1,
_pipe@2 = gleam@http@request:set_method(_pipe@1, post),
gleam@http@request:set_header(
_pipe@2,
<<"content-type"/utf8>>,
<<"application/x-www-form-urlencoded"/utf8>>
)
end,
gleam@result:map(
create_authentication(Client_auth),
fun(Auth_location) -> case Auth_location of
{header, Key, Value} ->
_pipe@3 = Req@2,
_pipe@4 = gleam@http@request:set_header(
_pipe@3,
Key,
Value
),
gleam@http@request:set_body(
_pipe@4,
gleam@uri:query_to_string(Body)
);
{body, Values} ->
_pipe@5 = lists:append(Values, Body),
_pipe@6 = gleam@uri:query_to_string(_pipe@5),
gleam@http@request:set_body(Req@2, _pipe@6)
end end
)
end
)
end,
gleam@result:flatten(_pipe@7).
-file("src/flwr_oauth2.gleam", 466).
-spec response_type_to_string(response_type()) -> binary().
response_type_to_string(Response_type) ->
case Response_type of
code ->
<<"code"/utf8>>;
token ->
<<"token"/utf8>>
end.
-file("src/flwr_oauth2.gleam", 473).
-spec string_not_empty(binary()) -> boolean().
string_not_empty(L) ->
not gleam@string:is_empty(L).
-file("src/flwr_oauth2.gleam", 439).
?DOC(
" Parses a string containing the space separated scopes.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" parse_scope(\"scope1 scope2\")\n"
" ````\n"
).
-spec parse_scope(binary()) -> list(binary()).
parse_scope(Scope) ->
_pipe = Scope,
_pipe@1 = gleam@string:trim(_pipe),
_pipe@2 = gleam@string:split(_pipe@1, <<" "/utf8>>),
gleam@list:filter(_pipe@2, fun string_not_empty/1).
-file("src/flwr_oauth2.gleam", 364).
-spec parse_token_success_response(gleam@http@response:response(binary())) -> {ok,
access_token_response()} |
{error, gleam@json:decode_error()}.
parse_token_success_response(Response) ->
Token_decoder = begin
gleam@dynamic@decode:field(
<<"access_token"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Access_token) ->
gleam@dynamic@decode:field(
<<"token_type"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Token_type) ->
gleam@dynamic@decode:optional_field(
<<"expires_in"/utf8>>,
none,
gleam@dynamic@decode:optional(
{decoder, fun gleam@dynamic@decode:decode_int/1}
),
fun(Expires_in) ->
gleam@dynamic@decode:optional_field(
<<"refresh_token"/utf8>>,
none,
gleam@dynamic@decode:optional(
{decoder,
fun gleam@dynamic@decode:decode_string/1}
),
fun(Refresh_token) ->
gleam@dynamic@decode:optional_field(
<<"scope"/utf8>>,
none,
gleam@dynamic@decode:optional(
{decoder,
fun gleam@dynamic@decode:decode_string/1}
),
fun(Scope) ->
Scope@1 = begin
_pipe = Scope,
_pipe@1 = gleam@option:map(
_pipe,
fun parse_scope/1
),
gleam@option:unwrap(
_pipe@1,
[]
)
end,
gleam@dynamic@decode:success(
{access_token_response,
Access_token,
Token_type,
Expires_in,
Refresh_token,
Scope@1}
)
end
)
end
)
end
)
end
)
end
)
end,
gleam@json:parse(erlang:element(4, Response), Token_decoder).
-file("src/flwr_oauth2.gleam", 355).
?DOC(" Parses a token response and returns the access and refresh token if valid response, otherwise the error response.\n").
-spec parse_token_response(gleam@http@response:response(binary())) -> {ok,
access_token_response()} |
{error, gleam@json:decode_error()}.
parse_token_response(Response) ->
case erlang:element(2, Response) of
200 ->
parse_token_success_response(Response);
_ ->
parse_token_error_response(Response)
end.
-file("src/flwr_oauth2.gleam", 477).
-spec add_if_present(list(JFH), gleam@option:option(JFH)) -> list(JFH).
add_if_present(D, Value) ->
_pipe = Value,
_pipe@1 = gleam@option:map(_pipe, fun gleam@list:wrap/1),
_pipe@2 = gleam@option:unwrap(_pipe@1, []),
lists:append(_pipe@2, D).
-file("src/flwr_oauth2.gleam", 279).
-spec add_scope(list({binary(), binary()}), list(binary())) -> list({binary(),
binary()}).
add_scope(D, Scope) ->
_pipe@1 = case gleam@list:is_empty(Scope) of
false ->
{some,
{<<"scope"/utf8>>,
begin
_pipe = Scope,
gleam@string:join(_pipe, <<" "/utf8>>)
end}};
true ->
none
end,
add_if_present(D, _pipe@1).
-file("src/flwr_oauth2.gleam", 492).
-spec wrap_tuple(JFN, JFO) -> {JFN, JFO}.
wrap_tuple(Name, Value) ->
{Name, Value}.
-file("src/flwr_oauth2.gleam", 484).
-spec to_redirect_uri_query(gleam@option:option(gleam@uri:uri())) -> gleam@option:option({binary(),
binary()}).
to_redirect_uri_query(Value) ->
_pipe = Value,
_pipe@1 = gleam@option:map(_pipe, fun gleam@uri:to_string/1),
gleam@option:map(
_pipe@1,
fun(_capture) -> wrap_tuple(<<"redirect_uri"/utf8>>, _capture) end
).
-file("src/flwr_oauth2.gleam", 188).
?DOC(" Creates the uri that the resource owner should be redirected too.\n").
-spec make_redirect_uri(authorization_code_grant_redirect_uri()) -> gleam@uri:uri().
make_redirect_uri(Redirect_config) ->
State = begin
_pipe = erlang:element(7, Redirect_config),
_pipe@1 = gleam@option:map(_pipe, fun(X) -> erlang:element(2, X) end),
gleam@option:map(
_pipe@1,
fun(_capture) -> wrap_tuple(<<"state"/utf8>>, _capture) end
)
end,
Redirect_uri = to_redirect_uri_query(erlang:element(4, Redirect_config)),
Code_callenge = case Redirect_config of
{authorization_code_grant_redirect_uri_with_p_k_c_e,
_,
_,
_,
_,
_,
_,
Code_challenge} ->
{some, {<<"code_challenge"/utf8>>, Code_challenge}};
_ ->
none
end,
Queries = begin
_pipe@2 = [{<<"response_type"/utf8>>,
response_type_to_string(erlang:element(3, Redirect_config))},
{<<"client_id"/utf8>>,
erlang:element(2, erlang:element(5, Redirect_config))},
{<<"scope"/utf8>>,
gleam@string:join(
erlang:element(6, Redirect_config),
<<" "/utf8>>
)}],
_pipe@3 = add_if_present(_pipe@2, Redirect_uri),
_pipe@4 = add_if_present(_pipe@3, State),
add_if_present(_pipe@4, Code_callenge)
end,
_record = erlang:element(2, Redirect_config),
{uri,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
{some, gleam@uri:query_to_string(Queries)},
erlang:element(8, _record)}.
-file("src/flwr_oauth2.gleam", 218).
?DOC(
" Creates a http request from the given TokenRequest, but does not send.\n"
" Sending the request is done by the user of the function.\n"
).
-spec to_http_request(token_request()) -> {ok,
gleam@http@request:request(binary())} |
{error, error()}.
to_http_request(Request) ->
_pipe@5 = case Request of
{authorization_code_grant_token_request, _, _, Redirect_uri, Code} ->
Redirect_uri@1 = to_redirect_uri_query(Redirect_uri),
_pipe = [{<<"grant_type"/utf8>>, <<"authorization_code"/utf8>>},
{<<"code"/utf8>>, Code}],
add_if_present(_pipe, Redirect_uri@1);
{authorization_code_grant_token_request_with_p_k_c_e,
_,
_,
Redirect_uri@2,
Code@1,
Code_verifier} ->
Redirect_uri@3 = to_redirect_uri_query(Redirect_uri@2),
_pipe@1 = [{<<"grant_type"/utf8>>, <<"authorization_code"/utf8>>},
{<<"code"/utf8>>, Code@1},
{<<"code_verifier"/utf8>>, Code_verifier}],
add_if_present(_pipe@1, Redirect_uri@3);
{resource_owner_credentials_grant_token_request,
_,
_,
Username,
Password,
Scope} ->
_pipe@2 = [{<<"grant_type"/utf8>>, <<"password"/utf8>>},
{<<"username"/utf8>>, Username},
{<<"password"/utf8>>, Password}],
add_scope(_pipe@2, Scope);
{refresh_token_grant_request, _, _, Refresh_token, Scope@1} ->
_pipe@3 = [{<<"grant_type"/utf8>>, <<"refresh_token"/utf8>>},
{<<"refresh_token"/utf8>>, Refresh_token}],
add_scope(_pipe@3, Scope@1);
{client_credentials_grant_token_request, _, _, Scope@2} ->
_pipe@4 = [{<<"grant_type"/utf8>>, <<"client_credentials"/utf8>>}],
add_scope(_pipe@4, Scope@2)
end,
setup_request(
erlang:element(2, Request),
_pipe@5,
erlang:element(3, Request)
).
-file("src/flwr_oauth2.gleam", 450).
?DOC(
" Generates a random State with the specified length including only uppercase and lowercase letters\n"
" If length <= 0 returns an empty string\n"
).
-spec random_state(integer()) -> state().
random_state(Length) ->
Value@1 = case begin
_pipe = prng@random:fixed_size_list(prng@random:int(0, 51), Length),
_pipe@1 = prng@random:to_random_yielder(_pipe),
gleam@yielder:step(_pipe@1)
end of
{next, Value, _} -> Value;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"flwr_oauth2"/utf8>>,
function => <<"random_state"/utf8>>,
line => 451,
value => _assert_fail,
start => 15091,
'end' => 15236,
pattern_start => 15102,
pattern_end => 15124})
end,
_pipe@2 = gleam@list:map(
Value@1,
fun(_capture) ->
gleam@string:slice(
<<"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"/utf8>>,
_capture,
1
)
end
),
_pipe@3 = gleam@string:join(_pipe@2, <<""/utf8>>),
{state, _pipe@3}.
-file("src/flwr_oauth2.gleam", 462).
?DOC(" Generates a random 32 character long State\n").
-spec random_state32() -> state().
random_state32() ->
random_state(32).