Software /
code /
prosody-modules
Comparison
mod_easy_invite/mod_easy_invite.lua @ 3777:26559776a87e
mod_easy_invite: New module that implements XEP-0401/XEP-0379
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Fri, 27 Dec 2019 10:41:01 +0000 |
child | 3778:7209f481bcfe |
comparison
equal
deleted
inserted
replaced
3776:80830d97da81 | 3777:26559776a87e |
---|---|
1 -- XEP-0401: Easy User Onboarding | |
2 local dataforms = require "util.dataforms"; | |
3 local datetime = require "util.datetime"; | |
4 local jid_bare = require "util.jid".bare; | |
5 local jid_split = require "util.jid".split; | |
6 local split_jid = require "util.jid".split; | |
7 local rostermanager = require "core.rostermanager"; | |
8 local st = require "util.stanza"; | |
9 | |
10 local invite_only = module:get_option_boolean("registration_invite_only", true); | |
11 local require_encryption = module:get_option_boolean("c2s_require_encryption", | |
12 module:get_option_boolean("require_encryption", false)); | |
13 | |
14 local new_adhoc = module:require("adhoc").new; | |
15 | |
16 -- Whether local users can invite other users to create an account on this server | |
17 local allow_user_invites = module:get_option_boolean("allow_user_invites", true); | |
18 | |
19 local invites = module:depends("invites"); | |
20 | |
21 local invite_result_form = dataforms.new({ | |
22 title = "Your Invite", | |
23 -- TODO instructions = something helpful | |
24 { | |
25 name = "uri"; | |
26 label = "Invite URI"; | |
27 -- TODO desc = something helpful | |
28 }, | |
29 { | |
30 name = "url" ; | |
31 var = "landing-url"; | |
32 label = "Invite landing URL"; | |
33 }, | |
34 { | |
35 name = "expire"; | |
36 label = "Token valid until"; | |
37 }, | |
38 }); | |
39 | |
40 module:depends("adhoc"); | |
41 module:provides("adhoc", new_adhoc("New Invite", "urn:xmpp:invite#invite", | |
42 function (_, data) | |
43 local username = split_jid(data.from); | |
44 local invite = invites.create_contact(username, allow_user_invites); | |
45 --TODO: check errors | |
46 return { | |
47 status = "completed"; | |
48 form = { | |
49 layout = invite_result_form; | |
50 values = { | |
51 uri = invite.uri; | |
52 url = invite.landing_page; | |
53 expire = datetime.datetime(invite.expires); | |
54 }; | |
55 }; | |
56 }; | |
57 end, "local_user")); | |
58 | |
59 | |
60 -- TODO | |
61 -- module:provides("adhoc", new_adhoc("Create account", "urn:xmpp:invite#create-account", function () end, "admin")); | |
62 | |
63 -- XEP-0379: Pre-Authenticated Roster Subscription | |
64 module:hook("presence/bare", function (event) | |
65 local stanza = event.stanza; | |
66 if stanza.attr.type ~= "subscribe" then return end | |
67 | |
68 local preauth = stanza:get_child("preauth", "urn:xmpp:pars:0"); | |
69 if not preauth then return end | |
70 local token = preauth.attr.token; | |
71 if not token then return end | |
72 | |
73 local username, host = jid_split(stanza.attr.to); | |
74 | |
75 local invite, err = invites.get(token, username); | |
76 | |
77 if not invite then | |
78 module:log("debug", "Got invalid token, error: %s", err); | |
79 return; | |
80 end | |
81 | |
82 local contact = jid_bare(stanza.attr.from); | |
83 | |
84 module:log("debug", "Approving inbound subscription to %s from %s", username, contact); | |
85 if rostermanager.set_contact_pending_in(username, host, contact, stanza) then | |
86 if rostermanager.subscribed(username, host, contact) then | |
87 invite:use(); | |
88 rostermanager.roster_push(username, host, contact); | |
89 | |
90 -- Send back a subscription request (goal is mutual subscription) | |
91 if not rostermanager.is_user_subscribed(username, host, contact) | |
92 and not rostermanager.is_contact_pending_out(username, host, contact) then | |
93 module:log("debug", "Sending automatic subscription request to %s from %s", contact, username); | |
94 if rostermanager.set_contact_pending_out(username, host, contact) then | |
95 rostermanager.roster_push(username, host, contact); | |
96 module:send(st.presence({type = "subscribe", to = contact })); | |
97 else | |
98 module:log("warn", "Failed to set contact pending out for %s", username); | |
99 end | |
100 end | |
101 end | |
102 end | |
103 end, 1); | |
104 | |
105 -- TODO sender side, magic automatic mutual subscription | |
106 | |
107 local invite_stream_feature = st.stanza("register", { xmlns = "urn:xmpp:invite" }):up(); | |
108 module:hook("stream-features", function(event) | |
109 local session, features = event.origin, event.features; | |
110 | |
111 -- Advertise to unauthorized clients only. | |
112 if session.type ~= "c2s_unauthed" or (require_encryption and not session.secure) then | |
113 return | |
114 end | |
115 | |
116 features:add_child(invite_stream_feature); | |
117 end); | |
118 | |
119 -- Client is submitting a preauth token to allow registration | |
120 module:hook("stanza/iq/urn:xmpp:pars:0:preauth", function(event) | |
121 local preauth = event.stanza.tags[1]; | |
122 local token = preauth.attr.token; | |
123 local validated_invite = invites.get(token); | |
124 if not validated_invite then | |
125 local reply = st.error_reply(event.stanza, "cancel", "forbidden", "The invite token is invalid or expired"); | |
126 event.origin.send(reply); | |
127 return true; | |
128 end | |
129 event.origin.validated_invite = validated_invite; | |
130 local reply = st.reply(event.stanza); | |
131 event.origin.send(reply); | |
132 return true; | |
133 end); | |
134 | |
135 -- Registration attempt - ensure a valid preauth token has been supplied | |
136 module:hook("user-registering", function (event) | |
137 local validated_invite = event.session.validated_invite; | |
138 if invite_only and not validated_invite then | |
139 event.allowed = false; | |
140 event.reason = "Registration on this server is through invitation only"; | |
141 return; | |
142 end | |
143 end); | |
144 | |
145 -- Make a *one-way* subscription. User will see when contact is online, | |
146 -- contact will not see when user is online. | |
147 function subscribe(host, user_username, contact_username) | |
148 local user_jid = user_username.."@"..host; | |
149 local contact_jid = contact_username.."@"..host; | |
150 -- Update user's roster to say subscription request is pending... | |
151 rostermanager.set_contact_pending_out(user_username, host, contact_jid); | |
152 -- Update contact's roster to say subscription request is pending... | |
153 rostermanager.set_contact_pending_in(contact_username, host, user_jid); | |
154 -- Update contact's roster to say subscription request approved... | |
155 rostermanager.subscribed(contact_username, host, user_jid); | |
156 -- Update user's roster to say subscription request approved... | |
157 rostermanager.process_inbound_subscription_approval(user_username, host, contact_jid); | |
158 end | |
159 | |
160 -- Make a mutual subscription between jid1 and jid2. Each JID will see | |
161 -- when the other one is online. | |
162 function subscribe_both(host, user1, user2) | |
163 subscribe(host, user1, user2); | |
164 subscribe(host, user2, user1); | |
165 end | |
166 | |
167 -- Registration successful, if there was a preauth token, mark it as used | |
168 module:hook("user-registered", function (event) | |
169 local validated_invite = event.session.validated_invite; | |
170 if not validated_invite then | |
171 return; | |
172 end | |
173 local inviter_username = validated_invite.inviter; | |
174 validated_invite:use(); | |
175 | |
176 if not inviter_username then return; end | |
177 | |
178 local contact_username = event.username; | |
179 | |
180 module:log("debug", "Creating mutual subscription between %s and %s", inviter_username, contact_username); | |
181 subscribe_both(module.host, inviter_username, contact_username); | |
182 end); |