Skip to content

Commit

Permalink
Merge branch 'release/3.7.8'
Browse files Browse the repository at this point in the history
  • Loading branch information
rhukster committed Apr 16, 2024
2 parents e2c5fc5 + fafc8f4 commit f94bbcc
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 16 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# v3.7.8
## 04/16/2024

1. [](#improved)
* Use `random_bytes()` for password reset and activation, only fallback to `mt_rand()` if there's a generation error
* Added a new `site_host` field in the "Security" section to use in password reset and activation links sent in email. This allows you to avoid any "Password Reset Poisoning" attacks.
* Added a new warning in reset and activation emails that shows the "site host" clearly in order to avoid any nefariously sent emails.

# v3.7.7
## 01/05/2024

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ dynamic_page_visibility: false # Integrate access into page visibil
parent_acl: false # Look to parent `access` rules for access requirements
protect_protected_page_media: false # Take `access` rules into account when directly accessing a page's media

site_host: # Optionally used in password reset and activation emails, to avoid "password poisoning attacks", this should be the URL of your site including the protocol. e.g. https://foo.com

rememberme:
enabled: true # Enable 'remember me' functionality
timeout: 604800 # Timeout in seconds. Defaults to 1 week
Expand Down Expand Up @@ -427,6 +429,10 @@ user_registration:
send_welcome_email: false # Send a welcome email to the user (probably should not be used with `send_activation_email`
```

## Email Security Considerations

For increased security and to deter users from being tricked into resetting their passwords or activating their accounts on 'fake' sites utilizing a [Password Poisoning Attack](https://portswigger.net/web-security/host-header/exploiting/password-reset-poisoning), you can now set the `site_host` property in the "Security" tab of the login properties, (e.g. `https://foo.com`) to ensure the users are sent to the original site only.

## Sending an activation email

By default the registration process adds a new user, and sets it as enabled.
Expand Down
8 changes: 7 additions & 1 deletion blueprints.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Login
slug: login
type: plugin
version: 3.7.7
version: 3.7.8
testing: false
description: Enables user authentication and login screen.
icon: sign-in
Expand Down Expand Up @@ -411,6 +411,12 @@ form:
title: PLUGIN_LOGIN.SECURITY_TAB

fields:
site_host:
type: text
size: medium
label: PLUGIN_LOGIN.SITE_HOST
help: PLUGIN_LOGIN.SITE_HOST_HELP
placeholder: "https://example.com"
max_pw_resets_count:
type: number
size: x-small
Expand Down
10 changes: 8 additions & 2 deletions classes/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -389,8 +389,14 @@ protected function taskForgot()
return true;
}

$token = md5(uniqid((string)mt_rand(), true));
$expire = time() + 604800; // next week
try {
$random_bytes = random_bytes(16);
} catch (\Exception $e) {
$random_bytes = mt_rand();
}

$token = md5(uniqid($random_bytes, true));
$expire = time() + 86400; // 24 hours

$user->reset = $token . '::' . $expire;
$user->save();
Expand Down
31 changes: 20 additions & 11 deletions classes/Email.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Grav\Common\Config\Config;
use Grav\Common\Grav;
use Grav\Common\Language\Language;
use Grav\Common\Page\Pages;
use Grav\Common\User\Interfaces\UserInterface;
use Grav\Common\Utils;
use Grav\Plugin\Login\Invitations\Invitation;
Expand Down Expand Up @@ -39,9 +38,12 @@ public static function sendActivationEmail(UserInterface $user, UserInterface $a
throw new \RuntimeException('User activation route does not exist!');
}

