./ct_report/coverage/mongoose_client_api.COVER.html

1 -module(mongoose_client_api).
2
3 -behaviour(mongoose_http_handler).
4
5 %% mongoose_http_handler callbacks
6 -export([config_spec/0, routes/1]).
7
8 -export([init/2]).
9 -export([content_types_provided/2]).
10 -export([is_authorized/2]).
11 -export([options/2]).
12 -export([allowed_methods/2]).
13 -export([to_json/2]).
14 -export([bad_request/2]).
15 -export([bad_request/3]).
16 -export([forbidden_request/2]).
17 -export([forbidden_request/3]).
18 -export([json_to_map/1]).
19
20 -ignore_xref([allowed_methods/2, content_types_provided/2, forbidden_request/3,
21 options/2, to_json/2]).
22
23 -include("mongoose.hrl").
24 -include("mongoose_config_spec.hrl").
25
26 -type handler_options() :: #{path := string(), handlers := [module()], docs := boolean(),
27 atom() => any()}.
28
29 %% mongoose_http_handler callbacks
30
31 -spec config_spec() -> mongoose_config_spec:config_section().
32 config_spec() ->
33 83 HandlerModules = [Module || {_, Module, _} <- api_paths()],
34 83 #section{items = #{<<"handlers">> => #list{items = #option{type = atom,
35 validate = {enum, HandlerModules}},
36 validate = unique},
37 <<"docs">> => #option{type = boolean}},
38 defaults = #{<<"handlers">> => HandlerModules,
39 <<"docs">> => true}}.
40
41 -spec routes(handler_options()) -> mongoose_http_handler:routes().
42 routes(Opts = #{path := BasePath}) ->
43 1085 [{[BasePath, Path], Module, ModuleOpts}
44 155 || {Path, Module, ModuleOpts} <- api_paths(Opts)] ++ api_doc_paths(Opts).
45
46 api_paths(#{handlers := HandlerModules}) ->
47 155 lists:filter(fun({_, Module, _}) -> lists:member(Module, HandlerModules) end, api_paths()).
48
49 api_paths() ->
50 238 [{"/sse", lasse_handler, #{module => mongoose_client_api_sse}},
51 {"/messages/[:with]", mongoose_client_api_messages, #{}},
52 {"/contacts/[:jid]", mongoose_client_api_contacts, #{}},
53 {"/rooms/[:id]", mongoose_client_api_rooms, #{}},
54 {"/rooms/[:id]/config", mongoose_client_api_rooms_config, #{}},
55 {"/rooms/:id/users/[:user]", mongoose_client_api_rooms_users, #{}},
56 {"/rooms/[:id]/messages", mongoose_client_api_rooms_messages, #{}}].
57
58 api_doc_paths(#{docs := true}) ->
59 155 [{"/api-docs", cowboy_swagger_redirect_handler, #{}},
60 {"/api-docs/swagger.json", cowboy_swagger_json_handler, #{}},
61 {"/api-docs/[...]", cowboy_static, {priv_dir, cowboy_swagger, "swagger",
62 [{mimetypes, cow_mimetypes, all}]}
63 }];
64 api_doc_paths(#{docs := false}) ->
65
:-(
[].
66
67 init(Req, _Opts) ->
68 99 State = #{},
69 99 case cowboy_req:header(<<"origin">>, Req) of
70 undefined ->
71 99 {cowboy_rest, Req, State};
72 Origin ->
73
:-(
Req1 = set_cors_headers(Origin, Req),
74
:-(
{cowboy_rest, Req1, State}
75 end.
76
77 set_cors_headers(Origin, Req) ->
78 %% set CORS headers
79
:-(
Headers = [{<<"access-control-allow-origin">>, Origin},
80 {<<"access-control-allow-methods">>, <<"GET, OPTIONS">>},
81 {<<"access-control-allow-credentials">>, <<"true">>},
82 {<<"access-control-allow-headers">>, <<"authorization, content-type">>}
83 ],
84
85
:-(
lists:foldl(fun set_cors_header/2, Req, Headers).
86
87 set_cors_header({Header, Value}, Req) ->
88
:-(
cowboy_req:set_resp_header(Header, Value, Req).
89
90 allowed_methods(Req, State) ->
91
:-(
{[<<"OPTIONS">>, <<"GET">>], Req, State}.
92
93 content_types_provided(Req, State) ->
94
:-(
{[
95 {{<<"application">>, <<"json">>, '*'}, to_json}
96 ], Req, State}.
97
98 options(Req, State) ->
99
:-(
{ok, Req, State}.
100
101 to_json(Req, User) ->
102
:-(
{<<"{}">>, Req, User}.
103
104 bad_request(Req, State) ->
105 3 bad_request(Req, <<"Bad request. The details are unknown.">>, State).
106
107 bad_request(Req, Reason, State) ->
108 5 reply(400, Req, Reason, State).
109
110 forbidden_request(Req, State) ->
111 3 forbidden_request(Req, <<>>, State).
112
113 forbidden_request(Req, Reason, State) ->
114 3 reply(403, Req, Reason, State).
115
116 reply(StatusCode, Req, Body, State) ->
117 8 maybe_report_error(StatusCode, Req, Body),
118 8 Req1 = set_resp_body_if_missing(Body, Req),
119 8 Req2 = cowboy_req:reply(StatusCode, Req1),
120 8 {stop, Req2, State#{was_replied => true}}.
121
122 set_resp_body_if_missing(Body, Req) ->
123 8 case cowboy_req:has_resp_body(Req) of
124 true ->
125 3 Req;
126 false ->
127 5 cowboy_req:set_resp_body(Body, Req)
128 end.
129
130 maybe_report_error(StatusCode, Req, Body) when StatusCode >= 400 ->
131 8 ?LOG_WARNING(#{what => reply_error,
132 stacktrace => element(2, erlang:process_info(self(), current_stacktrace)),
133
:-(
code => StatusCode, req => Req, reply_body => Body});
134 maybe_report_error(_StatusCode, _Req, _Body) ->
135
:-(
ok.
136
137 %%--------------------------------------------------------------------
138 %% Authorization
139 %%--------------------------------------------------------------------
140
141 % @doc cowboy callback
142 is_authorized(Req, State) ->
143 99 HTTPMethod = cowboy_req:method(Req),
144 99 AuthDetails = mongoose_api_common:get_auth_details(Req),
145 99 case AuthDetails of
146 undefined ->
147 2 mongoose_api_common:make_unauthorized_response(Req, State);
148 {AuthMethod, User, Password} ->
149 97 authorize(AuthMethod, User, Password, HTTPMethod, Req, State)
150 end.
151
152 authorize(AuthMethod, User, Password, HTTPMethod, Req, State) ->
153 97 MaybeJID = jid:from_binary(User),
154 97 case do_authorize(AuthMethod, MaybeJID, Password, HTTPMethod) of
155 noauth ->
156
:-(
{true, Req, State};
157 {true, Creds} ->
158 97 {true, Req, State#{user => User, jid => MaybeJID, creds => Creds}};
159 false ->
160
:-(
mongoose_api_common:make_unauthorized_response(Req, State)
161 end.
162
163 do_authorize(AuthMethod, MaybeJID, Password, HTTPMethod) ->
164 97 case is_noauth_http_method(HTTPMethod) of
165 true ->
166
:-(
noauth;
167 false ->
168 97 mongoose_api_common:is_known_auth_method(AuthMethod) andalso
169 97 mongoose_api_common:check_password(MaybeJID, Password)
170 end.
171
172 % Constraints
173
:-(
is_noauth_http_method(<<"OPTIONS">>) -> true;
174 97 is_noauth_http_method(_) -> false.
175
176 %% -------------------------------------------------------------------
177 %% @doc
178 %% Decode JSON binary into map
179 %% @end
180 %% -------------------------------------------------------------------
181 -spec json_to_map(JsonBin :: binary()) -> {ok, Map :: maps:map()} | {error, invalid_json}.
182
183 json_to_map(JsonBin) ->
184 60 case catch jiffy:decode(JsonBin, [return_maps]) of
185 Map when is_map(Map) ->
186 59 {ok, Map};
187 _ ->
188 1 {error, invalid_json}
189 end.
Line Hits Source