Current section
Files
Jump to
Current section
Files
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([add_scope/2, setup_request/3, parse_error_response/1, secret_is_valid/1, is_secret_invalid/1, parse_scope/1, parse_token_response/1, make_redirect_uri/1, authorization_setter/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, code_challenge_method/0, token_request/0, client_authentication/0, request_error/0, response_error/0, access_token_response/0, authorization_setter/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(
" This module implements functions and types for [RFC6749](https://datatracker.ietf.org/doc/html/rfc6749).\n"
" It offers types for all the major OAuth 2.0 grant types and functions to create the correct HTTP requests for those grant types.\n"
" Furthermore, it offers functions to generate redirect URIs for the [Authorization Code Grant Type](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1) and the [Implicit Grant Type](https://datatracker.ietf.org/doc/html/rfc6749#section-4.2).\n"
"\n"
" It also supports [RFC7636](https://datatracker.ietf.org/doc/html/rfc7636), which adds the Proof Key for Code Exchange to the Authorization Code Grant.\n"
).
-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(),
code_challenge_method()}.
-type code_challenge_method() :: plain | s256.
-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 request_error() :: secret_expired | invalid_uri.
-type response_error() :: {error_response,
integer(),
binary(),
gleam@option:option(binary()),
gleam@option:option(binary())} |
{parse_error, gleam@json:decode_error()}.
-type access_token_response() :: {access_token_response,
binary(),
binary(),
gleam@option:option(integer()),
gleam@option:option(binary()),
list(binary())}.
-type authorization_setter() :: {authorization_setter,
fun((gleam@http@request:request(list({binary(), binary()}))) -> {ok,
gleam@http@request:request(list({binary(), binary()}))} |
{error, request_error()})}.
-file("src/flwr_oauth2.gleam", 325).
?DOC(" Function that adds a scope to a list if the scope is not empty.\n").
-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,
flwr_oauth2@helpers:add_if_present(D, _pipe@1).
-file("src/flwr_oauth2.gleam", 338).
?DOC(
" A helper function that maps a general request with credentials to a gleam http request.\n"
" The credentials are either attached to the request body URL encoded or added as basic `authorization` header.\n"
).
-spec setup_request(
gleam@uri:uri(),
list({binary(), binary()}),
authorization_setter()
) -> {ok, gleam@http@request:request(binary())} | {error, request_error()}.
setup_request(Endpoint, Body, Client_auth) ->
Req = begin
_pipe = gleam@http@request:from_uri(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),
_pipe@3 = gleam@http@request:set_header(
_pipe@2,
<<"content-type"/utf8>>,
<<"application/x-www-form-urlencoded"/utf8>>
),
_pipe@4 = gleam@http@request:set_body(_pipe@3, Body),
(erlang:element(2, Client_auth))(_pipe@4)
end,
gleam@result:map(
Req@2,
fun(Req@3) -> _pipe@5 = erlang:element(4, Req@3),
_pipe@6 = gleam@uri:query_to_string(_pipe@5),
gleam@http@request:set_body(Req@3, _pipe@6) end
)
end
)
end,
gleam@result:flatten(_pipe@7).
-file("src/flwr_oauth2.gleam", 459).
?DOC(" Parses an OAuth 2.0 error response defined in [RFC6749 Error Response](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2).\n").
-spec parse_error_response(gleam@http@response:response(binary())) -> response_error().
parse_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(
{error_response,
erlang:element(2, Response),
Error,
Error_description,
Error_uri}
)
end
)
end
)
end
)
end,
_pipe = gleam@json:parse(erlang:element(4, Response), Error_decoder),
_pipe@1 = gleam@result:map_error(
_pipe,
fun(Field@0) -> {parse_error, Field@0} end
),
flwr_oauth2@helpers:unwrap_both(_pipe@1).
-file("src/flwr_oauth2.gleam", 488).
?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, _} ->
true;
{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", 499).
?DOC(
" Checks if a given secret is expired.\n"
" Returns always false for secrets that cannot expire.\n"
).
-spec is_secret_invalid(secret()) -> boolean().
is_secret_invalid(Secret) ->
case Secret of
{secret, _} ->
false;
{secret_with_expiration, _, _} ->
_pipe = Secret,
_pipe@1 = secret_is_valid(_pipe),
gleam@bool:negate(_pipe@1)
end.
-file("src/flwr_oauth2.gleam", 538).
-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", 545).
-spec string_not_empty(binary()) -> boolean().
string_not_empty(L) ->
not gleam@string:is_empty(L).
-file("src/flwr_oauth2.gleam", 512).
?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", 424).
-spec parse_token_success_response(gleam@http@response:response(binary())) -> {ok,
access_token_response()} |
{error, response_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,
_pipe@2 = gleam@json:parse(erlang:element(4, Response), Token_decoder),
gleam@result:map_error(_pipe@2, fun(Field@0) -> {parse_error, Field@0} end).
-file("src/flwr_oauth2.gleam", 415).
?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, response_error()}.
parse_token_response(Response) ->
case erlang:element(2, Response) of
200 ->
parse_token_success_response(Response);
_ ->
_pipe = parse_error_response(Response),
{error, _pipe}
end.
-file("src/flwr_oauth2.gleam", 549).
-spec add_many_if_present(list(HEF), gleam@option:option(list(HEF))) -> list(HEF).
add_many_if_present(D, Value) ->
_pipe = Value,
_pipe@1 = gleam@option:unwrap(_pipe, []),
lists:append(_pipe@1, D).
-file("src/flwr_oauth2.gleam", 563).
-spec wrap_tuple(HEM, HEN) -> {HEM, HEN}.
wrap_tuple(Name, Value) ->
{Name, Value}.
-file("src/flwr_oauth2.gleam", 555).
-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", 218).
?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,
Method} ->
{some,
[{<<"code_challenge"/utf8>>, Code_challenge},
{<<"code_challenge_method"/utf8>>, case Method of
plain ->
<<"plain"/utf8>>;
s256 ->
<<"S256"/utf8>>
end}]};
_ ->
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 = flwr_oauth2@helpers:add_if_present(_pipe@2, Redirect_uri),
_pipe@4 = flwr_oauth2@helpers:add_if_present(_pipe@3, State),
add_many_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", 364).
?DOC(
" Encodes the ClientAuthentication that is to be sent to the OAuth 2.0 Server.\n"
" For Basic Authentication it will always encode it with base64.\n"
).
-spec authorization_setter(client_authentication()) -> authorization_setter().
authorization_setter(Auth) ->
_pipe@12 = case Auth of
{client_secret_basic, Client_id, Client_secret} ->
fun(Req) ->
gleam@bool:guard(
begin
_pipe = Client_secret,
is_secret_invalid(_pipe)
end,
{error, secret_expired},
fun() ->
Encoded = begin
_pipe@1 = (<<<<(erlang:element(2, Client_id))/binary,
":"/utf8>>/binary,
(erlang:element(2, Client_secret))/binary>>),
_pipe@2 = gleam_stdlib:identity(_pipe@1),
gleam_stdlib:base64_encode(_pipe@2, true)
end,
_pipe@3 = Req,
_pipe@4 = gleam@http@request:set_header(
_pipe@3,
<<"authorization"/utf8>>,
<<"Basic "/utf8, Encoded/binary>>
),
{ok, _pipe@4}
end
)
end;
{client_secret_post, Client_id@1, Client_secret@1} ->
fun(Req@1) ->
gleam@bool:guard(
begin
_pipe@5 = Client_secret@1,
is_secret_invalid(_pipe@5)
end,
{error, secret_expired},
fun() ->
Body = begin
_pipe@6 = erlang:element(4, Req@1),
lists:append(
[{<<"client_id"/utf8>>,
erlang:element(2, Client_id@1)},
{<<"client_secret"/utf8>>,
erlang:element(2, Client_secret@1)}],
_pipe@6
)
end,
_pipe@7 = Req@1,
_pipe@8 = gleam@http@request:set_body(_pipe@7, Body),
{ok, _pipe@8}
end
)
end;
{public_authentication, Client_id@2} ->
fun(Req@2) ->
Body@1 = begin
_pipe@9 = erlang:element(4, Req@2),
lists:append(
[{<<"client_id"/utf8>>, erlang:element(2, Client_id@2)}],
_pipe@9
)
end,
_pipe@10 = Req@2,
_pipe@11 = gleam@http@request:set_body(_pipe@10, Body@1),
{ok, _pipe@11}
end
end,
{authorization_setter, _pipe@12}.
-file("src/flwr_oauth2.gleam", 263).
?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, request_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}],
flwr_oauth2@helpers: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}],
flwr_oauth2@helpers: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,
authorization_setter(erlang:element(3, Request))
).
-file("src/flwr_oauth2.gleam", 523).
?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, _} = begin
_pipe = prng@random:fixed_size_list(prng@random:int(0, 51), Length),
prng@random:step(_pipe, prng_ffi:new_seed(11))
end,
_pipe@1 = gleam@list:map(
Value,
fun(_capture) ->
gleam@string:slice(
<<"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"/utf8>>,
_capture,
1
)
end
),
_pipe@2 = gleam@string:join(_pipe@1, <<""/utf8>>),
{state, _pipe@2}.
-file("src/flwr_oauth2.gleam", 534).
?DOC(" Generates a random 32 character long State\n").
-spec random_state32() -> state().
random_state32() ->
random_state(32).