1 |
|
-module(mongoose_client_api). |
2 |
|
|
3 |
|
-export([init/2]). |
4 |
|
-export([content_types_provided/2]). |
5 |
|
-export([is_authorized/2]). |
6 |
|
-export([options/2]). |
7 |
|
-export([allowed_methods/2]). |
8 |
|
-export([to_json/2]). |
9 |
|
-export([bad_request/2]). |
10 |
|
-export([bad_request/3]). |
11 |
|
-export([forbidden_request/2]). |
12 |
|
-export([forbidden_request/3]). |
13 |
|
-export([json_to_map/1]). |
14 |
|
|
15 |
|
-ignore_xref([allowed_methods/2, content_types_provided/2, forbidden_request/3, |
16 |
|
options/2, to_json/2]). |
17 |
|
|
18 |
|
-include("mongoose.hrl"). |
19 |
|
-include("jlib.hrl"). |
20 |
|
|
21 |
|
init(Req, _Opts) -> |
22 |
99 |
State = #{}, |
23 |
99 |
case cowboy_req:header(<<"origin">>, Req) of |
24 |
|
undefined -> |
25 |
99 |
{cowboy_rest, Req, State}; |
26 |
|
Origin -> |
27 |
:-( |
Req1 = set_cors_headers(Origin, Req), |
28 |
:-( |
{cowboy_rest, Req1, State} |
29 |
|
end. |
30 |
|
|
31 |
|
set_cors_headers(Origin, Req) -> |
32 |
|
%% set CORS headers |
33 |
:-( |
Headers = [{<<"access-control-allow-origin">>, Origin}, |
34 |
|
{<<"access-control-allow-methods">>, <<"GET, OPTIONS">>}, |
35 |
|
{<<"access-control-allow-credentials">>, <<"true">>}, |
36 |
|
{<<"access-control-allow-headers">>, <<"authorization, content-type">>} |
37 |
|
], |
38 |
|
|
39 |
:-( |
lists:foldl(fun set_cors_header/2, Req, Headers). |
40 |
|
|
41 |
|
set_cors_header({Header, Value}, Req) -> |
42 |
:-( |
cowboy_req:set_resp_header(Header, Value, Req). |
43 |
|
|
44 |
|
allowed_methods(Req, State) -> |
45 |
:-( |
{[<<"OPTIONS">>, <<"GET">>], Req, State}. |
46 |
|
|
47 |
|
content_types_provided(Req, State) -> |
48 |
:-( |
{[ |
49 |
|
{{<<"application">>, <<"json">>, '*'}, to_json} |
50 |
|
], Req, State}. |
51 |
|
|
52 |
|
options(Req, State) -> |
53 |
:-( |
{ok, Req, State}. |
54 |
|
|
55 |
|
to_json(Req, User) -> |
56 |
:-( |
{<<"{}">>, Req, User}. |
57 |
|
|
58 |
|
bad_request(Req, State) -> |
59 |
3 |
bad_request(Req, <<"Bad request. The details are unknown.">>, State). |
60 |
|
|
61 |
|
bad_request(Req, Reason, State) -> |
62 |
5 |
reply(400, Req, Reason, State). |
63 |
|
|
64 |
|
forbidden_request(Req, State) -> |
65 |
3 |
forbidden_request(Req, <<>>, State). |
66 |
|
|
67 |
|
forbidden_request(Req, Reason, State) -> |
68 |
3 |
reply(403, Req, Reason, State). |
69 |
|
|
70 |
|
reply(StatusCode, Req, Body, State) -> |
71 |
8 |
maybe_report_error(StatusCode, Req, Body), |
72 |
8 |
Req1 = set_resp_body_if_missing(Body, Req), |
73 |
8 |
Req2 = cowboy_req:reply(StatusCode, Req1), |
74 |
8 |
{stop, Req2, State#{was_replied => true}}. |
75 |
|
|
76 |
|
set_resp_body_if_missing(Body, Req) -> |
77 |
8 |
case cowboy_req:has_resp_body(Req) of |
78 |
|
true -> |
79 |
3 |
Req; |
80 |
|
false -> |
81 |
5 |
cowboy_req:set_resp_body(Body, Req) |
82 |
|
end. |
83 |
|
|
84 |
|
maybe_report_error(StatusCode, Req, Body) when StatusCode >= 400 -> |
85 |
8 |
?LOG_WARNING(#{what => reply_error, |
86 |
|
stacktrace => element(2, erlang:process_info(self(), current_stacktrace)), |
87 |
:-( |
code => StatusCode, req => Req, reply_body => Body}); |
88 |
|
maybe_report_error(_StatusCode, _Req, _Body) -> |
89 |
:-( |
ok. |
90 |
|
|
91 |
|
%%-------------------------------------------------------------------- |
92 |
|
%% Authorization |
93 |
|
%%-------------------------------------------------------------------- |
94 |
|
|
95 |
|
% @doc cowboy callback |
96 |
|
is_authorized(Req, State) -> |
97 |
99 |
HTTPMethod = cowboy_req:method(Req), |
98 |
99 |
AuthDetails = mongoose_api_common:get_auth_details(Req), |
99 |
99 |
case AuthDetails of |
100 |
|
undefined -> |
101 |
2 |
mongoose_api_common:make_unauthorized_response(Req, State); |
102 |
|
{AuthMethod, User, Password} -> |
103 |
97 |
authorize(AuthMethod, User, Password, HTTPMethod, Req, State) |
104 |
|
end. |
105 |
|
|
106 |
|
authorize(AuthMethod, User, Password, HTTPMethod, Req, State) -> |
107 |
97 |
MaybeJID = jid:from_binary(User), |
108 |
97 |
case do_authorize(AuthMethod, MaybeJID, Password, HTTPMethod) of |
109 |
|
noauth -> |
110 |
:-( |
{true, Req, State}; |
111 |
|
{true, Creds} -> |
112 |
97 |
{true, Req, State#{user => User, jid => MaybeJID, creds => Creds}}; |
113 |
|
false -> |
114 |
:-( |
mongoose_api_common:make_unauthorized_response(Req, State) |
115 |
|
end. |
116 |
|
|
117 |
|
do_authorize(AuthMethod, MaybeJID, Password, HTTPMethod) -> |
118 |
97 |
case is_noauth_http_method(HTTPMethod) of |
119 |
|
true -> |
120 |
:-( |
noauth; |
121 |
|
false -> |
122 |
97 |
mongoose_api_common:is_known_auth_method(AuthMethod) andalso |
123 |
97 |
mongoose_api_common:check_password(MaybeJID, Password) |
124 |
|
end. |
125 |
|
|
126 |
|
% Constraints |
127 |
:-( |
is_noauth_http_method(<<"OPTIONS">>) -> true; |
128 |
97 |
is_noauth_http_method(_) -> false. |
129 |
|
|
130 |
|
%% ------------------------------------------------------------------- |
131 |
|
%% @doc |
132 |
|
%% Decode JSON binary into map |
133 |
|
%% @end |
134 |
|
%% ------------------------------------------------------------------- |
135 |
|
-spec json_to_map(JsonBin :: binary()) -> {ok, Map :: maps:map()} | {error, invalid_json}. |
136 |
|
|
137 |
|
json_to_map(JsonBin) -> |
138 |
60 |
case catch jiffy:decode(JsonBin, [return_maps]) of |
139 |
|
Map when is_map(Map) -> |
140 |
59 |
{ok, Map}; |
141 |
|
_ -> |
142 |
1 |
{error, invalid_json} |
143 |
|
end. |