瀏覽代碼

full generic rest http api template for framework x with hmac auth and jwt auth

ljoaquim 5 月之前
父節點
當前提交
6fd57b54dd

+ 3 - 0
.env.example

@@ -0,0 +1,3 @@
+APP_PORT=8080
+DB_FILE=test.db
+JWT_SECRET=Aer8woa9zeec2gai4ahQuah3Ahbee5eiSefae8pheepahnootuShoo0oKahf

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
 vendor/
 .env
 *.log
+test.db

+ 44 - 0
bin/setup

@@ -0,0 +1,44 @@
+#!/bin/bash
+
+# Nome do arquivo do banco de dados
+DB_FILE="test.db"
+
+# Executa comandos SQL no SQLite
+sqlite3 "$DB_FILE" <<EOF
+-- Cria tabela 'user' se não existir, com coluna 'password'
+CREATE TABLE IF NOT EXISTS user (
+    user_id INTEGER PRIMARY KEY AUTOINCREMENT,
+    user_name TEXT NOT NULL,
+    user_flag TEXT NOT NULL,
+    password TEXT NOT NULL  -- Nova coluna para senha hasheada
+);
+
+-- Cria tabela 'api_key' se não existir
+CREATE TABLE IF NOT EXISTS api_key (
+    api_key_id INTEGER PRIMARY KEY AUTOINCREMENT,
+    user_id INTEGER NOT NULL,
+    api_key_user TEXT NOT NULL,
+    api_key_secret TEXT NOT NULL,
+    FOREIGN KEY (user_id) REFERENCES user(user_id)
+);
+
+-- Insere usuário de exemplo ('admin') com senha hasheada se não existir
+-- Hash de 'pass' (gere com password_hash em PHP e substitua)
+INSERT OR IGNORE INTO user (user_name, user_flag, password) VALUES ('admin', 'a', '\$2y\$10\$K.0XhB3kXjZfZfZfZfZfZfZfZfZfZfZfZfZfZfZfZfZfZfZfZfZ');
+
+-- Insere chave API para o usuário 'admin' se não existir
+INSERT OR IGNORE INTO api_key (user_id, api_key_user, api_key_secret)
+SELECT user_id, 'myapikey', 'myapisecret' FROM user WHERE user_name = 'admin';
+
+-- Opcional: Insere mais um usuário de teste com senha hasheada
+-- Hash de 'testpass' (substitua pelo real)
+INSERT OR IGNORE INTO user (user_name, user_flag, password) VALUES ('testuser', 'a', '\$2y\$10\$AnotherHashHereForTestPass');
+INSERT OR IGNORE INTO api_key (user_id, api_key_user, api_key_secret)
+SELECT user_id, 'testapikey', 'testapisecret' FROM user WHERE user_name = 'testuser';
+
+-- Exibe os dados inseridos para verificação (sem mostrar hash real por segurança)
+SELECT user_id, user_name, user_flag FROM user;
+SELECT * FROM api_key;
+EOF
+
+echo "Banco de dados '$DB_FILE' criado e populado com sucesso! Senhas estão hasheadas."

+ 25 - 0
bin/testgetjwt

@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Script 1: Obtém JWT via login
+# Configurações
+API_URL="http://localhost:8000/login"  # Rota de login
+USERNAME="newuser"
+PASSWORD="strongpass123"  # Substitua pela senha real
+
+# Body JSON para login
+BODY="{\"username\":\"${USERNAME}\",\"password\":\"${PASSWORD}\"}"
+
+# Faz POST para login e extrai o token JWT usando jq (instale jq se necessário: sudo apt install jq)
+JWT=$(curl -s -X POST "${API_URL}" \
+    -H "Content-Type: application/json" \
+    -d "${BODY}" | jq -r '.data.token')
+
+# Verifica se JWT foi obtido
+if [ -z "$JWT" ] || [ "$JWT" = "null" ]; then
+    echo "Erro ao obter JWT. Resposta do servidor:"
+    curl -v -X POST "${API_URL}" -H "Content-Type: application/json" -d "${BODY}"
+    exit 1
+fi
+
+echo "JWT obtido: ${JWT}"
+echo "Salve este JWT para usar no próximo script (ex: export JWT=${JWT})"

+ 33 - 0
bin/testhmac

