1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
local st = require "util.stanza";
local jid_split = require "util.jid".split;
local jid_bare = require "util.jid".bare;
local rostermanager = require "core.rostermanager";
local require_encryption = module:get_option_boolean("c2s_require_encryption",
module:get_option_boolean("require_encryption", false));
local invite_only = module:get_option_boolean("registration_invite_only", true);
local invites;
if prosody.shutdown then -- COMPAT hack to detect prosodyctl
invites = module:depends("invites");
end
local legacy_invite_stream_feature = st.stanza("register", { xmlns = "urn:xmpp:invite" }):up();
local invite_stream_feature = st.stanza("register", { xmlns = "urn:xmpp:ibr-token:0" }):up();
module:hook("stream-features", function(event)
local session, features = event.origin, event.features;
-- Advertise to unauthorized clients only.
if session.type ~= "c2s_unauthed" or (require_encryption and not session.secure) then
return
end
features:add_child(legacy_invite_stream_feature);
features:add_child(invite_stream_feature);
end);
-- XEP-0379: Pre-Authenticated Roster Subscription
module:hook("presence/bare", function (event)
local stanza = event.stanza;
if stanza.attr.type ~= "subscribe" then return end
local preauth = stanza:get_child("preauth", "urn:xmpp:pars:0");
if not preauth then return end
local token = preauth.attr.token;
if not token then return end
local username, host = jid_split(stanza.attr.to);
local invite, err = invites.get(token, username);
if not invite then
module:log("debug", "Got invalid token, error: %s", err);
return;
end
local contact = jid_bare(stanza.attr.from);
module:log("debug", "Approving inbound subscription to %s from %s", username, contact);
if rostermanager.set_contact_pending_in(username, host, contact, stanza) then
if rostermanager.subscribed(username, host, contact) then
invite:use();
rostermanager.roster_push(username, host, contact);
-- Send back a subscription request (goal is mutual subscription)
if not rostermanager.is_user_subscribed(username, host, contact)
and not rostermanager.is_contact_pending_out(username, host, contact) then
module:log("debug", "Sending automatic subscription request to %s from %s", contact, username);
if rostermanager.set_contact_pending_out(username, host, contact) then
rostermanager.roster_push(username, host, contact);
module:send(st.presence({type = "subscribe", from = username.."@"..host, to = contact }));
else
module:log("warn", "Failed to set contact pending out for %s", username);
end
end
end
end
end, 1);
-- Client is submitting a preauth token to allow registration
module:hook("stanza/iq/urn:xmpp:pars:0:preauth", function(event)
local preauth = event.stanza.tags[1];
local token = preauth.attr.token;
local validated_invite = invites.get(token);
if not validated_invite then
local reply = st.error_reply(event.stanza, "cancel", "forbidden", "The invite token is invalid or expired");
event.origin.send(reply);
return true;
end
event.origin.validated_invite = validated_invite;
local reply = st.reply(event.stanza);
event.origin.send(reply);
return true;
end);
-- Registration attempt - ensure a valid preauth token has been supplied
module:hook("user-registering", function (event)
local validated_invite = event.validated_invite or (event.session and event.session.validated_invite);
if invite_only and not validated_invite then
event.allowed = false;
event.reason = "Registration on this server is through invitation only";
return;
elseif not validated_invite then
-- This registration is not using an invite, but
-- the server is not in invite-only mode, so nothing
-- for this module to do...
return;
end
if validated_invite and validated_invite.additional_data and validated_invite.additional_data.allow_reset then
event.allow_reset = validated_invite.additional_data.allow_reset;
end
end);
-- Make a *one-way* subscription. User will see when contact is online,
-- contact will not see when user is online.
function subscribe(host, user_username, contact_username)
local user_jid = user_username.."@"..host;
local contact_jid = contact_username.."@"..host;
-- Update user's roster to say subscription request is pending...
rostermanager.set_contact_pending_out(user_username, host, contact_jid);
-- Update contact's roster to say subscription request is pending...
rostermanager.set_contact_pending_in(contact_username, host, user_jid);
-- Update contact's roster to say subscription request approved...
rostermanager.subscribed(contact_username, host, user_jid);
-- Update user's roster to say subscription request approved...
rostermanager.process_inbound_subscription_approval(user_username, host, contact_jid);
end
-- Make a mutual subscription between jid1 and jid2. Each JID will see
-- when the other one is online.
function subscribe_both(host, user1, user2)
subscribe(host, user1, user2);
subscribe(host, user2, user1);
end
-- Registration successful, if there was a preauth token, mark it as used
module:hook("user-registered", function (event)
local validated_invite = event.validated_invite or (event.session and event.session.validated_invite);
if not validated_invite then
return;
end
local inviter_username = validated_invite.inviter;
local contact_username = event.username;
validated_invite:use();
if inviter_username then
module:log("debug", "Creating mutual subscription between %s and %s", inviter_username, contact_username);
subscribe_both(module.host, inviter_username, contact_username);
end
if validated_invite.additional_data then
module:log("debug", "Importing roles from invite");
local roles = validated_invite.additional_data.roles;
if roles then
module:open_store("roles"):set(contact_username, roles);
end
end
end);
-- Equivalent of user-registered but for when the account already existed
-- (i.e. password reset)
module:hook("user-password-reset", function (event)
local validated_invite = event.validated_invite or (event.session and event.session.validated_invite);
if not validated_invite then
return;
end
validated_invite:use();
end);
|