diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9baebae..77935a1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,12 +36,3 @@ jobs: - name: Run Bandit run: poetry run bandit -r ./server - - - name: Run Tests - run: poetry run pytest -s -x --cov=server -vv; poetry run coverage html - - - name: Store coverage files - uses: actions/upload-artifact@v4 - with: - name: coverage-html - path: htmlcov \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8b22eee --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use o IntelliSense para saber mais sobre os atributos possíveis. + // Focalizar para exibir as descrições dos atributos existentes. + // Para obter mais informações, acesse: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Confy Server", + "type": "debugpy", + "request": "launch", + "module": "uvicorn", + "args": [ + "server.main:app", + "--reload" + ], + "jinja": true + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index c416b5d..c57f6d3 100644 --- a/README.md +++ b/README.md @@ -30,48 +30,22 @@ Mesmo que alguma comunicação seja interceptada na rede, ela é ilegível. ## Executando o servidor -### Via Docker (recomendado) +A maneira mais rápida e fácil de executar o servidor é via docker compose. -A maneira mais rápida e fácil de executar o servidor é com um container [Docker](https://www.docker.com/). - -```shell -docker run -d --restart=always -p 8000:8000 --name confy-server henriquesebastiao/confy-server:latest -``` - -O servidor Confy agora está rodando em [http://0.0.0.0:8000](http://0.0.0.0:8000). - -### Localmente - -Caso queira executar o servidor sem Docker para fins de debug ou desenvolvimento siga as etapas abaixo. - -1. Tenha instalado as seguintes dependências: - - - [Git](https://git-scm.com/downloads) - - [Poetry](https://python-poetry.org/docs/#installation) - - [Python 3.13 ou superior](https://www.python.org/downloads/) - -2. Clone este repositório e entre na pasta. +1. Clone este repositório e entre na pasta do projeto. ```shell git clone https://github.com/confy-security/server.git && cd server ``` -3. Instale as dependência do servidor com Poetry. - - ```shell - poetry install - ``` - -4. Ative o ambiente virtual. - -5. Execute o servidor. +2. Execute o docker compose. ```shell - task run + docker compose up -d ``` -Pronto, agora o servidor Confy agora está rodando em [http://0.0.0.0:8000](http://0.0.0.0:8000). +O servidor Confy agora está rodando em [http://0.0.0.0:9000](http://0.0.0.0:9000). -## License +## Licença Este projeto está licenciado sob os termos da licença GNU GPL-3.0. diff --git a/docker-compose.yml b/docker-compose.yml index e2afb78..e0214cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,11 @@ services: build: . volumes: - .:/code + depends_on: + - redis + environment: + - REDIS_HOST=redis + - REDIS_PORT=6379 web: container_name: web-confy-server image: nginx:stable-alpine @@ -17,4 +22,14 @@ services: environment: - NGINX_PORT=80 depends_on: - - server \ No newline at end of file + - server + + redis: + image: redis:alpine + container_name: confy-redis + restart: always + volumes: + - redis_data:/data + +volumes: + redis_data: \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index db67591..e8f0e69 100644 --- a/poetry.lock +++ b/poetry.lock @@ -96,107 +96,6 @@ files = [ ] markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\""} -[[package]] -name = "coverage" -version = "7.10.2" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65"}, - {file = "coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ae385e1d58fbc6a9b1c315e5510ac52281e271478b45f92ca9b5ad42cf39643f"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f0cbe5f7dd19f3a32bac2251b95d51c3b89621ac88a2648096ce40f9a5aa1e7"}, - {file = "coverage-7.10.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd17f427f041f6b116dc90b4049c6f3e1230524407d00daa2d8c7915037b5947"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f10ca4cde7b466405cce0a0e9971a13eb22e57a5ecc8b5f93a81090cc9c7eb9"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b990df23dd51dccce26d18fb09fd85a77ebe46368f387b0ffba7a74e470b31b"}, - {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc3902584d25c7eef57fb38f440aa849a26a3a9f761a029a72b69acfca4e31f8"}, - {file = "coverage-7.10.2-cp310-cp310-win32.whl", hash = "sha256:9dd37e9ac00d5eb72f38ed93e3cdf2280b1dbda3bb9b48c6941805f265ad8d87"}, - {file = "coverage-7.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:99d16f15cb5baf0729354c5bd3080ae53847a4072b9ba1e10957522fb290417f"}, - {file = "coverage-7.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3b210d79925a476dfc8d74c7d53224888421edebf3a611f3adae923e212b27"}, - {file = "coverage-7.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf67d1787cd317c3f8b2e4c6ed1ae93497be7e30605a0d32237ac37a37a8a322"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:069b779d03d458602bc0e27189876e7d8bdf6b24ac0f12900de22dd2154e6ad7"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c2de4cb80b9990e71c62c2d3e9f3ec71b804b1f9ca4784ec7e74127e0f42468"}, - {file = "coverage-7.10.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75bf7ab2374a7eb107602f1e07310cda164016cd60968abf817b7a0b5703e288"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3f37516458ec1550815134937f73d6d15b434059cd10f64678a2068f65c62406"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:de3c6271c482c250d3303fb5c6bdb8ca025fff20a67245e1425df04dc990ece9"}, - {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98a838101321ac3089c9bb1d4bfa967e8afed58021fda72d7880dc1997f20ae1"}, - {file = "coverage-7.10.2-cp311-cp311-win32.whl", hash = "sha256:f2a79145a531a0e42df32d37be5af069b4a914845b6f686590739b786f2f7bce"}, - {file = "coverage-7.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e4f5f1320f8ee0d7cfa421ceb257bef9d39fd614dd3ddcfcacd284d4824ed2c2"}, - {file = "coverage-7.10.2-cp311-cp311-win_arm64.whl", hash = "sha256:d8f2d83118f25328552c728b8e91babf93217db259ca5c2cd4dd4220b8926293"}, - {file = "coverage-7.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:890ad3a26da9ec7bf69255b9371800e2a8da9bc223ae5d86daeb940b42247c83"}, - {file = "coverage-7.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38fd1ccfca7838c031d7a7874d4353e2f1b98eb5d2a80a2fe5732d542ae25e9c"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76c1ffaaf4f6f0f6e8e9ca06f24bb6454a7a5d4ced97a1bc466f0d6baf4bd518"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86da8a3a84b79ead5c7d0e960c34f580bc3b231bb546627773a3f53c532c2f21"}, - {file = "coverage-7.10.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cef9731c8a39801830a604cc53c93c9e57ea8b44953d26589499eded9576e0"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea58b112f2966a8b91eb13f5d3b1f8bb43c180d624cd3283fb33b1cedcc2dd75"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:20f405188d28da9522b7232e51154e1b884fc18d0b3a10f382d54784715bbe01"}, - {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:64586ce42bbe0da4d9f76f97235c545d1abb9b25985a8791857690f96e23dc3b"}, - {file = "coverage-7.10.2-cp312-cp312-win32.whl", hash = "sha256:bc2e69b795d97ee6d126e7e22e78a509438b46be6ff44f4dccbb5230f550d340"}, - {file = "coverage-7.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:adda2268b8cf0d11f160fad3743b4dfe9813cd6ecf02c1d6397eceaa5b45b388"}, - {file = "coverage-7.10.2-cp312-cp312-win_arm64.whl", hash = "sha256:164429decd0d6b39a0582eaa30c67bf482612c0330572343042d0ed9e7f15c20"}, - {file = "coverage-7.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186"}, - {file = "coverage-7.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074"}, - {file = "coverage-7.10.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0"}, - {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a"}, - {file = "coverage-7.10.2-cp313-cp313-win32.whl", hash = "sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b"}, - {file = "coverage-7.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe"}, - {file = "coverage-7.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7"}, - {file = "coverage-7.10.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e"}, - {file = "coverage-7.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0"}, - {file = "coverage-7.10.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca"}, - {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb"}, - {file = "coverage-7.10.2-cp313-cp313t-win32.whl", hash = "sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824"}, - {file = "coverage-7.10.2-cp313-cp313t-win_amd64.whl", hash = "sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3"}, - {file = "coverage-7.10.2-cp313-cp313t-win_arm64.whl", hash = "sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f"}, - {file = "coverage-7.10.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6"}, - {file = "coverage-7.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1"}, - {file = "coverage-7.10.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303"}, - {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd"}, - {file = "coverage-7.10.2-cp314-cp314-win32.whl", hash = "sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8"}, - {file = "coverage-7.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3"}, - {file = "coverage-7.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc"}, - {file = "coverage-7.10.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b"}, - {file = "coverage-7.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de"}, - {file = "coverage-7.10.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4"}, - {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed"}, - {file = "coverage-7.10.2-cp314-cp314t-win32.whl", hash = "sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0"}, - {file = "coverage-7.10.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf"}, - {file = "coverage-7.10.2-cp314-cp314t-win_arm64.whl", hash = "sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc"}, - {file = "coverage-7.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:765b13b164685a2f8b2abef867ad07aebedc0e090c757958a186f64e39d63dbd"}, - {file = "coverage-7.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a219b70100500d0c7fd3ebb824a3302efb6b1a122baa9d4eb3f43df8f0b3d899"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e33e79a219105aa315439ee051bd50b6caa705dc4164a5aba6932c8ac3ce2d98"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc3945b7bad33957a9eca16e9e5eae4b17cb03173ef594fdaad228f4fc7da53b"}, - {file = "coverage-7.10.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bdff88e858ee608a924acfad32a180d2bf6e13e059d6a7174abbae075f30436"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44329cbed24966c0b49acb386352c9722219af1f0c80db7f218af7793d251902"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:be127f292496d0fbe20d8025f73221b36117b3587f890346e80a13b310712982"}, - {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c031da749a05f7a01447dd7f47beedb498edd293e31e1878c0d52db18787df0"}, - {file = "coverage-7.10.2-cp39-cp39-win32.whl", hash = "sha256:22aca3e691c7709c5999ccf48b7a8ff5cf5a8bd6fe9b36efbd4993f5a36b2fcf"}, - {file = "coverage-7.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c7195444b932356055a8e287fa910bf9753a84a1bc33aeb3770e8fca521e032e"}, - {file = "coverage-7.10.2-py3-none-any.whl", hash = "sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f"}, - {file = "coverage-7.10.2.tar.gz", hash = "sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055"}, -] - -[package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] - [[package]] name = "dnspython" version = "2.7.0" @@ -438,18 +337,6 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] -[[package]] -name = "iniconfig" -version = "2.1.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, -] - [[package]] name = "jinja2" version = "3.1.6" @@ -607,18 +494,6 @@ files = [ {file = "mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d"}, ] -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - [[package]] name = "pbr" version = "6.1.1" @@ -634,22 +509,6 @@ files = [ [package.dependencies] setuptools = "*" -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - [[package]] name = "psutil" version = "6.1.1" @@ -817,61 +676,43 @@ files = [ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.4.1" -description = "pytest: simple powerful testing with Python" +name = "pydantic-settings" +version = "2.10.1" +description = "Settings management using Pydantic" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main"] files = [ - {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, - {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, + {file = "pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796"}, + {file = "pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee"}, ] [package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1" -packaging = ">=20" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" +typing-inspection = ">=0.4.0" [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] +aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] [[package]] -name = "pytest-cov" -version = "6.2.1" -description = "Pytest plugin for measuring coverage." +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.9" -groups = ["dev"] +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, - {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] -[package.dependencies] -coverage = {version = ">=7.5", extras = ["toml"]} -pluggy = ">=1.2" -pytest = ">=6.2.5" - [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "python-dotenv" @@ -982,6 +823,23 @@ mando = ">=0.6,<0.8" [package.extras] toml = ["tomli (>=2.0.1)"] +[[package]] +name = "redis" +version = "6.4.0" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f"}, + {file = "redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010"}, +] + +[package.extras] +hiredis = ["hiredis (>=3.2.0)"] +jwt = ["pyjwt (>=2.9.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (>=20.0.1)", "requests (>=2.31.0)"] + [[package]] name = "rich" version = "14.1.0" @@ -1729,4 +1587,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = ">=3.13,<4.0" -content-hash = "13edde0319fd367f3ebf270a39731dc4236ba7b44e0320c4036c0c3a0852a5a3" +content-hash = "fc0e866971e6979d265e8ad64b983ded8d3ee8b33e6a6a4733c9cd365493e320" diff --git a/pyproject.toml b/pyproject.toml index d4741d4..6bf5c80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,9 @@ dependencies = [ "fastapi[standard] (>=0.116.1,<0.117.0)", "websockets (>=15.0.1,<16.0.0)", "uvicorn (>=0.34.3,<0.35.0)", - "psutil (>=5.7.2,<7.0.0)" + "psutil (>=5.7.2,<7.0.0)", + "redis[async] (>=6.4.0,<7.0.0)", + "pydantic-settings (>=2.10.1,<3.0.0)", ] [tool.poetry.group.dev.dependencies] @@ -19,8 +21,6 @@ ruff = "^0.11.13" taskipy = "^1.14.1" radon = "^6.0.1" bandit = "^1.8.6" -pytest = "^8.4.1" -pytest-cov = "^6.2.1" [build-system] requires = ["poetry-core>=2.0.0,<3.0.0"] @@ -44,13 +44,9 @@ quote-style = 'single' "tests/*.py" = ['D103', 'D100', 'PLR2004'] "server/schemas/*.py" = ['D101', 'D100'] "server/routers/status.py" = ['D100'] - -[tool.pytest.ini_options] -pythonpath = "." -addopts = '-p no:warnings' - -[tool.coverage.run] -branch = true +"settings.py" = ['D103', 'D101', 'D100'] +"db.py" = ['D100'] +"hasher.py" = ['D100'] [tool.taskipy.tasks] run = 'uvicorn --reload --host 127.0.0.1 server.main:app' @@ -59,4 +55,3 @@ format = 'ruff format .; ruff check . --fix' radon = 'radon cc ./server -a -na' bandit = 'bandit -r ./server' export = './scripts/rm-requirements.sh && poetry export -f requirements.txt --output requirements.txt --without-hashes --without dev' -test = 'pytest -s -x --cov=server -vv; coverage html' \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 592eef9..fb4ff9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,11 +19,13 @@ markupsafe==3.0.2 ; python_version >= "3.13" and python_version < "4.0" mdurl==0.1.2 ; python_version >= "3.13" and python_version < "4.0" psutil==6.1.1 ; python_version >= "3.13" and python_version < "4.0" pydantic-core==2.33.2 ; python_version >= "3.13" and python_version < "4.0" +pydantic-settings==2.10.1 ; python_version >= "3.13" and python_version < "4.0" pydantic==2.11.7 ; python_version >= "3.13" and python_version < "4.0" pygments==2.19.2 ; python_version >= "3.13" and python_version < "4.0" python-dotenv==1.1.1 ; python_version >= "3.13" and python_version < "4.0" python-multipart==0.0.20 ; python_version >= "3.13" and python_version < "4.0" pyyaml==6.0.2 ; python_version >= "3.13" and python_version < "4.0" +redis==6.4.0 ; python_version >= "3.13" and python_version < "4.0" rich-toolkit==0.14.9 ; python_version >= "3.13" and python_version < "4.0" rich==14.1.0 ; python_version >= "3.13" and python_version < "4.0" rignore==0.6.4 ; python_version >= "3.13" and python_version < "4.0" diff --git a/server/db.py b/server/db.py new file mode 100644 index 0000000..4382bfc --- /dev/null +++ b/server/db.py @@ -0,0 +1,10 @@ +import redis.asyncio as redis + +from server.settings import get_settings + +settings = get_settings() + +# Cria uma conexão global com o Redis +redis_client = redis.Redis( + host=settings.REDIS_HOST, port=settings.REDIS_PORT, decode_responses=True +) diff --git a/server/hasher.py b/server/hasher.py new file mode 100644 index 0000000..efd5610 --- /dev/null +++ b/server/hasher.py @@ -0,0 +1,15 @@ +import hashlib + + +def hash_id(user_id: str) -> str: + """ + Gera o hash de uma determinada string. + + Args: + user_id (str): Texto para obter o hash + + Returns: + str: Hash do texto informado + + """ + return hashlib.sha256(user_id.encode('utf-8')).hexdigest() diff --git a/server/main.py b/server/main.py index ec3d514..c7c9325 100644 --- a/server/main.py +++ b/server/main.py @@ -1,7 +1,10 @@ """Confy Server - Servidor web de encaminhamento de mensagens enviadas por aplicativos clientes compatíveis.""" +from contextlib import asynccontextmanager + from fastapi import FastAPI +from server.db import redis_client from server.routers import status, ws description = """ @@ -9,6 +12,28 @@ via WebSocket utilizando algum aplicativo cliente compatível com o servidor. """ + +@asynccontextmanager +async def clean_online_users(app: FastAPI): + """ + Gerencia o ciclo de vida da aplicação FastAPI garantindo a limpeza dos usuários online. + + Este gerenciador de contexto é executado quando a aplicação inicia e termina. + Ao encerrar o ciclo de vida da aplicação, remove do Redis todos os registros + de usuários que estavam marcados como "online", evitando que conexões antigas + permaneçam ativas indevidamente após um restart do servidor. + + Args: + app (FastAPI): Instância da aplicação FastAPI. + + Yields: + None: Permite a execução normal da aplicação durante o ciclo de vida. + + """ + yield + await redis_client.delete('online_users') + + app = FastAPI( title='Confy Server', description=description, @@ -19,6 +44,7 @@ 'url': 'https://github.com/confy-security/server', 'email': 'confy@henriquesebastiao.com', }, + lifespan=clean_online_users, ) app.include_router(ws.router) diff --git a/server/routers/ws.py b/server/routers/ws.py index 36b11b4..f98e9c7 100644 --- a/server/routers/ws.py +++ b/server/routers/ws.py @@ -17,6 +17,8 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect +from server.db import redis_client +from server.hasher import hash_id from server.logger import logger router = APIRouter(prefix='/ws', tags=['WebSocket']) @@ -42,12 +44,27 @@ async def websocket_endpoint(websocket: WebSocket, sender_id: str, recipient_id: recipient_id (str): O ID do destinatário. """ + sender_id = hash_id(sender_id) + recipient_id = hash_id(recipient_id) + # Aceita a conexão WebSocket do cliente await websocket.accept() + # Verifica no Redis se o usuário já está conectado + is_online = await redis_client.sismember('online_users', sender_id) + if is_online: + await websocket.send_text( + 'system-message: Já há um usuário conectado com o ID que você solicitou.' + ) + await websocket.close() + return # encerra a função sem registrar o usuário novamente + # Registra a conexão do remetente como ativa active_connections[sender_id] = websocket + # Salva no Redis que o usuário está online + await redis_client.sadd('online_users', sender_id) + # Cria um identificador único e imutável para o túnel de comunicação tunnel_id = frozenset({sender_id, recipient_id}) @@ -94,6 +111,9 @@ async def websocket_endpoint(websocket: WebSocket, sender_id: str, recipient_id: if sender_id in active_connections: del active_connections[sender_id] + # Remove do Redis quando desconectar + await redis_client.srem('online_users', sender_id) + # Se o destinatário ainda estiver conectado, avisa e encerra a conexão dele também if recipient_id in active_connections: recipient_connection = active_connections[recipient_id] diff --git a/server/settings.py b/server/settings.py new file mode 100644 index 0000000..459ddcb --- /dev/null +++ b/server/settings.py @@ -0,0 +1,15 @@ +from functools import lru_cache + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', extra='ignore') + + REDIS_HOST: str + REDIS_PORT: int + + +@lru_cache +def get_settings(): + return Settings() diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 4ecf945..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,55 +0,0 @@ -from unittest.mock import MagicMock, patch - -import pytest -from fastapi.testclient import TestClient - -from server.main import app - - -@pytest.fixture -def client(): - with TestClient(app) as client: - yield client - - -@pytest.fixture -def mock_psutil(): - with patch('server.routers.status.psutil') as mock_psutil: - # cpu_count() - mock_psutil.cpu_count.return_value = 4 - - # cpu_freq() total - mock_psutil.cpu_freq.side_effect = [ - MagicMock(_asdict=lambda: {'current': 3200.0, 'min': 800.0, 'max': 4200.0}), # total - [ # per core - MagicMock(_asdict=lambda: {'current': 3100.0, 'min': 800.0, 'max': 4200.0}), - MagicMock(_asdict=lambda: {'current': 3200.0, 'min': 800.0, 'max': 4200.0}), - MagicMock(_asdict=lambda: {'current': 3300.0, 'min': 800.0, 'max': 4200.0}), - MagicMock(_asdict=lambda: {'current': 3400.0, 'min': 800.0, 'max': 4200.0}), - ], - ] - - # cpu_percent() total e per core - mock_psutil.cpu_percent.side_effect = [ - 55.0, # total - [10.0, 20.0, 30.0, 40.0], # per core - ] - - # virtual_memory() - mock_psutil.virtual_memory.return_value = MagicMock( - _asdict=lambda: { - 'total': 16_000_000_000, - 'available': 8_000_000_000, - 'percent': 50.0, - 'used': 8_000_000_000, - 'free': 8_000_000_000, - 'active': 4_000_000_000, - 'inactive': 2_000_000_000, - 'buffers': 500_000_000, - 'cached': 1_000_000_000, - 'shared': 250_000_000, - 'slab': 150_000_000, - } - ) - - yield mock_psutil diff --git a/tests/test_status_endpoint.py b/tests/test_status_endpoint.py deleted file mode 100644 index e91e710..0000000 --- a/tests/test_status_endpoint.py +++ /dev/null @@ -1,23 +0,0 @@ -def test_get_status(mock_psutil, client): - response = client.get('/status') - assert response.status_code == 200 - - data = response.json() - - # Campos principais - assert data['number_of_cores'] == 4 - assert data['cpu_frequency'] == {'current': 3200.0, 'min': 800.0, 'max': 4200.0} - assert data['cpu_percent'] == 55.0 - - # Lista por core - assert len(data['status_per_core']) == 4 - assert data['status_per_core'][0] == { - 'cpu_frequency': {'current': 3100.0, 'min': 800.0, 'max': 4200.0}, - 'cpu_percent': 10.0, - } - assert data['status_per_core'][3]['cpu_frequency']['current'] == 3400.0 - - # Memória - assert data['memory']['total'] == 16_000_000_000 - assert data['memory']['active'] == 4_000_000_000 - assert data['memory']['slab'] == 150_000_000 diff --git a/tests/test_websocket_endpoint.py b/tests/test_websocket_endpoint.py deleted file mode 100644 index 37ed237..0000000 --- a/tests/test_websocket_endpoint.py +++ /dev/null @@ -1,63 +0,0 @@ -from fastapi.testclient import TestClient - -from server.main import app - - -def test_connection_on_server(client): - with client.websocket_connect('/ws/pedro@maria') as websocket: - data = websocket.receive_text() - assert data == ( - 'system-message: O destinatário ainda não está conectado. ' - 'Você será notificado quando ele estiver online.' - ) - - -def test_notification_when_recipient_connects(): - client_pedro = TestClient(app) - with client_pedro.websocket_connect('/ws/pedro@maria') as websocket_pedro: - data = websocket_pedro.receive_text() - assert data == ( - 'system-message: O destinatário ainda não está conectado. ' - 'Você será notificado quando ele estiver online.' - ) - - client_maria = TestClient(app) - with client_maria.websocket_connect('/ws/maria@pedro'): - data_with_notification = websocket_pedro.receive_text() - - assert data_with_notification == ( - 'system-message: O usuário destinatário agora está conectado.' - ) - - -def test_notification_that_recipient_is_still_offline_if_sender_sends_something(client): - with client.websocket_connect('/ws/pedro@maria') as websocket: - data = websocket.receive_text() - assert data == ( - 'system-message: O destinatário ainda não está conectado. ' - 'Você será notificado quando ele estiver online.' - ) - websocket.send_text('Enviando mensagem.') - data = websocket.receive_text() - assert data == 'system-message: O outro usuário ainda não está conectado.' - - -def test_notification_if_recipient_disconnects(): - client_pedro = TestClient(app) - with client_pedro.websocket_connect('/ws/pedro@maria') as websocket_pedro: - data = websocket_pedro.receive_text() - assert data == ( - 'system-message: O destinatário ainda não está conectado. ' - 'Você será notificado quando ele estiver online.' - ) - - client_maria = TestClient(app) - with client_maria.websocket_connect('/ws/maria@pedro') as websocket_maria: - data = websocket_pedro.receive_text() - - assert data == 'system-message: O usuário destinatário agora está conectado.' - - websocket_pedro.close() - data = websocket_maria.receive_text() - - assert data == 'system-message: O outro usuário se desconectou.'