• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1212

18 Nov 2025 12:37PM UTC coverage: 33.784% (+0.003%) from 33.781%
1212

push

github

badlop
mod_conversejs: Improve link to conversejs in WebAdmin (#4495)

Until now, the WebAdmin menu included a link to the first request handler
with mod_conversejs that the admin configured in ejabberd.yml
That link included the authentication credentials hashed as URI arguments
if using HTTPS. Then process/2 extracted those arguments and passed them
as autologin options to Converse.

From now, mod_conversejs automatically adds a request_handler nested in
webadmin subpath. The webadmin menu links to that converse URI; this allows
to access the HTTP auth credentials, no need to explicitly pass them.
process/2 extracts this HTTP auth and passes autologin options to Converse.
Now scram password storage is supported too.

This minimum configuration allows WebAdmin to access Converse:

listen:
  -
    port: 5443
    module: ejabberd_http
    tls: true
    request_handlers:
      /admin: ejabberd_web_admin
      /ws: ejabberd_http_ws
modules:
  mod_conversejs:
    conversejs_resources: "/home/conversejs/12.0.0/dist"

0 of 12 new or added lines in 1 file covered. (0.0%)

11290 existing lines in 174 files now uncovered.

15515 of 45924 relevant lines covered (33.78%)

1277.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.53
/src/extauth.erl
1
%%%-------------------------------------------------------------------
2
%%% Created : 7 May 2018 by Evgeny Khramtsov <ekhramtsov@process-one.net>
3
%%%
4
%%%
5
%%% ejabberd, Copyright (C) 2002-2025   ProcessOne
6
%%%
7
%%% This program is free software; you can redistribute it and/or
8
%%% modify it under the terms of the GNU General Public License as
9
%%% published by the Free Software Foundation; either version 2 of the
10
%%% License, or (at your option) any later version.
11
%%%
12
%%% This program is distributed in the hope that it will be useful,
13
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
14
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
%%% General Public License for more details.
16
%%%
17
%%% You should have received a copy of the GNU General Public License along
18
%%% with this program; if not, write to the Free Software Foundation, Inc.,
19
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
%%%
21
%%%-------------------------------------------------------------------
22
-module(extauth).
23

24
-ifndef(GEN_SERVER).
25
-define(GEN_SERVER, gen_server).
26
-endif.
27
-behaviour(?GEN_SERVER).
28

29
-define(CALL_TIMEOUT, timer:seconds(30)).
30

31
%% API
32
-export([start/1, stop/1, reload/1, start_link/2]).
33
-export([check_password/3, set_password/3, try_register/3, remove_user/2,
34
         remove_user/3, user_exists/2, check_certificate/3]).
35
-export([prog_name/1, pool_name/1, worker_name/2, pool_size/1]).
36
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
37
         terminate/2, code_change/3]).
38

39
-include("logger.hrl").
40

41
-record(state, {port :: port(),
42
                prog :: string(),
43
                start_time :: integer(),
44
                os_pid :: integer() | undefined}).
45

46
%%%===================================================================
47
%%% API
48
%%%===================================================================
49
start(Host) ->
UNCOV
50
    extauth_sup:start(Host).
2✔
51

52
stop(Host) ->
53
    extauth_sup:stop(Host).
×
54

55
reload(Host) ->
56
    extauth_sup:reload(Host).
×
57

58
start_link(Name, Prog) ->
UNCOV
59
    ?GEN_SERVER:start_link({local, Name}, ?MODULE, [Prog], []).
8✔
60

61
check_password(User, Server, Password) ->
UNCOV
62
    call_port(Server, [<<"auth">>, User, Server, Password]).
4✔
63

64
check_certificate(User, Server, Certificate) ->
65
    call_port(Server, [<<"certauth">>, User, Server, Certificate]).
×
66

67
user_exists(User, Server) ->
68
    call_port(Server, [<<"isuser">>, User, Server]).
×
69

70
set_password(User, Server, Password) ->
71
    call_port(Server, [<<"setpass">>, User, Server, Password]).
×
72

73
try_register(User, Server, Password) ->
74
    call_port(Server, [<<"tryregister">>, User, Server, Password]).
×
75

76
remove_user(User, Server) ->
UNCOV
77
    call_port(Server, [<<"removeuser">>, User, Server]).
2✔
78

79
remove_user(User, Server, Password) ->
80
    call_port(Server, [<<"removeuser3">>, User, Server, Password]).
×
81

82
-spec prog_name(binary()) -> string() | undefined.
83
prog_name(Host) ->
UNCOV
84
    ejabberd_option:extauth_program(Host).
2✔
85

86
-spec pool_name(binary()) -> atom().
87
pool_name(Host) ->
UNCOV
88
    case ejabberd_option:extauth_pool_name(Host) of
8✔
89
        undefined ->
UNCOV
90
            list_to_atom("extauth_pool_" ++ binary_to_list(Host));
8✔
91
        Name ->
92
            list_to_atom("extauth_pool_" ++ binary_to_list(Name))
×
93
    end.
94

95
-spec worker_name(atom(), integer()) -> atom().
96
worker_name(Pool, N) ->
UNCOV
97
    list_to_atom(atom_to_list(Pool) ++ "_" ++ integer_to_list(N)).
14✔
98

99
-spec pool_size(binary()) -> pos_integer().
100
pool_size(Host) ->
UNCOV
101
    case ejabberd_option:extauth_pool_size(Host) of
8✔
UNCOV
102
        undefined -> misc:logical_processors();
