src/oidcc_cowboy_extract_authorization.erl

%%%-------------------------------------------------------------------
%% @doc Extract `authorization' request header
%%
%% This middleware should be used together with
%% {@link oidcc_cowboy_introspect_token}, {@link oidcc_cowboy_load_userinfo} or
%% {@link oidcc_cowboy_validate_jwt_token}.
%%
%% <h2>Usage</h2>
%%
%% ```
%% OidccCowboyOpts = #{
%%     %% ...
%% },
%% Dispatch = cowboy_router:compile([
%%     {'_', [
%%         %% ...
%%     ]}
%% ]),
%% {ok, _} = cowboy:start_clear(http, [{port, 8080}], #{
%%     middlewares => [
%%         oidcc_cowboy_extract_authorization,
%%         oidcc_cowboy_load_userinfo, %% Check Token via Introspection
%%         oidcc_cowboy_introspect_token, %% Check Token via Userinfo
%%         oidcc_cowboy_validate_jwt_token, %% Check Token via JWT validation
%%         cowboy_router,
%%         cowboy_handler
%%     ],
%%     env => #{
%%         dispatch => Dispatch,
%%         oidcc_cowboy_extract_authorization => #{}, %% Opts
%%     }
%% })
%% '''
%% @end
%% @since 2.0.0
%%%-------------------------------------------------------------------
-module(oidcc_cowboy_extract_authorization).

-behaviour(cowboy_middleware).

-export([execute/2]).

-export_type([opts/0]).

-type opts() :: #{
    send_invalid_header_response => fun(
        (Req :: cowboy_req:req(), Env :: cowboy_middleware:env(), GivenHeader :: binary()) ->
            {ok, cowboy_req:req(), cowboy_middleware:env()} | {stop, cowboy_req:req()}
    )
}.
%% Options for the middleware
%%
%% <h2>Options</h2>
%%
%% <ul>
%%   <li>`send_invalid_header_response' - Customize Error Response for invalid
%%     header</li>
%% </ul>

%% @private
execute(Req, #{?MODULE := Opts} = Env) ->
    SendInvalidHeaderResponse = maps:get(
        send_invalid_header_response, Opts, fun send_invalid_header_response/3
    ),
    case cowboy_req:headers(Req) of
        #{<<"authorization">> := <<"Bearer ", Token/binary>>} ->
            {ok, maps:put(oidcc_cowboy_extract_authorization, Token, Req), Env};
        #{<<"authorization">> := Authorization} ->
            SendInvalidHeaderResponse(Req, Env, Authorization);
        #{} ->
            {ok, maps:put(oidcc_cowboy_extract_authorization, undefined, Req), Env}
    end;
execute(Req, Env) ->
    execute(Req, maps:put(?MODULE, #{}, Env)).

send_invalid_header_response(Req0, _Env, GivenHeader) ->
    Req = cowboy_req:reply(
        400,
        #{<<"content-type">> => <<"text/plain">>},
        <<"Invalid authorization Header\n\nExpected: Authorization: Bearer <token>\nGiven: ",
            GivenHeader/binary>>,
        Req0
    ),
    {stop, Req}.