Preface#
To better protect our Typecho blog, I chose to add a Google reCAPTCHA verification service to the Typecho backend login/registration interface.
Tutorial#
Obtain reCAPTCHA#
Note: This step requires a Google account and a VPN. If you don't have the means, you can privately comment in the comment section to ask the blogger for help (the blogger will send the required content through the comment section and the email reserved during commenting) :::
First, open the [reCAPTCHA official website][1].
![1][2]
Then click on v3 Admin Console at the top.
Log in to your Google account.
![2][3]
Then, open [this][4] link.
![3][5]
Label, write anything (for example, a certain open-world game).
reCAPTCHA Type, select reCAPTCHA v2 → "I'm not a robot" checkbox.
Scroll down to find Domain, fill in your blog's domain, and you can add several more if needed.
Then scroll to the bottom and click Submit.
You will then receive your SiteKey and Secret Key.
![4][6]
Make sure to save these two keys, as you will need them later.
Install reCAPTCHA#
Since there are many things to change in the backend, for convenience, simply overwrite the following content.
Login Page#
Overwrite the following content into /admin/login.php.
<?php
include 'common.php';
if ($user->hasLogin()) {
$response->redirect($options->adminUrl);
}
$rememberName = htmlspecialchars(Typecho_Cookie::get('__typecho_remember_name'));
Typecho_Cookie::delete('__typecho_remember_name');
$bodyClass = 'body-100';
include 'header.php';
?>
<div class="typecho-login-wrap">
<div class="typecho-login">
<h1><a href="http://typecho.org" class="i-logo">Typecho</a></h1>
<form action="<?php $options->loginAction(); ?>" method="post" name="login" role="form">
<p>
<label for="name" class="sr-only"><?php _e('Username'); ?></label>
<input type="text" id="name" name="name" value="<?php echo $rememberName; ?>" placeholder="<?php _e('Username'); ?>" class="text-l w-100" autofocus />
</p>
<p>
<label for="password" class="sr-only"><?php _e('Password'); ?></label>
<input type="password" id="password" name="password" class="text-l w-100" placeholder="<?php _e('Password'); ?>" />
</p>
<p class="submit">
<button type="submit" class="btn btn-l w-100 primary"><?php _e('Login'); ?></button>
<input type="hidden" name="referer" value="<?php echo htmlspecialchars($request->get('referer')); ?>" />
</p>
<script src="https://www.recaptcha.net/recaptcha/api.js"></script> <!-- google -->
<p>
<div class="g-recaptcha" data-sitekey="Enter your SITE key here"></div><!-- google -->
</p>
<p>
<p>
<label for="remember"><input type="checkbox" name="remember" class="checkbox" value="1" id="remember" /> <?php _e('Remember me'); ?></label>
</p>
</form>
<p class="more-link">
<a href="<?php $options->siteUrl(); ?>"><?php _e('Return to homepage'); ?></a>
<?php if($options->allowRegister): ?>
•
<a href="<?php $options->registerUrl(); ?>"><?php _e('User Registration'); ?></a>
<?php endif; ?>
</p>
</div>
</div>
<?php
include 'common-js.php';
?>
<script>
$(document).ready(function () {
$('#name').focus();
});
</script>
<?php
include 'footer.php';
?>
Overwrite the following content into /var/Widget/Login.php.
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* Login action
*
* @category typecho
* @package Widget
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
* @version $Id$
*/
/**
* Login component
*
* @category typecho
* @package Widget
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
*/
/**
* reCAPTCHA
*
* By BLxcwg666
* https://blog.xcnya.cn
* [email protected]
*/
class Paul_GCaptcha {
public static $success, $failed;
// Send verification information
public static function send($post_data) {
$postdata = http_build_query($post_data);
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' => $postdata,
'timeout' => 15 * 60 // Timeout
)
);
$context = stream_context_create($options);
$result = file_get_contents("https://recaptcha.net/recaptcha/api/siteverify", false, $context);
return $result;
}
// Check verification status
public static function check(){
if($_POST["g-recaptcha-response"]){
$data = array(
'secret' => 'Enter your SECRET key here',
'response' => $_POST["g-recaptcha-response"] // Receive user-submitted verification data
);
$result = self::send($data);
$result = json_decode($result, true);
$result = $result["success"];
if($result == true){
return true; // Verification successful
}
else{
return false; // Verification failed
}
}
else{
return false; // User did not submit verification information
}
}
}
class Widget_Login extends Widget_Abstract_Users implements Widget_Interface_Do
{
/**
* Initialization function
*
* @access public
* @return void
*/
public function action()
{
// protect
$this->security->protect();
/** If already logged in */
if ($this->user->hasLogin()) {
/** Redirect directly */
$this->response->redirect($this->options->index);
}
/** Initialize verification class */
$validator = new Typecho_Validate();
$validator->addRule('name', 'required', _t('Please enter username'));
$validator->addRule('password', 'required', _t('Please enter password'));
/** Intercept verification exceptions */
if ($error = $validator->run($this->request->from('name', 'password'))) {
Typecho_Cookie::set('__typecho_remember_name', $this->request->name);
/** Set prompt information */
$this->widget('Widget_Notice')->set($error);
$this->response->goBack();
}
if(Paul_GCaptcha::check() == true){
/** Start verifying user **/
$valid = $this->user->login($this->request->name, $this->request->password,
false, 1 == $this->request->remember ? $this->options->time + $this->options->timezone + 30*24*3600 : 0);
/** Compare password */
if (!$valid) {
/** Prevent enumeration, sleep for 3 seconds */
sleep(3);
$this->pluginHandle()->loginFail($this->user, $this->request->name,
$this->request->password, 1 == $this->request->remember);
Typecho_Cookie::set('__typecho_remember_name', $this->request->name);
$this->widget('Widget_Notice')->set(_t('Invalid username or password'), 'error');
$this->response->goBack('?referer=' . urlencode($this->request->referer));
}
$this->pluginHandle()->loginSucceed($this->user, $this->request->name,
$this->request->password, 1 == $this->request->remember);
/** Redirect to verification address */
if (NULL != $this->request->referer) {
$this->response->redirect($this->request->referer);
} else if (!$this->user->pass('contributor', true)) {
/** Ordinary users are not allowed to directly jump to the backend */
$this->response->redirect($this->options->profileUrl);
} else {
$this->response->redirect($this->options->adminUrl);
}
}else{
$this->widget('Widget_Notice')->set(_t('Verification failed'),'error');
$this->response->goBack('?referer=' . urlencode($this->request->referer));
}
}
}
Replace the marked fields with your own SiteKey and Secret Key.
Registration Page#
Overwrite the following content into /admin/register.php.
<?php
include 'common.php';
if ($user->hasLogin() || !$options->allowRegister) {
$response->redirect($options->siteUrl);
}
$rememberName = htmlspecialchars(Typecho_Cookie::get('__typecho_remember_name'));
$rememberMail = htmlspecialchars(Typecho_Cookie::get('__typecho_remember_mail'));
Typecho_Cookie::delete('__typecho_remember_name');
Typecho_Cookie::delete('__typecho_remember_mail');
$bodyClass = 'body-100';
include 'header.php';
?>
<div class="typecho-login-wrap">
<div class="typecho-login">
<h1><a href="http://typecho.org" class="i-logo">Typecho</a></h1>
<form action="<?php $options->registerAction(); ?>" method="post" name="register" role="form">
<p>
<label for="name" class="sr-only"><?php _e('Username'); ?></label>
<input type="text" id="name" name="name" placeholder="<?php _e('Username'); ?>" value="<?php echo $rememberName; ?>" class="text-l w-100" autofocus />
</p>
<p>
<label for="mail" class="sr-only"><?php _e('Email'); ?></label>
<input type="email" id="mail" name="mail" placeholder="<?php _e('Email'); ?>" value="<?php echo $rememberMail; ?>" class="text-l w-100" />
</p>
<div class="g-recaptcha" data-sitekey="Enter your SITE key here"></div>
</p>
<p class="submit">
<button type="submit" class="btn btn-l w-100 primary"><?php _e('Register'); ?></button>
</p>
<script src="https://www.recaptcha.net/recaptcha/api.js"></script>
<p>
</form>
<p class="more-link">
<a href="<?php $options->siteUrl(); ?>"><?php _e('Return to homepage'); ?></a>
•
<a href="<?php $options->adminUrl('login.php'); ?>"><?php _e('User Login'); ?></a>
</p>
</div>
</div>
<?php
include 'common-js.php';
?>
<script>
$(document).ready(function () {
$('#name').focus();
});
</script>
<?php
include 'footer.php';
?>
Overwrite the following content into /var/Widget/Register.php.
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* Registration component
*
* @author qining
* @category typecho
* @package Widget
*/
/**
* reCAPTCHA
*
* By BLxcwg666
* https://blog.xcnya.cn
* [email protected]
*/
class Paul_GCaptcha {
public static $success, $failed;
// Send verification information
public static function send($post_data) {
$postdata = http_build_query($post_data);
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type:application/x-www-form-urlencoded',
'content' => $postdata,
'timeout' => 15 * 60 // Timeout
)
);
$context = stream_context_create($options);
$result = file_get_contents("https://recaptcha.net/recaptcha/api/siteverify", false, $context);
return $result;
}
// Check verification status
public static function check(){
if($_POST["g-recaptcha-response"]){
$data = array(
'secret' => 'Enter your SECRET key here',
'response' => $_POST["g-recaptcha-response"] // Receive user-submitted verification data
);
$result = self::send($data);
$result = json_decode($result, true);
$result = $result["success"];
if($result == true){
return true; // Verification successful
}
else{
return false; // Verification failed
}
}
else{
return false; // User did not submit verification information
}
}
}
class Widget_Register extends Widget_Abstract_Users implements Widget_Interface_Do
{
/**
* Initialization function
*
* @access public
* @return void
*/
public function action()
{
// protect
$this->security->protect();
/** If already logged in */
if ($this->user->hasLogin() || !$this->options->allowRegister) {
/** Redirect directly */
$this->response->redirect($this->options->index);
}
/** Initialize verification class */
$validator = new Typecho_Validate();
$validator->addRule('name', 'required', _t('Username is required'));
$validator->addRule('name', 'minLength', _t('Username must be at least 2 characters'), 2);
$validator->addRule('name', 'maxLength', _t('Username can be at most 32 characters'), 32);
$validator->addRule('name', 'xssCheck', _t('Please do not use special characters in the username'));
$validator->addRule('name', array($this, 'nameExists'), _t('Username already exists'));
$validator->addRule('mail', 'required', _t('Email is required'));
$validator->addRule('mail', array($this, 'mailExists'), _t('Email address already exists'));
$validator->addRule('mail', 'email', _t('Invalid email format'));
$validator->addRule('mail', 'maxLength', _t('Email can be at most 200 characters'), 200);
/** If there is a password in the request */
if (array_key_exists('password', $_REQUEST)) {
$validator->addRule('password', 'required', _t('Password is required'));
$validator->addRule('password', 'minLength', _t('To ensure account security, please enter at least six characters for the password'), 6);
$validator->addRule('password', 'maxLength', _t('For easier memory, please do not exceed eighteen characters for the password'), 18);
$validator->addRule('confirm', 'confirm', _t('The two passwords do not match'), 'password');
}
/** Intercept verification exceptions */
if ($error = $validator->run($this->request->from('name', 'password', 'mail', 'confirm'))) {
Typecho_Cookie::set('__typecho_remember_name', $this->request->name);
Typecho_Cookie::set('__typecho_remember_mail', $this->request->mail);
/** Set prompt information */
$this->widget('Widget_Notice')->set($error);
$this->response->goBack();
}
if(Paul_GCaptcha::check() == true){
$hasher = new PasswordHash(8, true);
$generatedPassword = Typecho_Common::randString(7);
$dataStruct = array(
'name' => $this->request->name,
'mail' => $this->request->mail,
'screenName'=> $this->request->name,
'password' => $hasher->HashPassword($generatedPassword),
'created' => $this->options->time,
'group' => 'subscriber'
);
$dataStruct = $this->pluginHandle()->register($dataStruct);
$insertId = $this->insert($dataStruct);
$this->db->fetchRow($this->select()->where('uid = ?', $insertId)
->limit(1), array($this, 'push'));
$this->pluginHandle()->finishRegister($this);
$this->user->login($this->request->name, $generatedPassword);
Typecho_Cookie::delete('__typecho_first_run');
Typecho_Cookie::delete('__typecho_remember_name');
Typecho_Cookie::delete('__typecho_remember_mail');
$this->widget('Widget_Notice')->set(_t('User <strong>%s</strong> has successfully registered, password is <strong>%s</strong>', $this->screenName, $generatedPassword), 'success');
$this->response->redirect($this->options->adminUrl);
}else{
$this->widget('Widget_Notice')->set(_t('Verification failed'),'error');
$this->response->goBack('?referer=' . urlencode($this->request->referer));
}
}
}
Replace the marked fields with your own SiteKey and Secret Key.
Done! Open the website backend, Enjoy!
![5][7]
I added a dark tag here; please refer to the [reCAPTCHA documentation][8] if needed.
Aftermath#
Note: If you are using the Handsome theme like me, to implement frontend login, you need to do the following steps (no need for non-Handsome themes) :::
Open the backend, Appearance Settings → Developer Settings → Custom Output HTML Code at the end of the body, enter the following content:
<script src="https://www.recaptcha.net/recaptcha/api.js" async defer></script>
Then in /usr/themes/handsome/component/headnav.php, add the following content on a new line around line 369 (Handsome 9.0.2):
<div class="g-recaptcha" style="transform: scale(0.827); -webkit-transform: scale(0.827); transform-origin: 0 0; -webkit-transform-origin: 0 0;" data-sitekey="Enter your SITE key here"></div>
Replace the marked fields with your own SiteKey and Secret Key.
If user registration is enabled, you also need to add the same content on a new line around line 390 in the same file (Handsome 9.0.2):
<div class="g-recaptcha" style="transform: scale(0.827); -webkit-transform: scale(0.827); transform-origin: 0 0; -webkit-transform-origin: 0 0;" data-sitekey="Enter your SITE key here"></div>
Replace the marked fields with your own SiteKey and Secret Key.
If there are other versions with mismatched line numbers and you cannot modify them yourself, please send the PHP file to the site owner via email for assistance.
Great job! Log out of the website, refresh the homepage, Enjoy!
Looking Ahead#
Known issue: The reCAPTCHA on the Handsome frontend login remains white in dark mode, and there is currently no solution. Suggestions are welcome in the comment section.
Postscript#
After repeated testing in the backend, everything is normal.
The reCAPTCHA backend also has relevant records.
Thus, the deployment of reCAPTCHA in Typecho is successfully completed!
This article is synchronized and updated by Mix Space to xLog. The original link is https://blog.nekorua.com/posts/coding/26.html