@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Configurações da API e dados do usuário (do test.db)
+API_URL="http://localhost:8000/hmachelloworld"  # Ajuste a porta se necessário (ex: 8080)
+API_USER="admin"
+API_KEY="myapikey"
+API_SECRET="myapisecret"
+
+# Gera nonce (timestamp atual em segundos)
+NONCE=$(date +%s)
+
+# Para GET, body é vazio, então message = "::${NONCE}"
+MESSAGE="::${NONCE}"
+
+# Secret = API_KEY::API_SECRET
+SECRET="${API_KEY}::${API_SECRET}"
+
+# Calcula a assinatura HMAC-SHA256 (usando openssl)
+SIGNATURE=$(echo -n "${MESSAGE}" | openssl dgst -sha256 -hmac "${SECRET}" | awk '{print $2}')
+
+# Verifica se a assinatura foi gerada corretamente
+if [ -z "$SIGNATURE" ]; then
+    echo "Erro ao gerar assinatura HMAC."
+    exit 1
+fi
+
+# Envia a requisição curl com headers
+echo "Testando rota / com HMAC:"
+curl -v \
+    -H "x-user: ${API_USER}" \
+    -H "x-nonce: ${NONCE}" \
+    -H "x-signature: ${SIGNATURE}" \
+    "${API_URL}"

+ 18 - 0
bin/testusejwt

@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Script 2: Envia JWT para rota autenticada /jwthelloworld
+# Configurações
+API_URL="http://localhost:8000/jwthelloworld"  # Rota autenticada por JWT
+JWT="${JWT:-seu-jwt-aqui}"  # Use export JWT=... do script anterior ou cole aqui
+
+# Verifica se JWT está definido
+if [ -z "$JWT" ] || [ "$JWT" = "seu-jwt-aqui" ]; then
+    echo "Defina o JWT (ex: export JWT=seu-token) antes de rodar."
+    exit 1
+fi
+
+# Envia GET (ou ajuste para POST se necessário) com Authorization Bearer
+echo "Testando rota /jwthelloworld com JWT:"
+curl -v \
+    -H "Authorization: Bearer ${JWT}" \
+    "${API_URL}"

+ 5 - 2
composer.json

@@ -7,7 +7,8 @@
             "Middlewares\\": "middlewares/",
             "Models\\": "models/",
             "Services\\": "services/",
-            "Utils\\": "utils/"
+            "Utils\\": "utils/",
+            "Libs\\": "libs/"
         }
     },
     "authors": [
@@ -17,6 +18,8 @@
         }
     ],
     "require": {
-        "clue/framework-x": "^0.17.0"
+        "clue/framework-x": "^0.17.0",
+        "vlucas/phpdotenv": "^5.6",
+        "firebase/php-jwt": "^6.11"
     }
 }

+ 525 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "7bd8c39b1bd892ae991dfe845c117bf9",
+    "content-hash": "290dc0f613a520d4fde731b332c80e13",
     "packages": [
         {
             "name": "clue/framework-x",
@@ -180,6 +180,131 @@
             },
             "time": "2020-11-24T22:02:12+00:00"
         },
+        {
+            "name": "firebase/php-jwt",
+            "version": "v6.11.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/firebase/php-jwt.git",
+                "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
+                "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^8.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^7.4",
+                "phpspec/prophecy-phpunit": "^2.0",
+                "phpunit/phpunit": "^9.5",
+                "psr/cache": "^2.0||^3.0",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0"
+            },
+            "suggest": {
+                "ext-sodium": "Support EdDSA (Ed25519) signatures",
+                "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Firebase\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Neuman Vong",
+                    "email": "neuman+pear@twilio.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Anant Narayanan",
+                    "email": "anant@php.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+            "homepage": "https://github.com/firebase/php-jwt",
+            "keywords": [
+                "jwt",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/firebase/php-jwt/issues",
+                "source": "https://github.com/firebase/php-jwt/tree/v6.11.1"
+            },
+            "time": "2025-04-09T20:32:01+00:00"
+        },
+        {
+            "name": "graham-campbell/result-type",
+            "version": "v1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/GrahamCampbell/Result-Type.git",
+                "reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
+                "reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GrahamCampbell\\ResultType\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "An Implementation Of The Result Type",
+            "keywords": [
+                "Graham Campbell",
+                "GrahamCampbell",
+                "Result Type",
+                "Result-Type",
+                "result"
+            ],
+            "support": {
+                "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+                "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-20T21:45:45+00:00"
+        },
         {
             "name": "nikic/fast-route",
             "version": "v1.3.0",
@@ -230,6 +355,81 @@
             },
             "time": "2018-02-13T20:26:39+00:00"
         },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.9.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
