# HG changeset patch # User Matthew Wild # Date 1599828714 -3600 # Node ID a2116f5a7c8f1efe033585289d1ea46968c26814 # Parent 2b6918714792e4cd76c5249add064b78410ef5d4 mod_invites_register_web: New module to allow web registration with an invite token diff -r 2b6918714792 -r a2116f5a7c8f mod_invites_register_web/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_register_web/README.markdown Fri Sep 11 13:51:54 2020 +0100 @@ -0,0 +1,34 @@ +--- +labels: +- 'Stage-Beta' +summary: 'Register accounts via the web using invite tokens' +... + +Introduction +============ + +This module is part of the suite of modules that implement invite-based +account registration for Prosody. The other modules are: + +- mod_invites +- mod_invites_adhoc +- mod_invites_page +- mod_invites_register +- mod_invites_register_web +- mod_register_apps + +For details and a full overview, start with the mod_invites documentation. + +Details +======= + +mod_invites_register_web implements a web-based registration form that +validates invite tokens. It also supports guiding the user through client +download and configuration via mod_register_apps. + +There is no specific configuration for this module (though it uses the +optional `site_name` to override the displayed site name. + +This module depends on mod_invites_page solely for the case where an invalid +invite token is received - it will redirect to mod_invites_page so that an +appropriate error can be served to the user. diff -r 2b6918714792 -r a2116f5a7c8f mod_invites_register_web/html/register.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_register_web/html/register.html Fri Sep 11 13:51:54 2020 +0100 @@ -0,0 +1,89 @@ + + + + + + {site_name} + + + + + + + + + + +
+
+
+

+ Register on {site_name}
+

+
+

{site_name} is part of XMPP, a secure and decentralized messaging network. To begin + chatting {app&using {app.name} }you need to first register an account.

+ +

Creating an account will allow to communicate with {inviter&{inviter} and }other + people on {site_name} and other services on the XMPP network.

+ + {app&{app.supports_preauth_uri& +
+

If you already have {app.name} installed, + we recommend that you continue the account creation process using the app + by clicking on the button below:

+ +
{app.name} already installed?
+ +
+
+ This button works only if you have the app installed already! +
+
+
+ }} + +
Create an account
+ + {message&} + +
+
+ +
+
+ +
+ @{domain} +
+
+ Choose a username, this will become the first part of your new chat address. +
+
+
+ +
+ + Enter a secure password that you do not use anywhere else. +
+
+
+ + {app&} + +
+
+
+
+
+ + + + diff -r 2b6918714792 -r a2116f5a7c8f mod_invites_register_web/html/register_error.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_register_web/html/register_error.html Fri Sep 11 13:51:54 2020 +0100 @@ -0,0 +1,33 @@ + + + + + + Invite to {site_name} + + + + + + + + + + +
+
+
+

+ Invite to {site_name}
+

+
+
Registration error
+ +

{message?Sorry, there was a problem registering your account.}

+
+
+
+ + + + diff -r 2b6918714792 -r a2116f5a7c8f mod_invites_register_web/html/register_success.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_register_web/html/register_success.html Fri Sep 11 13:51:54 2020 +0100 @@ -0,0 +1,80 @@ + + + + + + {site_name} + + + + + + + + + + + + +
+
+
+

+ {site_name}
+

+
+
Congratulations!
+ +

You have created an account on {site_name}.

+ +

To start chatting, you need to enter your new account + credentials into your chosen XMPP software.

+ +

As a final reminder, your account details are shown below:

+ + + +

Your password is stored encrypted on the server and will not be accessible after you close + this page. Keep it safe and never share it with anyone.

+
+
+
+ + + + diff -r 2b6918714792 -r a2116f5a7c8f mod_invites_register_web/html/register_success_setup.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_register_web/html/register_success_setup.html Fri Sep 11 13:51:54 2020 +0100 @@ -0,0 +1,104 @@ + + + + + + {site_name} + + + + + + + + + + + + +
+
+
+

+ {site_name}
+

+
+
Congratulations!
+ +

You have created an account on {site_name}!

+ +

You can now set up {app.name} and connect it to your new account.

+ +
Step 1: Download and install {app.name}
+ +

{app.download.text?Download and install {app.name} below:}

