*/
$wgEnableUserEmail = true;
+/**
+ * Minimum time, in hours, which must elapse between password reminder
+ * emails for a given account. This is to prevent abuse by mail flooding.
+ */
+$wgPasswordReminderResendTime = 24;
+
/**
* SMTP Mode
* For using a direct (authenticated) SMTP server connection.
}
$u->saveSettings();
- $result = $this->mailPasswordInternal($u);
+ $result = $this->mailPasswordInternal( $u, false );
wfRunHooks( 'AddNewAccount', array( $u ) );
return self::NOT_EXISTS;
}
} else {
- $u->loadFromDatabase();
+ $u->load();
}
if (!$u->checkPassword( $this->mPassword )) {
$wgOut->rateLimited();
return;
}
-
+
if ( '' == $this->mName ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
return;
return;
}
- $u->loadFromDatabase();
+ # Check against password throttle
+ if ( $u->isPasswordReminderThrottled() ) {
+ global $wgPasswordReminderResendTime;
+ # Round the time in hours to 3 d.p., in case someone is specifying minutes or seconds.
+ $this->mainLoginForm( wfMsg( 'throttled-mailpassword',
+ round( $wgPasswordReminderResendTime, 3 ) ) );
+ return;
+ }
- $result = $this->mailPasswordInternal( $u );
+ $result = $this->mailPasswordInternal( $u, true );
if( WikiError::isError( $result ) ) {
$this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) );
} else {
* @return mixed true on success, WikiError on failure
* @private
*/
- function mailPasswordInternal( $u ) {
+ function mailPasswordInternal( $u, $throttle = true ) {
global $wgCookiePath, $wgCookieDomain, $wgCookiePrefix, $wgCookieSecure;
global $wgServer, $wgScript;
}
$np = $u->randomPassword();
- $u->setNewpassword( $np );
+ $u->setNewpassword( $np, $throttle );
setcookie( "{$wgCookiePrefix}Token", '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
'mRealName',
'mPassword',
'mNewpassword',
+ 'mNewpassTime',
'mEmail',
'mOptions',
'mTouched',
/**
* The cache variable declarations
*/
- var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mEmail, $mOptions,
- $mTouched, $mToken, $mEmailAuthenticated, $mEmailToken, $mEmailTokenExpires,
- $mRegistration, $mGroups;
+ var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime,
+ $mEmail, $mOptions, $mTouched, $mToken, $mEmailAuthenticated,
+ $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups;
/**
* Whether the cache variables have been loaded
$this->mName = $name;
$this->mRealName = '';
$this->mPassword = $this->mNewpassword = '';
+ $this->mNewpassTime = null;
$this->mEmail = '';
$this->mOptions = null; # Defer init
$this->mRealName = $s->user_real_name;
$this->mPassword = $s->user_password;
$this->mNewpassword = $s->user_newpassword;
+ $this->mNewpassTime = wfTimestampOrNull( TS_MW, $s->user_newpass_time );
$this->mEmail = $s->user_email;
$this->decodeOptions( $s->user_options );
$this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
$this->setToken();
$this->mPassword = $this->encryptPassword( $str );
$this->mNewpassword = '';
+ $this->mNewpassTime = NULL;
}
/**
$this->mCookiePassword = md5( $str );
}
- function setNewpassword( $str ) {
+ /**
+ * Set the password for a password reminder or new account email
+ * Sets the user_newpass_time field if $throttle is true
+ */
+ function setNewpassword( $str, $throttle = true ) {
$this->load();
$this->mNewpassword = $this->encryptPassword( $str );
+ if ( $throttle ) {
+ $this->mNewpassTime = wfTimestampNow();
+ }
}
+ /**
+ * Returns true if a password reminder email has already been sent within
+ * the last $wgPasswordReminderResendTime hours
+ */
+ function isPasswordReminderThrottled() {
+ global $wgPasswordReminderResendTime;
+ $this->load();
+ if ( !$this->mNewpassTime || !$wgPasswordReminderResendTime ) {
+ return false;
+ }
+ $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgPasswordReminderResendTime * 3600;
+ return time() < $expiry;
+ }
+
function getEmail() {
$this->load();
return $this->mEmail;
'user_name' => $this->mName,
'user_password' => $this->mPassword,
'user_newpassword' => $this->mNewpassword,
+ 'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ),
'user_real_name' => $this->mRealName,
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_name' => $name,
'user_password' => $user->mPassword,
'user_newpassword' => $user->mNewpassword,
+ 'user_newpass_time' => $dbw->timestamp( $user->mNewpassTime ),
'user_email' => $user->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
'user_real_name' => $user->mRealName,
'user_name' => $this->mName,
'user_password' => $this->mPassword,
'user_newpassword' => $this->mNewpassword,
+ 'user_newpass_time' => $dbw->timestamp( $this->mNewpassTime ),
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_real_name' => $this->mRealName,
'ISO 8601',
);
+/**
+ * The date format to use for generated dates in the user interface.
+ * This may be one of the above date preferences, or the special value
+ * "dmy or mdy", which uses mdy if $wgAmericanDates is true, and dmy
+ * if $wgAmericanDates is false.
+ */
$defaultDateFormat = 'dmy or mdy';
+/**
+ * Associative array mapping old numeric date formats, which may still be
+ * stored in user preferences, to the new string formats.
+ */
$datePreferenceMigrationMap = array(
'default',
'mdy',
'ISO 8601 both' => 'xnY-xnm-xnd"T"xnH:xni:xns',
);
+/**
+ * Default list of book sources
+ */
$bookstoreList = array(
'AddALL' => 'http://www.addall.com/New/Partner.cgi?query=$1&type=ISBN',
'PriceSCAN' => 'http://www.pricescan.com/books/bookDetail.asp?isbn=$1',
'Amazon.com' => 'http://www.amazon.com/exec/obidos/ISBN=$1'
);
-# Note to translators:
-# Please include the English words as synonyms. This allows people
-# from other wikis to contribute more easily.
-#
+/**
+ * Magic words
+ * Customisable syntax for wikitext and elsewhere
+ *
+ * Note to translators:
+ * Please include the English words as synonyms. This allows people
+ * from other wikis to contribute more easily.
+ */
$magicWords = array(
# ID CASE SYNONYMS
'redirect' => array( 0, '#REDIRECT' ),
'formatnum' => array( 0, 'FORMATNUM' ),
'padleft' => array( 0, 'PADLEFT' ),
'padright' => array( 0, 'PADRIGHT' ),
-
);
+/**
+ * Regular expression matching the "link trail", e.g. "ed" in [[Toast]]ed, as
+ * the first group, and the remainder of the string as the second group.
+ */
$linkTrail = '/^([a-z]+)(.*)$/sD';
#-------------------------------------------------------------------
'eauthentsent' => 'A confirmation e-mail has been sent to the nominated e-mail address.
Before any other mail is sent to the account, you will have to follow the instructions in the e-mail,
to confirm that the account is actually yours.',
+'throttled-mailpassword' => 'A password reminder has already been sent, within the
+last $1 hours. To prevent abuse, only one password reminder will be sent per
+$1 hours.',
'loginend' => '',
'signupend' => '{{int:loginend}}',
'mailerror' => 'Error sending mail: $1',
--- /dev/null
+-- Timestamp of the last time when a new password was
+-- sent, for throttling purposes
+ALTER TABLE user ADD user_newpass_time char(14) binary;
+
-- at which point the hash is moved to user_password
-- and the old password is invalidated.
user_newpassword tinyblob NOT NULL default '',
+
+ -- Timestamp of the last time when a new password was
+ -- sent, for throttling purposes
+ user_newpass_time char(14) binary,
-- Note: email should be restricted, not public info.
-- Same with passwords.
user_real_name TEXT,
user_password TEXT,
user_newpassword TEXT,
+ user_newpass_time TIMESTAMPTZ,
user_token CHAR(32),
user_email TEXT,
user_email_token CHAR(32),
-- and the old password is invalidated.
user_newpassword tinyblob NOT NULL default '',
+ -- Timestamp of the last time when a new password was
+ -- sent, for throttling purposes
+ user_newpass_time char(14) binary,
+
-- Note: email should be restricted, not public info.
-- Same with passwords.
user_email tinytext NOT NULL default '',
array( 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ),
array( 'site_stats', 'ss_images', 'patch-ss_images.sql' ),
array( 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
+ array( 'user', 'user_newpass_time','patch-user_newpass_time.sql' ),
);
function rename_table( $from, $to, $patch ) {