+                "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpOption\\": "src/PhpOption/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Johannes M. Schmitt",
+                    "email": "schmittjoh@gmail.com",
+                    "homepage": "https://github.com/schmittjoh"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "support": {
+                "issues": "https://github.com/schmittjoh/php-option/issues",
+                "source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-07-20T21:41:07+00:00"
+        },
         {
             "name": "psr/http-message",
             "version": "1.1",
@@ -899,6 +1099,330 @@
                 }
             ],
             "time": "2024-06-11T12:45:25+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.32.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+                "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-ctype": "*"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-09-09T11:45:10+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.32.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+                "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+                "shasum": ""
+            },
+            "require": {
+                "ext-iconv": "*",
+                "php": ">=7.2"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2024-12-23T08:48:59+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.32.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+                "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2"
+            },
+            "type": "library",
+            "extra": {
+                "thanks": {
+                    "url": "https://github.com/symfony/polyfill",
+                    "name": "symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-01-02T08:10:11+00:00"
+        },
+        {
+            "name": "vlucas/phpdotenv",
+            "version": "v5.6.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/vlucas/phpdotenv.git",
+                "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
+                "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af",
+                "shasum": ""
+            },
+            "require": {
+                "ext-pcre": "*",
+                "graham-campbell/result-type": "^1.1.3",
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.3",
+                "symfony/polyfill-ctype": "^1.24",
+                "symfony/polyfill-mbstring": "^1.24",
+                "symfony/polyfill-php80": "^1.24"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-filter": "*",
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "suggest": {
+                "ext-filter": "Required to use the boolean validator."
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
+                },
+                "branch-alias": {
+                    "dev-master": "5.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Dotenv\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Vance Lucas",
+                    "email": "vance@vancelucas.com",
+                    "homepage": "https://github.com/vlucas"
+                }
+            ],
+            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+            "keywords": [
+                "dotenv",
+                "env",
+                "environment"
+            ],
+            "support": {
+                "issues": "https://github.com/vlucas/phpdotenv/issues",
+                "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2025-04-30T23:37:27+00:00"
         }
     ],
     "packages-dev": [],

+ 6 - 3
controllers/HelloController.php

@@ -2,12 +2,15 @@
 
 namespace Controllers;
 
-use React\Http\Message\Response;
+use Libs\ResponseLib;
+use Psr\Http\Message\ServerRequestInterface;
 
 class HelloController
 {
-    public function __invoke()
+    public function __invoke(ServerRequestInterface $request)
     {
-        return Response::plaintext("Hello World!\n");
+        //$apiUser = $request->getAttribute('api_user');  // Exemplo: usa atributo do middleware
+        $data = ["message" => "Hello World!"];
+        return ResponseLib::sendOk($data);
     }
 }

+ 40 - 0
controllers/LoginController.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace Controllers;
+
+use Firebase\JWT\JWT;
+use Libs\ResponseLib;
+use Models\UserModel;
+use Psr\Http\Message\ServerRequestInterface;
+
+class LoginController
+{
+    public function __invoke(ServerRequestInterface $request)
+    {
+        $body = json_decode((string) $request->getBody(), true);
+        $username = $body['username'] ?? '';
+        $password = $body['password'] ?? '';
+
+        if (empty($username) || empty($password)) {
+            return ResponseLib::sendFail("Missing username or password", [], "E_VALIDATE")->withStatus(401);
+        }
+
+        $userModel = new UserModel();
+        $user = $userModel->validateLogin($username, $password);
+
+        if (!$user) {
+            return ResponseLib::sendFail("Invalid credentials", [], "E_VALIDATE")->withStatus(401);
+        }
+
+        // Gera JWT
+        $payload = [
+            'sub' => $user['user_id'],
+            'username' => $user['user_name'],
+            'iat' => time(),
+            'exp' => time() + 3600  // 1 hora
+        ];
+        $jwt = JWT::encode($payload, $_ENV['JWT_SECRET'], 'HS256');
+
+        return ResponseLib::sendOk(['token' => $jwt, 'user_id' => $user['user_id']]);
+    }
+}   

+ 35 - 0
controllers/RegisterController.php

