src/foodog.erl

-module(foodog).

-export([generate/1, generate/2, verify/1, verify/2]).

-spec generate(map()) -> {ok, binary()} | {error, any()}.
generate(Payload0) ->
    generate(Payload0, #{}).

-spec generate(map(), map()) -> {ok, binary()} | {error, any()}.
generate(Payload0, Options) ->
    Payload1 = foodog_gen:jti(Payload0, maps:get(jti, Options, 1000000000)),
    Payload2 = foodog_gen:exp(Payload1, maps:get(exp, Options, {hours, 1})),

    case maps:get(iss, Options, issuer()) of
        {error, _Err} = E ->
            E;
        Issuer ->
            Payload3 = foodog_gen:iss(Payload2, Issuer),
            case keys() of
                {error, _Err} = E ->
                    E;
                {ok, []} ->
                    {error, keys_empty};
                {ok, Keys} ->
                    [{Kid, Key} | _] = Keys,
                    Payload4 = foodog_gen:sub_jwk(Payload3, Kid),
                    foodog_gen:token(Payload4, Key)
            end
    end.

-spec verify(binary()) -> {ok, map()} | {error, any()}.
verify(Token) ->
    verify(Token, #{}).

-spec verify(binary(), map()) -> {ok, map()} | {error, any()}.
verify(Token, Options) ->
    case verify_key(Token) of
        {error, _Err} = E ->
            E;
        {ok, Payload} ->
            case foodog_ver:exp(Payload) of
                {error, _Err} = E ->
                    E;
                {ok, _} ->
                    verify_issuer(Payload, Options)
            end
    end.

-spec verify_key(binary()) -> {ok, map()} | {error, any()}.
verify_key(Token) ->
    case keys() of
        {error, _Err} = E ->
            E;
        {ok, []} ->
            {error, keys_empty};
        {ok, Keys} ->
            case get_key(Token, Keys) of
                {error, _Err} = E ->
                    E;
                {ok, Key} ->
                    foodog_ver:key(Token, Key)
            end
    end.

-spec get_key(binary(), list()) -> {ok, binary()} | {error, any()}.
get_key(Token, Keys) ->
    case foodog_ver:fields(Token) of
        {error, _Err} = E ->
            E;
        {ok, #{<<"sub_jwk">> := Kid}} ->
            case proplists:get_value(Kid, Keys) of
                undefined ->
                    {error, key_not_found};
                Key ->
                    {ok, Key}
            end;
        _ ->
            {error, invalid_key_format}
    end.

-spec verify_issuer(map(), map()) -> {ok, map()} | {error, any()}.
verify_issuer(Payload, Options) ->
    case maps:get(iss, Options, issuer()) of
        {error, _Err} = E ->
            E;
        Issuer ->
            case foodog_ver:iss(Payload, Issuer) of
                {error, _Err} = E ->
                    E;
                {ok, _Payload} = Result ->
                    Result
            end
    end.

-spec issuer() -> binary() | {error, any()}.
issuer() ->
    case application:get_env(foodog, issuer) of
        undefined ->
            {error, undefined_issuer};
        {ok, Issuer} ->
            Issuer
    end.

-spec keys() -> {ok, list()} | {error, any()}.
keys() ->
    case application:get_env(foodog, keys) of
        undefined ->
            {error, undefined_keys};
        Keys ->
            Keys
    end.