diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..eb56c24 --- /dev/null +++ b/readme.md @@ -0,0 +1,31 @@ +├── bin +│   ├── email-cli +│   └── support-cli +├── data +│   └── data.db +├── logs +├── public +│   └── index.php +├── readme.md +├── src +│   ├── Middleware +│   ├ |── HmacAuthMiddleware.php +│   │   └── RateLImitMiddleware.php +│   ├── Controllers +│   │   └── RequestController.php +│   ├── Model +│   │   └── ModelFactory.php +│   │   └── LeadModel.php +│   ├── Libs +│   │   └── ResponseLib.php +│   │   ├── ExecLib.php +│   │   ├── GuardLib.php +│   │   ├── RequestLib.php +│   │   └── SanitizationLib.php +│   ├── migration +│   │   └── schema.sql +│   └── Schema +│   └── LeadSchema.php +└── test + ├── hmac_curl.sh + └── request.sh diff --git a/src/Auth/Infra/AuthModelFactory.php b/src/Auth/Infra/AuthModelFactory.php deleted file mode 100644 index 741f4a7..0000000 --- a/src/Auth/Infra/AuthModelFactory.php +++ /dev/null @@ -1,22 +0,0 @@ -setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - self::$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); - self::$db->exec('PRAGMA foreign_keys = ON;'); - } - return self::$db; - } -} diff --git a/src/Auth/Models/ApiKeyModel.php b/src/Auth/Models/ApiKeyModel.php deleted file mode 100644 index 992cca6..0000000 --- a/src/Auth/Models/ApiKeyModel.php +++ /dev/null @@ -1,65 +0,0 @@ -prepare($sql); - $stmt->execute(['api_key' => $apiKey]); - $row = $stmt->fetch(); - - if (!$row) { - return [ - false, - [ - 'code' => 'API_KEY_NOT_FOUND', - 'message' => 'Invalid API key' - ] - ]; - } - - return [true, $row]; - } catch (\Throwable $e) { - return [ - false, - [ - 'code' => 'AUTH_DB_ERROR', - 'message' => 'Failed to query auth database' - ] - ]; - } - } - - public function touchLastUsed(string $apiKey): void - { - $sql = "UPDATE api_keys SET last_used_at = datetime('now') WHERE api_key = :api_key"; - try { - AuthModelFactory::db() - ->prepare($sql) - ->execute(['api_key' => $apiKey]); - } catch (\Throwable $e) { - // falha aqui NÃO bloqueia request - } - } -} diff --git a/src/Libs/RequestLib.php b/src/Libs/RequestLib.php index e9a0e83..a8bfe2c 100644 --- a/src/Libs/RequestLib.php +++ b/src/Libs/RequestLib.php @@ -103,7 +103,7 @@ class RequestLib self::sendEmails($data); return ResponseLib::sendOk( [ - 'client_id' => $result['client_id'], + 'lead_id' => $result['lead_id'], 'status' => 'received' ], 201 diff --git a/src/Http/ResponseLib.php b/src/Libs/ResponseLib.php similarity index 100% rename from src/Http/ResponseLib.php rename to src/Libs/ResponseLib.php diff --git a/src/Auth/Middleware/HmacAuthMiddleware.php b/src/Middleware/HmacAuthMiddleware.php similarity index 100% rename from src/Auth/Middleware/HmacAuthMiddleware.php rename to src/Middleware/HmacAuthMiddleware.php diff --git a/src/Auth/Middleware/RateLImitMiddleware.php b/src/Middleware/RateLimitMiddleware.php similarity index 60% rename from src/Auth/Middleware/RateLImitMiddleware.php rename to src/Middleware/RateLimitMiddleware.php index eae2f81..5c0787e 100644 --- a/src/Auth/Middleware/RateLImitMiddleware.php +++ b/src/Middleware/RateLimitMiddleware.php @@ -11,8 +11,7 @@ class RateLimitMiddleware { private int $maxRequests; private int $windowSeconds; - - private const API_KEY_REGEX = '/^[a-f0-9]{48}$/'; + private array $limits = []; public function __construct(int $maxRequests = 30, int $windowSeconds = 60) { @@ -32,15 +31,6 @@ class RateLimitMiddleware ); } - // 🔒 FORMATO INVÁLIDO → DROP IMEDIATO - if (!preg_match(self::API_KEY_REGEX, $apiKey)) { - return ResponseLib::sendFail( - 'Invalid API key format', - 401, - ['code' => 'INVALID_API_KEY_FORMAT'] - ); - } - // 🔒 rate-limit SOMENTE para chave válida if (!$this->allowRequest($apiKey)) { return ResponseLib::sendFail( @@ -57,36 +47,21 @@ class RateLimitMiddleware } /** - * Controle de rate-limit por filesystem + * Controle de rate-limit em memória */ private function allowRequest(string $key): bool { $now = time(); - $file = sys_get_temp_dir() . '/rl_' . sha1($key); - $data = [ - 'count' => 0, - 'reset' => $now + $this->windowSeconds - ]; - - if (file_exists($file)) { - $stored = json_decode(file_get_contents($file), true); - if (is_array($stored)) { - $data = $stored; - } - } - - if ($now > $data['reset']) { - $data = [ + if (!isset($this->limits[$key]) || $now > $this->limits[$key]['reset']) { + $this->limits[$key] = [ 'count' => 0, 'reset' => $now + $this->windowSeconds ]; } - $data['count']++; + $this->limits[$key]['count']++; - file_put_contents($file, json_encode($data), LOCK_EX); - - return $data['count'] <= $this->maxRequests; + return $this->limits[$key]['count'] <= $this->maxRequests; } } diff --git a/src/Model/ApiKeyModel.php b/src/Model/ApiKeyModel.php new file mode 100644 index 0000000..cb35246 --- /dev/null +++ b/src/Model/ApiKeyModel.php @@ -0,0 +1,51 @@ +prepare($sql); + $stmt->execute(['api_key' => $apiKey]); + $row = $stmt->fetch(); + + if (!$row) { + return [ + false, + [ + 'code' => 'API_KEY_NOT_FOUND', + 'message' => 'Invalid API key' + ] + ]; + } + + return [true, $row]; + } catch (\Throwable $e) { + return [ + false, + [ + 'code' => 'AUTH_DB_ERROR', + 'message' => 'Failed to query auth database' + ] + ]; + } + } +} diff --git a/src/Domain/Client/ClientModel.php b/src/Model/LeadModel.php similarity index 78% rename from src/Domain/Client/ClientModel.php rename to src/Model/LeadModel.php index 53d0b4e..e2b053d 100644 --- a/src/Domain/Client/ClientModel.php +++ b/src/Model/LeadModel.php @@ -18,8 +18,8 @@ class ClientModel public function insert(array $data): array { $sql = " - INSERT INTO client_request - (name, phone, email, company_name, sector, number_of_employees, revenue, description) + INSERT INTO lead + (lead_name, lead_phone, lead_email, lead_company, lead_sector, lead_employees, lead_revenue, lead_description) VALUES (:name, :phone, :email, :company_name, :sector, :number_of_employees, :revenue, :description) "; @@ -29,7 +29,7 @@ class ClientModel return [ true, [ - 'client_id' => (int) $this->db->lastInsertId() + 'lead_id' => (int) $this->db->lastInsertId() ] ]; } catch (\Throwable $e) { diff --git a/src/Infra/ModelFactory.php b/src/Model/ModelFactory.php similarity index 87% rename from src/Infra/ModelFactory.php rename to src/Model/ModelFactory.php index cbe06b7..e3ad86f 100644 --- a/src/Infra/ModelFactory.php +++ b/src/Model/ModelFactory.php @@ -9,7 +9,7 @@ class ModelFactory { public static function db(): PDO { - $db = new PDO('sqlite:' . __DIR__ . '/../../data/app.db'); + $db = new PDO('sqlite:' . __DIR__ . '/../../data/data.db'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $db->exec('PRAGMA foreign_keys = ON;'); diff --git a/src/Schema/ClientCreateSchema.php b/src/Schema/LeadSchema.php similarity index 97% rename from src/Schema/ClientCreateSchema.php rename to src/Schema/LeadSchema.php index 1c8dd3c..f5a6c22 100644 --- a/src/Schema/ClientCreateSchema.php +++ b/src/Schema/LeadSchema.php @@ -3,7 +3,7 @@ declare(strict_types=1); namespace Bass\Webclient\Schema; -class ClientCreateSchema +class LeadSchema { public static function schema(): array {