summaryrefslogtreecommitdiff
path: root/src/couch/src/couch_httpd_auth.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/couch/src/couch_httpd_auth.erl')
-rw-r--r--src/couch/src/couch_httpd_auth.erl635
1 files changed, 378 insertions, 257 deletions
diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl
index 01a210d05..7bcb85fba 100644
--- a/src/couch/src/couch_httpd_auth.erl
+++ b/src/couch/src/couch_httpd_auth.erl
@@ -18,8 +18,10 @@
-export([party_mode_handler/1]).
--export([default_authentication_handler/1, default_authentication_handler/2,
- special_test_authentication_handler/1]).
+-export([
+ default_authentication_handler/1, default_authentication_handler/2,
+ special_test_authentication_handler/1
+]).
-export([cookie_authentication_handler/1, cookie_authentication_handler/2]).
-export([null_authentication_handler/1]).
-export([proxy_authentication_handler/1, proxy_authentification_handler/1]).
@@ -33,59 +35,68 @@
-export([jwt_authentication_handler/1]).
--import(couch_httpd, [header_value/2, send_json/2, send_json/4, send_method_not_allowed/2, maybe_decompress/2]).
+-import(couch_httpd, [
+ header_value/2, send_json/2, send_json/4, send_method_not_allowed/2, maybe_decompress/2
+]).
--compile({no_auto_import,[integer_to_binary/1, integer_to_binary/2]}).
+-compile({no_auto_import, [integer_to_binary/1, integer_to_binary/2]}).
party_mode_handler(Req) ->
- case chttpd_util:get_chttpd_auth_config_boolean(
- "require_valid_user", false) of
- true ->
- throw({unauthorized, <<"Authentication required.">>});
- false ->
- Req#httpd{user_ctx=#user_ctx{}}
+ case
+ chttpd_util:get_chttpd_auth_config_boolean(
+ "require_valid_user", false
+ )
+ of
+ true ->
+ throw({unauthorized, <<"Authentication required.">>});
+ false ->
+ Req#httpd{user_ctx = #user_ctx{}}
end.
special_test_authentication_handler(Req) ->
case header_value(Req, "WWW-Authenticate") of
- "X-Couch-Test-Auth " ++ NamePass ->
- % NamePass is a colon separated string: "joe schmoe:a password".
- [Name, Pass] = re:split(NamePass, ":", [{return, list}, {parts, 2}]),
- case {Name, Pass} of
- {"Jan Lehnardt", "apple"} -> ok;
- {"Christopher Lenz", "dog food"} -> ok;
- {"Noah Slater", "biggiesmalls endian"} -> ok;
- {"Chris Anderson", "mp3"} -> ok;
- {"Damien Katz", "pecan pie"} -> ok;
- {_, _} ->
- throw({unauthorized, <<"Name or password is incorrect.">>})
- end,
- Req#httpd{user_ctx=#user_ctx{name=?l2b(Name)}};
- _ ->
- % No X-Couch-Test-Auth credentials sent, give admin access so the
- % previous authentication can be restored after the test
- Req#httpd{user_ctx=?ADMIN_USER}
+ "X-Couch-Test-Auth " ++ NamePass ->
+ % NamePass is a colon separated string: "joe schmoe:a password".
+ [Name, Pass] = re:split(NamePass, ":", [{return, list}, {parts, 2}]),
+ case {Name, Pass} of
+ {"Jan Lehnardt", "apple"} -> ok;
+ {"Christopher Lenz", "dog food"} -> ok;
+ {"Noah Slater", "biggiesmalls endian"} -> ok;
+ {"Chris Anderson", "mp3"} -> ok;
+ {"Damien Katz", "pecan pie"} -> ok;
+ {_, _} -> throw({unauthorized, <<"Name or password is incorrect.">>})
+ end,
+ Req#httpd{user_ctx = #user_ctx{name = ?l2b(Name)}};
+ _ ->
+ % No X-Couch-Test-Auth credentials sent, give admin access so the
+ % previous authentication can be restored after the test
+ Req#httpd{user_ctx = ?ADMIN_USER}
end.
basic_name_pw(Req) ->
AuthorizationHeader = header_value(Req, "Authorization"),
case AuthorizationHeader of
- "Basic " ++ Base64Value ->
- try re:split(base64:decode(Base64Value), ":",
- [{return, list}, {parts, 2}]) of
- ["_", "_"] ->
- % special name and pass to be logged out
- nil;
- [User, Pass] ->
- {User, Pass};
+ "Basic " ++ Base64Value ->
+ try
+ re:split(
+ base64:decode(Base64Value),
+ ":",
+ [{return, list}, {parts, 2}]
+ )
+ of
+ ["_", "_"] ->
+ % special name and pass to be logged out
+ nil;
+ [User, Pass] ->
+ {User, Pass};
+ _ ->
+ nil
+ catch
+ error:function_clause ->
+ throw({bad_request, "Authorization header has invalid base64 value"})
+ end;
_ ->
nil
- catch
- error:function_clause ->
- throw({bad_request, "Authorization header has invalid base64 value"})
- end;
- _ ->
- nil
end.
default_authentication_handler(Req) ->
@@ -93,42 +104,47 @@ default_authentication_handler(Req) ->
default_authentication_handler(Req, AuthModule) ->
case basic_name_pw(Req) of
- {User, Pass} ->
- case AuthModule:get_user_creds(Req, User) of
- nil ->
- throw({unauthorized, <<"Name or password is incorrect.">>});
- {ok, UserProps, _AuthCtx} ->
- reject_if_totp(UserProps),
- UserName = ?l2b(User),
- Password = ?l2b(Pass),
- case authenticate(Password, UserProps) of
- true ->
- Req#httpd{user_ctx=#user_ctx{
- name=UserName,
- roles=couch_util:get_value(<<"roles">>, UserProps, [])
- }};
- false ->
- authentication_warning(Req, UserName),
- throw({unauthorized, <<"Name or password is incorrect.">>})
- end
- end;
- nil ->
- case couch_server:has_admins() of
- true ->
- Req;
- false ->
- case chttpd_util:get_chttpd_auth_config_boolean(
- "require_valid_user", false) of
- true -> Req;
- % If no admins, and no user required, then everyone is admin!
- % Yay, admin party!
- false -> Req#httpd{user_ctx=?ADMIN_USER}
+ {User, Pass} ->
+ case AuthModule:get_user_creds(Req, User) of
+ nil ->
+ throw({unauthorized, <<"Name or password is incorrect.">>});
+ {ok, UserProps, _AuthCtx} ->
+ reject_if_totp(UserProps),
+ UserName = ?l2b(User),
+ Password = ?l2b(Pass),
+ case authenticate(Password, UserProps) of
+ true ->
+ Req#httpd{
+ user_ctx = #user_ctx{
+ name = UserName,
+ roles = couch_util:get_value(<<"roles">>, UserProps, [])
+ }
+ };
+ false ->
+ authentication_warning(Req, UserName),
+ throw({unauthorized, <<"Name or password is incorrect.">>})
+ end
+ end;
+ nil ->
+ case couch_server:has_admins() of
+ true ->
+ Req;
+ false ->
+ case
+ chttpd_util:get_chttpd_auth_config_boolean(
+ "require_valid_user", false
+ )
+ of
+ true -> Req;
+ % If no admins, and no user required, then everyone is admin!
+ % Yay, admin party!
+ false -> Req#httpd{user_ctx = ?ADMIN_USER}
+ end
end
- end
end.
null_authentication_handler(Req) ->
- Req#httpd{user_ctx=?ADMIN_USER}.
+ Req#httpd{user_ctx = ?ADMIN_USER}.
%% @doc proxy auth handler.
%
@@ -155,39 +171,53 @@ proxy_authentication_handler(Req) ->
%% @deprecated
proxy_authentification_handler(Req) ->
proxy_authentication_handler(Req).
-
+
proxy_auth_user(Req) ->
XHeaderUserName = chttpd_util:get_chttpd_auth_config(
- "x_auth_username", "X-Auth-CouchDB-UserName"),
+ "x_auth_username", "X-Auth-CouchDB-UserName"
+ ),
XHeaderRoles = chttpd_util:get_chttpd_auth_config(
- "x_auth_roles", "X-Auth-CouchDB-Roles"),
+ "x_auth_roles", "X-Auth-CouchDB-Roles"
+ ),
XHeaderToken = chttpd_util:get_chttpd_auth_config(
- "x_auth_token", "X-Auth-CouchDB-Token"),
+ "x_auth_token", "X-Auth-CouchDB-Token"
+ ),
case header_value(Req, XHeaderUserName) of
- undefined -> nil;
+ undefined ->
+ nil;
UserName ->
- Roles = case header_value(Req, XHeaderRoles) of
- undefined -> [];
- Else ->
- [?l2b(R) || R <- string:tokens(Else, ",")]
- end,
- case chttpd_util:get_chttpd_auth_config_boolean(
- "proxy_use_secret", false) of
+ Roles =
+ case header_value(Req, XHeaderRoles) of
+ undefined -> [];
+ Else -> [?l2b(R) || R <- string:tokens(Else, ",")]
+ end,
+ case
+ chttpd_util:get_chttpd_auth_config_boolean(
+ "proxy_use_secret", false
+ )
+ of
true ->
case chttpd_util:get_chttpd_auth_config("secret") of
undefined ->
- Req#httpd{user_ctx=#user_ctx{name=?l2b(UserName), roles=Roles}};
+ Req#httpd{user_ctx = #user_ctx{name = ?l2b(UserName), roles = Roles}};
Secret ->
- ExpectedToken = couch_util:to_hex(couch_util:hmac(sha, Secret, UserName)),
+ ExpectedToken = couch_util:to_hex(
+ couch_util:hmac(sha, Secret, UserName)
+ ),
case header_value(Req, XHeaderToken) of
Token when Token == ExpectedToken ->
- Req#httpd{user_ctx=#user_ctx{name=?l2b(UserName),
- roles=Roles}};
- _ -> nil
+ Req#httpd{
+ user_ctx = #user_ctx{
+ name = ?l2b(UserName),
+ roles = Roles
+ }
+ };
+ _ ->
+ nil
end
end;
false ->
- Req#httpd{user_ctx=#user_ctx{name=?l2b(UserName), roles=Roles}}
+ Req#httpd{user_ctx = #user_ctx{name = ?l2b(UserName), roles = Roles}}
end
end.
@@ -198,22 +228,35 @@ jwt_authentication_handler(Req) ->
case jwtf:decode(?l2b(Jwt), [alg | RequiredClaims], fun jwtf_keystore:get/2) of
{ok, {Claims}} ->
case lists:keyfind(<<"sub">>, 1, Claims) of
- false -> throw({unauthorized, <<"Token missing sub claim.">>});
- {_, User} -> Req#httpd{user_ctx=#user_ctx{
- name = User,
- roles = couch_util:get_value(?l2b(config:get("jwt_auth", "roles_claim_name", "_couchdb.roles")), Claims, [])
- }}
+ false ->
+ throw({unauthorized, <<"Token missing sub claim.">>});
+ {_, User} ->
+ Req#httpd{
+ user_ctx = #user_ctx{
+ name = User,
+ roles = couch_util:get_value(
+ ?l2b(
+ config:get(
+ "jwt_auth", "roles_claim_name", "_couchdb.roles"
+ )
+ ),
+ Claims,
+ []
+ )
+ }
+ }
end;
{error, Reason} ->
throw(Reason)
end;
- _ -> Req
+ _ ->
+ Req
end.
get_configured_claims() ->
Claims = config:get("jwt_auth", "required_claims", ""),
Re = "((?<key1>[a-z]+)|{(?<key2>[a-z]+)\s*,\s*\"(?<val>[^\"]+)\"})",
- case re:run(Claims, Re, [global, {capture, [key1, key2, val], binary}]) of
+ case re:run(Claims, Re, [global, {capture, [key1, key2, val], binary}]) of
nomatch when Claims /= "" ->
couch_log:error("[jwt_auth] required_claims is set to an invalid value.", []),
throw({misconfigured_server, <<"JWT is not configured correctly">>});
@@ -231,61 +274,77 @@ to_claim([<<>>, Key, Value]) ->
cookie_authentication_handler(Req) ->
cookie_authentication_handler(Req, couch_auth_cache).
-cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req, AuthModule) ->
+cookie_authentication_handler(#httpd{mochi_req = MochiReq} = Req, AuthModule) ->
case MochiReq:get_cookie_value("AuthSession") of
- undefined -> Req;
- [] -> Req;
- Cookie ->
- [User, TimeStr, HashStr] = try
- AuthSession = couch_util:decodeBase64Url(Cookie),
- [_A, _B, _Cs] = re:split(?b2l(AuthSession), ":",
- [{return, list}, {parts, 3}])
- catch
- _:_Error ->
- Reason = <<"Malformed AuthSession cookie. Please clear your cookies.">>,
- throw({bad_request, Reason})
- end,
- % Verify expiry and hash
- CurrentTime = make_cookie_time(),
- case chttpd_util:get_chttpd_auth_config("secret") of
undefined ->
- couch_log:debug("cookie auth secret is not set",[]),
Req;
- SecretStr ->
- Secret = ?l2b(SecretStr),
- case AuthModule:get_user_creds(Req, User) of
- nil -> Req;
- {ok, UserProps, _AuthCtx} ->
- UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<"">>),
- FullSecret = <<Secret/binary, UserSalt/binary>>,
- ExpectedHash = couch_util:hmac(sha, FullSecret, User ++ ":" ++ TimeStr),
- Hash = ?l2b(HashStr),
- Timeout = chttpd_util:get_chttpd_auth_config_integer(
- "timeout", 600),
- couch_log:debug("timeout ~p", [Timeout]),
- case (catch erlang:list_to_integer(TimeStr, 16)) of
- TimeStamp when CurrentTime < TimeStamp + Timeout ->
- case couch_passwords:verify(ExpectedHash, Hash) of
- true ->
- TimeLeft = TimeStamp + Timeout - CurrentTime,
- couch_log:debug("Successful cookie auth as: ~p",
- [User]),
- Req#httpd{user_ctx=#user_ctx{
- name=?l2b(User),
- roles=couch_util:get_value(<<"roles">>, UserProps, [])
- }, auth={FullSecret, TimeLeft < Timeout*0.9}};
- _Else ->
- Req
- end;
- _Else ->
- Req
- end
+ [] ->
+ Req;
+ Cookie ->
+ [User, TimeStr, HashStr] =
+ try
+ AuthSession = couch_util:decodeBase64Url(Cookie),
+ [_A, _B, _Cs] = re:split(
+ ?b2l(AuthSession),
+ ":",
+ [{return, list}, {parts, 3}]
+ )
+ catch
+ _:_Error ->
+ Reason = <<"Malformed AuthSession cookie. Please clear your cookies.">>,
+ throw({bad_request, Reason})
+ end,
+ % Verify expiry and hash
+ CurrentTime = make_cookie_time(),
+ case chttpd_util:get_chttpd_auth_config("secret") of
+ undefined ->
+ couch_log:debug("cookie auth secret is not set", []),
+ Req;
+ SecretStr ->
+ Secret = ?l2b(SecretStr),
+ case AuthModule:get_user_creds(Req, User) of
+ nil ->
+ Req;
+ {ok, UserProps, _AuthCtx} ->
+ UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<"">>),
+ FullSecret = <<Secret/binary, UserSalt/binary>>,
+ ExpectedHash = couch_util:hmac(sha, FullSecret, User ++ ":" ++ TimeStr),
+ Hash = ?l2b(HashStr),
+ Timeout = chttpd_util:get_chttpd_auth_config_integer(
+ "timeout", 600
+ ),
+ couch_log:debug("timeout ~p", [Timeout]),
+ case (catch erlang:list_to_integer(TimeStr, 16)) of
+ TimeStamp when CurrentTime < TimeStamp + Timeout ->
+ case couch_passwords:verify(ExpectedHash, Hash) of
+ true ->
+ TimeLeft = TimeStamp + Timeout - CurrentTime,
+ couch_log:debug(
+ "Successful cookie auth as: ~p",
+ [User]
+ ),
+ Req#httpd{
+ user_ctx = #user_ctx{
+ name = ?l2b(User),
+ roles = couch_util:get_value(
+ <<"roles">>, UserProps, []
+ )
+ },
+ auth = {FullSecret, TimeLeft < Timeout * 0.9}
+ };
+ _Else ->
+ Req
+ end;
+ _Else ->
+ Req
+ end
+ end
end
- end
end.
-cookie_auth_header(#httpd{user_ctx=#user_ctx{name=null}}, _Headers) -> [];
-cookie_auth_header(#httpd{user_ctx=#user_ctx{name=User}, auth={Secret, true}}=Req, Headers) ->
+cookie_auth_header(#httpd{user_ctx = #user_ctx{name = null}}, _Headers) ->
+ [];
+cookie_auth_header(#httpd{user_ctx = #user_ctx{name = User}, auth = {Secret, true}} = Req, Headers) ->
% Note: we only set the AuthSession cookie if:
% * a valid AuthSession cookie has been received
% * we are outside a 10% timeout window
@@ -296,20 +355,24 @@ cookie_auth_header(#httpd{user_ctx=#user_ctx{name=User}, auth={Secret, true}}=Re
CookieHeader = couch_util:get_value("Set-Cookie", Headers, ""),
Cookies = mochiweb_cookies:parse_cookie(CookieHeader),
AuthSession = couch_util:get_value("AuthSession", Cookies),
- if AuthSession == undefined ->
- TimeStamp = make_cookie_time(),
- [cookie_auth_cookie(Req, ?b2l(User), Secret, TimeStamp)];
- true ->
- []
+ if
+ AuthSession == undefined ->
+ TimeStamp = make_cookie_time(),
+ [cookie_auth_cookie(Req, ?b2l(User), Secret, TimeStamp)];
+ true ->
+ []
end;
-cookie_auth_header(_Req, _Headers) -> [].
+cookie_auth_header(_Req, _Headers) ->
+ [].
cookie_auth_cookie(Req, User, Secret, TimeStamp) ->
SessionData = User ++ ":" ++ erlang:integer_to_list(TimeStamp, 16),
Hash = couch_util:hmac(sha, Secret, SessionData),
- mochiweb_cookies:cookie("AuthSession",
+ mochiweb_cookies:cookie(
+ "AuthSession",
couch_util:encodeBase64Url(SessionData ++ ":" ++ ?b2l(Hash)),
- [{path, "/"}] ++ cookie_scheme(Req) ++ max_age() ++ cookie_domain() ++ same_site()).
+ [{path, "/"}] ++ cookie_scheme(Req) ++ max_age() ++ cookie_domain() ++ same_site()
+ ).
ensure_cookie_auth_secret() ->
case chttpd_util:get_chttpd_auth_config("secret") of
@@ -317,7 +380,8 @@ ensure_cookie_auth_secret() ->
NewSecret = ?b2l(couch_uuids:random()),
config:set("chttpd_auth", "secret", NewSecret),
NewSecret;
- Secret -> Secret
+ Secret ->
+ Secret
end.
% session handlers
@@ -325,27 +389,32 @@ ensure_cookie_auth_secret() ->
handle_session_req(Req) ->
handle_session_req(Req, couch_auth_cache).
-handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req, AuthModule) ->
+handle_session_req(#httpd{method = 'POST', mochi_req = MochiReq} = Req, AuthModule) ->
ReqBody = MochiReq:recv_body(),
- Form = case MochiReq:get_primary_header_value("content-type") of
- % content type should be json
- "application/x-www-form-urlencoded" ++ _ ->
- mochiweb_util:parse_qs(ReqBody);
- "application/json" ++ _ ->
- {Pairs} = ?JSON_DECODE(maybe_decompress(Req, ReqBody)),
- lists:map(fun({Key, Value}) ->
- {?b2l(Key), ?b2l(Value)}
- end, Pairs);
- _ ->
- []
- end,
+ Form =
+ case MochiReq:get_primary_header_value("content-type") of
+ % content type should be json
+ "application/x-www-form-urlencoded" ++ _ ->
+ mochiweb_util:parse_qs(ReqBody);
+ "application/json" ++ _ ->
+ {Pairs} = ?JSON_DECODE(maybe_decompress(Req, ReqBody)),
+ lists:map(
+ fun({Key, Value}) ->
+ {?b2l(Key), ?b2l(Value)}
+ end,
+ Pairs
+ );
+ _ ->
+ []
+ end,
UserName = ?l2b(extract_username(Form)),
Password = ?l2b(couch_util:get_value("password", Form, "")),
- couch_log:debug("Attempt Login: ~s",[UserName]),
- {ok, UserProps, _AuthCtx} = case AuthModule:get_user_creds(Req, UserName) of
- nil -> {ok, [], nil};
- Result -> Result
- end,
+ couch_log:debug("Attempt Login: ~s", [UserName]),
+ {ok, UserProps, _AuthCtx} =
+ case AuthModule:get_user_creds(Req, UserName) of
+ nil -> {ok, [], nil};
+ Result -> Result
+ end,
case authenticate(Password, UserProps) of
true ->
verify_totp(UserProps, Form),
@@ -353,68 +422,102 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req, AuthModule) ->
Secret = ?l2b(ensure_cookie_auth_secret()),
UserSalt = couch_util:get_value(<<"salt">>, UserProps),
CurrentTime = make_cookie_time(),
- Cookie = cookie_auth_cookie(Req, ?b2l(UserName), <<Secret/binary, UserSalt/binary>>, CurrentTime),
+ Cookie = cookie_auth_cookie(
+ Req, ?b2l(UserName), <<Secret/binary, UserSalt/binary>>, CurrentTime
+ ),
% TODO document the "next" feature in Futon
- {Code, Headers} = case couch_httpd:qs_value(Req, "next", nil) of
- nil ->
- {200, [Cookie]};
- Redirect ->
- {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
- end,
- send_json(Req#httpd{req_body=ReqBody}, Code, Headers,
+ {Code, Headers} =
+ case couch_httpd:qs_value(Req, "next", nil) of
+ nil ->
+ {200, [Cookie]};
+ Redirect ->
+ {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
+ end,
+ send_json(
+ Req#httpd{req_body = ReqBody},
+ Code,
+ Headers,
{[
{ok, true},
{name, UserName},
{roles, couch_util:get_value(<<"roles">>, UserProps, [])}
- ]});
+ ]}
+ );
false ->
authentication_warning(Req, UserName),
% clear the session
- Cookie = mochiweb_cookies:cookie("AuthSession", "", [{path, "/"}] ++ cookie_scheme(Req)),
- {Code, Headers} = case couch_httpd:qs_value(Req, "fail", nil) of
- nil ->
- {401, [Cookie]};
- Redirect ->
- {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
- end,
- send_json(Req, Code, Headers, {[{error, <<"unauthorized">>},{reason, <<"Name or password is incorrect.">>}]})
+ Cookie = mochiweb_cookies:cookie(
+ "AuthSession", "", [{path, "/"}] ++ cookie_scheme(Req)
+ ),
+ {Code, Headers} =
+ case couch_httpd:qs_value(Req, "fail", nil) of
+ nil ->
+ {401, [Cookie]};
+ Redirect ->
+ {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
+ end,
+ send_json(
+ Req,
+ Code,
+ Headers,
+ {[{error, <<"unauthorized">>}, {reason, <<"Name or password is incorrect.">>}]}
+ )
end;
% get user info
% GET /_session
-handle_session_req(#httpd{method='GET', user_ctx=UserCtx}=Req, _AuthModule) ->
+handle_session_req(#httpd{method = 'GET', user_ctx = UserCtx} = Req, _AuthModule) ->
Name = UserCtx#user_ctx.name,
ForceLogin = couch_httpd:qs_value(Req, "basic", "false"),
case {Name, ForceLogin} of
{null, "true"} ->
throw({unauthorized, <<"Please login.">>});
{Name, _} ->
- send_json(Req, {[
- % remove this ok
- {ok, true},
- {<<"userCtx">>, {[
- {name, Name},
- {roles, UserCtx#user_ctx.roles}
- ]}},
- {info, {[
- {authentication_handlers, [
- N || {N, _Fun} <- Req#httpd.authentication_handlers]}
- ] ++ maybe_value(authenticated, UserCtx#user_ctx.handler, fun(Handler) ->
- Handler
- end) ++ maybe_value(authentication_db, config:get("chttpd_auth", "authentication_db"), fun(Val) ->
- ?l2b(Val)
- end)}}
- ]})
+ send_json(
+ Req,
+ {[
+ % remove this ok
+ {ok, true},
+ {<<"userCtx">>,
+ {[
+ {name, Name},
+ {roles, UserCtx#user_ctx.roles}
+ ]}},
+ {info, {
+ [
+ {authentication_handlers, [
+ N
+ || {N, _Fun} <- Req#httpd.authentication_handlers
+ ]}
+ ] ++
+ maybe_value(authenticated, UserCtx#user_ctx.handler, fun(Handler) ->
+ Handler
+ end) ++
+ maybe_value(
+ authentication_db,
+ config:get("chttpd_auth", "authentication_db"),
+ fun(Val) ->
+ ?l2b(Val)
+ end
+ )
+ }}
+ ]}
+ )
end;
% logout by deleting the session
-handle_session_req(#httpd{method='DELETE'}=Req, _AuthModule) ->
- Cookie = mochiweb_cookies:cookie("AuthSession", "", [{path, "/"}] ++
- cookie_domain() ++ cookie_scheme(Req)),
- {Code, Headers} = case couch_httpd:qs_value(Req, "next", nil) of
- nil ->
- {200, [Cookie]};
- Redirect ->
- {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
- end,
+handle_session_req(#httpd{method = 'DELETE'} = Req, _AuthModule) ->
+ Cookie = mochiweb_cookies:cookie(
+ "AuthSession",
+ "",
+ [{path, "/"}] ++
+ cookie_domain() ++ cookie_scheme(Req)
+ ),
+ {Code, Headers} =
+ case couch_httpd:qs_value(Req, "next", nil) of
+ nil ->
+ {200, [Cookie]};
+ Redirect ->
+ {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
+ end,
send_json(Req, Code, Headers, {[{ok, true}]});
handle_session_req(Req, _AuthModule) ->
send_method_not_allowed(Req, "GET,HEAD,POST,DELETE").
@@ -433,22 +536,25 @@ extract_username(Form) ->
end.
maybe_value(_Key, undefined, _Fun) -> [];
-maybe_value(Key, Else, Fun) ->
- [{Key, Fun(Else)}].
+maybe_value(Key, Else, Fun) -> [{Key, Fun(Else)}].
authenticate(Pass, UserProps) ->
UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<>>),
{PasswordHash, ExpectedHash} =
case couch_util:get_value(<<"password_scheme">>, UserProps, <<"simple">>) of
- <<"simple">> ->
- {couch_passwords:simple(Pass, UserSalt),
- couch_util:get_value(<<"password_sha">>, UserProps, nil)};
- <<"pbkdf2">> ->
- Iterations = couch_util:get_value(<<"iterations">>, UserProps, 10000),
- verify_iterations(Iterations),
- {couch_passwords:pbkdf2(Pass, UserSalt, Iterations),
- couch_util:get_value(<<"derived_key">>, UserProps, nil)}
- end,
+ <<"simple">> ->
+ {
+ couch_passwords:simple(Pass, UserSalt),
+ couch_util:get_value(<<"password_sha">>, UserProps, nil)
+ };
+ <<"pbkdf2">> ->
+ Iterations = couch_util:get_value(<<"iterations">>, UserProps, 10000),
+ verify_iterations(Iterations),
+ {
+ couch_passwords:pbkdf2(Pass, UserSalt, Iterations),
+ couch_util:get_value(<<"derived_key">>, UserProps, nil)
+ }
+ end,
couch_passwords:verify(PasswordHash, ExpectedHash).
verify_iterations(Iterations) when is_integer(Iterations) ->
@@ -471,21 +577,25 @@ make_cookie_time() ->
{NowMS, NowS, _} = os:timestamp(),
NowMS * 1000000 + NowS.
-cookie_scheme(#httpd{mochi_req=MochiReq}) ->
+cookie_scheme(#httpd{mochi_req = MochiReq}) ->
[{http_only, true}] ++
- case MochiReq:get(scheme) of
- http -> [];
- https -> [{secure, true}]
- end.
+ case MochiReq:get(scheme) of
+ http -> [];
+ https -> [{secure, true}]
+ end.
max_age() ->
- case chttpd_util:get_chttpd_auth_config_boolean(
- "allow_persistent_cookies", true) of
+ case
+ chttpd_util:get_chttpd_auth_config_boolean(
+ "allow_persistent_cookies", true
+ )
+ of
false ->
[];
true ->
Timeout = chttpd_util:get_chttpd_auth_config_integer(
- "timeout", 600),
+ "timeout", 600
+ ),
[{max_age, Timeout}]
end.
@@ -496,20 +606,22 @@ cookie_domain() ->
_ -> [{domain, Domain}]
end.
-
same_site() ->
SameSite = chttpd_util:get_chttpd_auth_config("same_site", ""),
case string:to_lower(SameSite) of
- "" -> [];
- "none" -> [{same_site, none}];
- "lax" -> [{same_site, lax}];
- "strict" -> [{same_site, strict}];
+ "" ->
+ [];
+ "none" ->
+ [{same_site, none}];
+ "lax" ->
+ [{same_site, lax}];
+ "strict" ->
+ [{same_site, strict}];
_ ->
- couch_log:error("invalid config value couch_httpd_auth.same_site: ~p ",[SameSite]),
+ couch_log:error("invalid config value couch_httpd_auth.same_site: ~p ", [SameSite]),
[]
end.
-
reject_if_totp(User) ->
case get_totp_config(User) of
undefined ->
@@ -525,7 +637,8 @@ verify_totp(User, Form) ->
{Props} ->
Key = couch_base32:decode(couch_util:get_value(<<"key">>, Props)),
Alg = couch_util:to_existing_atom(
- couch_util:get_value(<<"algorithm">>, Props, <<"sha">>)),
+ couch_util:get_value(<<"algorithm">>, Props, <<"sha">>)
+ ),
Len = couch_util:get_value(<<"length">>, Props, 6),
Token = ?l2b(couch_util:get_value("token", Form, "")),
verify_token(Alg, Key, Len, Token)
@@ -536,12 +649,17 @@ get_totp_config(User) ->
verify_token(Alg, Key, Len, Token) ->
Now = make_cookie_time(),
- Tokens = [generate_token(Alg, Key, Len, Now - 30),
- generate_token(Alg, Key, Len, Now),
- generate_token(Alg, Key, Len, Now + 30)],
+ Tokens = [
+ generate_token(Alg, Key, Len, Now - 30),
+ generate_token(Alg, Key, Len, Now),
+ generate_token(Alg, Key, Len, Now + 30)
+ ],
%% evaluate all tokens in constant time
- Match = lists:foldl(fun(T, Acc) -> couch_util:verify(T, Token) or Acc end,
- false, Tokens),
+ Match = lists:foldl(
+ fun(T, Acc) -> couch_util:verify(T, Token) or Acc end,
+ false,
+ Tokens
+ ),
case Match of
true ->
ok;
@@ -553,17 +671,20 @@ generate_token(Alg, Key, Len, Timestamp) ->
integer_to_binary(couch_totp:generate(Alg, Key, Timestamp, 30, Len), Len).
integer_to_binary(Int, Len) when is_integer(Int), is_integer(Len) ->
- Unpadded = case erlang:function_exported(erlang, integer_to_binary, 1) of
- true ->
- erlang:integer_to_binary(Int);
- false ->
- ?l2b(integer_to_list(Int))
- end,
+ Unpadded =
+ case erlang:function_exported(erlang, integer_to_binary, 1) of
+ true ->
+ erlang:integer_to_binary(Int);
+ false ->
+ ?l2b(integer_to_list(Int))
+ end,
Padding = binary:copy(<<"0">>, Len),
Padded = <<Padding/binary, Unpadded/binary>>,
binary:part(Padded, byte_size(Padded), -Len).
authentication_warning(#httpd{mochi_req = Req}, User) ->
Peer = Req:get(peer),
- couch_log:warning("~p: Authentication failed for user ~s from ~s",
- [?MODULE, User, Peer]).
+ couch_log:warning(
+ "~p: Authentication failed for user ~s from ~s",
+ [?MODULE, User, Peer]
+ ).