aboutsummaryrefslogtreecommitdiff
path: root/apps/styx_web/src/styx_web_oauth2_consent.erl
blob: 26da05a8599ca22869f7bf7170d1cb84215e15c8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
-module(styx_web_oauth2_consent).
-behaviour(cowboy_handler).
-export([init/2]).

init(Req = #{method := <<"GET">>}, State) ->
    init_(Req, State, styx_web:req_param(Req, <<"consent_challenge">>));
init(Req = #{method := <<"POST">>}, State) ->
    init_(Req, State, styx_web:req_param(Req, <<"consent_challenge">>));
init(Req, _) ->
    styx_web_error:init(Req, #{code => 404, status => <<"Not Found">>}).

init_(Req0, State, {ok, Challenge}) ->
    Req = styx_web_oauth2_login:unset_cookie(Req0),
    Cookie = cowboy_req:header(<<"cookie">>, Req),
    authentication(Req, State, Challenge, ory_kratos:whoami(Cookie));
init_(Req, _, {error, {missing_param, _}}) ->
    styx_web_error:init(Req, not_found).

authentication(Req0, State, Challenge, {ok, Session = #{<<"active">> := true}}) ->
    do(Req0, State, Session, ory_hydra:consent_request(Challenge));
authentication(Req0, State, _Challenge, Error) ->
    render_error(Req0, State, Error).

do(Req0 = #{method := <<"GET">>}, State, _Session, {ok, #{<<"challenge">> := Challenge, <<"skip">> := true, <<"requested_scope">> := Scopes}}) ->
    ConsentData = #{<<"grant_scope">> => Scopes},
    case ory_hydra:accept_consent_request(Challenge, ConsentData) of
        {ok, #{<<"redirect_to">> := Url}} ->
            Req = styx_web:temporary_redirect(Req0, Url),
            {ok, Req, State};
        Error ->
            render_error(Req0, State, Error)
    end;
do(Req0 = #{method := <<"GET">>}, State, _Session, {ok, Flow = #{<<"client">> := Client}}) ->
    %% FIXME client_name can be blank, not just undefined.
    logger:debug("oAuth request ~p", [Flow]),
    AppName = maps:get(<<"client_name">>, Client, maps:get(<<"client_id">>, Client, <<"Unnamed App">>)),
    Assigns = [{"page_title", ["Authorize ", AppName]}, {"flow", Flow}],
    Html = styx_web:render(Req0, oauth2_consent_form_dtl, Assigns),
    Req = styx_web:reply_html(Req0, 200, Html),
    {ok, Req, State};
do(Req0 = #{method := <<"POST">>}, State, Session, {ok, Flow}) ->
    {ok, Data, Req} = cowboy_req:read_urlencoded_body(Req0),
    post(Req, State, Session, Flow, Data).

post(Req0, State, Session, Flow, Data) ->
    Consent = case lists:keyfind(<<"consent">>, 1, Data) of
                  {_, <<"true">>} -> true;
                  _ -> false
    end,
    consent(Req0, State, Session, Flow, Data, Consent).


consent(Req0, State, _Session, #{<<"challenge">> := Challenge}, Data, true) ->
    ScopesFun = fun
                    ({<<"scope-", S/binary>>, <<"on">>}, Acc) -> [S | Acc];
                    (_, Acc) -> Acc
    end,
    Scopes = lists:foldl(ScopesFun, [], Data),
    ConsentData = #{<<"grant_scope">> => Scopes},
    case ory_hydra:accept_consent_request(Challenge, ConsentData) of
        {ok, #{<<"redirect_to">> := Url}} ->
            Req = styx_web:temporary_redirect(Req0, Url),
            {ok, Req, State};
        Error ->
            render_error(Req0, State, Error)
    end;
consent(Req0, State, _Session, #{<<"challenge">> := Challenge}, _Data, false) ->
    Data = #{<<"error">> => <<"User denied access.">>, <<"status_code">> => 403},
    case ory_hydra:reject_consent_request(Challenge, Data) of
        {ok, #{<<"redirect_to">> := Url}} ->
            Req = styx_web:temporary_redirect(Req0, Url),
            {ok, Req, State};
        Error ->
            render_error(Req0, State, Error)
    end.


render_error(Req, _State, {error, #{<<"code">> := Code, <<"status">> := Status, <<"message">> := Msg}}) ->
    styx_web_error:init(Req, #{code => Code, status => Status, message => Msg}).