@@ -0,0 +1,35 @@
+<?php
+
+namespace Controllers;
+
+use Libs\ResponseLib;
+use Models\UserModel;
+use Psr\Http\Message\ServerRequestInterface;
+
+class RegisterController
+{
+    public function __invoke(ServerRequestInterface $request)
+    {
+        $body = json_decode((string) $request->getBody(), true);
+        $username = $body['username'] ?? '';
+        $password = $body['password'] ?? '';
+
+        if (empty($username) || empty($password)) {
+            return ResponseLib::sendFail("Missing username or password", [], "E_VALIDATE")->withStatus(400);
+        }
+
+        // Validação básica (ex: comprimento mínimo)
+        if (strlen($password) < 8) {
+            return ResponseLib::sendFail("Password must be at least 8 characters", [], "E_VALIDATE")->withStatus(400);
+        }
+
+        $userModel = new UserModel();
+        $userData = $userModel->createUser($username, $password);
+
+        if (!$userData) {
+            return ResponseLib::sendFail("Username already exists or creation failed", [], "E_VALIDATE")->withStatus(400);
+        }
+
+        return ResponseLib::sendOk($userData, "S_CREATED");
+    }
+}

+ 31 - 0
libs/ResponseLib.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Libs;
+
+use React\Http\Message\Response;
+
+class ResponseLib
+{
+    public static function sendOk($data, $code = "S_OK")
+    {
+        $reply = [
+            'status' => 'ok',
+            'msg' => '[100] Request ok.',
+            'code' => $code,
+            'data' => $data
+        ];
+        return Response::json($reply);
+    }
+
+    public static function sendFail($message, $data = [], $code = "E_GENERIC")
+    {
+        $reply = [
+            'status' => 'failed',
+            'msg' => $message,
+            'code' => $code,
+            'data' => $data
+        ];
+        return Response::json($reply);
+    }
+}
+

+ 66 - 0
middlewares/HmacAuthMiddleware.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace Middlewares;
+
+use Libs\ResponseLib;
+use Models\ApiUserModel;
+use Psr\Http\Message\ServerRequestInterface;
+use React\Http\Message\Response;
+
+class HmacAuthMiddleware
+{
+    private array $api_Key;
+
+    public function __construct()
+    {
+        // Instancia a model e carrega as chaves API
+        $apiUserModel = new ApiUserModel();
+        $this->api_Key = $apiUserModel->getApiKeys();
+    }
+
+    public function __invoke(ServerRequestInterface $request, callable $next)
+    {
+        // 1. Extrai headers
+        $signature = $request->getHeaderLine('x-signature');
+        $apiUser = $request->getHeaderLine('x-user');
+        $nonce = $request->getHeaderLine('x-nonce');
+
+        if (empty($signature) || empty($apiUser) || empty($nonce)) {
+            return ResponseLib::sendFail("Unauthorized: Missing signature or headers", [], "E_VALIDATE")->withStatus(401);
+        }
+
+        // 2. Verifica se nonce está dentro do intervalo
+        $currentTime = time();
+        if (abs($currentTime - (int) $nonce) > 2) {
+            return ResponseLib::sendFail("Unauthorized: Invalid or expired nonce", [], "E_VALIDATE")->withStatus(401);
+        }
+
+        // 3. Verifica se o usuário é válido
+        if (!isset($this->api_Key[$apiUser])) {
+            return ResponseLib::sendFail("Unauthorized: Invalid API User", [], "E_VALIDATE")->withStatus(401);
+        }
+
+        $apiKey = $this->api_Key[$apiUser]['user_apikey'];
+        $apiSecret = $this->api_Key[$apiUser]['user_apisecret'];
+        $secret = $apiKey . "::" . $apiSecret;
+
+        // 4. Monta mensagem para HMAC: <jsonBody>::<nonce>
+        $rawBody = (string) $request->getBody();
+        $message = $rawBody . "::" . $nonce;
+
+        // 5. Calcula assinatura esperada
+        $expectedSignature = hash_hmac('sha256', $message, $secret);
+
+        // 6. Verifica assinatura
+        if (!hash_equals($expectedSignature, $signature)) {
+            return ResponseLib::sendFail("Unauthorized: Signature mismatch", [], "E_VALIDATE")->withStatus(401);
+        }
+
+        // 7. Tudo certo, adiciona atributos ao request e segue
+        $request = $request
+            ->withAttribute('api_user', $apiUser)
+            ->withAttribute('api_user_id', $this->api_Key[$apiUser]['user_id']);
+
+        return $next($request);
+    }
+}

