Packages

Attach DPoP proofs to your Requests

Current section

Files

Jump to
gpop src gpop.erl
Raw

src/gpop.erl

-module(gpop).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gpop.gleam").
-export([generate_key/0, with_proof/3, with_authorization/4]).
-export_type([key/0, p256_keypair/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.
-opaque key() :: {key, p256_keypair(), bitstring(), bitstring()}.
-type p256_keypair() :: any().
-file("src/gpop.gleam", 99).
-spec append_optional_string(
list({binary(), gleam@json:json()}),
binary(),
gleam@option:option(binary())
) -> list({binary(), gleam@json:json()}).
append_optional_string(Claims, Name, Value) ->
case Value of
none ->
Claims;
{some, V} ->
lists:append([Claims, [{Name, gleam@json:string(V)}]])
end.
-file("src/gpop.gleam", 122).
-spec encode_segment(binary()) -> binary().
encode_segment(Value) ->
gleam@bit_array:base64_url_encode(<<Value/binary>>, false).
-file("src/gpop.gleam", 126).
-spec encode_coordinate(bitstring()) -> binary().
encode_coordinate(Coordinate) ->
gleam@bit_array:base64_url_encode(Coordinate, false).
-file("src/gpop.gleam", 70).
-spec jwk_json(key()) -> gleam@json:json().
jwk_json(Key) ->
gleam@json:object(
[{<<"kty"/utf8>>, gleam@json:string(<<"EC"/utf8>>)},
{<<"crv"/utf8>>, gleam@json:string(<<"P-256"/utf8>>)},
{<<"x"/utf8>>,
gleam@json:string(encode_coordinate(erlang:element(3, Key)))},
{<<"y"/utf8>>,
gleam@json:string(encode_coordinate(erlang:element(4, Key)))}]
).
-file("src/gpop.gleam", 62).
-spec header_json(key()) -> gleam@json:json().
header_json(Key) ->
gleam@json:object(
[{<<"typ"/utf8>>, gleam@json:string(<<"dpop+jwt"/utf8>>)},
{<<"alg"/utf8>>, gleam@json:string(<<"ES256"/utf8>>)},
{<<"jwk"/utf8>>, jwk_json(Key)}]
).
-file("src/gpop.gleam", 130).
-spec hash_access_token(binary()) -> binary().
hash_access_token(Access_token) ->
_pipe = gleam@crypto:hash(sha256, <<Access_token/binary>>),
gleam@bit_array:base64_url_encode(_pipe, false).
-file("src/gpop.gleam", 135).
-spec current_epoch_seconds() -> integer().
current_epoch_seconds() ->
_pipe = gleam@time@timestamp:system_time(),
_pipe@1 = gleam@time@timestamp:to_unix_seconds_and_nanoseconds(_pipe),
gleam@pair:first(_pipe@1).
-file("src/gpop.gleam", 79).
-spec payload_json(
gleam@http@request:request(any()),
gleam@option:option(binary()),
gleam@option:option(binary())
) -> gleam@json:json().
payload_json(Request, Nonce, Ath) ->
Htm = gleam@http:method_to_string(erlang:element(2, Request)),
Htu = begin
_record = gleam@http@request:to_uri(Request),
{uri,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
none,
none}
end,
Claims = begin
_pipe = [{<<"htm"/utf8>>, gleam@json:string(Htm)},
{<<"htu"/utf8>>, gleam@json:string(gleam@uri:to_string(Htu))},
{<<"jti"/utf8>>, gleam@json:string(youid@uuid:v4_string())},
{<<"iat"/utf8>>, gleam@json:int(current_epoch_seconds())}],
_pipe@1 = append_optional_string(_pipe, <<"nonce"/utf8>>, Nonce),
append_optional_string(_pipe@1, <<"ath"/utf8>>, Ath)
end,
gleam@json:object(Claims).
-file("src/gpop.gleam", 19).
?DOC(" Generate a new ES256 key pair suitable for DPoP proofs.\n").
-spec generate_key() -> key().
generate_key() ->
{Pair, Public_key} = gpop_ffi:generate_p256_keypair(),
{X@1, Y@1} = case Public_key of
<<4, X:256/bitstring, Y:256/bitstring>> -> {X, Y};
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gpop"/utf8>>,
function => <<"generate_key"/utf8>>,
line => 21,
value => _assert_fail,
start => 552,
'end' => 617,
pattern_start => 563,
pattern_end => 604})
end,
{key, Pair, X@1, Y@1}.
-file("src/gpop.gleam", 110).
-spec sign_jwt(gleam@json:json(), gleam@json:json(), key()) -> binary().
sign_jwt(Header, Payload, Key) ->
Header_segment = begin
_pipe = Header,
_pipe@1 = gleam@json:to_string(_pipe),
encode_segment(_pipe@1)
end,
Payload_segment = begin
_pipe@2 = Payload,
_pipe@3 = gleam@json:to_string(_pipe@2),
encode_segment(_pipe@3)
end,
Signing_input = <<<<Header_segment/binary, "."/utf8>>/binary,
Payload_segment/binary>>,
Signature = begin
_pipe@4 = <<Signing_input/binary>>,
_pipe@5 = gpop_ffi:sign_es256(_pipe@4, erlang:element(2, Key)),
gleam@bit_array:base64_url_encode(_pipe@5, false)
end,
<<<<Signing_input/binary, "."/utf8>>/binary, Signature/binary>>.
-file("src/gpop.gleam", 51).
-spec build_proof(
gleam@http@request:request(any()),
key(),
gleam@option:option(binary()),
gleam@option:option(binary())
) -> binary().
build_proof(Request, Key, Nonce, Ath) ->
Header = header_json(Key),
Payload = payload_json(Request, Nonce, Ath),
sign_jwt(Header, Payload, Key).
-file("src/gpop.gleam", 26).
?DOC(" Attach a standalone DPoP proof header to the request.\n").
-spec with_proof(
gleam@http@request:request(FQS),
key(),
gleam@option:option(binary())
) -> gleam@http@request:request(FQS).
with_proof(Req, Key, Nonce) ->
Proof = build_proof(Req, Key, Nonce, none),
gleam@http@request:set_header(Req, <<"dpop"/utf8>>, Proof).
-file("src/gpop.gleam", 37).
?DOC(
" Attach an Authorization header using the DPoP scheme together with the\n"
" accompanying proof bound to the access token.\n"
).
-spec with_authorization(
gleam@http@request:request(FQW),
key(),
binary(),
gleam@option:option(binary())
) -> gleam@http@request:request(FQW).
with_authorization(Req, Key, Access_token, Nonce) ->
Proof = build_proof(
Req,
Key,
Nonce,
{some, hash_access_token(Access_token)}
),
_pipe = Req,
_pipe@1 = gleam@http@request:set_header(
_pipe,
<<"authorization"/utf8>>,
<<"DPoP "/utf8, Access_token/binary>>
),
gleam@http@request:set_header(_pipe@1, <<"dpop"/utf8>>, Proof).