8✔
103
        Size ->
104
            Size
×
105
    end.
106

107
%%%===================================================================
108
%%% gen_server callbacks
109
%%%===================================================================
110
init([Prog]) ->
UNCOV
111
    process_flag(trap_exit, true),
8✔
UNCOV
112
    {Port, OSPid} = start_port(Prog),
8✔
UNCOV
113
    Time = curr_time(),
8✔
UNCOV
114
    {ok, #state{port = Port, start_time = Time,
8✔
115
                prog = Prog, os_pid = OSPid}}.
116

117
handle_call({cmd, Cmd, EndTime}, _From, State) ->
UNCOV
118
    Timeout = EndTime - curr_time(),
6✔
UNCOV
119
    if Timeout > 0 ->
6✔
UNCOV
120
            Port = State#state.port,
6✔
UNCOV
121
            port_command(Port, Cmd),
6✔
UNCOV
122
            receive
6✔
123
                {Port, {data, [0, N] = Data}} when N == 0; N == 1 ->
UNCOV
124
                    ?DEBUG("Received response from external authentication "
6✔
UNCOV
125
                           "program: ~p", [Data]),
6✔
UNCOV
126
                    {reply, decode_bool(N), State};
6✔
127
                {Port, Data} ->
128
                    ?ERROR_MSG("Received unexpected response from external "
×
129
                               "authentication program '~ts': ~p "
130
                               "(port = ~p, pid = ~w)",
131
                               [State#state.prog, Data, Port, State#state.os_pid]),
×
132
                    {reply, {error, unexpected_response}, State};
×
133
                {'EXIT', Port, Reason} ->
134
                    handle_info({'EXIT', Port, Reason}, State)
×
135
            after Timeout ->
136
                    {stop, normal, State}
×
137
            end;
138
       true ->
139
            {noreply, State}
×
140
    end.
141

142
handle_cast(_Msg, State) ->
143
    {noreply, State}.
×
144

145
handle_info({'EXIT', Port, _Reason}, #state{port = Port,
146
                                            start_time = Time} = State) ->
147
    case curr_time() - Time of
×
148
        Diff when Diff < 1000 ->
149
            ?ERROR_MSG("Failed to start external authentication program '~ts'",
×
150
                       [State#state.prog]),
×
151
            {stop, normal, State};
×
152
        _ ->
153
            ?ERROR_MSG("External authentication program '~ts' has terminated "
×
154
                       "unexpectedly (pid=~w), restarting via supervisor...",
155
                       [State#state.prog, State#state.os_pid]),
×
156
            {stop, normal, State}
×
157
    end;
158
handle_info(Info, State) ->
159
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
×
160
    {noreply, State}.
×
161

162
terminate(_Reason, State) ->
UNCOV
163
    catch port_close(State#state.port),
8✔
UNCOV
164
    ok.
8✔
165

166
code_change(_OldVsn, State, _Extra) ->
167
    {ok, State}.
×
168

169
%%%===================================================================
170
%%% Internal functions
171
%%%===================================================================
172
-spec curr_time() -> non_neg_integer().
173
curr_time() ->
UNCOV
174
    erlang:monotonic_time(millisecond).
14✔
175

176
-spec start_port(string()) -> {port(), integer() | undefined}.
177
start_port(Path) ->
UNCOV
178
    Port = open_port({spawn, Path}, [{packet, 2}]),
8✔
UNCOV
179
    link(Port),
8✔
UNCOV
180
    case erlang:port_info(Port, os_pid) of
8✔
181
        {os_pid, OSPid} ->
UNCOV
182
            {Port, OSPid};
8✔
183
        undefined ->
184
            {Port, undefined}
×
185
    end.
186

187
call_port(Server, Args) ->
UNCOV
188
    call_port(Server, Args, ?CALL_TIMEOUT).
6✔
189

190
call_port(Server, Args, Timeout) ->
UNCOV
191
    StartTime = erlang:monotonic_time(millisecond),
6✔
UNCOV
192
    Pool = pool_name(Server),
6✔
UNCOV
193
    PoolSize = pool_size(Server),
6✔
UNCOV
194
    I = p1_rand:round_robin(PoolSize),
6✔
UNCOV
195
    Cmd = str:join(Args, <<":">>),
6✔
UNCOV
196
    do_call(Cmd, I, I + PoolSize, Pool, PoolSize,
6✔
197
            StartTime + Timeout, StartTime).
198

199
do_call(_, Max, Max, _, _, _, _) ->
200
    {error, disconnected};
×
201
do_call(Cmd, I, Max, Pool, PoolSize, EndTime, CurrTime) ->
UNCOV
202
    Timeout = EndTime - CurrTime,
6✔
UNCOV
203
    if Timeout > 0 ->
6✔
UNCOV
204
            Proc = worker_name(Pool, (I rem PoolSize) + 1),
6✔
UNCOV
205
            try ?GEN_SERVER:call(Proc, {cmd, Cmd, EndTime}, Timeout)
6✔
206
            catch exit:{timeout, {?GEN_SERVER, call, _}} ->
207
                    {error, timeout};
×
208
                  exit:{_, {?GEN_SERVER, call, _}} ->
209
                    do_call(Cmd, I+1, Max, Pool, PoolSize, EndTime, curr_time())
×
210
            end;
211
       true ->
212
            {error, timeout}
×
213
    end.
214

UNCOV
215
decode_bool(0) -> false;
2✔
UNCOV
216
decode_bool(1) -> true.
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc