Fix up signup process to use reCAPTCHA

This commit is contained in:
Chris 2016-12-31 22:09:33 +00:00
parent e6fd389836
commit bba2fa2c06
5 changed files with 179 additions and 136 deletions

View File

@ -26,7 +26,8 @@ use strict;
use parent qw(ORB); # This class extends the ORB block class
use experimental qw(smartmatch);
use Webperl::Utils qw(path_join is_defined_numeric);
use Data::Dumper;
use LWP::UserAgent;
use JSON;
use v5.14;
# ============================================================================
@ -54,7 +55,7 @@ sub _signup_email {
"block" => "login",
"pathinfo" => [ "activate" ]);
my $status = $self -> {"messages"} -> queue_message(subject => $self -> {"template"} -> replace_langvar("LOGIN_REG_SUBJECT"),
my $status = $self -> {"messages"} -> queue_message(subject => $self -> {"template"} -> replace_langvar("LOGIN_SIGNUP_SUBJECT"),
message => $self -> {"template"} -> load_template("login/email_signedup.tem",
{"%(username)s" => $user -> {"username"},
"%(password)s" => $password,
@ -305,6 +306,147 @@ sub _validate_signin {
}
## @method private $ _validate_recaptcha($response)
# Given a response code submitted as part of an operation by the user, ask the
# google reCAPTCHA validation service to check whether the response is valid.
#
# @param response The reCAPTCHA response code submitted by the user
# @return true if the code is valid, false if is not, undef on error.
sub _validate_recaptcha {
my $self = shift;
my $response = shift;
$self -> clear_error();
my $ua = LWP::UserAgent -> new();
my $data = {
"secret" => $self -> {"settings"} -> {"config"} -> {"Login:recaptcha_secret"},
"response" => $response,
"remoteip" => $self -> {"cgi"} -> remote_addr()
};
# Ask the recaptcha server to verify the response
my $resp = $ua -> post($self -> {"settings"} -> {"config"} -> {"Login:recaptcha_verify"}, $data);
if($resp -> is_success()) {
# Convert the validator response
my $json = eval { decode_json($resp -> decoded_content()) };
return $self -> self_error("JSON decoding failed: $@") if($@);
$self -> log("recaptcha:status", "Validation response: ".($json -> {"success"} ? "successful" : "failed")." JSON: ".$resp -> decoded_content());
return 1 if($json -> {"success"});
} else {
return $self -> self_error("HTTP problem: ".$resp -> status_line());
}
return 0;
}
## @method private @ _validate_signup()
# Determine whether the username, email, and security question provided by the user
# are valid. If they are, return true.
#
# @return The new user's record on success, an error string if the signup failed.
sub _validate_signup {
my $self = shift;
my $error = "";
my $errors = "";
my $args = {};
# User attempted self-register when it is disabled? Naughty user, no cookie!
return ($self -> {"template"} -> load_template("error/error.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(reason)s" => "{L_LOGIN_ERR_NOSELFREG}"}), $args)
unless($self -> {"settings"} -> {"config"} -> {"Login:allow_self_register"});
# Check that the username is provided and valid
($args -> {"username"}, $error) = $self -> validate_string("username", {"required" => 1,
"nicename" => "{L_LOGIN_USERNAME}",
"minlen" => 2,
"maxlen" => 32,
"formattest" => '^[-\w ]+$',
"formatdesc" => "{L_LOGIN_ERR_BADUSERCHAR}"
});
# Is the username valid?
if($error) {
$errors .= $self -> {"template"} -> load_template("error/error_item.tem", {"%(reason)s" => $error});
} else {
# Is the username in use?
my $user = $self -> {"session"} -> get_user($args -> {"username"});
$errors .= $self -> {"template"} -> load_template("error/error.tem",
{ "%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(reason)s" => $self -> {"template"} -> replace_langvar("LOGIN_ERR_USERINUSE",
{ "%(url-recover)s" => $self -> build_url("block" => "login", "pathinfo" => [ "recover" ]) })
})
if($user);
}
# And the email
($args -> {"email"}, $error) = $self -> validate_string("email", {"required" => 1,
"nicename" => "{L_LOGIN_EMAIL}",
"minlen" => 2,
"maxlen" => 256
});
if($error) {
$errors .= $self -> {"template"} -> load_template("error/error_item.tem", {"%(reason)s" => $error});
} else {
# Check that the address is structured in a vaguely valid way
# Yes, this is not fully RFC compliant, but frankly going down that road invites a
# level of utter madness that would make Azathoth himself utter "I say, steady on now..."
$errors .= $self -> {"template"} -> load_template("error/error_item.tem", {"%(reason)s" => "{L_LOGIN_ERR_BADEMAIL}"})
if($args -> {"email"} !~ /^[\w.+-]+\@([\w-]+\.)+\w+$/);
# Is the email address in use?
my $user = $self -> {"session"} -> {"auth"} -> {"app"} -> get_user_byemail($args -> {"email"});
$errors .= $self -> {"template"} -> load_template("error/error_item.tem", {"%(reason)s" => $self -> {"template"} -> replace_langvar("LOGIN_ERR_EMAILINUSE",
{ "%(url-recover)s" => $self -> build_url("block" => "login", "pathinfo" => [ "recover" ]) })
})
if($user);
}
# Pull the reCAPTCHA response code
($args -> {"recaptcha"}, $error) = $self -> validate_string("g-recaptcha-response", {"required" => 1,
"nicename" => "{L_LOGIN_RECAPTCHA}",
"minlen" => 2,
"formattest" => '^[-\w]+$',
"formatdesc" => "{L_LOGIN_ERR_BADRECAPTCHA}"
});
# Halt here if there are any problems.
return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(errors)s" => $errors}), $args)
if($errors);
# Is the response valid?
return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(errors)s" => "{L_LOGIN_ERR_RECAPTCHA}"}), $args)
unless($self -> _validate_recaptcha($args -> {"recaptcha"}));
# Get here an the user's details are okay, register the new user.
my $methodimpl = $self -> {"session"} -> {"auth"} -> get_authmethod_module($self -> {"settings"} -> {"config"} -> {"default_authmethod"})
or return ($self -> {"template"} -> load_template("error/error.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(reason)s" => $self -> {"session"} -> {"auth"} -> errstr() }),
$args);
my ($user, $password) = $methodimpl -> create_user($args -> {"username"}, $self -> {"settings"} -> {"config"} -> {"default_authmethod"}, $args -> {"email"});
return ($self -> {"template"} -> load_template("error/error.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(reason)s" => $methodimpl -> errstr() }),
$args)
if(!$user);
# Send registration email
my $err = $self -> _signup_email($user, $password);
return ($err, $args) if($err);
# User is registered...
return ($user, $args);
}
## @method private @ validate_passchange()
# Determine whether the password change request made by the user is valid. If the
# password change is valid (passwords match, pass policy, and the old password is
@ -404,103 +546,6 @@ sub _validate_passchange {
}
## @method private @ _validate_signup()
# Determine whether the username, email, and security question provided by the user
# are valid. If they are, return true.
#
# @return The new user's record on success, an error string if the signup failed.
sub _validate_signup {
my $self = shift;
my $error = "";
my $errors = "";
my $args = {};
# User attempted self-register when it is disabled? Naughty user, no cookie!
return ($self -> {"template"} -> load_template("error/error.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(reason)s" => "{L_LOGIN_ERR_NOSELFREG}"}), $args)
unless($self -> {"settings"} -> {"config"} -> {"Login:allow_self_register"});
# Check that the username is provided and valid
($args -> {"regname"}, $error) = $self -> validate_string("regname", {"required" => 1,
"nicename" => "{L_LOGIN_USERNAME}",
"minlen" => 2,
"maxlen" => 32,
"formattest" => '^[-\w ]+$',
"formatdesc" => "{L_LOGIN_ERR_BADUSERCHAR}"
});
# Is the username valid?
if($error) {
$errors .= $self -> {"template"} -> load_template("error/error_item.tem", {"%(reason)s" => $error});
} else {
# Is the username in use?
my $user = $self -> {"session"} -> get_user($args -> {"regname"});
$errors .= $self -> {"template"} -> load_template("error/error.tem",
{ "%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(reason)s" => $self -> {"template"} -> replace_langvar("LOGIN_ERR_USERINUSE",
{ "%(url-recover)s" => $self -> build_url("block" => "login", "pathinfo" => [ "recover" ]) })
})
if($user);
}
# And the email
($args -> {"email"}, $error) = $self -> validate_string("email", {"required" => 1,
"nicename" => "{L_LOGIN_EMAIL}",
"minlen" => 2,
"maxlen" => 256
});
if($error) {
$errors .= $self -> {"template"} -> load_template("error/error_item.tem", {"%(reason)s" => $error});
} else {
# Check that the address is structured in a vaguely valid way
# Yes, this is not fully RFC compliant, but frankly going down that road invites a
# level of utter madness that would make Azathoth himself utter "I say, steady on now..."
$errors .= $self -> {"template"} -> load_template("error/error_item.tem", {"%(reason)s" => "{L_LOGIN_ERR_BADEMAIL}"})
if($args -> {"email"} !~ /^[\w.+-]+\@([\w-]+\.)+\w+$/);
# Is the email address in use?
my $user = $self -> {"session"} -> {"auth"} -> {"app"} -> get_user_byemail($args -> {"email"});
$errors .= $self -> {"template"} -> load_template("error/error_item.tem", {"%(reason)s" => $self -> {"template"} -> replace_langvar("LOGIN_ERR_EMAILINUSE",
{ "%(url-recover)s" => $self -> build_url("block" => "login", "pathinfo" => [ "recover" ]) })
})
if($user);
}
# FIXME: Validate the recaptcha code here.
($args -> {"recaptcha"}, $error) = $self -> validate_string("g-recaptcha-response", {"required" => 1,
"nicename" => "{L_LOGIN_RECAPTCHA}",
"minlen" => 2,
"maxlen" => 32,
"formattest" => '^[-\w]+$',
"formatdesc" => "{L_LOGIN_ERR_BADUSERCHAR}"
});
# Halt here if there are any problems.
return ($self -> {"template"} -> load_template("error/error_list.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(errors)s" => $errors}), $args)
if($errors);
# Get here an the user's details are okay, register the new user.
my $methodimpl = $self -> {"session"} -> {"auth"} -> get_authmethod_module($self -> {"settings"} -> {"config"} -> {"default_authmethod"})
or return ($self -> {"template"} -> load_template("error/error.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(reason)s" => $self -> {"session"} -> {"auth"} -> errstr() }),
$args);
my ($user, $password) = $methodimpl -> create_user($args -> {"regname"}, $self -> {"settings"} -> {"config"} -> {"default_authmethod"}, $args -> {"email"});
return ($self -> {"template"} -> load_template("error/error.tem", {"%(message)s" => "{L_LOGIN_ERR_REGFAILED}",
"%(reason)s" => $methodimpl -> errstr() }),
$args)
if(!$user);
# Send registration email
my $err = $self -> signup_email($user, $password);
return ($err, $args) if($err);
# User is registered...
return ($user, $args);
}
## @method private @ validate_actcode()
# Determine whether the activation code provided by the user is valid
#
@ -760,7 +805,7 @@ sub _generate_signup_form {
return ("{L_LOGIN_SIGNUP_TITLE}",
$self -> {"template"} -> load_template("login/signup.tem", {"%(error)s" => $error,
"%(sitekey)s" => $self -> {"settings"} -> {"config"} -> {"Login:self_register_sitekey"},
"%(sitekey)s" => $self -> {"settings"} -> {"config"} -> {"Login:recaptcha_sitekey"},
"%(url-activate)s" => $self -> build_url("block" => "login", "pathinfo" => [ "activate" ]),
"%(target)s" => $self -> build_url("block" => "login", "pathinfo" => [ "signup" ]),
"%(username)s" => $args -> {"username"},
@ -971,16 +1016,14 @@ sub _generate_signedup {
my $url = $self -> build_url(block => "login",
pathinfo => [ "activate" ]);
return ("{L_LOGIN_REG_DONETITLE}",
$self -> message_box("{L_LOGIN_REG_DONETITLE}",
"security",
"{L_LOGIN_REG_SUMMARY}",
"{L_LOGIN_REG_LONGDESC}",
undef,
"logincore",
[ {"message" => "{L_LOGIN_ACTIVATE}",
"colour" => "blue",
"action" => "location.href='$url'"} ]));
return ("{L_LOGIN_SIGNUP_DONETITLE}",
$self -> message_box(title => "{L_LOGIN_SIGNUP_DONETITLE}",
type => "account",
summary => "{L_LOGIN_SIGNUP_SUMMARY}",
message => "{L_LOGIN_SIGNUP_MESSAGE}",
buttons => [ {"message" => "{L_LOGIN_ACTIVATE}",
"colour" => "warning",
"href" => $url} ]));
}

View File

@ -27,32 +27,31 @@ LOGIN_ERR_BADUSERCHAR = Illegal character in username. Usernames may only contai
LOGIN_ERR_INVALID = Login failed: unknown username or password provided.
# Registration-related stuff
LOGIN_REGISTER = Sign up
LOGIN_REG_INTRO = Create an account by choosing a username and giving a valid email address. A password will be emailed to you.
LOGIN_SECURITY = Security question
LOGIN_SEC_INTRO = In order to prevent abuse by automated spamming systems, please answer the following question to prove that you are a human.<br/>Note: the answer is not case sensitive.
LOGIN_SEC_SUBMIT = Sign up
LOGIN_SIGNUP_TITLE = Sign up
LOGIN_SIGNUP_INTRO = Create an account by choosing a username and giving a valid email address. A password and activation code will be emailed to you, so you must have access to the email address.
LOGIN_SIGNUP = Sign up
LOGIN_ERR_NOSELFREG = Self-registration is not currently permitted.
LOGIN_ERR_REGFAILED = Registration failed
LOGIN_ERR_BADSECURE = You did not answer the security question correctly, please check your answer and try again.
LOGIN_ERR_BADRECAPTCHA = reCAPTCHA information in your request was missing or corrupt
LOGIN_ERR_RECAPTCHA = The reCAPTCHA verifcation step failed, please try again
LOGIN_ERR_BADEMAIL = The specified email address does not appear to be valid.
LOGIN_ERR_USERINUSE = The specified username is already in use. If you can't remember your password, <strong>please use the <a href="%(url-recover)s">account recovery</a> facility</strong> rather than attempt to make a new account.
LOGIN_ERR_EMAILINUSE = The specified email address is already in use. If you can't remember your username or password, <strong>please use the <a href="%(url-recover)s">account recovery</a> facility</strong> rather than attempt to make a new account.
LOGIN_ERR_INACTIVE = Your account is currently inactive. Please check your email for an 'Activation Required' email and follow the link it contains to activate your account. If you have not received an actication email, or need a new one, <a href="%(url-resend)s">request a new activation email</a>.
# Registration done
LOGIN_REG_DONETITLE = Registration successful
LOGIN_REG_SUMMARY = Activation required!
LOGIN_REG_LONGDESC = A new user account has been created for you, and an email has been sent to you with your new account password and an activation link.<br /><br />Please check your email for a message with the subject '{V_[sitename]} account created - Activation required!' and follow the instructions it contains to activate your account.
LOGIN_SIGNUP_DONETITLE = Signup successful
LOGIN_SIGNUP_SUMMARY = Activation required!
LOGIN_SIGNUP_MESSAGE = A new user account has been created for you, and an email has been sent to you with your new account password and an activation link.<br /><br />Please check your email for a message with the subject '{V_[sitename]} account created - Activation required!' and follow the instructions it contains to activate your account.
# Registration email
LOGIN_REG_SUBJECT = {V_[sitename]} account created - Activation required!
LOGIN_REG_GREETING = Hi %(username)s
LOGIN_REG_CREATED = A new account in the {V_[sitename]} system has just been created for you. Your username and password for the system are given below.
LOGIN_REG_ACTNEEDED = Before you can sign in, you must activate your account. To activate your account, please click on the following link, or copy and paste it into your web browser:
LOGIN_REG_ALTACT = Alternatively, enter the following code in the account activation form:
LOGIN_REG_ENJOY = Thank you for registering!
LOGIN_SIGNUP_SUBJECT = {V_[sitename]} account created - Activation required!
LOGIN_SIGNUP_GREETING = Hi %(username)s
LOGIN_SIGNUP_CREATED = A new account in the {V_[sitename]} system has just been created for you. Your username and password for the system are given below.
LOGIN_SIGNUP_ACTNEEDED = Before you can sign in, you must activate your account. To activate your account, please click on the following link, or copy and paste it into your web browser:
LOGIN_SIGNUP_ALTACT = Alternatively, enter the following code in the account activation form:
LOGIN_SIGNUP_ENJOY = Thank you for registering!
# Activation related
LOGIN_ACTCODE = Activation code

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -2,7 +2,7 @@
%(error)s
<!-- Start signup form -->
<div class="signupform callout secondary">
<div>{L_LOGIN_REG_INTRO}</div>
<div>{L_LOGIN_SIGNUP_INTRO}</div>
<form id="signinform" method="post" action="%(target)s">
<div class="entry">
<label for="username">{L_LOGIN_USERNAME}:<br />
@ -17,7 +17,7 @@
<div class="contextlink">(<a href="%(url-activate)s">{L_LOGIN_ACTFORM}</a>)</div>
<div class="g-recaptcha" data-sitekey="%(sitekey)s"></div>
<div class="submit clearfix">
<input type="submit" class="button float-right nomargin" id="signup" name="signup" value="{L_LOGIN_REGISTER}" />
<input type="submit" class="button float-right nomargin" id="signup" name="signup" value="{L_LOGIN_SIGNUP}" />
</div>
</form>
</div>

View File

@ -1 +1,2 @@
<script src='https://www.google.com/recaptcha/api.js'></script>
<link rel="stylesheet" href="{V_[csspath]}login.css" />