Harden for public launch: rate limiting, CORS, error display, Gitea webhook sig
- User::login() locks an account for 15 min after 5 failed attempts - CORS now restricted to an explicit origin whitelist instead of * - display_errors disabled in production (errors still logged server-side) - webhook.php now checks Gitea's actual signature header (X-Gitea-Signature, raw hex) instead of GitHub's format, which never matched on this Gitea instance Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+46
-17
@@ -4,6 +4,8 @@
|
||||
class User {
|
||||
private $conn;
|
||||
private $table_name = "users";
|
||||
private $max_attempts = 5;
|
||||
private $lockout_minutes = 15;
|
||||
|
||||
public function __construct($db) {
|
||||
$this->conn = $db;
|
||||
@@ -44,28 +46,55 @@ class User {
|
||||
|
||||
// --- 2. CONNEXION ---
|
||||
public function login($email, $password) {
|
||||
$query = "SELECT id, username, email, password, pairing_code, partner_id FROM " . $this->table_name . " WHERE email = :email LIMIT 0,1";
|
||||
$query = "SELECT id, username, email, password, pairing_code, partner_id, failed_attempts, locked_until FROM " . $this->table_name . " WHERE email = :email LIMIT 0,1";
|
||||
$stmt = $this->conn->prepare($query);
|
||||
$stmt->bindParam(":email", $email);
|
||||
$stmt->execute();
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$row = $stmt->fetch();
|
||||
|
||||
// Vérification du mot de passe
|
||||
if (password_verify($password, $row['password'])) {
|
||||
return [
|
||||
"success" => true,
|
||||
"user" => [
|
||||
"id" => $row['id'],
|
||||
"username" => $row['username'],
|
||||
"email" => $row['email'],
|
||||
"pairing_code" => $row['pairing_code'],
|
||||
"partner_id" => $row['partner_id']
|
||||
]
|
||||
];
|
||||
}
|
||||
if ($stmt->rowCount() === 0) {
|
||||
return ["success" => false, "message" => "Identifiants incorrects."];
|
||||
}
|
||||
|
||||
$row = $stmt->fetch();
|
||||
|
||||
// Compte temporairement verrouillé après trop d'échecs
|
||||
if ($row['locked_until'] && strtotime($row['locked_until']) > time()) {
|
||||
return ["success" => false, "message" => "Trop de tentatives échouées. Réessaie dans quelques minutes."];
|
||||
}
|
||||
|
||||
// Vérification du mot de passe
|
||||
if (password_verify($password, $row['password'])) {
|
||||
$reset = $this->conn->prepare("UPDATE " . $this->table_name . " SET failed_attempts = 0, locked_until = NULL WHERE id = :id");
|
||||
$reset->bindParam(":id", $row['id']);
|
||||
$reset->execute();
|
||||
|
||||
return [
|
||||
"success" => true,
|
||||
"user" => [
|
||||
"id" => $row['id'],
|
||||
"username" => $row['username'],
|
||||
"email" => $row['email'],
|
||||
"pairing_code" => $row['pairing_code'],
|
||||
"partner_id" => $row['partner_id']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// Échec : on incrémente le compteur et on verrouille si le seuil est atteint
|
||||
$attempts = $row['failed_attempts'] + 1;
|
||||
if ($attempts >= $this->max_attempts) {
|
||||
$lock = $this->conn->prepare("UPDATE " . $this->table_name . " SET failed_attempts = :attempts, locked_until = DATE_ADD(NOW(), INTERVAL :minutes MINUTE) WHERE id = :id");
|
||||
$lock->bindParam(":attempts", $attempts, PDO::PARAM_INT);
|
||||
$lock->bindParam(":minutes", $this->lockout_minutes, PDO::PARAM_INT);
|
||||
$lock->bindParam(":id", $row['id']);
|
||||
$lock->execute();
|
||||
} else {
|
||||
$incr = $this->conn->prepare("UPDATE " . $this->table_name . " SET failed_attempts = :attempts WHERE id = :id");
|
||||
$incr->bindParam(":attempts", $attempts, PDO::PARAM_INT);
|
||||
$incr->bindParam(":id", $row['id']);
|
||||
$incr->execute();
|
||||
}
|
||||
|
||||
return ["success" => false, "message" => "Identifiants incorrects."];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user