./ct_report/coverage/mongoose_account_api.COVER.html

1 %% @doc Provide an interface for frontends (like graphql or ctl) to manage accounts.
2 -module(mongoose_account_api).
3
4 -export([list_users/1,
5 count_users/1,
6 register_user/3,
7 register_generated_user/2,
8 unregister_user/1,
9 unregister_user/2,
10 ban_account/2,
11 ban_account/3,
12 change_password/2,
13 change_password/3,
14 check_account/1,
15 check_account/2,
16 check_password/2,
17 check_password/3,
18 check_password_hash/3,
19 check_password_hash/4]).
20
21 -type register_result() :: {ok | exists | invalid_jid | cannot_register, iolist()}.
22
23 -type unregister_result() :: {ok | not_allowed | invalid_jid, string()}.
24
25 -type change_password_result() :: {ok | empty_password | not_allowed | invalid_jid, string()}.
26
27 -type check_password_result() :: {ok | incorrect | user_does_not_exist, string()}.
28
29 -type check_password_hash_result() :: {ok | incorrect | wrong_user | wrong_method, string()}.
30
31 -type check_account_result() :: {ok | user_does_not_exist, string()}.
32
33 -export_type([register_result/0,
34 unregister_result/0,
35 change_password_result/0,
36 check_password_result/0,
37 check_password_hash_result/0,
38 check_account_result/0]).
39
40 %% API
41
42 -spec list_users(jid:server()) -> [jid:literal_jid()].
43 list_users(Domain) ->
44 9 Users = ejabberd_auth:get_vh_registered_users(Domain),
45 9 SUsers = lists:sort(Users),
46 9 [jid:to_binary(US) || US <- SUsers].
47
48 -spec count_users(jid:server()) -> integer().
49 count_users(Domain) ->
50 2 ejabberd_auth:get_vh_registered_users_number(Domain).
51
52 -spec register_generated_user(jid:server(), binary()) -> {register_result(), jid:literal_jid()}.
53 register_generated_user(Host, Password) ->
54 2 Username = generate_username(),
55 2 JID = jid:to_binary({Username, Host}),
56 2 {register_user(Username, Host, Password), JID}.
57
58 -spec register_user(jid:user(), jid:server(), binary()) -> register_result().
59 register_user(User, Host, Password) ->
60 2824 JID = jid:make(User, Host, <<>>),
61 2824 case ejabberd_auth:try_register(JID, Password) of
62 {error, exists} ->
63 7 String =
64 io_lib:format("User ~s already registered at node ~p",
65 [jid:to_binary(JID), node()]),
66 7 {exists, String};
67 {error, invalid_jid} ->
68 1 String = io_lib:format("Invalid JID ~s@~s", [User, Host]),
69 1 {invalid_jid, String};
70 {error, Reason} ->
71
:-(
String =
72 io_lib:format("Can't register user ~s at node ~p: ~p",
73 [jid:to_binary(JID), node(), Reason]),
74
:-(
{cannot_register, String};
75 _ ->
76 2816 {ok, io_lib:format("User ~s successfully registered", [jid:to_binary(JID)])}
77 end.
78
79 -spec unregister_user(jid:user(), jid:server()) -> unregister_result().
80 unregister_user(User, Host) ->
81 2851 JID = jid:make(User, Host, <<>>),
82 2851 unregister_user(JID).
83
84 -spec unregister_user(jid:jid()) -> unregister_result().
85 unregister_user(JID) ->
86 2854 case ejabberd_auth:remove_user(JID) of
87 ok ->
88 2847 {ok, io_lib:format("User ~s successfully unregistered", [jid:to_binary(JID)])};
89 error ->
90 1 {invalid_jid, "Invalid JID"};
91 {error, not_allowed} ->
92 6 {not_allowed, "User does not exist or you are not authorised properly"}
93 end.
94
95 -spec change_password(jid:user(), jid:server(), binary()) -> change_password_result().
96 change_password(User, Host, Password) ->
97 7 JID = jid:make(User, Host, <<>>),
98 7 change_password(JID, Password).
99
100 -spec change_password(jid:jid(), binary()) -> change_password_result().
101 change_password(JID, Password) ->
102 12 Result = ejabberd_auth:set_password(JID, Password),
103 12 format_change_password(Result).
104
105 -spec check_account(jid:user(), jid:server()) -> check_account_result().
106 check_account(User, Host) ->
107 13 JID = jid:make(User, Host, <<>>),
108 13 check_account(JID).
109
110 -spec check_account(jid:jid()) -> check_account_result().
111 check_account(JID) ->
112 15 case ejabberd_auth:does_user_exist(JID) of
113 true ->
114 8 {ok, io_lib:format("User ~s exists", [jid:to_binary(JID)])};
115 false ->
116 7 {user_does_not_exist, io_lib:format("User ~s does not exist", [jid:to_binary(JID)])}
117 end.
118
119 -spec check_password(jid:user(), jid:server(), binary()) -> check_password_result().
120 check_password(User, Host, Password) ->
121 2 JID = jid:make(User, Host, <<>>),
122 2 check_password(JID, Password).
123
124 -spec check_password(jid:jid(), binary()) -> check_password_result().
125 check_password(JID, Password) ->
126 5 case ejabberd_auth:does_user_exist(JID) of
127 true ->
128 4 case ejabberd_auth:check_password(JID, Password) of
129 true ->
130 2 {ok, io_lib:format("Password '~s' for user ~s is correct",
131 [Password, jid:to_binary(JID)])};
132 false ->
133 2 {incorrect, io_lib:format("Password '~s' for user ~s is incorrect",
134 [Password, jid:to_binary(JID)])}
135 end;
136 false ->
137 1 {user_does_not_exist,
138 io_lib:format("Password ~s for user ~s is incorrect because this user does not"
139 " exist", [Password, jid:to_binary(JID)])}
140 end.
141
142 -spec check_password_hash(jid:user(), jid:server(), string(), string()) ->
143 check_password_hash_result().
144 check_password_hash(User, Host, PasswordHash, HashMethod) ->
145 3 JID = jid:make(User, Host, <<>>),
146 3 check_password_hash(JID, PasswordHash, HashMethod).
147
148 -spec check_password_hash(jid:jid(), string(), string()) -> check_password_hash_result().
149 check_password_hash(JID, PasswordHash, HashMethod) ->
150 8 AccountPass = ejabberd_auth:get_password_s(JID),
151 8 AccountPassHash = case HashMethod of
152 6 "md5" -> get_md5(AccountPass);
153 1 "sha" -> get_sha(AccountPass);
154 1 _ -> undefined
155 end,
156 8 case {AccountPass, AccountPassHash} of
157 {<<>>, _} ->
158 2 {wrong_user, "User does not exist or using SCRAM password"};
159 {_, undefined} ->
160 1 Msg = io_lib:format("Given hash method `~s` is not supported. Try `md5` or `sha`",
161 [HashMethod]),
162 1 {wrong_method, Msg};
163 {_, PasswordHash} ->
164 3 {ok, "Password hash is correct"};
165 _->
166 2 {incorrect, "Password hash is incorrect"}
167 end.
168
169 -spec ban_account(jid:user(), jid:server(), binary()) -> change_password_result().
170 ban_account(User, Host, ReasonText) ->
171 1 JID = jid:make(User, Host, <<>>),
172 1 ban_account(JID, ReasonText).
173
174 -spec ban_account(jid:jid(), binary()) -> change_password_result().
175 ban_account(JID, ReasonText) ->
176 3 Reason = mongoose_session_api:prepare_reason(ReasonText),
177 3 mongoose_session_api:kick_sessions(JID, Reason),
178 3 case set_random_password(JID, Reason) of
179 ok ->
180 2 {ok, io_lib:format("User ~s successfully banned with reason: ~s",
181 [jid:to_binary(JID), ReasonText])};
182 ErrResult ->
183 1 format_change_password(ErrResult)
184 end.
185
186 %% Internal
187
188 format_change_password(ok) ->
189 7 {ok, "Password changed"};
190 format_change_password({error, empty_password}) ->
191 3 {empty_password, "Empty password"};
192 format_change_password({error, not_allowed}) ->
193 2 {not_allowed, "Password change not allowed"};
194 format_change_password({error, invalid_jid}) ->
195 1 {invalid_jid, "Invalid JID"}.
196
197 -spec set_random_password(JID, Reason) -> Result when
198 JID :: jid:jid(),
199 Reason :: binary(),
200 Result :: ok | {error, any()}.
201 set_random_password(JID, Reason) ->
202 3 NewPass = build_random_password(Reason),
203 3 ejabberd_auth:set_password(JID, NewPass).
204
205 -spec build_random_password(Reason :: binary()) -> binary().
206 build_random_password(Reason) ->
207 3 {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(),
208 3 Date = iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0wZ",
209 [Year, Month, Day, Hour, Minute, Second])),
210 3 RandomString = mongoose_bin:gen_from_crypto(),
211 3 <<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>.
212
213 -spec generate_username() -> binary().
214 generate_username() ->
215 2 mongoose_bin:join([mongoose_bin:gen_from_timestamp(),
216 mongoose_bin:gen_from_crypto()], $-).
217
218 -spec get_md5(binary()) -> string().
219 get_md5(AccountPass) ->
220 6 lists:flatten([io_lib:format("~.16B", [X])
221 6 || X <- binary_to_list(crypto:hash(md5, AccountPass))]).
222
223 -spec get_sha(binary()) -> string().
224 get_sha(AccountPass) ->
225 1 lists:flatten([io_lib:format("~.16B", [X])
226 1 || X <- binary_to_list(crypto:hash(sha, AccountPass))]).
Line Hits Source