aboutsummaryrefslogtreecommitdiff
path: root/src/mod_register_web.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_register_web.erl')
-rw-r--r--src/mod_register_web.erl566
1 files changed, 566 insertions, 0 deletions
diff --git a/src/mod_register_web.erl b/src/mod_register_web.erl
new file mode 100644
index 000000000..841685a94
--- /dev/null
+++ b/src/mod_register_web.erl
@@ -0,0 +1,566 @@
+%%%-------------------------------------------------------------------
+%%% File : mod_register_web.erl
+%%% Author : Badlop <badlop@process-one.net>
+%%% Purpose : Web page to register account and related tasks
+%%% Created : 4 May 2008 by Badlop <badlop@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2013 ProcessOne
+%%%
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License as
+%%% published by the Free Software Foundation; either version 2 of the
+%%% License, or (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+%%% General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with this program; if not, write to the Free Software
+%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+%%% 02111-1307 USA
+%%%
+%%%----------------------------------------------------------------------
+
+%%% IDEAS:
+%%%
+%%% * Implement those options, already present in mod_register:
+%%% + access
+%%% + captcha_protected
+%%% + password_strength
+%%% + welcome_message
+%%% + registration_timeout
+%%%
+%%% * Improve this module to allow each virtual host to have different
+%%% options. See http://support.process-one.net/browse/EJAB-561
+%%%
+%%% * Check that all the text is translatable.
+%%%
+%%% * Add option to use a custom CSS file, or custom CSS lines.
+%%%
+%%% * Don't hardcode the "register" path in URL.
+%%%
+%%% * Allow private email during register, and store in custom table.
+%%% * Optionally require private email to register.
+%%% * Optionally require email confirmation to register.
+%%% * Allow to set a private email address anytime.
+%%% * Allow to recover password using private email to confirm (mod_passrecover)
+%%% * Optionally require invitation
+%%% * Optionally register request is forwarded to admin, no account created.
+
+-module(mod_register_web).
+
+-author('badlop@process-one.net').
+
+-behaviour(gen_mod).
+
+-export([start/2, stop/1, process/2]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+
+-include("jlib.hrl").
+
+-include("ejabberd_http.hrl").
+
+-include("ejabberd_web_admin.hrl").
+
+%%%----------------------------------------------------------------------
+%%% gen_mod callbacks
+%%%----------------------------------------------------------------------
+
+start(_Host, _Opts) ->
+ %% case gen_mod:get_opt(docroot, Opts, fun(A) -> A end, undefined) of
+ ok.
+
+stop(_Host) -> ok.
+
+%%%----------------------------------------------------------------------
+%%% HTTP handlers
+%%%----------------------------------------------------------------------
+
+process([], #request{method = 'GET', lang = Lang}) ->
+ index_page(Lang);
+process([<<"register.css">>],
+ #request{method = 'GET'}) ->
+ serve_css();
+process([<<"new">>],
+ #request{method = 'GET', lang = Lang, host = Host,
+ ip = IP}) ->
+ {Addr, _Port} = IP, form_new_get(Host, Lang, Addr);
+process([<<"delete">>],
+ #request{method = 'GET', lang = Lang, host = Host}) ->
+ form_del_get(Host, Lang);
+process([<<"change_password">>],
+ #request{method = 'GET', lang = Lang, host = Host}) ->
+ form_changepass_get(Host, Lang);
+process([<<"new">>],
+ #request{method = 'POST', q = Q, ip = {Ip, _Port},
+ lang = Lang, host = Host}) ->
+ case form_new_post(Q, Host) of
+ {success, ok, {Username, Host, _Password}} ->
+ Jid = jlib:make_jid(Username, Host, <<"">>),
+ mod_register:send_registration_notifications(?MODULE, Jid, Ip),
+ Text = (?T(<<"Your Jabber account was successfully "
+ "created.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error creating the account: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
+ end;
+process([<<"delete">>],
+ #request{method = 'POST', q = Q, lang = Lang,
+ host = Host}) ->
+ case form_del_post(Q, Host) of
+ {atomic, ok} ->
+ Text = (?T(<<"Your Jabber account was successfully "
+ "deleted.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error deleting the account: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
+ end;
+%% TODO: Currently only the first vhost is usable. The web request record
+%% should include the host where the POST was sent.
+process([<<"change_password">>],
+ #request{method = 'POST', q = Q, lang = Lang,
+ host = Host}) ->
+ case form_changepass_post(Q, Host) of
+ {atomic, ok} ->
+ Text = (?T(<<"The password of your Jabber account "
+ "was successfully changed.">>)),
+ {200, [], Text};
+ Error ->
+ ErrorText =
+ list_to_binary([?T(<<"There was an error changing the password: ">>),
+ ?T(get_error_text(Error))]),
+ {404, [], ErrorText}
+ end.
+
+%%%----------------------------------------------------------------------
+%%% CSS
+%%%----------------------------------------------------------------------
+
+serve_css() ->
+ {200,
+ [{<<"Content-Type">>, <<"text/css">>}, last_modified(),
+ cache_control_public()],
+ css()}.
+
+last_modified() ->
+ {<<"Last-Modified">>,
+ <<"Mon, 25 Feb 2008 13:23:30 GMT">>}.
+
+cache_control_public() ->
+ {<<"Cache-Control">>, <<"public">>}.
+
+css() ->
+ <<"html,body {\nbackground: white;\nmargin: "
+ "0;\npadding: 0;\nheight: 100%;\n}">>.
+
+%%%----------------------------------------------------------------------
+%%% Index page
+%%%----------------------------------------------------------------------
+
+index_page(Lang) ->
+ HeadEls = [?XCT(<<"title">>,
+ <<"Jabber Account Registration">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Jabber Account Registration">>),
+ ?XE(<<"ul">>,
+ [?XE(<<"li">>,
+ [?ACT(<<"new">>, <<"Register a Jabber account">>)]),
+ ?XE(<<"li">>,
+ [?ACT(<<"change_password">>, <<"Change Password">>)]),
+ ?XE(<<"li">>,
+ [?ACT(<<"delete">>,
+ <<"Unregister a Jabber account">>)])])],
+ {200,
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
+ ejabberd_web:make_xhtml(HeadEls, Els)}.
+
+%%%----------------------------------------------------------------------
+%%% Formulary new account GET
+%%%----------------------------------------------------------------------
+
+form_new_get(Host, Lang, IP) ->
+ CaptchaEls = build_captcha_li_list(Lang, IP),
+ HeadEls = [?XCT(<<"title">>,
+ <<"Register a Jabber account">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Register a Jabber account">>),
+ ?XCT(<<"p">>,
+ <<"This page allows to create a Jabber "
+ "account in this Jabber server. Your "
+ "JID (Jabber IDentifier) will be of the "
+ "form: username@server. Please read carefully "
+ "the instructions to fill correctly the "
+ "fields.">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ ([?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>),
+ ?BR,
+ ?XE(<<"ul">>,
+ [?XCT(<<"li">>,
+ <<"This is case insensitive: macbeth is "
+ "the same that MacBeth and Macbeth.">>),
+ ?XC(<<"li">>,
+ <<(?T(<<"Characters not allowed:">>))/binary,
+ " \" & ' / : < > @ ">>)])]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>),
+ ?BR,
+ ?XE(<<"ul">>,
+ [?XCT(<<"li">>,
+ <<"Don't tell your password to anybody, "
+ "not even the administrators of the Jabber "
+ "server.">>),
+ ?XCT(<<"li">>,
+ <<"You can later change your password using "
+ "a Jabber client.">>),
+ ?XCT(<<"li">>,
+ <<"Some Jabber clients can store your password "
+ "in your computer. Use that feature only "
+ "if you trust your computer is safe.">>),
+ ?XCT(<<"li">>,
+ <<"Memorize your password, or write it "
+ "in a paper placed in a safe place. In "
+ "Jabber there isn't an automated way "
+ "to recover your password if you forget "
+ "it.">>)])]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password Verification:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password2">>, <<"">>,
+ <<"20">>)])]
+ ++
+ CaptchaEls ++
+ [?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"register">>,
+ <<"Register">>)])]))])],
+ {200,
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
+ ejabberd_web:make_xhtml(HeadEls, Els)}.
+
+%% Copied from mod_register.erl
+%% Function copied from ejabberd_logger_h.erl and customized
+%%%----------------------------------------------------------------------
+%%% Formulary new POST
+%%%----------------------------------------------------------------------
+
+form_new_post(Q, Host) ->
+ case catch get_register_parameters(Q) of
+ [Username, Password, Password, Id, Key] ->
+ form_new_post(Username, Host, Password, {Id, Key});
+ [_Username, _Password, _Password2, false, false] ->
+ {error, passwords_not_identical};
+ [_Username, _Password, _Password2, Id, Key] ->
+ ejabberd_captcha:check_captcha(Id, Key),
+ {error, passwords_not_identical};
+ _ -> {error, wrong_parameters}
+ end.
+
+get_register_parameters(Q) ->
+ lists:map(fun (Key) ->
+ case lists:keysearch(Key, 1, Q) of
+ {value, {_Key, Value}} -> Value;
+ false -> false
+ end
+ end,
+ [<<"username">>, <<"password">>, <<"password2">>,
+ <<"id">>, <<"key">>]).
+
+form_new_post(Username, Host, Password,
+ {false, false}) ->
+ register_account(Username, Host, Password);
+form_new_post(Username, Host, Password, {Id, Key}) ->
+ case ejabberd_captcha:check_captcha(Id, Key) of
+ captcha_valid ->
+ register_account(Username, Host, Password);
+ captcha_non_valid -> {error, captcha_non_valid};
+ captcha_not_found -> {error, captcha_non_valid}
+ end.
+
+%%%----------------------------------------------------------------------
+%%% Formulary Captcha support for new GET/POST
+%%%----------------------------------------------------------------------
+
+build_captcha_li_list(Lang, IP) ->
+ case ejabberd_captcha:is_feature_available() of
+ true -> build_captcha_li_list2(Lang, IP);
+ false -> []
+ end.
+
+build_captcha_li_list2(Lang, IP) ->
+ SID = <<"">>,
+ From = #jid{user = <<"">>, server = <<"test">>,
+ resource = <<"">>},
+ To = #jid{user = <<"">>, server = <<"test">>,
+ resource = <<"">>},
+ Args = [],
+ case ejabberd_captcha:create_captcha(SID, From, To,
+ Lang, IP, Args)
+ of
+ {ok, Id, _} ->
+ {_, {CImg, CText, CId, CKey}} =
+ ejabberd_captcha:build_captcha_html(Id, Lang),
+ [?XE(<<"li">>,
+ [CText, ?C(<<" ">>), CId, CKey, ?BR, CImg])];
+ _ -> []
+ end.
+
+%%%----------------------------------------------------------------------
+%%% Formulary change password GET
+%%%----------------------------------------------------------------------
+
+form_changepass_get(Host, Lang) ->
+ HeadEls = [?XCT(<<"title">>, <<"Change Password">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Change Password">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ [?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Old Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"passwordold">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"New Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password Verification:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password2">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"changepass">>,
+ <<"Change Password">>)])])])],
+ {200,
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
+ ejabberd_web:make_xhtml(HeadEls, Els)}.
+
+%%%----------------------------------------------------------------------
+%%% Formulary change password POST
+%%%----------------------------------------------------------------------
+
+form_changepass_post(Q, Host) ->
+ case catch get_changepass_parameters(Q) of
+ [Username, PasswordOld, Password, Password] ->
+ try_change_password(Username, Host, PasswordOld,
+ Password);
+ [_Username, _PasswordOld, _Password, _Password2] ->
+ {error, passwords_not_identical};
+ _ -> {error, wrong_parameters}
+ end.
+
+get_changepass_parameters(Q) ->
+%% @spec(Username,Host,PasswordOld,Password) -> {atomic, ok} |
+%% {error, account_doesnt_exist} |
+%% {error, password_not_changed} |
+%% {error, password_incorrect}
+ lists:map(fun (Key) ->
+ {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
+ Value
+ end,
+ [<<"username">>, <<"passwordold">>, <<"password">>,
+ <<"password2">>]).
+
+try_change_password(Username, Host, PasswordOld,
+ Password) ->
+ try change_password(Username, Host, PasswordOld,
+ Password)
+ of
+ {atomic, ok} -> {atomic, ok}
+ catch
+ error:{badmatch, Error} -> {error, Error}
+ end.
+
+change_password(Username, Host, PasswordOld,
+ Password) ->
+ account_exists = check_account_exists(Username, Host),
+ password_correct = check_password(Username, Host,
+ PasswordOld),
+ ok = ejabberd_auth:set_password(Username, Host,
+ Password),
+ case check_password(Username, Host, Password) of
+ password_correct -> {atomic, ok};
+ password_incorrect -> {error, password_not_changed}
+ end.
+
+check_account_exists(Username, Host) ->
+ case ejabberd_auth:is_user_exists(Username, Host) of
+ true -> account_exists;
+ false -> account_doesnt_exist
+ end.
+
+check_password(Username, Host, Password) ->
+ case ejabberd_auth:check_password(Username, Host,
+ Password)
+ of
+ true -> password_correct;
+ false -> password_incorrect
+ end.
+
+%%%----------------------------------------------------------------------
+%%% Formulary delete account GET
+%%%----------------------------------------------------------------------
+
+form_del_get(Host, Lang) ->
+ HeadEls = [?XCT(<<"title">>,
+ <<"Unregister a Jabber account">>),
+ ?XA(<<"link">>,
+ [{<<"href">>, <<"/register/register.css">>},
+ {<<"type">>, <<"text/css">>},
+ {<<"rel">>, <<"stylesheet">>}])],
+ Els = [?XACT(<<"h1">>,
+ [{<<"class">>, <<"title">>},
+ {<<"style">>, <<"text-align:center;">>}],
+ <<"Unregister a Jabber account">>),
+ ?XCT(<<"p">>,
+ <<"This page allows to unregister a Jabber "
+ "account in this Jabber server.">>),
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}],
+ [?XE(<<"ol">>,
+ [?XE(<<"li">>,
+ [?CT(<<"Username:">>), ?C(<<" ">>),
+ ?INPUTS(<<"text">>, <<"username">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]),
+ ?XE(<<"li">>,
+ [?CT(<<"Password:">>), ?C(<<" ">>),
+ ?INPUTS(<<"password">>, <<"password">>, <<"">>,
+ <<"20">>)]),
+ ?XE(<<"li">>,
+ [?INPUTT(<<"submit">>, <<"unregister">>,
+ <<"Unregister">>)])])])],
+ {200,
+ [{<<"Server">>, <<"ejabberd">>},
+ {<<"Content-Type">>, <<"text/html">>}],
+ ejabberd_web:make_xhtml(HeadEls, Els)}.
+
+%% @spec(Username, Host, Password) -> {success, ok, {Username, Host, Password} |
+%% {success, exists, {Username, Host, Password}} |
+%% {error, not_allowed} |
+%% {error, invalid_jid}
+register_account(Username, Host, Password) ->
+ case jlib:make_jid(Username, Host, <<"">>) of
+ error -> {error, invalid_jid};
+ _ -> register_account2(Username, Host, Password)
+ end.
+
+register_account2(Username, Host, Password) ->
+ case ejabberd_auth:try_register(Username, Host,
+ Password)
+ of
+ {atomic, Res} ->
+ {success, Res, {Username, Host, Password}};
+ Other -> Other
+ end.
+
+%%%----------------------------------------------------------------------
+%%% Formulary delete POST
+%%%----------------------------------------------------------------------
+
+form_del_post(Q, Host) ->
+ case catch get_unregister_parameters(Q) of
+ [Username, Password] ->
+ try_unregister_account(Username, Host, Password);
+ _ -> {error, wrong_parameters}
+ end.
+
+get_unregister_parameters(Q) ->
+%% @spec(Username, Host, Password) -> {atomic, ok} |
+%% {error, account_doesnt_exist} |
+%% {error, account_exists} |
+%% {error, password_incorrect}
+ lists:map(fun (Key) ->
+ {value, {_Key, Value}} = lists:keysearch(Key, 1, Q),
+ Value
+ end,
+ [<<"username">>, <<"password">>]).
+
+try_unregister_account(Username, Host, Password) ->
+ try unregister_account(Username, Host, Password) of
+ {atomic, ok} -> {atomic, ok}
+ catch
+ error:{badmatch, Error} -> {error, Error}
+ end.
+
+unregister_account(Username, Host, Password) ->
+ account_exists = check_account_exists(Username, Host),
+ password_correct = check_password(Username, Host,
+ Password),
+ ok = ejabberd_auth:remove_user(Username, Host,
+ Password),
+ account_doesnt_exist = check_account_exists(Username,
+ Host),
+ {atomic, ok}.
+
+%%%----------------------------------------------------------------------
+%%% Error texts
+%%%----------------------------------------------------------------------
+
+get_error_text({error, captcha_non_valid}) ->
+ <<"The captcha you entered is wrong">>;
+get_error_text({success, exists, _}) ->
+ get_error_text({atomic, exists});
+get_error_text({atomic, exists}) ->
+ <<"The account already exists">>;
+get_error_text({error, password_incorrect}) ->
+ <<"Incorrect password">>;
+get_error_text({error, invalid_jid}) ->
+ <<"The username is not valid">>;
+get_error_text({error, not_allowed}) ->
+ <<"Not allowed">>;
+get_error_text({error, account_doesnt_exist}) ->
+ <<"Account doesn't exist">>;
+get_error_text({error, account_exists}) ->
+ <<"The account was not deleted">>;
+get_error_text({error, password_not_changed}) ->
+ <<"The password was not changed">>;
+get_error_text({error, passwords_not_identical}) ->
+ <<"The passwords are different">>;
+get_error_text({error, wrong_parameters}) ->
+ <<"Wrong parameters in the web formulary">>.