• 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

44.12
/src/mod_push_mnesia.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_push_mnesia.erl
3
%%% Author  : Holger Weiss <holger@zedat.fu-berlin.de>
4
%%% Purpose : Mnesia backend for Push Notifications (XEP-0357)
5
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2017-2025 ProcessOne
9
%%%
10
%%% This program is free software; you can redistribute it and/or
11
%%% modify it under the terms of the GNU General Public License as
12
%%% published by the Free Software Foundation; either version 2 of the
13
%%% License, or (at your option) any later version.
14
%%%
15
%%% This program is distributed in the hope that it will be useful,
16
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
%%% General Public License for more details.
19
%%%
20
%%% You should have received a copy of the GNU General Public License along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25

26
-module(mod_push_mnesia).
27
-author('holger@zedat.fu-berlin.de').
28

29
-behaviour(mod_push).
30

31
%% API
32
-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
33
         lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
34
         delete_session/3, delete_old_sessions/2, transform/1]).
35

36
-include_lib("stdlib/include/ms_transform.hrl").
37
-include("logger.hrl").
38
-include_lib("xmpp/include/xmpp.hrl").
39
-include("mod_push.hrl").
40

41
%%%-------------------------------------------------------------------
42
%%% API
43
%%%-------------------------------------------------------------------
44
init(_Host, _Opts) ->
UNCOV
45
    ejabberd_mnesia:create(?MODULE, push_session,
2✔
46
                           [{disc_only_copies, [node()]},
47
                            {type, bag},
48
                            {attributes, record_info(fields, push_session)}]).
49

50
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
UNCOV
51
    US = {LUser, LServer},
4✔
UNCOV
52
    PushLJID = jid:tolower(PushJID),
4✔
UNCOV
53
    MaxSessions = ejabberd_sm:get_max_user_sessions(LUser, LServer),
4✔
UNCOV
54
    F = fun() ->
4✔
UNCOV
55
                enforce_max_sessions(US, MaxSessions),
4✔
UNCOV
56
                mnesia:write(#push_session{us = US,
4✔
57
                                           timestamp = TS,
58
                                           service = PushLJID,
59
                                           node = Node,
60
                                           xml = encode_xdata(XData)})
61
        end,
UNCOV
62
    case mnesia:transaction(F) of
4✔
63
        {atomic, ok} ->
UNCOV
64
            {ok, {TS, PushLJID, Node, XData}};
4✔
65
        {aborted, E} ->
66
            ?ERROR_MSG("Cannot store push session for ~ts@~ts: ~p",
×
67
                       [LUser, LServer, E]),
×
68
            {error, db_failure}
×
69
    end.
70

71
lookup_session(LUser, LServer, PushJID, Node) ->
UNCOV
72
    PushLJID = jid:tolower(PushJID),
6✔
UNCOV
73
    MatchSpec = ets:fun2ms(
6✔
74
                  fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
75
                        when U == LUser,
76
                             S == LServer,
77
                             P == PushLJID,
78
                             N == Node ->
79
                          Rec
80
                  end),
UNCOV
81
    case mnesia:dirty_select(push_session, MatchSpec) of
