Packages

OAuth2 endpoints for cowboy and snarl.

Current section

Files

Jump to
cowboy_oauth src cowboy_oauth_auth.erl
Raw

src/cowboy_oauth_auth.erl

-module(cowboy_oauth_auth).
-include("cowboy_oauth.hrl").
-export([init/3]).
-export([handle/2]).
-export([terminate/3]).
-ignore_xref([init/3]).
-ignore_xref([handle/2]).
-ignore_xref([terminate/3]).
-record(auth_req, {
response_type,
client_id,
redirect_uri,
scope,
username,
password,
state,
method,
user_uuid,
redirect_base,
bearer
}).
init(_Transport, Req, []) ->
{ok, Req, <<"/">>};
init(_Transport, Req, [RedirectBase]) ->
{ok, Req, RedirectBase}.
terminate(_Reason, _Req, _State) ->
ok.
handle(Req, RedirectBase) ->
{ok, Req3} = case cowboy_req:method(Req) of
%% TODO: This should prompt a permission form
%% And check for the bearer token
{<<"GET">>, Req2} ->
do_get(Req2, RedirectBase);
%% TODO: This should do the actual redirect etc.
{<<"POST">>, Req2} ->
do_post(Req2, RedirectBase);
{_, Req2} ->
cowboy_req:reply(405, Req2)
end,
lager:info("[oath:auth] Request finished!"),
{ok, Req3, RedirectBase}.
do_get(Req, RedirectBase) ->
{QSVals, Req2} = cowboy_req:qs_vals(Req),
AuthReq = #auth_req{method = get, redirect_base = RedirectBase},
do_vals(AuthReq, QSVals, Req2).
do_post(Req, RedirectBase)->
{ok, PostVals, Req2} = cowboy_req:body_qs(Req),
AuthReq = #auth_req{method = post, redirect_base = RedirectBase},
do_vals(AuthReq, PostVals, Req2).
do_vals(AuthReq, Vals, Req) ->
ResponseType = cowboy_oauth:decode_response_type(
proplists:get_value(<<"response_type">>, Vals)),
ClientID = proplists:get_value(<<"client_id">>, Vals),
RedirectURI = proplists:get_value(<<"redirect_uri">>, Vals),
Scope = proplists:get_value(<<"scope">>, Vals),
State = proplists:get_value(<<"state">>, Vals),
Username = proplists:get_value(<<"username">>, Vals),
Password = proplists:get_value(<<"password">>, Vals),
AuthReq1 = AuthReq#auth_req{
response_type = ResponseType,
client_id = ClientID,
redirect_uri = RedirectURI,
username = Username,
password = Password,
scope = Scope,
state = State},
do_basic_auth(AuthReq1, Req).
do_basic_auth(AuthReq, Req) ->
{ok, Auth, Req1} = cowboy_req:parse_header(<<"authorization">>, Req),
case Auth of
{<<"basic">>, {Username, Password}} ->
AuthReq1 = AuthReq#auth_req{username = Username, password = Password},
update_scope(AuthReq1, Req1);
{<<"bearer">>, Bearer} ->
AuthReq1 = AuthReq#auth_req{bearer = Bearer},
check_token(AuthReq1, Req1);
_ ->
update_scope(AuthReq, Req1)
end.
check_token(AuthReq = #auth_req{bearer = Bearer}, Req) ->
case ls_oauth:verify_access_token(Bearer) of
{ok, Context} ->
case proplists:get_value(<<"resource_owner">>, Context) of
undefined ->
cowboy_oauth:json_error_response(access_denied, Req);
OwnerUUID ->
AuthReq1 = AuthReq#auth_req{user_uuid = OwnerUUID},
update_scope(AuthReq1, Req)
end;
_ ->
cowboy_oauth:json_error_response(access_denied, Req)
end.
update_scope(AuthReq = #auth_req{scope = Scope}, Req) ->
do_request(AuthReq#auth_req{
scope = cowboy_oauth:list_to_scope(Scope)
}, Req).
do_request(AuthReq = #auth_req{method = get}, Req) ->
Params = build_params(AuthReq),
Form = application:get_env(cowboy_oauth, oauth_form, oauth_login_form_dtl),
{ok, Reply} = Form:render(Params),
cowboy_req:reply(200, [], Reply, Req);
do_request(AuthReq = #auth_req{response_type = code}, Req) ->
do_code(AuthReq, Req);
do_request(AuthReq = #auth_req{response_type = token}, Req) ->
do_token(AuthReq, Req);
do_request(#auth_req{}, Req) ->
cowboy_oauth:json_error_response(unsupported_response_type, Req).
%% 4.1.1
do_code(#auth_req{
client_id = ClientID,
redirect_uri = URI,
user_uuid = UserUUID,
scope = Scope,
state = State,
redirect_base = RedirectBase}, Req)
when is_binary(UserUUID),
is_binary(ClientID) ->
do_code({UserUUID}, ClientID, URI, Scope, State, RedirectBase, Req);
do_code(#auth_req{
client_id = ClientID,
redirect_uri = URI,
username = Username,
password = Password,
scope = Scope,
state = State,
redirect_base = RedirectBase}, Req)
when is_binary(Username),
is_binary(Password),
is_binary(ClientID) ->
do_code({Username, Password}, ClientID, URI, Scope, State, RedirectBase, Req);
do_code(#auth_req{redirect_uri = Uri, state = State}, Req) ->
cowboy_oauth:redirected_error_response(Uri, invalid_request, State, Req).
do_code(User, ClientID, URI, Scope, State, RedirectBase, Req) ->
case ls_oauth:authorize_code_request(User, ClientID, URI, Scope) of
{ok, Authorization = #a{resowner = UUID}} ->
{ok, U} = ls_user:get(UUID),
case ft_user:yubikeys(U) of
[] ->
{ok, Response} = ls_oauth:issue_code(Authorization),
{ok, Code} = oauth2_response:access_code(Response),
cowboy_oauth:redirected_authorization_code_response(URI, Code, State, Req);
_ ->
%%TODO
cowboy_oauth:redirected_2fa_request(
<<"code">>, UUID, Authorization, State, URI, RedirectBase,
Req)
end;
{error, unauthorized_client} ->
%% cliend_id is not registered or redirection_uri is not valid
cowboy_oauth:json_error_response(unauthorized_client, Req);
{error, Error} ->
cowboy_oauth:redirected_error_response(URI, Error, State, Req)
end.
do_token(#auth_req{
client_id = ClientID,
redirect_uri = URI,
user_uuid = UserUUID,
scope = Scope,
state = State,
redirect_base = RedirectBase}, Req)
when is_binary(UserUUID),
is_binary(ClientID) ->
do_token({UserUUID}, ClientID, URI, Scope, State, RedirectBase, Req);
do_token(#auth_req{
client_id = ClientID,
redirect_uri = URI,
username = Username,
password = Password,
scope = Scope,
state = State,
redirect_base = RedirectBase}, Req)
when is_binary(Username),
is_binary(Password),
is_binary(ClientID) ->
do_token({Username, Password}, ClientID, URI, Scope, State, RedirectBase,
Req);
do_token(#auth_req{redirect_uri = Uri, state = State}, Req) ->
cowboy_oauth:redirected_error_response(Uri, invalid_request, State, Req).
do_token(Authentication, ClientID, URI, Scope, State, RedirectBase, Req) when
is_binary(ClientID) ->
{ok, C} = ls_client:lookup(ClientID),
UUID = ft_client:uuid(C),
do_token(Authentication, {UUID}, URI, Scope, State, RedirectBase, Req);
do_token(Authentication, ClientID, URI, Scope, State, RedirectBase, Req) when
is_tuple(Authentication),
is_tuple(ClientID) ->
case ls_oauth:authorize_password(Authentication, ClientID, URI, Scope) of
{ok, Authorization = #a{resowner = UUID}} ->
{ok, U} = ls_user:get(UUID),
case ft_user:yubikeys(U) of
[] ->
{ok, Response} = ls_oauth:issue_token(Authorization),
{ok, AccessToken} = oauth2_response:access_token(Response),
{ok, Type} = oauth2_response:token_type(Response),
{ok, Expires} = oauth2_response:expires_in(Response),
{ok, VerifiedScope} = oauth2_response:scope(Response),
cowboy_oauth:redirected_access_token_response(URI,
AccessToken,
Type,
Expires,
VerifiedScope,
State,
Req);
_ ->
cowboy_oauth:redirected_2fa_request(
<<"token">>, UUID, Authorization, State, URI,
RedirectBase, Req)
end;
{error, Error} ->
cowboy_oauth:redirected_error_response(URI, Error, State, Req)
end.
build_params(R = #auth_req{response_type = code}) ->
build_params(R, [{response_type, <<"code">>}]);
build_params(R = #auth_req{response_type = token}) ->
build_params(R, [{response_type, <<"token">>}]);
build_params(_) ->
[{error, <<"illegal request type">>}].
build_params(R = #auth_req{client_id = ClientID}, Acc)
when ClientID =/= undefined ->
{ok, Client} = ls_client:lookup(ClientID),
build_params1(R, [{client_id, ClientID},
{client_name, ft_client:name(Client)} | Acc]);
build_params(_, _) ->
[{error, <<"no_client_id">>}].
build_params1(R = #auth_req{redirect_uri = RedirectURI}, Acc)
when RedirectURI =/= undefined ->
build_params2(R, [{redirect_uri, RedirectURI} | Acc]);
build_params1(R, Acc) ->
build_params2(R, Acc).
build_params2(R = #auth_req{scope = Scope}, Acc)
when Scope =/= undefined ->
Descs = scope_desc(Scope),
build_params3(R, [{scope, cowboy_oauth:scope_to_list(Scope)},
{scope_list, Descs} | Acc]);
build_params2(R, Acc) ->
build_params3(R, Acc).
build_params3(R = #auth_req{user_uuid = OwnerUUID}, Acc)
when OwnerUUID =/= undefined ->
{ok, User} = ls_user:get(OwnerUUID),
build_params4(R, [{user_name, ft_user:name(User)} | Acc]);
build_params3(R, Acc) ->
build_params4(R, Acc).
build_params4(#auth_req{state = State}, Acc)
when State =/= undefined ->
[{state, State} | Acc];
build_params4(_R, Acc) ->
Acc.
scope_desc(Scope) ->
{ok, Scopes} = ls_oauth:scope(Scope),
[Desc || #{desc := Desc} <- Scopes].