+ +
+ {app.download.buttons# + {item.image& + + + + } + {item.text& + + + + } + } +
+ +
Step 2: Connect {app.name} to your new account
+ +

{app.setup.text?Launch {app.name} and sign in using your account credentials.}

+ +

As a final reminder, your account details are shown below:

+ + + +

Your password is stored encrypted at {site_name} and will not be accessible after you close + this page. Keep it safe and never share it with anyone.

+
+
+
+ + + + diff -r 2b6918714792 -r a2116f5a7c8f mod_invites_register_web/mod_invites_register_web.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_invites_register_web/mod_invites_register_web.lua Fri Sep 11 13:51:54 2020 +0100 @@ -0,0 +1,183 @@ +local id = require "util.id"; +local http_formdecode = require "net.http".formdecode; +local usermanager = require "core.usermanager"; +local nodeprep = require "util.encodings".stringprep.nodeprep; +local st = require "util.stanza"; +local url_escape = require "util.http".urlencode; +local render_html_template = require"util.interpolation".new("%b{}", st.xml_escape, { + urlescape = url_escape; +}); + + +module:depends("register_apps"); + +local site_name = module:get_option_string("site_name", module.host); +local site_apps = module:shared("register_apps/apps"); + +module:depends("http"); +module:depends("easy_invite"); +local invites = module:depends("invites"); +local invites_page = module:depends("invites_page"); + +function serve_register_page(event) + local register_page_template = assert(module:load_resource("html/register.html")):read("*a"); + + local query_params = http_formdecode(event.request.url.query); + + local invite = invites.get(query_params.t); + if not invite then + return { + status_code = 303; + headers = { + ["Location"] = invites.module:http_url().."?"..event.request.url.query; + }; + }; + end + + local invite_page = render_html_template(register_page_template, { + site_name = site_name; + token = invite.token; + domain = module.host; + uri = invite.uri; + type = invite.type; + jid = invite.jid; + inviter = invite.inviter; + app = query_params.c and site_apps[query_params.c]; + }); + return invite_page; +end + +function handle_register_form(event) + local request, response = event.request, event.response; + local form_data = http_formdecode(request.body); + local user, password, token = form_data["user"], form_data["password"], form_data["token"]; + local app_id = form_data["app_id"]; + + local register_page_template = assert(module:load_resource("html/register.html")):read("*a"); + local error_template = assert(module:load_resource("html/register_error.html")):read("*a"); + + local invite = invites.get(token); + if not invite then + return { + status_code = 303; + headers = { + ["Location"] = invites_page.module:http_url().."?"..event.request.url.query; + }; + }; + end + + response.headers.content_type = "text/html; charset=utf-8"; + + if not user or #user == 0 or not password or #password == 0 or not token then + return render_html_template(register_page_template, { + site_name = site_name; + token = invite.token; + domain = module.host; + uri = invite.uri; + type = invite.type; + jid = invite.jid; + + msg_class = "alert-warning"; + message = "Please fill in all fields."; + }); + end + + -- Shamelessly copied from mod_register_web. + local prepped_username = nodeprep(user); + + if not prepped_username or #prepped_username == 0 then + return render_html_template(register_page_template, { + site_name = site_name; + token = invite.token; + domain = module.host; + uri = invite.uri; + type = invite.type; + jid = invite.jid; + + msg_class = "alert-warning"; + message = "This username contains invalid characters."; + }); + end + + if usermanager.user_exists(prepped_username, module.host) then + return render_html_template(register_page_template, { + site_name = site_name; + token = invite.token; + domain = module.host; + uri = invite.uri; + type = invite.type; + jid = invite.jid; + + msg_class = "alert-warning"; + message = "This username is already in use."; + }); + end + + local registering = { + validated_invite = invite; + username = prepped_username; + host = module.host; + allowed = true; + }; + + module:fire_event("user-registering", registering); + + if not registering.allowed then + return render_html_template(error_template, { + site_name = site_name; + msg_class = "alert-danger"; + message = registering.reason or "Registration is not allowed."; + }); + end + + local ok, err = usermanager.create_user(prepped_username, password, module.host); + + if ok then + module:fire_event("user-registered", { + username = prepped_username; + host = module.host; + source = "mod_"..module.name; + validated_invite = invite; + }); + + local app_info = site_apps[app_id]; + + local success_template; + if app_info then + -- If recognised app, we serve a page that includes setup instructions + success_template = assert(module:load_resource("html/register_success_setup.html")):read("*a"); + else + success_template = assert(module:load_resource("html/register_success.html")):read("*a"); + end + + -- Due to the credentials being served here, ensure that + -- the browser or any intermediary does not cache the page + event.response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"; + event.response.headers["Pragma"] = "no-cache"; + event.response.headers["Expires"] = "0"; + + return render_html_template(success_template, { + site_name = site_name; + username = prepped_username; + domain = module.host; + password = password; + app = app_info; + }); + else + local err_id = id.short(); + module:log("warn", "Registration failed (%s): %s", err_id, tostring(err)); + return render_html_template(error_template, { + site_name = site_name; + msg_class = "alert-danger"; + message = ("An unknown error has occurred (%s)"):format(err_id); + }); + end +end + +module:provides("http", { + default_path = "register"; + route = { + ["GET"] = serve_register_page; + ["POST"] = handle_register_form; + }; +});