6✔
82
        [#push_session{timestamp = TS, xml = El}] ->
UNCOV
83
            {ok, {TS, PushLJID, Node, decode_xdata(El)}};
4✔
84
        [] ->
UNCOV
85
            ?DEBUG("No push session found for ~ts@~ts (~p, ~ts)",
2✔
UNCOV
86
                   [LUser, LServer, PushJID, Node]),
2✔
UNCOV
87
            {error, notfound}
2✔
88
    end.
89

90
lookup_session(LUser, LServer, TS) ->
91
    MatchSpec = ets:fun2ms(
×
92
                  fun(#push_session{us = {U, S}, timestamp = T} = Rec)
93
                        when U == LUser,
94
                             S == LServer,
95
                             T == TS ->
96
                          Rec
97
                  end),
98
    case mnesia:dirty_select(push_session, MatchSpec) of
×
99
        [#push_session{service = PushLJID, node = Node, xml = El}] ->
100
            {ok, {TS, PushLJID, Node, decode_xdata(El)}};
×
101
        [] ->
102
            ?DEBUG("No push session found for ~ts@~ts (~p)",
×
103
                   [LUser, LServer, TS]),
×
104
            {error, notfound}
×
105
    end.
106

107
lookup_sessions(LUser, LServer, PushJID) ->
108
    PushLJID = jid:tolower(PushJID),
×
109
    MatchSpec = ets:fun2ms(
×
110
                  fun(#push_session{us = {U, S}, service = P} = Rec)
111
                        when U == LUser,
112
                             S == LServer,
113
                             P == PushLJID ->
114
                          Rec
115
                  end),
116
    Records = mnesia:dirty_select(push_session, MatchSpec),
×
117
    {ok, records_to_sessions(Records)}.
×
118

119
lookup_sessions(LUser, LServer) ->
UNCOV
120
    Records = mnesia:dirty_read(push_session, {LUser, LServer}),
14✔
UNCOV
121
    {ok, records_to_sessions(Records)}.
14✔
122

123
lookup_sessions(LServer) ->
124
    MatchSpec = ets:fun2ms(
×
125
                  fun(#push_session{us = {_U, S}} = Rec)
126
                        when S == LServer ->
127
                          Rec
128
                  end),
129
    Records = mnesia:dirty_select(push_session, MatchSpec),
×
130
    {ok, records_to_sessions(Records)}.
×
131

132
delete_session(LUser, LServer, TS) ->
UNCOV
133
    MatchSpec = ets:fun2ms(
4✔
134
                  fun(#push_session{us = {U, S}, timestamp = T} = Rec)
135
                        when U == LUser,
136
                             S == LServer,
137
                             T == TS ->
138
                          Rec
139
                  end),
UNCOV
140
    F = fun() ->
4✔
UNCOV
141
                Recs = mnesia:select(push_session, MatchSpec),
4✔
UNCOV
142
                lists:foreach(fun mnesia:delete_object/1, Recs)
4✔
143
        end,
UNCOV
144
    case mnesia:transaction(F) of
4✔
145
        {atomic, ok} ->
UNCOV
146
            ok;
4✔
147
        {aborted, E} ->
148
            ?ERROR_MSG("Cannot delete push session of ~ts@~ts: ~p",
×
149
                       [LUser, LServer, E]),
×
150
            {error, db_failure}
×
151
    end.
152

153
delete_old_sessions(_LServer, Time) ->
154
    DelIfOld = fun(#push_session{timestamp = T} = Rec, ok) when T < Time ->
×
155
                       mnesia:delete_object(Rec);
×
156
                  (_Rec, ok) ->
157
                       ok
×
158
               end,
159
    F = fun() ->
×
160
                mnesia:foldl(DelIfOld, ok, push_session)
×
161
        end,
162
    case mnesia:transaction(F) of
×
163
        {atomic, ok} ->
164
            ok;
×
165
        {aborted, E} ->
166
            ?ERROR_MSG("Cannot delete old push sessions: ~p", [E]),
×
167
            {error, db_failure}
×
168
    end.
169

170
transform({push_session, US, TS, Service, Node, XData}) ->
171
    ?INFO_MSG("Transforming push_session Mnesia table", []),
×
172
    #push_session{us = US, timestamp = TS, service = Service,
×
173
                  node = Node, xml = encode_xdata(XData)}.
174

175
%%--------------------------------------------------------------------
176
%% Internal functions.
177
%%--------------------------------------------------------------------
178
-spec enforce_max_sessions({binary(), binary()}, non_neg_integer() | infinity)
179
      -> ok.
180
enforce_max_sessions(_US, infinity) ->
181
    ok;
×
182
enforce_max_sessions({U, S} = US, MaxSessions) ->
UNCOV
183
    case mnesia:wread({push_session, US}) of
4✔
184
        Recs when length(Recs) >= MaxSessions ->
185
            Recs1 = lists:sort(fun(#push_session{timestamp = TS1},
×
186
                                   #push_session{timestamp = TS2}) ->
187
                                       TS1 >= TS2
×
188
                               end, Recs),
189
            OldRecs = lists:nthtail(MaxSessions - 1, Recs1),
×
190
            ?INFO_MSG("Disabling old push session(s) of ~ts@~ts", [U, S]),
×
191
            lists:foreach(fun(Rec) -> mnesia:delete_object(Rec) end, OldRecs);
×
192
        _ ->
UNCOV
193
            ok
4✔
194
    end.
195

196
decode_xdata(undefined) ->
197
    undefined;
×
198
decode_xdata(El) ->
UNCOV
199
    xmpp:decode(El).
6✔
200

201
encode_xdata(undefined) ->
202
    undefined;
×
203
encode_xdata(XData) ->
UNCOV
204
    xmpp:encode(XData).
4✔
205

206
records_to_sessions(Records) ->
UNCOV
207
    [{TS, PushLJID, Node, decode_xdata(El)}
14✔
208
     || #push_session{timestamp = TS,
209
                      service = PushLJID,
210
                      node = Node,
UNCOV
211
                      xml = El} <- Records].
14✔
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