/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$activationLink = $pages->url(
$site_host = $config->get('plugins.login.site_host');
if (!empty($site_host)) {
$activationRoute = rtrim($site_host, '/') . '/' . ltrim($activationRoute, '/');
}

$activationLink = Utilis::url(
$activationRoute . '/token' . $param_sep . $token . '/username' . $param_sep . $user->username,
null,
true
Expand Down Expand Up @@ -89,11 +91,14 @@ public static function sendResetPasswordEmail(UserInterface $user, UserInterface
throw new \RuntimeException('Password reset route does not exist!');
}

/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$resetLink = $pages->url(
$site_host = static::getConfig()->get('plugins.login.site_host');
if (!empty($site_host)) {
$resetRoute = rtrim($site_host, '/') . '/' . ltrim($resetRoute, '/');
}

$resetLink = Utils::url(
"{$resetRoute}/task{$param_sep}login.reset/token{$param_sep}{$token}/user{$param_sep}{$user->username}/nonce{$param_sep}" . Utils::getNonce('reset-form'),
null,
true,
true
);

Expand Down Expand Up @@ -190,9 +195,7 @@ public static function sendInvitationEmail(Invitation $invitation, string $messa
throw new \RuntimeException('User registration route does not exist!');
}

/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$invitationLink = $pages->url("{$inviteRoute}/{$param_sep}{$invitation->token}", null, true);
$invitationLink = Utils::url("{$inviteRoute}/{$param_sep}{$invitation->token}", true, true);

$context = [
'invitation_link' => $invitationLink,
Expand All @@ -218,11 +221,17 @@ protected static function sendEmail(string $template, array $context, array $par

$config = static::getConfig();

$site_host = $config->get('plugins.login.site_host');
if (empty($site_host)) {
$site_host = Grav::instance()['uri']->host();
}

// Twig context.
$context += [
'actor' => $actor,
'user' => $user,
'site_name' => $config->get('site.title', 'Website'),
'site_host' => $site_host,
'author' => $config->get('site.author.name', ''),
];

Expand Down
8 changes: 7 additions & 1 deletion classes/Login.php
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,13 @@ public function sendActivationEmail(UserInterface $user)
throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD'));
}

$token = md5(uniqid(mt_rand(), true));
try {
$random_bytes = random_bytes(16);
} catch (\Exception $e) {
$random_bytes = mt_rand();
}

$token = md5(uniqid($random_bytes, true));
$expire = time() + 604800; // next week
$user->activation_token = $token . '::' . $expire;
$user->save();
Expand Down
5 changes: 4 additions & 1 deletion languages/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,7 @@ PLUGIN_LOGIN:
INVITATION_EMAIL_MESSAGE: "We welcome you to register an account to on site."
INVALID_INVITE_EMAILS: "<strong>Error:</strong> An invalid list of emails was provided"
INVALID_FORM: "<strong>Error:</strong> Invalid form"
FAILED_TO_SEND_EMAILS: "Failed to send emails to: %s"
FAILED_TO_SEND_EMAILS: "Failed to send emails to: %s"
HOST_WARNING: '<div style="background-color: #FFEDAD; color: #725F1C; border: 1px solid #FFD74E; padding: 10px; margin: 10px 0; border-radius: 5px;">NOTE: If you did not initiate this email or you don''t recognize the originating site: <strong>"%s"</strong> please ignore or delete this email.</div>'
SITE_HOST: "Site Host"
SITE_HOST_HELP: "For extra security, force this URL to be used in all password reset and activation emails. Leave empty to use the default site URL"
1 change: 1 addition & 0 deletions templates/emails/login/activate.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{%- do email.message.setSubject(subject) %}

{%- block content -%}
{{ 'PLUGIN_LOGIN.HOST_WARNING'|t(site_host)|raw }}
{{ 'PLUGIN_LOGIN.ACTIVATION_EMAIL_BODY'|t(user.fullname, activation_link, site_name, author)|raw }}
{%- endblock content -%}

Expand Down
1 change: 1 addition & 0 deletions templates/emails/login/reset-password.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{%- do email.message.setSubject(subject) %}

{%- block content -%}
{{ 'PLUGIN_LOGIN.HOST_WARNING'|t(site_host)|raw }}
{{ 'PLUGIN_LOGIN.FORGOT_EMAIL_BODY'|t(user.fullname ?? user.username, reset_link, author, site_name)|raw }}
{%- endblock content -%}

Expand Down

0 comments on commit f94bbcc

Please sign in to comment.