aboutsummaryrefslogtreecommitdiff
path: root/server/_build/default/lib/jwt
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
committerCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
commit49fa5aa2a127bdf8924d02bf77e5086b39c7a447 (patch)
tree61d86a7705dacc9fddccc29fa79d075d83ab8059 /server/_build/default/lib/jwt
i vibe coded itHEADmaster
Diffstat (limited to 'server/_build/default/lib/jwt')
-rw-r--r--server/_build/default/lib/jwt/LICENSE20
-rw-r--r--server/_build/default/lib/jwt/README.md101
-rw-r--r--server/_build/default/lib/jwt/ebin/jwk.beambin0 -> 10844 bytes
-rw-r--r--server/_build/default/lib/jwt/ebin/jwt.app9
-rw-r--r--server/_build/default/lib/jwt/ebin/jwt.beambin0 -> 12536 bytes
-rw-r--r--server/_build/default/lib/jwt/ebin/jwt_ecdsa.beambin0 -> 13516 bytes
-rw-r--r--server/_build/default/lib/jwt/hex_metadata.config21
-rw-r--r--server/_build/default/lib/jwt/include/jwt_ecdsa.hrl49
-rw-r--r--server/_build/default/lib/jwt/rebar.config22
-rw-r--r--server/_build/default/lib/jwt/rebar.config.script35
-rw-r--r--server/_build/default/lib/jwt/rebar.lock11
-rw-r--r--server/_build/default/lib/jwt/src/jwk.erl69
-rw-r--r--server/_build/default/lib/jwt/src/jwt.app.src8
-rw-r--r--server/_build/default/lib/jwt/src/jwt.erl340
-rw-r--r--server/_build/default/lib/jwt/src/jwt_ecdsa.erl76
15 files changed, 761 insertions, 0 deletions
diff --git a/server/_build/default/lib/jwt/LICENSE b/server/_build/default/lib/jwt/LICENSE
new file mode 100644
index 0000000..0e00cef
--- /dev/null
+++ b/server/_build/default/lib/jwt/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Katō
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/server/_build/default/lib/jwt/README.md b/server/_build/default/lib/jwt/README.md
new file mode 100644
index 0000000..09e56f6
--- /dev/null
+++ b/server/_build/default/lib/jwt/README.md
@@ -0,0 +1,101 @@
+
+
+# jwt — Erlang JWT Library #
+---------
+
+[![Build Status](https://travis-ci.org/artemeff/jwt.svg?branch=master)
+](https://travis-ci.org/artemeff/jwt)
+[![Coverage Status](https://coveralls.io/repos/github/artemeff/jwt/badge.svg?branch=master)
+](https://coveralls.io/github/artemeff/jwt?branch=master)
+[![Hex.pm](https://img.shields.io/hexpm/v/jwt.svg)
+](https://hex.pm/packages/jwt)
+
+---------
+
+JWT is a simple authorization token [format](https://jwt.io/) based on JSON.
+
+
+#### <a name="Installation">Installation</a> ####
+
+If you use rebar (supports both 2 and 3 versions) or mix (Elixir):
+
+```erlang
+
+% in rebar.config for rebar3
+{deps, [{jwt}]}.
+
+% or for rebar2
+{deps, [{jwt, ".*", {git, "https://github.com/artemeff/jwt", {tag, "0.1.0"}}}]}
+
+```
+
+```elixir
+
+% mix.exs
+def deps do
+ [{:jwt, "~> 0.1"}]
+end
+
+```
+
+Or use it as git dependency.
+
+
+#### <a name="Usage_example">Usage example</a> ####
+
+```erlang
+
+%% Create JWT token
+> application:ensure_all_started(jwt).
+> Key = <<"supas3cri7">>.
+> Claims = [
+ {user_id, 42},
+ {user_name, <<"Bob">>}
+ ].
+> {ok, Token} = jwt:encode(<<"HS256">>, Claims, Key).
+%% or with expiration
+> ExpirationSeconds = 86400.
+> {ok, Token} = jwt:encode(<<"HS256">>, Claims, ExpirationSeconds, Key).
+
+%% Parse JWT token
+> {ok, Claims} = jwt:decode(Token, Key).
+%% Issuer specific keys workflow
+
+%% The encoder just knows about itself
+> Issuer = <<"iss1">>.
+> IssuerKey = <<"Issuer-1-Key">>.
+> Claims2 = [
+ {iss, Issuer},
+ {user_id, 42},
+ {user_name, <<"Bob">>}
+ ].
+> {ok, Token2} = jwt:encode(<<"HS256">>, Claims, ExpirationSeconds, IssuerKey).
+
+%% Decoder Workflow
+%% The decoder knows about all encoder keys (issuer specific)
+> IssuerKeyMapping = #{ Issuer => IssuerKey,
+ <<"iss2">> => <<"Issuer2Key">>}.
+> {ok, Claims} = jwt:decode(Token, <<"default-key">>, IssuerKeyMapping).
+
+```
+---------
+
+
+### <a name="Contributing">Contributing</a> ###
+* Fork it
+* Create your feature branch (`git checkout -b my-new-feature`)
+* Commit your changes (`git commit -am 'add some feature'`)
+* Push to the branch (`git push origin my-new-feature`)
+* Create new Pull Request
+
+
+
+
+## Modules ##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="http://github.com/artemeff/jwt/blob/master/doc/jwk.md" class="module">jwk</a></td></tr>
+<tr><td><a href="http://github.com/artemeff/jwt/blob/master/doc/jwt.md" class="module">jwt</a></td></tr>
+<tr><td><a href="http://github.com/artemeff/jwt/blob/master/doc/jwt_ecdsa.md" class="module">jwt_ecdsa</a></td></tr></table>
+
diff --git a/server/_build/default/lib/jwt/ebin/jwk.beam b/server/_build/default/lib/jwt/ebin/jwk.beam
new file mode 100644
index 0000000..c583f9c
--- /dev/null
+++ b/server/_build/default/lib/jwt/ebin/jwk.beam
Binary files differ
diff --git a/server/_build/default/lib/jwt/ebin/jwt.app b/server/_build/default/lib/jwt/ebin/jwt.app
new file mode 100644
index 0000000..c792f7d
--- /dev/null
+++ b/server/_build/default/lib/jwt/ebin/jwt.app
@@ -0,0 +1,9 @@
+{application,jwt,
+ [{description,"Erlang JWT library"},
+ {vsn,"0.1.11"},
+ {registered,[]},
+ {applications,[kernel,stdlib,crypto,public_key,jsx,base64url]},
+ {env,[]},
+ {licenses,["MIT"]},
+ {links,[{"GitHub","https://github.com/artemeff/jwt"}]},
+ {modules,[jwk,jwt,jwt_ecdsa]}]}.
diff --git a/server/_build/default/lib/jwt/ebin/jwt.beam b/server/_build/default/lib/jwt/ebin/jwt.beam
new file mode 100644
index 0000000..fe6332a
--- /dev/null
+++ b/server/_build/default/lib/jwt/ebin/jwt.beam
Binary files differ
diff --git a/server/_build/default/lib/jwt/ebin/jwt_ecdsa.beam b/server/_build/default/lib/jwt/ebin/jwt_ecdsa.beam
new file mode 100644
index 0000000..cb8ffde
--- /dev/null
+++ b/server/_build/default/lib/jwt/ebin/jwt_ecdsa.beam
Binary files differ
diff --git a/server/_build/default/lib/jwt/hex_metadata.config b/server/_build/default/lib/jwt/hex_metadata.config
new file mode 100644
index 0000000..6de7750
--- /dev/null
+++ b/server/_build/default/lib/jwt/hex_metadata.config
@@ -0,0 +1,21 @@
+{<<"app">>,<<"jwt">>}.
+{<<"build_tools">>,[<<"rebar3">>]}.
+{<<"description">>,<<"Erlang JWT library">>}.
+{<<"files">>,
+ [<<"LICENSE">>,<<"README.md">>,<<"include/jwt_ecdsa.hrl">>,
+ <<"rebar.config">>,<<"rebar.config.script">>,<<"rebar.lock">>,
+ <<"src/jwk.erl">>,<<"src/jwt.app.src">>,<<"src/jwt.erl">>,
+ <<"src/jwt_ecdsa.erl">>]}.
+{<<"licenses">>,[<<"MIT">>]}.
+{<<"links">>,[{<<"GitHub">>,<<"https://github.com/artemeff/jwt">>}]}.
+{<<"name">>,<<"jwt">>}.
+{<<"requirements">>,
+ [{<<"base64url">>,
+ [{<<"app">>,<<"base64url">>},
+ {<<"optional">>,false},
+ {<<"requirement">>,<<"~>0.0.1">>}]},
+ {<<"jsx">>,
+ [{<<"app">>,<<"jsx">>},
+ {<<"optional">>,false},
+ {<<"requirement">>,<<"~>2.8.0">>}]}]}.
+{<<"version">>,<<"0.1.11">>}.
diff --git a/server/_build/default/lib/jwt/include/jwt_ecdsa.hrl b/server/_build/default/lib/jwt/include/jwt_ecdsa.hrl
new file mode 100644
index 0000000..7f58843
--- /dev/null
+++ b/server/_build/default/lib/jwt/include/jwt_ecdsa.hrl
@@ -0,0 +1,49 @@
+-define(EC_GROUP_DEGREE, #{
+ sect571r1 => 571,
+ sect571k1 => 571,
+ sect409r1 => 409,
+ sect409k1 => 409,
+ secp521r1 => 521,
+ secp384r1 => 384,
+ secp224r1 => 224,
+ secp224k1 => 224,
+ secp192k1 => 192,
+ secp160r2 => 160,
+ secp128r2 => 128,
+ secp128r1 => 128,
+ sect233r1 => 233,
+ sect233k1 => 233,
+ sect193r2 => 193,
+ sect193r1 => 193,
+ sect131r2 => 131,
+ sect131r1 => 131,
+ sect283r1 => 283,
+ sect283k1 => 283,
+ sect163r2 => 163,
+ secp256k1 => 256,
+ secp160k1 => 160,
+ secp160r1 => 160,
+ secp112r2 => 112,
+ secp112r1 => 112,
+ sect113r2 => 113,
+ sect113r1 => 113,
+ sect239k1 => 239,
+ sect163r1 => 163,
+ sect163k1 => 163,
+ secp256r1 => 256,
+ secp192r1 => 192,
+ brainpoolP160r1 => 160,
+ brainpoolP160t1 => 160,
+ brainpoolP192r1 => 192,
+ brainpoolP192t1 => 192,
+ brainpoolP224r1 => 224,
+ brainpoolP224t1 => 224,
+ brainpoolP256r1 => 256,
+ brainpoolP256t1 => 256,
+ brainpoolP320r1 => 320,
+ brainpoolP320t1 => 320,
+ brainpoolP384r1 => 384,
+ brainpoolP384t1 => 384,
+ brainpoolP512r1 => 512,
+ brainpoolP512t1 => 512
+}).
diff --git a/server/_build/default/lib/jwt/rebar.config b/server/_build/default/lib/jwt/rebar.config
new file mode 100644
index 0000000..3d00da9
--- /dev/null
+++ b/server/_build/default/lib/jwt/rebar.config
@@ -0,0 +1,22 @@
+{erl_opts, [warnings_as_errors]}.
+{deps, [base64url, jsx]}.
+{plugins, [coveralls]}.
+{eunit_compile_opts, [export_all]}.
+
+{profiles, [
+ {edown, [
+ {deps, [
+ {edown, "0.8.1"}
+ ]},
+ {edoc_opts, [
+ {doclet, edown_doclet},
+ {top_level_readme,
+ {"./README.md", "http://github.com/artemeff/jwt", "master"}}
+ ]}
+ ]}
+]}.
+
+{cover_enabled, true}.
+{cover_export_enabled, true}.
+{coveralls_coverdata, "_build/test/cover/eunit.coverdata"}.
+{coveralls_service_name, "travis-ci"}.
diff --git a/server/_build/default/lib/jwt/rebar.config.script b/server/_build/default/lib/jwt/rebar.config.script
new file mode 100644
index 0000000..45cbf92
--- /dev/null
+++ b/server/_build/default/lib/jwt/rebar.config.script
@@ -0,0 +1,35 @@
+IsRebar3OrMix = case application:get_env(rebar, vsn) of
+ {ok, VSN} ->
+ [Major|_] = string:tokens(VSN, "."),
+ (list_to_integer(Major) >= 3);
+
+ undefined ->
+ %% mix is used?
+ lists:keymember(mix, 1, application:loaded_applications())
+ end,
+
+GitDeps =
+ [ {base64url, ".*", {git, "https://github.com/dvv/base64url", {tag, "v1.0"}}}
+ , {jsx, ".*", {git, "https://github.com/talentdeficit/jsx", {tag, "2.8.0"}}}
+ ],
+
+Config = case IsRebar3OrMix of
+ true -> CONFIG;
+ _ -> lists:keyreplace(deps, 1, CONFIG, {deps, GitDeps})
+end,
+
+ConfigCI = case os:getenv("CI") of
+ "true" ->
+ Plugins = [rebar3_lint | proplists:get_value(plugins, Config, [])],
+ lists:keystore(plugins, 1, Config, {plugins, Plugins});
+ _ ->
+ Config
+end,
+
+case os:getenv("TRAVIS") of
+ "true" ->
+ JobId = os:getenv("TRAVIS_JOB_ID"),
+ lists:keystore(coveralls_service_job_id, 1, ConfigCI, {coveralls_service_job_id, JobId});
+ _ ->
+ ConfigCI
+end.
diff --git a/server/_build/default/lib/jwt/rebar.lock b/server/_build/default/lib/jwt/rebar.lock
new file mode 100644
index 0000000..8e1c439
--- /dev/null
+++ b/server/_build/default/lib/jwt/rebar.lock
@@ -0,0 +1,11 @@
+{"1.2.0",
+[{<<"base64url">>,{pkg,<<"base64url">>,<<"0.0.1">>},0},
+ {<<"jsx">>,{pkg,<<"jsx">>,<<"2.8.0">>},0}]}.
+[
+{pkg_hash,[
+ {<<"base64url">>, <<"36A90125F5948E3AFD7BE97662A1504B934DD5DAC78451CA6E9ABF85A10286BE">>},
+ {<<"jsx">>, <<"749BEC6D205C694AE1786D62CEA6CC45A390437E24835FD16D12D74F07097727">>}]},
+{pkg_hash_ext,[
+ {<<"base64url">>, <<"FAB09B20E3F5DB886725544CBCF875B8E73EC93363954EB8A1A9ED834AA8C1F9">>},
+ {<<"jsx">>, <<"A8BA15D5BAC2C48B2BE1224A0542AD794538D79E2CC16841A4E24CA75F0F8378">>}]}
+].
diff --git a/server/_build/default/lib/jwt/src/jwk.erl b/server/_build/default/lib/jwt/src/jwk.erl
new file mode 100644
index 0000000..9226aae
--- /dev/null
+++ b/server/_build/default/lib/jwt/src/jwk.erl
@@ -0,0 +1,69 @@
+%% @doc RFC 7517: JSON Web Key (JWK)
+
+-module(jwk).
+-include_lib("public_key/include/OTP-PUB-KEY.hrl").
+
+-export([encode/2, decode/2]).
+
+-type id() :: binary().
+-type public_key() :: #'RSAPublicKey'{} | pem().
+-type pem() :: binary().
+-type json() :: binary().
+
+-spec encode(id(), public_key()) -> {ok, json()} | {error, _}.
+%% @doc encode Erlang/OTP Key to JWK
+encode(Id, #'RSAPublicKey'{modulus = N, publicExponent = E}) ->
+ {ok, jsx:encode(
+ #{
+ keys =>
+ [
+ #{
+ kid => Id,
+ kty => <<"RSA">>,
+ n => encode_int(N),
+ e => encode_int(E)
+ }
+ ]
+ }
+ )};
+encode(Id, PEM) when is_binary(PEM) ->
+ [RSAEntry] = public_key:pem_decode(PEM),
+ encode(Id, public_key:pem_entry_decode(RSAEntry));
+encode(_, _) ->
+ {error, not_supported}.
+
+-spec decode(id(), json()) -> {ok, public_key()} | {error, _}.
+%% @doc decode JWK to Erlang/OTP Key
+decode(Id, #{<<"keys">> := JWTs}) ->
+ decode(
+ lists:dropwhile(
+ fun(X) ->
+ maps:get(<<"kid">>, X, undefined) /= Id
+ end,
+ JWTs
+ )
+ );
+decode(Id, Json) when is_binary(Json) ->
+ decode(Id, jsx:decode(Json, [return_maps])).
+
+%% @private
+decode([#{<<"kty">> := <<"RSA">>, <<"n">> := N, <<"e">> := E} | _]) ->
+ {ok,
+ #'RSAPublicKey'{
+ modulus = decode_int(N),
+ publicExponent = decode_int(E)
+ }
+ };
+decode([]) ->
+ {error, not_found};
+decode(_) ->
+ {error, not_supported}.
+
+
+%% @private
+encode_int(X) ->
+ base64url:encode(binary:encode_unsigned(X)).
+
+%% @private
+decode_int(X) ->
+ binary:decode_unsigned(base64url:decode(X)).
diff --git a/server/_build/default/lib/jwt/src/jwt.app.src b/server/_build/default/lib/jwt/src/jwt.app.src
new file mode 100644
index 0000000..b39bcb6
--- /dev/null
+++ b/server/_build/default/lib/jwt/src/jwt.app.src
@@ -0,0 +1,8 @@
+{application,jwt,
+ [{description,"Erlang JWT library"},
+ {vsn,"0.1.11"},
+ {registered,[]},
+ {applications,[kernel,stdlib,crypto,public_key,jsx,base64url]},
+ {env,[]},
+ {licenses,["MIT"]},
+ {links,[{"GitHub","https://github.com/artemeff/jwt"}]}]}.
diff --git a/server/_build/default/lib/jwt/src/jwt.erl b/server/_build/default/lib/jwt/src/jwt.erl
new file mode 100644
index 0000000..b17b679
--- /dev/null
+++ b/server/_build/default/lib/jwt/src/jwt.erl
@@ -0,0 +1,340 @@
+%% @doc JWT Library for Erlang.
+%%
+%% Written by Peter Hizalev at Kato (http://kato.im)
+%%
+%% Rewritten by Yuri Artemev (http://artemff.com)
+%%
+%% @end
+-module(jwt).
+
+-export([decode/2, decode/3]).
+-export([encode/3, encode/4]).
+
+-define(HOUR, 3600).
+-define(DAY, (?HOUR * 24)).
+
+%% Handle version compatibility for crypto
+-ifdef(OTP_RELEASE).
+ -if(?OTP_RELEASE >= 23).
+ -define(HMAC(Type, Key, Data), crypto:mac(hmac, Type, Key, Data)).
+ -else.
+ -define(HMAC(Type, Key, Data), crypto:hmac(Type, Key, Data)).
+ -endif.
+-else.
+ -define(HMAC(Type, Key, Data), crypto:hmac(Type, Key, Data)).
+-endif.
+
+-type expiration() :: {hourly, non_neg_integer()} | {daily, non_neg_integer()} | non_neg_integer().
+-type context() :: map().
+
+%%
+%% API
+%%
+-spec encode(
+ Alg :: binary(),
+ ClaimsSet :: map() | list(),
+ Key :: binary() | public_key:private_key()
+) -> {ok, Token :: binary()} | {error, any()}.
+%% @doc Creates a token from given data and signs it with a given secret
+%%
+%% Parameters are
+%% <ul>
+%% <li>
+%% `Alg' is a binary one of
+%%
+%% [HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512]
+%%
+%% But only [HS256, HS384, HS512, RS256] are supported
+%% </li>
+%% <li>`ClaimsSet' the payload of the token. Can be both map and proplist</li>
+%% <li>`Key' binary in case of hmac encryption and private key if rsa</li>
+%% </ul>
+%%
+%% @end
+encode(Alg, ClaimsSet, Key) ->
+ Claims = base64url:encode(jsx:encode(ClaimsSet)),
+ Header = base64url:encode(jsx:encode(jwt_header(Alg))),
+ Payload = <<Header/binary, ".", Claims/binary>>,
+ case jwt_sign(Alg, Payload, Key) of
+ undefined -> {error, algorithm_not_supported};
+ Signature -> {ok, <<Payload/binary, ".", Signature/binary>>}
+ end.
+
+-spec encode(
+ Alg :: binary(),
+ ClaimsSet :: map() | list(),
+ Expiration :: expiration(),
+ Key :: binary() | public_key:private_key()
+) -> {ok, Token :: binary()} | {error, any()}.
+%% @doc Creates a token from given data and signs it with a given secret
+%%
+%% and also adds `exp' claim to payload
+%%
+%% `Expiration' can be one of the tuples:
+%% `{hourly, SecondsAfterBeginningOfCurrentHour}',
+%% `{daily, SecondsAfterBeginningOfCurrentDay}'
+%% or can be just an integer representing the amount of seconds
+%% the token will live
+%%
+%% @end
+encode(Alg, ClaimsSet, Expiration, Key) ->
+ Claims = jwt_add_exp(ClaimsSet, Expiration),
+ encode(Alg, Claims, Key).
+
+-spec decode(
+ Token :: binary(),
+ Key :: binary() | public_key:public_key() | public_key:private_key()
+) -> {ok, Claims :: map()} | {error, any()}.
+%% @doc Decodes a token, checks the signature and returns the content of the token
+%%
+%% <ul>
+%% <li>`Token' is a JWT itself</li>
+%% <li>`Key' is a secret phrase or public/private key depend on encryption algorithm</li>
+%% </ul>
+%%
+%% @end
+decode(Token, Key) ->
+ decode(Token, Key, #{}).
+
+% When there are multiple issuers and keys are on a per issuer bases
+% then apply those keys instead
+-spec decode(
+ Token :: binary(),
+ DefaultKey :: binary() | public_key:public_key() | public_key:private_key(),
+ IssuerKeyMapping :: map()
+) -> {ok, Claims :: map()} | {error, any()}.
+%% @doc Decode with an issuer key mapping
+%%
+%% Receives the issuer key mapping as the last parameter
+%%
+%% @end
+decode(Token, DefaultKey, IssuerKeyMapping) ->
+ result(reduce_while(fun(F, Acc) -> apply(F, [Acc]) end, #{token => Token}, [
+ fun split_token/1,
+ fun decode_jwt/1,
+ fun (Context) ->
+ get_key(Context, DefaultKey, IssuerKeyMapping)
+ end,
+ fun check_signature/1,
+ fun check_expired/1
+ ])).
+
+result(#{claims_json := ClaimsJSON}) ->
+ {ok, ClaimsJSON};
+result({error, _} = Error) ->
+ Error.
+
+reduce_while(_Fun, Acc, []) ->
+ Acc;
+reduce_while(Fun, Acc, [Item|Rest]) ->
+ case Fun(Item, Acc) of
+ {cont, NewAcc} ->
+ reduce_while(Fun, NewAcc, Rest);
+ {halt, Result} ->
+ Result
+ end.
+
+-spec split_token(Context :: context()) ->
+ {cont, context()} | {halt, {error, invalid_token}}.
+%% @private
+split_token(#{token := Token} = Context) ->
+ case binary:split(Token, <<".">>, [global]) of
+ [Header, Claims, Signature] ->
+ {cont, maps:merge(Context, #{
+ header => Header,
+ claims => Claims,
+ signature => Signature
+ })};
+ _ ->
+ {halt, {error, invalid_token}}
+ end.
+
+-spec decode_jwt(context()) -> {cont, context()} | {halt, {error, invalid_token}}.
+%% @private
+decode_jwt(#{header := Header, claims := Claims} = Context) ->
+ try
+ [HeaderJSON, ClaimsJSON] =
+ Decoded = [jsx_decode_safe(base64url:decode(X)) || X <- [Header, Claims]],
+ case lists:any(fun(E) -> E =:= invalid end, Decoded) of
+ false ->
+ {cont, maps:merge(Context, #{
+ header_json => HeaderJSON,
+ claims_json => ClaimsJSON
+ })};
+ true ->
+ {halt, {error, invalid_token}}
+ end
+ catch _:_ ->
+ {halt, {error, invalid_token}}
+ end.
+
+%% @private
+get_key(#{claims_json := Claims} = Context, DefaultKey, IssuerKeyMapping) ->
+ Issuer = maps:get(<<"iss">>, Claims, undefined),
+ Key = maps:get(Issuer, IssuerKeyMapping, DefaultKey),
+ {cont, maps:merge(Context, #{key => Key})}.
+
+%% @private
+check_signature(#{
+ key := Key,
+ header := Header,
+ claims := Claims,
+ signature := Signature,
+ header_json := #{<<"alg">> := Alg}
+} = Context) ->
+ case jwt_check_sig(Alg, Header, Claims, Signature, Key) of
+ true ->
+ {cont, Context};
+ false ->
+ {halt, {error, invalid_signature}}
+ end.
+
+%% @private
+check_expired(#{claims_json := ClaimsJSON} = Context) ->
+ case jwt_is_expired(ClaimsJSON) of
+ true ->
+ {halt, {error, expired}};
+ false ->
+ {cont, Context}
+ end.
+
+%%
+%% Decoding helpers
+%%
+-spec jsx_decode_safe(binary()) -> map() | invalid.
+%% @private
+jsx_decode_safe(Bin) ->
+ try
+ jsx:decode(Bin, [return_maps])
+ catch _ ->
+ invalid
+ end.
+
+-spec jwt_is_expired(map()) -> boolean().
+%% @private
+jwt_is_expired(#{<<"exp">> := Exp} = _ClaimsJSON) ->
+ case (Exp - epoch()) of
+ DeltaSecs when DeltaSecs > 0 -> false;
+ _ -> true
+ end;
+jwt_is_expired(_) ->
+ false.
+
+-spec jwt_check_sig(
+ Alg :: binary(),
+ Header :: binary(),
+ Claims :: binary(),
+ Signature :: binary(),
+ Key :: binary() | public_key:public_key() | public_key:private_key()
+) -> boolean().
+%% @private
+jwt_check_sig(Alg, Header, Claims, Signature, Key) ->
+ jwt_check_sig(algorithm_to_crypto(Alg), <<Header/binary, ".", Claims/binary>>, Signature, Key).
+
+-spec jwt_check_sig(
+ {atom(), atom()},
+ Payload :: binary(),
+ Signature :: binary(),
+ Key :: binary() | public_key:public_key() | public_key:private_key()
+) -> boolean().
+%% @private
+jwt_check_sig({hmac, _} = Alg, Payload, Signature, Key) ->
+ jwt_sign_with_crypto(Alg, Payload, Key) =:= Signature;
+
+jwt_check_sig({Algo, Crypto}, Payload, Signature, Pem)
+ when (Algo =:= rsa orelse Algo =:= ecdsa) andalso is_binary(Pem) ->
+ jwt_check_sig({Algo, Crypto}, Payload, Signature, pem_to_key(Pem));
+
+jwt_check_sig({rsa, Crypto}, Payload, Signature, Key) ->
+ public_key:verify(Payload, Crypto, base64url:decode(Signature), Key);
+
+jwt_check_sig({ecdsa, Crypto}, Payload, Signature, Key) ->
+ public_key:verify(Payload, Crypto, jwt_ecdsa:signature(Signature), Key);
+
+jwt_check_sig(_, _, _, _) ->
+ false.
+
+%%
+%% Encoding helpers
+%%
+-spec jwt_add_exp(ClaimsSet :: map() | list(), Expiration :: expiration()) -> map() | list().
+%% @private
+jwt_add_exp(ClaimsSet, Expiration) ->
+ Exp = expiration_to_epoch(Expiration),
+ append_claim(ClaimsSet, <<"exp">>, Exp).
+
+-spec jwt_header(Alg :: binary()) -> list().
+jwt_header(Alg) ->
+ [ {<<"alg">>, Alg}
+ , {<<"typ">>, <<"JWT">>}
+ ].
+
+%%
+%% Helpers
+%%
+-spec jwt_sign(
+ Alg :: binary(),
+ Payload :: binary(),
+ Key :: binary() | public_key:private_key()
+) -> binary() | undefined.
+%% @private
+jwt_sign(Alg, Payload, Key) ->
+ jwt_sign_with_crypto(algorithm_to_crypto(Alg), Payload, Key).
+
+jwt_sign_with_crypto({hmac, Crypto}, Payload, Key) ->
+ base64url:encode(?HMAC(Crypto, Key, Payload));
+
+jwt_sign_with_crypto({Algo, Crypto}, Payload, Pem)
+ when (Algo =:= rsa orelse Algo =:= ecdsa) andalso is_binary(Pem) ->
+ jwt_sign_with_crypto({Algo, Crypto}, Payload, pem_to_key(Pem));
+
+jwt_sign_with_crypto({rsa, Crypto}, Payload, Key) ->
+ base64url:encode(public_key:sign(Payload, Crypto, Key));
+
+jwt_sign_with_crypto({ecdsa, Crypto}, Payload, Key) ->
+ base64url:encode(jwt_ecdsa:signature(Payload, Crypto, Key));
+
+jwt_sign_with_crypto(_, _Payload, _Key) ->
+ undefined.
+
+-spec algorithm_to_crypto(binary()) -> {atom(), atom()} | undefined.
+%% @private
+algorithm_to_crypto(<<"HS256">>) -> {hmac, sha256};
+algorithm_to_crypto(<<"HS384">>) -> {hmac, sha384};
+algorithm_to_crypto(<<"HS512">>) -> {hmac, sha512};
+algorithm_to_crypto(<<"RS256">>) -> {rsa, sha256};
+algorithm_to_crypto(<<"RS384">>) -> {rsa, sha384};
+algorithm_to_crypto(<<"RS512">>) -> {rsa, sha512};
+algorithm_to_crypto(<<"ES256">>) -> {ecdsa, sha256};
+algorithm_to_crypto(_) -> undefined.
+
+-spec epoch() -> non_neg_integer().
+%% @private
+epoch() -> erlang:system_time(seconds).
+
+-spec expiration_to_epoch(Expiration :: expiration()) -> neg_integer().
+%% @private
+expiration_to_epoch(Expiration) ->
+ expiration_to_epoch(Expiration, epoch()).
+
+expiration_to_epoch(Expiration, Ts) ->
+ case Expiration of
+ {hourly, Expiration0} -> (Ts - (Ts rem ?HOUR)) + Expiration0;
+ {daily, Expiration0} -> (Ts - (Ts rem ?DAY)) + Expiration0;
+ _ -> epoch() + Expiration
+ end.
+
+-spec append_claim(ClaimsSet :: map() | list(), binary(), any()) -> map() | list().
+%% @private
+append_claim(ClaimsSet, Key, Val) when is_map(ClaimsSet) ->
+ ClaimsSet#{ Key => Val };
+append_claim(ClaimsSet, Key, Val) -> [{ Key, Val } | ClaimsSet].
+
+pem_to_key(Pem) ->
+ Decoded = case public_key:pem_decode(Pem) of
+ [_, Key] ->
+ Key;
+ [Key] ->
+ Key
+ end,
+ public_key:pem_entry_decode(Decoded).
diff --git a/server/_build/default/lib/jwt/src/jwt_ecdsa.erl b/server/_build/default/lib/jwt/src/jwt_ecdsa.erl
new file mode 100644
index 0000000..7596e90
--- /dev/null
+++ b/server/_build/default/lib/jwt/src/jwt_ecdsa.erl
@@ -0,0 +1,76 @@
+%% @doc Eliptic curve digital signature algorithm
+%%
+%% Helper functions for encoding/decoding ECDSA signature
+%%
+%% @end
+-module(jwt_ecdsa).
+
+-include_lib("jwt_ecdsa.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-export([
+ signature/1,
+ signature/3
+]).
+
+%% @doc Signature for JWT verification
+%%
+%% Transcode the ECDSA Base64-encoded signature into ASN.1/DER format
+%%
+%% @end
+signature(Base64Sig) ->
+ Signature = base64url:decode(Base64Sig),
+ SignatureLen = byte_size(Signature),
+ {RBin, SBin} = split_binary(Signature, (SignatureLen div 2)),
+ R = crypto:bytes_to_integer(RBin),
+ S = crypto:bytes_to_integer(SBin),
+ public_key:der_encode('ECDSA-Sig-Value', #'ECDSA-Sig-Value'{ r = R, s = S }).
+
+%% @doc Signature to sign JWT
+%%
+%% Transcodes the JCA ASN.1/DER-encoded signature into the concatenated R + S format
+%% a.k.a <em>raw</em> format
+%%
+%% @end
+signature(Payload, Crypto, Key) ->
+ Der = public_key:sign(Payload, Crypto, Key),
+ raw(Der, Key).
+
+raw(Der, #'ECPrivateKey'{parameters = {namedCurve, NamedCurve}}) ->
+ #'ECDSA-Sig-Value'{ r = R, s = S } = public_key:der_decode('ECDSA-Sig-Value', Der),
+ CurveName = pubkey_cert_records:namedCurves(NamedCurve),
+ GroupDegree = group_degree(CurveName),
+ Size = (GroupDegree + 7) div 8,
+ RBin = int_to_bin(R),
+ SBin = int_to_bin(S),
+ RPad = pad(RBin, Size),
+ SPad = pad(SBin, Size),
+ <<RPad/binary, SPad/binary>>.
+
+%% @private
+int_to_bin(X) when X < 0 ->
+ int_to_bin_neg(X, []);
+int_to_bin(X) ->
+ int_to_bin_pos(X, []).
+
+%% @private
+int_to_bin_pos(0, Ds = [_|_]) ->
+ list_to_binary(Ds);
+int_to_bin_pos(X, Ds) ->
+ int_to_bin_pos(X bsr 8, [(X band 255)|Ds]).
+
+%% @private
+int_to_bin_neg(-1, Ds = [MSB|_]) when MSB >= 16#80 ->
+ list_to_binary(Ds);
+int_to_bin_neg(X, Ds) ->
+ int_to_bin_neg(X bsr 8, [(X band 255)|Ds]).
+
+%% @private
+pad(Bin, Size) when byte_size(Bin) =:= Size ->
+ Bin;
+pad(Bin, Size) when byte_size(Bin) < Size ->
+ pad(<<0, Bin/binary>>, Size).
+
+%% See the OpenSSL documentation for EC_GROUP_get_degree()
+group_degree(CurveName) ->
+ maps:get(CurveName, ?EC_GROUP_DEGREE).