1
0
mirror of https://github.com/delight-im/PHP-Auth.git synced 2025-08-03 22:57:27 +02:00

Rewrite all SQL operations to use 'delight-im/db' instead of raw PDO

This commit is contained in:
Marco
2016-09-15 23:43:40 +02:00
parent 51a5735295
commit 989c7940e5

View File

@@ -10,6 +10,10 @@ namespace Delight\Auth;
use Delight\Cookie\Cookie; use Delight\Cookie\Cookie;
use Delight\Cookie\Session; use Delight\Cookie\Session;
use Delight\Db\PdoDatabase;
use Delight\Db\PdoDsn;
use Delight\Db\Throwable\Error;
use Delight\Db\Throwable\IntegrityConstraintViolationException;
require __DIR__.'/Base64.php'; require __DIR__.'/Base64.php';
require __DIR__.'/Exceptions.php'; require __DIR__.'/Exceptions.php';
@@ -30,7 +34,7 @@ class Auth {
const THROTTLE_ACTION_CONSUME_TOKEN = 'confirm_email'; const THROTTLE_ACTION_CONSUME_TOKEN = 'confirm_email';
const HTTP_STATUS_CODE_TOO_MANY_REQUESTS = 429; const HTTP_STATUS_CODE_TOO_MANY_REQUESTS = 429;
/** @var \PDO the database connection that will be used */ /** @var PdoDatabase the database connection that will be used */
private $db; private $db;
/** @var boolean whether HTTPS (TLS/SSL) will be used (recommended) */ /** @var boolean whether HTTPS (TLS/SSL) will be used (recommended) */
private $useHttps; private $useHttps;
@@ -44,13 +48,25 @@ class Auth {
private $throttlingTimeBucketSize; private $throttlingTimeBucketSize;
/** /**
* @param \PDO $databaseConnection the database connection that will be used * @param PdoDatabase|PdoDsn|\PDO $databaseConnection the database connection that will be used
* @param bool $useHttps whether HTTPS (TLS/SSL) will be used (recommended) * @param bool $useHttps whether HTTPS (TLS/SSL) will be used (recommended)
* @param bool $allowCookiesScriptAccess whether cookies should be accessible via client-side scripts (*not* recommended) * @param bool $allowCookiesScriptAccess whether cookies should be accessible via client-side scripts (*not* recommended)
* @param string $ipAddress the IP address that should be used instead of the default setting (if any), e.g. when behind a proxy * @param string $ipAddress the IP address that should be used instead of the default setting (if any), e.g. when behind a proxy
*/ */
public function __construct(\PDO $databaseConnection, $useHttps = false, $allowCookiesScriptAccess = false, $ipAddress = null) { public function __construct($databaseConnection, $useHttps = false, $allowCookiesScriptAccess = false, $ipAddress = null) {
if ($databaseConnection instanceof PdoDatabase) {
$this->db = $databaseConnection; $this->db = $databaseConnection;
}
elseif ($databaseConnection instanceof PdoDsn) {
$this->db = PdoDatabase::fromDsn($databaseConnection);
}
elseif ($databaseConnection instanceof \PDO) {
$this->db = PdoDatabase::fromPdo($databaseConnection, true);
}
else {
throw new \InvalidArgumentException('The database connection must be an instance of either `PdoDatabase`, `PdoDsn` or `PDO`');
}
$this->useHttps = $useHttps; $this->useHttps = $useHttps;
$this->allowCookiesScriptAccess = $allowCookiesScriptAccess; $this->allowCookiesScriptAccess = $allowCookiesScriptAccess;
$this->ipAddress = empty($ipAddress) ? $_SERVER['REMOTE_ADDR'] : $ipAddress; $this->ipAddress = empty($ipAddress) ? $_SERVER['REMOTE_ADDR'] : $ipAddress;
@@ -110,11 +126,17 @@ class Auth {
$parts = explode(self::COOKIE_CONTENT_SEPARATOR, $_COOKIE[self::COOKIE_NAME_REMEMBER], 2); $parts = explode(self::COOKIE_CONTENT_SEPARATOR, $_COOKIE[self::COOKIE_NAME_REMEMBER], 2);
// if both selector and token were found // if both selector and token were found
if (isset($parts[0]) && isset($parts[1])) { if (isset($parts[0]) && isset($parts[1])) {
$stmt = $this->db->prepare("SELECT a.user, a.token, a.expires, b.email, b.username FROM users_remembered AS a JOIN users AS b ON a.user = b.id WHERE a.selector = :selector"); try {
$stmt->bindValue(':selector', $parts[0], \PDO::PARAM_STR); $rememberData = $this->db->selectRow(
if ($stmt->execute()) { 'SELECT a.user, a.token, a.expires, b.email, b.username FROM users_remembered AS a JOIN users AS b ON a.user = b.id WHERE a.selector = ?',
$rememberData = $stmt->fetch(\PDO::FETCH_ASSOC); [ $parts[0] ]
if ($rememberData !== false) { );
}
catch (Error $e) {
throw new DatabaseError();
}
if (!empty($rememberData)) {
if ($rememberData['expires'] >= time()) { if ($rememberData['expires'] >= time()) {
if (password_verify($parts[1], $rememberData['token'])) { if (password_verify($parts[1], $rememberData['token'])) {
$this->onLoginSuccessful($rememberData['user'], $rememberData['email'], $rememberData['username'], true); $this->onLoginSuccessful($rememberData['user'], $rememberData['email'], $rememberData['username'], true);
@@ -125,7 +147,6 @@ class Auth {
} }
} }
} }
}
/** /**
* Attempts to sign up a user * Attempts to sign up a user
@@ -162,53 +183,33 @@ class Auth {
$password = password_hash($password, PASSWORD_DEFAULT); $password = password_hash($password, PASSWORD_DEFAULT);
$verified = isset($callback) && is_callable($callback) ? 0 : 1; $verified = isset($callback) && is_callable($callback) ? 0 : 1;
$stmt = $this->db->prepare("INSERT INTO users (email, password, username, verified, registered) VALUES (:email, :password, :username, :verified, :registered)");
$stmt->bindValue(':email', $email, \PDO::PARAM_STR);
$stmt->bindValue(':password', $password, \PDO::PARAM_STR);
$stmt->bindValue(':username', $username, \PDO::PARAM_STR);
$stmt->bindValue(':verified', $verified, \PDO::PARAM_INT);
$stmt->bindValue(':registered', time(), \PDO::PARAM_INT);
try { try {
$result = $stmt->execute(); $this->db->insert(
'users',
[
'email' => $email,
'password' => $password,
'username' => $username,
'verified' => $verified,
'registered' => time()
]
);
} }
catch (\PDOException $e) { catch (IntegrityConstraintViolationException $e) {
// if we have a duplicate entry // if we have a duplicate entry
if ($e->getCode() == '23000') {
throw new UserAlreadyExistsException(); throw new UserAlreadyExistsException();
} }
// if we have another error catch (Error $e) {
else {
// throw an exception
throw new DatabaseError(null, null, $e);
}
}
// if creating the new user was successful
if ($result) {
// get the ID of the user that we've just created
$stmt = $this->db->prepare("SELECT id FROM users WHERE email = :email");
$stmt->bindValue(':email', $email, \PDO::PARAM_STR);
if ($result = $stmt->execute()) {
$newUserId = $stmt->fetchColumn();
}
else {
$newUserId = null;
}
if ($verified === 1) {
return $newUserId;
}
else {
$this->createConfirmationRequest($email, $callback);
return $newUserId;
}
}
else {
throw new DatabaseError(); throw new DatabaseError();
} }
$newUserId = (int) $this->db->getLastInsertId();
if ($verified === 0) {
$this->createConfirmationRequest($email, $callback);
}
return $newUserId;
} }
/** /**
@@ -232,25 +233,27 @@ class Auth {
$tokenHashed = password_hash($token, PASSWORD_DEFAULT); $tokenHashed = password_hash($token, PASSWORD_DEFAULT);
$expires = time() + 3600 * 24; $expires = time() + 3600 * 24;
$stmt = $this->db->prepare("INSERT INTO users_confirmations (email, selector, token, expires) VALUES (:email, :selector, :token, :expires)"); try {
$stmt->bindValue(':email', $email, \PDO::PARAM_STR); $this->db->insert(
$stmt->bindValue(':selector', $selector, \PDO::PARAM_STR); 'users_confirmations',
$stmt->bindValue(':token', $tokenHashed, \PDO::PARAM_STR); [
$stmt->bindValue(':expires', $expires, \PDO::PARAM_INT); 'email' => $email,
'selector' => $selector,
'token' => $tokenHashed,
'expires' => $expires
]
);
}
catch (Error $e) {
throw new DatabaseError();
}
if ($stmt->execute()) {
if (isset($callback) && is_callable($callback)) { if (isset($callback) && is_callable($callback)) {
$callback($selector, $token); $callback($selector, $token);
} }
else { else {
throw new MissingCallbackError(); throw new MissingCallbackError();
} }
return;
}
else {
throw new DatabaseError();
}
} }
/** /**
@@ -268,11 +271,17 @@ class Auth {
$email = self::validateEmailAddress($email); $email = self::validateEmailAddress($email);
$password = self::validatePassword($password); $password = self::validatePassword($password);
$stmt = $this->db->prepare("SELECT id, password, verified, username FROM users WHERE email = :email"); try {
$stmt->bindValue(':email', $email, \PDO::PARAM_STR); $userData = $this->db->selectRow(
if ($stmt->execute()) { 'SELECT id, password, verified, username FROM users WHERE email = ?',
$userData = $stmt->fetch(\PDO::FETCH_ASSOC); [ $email ]
if ($userData !== false) { );
}
catch (Error $e) {
throw new DatabaseError();
}
if (!empty($userData)) {
if (password_verify($password, $userData['password'])) { if (password_verify($password, $userData['password'])) {
// if the password needs to be re-hashed to keep up with improving password cracking techniques // if the password needs to be re-hashed to keep up with improving password cracking techniques
if (password_needs_rehash($userData['password'], PASSWORD_DEFAULT)) { if (password_needs_rehash($userData['password'], PASSWORD_DEFAULT)) {
@@ -280,7 +289,7 @@ class Auth {
$this->updatePassword($userData['id'], $password); $this->updatePassword($userData['id'], $password);
} }
if ($userData['verified'] == 1) { if ($userData['verified'] === 1) {
$this->onLoginSuccessful($userData['id'], $email, $userData['username'], false); $this->onLoginSuccessful($userData['id'], $email, $userData['username'], false);
if ($remember) { if ($remember) {
@@ -307,10 +316,6 @@ class Auth {
throw new InvalidEmailException(); throw new InvalidEmailException();
} }
} }
else {
throw new DatabaseError();
}
}
/** /**
* Validates an email address * Validates an email address
@@ -366,20 +371,22 @@ class Auth {
$tokenHashed = password_hash($token, PASSWORD_DEFAULT); $tokenHashed = password_hash($token, PASSWORD_DEFAULT);
$expires = time() + 3600 * 24 * 28; $expires = time() + 3600 * 24 * 28;
$stmt = $this->db->prepare("INSERT INTO users_remembered (user, selector, token, expires) VALUES (:user, :selector, :token, :expires)"); try {
$stmt->bindValue(':user', $userId, \PDO::PARAM_INT); $this->db->insert(
$stmt->bindValue(':selector', $selector, \PDO::PARAM_STR); 'users_remembered',
$stmt->bindValue(':token', $tokenHashed, \PDO::PARAM_STR); [
$stmt->bindValue(':expires', $expires, \PDO::PARAM_INT); 'user' => $userId,
'selector' => $selector,
if ($stmt->execute()) { 'token' => $tokenHashed,
$this->setRememberCookie($selector, $token, $expires); 'expires' => $expires
]
return; );
} }
else { catch (Error $e) {
throw new DatabaseError(); throw new DatabaseError();
} }
$this->setRememberCookie($selector, $token, $expires);
} }
/** /**
@@ -389,17 +396,17 @@ class Auth {
* @throws AuthError if an internal problem occurred (do *not* catch) * @throws AuthError if an internal problem occurred (do *not* catch)
*/ */
private function deleteRememberDirective($userId) { private function deleteRememberDirective($userId) {
$stmt = $this->db->prepare("DELETE FROM users_remembered WHERE user = :user"); try {
$stmt->bindValue(':user', $userId, \PDO::PARAM_INT); $this->db->delete(
'users_remembered',
if ($stmt->execute()) { [ 'user' => $userId ]
$this->setRememberCookie(null, null, time() - 3600); );
return;
} }
else { catch (Error $e) {
throw new DatabaseError(); throw new DatabaseError();
} }
$this->setRememberCookie(null, null, time() - 3600);
} }
/** /**
@@ -447,12 +454,19 @@ class Auth {
* @param string $email the email address of the user who has just logged in * @param string $email the email address of the user who has just logged in
* @param string $username the username (if any) * @param string $username the username (if any)
* @param bool $remembered whether the user was remembered ("remember me") or logged in actively * @param bool $remembered whether the user was remembered ("remember me") or logged in actively
* @throws AuthError if an internal problem occurred (do *not* catch)
*/ */
private function onLoginSuccessful($userId, $email, $username, $remembered) { private function onLoginSuccessful($userId, $email, $username, $remembered) {
$stmt = $this->db->prepare("UPDATE users SET last_login = :lastLogin WHERE id = :id"); try {
$stmt->bindValue(':lastLogin', time(), \PDO::PARAM_INT); $this->db->update(
$stmt->bindValue(':id', $userId, \PDO::PARAM_INT); 'users',
$stmt->execute(); [ 'last_login' => time() ],
[ 'id' => $userId ]
);
}
catch (Error $e) {
throw new DatabaseError();
}
// re-generate the session ID to prevent session fixation attacks // re-generate the session ID to prevent session fixation attacks
Session::regenerate(true); Session::regenerate(true);
@@ -533,27 +547,37 @@ class Auth {
$this->throttle(self::THROTTLE_ACTION_CONSUME_TOKEN); $this->throttle(self::THROTTLE_ACTION_CONSUME_TOKEN);
$this->throttle(self::THROTTLE_ACTION_CONSUME_TOKEN, $selector); $this->throttle(self::THROTTLE_ACTION_CONSUME_TOKEN, $selector);
$stmt = $this->db->prepare("SELECT id, email, token, expires FROM users_confirmations WHERE selector = :selector"); try {
$stmt->bindValue(':selector', $selector, \PDO::PARAM_STR); $confirmationData = $this->db->selectRow(
if ($stmt->execute()) { 'SELECT id, email, token, expires FROM users_confirmations WHERE selector = ?',
$confirmationData = $stmt->fetch(\PDO::FETCH_ASSOC); [ $selector ]
if ($confirmationData !== false) { );
if (password_verify($token, $confirmationData['token'])) {
if ($confirmationData['expires'] >= time()) {
$stmt = $this->db->prepare("UPDATE users SET verified = :verified WHERE email = :email");
$stmt->bindValue(':verified', 1, \PDO::PARAM_INT);
$stmt->bindValue(':email', $confirmationData['email'], \PDO::PARAM_STR);
if ($stmt->execute()) {
$stmt = $this->db->prepare("DELETE FROM users_confirmations WHERE id = :id");
$stmt->bindValue(':id', $confirmationData['id'], \PDO::PARAM_INT);
if ($stmt->execute()) {
return;
} }
else { catch (Error $e) {
throw new DatabaseError(); throw new DatabaseError();
} }
if (!empty($confirmationData)) {
if (password_verify($token, $confirmationData['token'])) {
if ($confirmationData['expires'] >= time()) {
try {
$this->db->update(
'users',
[ 'verified' => 1 ],
[ 'email' => $confirmationData['email'] ]
);
} }
else { catch (Error $e) {
throw new DatabaseError();
}
try {
$this->db->delete(
'users_confirmations',
[ 'id' => $confirmationData['id'] ]
);
}
catch (Error $e) {
throw new DatabaseError(); throw new DatabaseError();
} }
} }
@@ -569,10 +593,6 @@ class Auth {
throw new InvalidSelectorTokenPairException(); throw new InvalidSelectorTokenPairException();
} }
} }
else {
throw new DatabaseError();
}
}
/** /**
* Changes the (currently logged-in) user's password * Changes the (currently logged-in) user's password
@@ -590,21 +610,26 @@ class Auth {
$userId = $this->getUserId(); $userId = $this->getUserId();
$stmt = $this->db->prepare("SELECT password FROM users WHERE id = :userId"); try {
$stmt->bindValue(':userId', $userId, \PDO::PARAM_INT); $passwordInDatabase = $this->db->selectValue(
if ($stmt->execute()) { 'SELECT password FROM users WHERE id = ?',
$passwordInDatabase = $stmt->fetchColumn(); [ $userId ]
);
}
catch (Error $e) {
throw new DatabaseError();
}
if (!empty($passwordInDatabase)) {
if (password_verify($oldPassword, $passwordInDatabase)) { if (password_verify($oldPassword, $passwordInDatabase)) {
$this->updatePassword($userId, $newPassword); $this->updatePassword($userId, $newPassword);
return;
} }
else { else {
throw new InvalidPasswordException(); throw new InvalidPasswordException();
} }
} }
else { else {
throw new DatabaseError(); throw new NotLoggedInException();
} }
} }
else { else {
@@ -617,14 +642,21 @@ class Auth {
* *
* @param int $userId the ID of the user whose password should be updated * @param int $userId the ID of the user whose password should be updated
* @param string $newPassword the new password * @param string $newPassword the new password
* @throws AuthError if an internal problem occurred (do *not* catch)
*/ */
private function updatePassword($userId, $newPassword) { private function updatePassword($userId, $newPassword) {
$newPassword = password_hash($newPassword, PASSWORD_DEFAULT); $newPassword = password_hash($newPassword, PASSWORD_DEFAULT);
$stmt = $this->db->prepare("UPDATE users SET password = :password WHERE id = :userId"); try {
$stmt->bindValue(':password', $newPassword, \PDO::PARAM_STR); $this->db->update(
$stmt->bindValue(':userId', $userId, \PDO::PARAM_INT); 'users',
$stmt->execute(); [ 'password' => $newPassword ],
[ 'id' => $userId ]
);
}
catch (Error $e) {
throw new DatabaseError();
}
} }
/** /**
@@ -685,23 +717,23 @@ class Auth {
* @throws AuthError if an internal problem occurred (do *not* catch) * @throws AuthError if an internal problem occurred (do *not* catch)
*/ */
private function getUserIdByEmailAddress($email) { private function getUserIdByEmailAddress($email) {
$stmt = $this->db->prepare("SELECT id FROM users WHERE email = :email"); try {
$stmt->bindValue(':email', $email, \PDO::PARAM_STR); $userId = $this->db->selectValue(
'SELECT id FROM users WHERE email = ?',
[ $email ]
);
}
catch (Error $e) {
throw new DatabaseError();
}
if ($stmt->execute()) { if (!empty($userId)) {
$userId = $stmt->fetchColumn();
if ($userId !== false) {
return $userId; return $userId;
} }
else { else {
throw new InvalidEmailException(); throw new InvalidEmailException();
} }
} }
else {
throw new DatabaseError();
}
}
/** /**
* Returns the number of open requests for a password reset by the specified user * Returns the number of open requests for a password reset by the specified user
@@ -711,14 +743,23 @@ class Auth {
* @throws AuthError if an internal problem occurred (do *not* catch) * @throws AuthError if an internal problem occurred (do *not* catch)
*/ */
private function getOpenPasswordResetRequests($userId) { private function getOpenPasswordResetRequests($userId) {
$stmt = $this->db->prepare("SELECT COUNT(*) FROM users_resets WHERE user = :userId AND expires > :expiresAfter"); try {
$stmt->bindValue(':userId', $userId, \PDO::PARAM_INT); $requests = $this->db->selectValue(
$stmt->bindValue(':expiresAfter', time(), \PDO::PARAM_INT); 'SELECT COUNT(*) FROM users_resets WHERE user = ? AND expires > ?',
[
$userId,
time()
]
);
if ($stmt->execute()) { if (!empty($requests)) {
return $stmt->fetchColumn(); return $requests;
} }
else { else {
return 0;
}
}
catch (Error $e) {
throw new DatabaseError(); throw new DatabaseError();
} }
} }
@@ -745,25 +786,27 @@ class Auth {
$tokenHashed = password_hash($token, PASSWORD_DEFAULT); $tokenHashed = password_hash($token, PASSWORD_DEFAULT);
$expiresAt = time() + $expiresAfter; $expiresAt = time() + $expiresAfter;
$stmt = $this->db->prepare("INSERT INTO users_resets (user, selector, token, expires) VALUES (:userId, :selector, :token, :expires)"); try {
$stmt->bindValue(':userId', $userId, \PDO::PARAM_INT); $this->db->insert(
$stmt->bindValue(':selector', $selector, \PDO::PARAM_STR); 'users_resets',
$stmt->bindValue(':token', $tokenHashed, \PDO::PARAM_STR); [
$stmt->bindValue(':expires', $expiresAt, \PDO::PARAM_INT); 'user' => $userId,
'selector' => $selector,
'token' => $tokenHashed,
'expires' => $expiresAt
]
);
}
catch (Error $e) {
throw new DatabaseError();
}
if ($stmt->execute()) {
if (isset($callback) && is_callable($callback)) { if (isset($callback) && is_callable($callback)) {
$callback($selector, $token); $callback($selector, $token);
} }
else { else {
throw new MissingCallbackError(); throw new MissingCallbackError();
} }
return;
}
else {
throw new DatabaseError();
}
} }
/** /**
@@ -784,25 +827,30 @@ class Auth {
$this->throttle(self::THROTTLE_ACTION_CONSUME_TOKEN); $this->throttle(self::THROTTLE_ACTION_CONSUME_TOKEN);
$this->throttle(self::THROTTLE_ACTION_CONSUME_TOKEN, $selector); $this->throttle(self::THROTTLE_ACTION_CONSUME_TOKEN, $selector);
$stmt = $this->db->prepare("SELECT id, user, token, expires FROM users_resets WHERE selector = :selector"); try {
$stmt->bindValue(':selector', $selector, \PDO::PARAM_STR); $resetData = $this->db->selectRow(
if ($stmt->execute()) { 'SELECT id, user, token, expires FROM users_resets WHERE selector = ?',
$resetData = $stmt->fetch(\PDO::FETCH_ASSOC); [ $selector ]
);
}
catch (Error $e) {
throw new DatabaseError();
}
if ($resetData !== false) { if (!empty($resetData)) {
if (password_verify($token, $resetData['token'])) { if (password_verify($token, $resetData['token'])) {
if ($resetData['expires'] >= time()) { if ($resetData['expires'] >= time()) {
$newPassword = self::validatePassword($newPassword); $newPassword = self::validatePassword($newPassword);
$this->updatePassword($resetData['user'], $newPassword); $this->updatePassword($resetData['user'], $newPassword);
$stmt = $this->db->prepare("DELETE FROM users_resets WHERE id = :id"); try {
$stmt->bindValue(':id', $resetData['id'], \PDO::PARAM_INT); $this->db->delete(
'users_resets',
if ($stmt->execute()) { [ 'id' => $resetData['id'] ]
return; );
} }
else { catch (Error $e) {
throw new DatabaseError(); throw new DatabaseError();
} }
} }
@@ -818,10 +866,6 @@ class Auth {
throw new InvalidSelectorTokenPairException(); throw new InvalidSelectorTokenPairException();
} }
} }
else {
throw new DatabaseError();
}
}
/** /**
* Check if the supplied selector/token pair can be used to reset a password * Check if the supplied selector/token pair can be used to reset a password
@@ -1033,45 +1077,58 @@ class Auth {
// get the time bucket that we do the throttling for // get the time bucket that we do the throttling for
$timeBucket = self::getTimeBucket(); $timeBucket = self::getTimeBucket();
$stmt = $this->db->prepare('INSERT INTO users_throttling (action_type, selector, time_bucket, attempts) VALUES (:actionType, :selector, :timeBucket, 1)');
$stmt->bindValue(':actionType', $actionType, \PDO::PARAM_STR);
$stmt->bindValue(':selector', $selector, \PDO::PARAM_STR);
$stmt->bindValue(':timeBucket', $timeBucket, \PDO::PARAM_INT);
try { try {
$stmt->execute(); $this->db->insert(
'users_throttling',
[
'action_type' => $actionType,
'selector' => $selector,
'time_bucket' => $timeBucket,
'attempts' => 1
]
);
} }
catch (\PDOException $e) { catch (IntegrityConstraintViolationException $e) {
// if we have a duplicate entry // if we have a duplicate entry, update the old entry
if ($e->getCode() == '23000') { try {
// update the old entry $this->db->exec(
$stmt = $this->db->prepare('UPDATE users_throttling SET attempts = attempts+1 WHERE action_type = :actionType AND selector = :selector AND time_bucket = :timeBucket'); 'UPDATE users_throttling SET attempts = attempts+1 WHERE action_type = ? AND selector = ? AND time_bucket = ?',
$stmt->bindValue(':actionType', $actionType, \PDO::PARAM_STR); [
$stmt->bindValue(':selector', $selector, \PDO::PARAM_STR); $actionType,
$stmt->bindValue(':timeBucket', $timeBucket, \PDO::PARAM_INT); $selector,
$stmt->execute(); $timeBucket
]
);
} }
// if we have another error catch (Error $e) {
else { throw new DatabaseError();
// throw an exception
throw new DatabaseError(null, null, $e);
} }
} }
catch (Error $e) {
throw new DatabaseError();
}
$stmt = $this->db->prepare('SELECT attempts FROM users_throttling WHERE action_type = :actionType AND selector = :selector AND time_bucket = :timeBucket'); try {
$stmt->bindValue(':actionType', $actionType, \PDO::PARAM_STR); $attempts = $this->db->selectValue(
$stmt->bindValue(':selector', $selector, \PDO::PARAM_STR); 'SELECT attempts FROM users_throttling WHERE action_type = ? AND selector = ? AND time_bucket = ?',
$stmt->bindValue(':timeBucket', $timeBucket, \PDO::PARAM_INT); [
if ($stmt->execute()) { $actionType,
$attempts = $stmt->fetchColumn(); $selector,
$timeBucket
]
);
}
catch (Error $e) {
throw new DatabaseError();
}
if ($attempts !== false) { if (!empty($attempts)) {
// if the number of attempts has acceeded our accepted limit // if the number of attempts has acceeded our accepted limit
if ($attempts > $this->throttlingActionsPerTimeBucket) { if ($attempts > $this->throttlingActionsPerTimeBucket) {
self::onTooManyRequests($this->throttlingTimeBucketSize); self::onTooManyRequests($this->throttlingTimeBucketSize);
} }
} }
} }
}
/** /**
* Called when there have been too many requests for some action or object * Called when there have been too many requests for some action or object