forked from martinrehfeld/elli_basicauth
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Publish to Hex
- Loading branch information
Showing
5 changed files
with
270 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,132 @@ | ||
%% @doc Elli basicauth middleware | ||
%% @author Martin Rehfeld | ||
%% @author Eric Bailey | ||
%% @copyright 2013, Martin Rehfeld; 2018, elli-lib team | ||
%% | ||
%% This middleware provides basic authentication to protect | ||
%% Reqs based on a user-configured authentication function | ||
%% @author Martin Rehfeld | ||
|
||
%% requests, based on a user-configured authentication function. | ||
-module(elli_basicauth). | ||
|
||
-behaviour(elli_handler). | ||
|
||
-export([handle/2, handle_event/3]). | ||
-export([handle/2, handle_event/3, default_auth_fun/2]). | ||
|
||
-export_type([auth_fun/0, auth_status/0, config/0, credentials/0]). | ||
|
||
handle(Req, Config) -> | ||
{User, Password} = credentials(Req), | ||
|
||
case apply(auth_fun(Config), [Req, User, Password]) of | ||
unauthorized -> | ||
throw({401, | ||
[{<<"WWW-Authenticate">>, auth_realm(Config)}], | ||
<<"Unauthorized">>}); | ||
%% @type auth_fun(). A user-configurable authentication function. | ||
-type auth_fun() :: fun((Req :: elli:req(), | ||
Credentials :: credentials()) -> | ||
AuthStatus :: auth_status()). | ||
|
||
|
||
-type credentials() :: {undefined, undefined} | | ||
{Username :: binary(), | ||
Password :: binary()}. | ||
|
||
|
||
%% @type auth_status(). The result of an {@type auth_fun()}. | ||
-type auth_status() :: ok | | ||
unauthorized | | ||
forbidden | | ||
hidden. | ||
|
||
|
||
forbidden -> | ||
throw({403, [], <<"Forbidden">>}); | ||
%% @type config(). A property list of options. | ||
%% The configurable options are: | ||
%% <dl> | ||
%% <dt>`auth_fun'</dt> | ||
%% <dd>An {@type auth_fun()}</dd> | ||
%% <dt>`auth_realm'</dt> | ||
%% <dd>A binary <a href="https://tools.ietf.org/html/rfc1945#section-11">realm</a>.</dd> | ||
%% </dl> | ||
-type config() :: [{auth_fun, auth_fun()} | | ||
{auth_realm, binary()} | | ||
term()]. | ||
|
||
hidden -> | ||
throw({404, [], <<>>}); | ||
|
||
_ -> | ||
ignore | ||
end. | ||
-define(DEFAULT_CREDENTIALS, {undefined, undefined}). | ||
|
||
|
||
handle_event(_, _, _) -> | ||
-define(DEFAULT_REALM, <<"Secure Area">>). | ||
|
||
|
||
%% @doc Protect `Req' based on the configured `auth_fun'. | ||
%% If none is given, the default authentication is `forbidden'. | ||
-spec handle(elli:req(), config()) -> elli_handler:result(). | ||
handle(Req, Config) -> | ||
Credentials = credentials(Req), | ||
Authentication = apply(auth_fun(Config), [Req, Credentials]), | ||
do_handle(Authentication, Config). | ||
|
||
|
||
-spec do_handle(auth_status(), config()) -> elli_handler:result(). | ||
do_handle(ok, _Config) -> | ||
ignore; | ||
do_handle(unauthorized, Config) -> | ||
Headers = [{<<"WWW-Authenticate">>, auth_realm(Config)}], | ||
{401, Headers, <<"Unauthorized">>}; | ||
do_handle(forbidden, _Config) -> | ||
{403, [], <<"Forbidden">>}; | ||
do_handle(hidden, _Config) -> | ||
{404, [], <<>>}. | ||
|
||
|
||
%% @doc No-op to satisfy the `elli_handler' behaviour. Return `ok'. | ||
-spec handle_event(elli_handler:event(), list(), config()) -> ok. | ||
handle_event(_Event, _Args, _Config) -> | ||
ok. | ||
|
||
|
||
%% @doc Default to `forbidden', in case of missing `auth_fun' config. | ||
-spec default_auth_fun(Req, Credentials) -> AuthStatus when | ||
Req :: elli:req(), | ||
Credentials :: credentials(), | ||
AuthStatus :: auth_status(). | ||
default_auth_fun(_Req, {_User, _Password}) -> | ||
forbidden. | ||
|
||
|
||
%% | ||
%% INTERNAL HELPERS | ||
%% | ||
|
||
-spec auth_fun(config()) -> auth_fun(). | ||
auth_fun(Config) -> | ||
proplists:get_value(auth_fun, Config, | ||
%% default to forbidden in case of missing auth_fun config | ||
fun (_Req, _User, _Password) -> | ||
forbidden | ||
end). | ||
proplists:get_value(auth_fun, Config, fun default_auth_fun/2). | ||
|
||
|
||
-spec auth_realm(config()) -> binary(). | ||
auth_realm(Config) -> | ||
Realm = proplists:get_value(auth_realm, Config, <<"Secure Area">>), | ||
Realm = proplists:get_value(auth_realm, Config, ?DEFAULT_REALM), | ||
iolist_to_binary([<<"Basic realm=\"">>, Realm, <<"\"">>]). | ||
|
||
|
||
-spec credentials(elli:req()) -> credentials(). | ||
credentials(Req) -> | ||
case authorization_header(Req) of | ||
undefined -> | ||
{undefined, undefined}; | ||
|
||
AuthorizationHeader -> | ||
credentials_from_header(AuthorizationHeader) | ||
end. | ||
credentials_from_header(authorization_header(Req)). | ||
|
||
|
||
-spec authorization_header(elli:req()) -> undefined | binary(). | ||
authorization_header(Req) -> | ||
elli_request:get_header(<<"Authorization">>, Req). | ||
|
||
|
||
credentials_from_header(AuthorizationHeader) -> | ||
case binary:split(AuthorizationHeader, <<$ >>) of | ||
[<<"Basic">>, EncodedCredentials] -> | ||
decoded_credentials(EncodedCredentials); | ||
|
||
_ -> | ||
{undefined, undefined} | ||
end. | ||
-spec credentials_from_header(undefined | binary()) -> credentials(). | ||
credentials_from_header(<<"Basic ", EncodedCredentials/binary>>) -> | ||
decoded_credentials(EncodedCredentials); | ||
credentials_from_header(_Authorization) -> | ||
?DEFAULT_CREDENTIALS. | ||
|
||
|
||
-spec decoded_credentials(binary()) -> credentials(). | ||
decoded_credentials(EncodedCredentials) -> | ||
DecodedCredentials = base64:decode(EncodedCredentials), | ||
case binary:split(DecodedCredentials, <<$:>>) of | ||
[User, Password] -> | ||
{User, Password}; | ||
do_decoded_credentials(binary:split(DecodedCredentials, <<$:>>)). | ||
|
||
|
||
_ -> | ||
{undefined, undefined} | ||
end. | ||
-spec do_decoded_credentials([binary()]) -> credentials(). | ||
do_decoded_credentials([User, Password]) -> | ||
{User, Password}; | ||
do_decoded_credentials(_Bins) -> | ||
?DEFAULT_CREDENTIALS. |
Oops, something went wrong.