mirror of
https://github.com/Piwigo/Piwigo.git
synced 2026-03-28 17:42:57 +01:00
fixes GHSA-9986-w7jf-33f6 and fixes GHSA-9986-w7jf-33f6
* Introduces a verification code step before generating password reset links. * New configuration "password_reset_code_duration". * Adds Base32, TOTP and PHPQRCode classes . * New section is required in password.tpl: code verification won't work on themes not updated yet. * 5 new language strings were added.
This commit is contained in:
@@ -24,6 +24,8 @@ if (!is_a_guest())
|
|||||||
|
|
||||||
trigger_notify('loc_begin_identification');
|
trigger_notify('loc_begin_identification');
|
||||||
|
|
||||||
|
unset($_SESSION['reset_password_code']);
|
||||||
|
|
||||||
//-------------------------------------------------------------- identification
|
//-------------------------------------------------------------- identification
|
||||||
|
|
||||||
// security (level 1): the redirect must occur within Piwigo, so the
|
// security (level 1): the redirect must occur within Piwigo, so the
|
||||||
|
|||||||
85
include/base32.class.php
Normal file
85
include/base32.class.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
defined('PHPWG_ROOT_PATH') or die('Hacking attempt!');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode in Base32 based on RFC 4648.
|
||||||
|
* Requires 20% more space than base64
|
||||||
|
* Great for case-insensitive filesystems like Windows and URL's (except for = char which can be excluded using the pad option for urls)
|
||||||
|
*
|
||||||
|
* @author Bryan Ruiz
|
||||||
|
* @url https://www.php.net/manual/en/function.base-convert.php#102232
|
||||||
|
**/
|
||||||
|
class PwgBase32 {
|
||||||
|
private static $map = array(
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
|
||||||
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
|
||||||
|
'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
|
||||||
|
'=' // padding char
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $flippedMap = array(
|
||||||
|
'A'=>'0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7',
|
||||||
|
'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15',
|
||||||
|
'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23',
|
||||||
|
'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use padding false when encoding for urls
|
||||||
|
*
|
||||||
|
* @return base32 encoded string
|
||||||
|
**/
|
||||||
|
public static function encode($input, $padding = true)
|
||||||
|
{
|
||||||
|
if (empty($input)) return "";
|
||||||
|
$input = str_split($input);
|
||||||
|
$binaryString = "";
|
||||||
|
for ($i = 0; $i < count($input); $i++) {
|
||||||
|
$binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
$fiveBitBinaryArray = str_split($binaryString, 5);
|
||||||
|
$base32 = "";
|
||||||
|
$i = 0;
|
||||||
|
while ($i < count($fiveBitBinaryArray)) {
|
||||||
|
$base32 .= self::$map[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)];
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
if ($padding && ($x = strlen($binaryString) % 40) != 0) {
|
||||||
|
if ($x == 8) $base32 .= str_repeat(self::$map[32], 6);
|
||||||
|
else if ($x == 16) $base32 .= str_repeat(self::$map[32], 4);
|
||||||
|
else if ($x == 24) $base32 .= str_repeat(self::$map[32], 3);
|
||||||
|
else if ($x == 32) $base32 .= self::$map[32];
|
||||||
|
}
|
||||||
|
return $base32;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function decode($input)
|
||||||
|
{
|
||||||
|
if (empty($input)) return;
|
||||||
|
$paddingCharCount = substr_count($input, self::$map[32]);
|
||||||
|
$allowedValues = array(6, 4, 3, 1, 0);
|
||||||
|
if (!in_array($paddingCharCount, $allowedValues)) return false;
|
||||||
|
for ($i = 0; $i < 4; $i++) {
|
||||||
|
if (
|
||||||
|
$paddingCharCount == $allowedValues[$i] &&
|
||||||
|
substr($input, - ($allowedValues[$i])) != str_repeat(self::$map[32], $allowedValues[$i])
|
||||||
|
) return false;
|
||||||
|
}
|
||||||
|
$input = str_replace('=', '', $input);
|
||||||
|
$input = str_split($input);
|
||||||
|
$binaryString = "";
|
||||||
|
for ($i = 0; $i < count($input); $i = $i + 8) {
|
||||||
|
$x = "";
|
||||||
|
if (!in_array($input[$i], self::$map)) return false;
|
||||||
|
for ($j = 0; $j < 8; $j++) {
|
||||||
|
$x .= str_pad(base_convert(@self::$flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
$eightBits = str_split($x, 8);
|
||||||
|
for ($z = 0; $z < count($eightBits); $z++) {
|
||||||
|
$binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $binaryString;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -635,6 +635,11 @@ $conf['password_reset_duration'] = 60*60;
|
|||||||
// of an password activation link. Default value is 72 hours (259200 seconds).
|
// of an password activation link. Default value is 72 hours (259200 seconds).
|
||||||
$conf['password_activation_duration'] = 3*24*60*60;
|
$conf['password_activation_duration'] = 3*24*60*60;
|
||||||
|
|
||||||
|
// password_reset_code_duration: defines the validity duration (in seconds)
|
||||||
|
// for the verification code sent before genrating the reset link.
|
||||||
|
// Default value is 5 minutes (max = 15 minutes)
|
||||||
|
$conf['password_reset_code_duration'] = 5 * 60;
|
||||||
|
|
||||||
// +-----------------------------------------------------------------------+
|
// +-----------------------------------------------------------------------+
|
||||||
// | history |
|
// | history |
|
||||||
// +-----------------------------------------------------------------------+
|
// +-----------------------------------------------------------------------+
|
||||||
|
|||||||
@@ -1077,6 +1077,33 @@ function pwg_generate_set_password_mail($username, $set_password_link, $gallery_
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate content mail for user code verification
|
||||||
|
*
|
||||||
|
* Return the content mail to send
|
||||||
|
* @since 16
|
||||||
|
* @param string $code
|
||||||
|
* @return array mail content
|
||||||
|
*/
|
||||||
|
function pwg_generate_code_verification_mail($code)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
set_make_full_url();
|
||||||
|
$message = '<p style="margin: 20px 0">';
|
||||||
|
$message.= l10n('Here is your verification code:').' <br />';
|
||||||
|
$message.= '<span style="font-size: 16px">'. $code .'</span></p>';
|
||||||
|
$message.= '<p style="margin: 20px 0;">';
|
||||||
|
$message.= l10n('If this was a mistake, just ignore this email and nothing will happen.') . '</p>';
|
||||||
|
unset_make_full_url();
|
||||||
|
|
||||||
|
$subject = '['.$conf['gallery_title'].'] '.l10n('Your verification code');
|
||||||
|
return array(
|
||||||
|
'subject' => $subject,
|
||||||
|
'content' => $message,
|
||||||
|
'content_format' => 'text/html',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
trigger_notify('functions_mail_included');
|
trigger_notify('functions_mail_included');
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -2708,4 +2708,40 @@ function notification_api_key_expiration($username, $email, $days_left)
|
|||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an user code for verification
|
||||||
|
*
|
||||||
|
* @since 16
|
||||||
|
* @return array [$secret, $code]
|
||||||
|
*/
|
||||||
|
function generate_user_code()
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
require_once(PHPWG_ROOT_PATH . 'include/totp.class.php');
|
||||||
|
$secret = PwgTOTP::generateSecret();
|
||||||
|
$code = PwgTOTP::generateCode($secret, min($conf['password_reset_code_duration'], 900)); // max 15 minutes
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'secret' => $secret,
|
||||||
|
'code' => $code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify user code
|
||||||
|
*
|
||||||
|
* @since 16
|
||||||
|
* @param string $secret
|
||||||
|
* @param string $code
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function verify_user_code($secret, $code)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
require_once(PHPWG_ROOT_PATH . 'include/totp.class.php');
|
||||||
|
return PwgTOTP::verifyCode($code, $secret, min($conf['password_reset_code_duration'], 900), 1);
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
3311
include/phpqrcode.php
Normal file
3311
include/phpqrcode.php
Normal file
File diff suppressed because it is too large
Load Diff
115
include/totp.class.php
Normal file
115
include/totp.class.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
defined('PHPWG_ROOT_PATH') or die('Hacking attempt!');
|
||||||
|
|
||||||
|
require_once(PHPWG_ROOT_PATH . 'include/base32.class.php');
|
||||||
|
|
||||||
|
class PwgTOTP
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Base32 secret for TOTP
|
||||||
|
*
|
||||||
|
* @param string $secret Base32-encoded secret
|
||||||
|
* @param int $timestamp 30s intervasl since 1970
|
||||||
|
* @return string TOTP Code
|
||||||
|
*/
|
||||||
|
private static function generateCodeFromTimestamp($secret, $timestamp)
|
||||||
|
{
|
||||||
|
$key = PwgBase32::decode($secret);
|
||||||
|
|
||||||
|
$msg = pack('N*', 0) . pack('N*', $timestamp); // hash_hmac need this form
|
||||||
|
$hash = hash_hmac('sha1', $msg, $key, true);
|
||||||
|
|
||||||
|
// RFC 4226, section 5.3
|
||||||
|
$offset = ord(substr($hash, -1)) & 0x0F;
|
||||||
|
$part = substr($hash, $offset, 4);
|
||||||
|
$number = unpack('N', $part)[1] & 0x7FFFFFFF;
|
||||||
|
|
||||||
|
$code = $number % 1000000; // code 6 digits $number % 10^6
|
||||||
|
return str_pad((string)$code, 6, '0', STR_PAD_LEFT); // 123 become 000123
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Base32 secret for TOTP
|
||||||
|
*
|
||||||
|
* @param int $length Length in bytes (default: 20)
|
||||||
|
* @return string Base32-encoded secret
|
||||||
|
*/
|
||||||
|
public static function generateSecret($length = 20)
|
||||||
|
{
|
||||||
|
$random = random_bytes($length);
|
||||||
|
return PwgBase32::encode($random, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Otp auth url
|
||||||
|
*
|
||||||
|
* @param string $secret Encoded base32 secret
|
||||||
|
* @return string otpauth://totp/ url
|
||||||
|
*/
|
||||||
|
public static function getOtpAuthUrl($secret)
|
||||||
|
{
|
||||||
|
global $user;
|
||||||
|
$url = substr(get_absolute_root_url(), 0, -1);
|
||||||
|
return 'otpauth://totp/'.$user['username'].':'.$url.'?secret='.$secret.'&issuer=Piwigo&algorithm=sha1&digits=6&period=30';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Qr Code
|
||||||
|
*
|
||||||
|
* @param string $secret Encoded base32 secret
|
||||||
|
* @return string data:image/png;base64..
|
||||||
|
*/
|
||||||
|
public static function getQrCode($secret)
|
||||||
|
{
|
||||||
|
// require_once(TF_REALPATH . 'include/phpqrcode.php');
|
||||||
|
// $otp_url = self::getOtpAuthUrl($secret);
|
||||||
|
|
||||||
|
// ob_start();
|
||||||
|
// QRcode::png($otp_url);
|
||||||
|
// $qrcode_image = ob_get_clean();
|
||||||
|
// $base64_qrcode = base64_encode($qrcode_image);
|
||||||
|
// return 'data:image/png;base64,' . $base64_qrcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a TOTP Code
|
||||||
|
*
|
||||||
|
* @param string $secret Encoded base32 secret
|
||||||
|
* @param int $timestamp timestamp used in second (default: 30)
|
||||||
|
* @return string 6 digits TOTP code
|
||||||
|
*/
|
||||||
|
public static function generateCode($secret, $timestamp = 30)
|
||||||
|
{
|
||||||
|
$timestamp = floor(time() / $timestamp); // e.g 58338889 > 30-second intervals since 1970 at the moment T
|
||||||
|
return self::generateCodeFromTimestamp($secret, $timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify TOTP Code
|
||||||
|
*
|
||||||
|
* @param string $code Digits 6 TOTP Code
|
||||||
|
* @param string $secret Encoded base32 secret
|
||||||
|
* @param int $timestamp timestamp used in second (default: 30)
|
||||||
|
* @param int $check_interval Number of 30s steps to check before/after current (default: 1)
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function verifyCode($code, $secret, $timestamp = 30, $check_interval = 1)
|
||||||
|
{
|
||||||
|
$timestamp = floor(time() / $timestamp);
|
||||||
|
|
||||||
|
// generate a totp code for 30s intervals
|
||||||
|
// following or preceding the current one and check it
|
||||||
|
for ($i=-$check_interval; $i <= $check_interval; $i++)
|
||||||
|
{
|
||||||
|
$interval_timestamp = $timestamp + $i;
|
||||||
|
$generated_code = self::generateCodeFromTimestamp($secret, $interval_timestamp);
|
||||||
|
if (hash_equals($generated_code, $code))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -524,3 +524,8 @@ $lang['Your API key will expire in %d days.'] = 'Your API key will expire in %d
|
|||||||
$lang['To continue using the API, please renew your key before it expires.'] = 'To continue using the API, please renew your key before it expires.';
|
$lang['To continue using the API, please renew your key before it expires.'] = 'To continue using the API, please renew your key before it expires.';
|
||||||
$lang['You can manage your API keys in your <a href="%s">account settings.</a>'] = 'You can manage your API keys in your <a href="%s">account settings.</a>';
|
$lang['You can manage your API keys in your <a href="%s">account settings.</a>'] = 'You can manage your API keys in your <a href="%s">account settings.</a>';
|
||||||
$lang['Expert mode'] = 'Expert mode';
|
$lang['Expert mode'] = 'Expert mode';
|
||||||
|
$lang['An email has been sent with a verification code'] = 'An email has been sent with a verification code';
|
||||||
|
$lang['If you do not receive the email, please contact your webmaster.'] = 'If you do not receive the email, please contact your webmaster.';
|
||||||
|
$lang['Verification code'] = 'Verification code';
|
||||||
|
$lang['Verify'] = 'Verify';
|
||||||
|
$lang['Invalid verification code'] = 'Invalid verification code';
|
||||||
|
|||||||
@@ -523,3 +523,8 @@ $lang['Your API key will expire in %d days.'] = 'Votre clé API expirera dans %d
|
|||||||
$lang['To continue using the API, please renew your key before it expires.'] = 'Pour continuer à utiliser l\'API, veuillez renouveler votre clé avant son expiration.';
|
$lang['To continue using the API, please renew your key before it expires.'] = 'Pour continuer à utiliser l\'API, veuillez renouveler votre clé avant son expiration.';
|
||||||
$lang['You can manage your API keys in your <a href="%s">account settings.</a>'] = 'Vous pouvez gérer vos clés API dans les <a href="%s">paramètres de votre compte.</a>';
|
$lang['You can manage your API keys in your <a href="%s">account settings.</a>'] = 'Vous pouvez gérer vos clés API dans les <a href="%s">paramètres de votre compte.</a>';
|
||||||
$lang['Expert mode'] = 'Mode expert';
|
$lang['Expert mode'] = 'Mode expert';
|
||||||
|
$lang['An email has been sent with a verification code'] = 'Un e-mail contenant un code de vérification vous a été envoyé';
|
||||||
|
$lang['If you do not receive the email, please contact your webmaster.'] = 'Si vous ne recevez pas cet e-mail, veuillez contacter votre webmaster.';
|
||||||
|
$lang['Verification code'] = 'Code de vérification';
|
||||||
|
$lang['Verify'] = 'Vérifier';
|
||||||
|
$lang['Invalid verification code'] = 'Code de vérification invalide';
|
||||||
|
|||||||
179
password.php
179
password.php
@@ -22,7 +22,7 @@ check_status(ACCESS_FREE);
|
|||||||
|
|
||||||
trigger_notify('loc_begin_password');
|
trigger_notify('loc_begin_password');
|
||||||
|
|
||||||
check_input_parameter('action', $_GET, false, '/^(lost|reset|lost_end|reset_end|none)$/');
|
check_input_parameter('action', $_GET, false, '/^(lost|reset|lost_code|lost_end|reset_end|none)$/');
|
||||||
|
|
||||||
// +-----------------------------------------------------------------------+
|
// +-----------------------------------------------------------------------+
|
||||||
// | Functions |
|
// | Functions |
|
||||||
@@ -30,7 +30,80 @@ check_input_parameter('action', $_GET, false, '/^(lost|reset|lost_end|reset_end|
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* checks the validity of input parameters, fills $page['errors'] and
|
* checks the validity of input parameters, fills $page['errors'] and
|
||||||
* $page['infos'] and send an email with confirmation link
|
* $page['infos'] and send an email with the verification code
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function process_verification_code()
|
||||||
|
{
|
||||||
|
global $page, $conf, $logger;
|
||||||
|
|
||||||
|
if (isset($_SESSION['reset_password_code']))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty param
|
||||||
|
$username_or_email = trim($_POST['username_or_email']);
|
||||||
|
if (empty($username_or_email))
|
||||||
|
{
|
||||||
|
$page['errors']['password_form_error'] = l10n('Invalid username or email');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrievies user by email is not try by username
|
||||||
|
$user_id = get_userid_by_email($username_or_email);
|
||||||
|
|
||||||
|
if (!is_numeric($user_id))
|
||||||
|
{
|
||||||
|
$user_id = get_userid($username_or_email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when no user is found, we assign guest_id instead of stopping.
|
||||||
|
// this lets the function behave identically for unknown users,
|
||||||
|
// preventing username/email enumeration through timing or responses.
|
||||||
|
$is_user_founded = is_numeric($user_id);
|
||||||
|
if (!$is_user_founded)
|
||||||
|
{
|
||||||
|
$user_id = $conf['guest_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$userdata = getuserdata($user_id, false);
|
||||||
|
|
||||||
|
// check if we want to skip email sending
|
||||||
|
// if user is guest, generic or doesn't have email
|
||||||
|
$status = $userdata['status'];
|
||||||
|
$skip_mail = !$is_user_founded or is_a_guest($status) or is_generic($status) or empty($userdata['email']);
|
||||||
|
|
||||||
|
// send mail with verification code to user
|
||||||
|
switch_lang_to($userdata['language']);
|
||||||
|
$user_code = generate_user_code();
|
||||||
|
$template_mail = pwg_generate_code_verification_mail($user_code['code']);
|
||||||
|
if (!$skip_mail)
|
||||||
|
{
|
||||||
|
$mail_send = pwg_mail($userdata['email'], $template_mail);
|
||||||
|
// pwg_activity('user', $userdata['id'], 'reset_password_code', array(
|
||||||
|
// 'ip' => $_SERVER['REMOTE_ADDR'],
|
||||||
|
// 'agent' => $_SERVER['HTTP_USER_AGENT'],
|
||||||
|
// 'is_mail_sent' => $mail_send
|
||||||
|
// ));
|
||||||
|
}
|
||||||
|
switch_lang_back();
|
||||||
|
|
||||||
|
$_SESSION['reset_password_code'] = [
|
||||||
|
'secret' => $user_code['secret'],
|
||||||
|
'attempts' => 0,
|
||||||
|
'user_id' => $is_user_founded ? $user_id : null,
|
||||||
|
'created_at' => time(),
|
||||||
|
'ttl' => min($conf['password_reset_code_duration'], 900) // max 15 min
|
||||||
|
];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks the validity of input parameters, fills $page['errors'] and
|
||||||
|
* $page['infos'] and send an email with reset link
|
||||||
*
|
*
|
||||||
* @return bool (true if email was sent, false otherwise)
|
* @return bool (true if email was sent, false otherwise)
|
||||||
*/
|
*/
|
||||||
@@ -38,64 +111,80 @@ function process_password_request()
|
|||||||
{
|
{
|
||||||
global $page, $conf;
|
global $page, $conf;
|
||||||
|
|
||||||
if (empty($_POST['username_or_email']))
|
$state = $_SESSION['reset_password_code'] ?? null;
|
||||||
|
if (!$state)
|
||||||
{
|
{
|
||||||
$page['errors']['password_form_error'] = l10n('Invalid username or email');
|
return true; // fallback line 366
|
||||||
|
}
|
||||||
|
|
||||||
|
// check expired
|
||||||
|
if (time() > $state['created_at'] + $state['ttl'])
|
||||||
|
{
|
||||||
|
unset($_SESSION['reset_password_code']);
|
||||||
|
$page['errors']['password_form_error'] = l10n('Code expired');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user_id = get_userid_by_email($_POST['username_or_email']);
|
$_SESSION['reset_password_code']['attempts']++;
|
||||||
|
|
||||||
if (!is_numeric($user_id))
|
$is_valid = true;
|
||||||
|
$user_code = trim($_POST['user_code'] ?? '');
|
||||||
|
|
||||||
|
if (
|
||||||
|
empty($user_code) // empty user code
|
||||||
|
|| !preg_match('/^\d{6}$/', $user_code) // check digit 6
|
||||||
|
|| !verify_user_code($state['secret'], $user_code)) // verify user code
|
||||||
{
|
{
|
||||||
$user_id = get_userid($_POST['username_or_email']);
|
$is_valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_numeric($user_id))
|
if (!$is_valid)
|
||||||
{
|
{
|
||||||
$page['errors']['password_form_error'] = l10n('Invalid username or email');
|
if ($_SESSION['reset_password_code']['attempts'] >= 3)
|
||||||
|
{
|
||||||
|
unset($_SESSION['reset_password_code']);
|
||||||
|
$page['errors']['login_page_error'] = l10n('Too many attempts');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$userdata = getuserdata($user_id, false);
|
$page['errors']['password_form_error'] = l10n('Invalid verification code');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// password request is not possible for guest/generic users
|
// verify code success
|
||||||
$status = $userdata['status'];
|
$user_id = $state['user_id'];
|
||||||
if (is_a_guest($status) or is_generic($status))
|
unset($_SESSION['reset_password_code']);
|
||||||
|
|
||||||
|
if (empty($user_id))
|
||||||
|
{
|
||||||
|
$page['errors']['password_form_error'] = l10n('Invalid verification code');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userdata = getuserdata($user_id);
|
||||||
|
$status = $userdata['status'] ?? null;
|
||||||
|
|
||||||
|
// fallback check: don't send mail when user is guest, generic or doesn't have email
|
||||||
|
if (is_a_guest($status) || is_generic($status) || empty($userdata['email']))
|
||||||
{
|
{
|
||||||
$page['errors']['password_form_error'] = l10n('Password reset is not allowed for this user');
|
$page['errors']['password_form_error'] = l10n('Password reset is not allowed for this user');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($userdata['email']))
|
|
||||||
{
|
|
||||||
$page['errors']['password_form_error'] = l10n(
|
|
||||||
'User "%s" has no email address, password reset is not possible',
|
|
||||||
$userdata['username']
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$generate_link = generate_password_link($user_id);
|
$generate_link = generate_password_link($user_id);
|
||||||
|
|
||||||
// $userdata['activation_key'] = $generate_link['activation_key'];
|
|
||||||
|
|
||||||
switch_lang_to($userdata['language']);
|
switch_lang_to($userdata['language']);
|
||||||
$email_params = pwg_generate_reset_password_mail($userdata['username'], $generate_link['password_link'], $conf['gallery_title'], $generate_link['time_validation']);
|
$email_params = pwg_generate_reset_password_mail($userdata['username'], $generate_link['password_link'], $conf['gallery_title'], $generate_link['time_validation']);
|
||||||
$send_email = pwg_mail($userdata['email'], $email_params);
|
$send_email = pwg_mail($userdata['email'], $email_params);
|
||||||
switch_lang_back();
|
switch_lang_back();
|
||||||
|
|
||||||
if ($send_email)
|
// pwg_activity('user', $userdata['id'], 'reset_password_link', array(
|
||||||
{
|
// 'ip' => $_SERVER['REMOTE_ADDR'],
|
||||||
$page['infos'][] = l10n('Check your email for the confirmation link');
|
// 'agent' => $_SERVER['HTTP_USER_AGENT'],
|
||||||
|
// 'is_mail_sent' => $send_email
|
||||||
|
// ));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
$page['errors']['password_page_error'] = l10n('Error sending email');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checks the activation key: does it match the expected pattern? is it
|
* checks the activation key: does it match the expected pattern? is it
|
||||||
@@ -199,9 +288,19 @@ if (isset($_POST['submit']))
|
|||||||
check_pwg_token();
|
check_pwg_token();
|
||||||
|
|
||||||
if ('lost' == $_GET['action'])
|
if ('lost' == $_GET['action'])
|
||||||
|
{
|
||||||
|
if (process_verification_code())
|
||||||
|
{
|
||||||
|
$page['infos'][] = l10n('An email has been sent with a verification code');
|
||||||
|
$page['action'] = 'lost_code';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('lost_code' == $_GET['action'])
|
||||||
{
|
{
|
||||||
if (process_password_request())
|
if (process_password_request())
|
||||||
{
|
{
|
||||||
|
$page['infos'][] = l10n('An email has been sent with a link to reset your password');
|
||||||
$page['action'] = 'lost_end';
|
$page['action'] = 'lost_end';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,7 +352,7 @@ if (!isset($page['action']))
|
|||||||
{
|
{
|
||||||
$page['action'] = 'lost';
|
$page['action'] = 'lost';
|
||||||
}
|
}
|
||||||
elseif (in_array($_GET['action'], array('lost', 'reset', 'none')))
|
elseif (in_array($_GET['action'], array('lost', 'lost_code', 'reset', 'none')))
|
||||||
{
|
{
|
||||||
$page['action'] = $_GET['action'];
|
$page['action'] = $_GET['action'];
|
||||||
}
|
}
|
||||||
@@ -269,6 +368,16 @@ if ('lost' == $page['action'] and !is_a_guest())
|
|||||||
redirect(get_gallery_home_url());
|
redirect(get_gallery_home_url());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('lost_code' == $page['action'] and !isset($_SESSION['reset_password_code']))
|
||||||
|
{
|
||||||
|
redirect(get_gallery_home_url(). 'identification.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('lost' == $page['action'] and isset($_SESSION['reset_password_code']))
|
||||||
|
{
|
||||||
|
$page['action'] = 'lost_code';
|
||||||
|
}
|
||||||
|
|
||||||
// +-----------------------------------------------------------------------+
|
// +-----------------------------------------------------------------------+
|
||||||
// | template initialization |
|
// | template initialization |
|
||||||
// +-----------------------------------------------------------------------+
|
// +-----------------------------------------------------------------------+
|
||||||
|
|||||||
@@ -27,6 +27,17 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="bottomButtons"><input type="submit" name="submit" value="{'Change my password'|@translate}"></p>
|
<p class="bottomButtons"><input type="submit" name="submit" value="{'Change my password'|@translate}"></p>
|
||||||
|
{elseif $action eq 'lost_code'}
|
||||||
|
<div>
|
||||||
|
<div class="message">{"If you do not receive the email, please contact your webmaster."|translate}</div>
|
||||||
|
<label>
|
||||||
|
{'Verification code'|@translate}
|
||||||
|
<br>
|
||||||
|
<input type="text" id="user_code" name="user_code" size="100" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<p class="bottomButtons"><input type="submit" name="submit" value="{'Verify'|@translate}"></p>
|
||||||
|
</div>
|
||||||
{elseif $action eq 'reset'}
|
{elseif $action eq 'reset'}
|
||||||
|
|
||||||
<div class="message">
|
<div class="message">
|
||||||
@@ -60,6 +71,8 @@
|
|||||||
{literal}try{document.getElementById('username_or_email').focus();}catch(e){}{/literal}
|
{literal}try{document.getElementById('username_or_email').focus();}catch(e){}{/literal}
|
||||||
{elseif $action eq 'reset'}
|
{elseif $action eq 'reset'}
|
||||||
{literal}try{document.getElementById('use_new_pwd').focus();}catch(e){}{/literal}
|
{literal}try{document.getElementById('use_new_pwd').focus();}catch(e){}{/literal}
|
||||||
|
{elseif $action eq 'lost_code'}
|
||||||
|
{literal}try{document.getElementById('user_code').focus();}catch(e){}{/literal}
|
||||||
{/if}
|
{/if}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -37,14 +37,14 @@
|
|||||||
<section id="password-form">
|
<section id="password-form">
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|
||||||
{if $action eq 'lost' or $action eq 'reset'}
|
{if $action eq 'lost' or $action eq 'reset' or $action eq 'lost_code'}
|
||||||
<h1 class="">{if !isset($is_first_login)}{'Forgot your password?'|translate}{else}{'Welcome !'|translate}<br>{'It\'s your first login !'|translate}{/if}</h1>
|
<h1 class="">{if !isset($is_first_login)}{'Forgot your password?'|translate}{else}{'Welcome !'|translate}<br>{'It\'s your first login !'|translate}{/if}</h1>
|
||||||
<form id="lostPassword" class="properties" action="{$form_action}?action={$action}{if isset($key)}&key={$key}{/if}" method="post">
|
<form id="lostPassword" class="properties" action="{$form_action}?action={$action}{if isset($key)}&key={$key}{/if}" method="post">
|
||||||
|
|
||||||
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN}">
|
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN}">
|
||||||
{if $action eq 'lost'}
|
{if $action eq 'lost'}
|
||||||
|
|
||||||
<p class="form-instructions">{'Please enter your username or email address.'|@translate}<br>{'You will receive a link to create a new password via email.'|@translate}</p>
|
<p class="form-instructions">{'Please enter your username or email address.'|@translate} {'You will receive a link to create a new password via email.'|@translate}</p>
|
||||||
|
|
||||||
<div class="column-flex">
|
<div class="column-flex">
|
||||||
<label for="username">{'Username or email'|@translate}</label>
|
<label for="username">{'Username or email'|@translate}</label>
|
||||||
@@ -105,6 +105,24 @@
|
|||||||
<input tabindex="4" type="submit" name="submit" {if !isset($is_first_login)}value="{'Confirm my new password'|@translate}"{else}value="{'Set my password'|@translate}"{/if} class="btn btn-main ">
|
<input tabindex="4" type="submit" name="submit" {if !isset($is_first_login)}value="{'Confirm my new password'|@translate}"{else}value="{'Set my password'|@translate}"{/if} class="btn btn-main ">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{elseif $action eq 'lost_code'}
|
||||||
|
<span class="success-message"><i class="gallery-icon-ok-circled"></i>{'An email has been sent with a verification code'|translate}</span>
|
||||||
|
<div class="column-flex">
|
||||||
|
<label for="user_code">{'Verification code'|@translate}</label>
|
||||||
|
<div class="row-flex input-container">
|
||||||
|
<i class="gallery-icon-user-2"></i>
|
||||||
|
<input type="text" id="user_code" name="user_code" size="100" maxlength="100" autofocus>
|
||||||
|
</div>
|
||||||
|
<p class="error-message"><i class="gallery-icon-attention-circled"></i> {'must not be empty'|translate}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column-flex">
|
||||||
|
<input tabindex="4" type="submit" name="submit" value="{'Verify'|@translate}" class="btn btn-main">
|
||||||
|
{if isset($errors['password_form_error'])}
|
||||||
|
<p class="error-message" style="display:block;bottom:-20px;"><i class="gallery-icon-attention-circled"></i> {$errors['password_form_error']}</p>
|
||||||
|
{/if}
|
||||||
|
<p style="font-size: 12px;">{"If you do not receive the email, please contact your webmaster."|translate}</p>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user