+ 69 - 0
middlewares/JWTAuthMiddleware.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace Middlewares;
+
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+use Libs\ResponseLib;
+use Psr\Http\Message\ServerRequestInterface;
+use React\Http\Message\Response;
+
+class JwtAuthMiddleware
+{
+    private string $jwtSecret;
+
+    public function __construct()
+    {
+        // Carrega a chave secreta do .env (ex: JWT_SECRET=seu-segredo-aqui)
+        $this->jwtSecret = $_ENV['JWT_SECRET'] ?? 'default-secret-fallback';  // Use um fallback seguro em dev
+    }
+
+    public function __invoke(ServerRequestInterface $request, callable $next)
+    {
+        // 1. Extrai o token do header Authorization
+        $authHeader = $request->getHeaderLine('Authorization');
+        if (empty($authHeader) || !preg_match('/Bearer\s+(.*)/', $authHeader, $matches)) {
+            return ResponseLib::sendFail("Unauthorized: Missing or invalid Authorization header", [], "E_VALIDATE")->withStatus(401);
+        }
+
+        $token = $matches[1];
+
+        try {
+            // 2. Decodifica e valida o JWT
+            $decoded = JWT::decode($token, new Key($this->jwtSecret, 'HS256'));  // Use HS256 ou algoritmo desejado
+
+            // 3. Extrai claims (assuma que o JWT tem 'sub' como user_id e 'username')
+            $userId = $decoded->sub ?? null;
+            $apiUser = $decoded->username ?? null;
+
+            if (empty($userId) || empty($apiUser)) {
+                return ResponseLib::sendFail("Unauthorized: Invalid JWT claims", [], "E_VALIDATE")->withStatus(401);
+            }
+
+            // 4. Verifica se o usuário existe e está ativo no banco (similar ao HMAC)
+            $dbFile = $_ENV['DB_FILE'] ?? 'bridge.db';
+            $dbPath = __DIR__ . '/../' . $dbFile;
+            $pdo = new \PDO("sqlite:" . $dbPath);
+            $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+
+            $stmt = $pdo->prepare("SELECT user_id FROM user WHERE user_id = :user_id AND user_name = :user_name AND user_flag = 'a'");
+            $stmt->execute(['user_id' => $userId, 'user_name' => $apiUser]);
+            $user = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+            if (!$user) {
+                return ResponseLib::sendFail("Unauthorized: Invalid or inactive user", [], "E_VALIDATE")->withStatus(401);
+            }
+
+            // 5. Tudo certo, adiciona atributos ao request (compatível com HMAC)
+            $request = $request
+                ->withAttribute('api_user', $apiUser)
+                ->withAttribute('api_user_id', $userId);
+
+            return $next($request);
+
+        } catch (\Exception $e) {
+            // Captura erros de JWT (ex: expirado, inválido)
+            return ResponseLib::sendFail("Unauthorized: " . $e->getMessage(), [], "E_VALIDATE")->withStatus(401);
+        }
+    }
+}

+ 58 - 0
models/ApiUserModel.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace Models;
+
+class ApiUserModel
+{
+    private \PDO $pdo;
+    private array $api_Key = [];
+
+    public function __construct()
+    {
+        // Conecta ao DB usando variável do .env
+        $dbFile = $_ENV['DB_FILE'] ?? 'bridge.db';
+        $dbPath = __DIR__ . '/../' . $dbFile;
+        $this->pdo = new \PDO("sqlite:" . $dbPath);
+        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+
+        $this->loadApiKeys();
+    }
+
+    /**
+     * Carrega as chaves API dos usuários ativos do banco de dados.
+     */
+    private function loadApiKeys(): void
+    {
+        try {
+            $stmt = $this->pdo->query("SELECT user_id, user_name, api_key_user, api_key_secret FROM user NATURAL JOIN api_key WHERE user_flag = 'a'");
+            $users = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+
+            foreach ($users as $user) {
+                $this->api_Key[$user['user_name']] = [
+                    'user_apikey' => $user['api_key_user'],
+                    'user_apisecret' => $user['api_key_secret'],
+                    'user_id' => $user['user_id']
+                ];
+            }
+        } catch (\PDOException $e) {
+            error_log("Erro ao carregar chaves API: " . $e->getMessage());
+        }
+    }
+
+    /**
+     * Retorna o array de chaves API carregadas.
+     *
+     * @return array
+     */
+    public function getApiKeys(): array
+    {
+        return $this->api_Key;
+    }
+
+    // Opcional: Método para recarregar as chaves se necessário
+    public function reloadApiKeys(): void
+    {
+        $this->api_Key = [];
+        $this->loadApiKeys();
+    }
+}

