diff --git a/bin/support-cli b/bin/support-cli index 0d38de4..dcbbdf5 100755 --- a/bin/support-cli +++ b/bin/support-cli @@ -5,10 +5,10 @@ const Database = require("better-sqlite3"); const path = require("path"); const crypto = require("crypto"); +const DB_PATH = path.resolve(__dirname, "../data/data.db"); + function callDb(fn) { - const db = new Database(path.resolve(__dirname, "../data/auth.db"), { - fileMustExist: true, - }); + const db = new Database(DB_PATH, { fileMustExist: true }); try { fn(db); } finally { @@ -20,118 +20,120 @@ function gen(bytes) { return crypto.randomBytes(bytes).toString("hex"); } +/** + * adduser + */ function addUser(args) { - const [username, email, status, displayName] = args; + const [userName, source] = args; + + if (!userName || !source) { + console.error("usage: adduser "); + process.exit(1); + } callDb((db) => { const res = db .prepare( ` - INSERT INTO users (username, email, status, display_name) - VALUES (?, ?, ?, ?) - `, + INSERT INTO users ( + user_name, + user_source, + user_status, + user_created_at + ) + VALUES (?, ?, 1, ?) + `, ) - .run(username, email, status, displayName); + .run(userName, source, Date.now()); console.log("user_id:", res.lastInsertRowid); }); } -function disUser(args) { - const [username] = args; - - callDb((db) => { - db.prepare( - ` - UPDATE users SET status = 'disabled' - WHERE username = ? - `, - ).run(username); - - console.log("user disabled:", username); - }); -} - -function listUser(args) { - const [username] = args; +/** + * genkey + */ +function genKey(args) { + const [userName] = args; callDb((db) => { const user = db - .prepare( - ` - SELECT user_id, username, email, status, display_name, created_at - FROM users - WHERE username = ? - `, - ) - .get(username); + .prepare(`SELECT user_id FROM users WHERE user_name = ?`) + .get(userName); if (!user) { - console.log("user not found"); - return; + console.error("user not found"); + process.exit(1); } - console.log("USER"); - console.log(user); - - const keys = db - .prepare( - ` - SELECT api_key, status, created_at, last_used_at - FROM api_keys - WHERE user_id = ? - `, - ) - .all(user.user_id); - - if (keys.length) { - console.log("\nAPI_KEYS"); - keys.forEach((k) => console.log(k)); - } - }); -} - -function addKey(args) { - const [username] = args; - - callDb((db) => { - const user = db - .prepare( - ` - SELECT user_id FROM users WHERE username = ? - `, - ) - .get(username); - - const apiKey = gen(24); - const apiSecret = gen(48); + const apiKey = gen(24); // 48 hex chars + const apiSecret = gen(48); // 96 hex chars db.prepare( ` - INSERT INTO api_keys (user_id, api_key, api_secret, status) - VALUES (?, ?, ?, 'active') + UPDATE users + SET user_api_key = ?, user_api_secret = ? + WHERE user_id = ? `, - ).run(user.user_id, apiKey, apiSecret); + ).run(apiKey, apiSecret, user.user_id); console.log("api_key:", apiKey); console.log("api_secret:", apiSecret); }); } -function disKey(args) { - const [apiKey] = args; +/** + * disable + */ +function disableUser(args) { + const [userName] = args; + callDb((db) => { db.prepare( ` - UPDATE api_keys SET status = 'revoked' - WHERE api_key = ? + UPDATE users SET user_status = 0 + WHERE user_name = ? `, - ).run(apiKey); + ).run(userName); - console.log("api_key revoked"); + console.log("user disabled:", userName); }); } +/** + * list + */ +function listUser(args) { + const [userName] = args; + + callDb((db) => { + const user = db + .prepare( + ` + SELECT + user_id, + user_name, + user_source, + user_status, + user_api_key, + user_created_at + FROM users + WHERE user_name = ? + `, + ) + .get(userName); + + if (!user) { + console.log("user not found"); + return; + } + + console.log(user); + }); +} + +/* ======================== */ + const argv = process.argv.slice(2); const cmd = argv[0]; const args = argv.slice(1); @@ -141,23 +143,20 @@ switch (cmd) { addUser(args); break; - case "disuser": - disUser(args); + case "genkey": + genKey(args); break; - case "listuser": + case "disable": + disableUser(args); + break; + + case "list": listUser(args); break; - case "addkey": - addKey(args); - break; - - case "diskey": - disKey(args); - break; - default: console.error("Unknown command"); + console.error("Commands: adduser | genkey | disable | list"); process.exit(1); } diff --git a/public/index.php b/public/index.php index 24543f7..d930ba1 100644 --- a/public/index.php +++ b/public/index.php @@ -4,9 +4,9 @@ declare(strict_types=1); require __DIR__ . '/../vendor/autoload.php'; use FrameworkX\App; -use Bass\Webclient\Auth\Middleware\HmacAuthMiddleware; +use Bass\Webclient\Middleware\HmacAuthMiddleware; use Bass\Webclient\Controllers\RequestController; -use Bass\Webclient\Http\ResponseLib; +use Bass\Webclient\Libs\ResponseLib; ini_set('display_errors', '1'); ini_set('display_startup_errors', '1'); @@ -16,14 +16,12 @@ $app = new App(); $hmacAuth = new HmacAuthMiddleware(); - $app->post( '/v1/request', $hmacAuth, RequestController::class ); - $app->get( '/health', fn() => ResponseLib::sendOk(['status' => 'ok']) diff --git a/src/Controllers/RequestController.php b/src/Controllers/RequestController.php index 565d1bb..20f720c 100644 --- a/src/Controllers/RequestController.php +++ b/src/Controllers/RequestController.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace Bass\Webclient\Controllers; use Bass\Webclient\Libs\RequestLib; -use Bass\Webclient\Http\ResponseLib; use Psr\Http\Message\ServerRequestInterface; use React\Http\Message\Response; + class RequestController { public function __invoke(ServerRequestInterface $request): Response diff --git a/src/Libs/RequestLib.php b/src/Libs/RequestLib.php index a8bfe2c..89f3544 100644 --- a/src/Libs/RequestLib.php +++ b/src/Libs/RequestLib.php @@ -2,11 +2,9 @@ declare(strict_types=1); namespace Bass\Webclient\Libs; - -use Bass\Webclient\Domain\Client\ClientModel; +use Bass\Webclient\Model\LeadModel; +use Bass\Webclient\Schema\LeadSchema; use React\Http\Message\Response; -use Bass\Webclient\Http\ResponseLib; -use Bass\Webclient\Schema\ClientCreateSchema; class RequestLib { @@ -36,71 +34,51 @@ class RequestLib ); } - $schema = ClientCreateSchema::schema(); + $schema = LeadSchema::schema(); [$ok, $err] = GuardLib::requireJsonObject($data); if (!$ok) { - return ResponseLib::sendFail( - $err['message'], - 400, - $err - ); + return ResponseLib::sendFail($err['message'], 400, $err); } [$ok, $err] = GuardLib::maxPayloadFields($data); if (!$ok) { - return ResponseLib::sendFail( - $err['message'], - 400, - $err - ); + return ResponseLib::sendFail($err['message'], 400, $err); } [$ok, $err] = GuardLib::allowOnlyFields($data, array_keys($schema)); if (!$ok) { - return ResponseLib::sendFail( - $err['message'], - 400, - $err - ); + return ResponseLib::sendFail($err['message'], 400, $err); } [$ok, $err] = GuardLib::blockDangerousPatterns($data); if (!$ok) { - return ResponseLib::sendFail( - $err['message'], - 400, - $err - ); + return ResponseLib::sendFail($err['message'], 400, $err); } + [$ok, $err] = GuardLib::requiredBySchema($data, $schema); if (!$ok) { - return ResponseLib::sendFail( - $err['message'], - 422, - $err - ); + return ResponseLib::sendFail($err['message'], 422, $err); } + [$ok, $err] = GuardLib::validateBySchema($data, $schema); if (!$ok) { - return ResponseLib::sendFail( - $err['message'], - 422, - $err - ); + return ResponseLib::sendFail($err['message'], 422, $err); } + [, $data] = SanitizationLib::cleanBySchema($data, $schema); - [$ok, $result] = (new ClientModel())->insert($data); + + [$ok, $result] = (new LeadModel())->insert($data); if (!$ok) { return ResponseLib::sendFail( 'Internal server error', 500, - [ - 'code' => 'DATABASE_ERROR' - ] + ['code' => 'DATABASE_ERROR'] ); } + self::sendEmails($data); + return ResponseLib::sendOk( [ 'lead_id' => $result['lead_id'], diff --git a/src/Libs/ResponseLib.php b/src/Libs/ResponseLib.php index 5470bd7..e77d287 100644 --- a/src/Libs/ResponseLib.php +++ b/src/Libs/ResponseLib.php @@ -1,7 +1,7 @@ 300) { return ResponseLib::sendFail( 'Expired request', @@ -66,8 +67,6 @@ class HmacAuthMiddleware ); } - (new ApiKeyModel())->touchLastUsed($apiKey); - return $next( $request->withAttribute('auth', [ 'user_id' => $result['user_id'], diff --git a/src/Middleware/RateLimitMiddleware.php b/src/Middleware/RateLimitMiddleware.php index 5c0787e..5266f54 100644 --- a/src/Middleware/RateLimitMiddleware.php +++ b/src/Middleware/RateLimitMiddleware.php @@ -1,11 +1,11 @@ windowSeconds = $windowSeconds; } - public function __invoke(ServerRequestInterface $request, callable $next): Response - { + public function __invoke( + ServerRequestInterface $request, + callable $next + ): Response { $apiKey = $request->getHeaderLine('X-API-KEY'); if (!$apiKey) { @@ -31,7 +33,7 @@ class RateLimitMiddleware ); } - // 🔒 rate-limit SOMENTE para chave válida + // 🔒 rate-limit por API key (em memória) if (!$this->allowRequest($apiKey)) { return ResponseLib::sendFail( 'Rate limit exceeded', @@ -47,7 +49,8 @@ class RateLimitMiddleware } /** - * Controle de rate-limit em memória + * In-memory rate-limit control + * (per process, per API key) */ private function allowRequest(string $key): bool { diff --git a/src/Model/ApiKeyModel.php b/src/Model/ApiKeyModel.php index cb35246..f7d830d 100644 --- a/src/Model/ApiKeyModel.php +++ b/src/Model/ApiKeyModel.php @@ -1,26 +1,26 @@ prepare($sql); diff --git a/src/Model/LeadModel.php b/src/Model/LeadModel.php index e2b053d..473cd37 100644 --- a/src/Model/LeadModel.php +++ b/src/Model/LeadModel.php @@ -1,12 +1,13 @@ db->prepare($sql); $stmt->execute($data); + return [ true, [ @@ -37,7 +58,7 @@ class ClientModel false, [ 'code' => 'DB_ERROR', - 'message' => 'Failed to insert client' + 'message' => 'Failed to insert lead' ] ]; } diff --git a/src/Model/ModelFactory.php b/src/Model/ModelFactory.php index e3ad86f..390a27e 100644 --- a/src/Model/ModelFactory.php +++ b/src/Model/ModelFactory.php @@ -1,7 +1,7 @@ 60, ], 'number_of_employees' => [ - 'required' => false, + 'required' => true, 'type' => 'int', 'min' => 1, ], diff --git a/src/Service/LeadService.php b/src/Service/LeadService.php new file mode 100644 index 0000000..e6606f4 --- /dev/null +++ b/src/Service/LeadService.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/migration/schema.sql b/src/migration/schema.sql index 69cf991..fb2411a 100644 --- a/src/migration/schema.sql +++ b/src/migration/schema.sql @@ -1,5 +1,6 @@ PRAGMA foreign_keys=ON; +-- TODO: ADD TAXID TO BE LIMIT ONE REQUEST PER VALID USER ID CREATE TABLE users ( user_id INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/test/request.sh b/test/request.sh index f320503..5e20b57 100755 --- a/test/request.sh +++ b/test/request.sh @@ -4,9 +4,9 @@ set -euo pipefail # ========= CONFIG ========= URL="http://127.0.0.1:8080/v1/request" -API_KEY="test_api_key" -API_SECRET="test_api_secret" -API_USER="test_user" +API_KEY="4677fb5d0258618550bdceacaf0acf39e5a38de18855bb65" +API_SECRET="5177347e4209991841d6721d733c831caf3752424941a5c9d3560dccb5e40711df34f9df7a9dda667a26be21a7e699ac" +API_USER="my-client" # ========= PAYLOAD ========= BODY='{