generated from jric11/baseProject
Chat's updates for security, db and workflow
Security and workflow regressions / regression (push) Has been cancelled
Security and workflow regressions / regression (push) Has been cancelled
This commit is contained in:
+22
-6
@@ -61,6 +61,7 @@ class AuthService extends Base {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
$stmt = $this->connect()->prepare('UPDATE users SET user_last_login = :now WHERE user_serial = :id');
|
||||
$stmt->execute([':now' => $this->getTimestamp(), ':id' => $user['user_serial']]);
|
||||
Audit::record($this->connect(),(int)$user['user_serial'],'logged in ('.$user['user_role'].')');
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ class AuthService extends Base {
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || strlen($email) > 100)
|
||||
throw new InvalidArgumentException('Enter a valid email address (up to 100 bytes).');
|
||||
if (!preg_match('/\A[A-Za-z0-9_.-]{3,64}\z/', $name))
|
||||
throw new InvalidArgumentException('Use 3–64 letters, numbers, dots, underscores or hyphens for your username.');
|
||||
throw new InvalidArgumentException('Use 3 to 64 letters, numbers, dots, underscores or hyphens for your username.');
|
||||
$error = self::passwordError($input['user_password'] ?? '', $input['user_password_confirm'] ?? '');
|
||||
if ($error)
|
||||
throw new InvalidArgumentException($error);
|
||||
@@ -113,14 +114,14 @@ class AuthService extends Base {
|
||||
}
|
||||
|
||||
/** Never modify the password when somebody merely requests an email. */
|
||||
public function requestReset(string $identifier): void {
|
||||
public function requestReset(string $identifier): bool {
|
||||
$ipAllowed = $this->allowAttempt('reset-ip', $_SERVER['REMOTE_ADDR'] ?? '', 10, 3600);
|
||||
$accountAllowed = $this->allowAttempt('reset-account', $identifier, 3, 3600);
|
||||
if (!$ipAllowed || !$accountAllowed)
|
||||
return;
|
||||
return false;
|
||||
$user = $this->findUser($identifier);
|
||||
if (!$user || $user['user_status'] !== 'active')
|
||||
return;
|
||||
return false;
|
||||
$baseUrl = rtrim((string) $this->getSettings()->ApplicationUrl, '/');
|
||||
if (!filter_var($baseUrl, FILTER_VALIDATE_URL) || parse_url($baseUrl, PHP_URL_SCHEME) !== 'https'
|
||||
|| parse_url($baseUrl, PHP_URL_QUERY) !== null || parse_url($baseUrl, PHP_URL_FRAGMENT) !== null
|
||||
@@ -129,15 +130,27 @@ class AuthService extends Base {
|
||||
}
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$db = $this->connect();
|
||||
$stmt = $db->prepare('INSERT INTO password_resets (token_hash, user_serial, expires_at) VALUES (:hash, :id, DATE_ADD(UTC_TIMESTAMP(), INTERVAL 30 MINUTE))');
|
||||
$hash = hash('sha256', $token);
|
||||
$stmt->execute([':hash' => $hash, ':id' => $user['user_serial']]);
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
// Share the user-row lock with account edits and password changes. A
|
||||
// request resolved before such a change must not mint a token afterward.
|
||||
$stmt=$db->prepare('SELECT user_email,user_status,user_auth_version FROM users WHERE user_serial=? FOR UPDATE');
|
||||
$stmt->execute([$user['user_serial']]);$current=$stmt->fetch();
|
||||
if(!$current || $current['user_status']!=='active' || $current['user_email']!==$user['user_email'] || (int)$current['user_auth_version']!==(int)$user['user_auth_version']){
|
||||
$db->rollBack();return false;
|
||||
}
|
||||
$stmt=$db->prepare('INSERT INTO password_resets (token_hash,user_serial,expires_at) VALUES (?,?,DATE_ADD(UTC_TIMESTAMP(),INTERVAL 30 MINUTE))');
|
||||
$stmt->execute([$hash,$user['user_serial']]);$db->commit();
|
||||
}catch(Throwable $e){if($db->inTransaction())$db->rollBack();throw $e;}
|
||||
$url = $baseUrl . '/main.php?Xaction=resetpassword&token=' . rawurlencode($token);
|
||||
if (!$this->deliverResetEmail($user, $url)) {
|
||||
$stmt = $db->prepare('DELETE FROM password_resets WHERE token_hash = :hash');
|
||||
$stmt->execute([':hash' => $hash]);
|
||||
throw new RuntimeException('Password reset email delivery failed.');
|
||||
}
|
||||
Audit::record($db,(int)$user['user_serial'],'requested password reset');
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function deliverResetEmail(array $user, string $url): bool {
|
||||
@@ -174,7 +187,9 @@ class AuthService extends Base {
|
||||
return false;
|
||||
}
|
||||
$this->storePassword($db, (int) $id, $password);
|
||||
$stmt=$db->prepare('UPDATE users SET user_email_verified_at=UTC_TIMESTAMP() WHERE user_serial=?');$stmt->execute([$id]);
|
||||
$db->commit();
|
||||
Audit::record($db,(int)$id,'reset password');
|
||||
return true;
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction())
|
||||
@@ -199,6 +214,7 @@ class AuthService extends Base {
|
||||
}
|
||||
$this->storePassword($db, $id, $password);
|
||||
$db->commit();
|
||||
Audit::record($db,$id,'changed password');
|
||||
return true;
|
||||
} catch (Throwable $e) {
|
||||
if ($db->inTransaction())
|
||||
|
||||
Reference in New Issue
Block a user