+ 81 - 0
models/UserModel.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace Models;
+
+class UserModel
+{
+    private \PDO $pdo;
+
+    public function __construct()
+    {
+        // Conecta ao DB usando variável do .env
+        $dbFile = $_ENV['DB_FILE'] ?? 'bridge.db';
+        $dbPath = __DIR__ . '/../' . $dbFile;
+        $this->pdo = new \PDO("sqlite:" . $dbPath);
+        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+    }
+
+    /**
+     * Valida credenciais de login e retorna dados do usuário se válido.
+     *
+     * @param string $username
+     * @param string $password Plain-text password para verificar
+     * @return array|null Dados do usuário (user_id, user_name, etc.) ou null se inválido
+     */
+    public function validateLogin(string $username, string $password): ?array
+    {
+        $stmt = $this->pdo->prepare("SELECT user_id, user_name, password FROM user WHERE user_name = :username AND user_flag = 'a'");
+        $stmt->execute(['username' => $username]);
+        $user = $stmt->fetch(\PDO::FETCH_ASSOC);
+
+        if ($user && password_verify($password, $user['password'])) {
+            unset($user['password']);  // Remove hash por segurança
+            return $user;
+        }
+
+        return null;
+    }
+
+    /**
+     * Cria um novo usuário com senha hasheada e gera chaves API.
+     *
+     * @param string $username
+     * @param string $password Plain-text password
+     * @param string $flag Default 'a' para ativo
+     * @return array|bool Dados do usuário criado (incluindo api_key) ou false em erro
+     */
+    public function createUser(string $username, string $password, string $flag = 'a')
+    {
+        // Verifica se username já existe
+        $stmt = $this->pdo->prepare("SELECT user_id FROM user WHERE user_name = :username");
+        $stmt->execute(['username' => $username]);
+        if ($stmt->fetch()) {
+            return false;  // Já existe
+        }
+
+        $hash = password_hash($password, PASSWORD_DEFAULT);
+
+        // Insere usuário
+        $stmt = $this->pdo->prepare("INSERT INTO user (user_name, user_flag, password) VALUES (:username, :flag, :hash)");
+        if (!$stmt->execute(['username' => $username, 'flag' => $flag, 'hash' => $hash])) {
+            return false;
+        }
+
+        $userId = $this->pdo->lastInsertId();
+
+        // Gera e insere chaves API (random para HMAC)
+        $apiKey = bin2hex(random_bytes(16));  // Ex: 32 chars hex
+        $apiSecret = bin2hex(random_bytes(32));  // Mais longo para secret
+        $stmt = $this->pdo->prepare("INSERT INTO api_key (user_id, api_key_user, api_key_secret) VALUES (:user_id, :api_key, :api_secret)");
+        if (!$stmt->execute(['user_id' => $userId, 'api_key' => $apiKey, 'api_secret' => $apiSecret])) {
+            return false;
+        }
+
+        return [
+            'user_id' => $userId,
+            'user_name' => $username,
+            'api_key_user' => $apiKey,
+            'api_key_secret' => $apiSecret  // Retorne para o usuário (apenas uma vez!)
+        ];
+    }
+}

+ 24 - 1
public/index.php

@@ -2,11 +2,34 @@
 
 require __DIR__ . '/../vendor/autoload.php';
 
+$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
+$file = __DIR__ . $path;
+if (php_sapi_name() === 'cli-server' && is_file($file)) {
+    return false;
+}
+
+if (class_exists(Dotenv\Dotenv::class) && file_exists(__DIR__ . '/../.env')) {
+    Dotenv\Dotenv::createImmutable(
+        dirname(__DIR__),
+        null,
+        true
+    )->safeLoad();
+}
+
+error_reporting(E_ALL);
+
 use FrameworkX\App;
+use Middlewares\HmacAuthMiddleware;
+use Middlewares\JWTAuthMiddleware;
 
 $app = new App();
+$authHmac = new HmacAuthMiddleware();
+$authJwt = new JWTAuthMiddleware();
 
+$app->get('/hmachelloworld', $authHmac,\Controllers\HelloController::class);
+$app->get('/jwthelloworld', $authJwt,\Controllers\HelloController::class);
 
-$app->get('/', \Controllers\HelloController::class);
+$app->post('/login', \Controllers\LoginController::class);
+$app->post('/register', \Controllers\RegisterController::class);
 
 $app->run();