-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(<>, 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, <>), 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 => <>, 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 = <<<>/binary, Payload_segment/binary>>, Signature = begin _pipe@4 = <>, _pipe@5 = gpop_ffi:sign_es256(_pipe@4, erlang:element(2, Key)), gleam@bit_array:base64_url_encode(_pipe@5, false) end, <<<>/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).