From 2f994804f6ccd3434bc4df37f3eacd41ca77f25e Mon Sep 17 00:00:00 2001 From: wavy42 Date: Fri, 27 Oct 2023 12:39:27 +0200 Subject: [PATCH] Initial commit --- .github/workflows/black_linter.yml | 15 + .github/workflows/super_linter.yml | 11 + .gitignore | 1 + LICENSE | 26 + README.md | 83 + backend/Pipfile | 22 + backend/Pipfile.lock | 1413 +++++++++++++++++ backend/api/__init__.py | 0 backend/api/api_router.py | 32 + backend/api/api_v1/__init__.py | 0 backend/api/api_v1/api.py | 8 + backend/api/api_v1/authentication/__init__.py | 0 backend/api/api_v1/authentication/logging.py | 68 + backend/api/api_v1/authentication/token.py | 86 + .../authentication/user_authorization.py | 2 + backend/api/api_v1/key/__init__.py | 0 backend/api/api_v1/key/api.py | 89 ++ backend/api/api_v1/key/generation.py | 66 + backend/api/api_v1/key/managment.py | 83 + backend/api/api_v1/user/__init__.py | 0 backend/api/api_v1/user/model.py | 91 ++ backend/api/api_v1/user/user.py | 213 +++ backend/app.py | 18 + backend/config.json | 66 + backend/core/__init__.py | 0 backend/core/config.py | 300 ++++ backend/core/converter.py | 68 + backend/core/db/__init__.py | 0 backend/core/db/mongo.py | 232 +++ backend/core/db/redis.py | 55 + backend/core/hashing.py | 192 +++ backend/core/password_validation.py | 60 + backend/core/username_validation.py | 54 + backend/requirements.txt | Bin 0 -> 1734 bytes 34 files changed, 3354 insertions(+) create mode 100644 .github/workflows/black_linter.yml create mode 100644 .github/workflows/super_linter.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 backend/Pipfile create mode 100644 backend/Pipfile.lock create mode 100644 backend/api/__init__.py create mode 100644 backend/api/api_router.py create mode 100644 backend/api/api_v1/__init__.py create mode 100644 backend/api/api_v1/api.py create mode 100644 backend/api/api_v1/authentication/__init__.py create mode 100644 backend/api/api_v1/authentication/logging.py create mode 100644 backend/api/api_v1/authentication/token.py create mode 100644 backend/api/api_v1/authentication/user_authorization.py create mode 100644 backend/api/api_v1/key/__init__.py create mode 100644 backend/api/api_v1/key/api.py create mode 100644 backend/api/api_v1/key/generation.py create mode 100644 backend/api/api_v1/key/managment.py create mode 100644 backend/api/api_v1/user/__init__.py create mode 100644 backend/api/api_v1/user/model.py create mode 100644 backend/api/api_v1/user/user.py create mode 100644 backend/app.py create mode 100644 backend/config.json create mode 100644 backend/core/__init__.py create mode 100644 backend/core/config.py create mode 100644 backend/core/converter.py create mode 100644 backend/core/db/__init__.py create mode 100644 backend/core/db/mongo.py create mode 100644 backend/core/db/redis.py create mode 100644 backend/core/hashing.py create mode 100644 backend/core/password_validation.py create mode 100644 backend/core/username_validation.py create mode 100644 backend/requirements.txt diff --git a/.github/workflows/black_linter.yml b/.github/workflows/black_linter.yml new file mode 100644 index 0000000..b242763 --- /dev/null +++ b/.github/workflows/black_linter.yml @@ -0,0 +1,15 @@ +--- +name: Python Black Lint + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: psf/black@stable + with: + options: "--check --verbose --line-length 79" + src: "./backend" + version: "~= 22.0" diff --git a/.github/workflows/super_linter.yml b/.github/workflows/super_linter.yml new file mode 100644 index 0000000..c37e009 --- /dev/null +++ b/.github/workflows/super_linter.yml @@ -0,0 +1,11 @@ +--- +name: Python Black Lint + +on: [push, pull_request] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: psf/black@stable diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dc7b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.vscode \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5736aff --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +MIT License with Copyleft and Commercial Use Attribution Clause + +Copyright (c) 2023 wavy42 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Copyleft Clause: +Any modifications, adaptations, or derivative works based on this Software must be made publicly available on GitHub, with proper attribution to the original developer wavy42, by adding a link to the original repository. + +Commercial Use Attribution Clause: +For any commercial use of this Software, proper attribution to the original developer wavy42 is required, and a link to the original repository on GitHub must be provided. + +THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1765879 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# Authly + +Authly is a user management application built on [FastAPI](https://fastapi.tiangolo.com/) in Python, designed to provide user authentication and access control for various applications. + +--- + +# 🚧 Work in Progress: Not Ready for Deployment + +This project is currently under development and remains untested. As a result, it may not be secure and is not recommended for deployment. While contributions are welcome, we cannot provide a definite timeline for its completion. + +Thank you for your interest and support in this open-source initiative. + +## 🔍 Current Focus: Login Process and Database Operations + +While the project is a work in progress and not yet ready for deployment, our current focus lies in strengthening the login process and optimizing database operations, including data storage mechanisms. Our team is dedicated to ensuring secure and efficient data management, with plans to address other areas in the future. + +We greatly value any potential contributions and support from the community. We aim to make this project as robust and user-friendly as possible, building a strong foundation for future developments. + +TODO: + +- [ ] rework mongodb crud (beanie maybe) +- [ ] refactor some apiv1 stuff +- [ ] add cookie based login (redis) +- [ ] key add checks for already exist (auto add random num?) + +## 💼 Commercial Use + +For any commercial use of this software, proper attribution to the original developer wavy42 is required, and a link to the original repository on GitHub must be provided. + + + + + + + +## Credits + +🌟 Authly is developed by wavy42 & Arteii. 🚀 + +We appreciate the contributions of all our collaborators and supporters. 🙏 + +## License + +Authly is licensed under the MIT License with additional clauses: + +- **Copyleft Clause** 🔄
+ Any modifications, adaptations, or derivative works based on this software must be made publicly available on GitHub with proper attribution to the original developer wavy42 by adding a link to the original repository. + +- **Commercial Use Attribution Clause** 💼
+ For any commercial use of this software, proper attribution to the original developer wavy42 is required, and a link to the original repository on GitHub must be provided. diff --git a/backend/Pipfile b/backend/Pipfile new file mode 100644 index 0000000..21a6eab --- /dev/null +++ b/backend/Pipfile @@ -0,0 +1,22 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +pydantic-settings = "*" +argon2-cffi = "*" +bcrypt = "*" +motor = "*" +pydantic = "*" +fastapi = {extras = ["all"], version = "*"} +aioredis = "*" +exrex = "*" + +[dev-packages] +uvicorn = {extras = ["standard"], version = "*"} +pytest = "*" +mypy = "*" + +[requires] +python_version = "3.11" diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock new file mode 100644 index 0000000..6f1b78b --- /dev/null +++ b/backend/Pipfile.lock @@ -0,0 +1,1413 @@ +{ + "_meta": { + "hash": { + "sha256": "24449a45d73e5276c1ce0abf48af5767bbb2fb864cf1395252c4b7b7a0a6988b" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.11" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aioredis": { + "hashes": [ + "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6", + "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==2.0.1" + }, + "annotated-types": { + "hashes": [ + "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", + "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.0" + }, + "anyio": { + "hashes": [ + "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780", + "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5" + ], + "markers": "python_version >= '3.7'", + "version": "==3.7.1" + }, + "argon2-cffi": { + "hashes": [ + "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", + "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==23.1.0" + }, + "argon2-cffi-bindings": { + "hashes": [ + "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670", + "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f", + "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583", + "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194", + "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", + "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a", + "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", + "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5", + "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", + "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7", + "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", + "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", + "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", + "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", + "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", + "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", + "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d", + "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", + "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb", + "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", + "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351" + ], + "markers": "python_version >= '3.6'", + "version": "==21.2.0" + }, + "async-timeout": { + "hashes": [ + "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", + "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028" + ], + "markers": "python_version >= '3.7'", + "version": "==4.0.3" + }, + "bcrypt": { + "hashes": [ + "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535", + "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0", + "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410", + "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd", + "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665", + "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab", + "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71", + "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215", + "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b", + "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda", + "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9", + "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a", + "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344", + "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f", + "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d", + "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c", + "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c", + "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2", + "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d", + "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e", + "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==4.0.1" + }, + "certifi": { + "hashes": [ + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.7.22" + }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "python_version >= '3.8'", + "version": "==1.16.0" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.4.6" + }, + "dnspython": { + "hashes": [ + "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8", + "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984" + ], + "markers": "python_version >= '3.8' and python_version < '4.0'", + "version": "==2.4.2" + }, + "email-validator": { + "hashes": [ + "sha256:a4b0bd1cf55f073b924258d19321b1f3aa74b4b5a71a42c305575dba920e1a44", + "sha256:c973053efbeddfef924dc0bd93f6e77a1ea7ee0fce935aea7103c7a3d6d2d637" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.0.post1" + }, + "exrex": { + "hashes": [ + "sha256:59912f0234567a5966b10d963c37ca9fe07f1640fd158e77c0dc7c3aee780489", + "sha256:7bbc4987ebbb63cda7aad1a63b4e29032ba24826de4f295354bce1743e3aeb52" + ], + "index": "pypi", + "version": "==0.11.0" + }, + "fastapi": { + "extras": [ + "all" + ], + "hashes": [ + "sha256:456482c1178fb7beb2814b88e1885bc49f9a81f079665016feffe3e1c6a7663e", + "sha256:9c44de45693ae037b0c6914727a29c49a40668432b67c859a87851fc6a7b74c6" + ], + "markers": "python_version >= '3.8'", + "version": "==0.104.0" + }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httpcore": { + "hashes": [ + "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9", + "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced" + ], + "markers": "python_version >= '3.8'", + "version": "==0.18.0" + }, + "httptools": { + "hashes": [ + "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563", + "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142", + "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d", + "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b", + "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4", + "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb", + "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658", + "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084", + "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2", + "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97", + "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837", + "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3", + "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58", + "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da", + "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d", + "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90", + "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0", + "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1", + "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2", + "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e", + "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0", + "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf", + "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc", + "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3", + "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503", + "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a", + "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3", + "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949", + "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84", + "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb", + "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", + "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f", + "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e", + "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81", + "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185", + "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3" + ], + "version": "==0.6.1" + }, + "httpx": { + "hashes": [ + "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100", + "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875" + ], + "version": "==0.25.0" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "itsdangerous": { + "hashes": [ + "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", + "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" + ], + "version": "==2.1.2" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "version": "==3.1.2" + }, + "markupsafe": { + "hashes": [ + "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", + "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", + "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", + "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", + "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", + "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", + "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", + "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", + "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", + "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", + "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", + "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", + "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", + "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", + "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", + "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", + "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", + "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", + "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", + "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", + "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", + "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", + "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", + "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", + "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", + "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", + "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", + "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", + "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", + "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", + "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", + "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", + "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", + "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", + "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", + "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", + "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", + "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", + "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", + "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", + "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", + "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", + "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", + "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", + "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", + "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", + "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", + "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", + "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.3" + }, + "motor": { + "hashes": [ + "sha256:a0dee83ad0d47b353932ac37467ba397b1e649ce7e3eea7f5a90554883d7cdbe", + "sha256:c5eb400e27d722a3db03a9826656b6d13acf9b6c70c2fb4604f474eac9da5be4" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==3.3.1" + }, + "orjson": { + "hashes": [ + "sha256:02e693843c2959befdd82d1ebae8b05ed12d1cb821605d5f9fe9f98ca5c9fd2b", + "sha256:06f0c024a75e8ba5d9101facb4fb5a028cdabe3cdfe081534f2a9de0d5062af2", + "sha256:0a1a4d9e64597e550428ba091e51a4bcddc7a335c8f9297effbfa67078972b5c", + "sha256:0d2cd6ef4726ef1b8c63e30d8287225a383dbd1de3424d287b37c1906d8d2855", + "sha256:0f89dc338a12f4357f5bf1b098d3dea6072fb0b643fd35fec556f4941b31ae27", + "sha256:12b83e0d8ba4ca88b894c3e00efc59fe6d53d9ffb5dbbb79d437a466fc1a513d", + "sha256:1ef06431f021453a47a9abb7f7853f04f031d31fbdfe1cc83e3c6aadde502cce", + "sha256:1f352117eccac268a59fedac884b0518347f5e2b55b9f650c2463dd1e732eb61", + "sha256:24301f2d99d670ded4fb5e2f87643bc7428a54ba49176e38deb2887e42fe82fb", + "sha256:31d676bc236f6e919d100fb85d0a99812cff1ebffaa58106eaaec9399693e227", + "sha256:335406231f9247f985df045f0c0c8f6b6d5d6b3ff17b41a57c1e8ef1a31b4d04", + "sha256:397a185e5dd7f8ebe88a063fe13e34d61d394ebb8c70a443cee7661b9c89bda7", + "sha256:4a308aeac326c2bafbca9abbae1e1fcf682b06e78a54dad0347b760525838d85", + "sha256:50232572dd300c49f134838c8e7e0917f29a91f97dbd608d23f2895248464b7f", + "sha256:512e5a41af008e76451f5a344941d61f48dddcf7d7ddd3073deb555de64596a6", + "sha256:5424ecbafe57b2de30d3b5736c5d5835064d522185516a372eea069b92786ba6", + "sha256:543b36df56db195739c70d645ecd43e49b44d5ead5f8f645d2782af118249b37", + "sha256:678ffb5c0a6b1518b149cc328c610615d70d9297e351e12c01d0beed5d65360f", + "sha256:6fcf06c69ccc78e32d9f28aa382ab2ab08bf54b696dbe00ee566808fdf05da7d", + "sha256:75b805549cbbcb963e9c9068f1a05abd0ea4c34edc81f8d8ef2edb7e139e5b0f", + "sha256:8038ba245d0c0a6337cfb6747ea0c51fe18b0cf1a4bc943d530fd66799fae33d", + "sha256:879d2d1f6085c9c0831cec6716c63aaa89e41d8e036cabb19a315498c173fcc6", + "sha256:8cba20c9815c2a003b8ca4429b0ad4aa87cb6649af41365821249f0fd397148e", + "sha256:8e7877256b5092f1e4e48fc0f1004728dc6901e7a4ffaa4acb0a9578610aa4ce", + "sha256:906cac73b7818c20cf0f6a7dde5a6f009c52aecc318416c7af5ea37f15ca7e66", + "sha256:920814e02e3dd7af12f0262bbc18b9fe353f75a0d0c237f6a67d270da1a1bb44", + "sha256:957a45fb201c61b78bcf655a16afbe8a36c2c27f18a998bd6b5d8a35e358d4ad", + "sha256:9a4402e7df1b5c9a4c71c7892e1c8f43f642371d13c73242bda5964be6231f95", + "sha256:9d9b5440a5d215d9e1cfd4aee35fd4101a8b8ceb8329f549c16e3894ed9f18b5", + "sha256:a3bf6ca6bce22eb89dd0650ef49c77341440def966abcb7a2d01de8453df083a", + "sha256:a71b0cc21f2c324747bc77c35161e0438e3b5e72db6d3b515310457aba743f7f", + "sha256:ab7bae2b8bf17620ed381e4101aeeb64b3ba2a45fc74c7617c633a923cb0f169", + "sha256:ae72621f216d1d990468291b1ec153e1b46e0ed188a86d54e0941f3dabd09ee8", + "sha256:b20becf50d4aec7114dc902b58d85c6431b3a59b04caa977e6ce67b6fee0e159", + "sha256:b28c1a65cd13fff5958ab8b350f0921121691464a7a1752936b06ed25c0c7b6e", + "sha256:b97a67c47840467ccf116136450c50b6ed4e16a8919c81a4b4faef71e0a2b3f4", + "sha256:bd55ea5cce3addc03f8fb0705be0cfed63b048acc4f20914ce5e1375b15a293b", + "sha256:c4eb31a8e8a5e1d9af5aa9e247c2a52ad5cf7e968aaa9aaefdff98cfcc7f2e37", + "sha256:c63eca397127ebf46b59c9c1fb77b30dd7a8fc808ac385e7a58a7e64bae6e106", + "sha256:c959550e0705dc9f59de8fca1a316da0d9b115991806b217c82931ac81d75f74", + "sha256:cffb77cf0cd3cbf20eb603f932e0dde51b45134bdd2d439c9f57924581bb395b", + "sha256:d1c01cf4b8e00c7e98a0a7cf606a30a26c32adf2560be2d7d5d6766d6f474b31", + "sha256:d3f56e41bc79d30fdf077073072f2377d2ebf0b946b01f2009ab58b08907bc28", + "sha256:e159b97f5676dcdac0d0f75ec856ef5851707f61d262851eb41a30e8fadad7c9", + "sha256:e98ca450cb4fb176dd572ce28c6623de6923752c70556be4ef79764505320acb", + "sha256:eb50d869b3c97c7c5187eda3759e8eb15deb1271d694bc5d6ba7040db9e29036", + "sha256:ece2d8ed4c34903e7f1b64fb1e448a00e919a4cdb104fc713ad34b055b665fca", + "sha256:f28090060a31f4d11221f9ba48b2273b0d04b702f4dcaa197c38c64ce639cc51", + "sha256:f692e7aabad92fa0fff5b13a846fb586b02109475652207ec96733a085019d80", + "sha256:f708ca623287186e5876256cb30599308bce9b2757f90d917b7186de54ce6547" + ], + "version": "==3.9.9" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pydantic": { + "hashes": [ + "sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7", + "sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.4.2" + }, + "pydantic-core": { + "hashes": [ + "sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e", + "sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33", + "sha256:05560ab976012bf40f25d5225a58bfa649bb897b87192a36c6fef1ab132540d7", + "sha256:0675ba5d22de54d07bccde38997e780044dcfa9a71aac9fd7d4d7a1d2e3e65f7", + "sha256:073d4a470b195d2b2245d0343569aac7e979d3a0dcce6c7d2af6d8a920ad0bea", + "sha256:07ec6d7d929ae9c68f716195ce15e745b3e8fa122fc67698ac6498d802ed0fa4", + "sha256:0880e239827b4b5b3e2ce05e6b766a7414e5f5aedc4523be6b68cfbc7f61c5d0", + "sha256:0c27f38dc4fbf07b358b2bc90edf35e82d1703e22ff2efa4af4ad5de1b3833e7", + "sha256:0d8a8adef23d86d8eceed3e32e9cca8879c7481c183f84ed1a8edc7df073af94", + "sha256:0e2a35baa428181cb2270a15864ec6286822d3576f2ed0f4cd7f0c1708472aff", + "sha256:0f8682dbdd2f67f8e1edddcbffcc29f60a6182b4901c367fc8c1c40d30bb0a82", + "sha256:0fa467fd300a6f046bdb248d40cd015b21b7576c168a6bb20aa22e595c8ffcdd", + "sha256:128552af70a64660f21cb0eb4876cbdadf1a1f9d5de820fed6421fa8de07c893", + "sha256:1396e81b83516b9d5c9e26a924fa69164156c148c717131f54f586485ac3c15e", + "sha256:149b8a07712f45b332faee1a2258d8ef1fb4a36f88c0c17cb687f205c5dc6e7d", + "sha256:14ac492c686defc8e6133e3a2d9eaf5261b3df26b8ae97450c1647286750b901", + "sha256:14cfbb00959259e15d684505263d5a21732b31248a5dd4941f73a3be233865b9", + "sha256:14e09ff0b8fe6e46b93d36a878f6e4a3a98ba5303c76bb8e716f4878a3bee92c", + "sha256:154ea7c52e32dce13065dbb20a4a6f0cc012b4f667ac90d648d36b12007fa9f7", + "sha256:15d6bca84ffc966cc9976b09a18cf9543ed4d4ecbd97e7086f9ce9327ea48891", + "sha256:1d40f55222b233e98e3921df7811c27567f0e1a4411b93d4c5c0f4ce131bc42f", + "sha256:25bd966103890ccfa028841a8f30cebcf5875eeac8c4bde4fe221364c92f0c9a", + "sha256:2cf5bb4dd67f20f3bbc1209ef572a259027c49e5ff694fa56bed62959b41e1f9", + "sha256:2e0e2959ef5d5b8dc9ef21e1a305a21a36e254e6a34432d00c72a92fdc5ecda5", + "sha256:320f14bd4542a04ab23747ff2c8a778bde727158b606e2661349557f0770711e", + "sha256:3625578b6010c65964d177626fde80cf60d7f2e297d56b925cb5cdeda6e9925a", + "sha256:39215d809470f4c8d1881758575b2abfb80174a9e8daf8f33b1d4379357e417c", + "sha256:3f0ac9fb8608dbc6eaf17956bf623c9119b4db7dbb511650910a82e261e6600f", + "sha256:417243bf599ba1f1fef2bb8c543ceb918676954734e2dcb82bf162ae9d7bd514", + "sha256:420a692b547736a8d8703c39ea935ab5d8f0d2573f8f123b0a294e49a73f214b", + "sha256:443fed67d33aa85357464f297e3d26e570267d1af6fef1c21ca50921d2976302", + "sha256:48525933fea744a3e7464c19bfede85df4aba79ce90c60b94d8b6e1eddd67096", + "sha256:485a91abe3a07c3a8d1e082ba29254eea3e2bb13cbbd4351ea4e5a21912cc9b0", + "sha256:4a5be350f922430997f240d25f8219f93b0c81e15f7b30b868b2fddfc2d05f27", + "sha256:4d966c47f9dd73c2d32a809d2be529112d509321c5310ebf54076812e6ecd884", + "sha256:524ff0ca3baea164d6d93a32c58ac79eca9f6cf713586fdc0adb66a8cdeab96a", + "sha256:53df009d1e1ba40f696f8995683e067e3967101d4bb4ea6f667931b7d4a01357", + "sha256:5994985da903d0b8a08e4935c46ed8daf5be1cf217489e673910951dc533d430", + "sha256:5cabb9710f09d5d2e9e2748c3e3e20d991a4c5f96ed8f1132518f54ab2967221", + "sha256:5fdb39f67c779b183b0c853cd6b45f7db84b84e0571b3ef1c89cdb1dfc367325", + "sha256:600d04a7b342363058b9190d4e929a8e2e715c5682a70cc37d5ded1e0dd370b4", + "sha256:631cb7415225954fdcc2a024119101946793e5923f6c4d73a5914d27eb3d3a05", + "sha256:63974d168b6233b4ed6a0046296803cb13c56637a7b8106564ab575926572a55", + "sha256:64322bfa13e44c6c30c518729ef08fda6026b96d5c0be724b3c4ae4da939f875", + "sha256:655f8f4c8d6a5963c9a0687793da37b9b681d9ad06f29438a3b2326d4e6b7970", + "sha256:6835451b57c1b467b95ffb03a38bb75b52fb4dc2762bb1d9dbed8de31ea7d0fc", + "sha256:6db2eb9654a85ada248afa5a6db5ff1cf0f7b16043a6b070adc4a5be68c716d6", + "sha256:7c4d1894fe112b0864c1fa75dffa045720a194b227bed12f4be7f6045b25209f", + "sha256:7eb037106f5c6b3b0b864ad226b0b7ab58157124161d48e4b30c4a43fef8bc4b", + "sha256:8282bab177a9a3081fd3d0a0175a07a1e2bfb7fcbbd949519ea0980f8a07144d", + "sha256:82f55187a5bebae7d81d35b1e9aaea5e169d44819789837cdd4720d768c55d15", + "sha256:8572cadbf4cfa95fb4187775b5ade2eaa93511f07947b38f4cd67cf10783b118", + "sha256:8cdbbd92154db2fec4ec973d45c565e767ddc20aa6dbaf50142676484cbff8ee", + "sha256:8f6e6aed5818c264412ac0598b581a002a9f050cb2637a84979859e70197aa9e", + "sha256:92f675fefa977625105708492850bcbc1182bfc3e997f8eecb866d1927c98ae6", + "sha256:962ed72424bf1f72334e2f1e61b68f16c0e596f024ca7ac5daf229f7c26e4208", + "sha256:9badf8d45171d92387410b04639d73811b785b5161ecadabf056ea14d62d4ede", + "sha256:9c120c9ce3b163b985a3b966bb701114beb1da4b0468b9b236fc754783d85aa3", + "sha256:9f6f3e2598604956480f6c8aa24a3384dbf6509fe995d97f6ca6103bb8c2534e", + "sha256:a1254357f7e4c82e77c348dabf2d55f1d14d19d91ff025004775e70a6ef40ada", + "sha256:a1392e0638af203cee360495fd2cfdd6054711f2db5175b6e9c3c461b76f5175", + "sha256:a1c311fd06ab3b10805abb72109f01a134019739bd3286b8ae1bc2fc4e50c07a", + "sha256:a5cb87bdc2e5f620693148b5f8f842d293cae46c5f15a1b1bf7ceeed324a740c", + "sha256:a7a7902bf75779bc12ccfc508bfb7a4c47063f748ea3de87135d433a4cca7a2f", + "sha256:aad7bd686363d1ce4ee930ad39f14e1673248373f4a9d74d2b9554f06199fb58", + "sha256:aafdb89fdeb5fe165043896817eccd6434aee124d5ee9b354f92cd574ba5e78f", + "sha256:ae8a8843b11dc0b03b57b52793e391f0122e740de3df1474814c700d2622950a", + "sha256:b00bc4619f60c853556b35f83731bd817f989cba3e97dc792bb8c97941b8053a", + "sha256:b1f22a9ab44de5f082216270552aa54259db20189e68fc12484873d926426921", + "sha256:b3c01c2fb081fced3bbb3da78510693dc7121bb893a1f0f5f4b48013201f362e", + "sha256:b3dcd587b69bbf54fc04ca157c2323b8911033e827fffaecf0cafa5a892a0904", + "sha256:b4a6db486ac8e99ae696e09efc8b2b9fea67b63c8f88ba7a1a16c24a057a0776", + "sha256:bec7dd208a4182e99c5b6c501ce0b1f49de2802448d4056091f8e630b28e9a52", + "sha256:c0877239307b7e69d025b73774e88e86ce82f6ba6adf98f41069d5b0b78bd1bf", + "sha256:caa48fc31fc7243e50188197b5f0c4228956f97b954f76da157aae7f67269ae8", + "sha256:cfe1090245c078720d250d19cb05d67e21a9cd7c257698ef139bc41cf6c27b4f", + "sha256:d43002441932f9a9ea5d6f9efaa2e21458221a3a4b417a14027a1d530201ef1b", + "sha256:d64728ee14e667ba27c66314b7d880b8eeb050e58ffc5fec3b7a109f8cddbd63", + "sha256:d6495008733c7521a89422d7a68efa0a0122c99a5861f06020ef5b1f51f9ba7c", + "sha256:d8f1ebca515a03e5654f88411420fea6380fc841d1bea08effb28184e3d4899f", + "sha256:d99277877daf2efe074eae6338453a4ed54a2d93fb4678ddfe1209a0c93a2468", + "sha256:da01bec0a26befab4898ed83b362993c844b9a607a86add78604186297eb047e", + "sha256:db9a28c063c7c00844ae42a80203eb6d2d6bbb97070cfa00194dff40e6f545ab", + "sha256:dda81e5ec82485155a19d9624cfcca9be88a405e2857354e5b089c2a982144b2", + "sha256:e357571bb0efd65fd55f18db0a2fb0ed89d0bb1d41d906b138f088933ae618bb", + "sha256:e544246b859f17373bed915182ab841b80849ed9cf23f1f07b73b7c58baee5fb", + "sha256:e562617a45b5a9da5be4abe72b971d4f00bf8555eb29bb91ec2ef2be348cd132", + "sha256:e570ffeb2170e116a5b17e83f19911020ac79d19c96f320cbfa1fa96b470185b", + "sha256:e6f31a17acede6a8cd1ae2d123ce04d8cca74056c9d456075f4f6f85de055607", + "sha256:e9121b4009339b0f751955baf4543a0bfd6bc3f8188f8056b1a25a2d45099934", + "sha256:ebedb45b9feb7258fac0a268a3f6bec0a2ea4d9558f3d6f813f02ff3a6dc6698", + "sha256:ecaac27da855b8d73f92123e5f03612b04c5632fd0a476e469dfc47cd37d6b2e", + "sha256:ecdbde46235f3d560b18be0cb706c8e8ad1b965e5c13bbba7450c86064e96561", + "sha256:ed550ed05540c03f0e69e6d74ad58d026de61b9eaebebbaaf8873e585cbb18de", + "sha256:eeb3d3d6b399ffe55f9a04e09e635554012f1980696d6b0aca3e6cf42a17a03b", + "sha256:ef337945bbd76cce390d1b2496ccf9f90b1c1242a3a7bc242ca4a9fc5993427a", + "sha256:f1365e032a477c1430cfe0cf2856679529a2331426f8081172c4a74186f1d595", + "sha256:f23b55eb5464468f9e0e9a9935ce3ed2a870608d5f534025cd5536bca25b1402", + "sha256:f2e9072d71c1f6cfc79a36d4484c82823c560e6f5599c43c1ca6b5cdbd54f881", + "sha256:f323306d0556351735b54acbf82904fe30a27b6a7147153cbe6e19aaaa2aa429", + "sha256:f36a3489d9e28fe4b67be9992a23029c3cec0babc3bd9afb39f49844a8c721c5", + "sha256:f64f82cc3443149292b32387086d02a6c7fb39b8781563e0ca7b8d7d9cf72bd7", + "sha256:f6defd966ca3b187ec6c366604e9296f585021d922e666b99c47e78738b5666c", + "sha256:f7c2b8eb9fc872e68b46eeaf835e86bccc3a58ba57d0eedc109cbb14177be531", + "sha256:fa7db7558607afeccb33c0e4bf1c9a9a835e26599e76af6fe2fcea45904083a6", + "sha256:fcb83175cc4936a5425dde3356f079ae03c0802bbdf8ff82c035f8a54b333521" + ], + "markers": "python_version >= '3.7'", + "version": "==2.10.1" + }, + "pydantic-extra-types": { + "hashes": [ + "sha256:1b8aa83a2986b0bc6a7179834fdb423c5e0bcef6b2b4cd9261bf753ad7dcc483", + "sha256:d07b869e733d33712b07d6b8cd7b0223077c23ae5a1e23bd0699a00401259ec7" + ], + "version": "==2.1.0" + }, + "pydantic-settings": { + "hashes": [ + "sha256:962dc3672495aad6ae96a4390fac7e593591e144625e5112d359f8f67fb75945", + "sha256:ddd907b066622bd67603b75e2ff791875540dc485b7307c4fffc015719da8625" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.0.3" + }, + "pymongo": { + "hashes": [ + "sha256:076afa0a4a96ca9f77fec0e4a0d241200b3b3a1766f8d7be9a905ecf59a7416b", + "sha256:08819da7864f9b8d4a95729b2bea5fffed08b63d3b9c15b4fea47de655766cf5", + "sha256:0a1f26bc1f5ce774d99725773901820dfdfd24e875028da4a0252a5b48dcab5c", + "sha256:0f4b125b46fe377984fbaecf2af40ed48b05a4b7676a2ff98999f2016d66b3ec", + "sha256:1240edc1a448d4ada4bf1a0e55550b6292420915292408e59159fd8bbdaf8f63", + "sha256:152259f0f1a60f560323aacf463a3642a65a25557683f49cfa08c8f1ecb2395a", + "sha256:168172ef7856e20ec024fe2a746bfa895c88b32720138e6438fd765ebd2b62dd", + "sha256:1b1d7d9aabd8629a31d63cd106d56cca0e6420f38e50563278b520f385c0d86e", + "sha256:1d40ad09d9f5e719bc6f729cc6b17f31c0b055029719406bd31dde2f72fca7e7", + "sha256:21b953da14549ff62ea4ae20889c71564328958cbdf880c64a92a48dda4c9c53", + "sha256:23cc6d7eb009c688d70da186b8f362d61d5dd1a2c14a45b890bd1e91e9c451f2", + "sha256:2988ef5e6b360b3ff1c6d55c53515499de5f48df31afd9f785d788cdacfbe2d3", + "sha256:2a0aade2b11dc0c326ccd429ee4134d2d47459ff68d449c6d7e01e74651bd255", + "sha256:2b0176f9233a5927084c79ff80b51bd70bfd57e4f3d564f50f80238e797f0c8a", + "sha256:2d4fa1b01fa7e5b7bb8d312e3542e211b320eb7a4e3d8dc884327039d93cb9e0", + "sha256:3236cf89d69679eaeb9119c840f5c7eb388a2110b57af6bb6baf01a1da387c18", + "sha256:33faa786cc907de63f745f587e9879429b46033d7d97a7b84b37f4f8f47b9b32", + "sha256:37df8f6006286a5896d1cbc3efb8471ced42e3568d38e6cb00857277047b0d63", + "sha256:3a7166d57dc74d679caa7743b8ecf7dc3a1235a9fd178654dddb2b2a627ae229", + "sha256:3d79ae3bb1ff041c0db56f138c88ce1dfb0209f3546d8d6e7c3f74944ecd2439", + "sha256:3e33064f1984db412b34d51496f4ea785a9cff621c67de58e09fb28da6468a52", + "sha256:3fa3648e4f1e63ddfe53563ee111079ea3ab35c3b09cd25bc22dadc8269a495f", + "sha256:40d5f6e853ece9bfc01e9129b228df446f49316a4252bb1fbfae5c3c9dedebad", + "sha256:41771b22dd2822540f79a877c391283d4e6368125999a5ec8beee1ce566f3f82", + "sha256:435228d3c16a375274ac8ab9c4f9aef40c5e57ddb8296e20ecec9e2461da1017", + "sha256:44ee985194c426ddf781fa784f31ffa29cb59657b2dba09250a4245431847d73", + "sha256:465fd5b040206f8bce7016b01d7e7f79d2fcd7c2b8e41791be9632a9df1b4999", + "sha256:496c9cbcb4951183d4503a9d7d2c1e3694aab1304262f831d5e1917e60386036", + "sha256:49dce6957598975d8b8d506329d2a3a6c4aee911fa4bbcf5e52ffc6897122950", + "sha256:4c42748ccc451dfcd9cef6c5447a7ab727351fd9747ad431db5ebb18a9b78a4d", + "sha256:505f8519c4c782a61d94a17b0da50be639ec462128fbd10ab0a34889218fdee3", + "sha256:53f2dda54d76a98b43a410498bd12f6034b2a14b6844ca08513733b2b20b7ad8", + "sha256:56320c401f544d762fc35766936178fbceb1d9261cd7b24fbfbc8fb6f67aa8a5", + "sha256:58a63a26a1e3dc481dd3a18d6d9f8bd1d576cd1ffe0d479ba7dd38b0aeb20066", + "sha256:5caee7bd08c3d36ec54617832b44985bd70c4cbd77c5b313de6f7fce0bb34f93", + "sha256:631492573a1bef2f74f9ac0f9d84e0ce422c251644cd81207530af4aa2ee1980", + "sha256:63d8019eee119df308a075b8a7bdb06d4720bf791e2b73d5ab0e7473c115d79c", + "sha256:6422b6763b016f2ef2beedded0e546d6aa6ba87910f9244d86e0ac7690f75c96", + "sha256:681f252e43b3ef054ca9161635f81b730f4d8cadd28b3f2b2004f5a72f853982", + "sha256:6d64878d1659d2a5bdfd0f0a4d79bafe68653c573681495e424ab40d7b6d6d41", + "sha256:74c0da07c04d0781490b2915e7514b1adb265ef22af039a947988c331ee7455b", + "sha256:7591a3beea6a9a4fa3080d27d193b41f631130e3ffa76b88c9ccea123f26dc59", + "sha256:76a262c41c1a7cbb84a3b11976578a7eb8e788c4b7bfbd15c005fb6ca88e6e50", + "sha256:77cfff95c1fafd09e940b3fdcb7b65f11442662fad611d0e69b4dd5d17a81c60", + "sha256:8027c9063579083746147cf401a7072a9fb6829678076cd3deff28bb0e0f50c8", + "sha256:80a167081c75cf66b32f30e2f1eaee9365af935a86dbd76788169911bed9b5d5", + "sha256:840eaf30ccac122df260b6005f9dfae4ac287c498ee91e3e90c56781614ca238", + "sha256:8543253adfaa0b802bfa88386db1009c6ebb7d5684d093ee4edc725007553d21", + "sha256:89b3f2da57a27913d15d2a07d58482f33d0a5b28abd20b8e643ab4d625e36257", + "sha256:8e559116e4128630ad3b7e788e2e5da81cbc2344dee246af44471fa650486a70", + "sha256:9aff6279e405dc953eeb540ab061e72c03cf38119613fce183a8e94f31be608f", + "sha256:9c04b9560872fa9a91251030c488e0a73bce9321a70f991f830c72b3f8115d0d", + "sha256:9d2346b00af524757576cc2406414562cced1d4349c92166a0ee377a2a483a80", + "sha256:a253b765b7cbc4209f1d8ee16c7287c4268d3243070bf72d7eec5aa9dfe2a2c2", + "sha256:a8127437ebc196a6f5e8fddd746bd0903a400dc6b5ae35df672dd1ccc7170a2a", + "sha256:b25f7bea162b3dbec6d33c522097ef81df7c19a9300722fa6853f5b495aecb77", + "sha256:b33c17d9e694b66d7e96977e9e56df19d662031483efe121a24772a44ccbbc7e", + "sha256:b4fe46b58010115514b842c669a0ed9b6a342017b15905653a5b1724ab80917f", + "sha256:b520aafc6cb148bac09ccf532f52cbd31d83acf4d3e5070d84efe3c019a1adbf", + "sha256:b5bbb87fa0511bd313d9a2c90294c88db837667c2bda2ea3fa7a35b59fd93b1f", + "sha256:b6d2a56fc2354bb6378f3634402eec788a8f3facf0b3e7d468db5f2b5a78d763", + "sha256:bbd705d5f3c3d1ff2d169e418bb789ff07ab3c70d567cc6ba6b72b04b9143481", + "sha256:bc5d8c3647b8ae28e4312f1492b8f29deebd31479cd3abaa989090fb1d66db83", + "sha256:c3c3525ea8658ee1192cdddf5faf99b07ebe1eeaa61bf32821126df6d1b8072b", + "sha256:c9a9a39b7cac81dca79fca8c2a6479ef4c7b1aab95fad7544cc0e8fd943595a2", + "sha256:cd4c8d6aa91d3e35016847cbe8d73106e3d1c9a4e6578d38e2c346bfe8edb3ca", + "sha256:cf62da7a4cdec9a4b2981fcbd5e08053edffccf20e845c0b6ec1e77eb7fab61d", + "sha256:d67225f05f6ea27c8dc57f3fa6397c96d09c42af69d46629f71e82e66d33fa4f", + "sha256:dfcd2b9f510411de615ccedd47462dae80e82fdc09fe9ab0f0f32f11cf57eeb5", + "sha256:e1f61355c821e870fb4c17cdb318669cfbcf245a291ce5053b41140870c3e5cc", + "sha256:e249190b018d63c901678053b4a43e797ca78b93fb6d17633e3567d4b3ec6107", + "sha256:e2654d1278384cff75952682d17c718ecc1ad1d6227bb0068fd826ba47d426a5", + "sha256:e57d859b972c75ee44ea2ef4758f12821243e99de814030f69a3decb2aa86807", + "sha256:e5a27f348909235a106a3903fc8e70f573d89b41d723a500869c6569a391cff7", + "sha256:ead4f19d0257a756b21ac2e0e85a37a7245ddec36d3b6008d5bfe416525967dc", + "sha256:f076b779aa3dc179aa3ed861be063a313ed4e48ae9f6a8370a9b1295d4502111", + "sha256:f1bb3a62395ffe835dbef3a1cbff48fbcce709c78bd1f52e896aee990928432b", + "sha256:f2227a08b091bd41df5aadee0a5037673f691e2aa000e1968b1ea2342afc6880", + "sha256:f3754acbd7efc7f1b529039fcffc092a15e1cf045e31f22f6c9c5950c613ec4d", + "sha256:fe48f50fb6348511a3268a893bfd4ab5f263f5ac220782449d03cd05964d1ae7", + "sha256:fff7d17d30b2cd45afd654b3fc117755c5d84506ed25fda386494e4e0a3416e1" + ], + "markers": "python_version >= '3.7'", + "version": "==4.5.0" + }, + "python-dotenv": { + "hashes": [ + "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", + "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.0" + }, + "python-multipart": { + "hashes": [ + "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132", + "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18" + ], + "markers": "python_version >= '3.7'", + "version": "==0.0.6" + }, + "pyyaml": { + "hashes": [ + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + ], + "version": "==6.0.1" + }, + "sniffio": { + "hashes": [ + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + }, + "starlette": { + "hashes": [ + "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75", + "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91" + ], + "markers": "python_version >= '3.7'", + "version": "==0.27.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" + ], + "markers": "python_version >= '3.8'", + "version": "==4.8.0" + }, + "ujson": { + "hashes": [ + "sha256:07d459aca895eb17eb463b00441986b021b9312c6c8cc1d06880925c7f51009c", + "sha256:0be81bae295f65a6896b0c9030b55a106fb2dec69ef877253a87bc7c9c5308f7", + "sha256:0fe1b7edaf560ca6ab023f81cbeaf9946a240876a993b8c5a21a1c539171d903", + "sha256:102bf31c56f59538cccdfec45649780ae00657e86247c07edac434cb14d5388c", + "sha256:11da6bed916f9bfacf13f4fc6a9594abd62b2bb115acfb17a77b0f03bee4cfd5", + "sha256:16fde596d5e45bdf0d7de615346a102510ac8c405098e5595625015b0d4b5296", + "sha256:193349a998cd821483a25f5df30b44e8f495423840ee11b3b28df092ddfd0f7f", + "sha256:20768961a6a706170497129960762ded9c89fb1c10db2989c56956b162e2a8a3", + "sha256:27a2a3c7620ebe43641e926a1062bc04e92dbe90d3501687957d71b4bdddaec4", + "sha256:2873d196725a8193f56dde527b322c4bc79ed97cd60f1d087826ac3290cf9207", + "sha256:299a312c3e85edee1178cb6453645217ba23b4e3186412677fa48e9a7f986de6", + "sha256:2a64cc32bb4a436e5813b83f5aab0889927e5ea1788bf99b930fad853c5625cb", + "sha256:2b852bdf920fe9f84e2a2c210cc45f1b64f763b4f7d01468b33f7791698e455e", + "sha256:2e72ba76313d48a1a3a42e7dc9d1db32ea93fac782ad8dde6f8b13e35c229130", + "sha256:3659deec9ab9eb19e8646932bfe6fe22730757c4addbe9d7d5544e879dc1b721", + "sha256:3b27a8da7a080add559a3b73ec9ebd52e82cc4419f7c6fb7266e62439a055ed0", + "sha256:3f9b63530a5392eb687baff3989d0fb5f45194ae5b1ca8276282fb647f8dcdb3", + "sha256:407d60eb942c318482bbfb1e66be093308bb11617d41c613e33b4ce5be789adc", + "sha256:40931d7c08c4ce99adc4b409ddb1bbb01635a950e81239c2382cfe24251b127a", + "sha256:48c7d373ff22366eecfa36a52b9b55b0ee5bd44c2b50e16084aa88b9de038916", + "sha256:4ddeabbc78b2aed531f167d1e70387b151900bc856d61e9325fcdfefb2a51ad8", + "sha256:5ac97b1e182d81cf395ded620528c59f4177eee024b4b39a50cdd7b720fdeec6", + "sha256:5ce24909a9c25062e60653073dd6d5e6ec9d6ad7ed6e0069450d5b673c854405", + "sha256:69b3104a2603bab510497ceabc186ba40fef38ec731c0ccaa662e01ff94a985c", + "sha256:6a4dafa9010c366589f55afb0fd67084acd8added1a51251008f9ff2c3e44042", + "sha256:6d230d870d1ce03df915e694dcfa3f4e8714369cce2346686dbe0bc8e3f135e7", + "sha256:78e318def4ade898a461b3d92a79f9441e7e0e4d2ad5419abed4336d702c7425", + "sha256:7a42baa647a50fa8bed53d4e242be61023bd37b93577f27f90ffe521ac9dc7a3", + "sha256:7cba16b26efe774c096a5e822e4f27097b7c81ed6fb5264a2b3f5fd8784bab30", + "sha256:7d8283ac5d03e65f488530c43d6610134309085b71db4f675e9cf5dff96a8282", + "sha256:7ecc33b107ae88405aebdb8d82c13d6944be2331ebb04399134c03171509371a", + "sha256:9249fdefeb021e00b46025e77feed89cd91ffe9b3a49415239103fc1d5d9c29a", + "sha256:9399eaa5d1931a0ead49dce3ffacbea63f3177978588b956036bfe53cdf6af75", + "sha256:94c7bd9880fa33fcf7f6d7f4cc032e2371adee3c5dba2922b918987141d1bf07", + "sha256:9571de0c53db5cbc265945e08f093f093af2c5a11e14772c72d8e37fceeedd08", + "sha256:9721cd112b5e4687cb4ade12a7b8af8b048d4991227ae8066d9c4b3a6642a582", + "sha256:9ab282d67ef3097105552bf151438b551cc4bedb3f24d80fada830f2e132aeb9", + "sha256:9d9707e5aacf63fb919f6237d6490c4e0244c7f8d3dc2a0f84d7dec5db7cb54c", + "sha256:a70f776bda2e5072a086c02792c7863ba5833d565189e09fabbd04c8b4c3abba", + "sha256:a89cf3cd8bf33a37600431b7024a7ccf499db25f9f0b332947fbc79043aad879", + "sha256:a8c91b6f4bf23f274af9002b128d133b735141e867109487d17e344d38b87d94", + "sha256:ad24ec130855d4430a682c7a60ca0bc158f8253ec81feed4073801f6b6cb681b", + "sha256:ae7f4725c344bf437e9b881019c558416fe84ad9c6b67426416c131ad577df67", + "sha256:b748797131ac7b29826d1524db1cc366d2722ab7afacc2ce1287cdafccddbf1f", + "sha256:bdf04c6af3852161be9613e458a1fb67327910391de8ffedb8332e60800147a2", + "sha256:bf5737dbcfe0fa0ac8fa599eceafae86b376492c8f1e4b84e3adf765f03fb564", + "sha256:c4e7bb7eba0e1963f8b768f9c458ecb193e5bf6977090182e2b4f4408f35ac76", + "sha256:d524a8c15cfc863705991d70bbec998456a42c405c291d0f84a74ad7f35c5109", + "sha256:d53039d39de65360e924b511c7ca1a67b0975c34c015dd468fca492b11caa8f7", + "sha256:d6f84a7a175c75beecde53a624881ff618e9433045a69fcfb5e154b73cdaa377", + "sha256:e0147d41e9fb5cd174207c4a2895c5e24813204499fd0839951d4c8784a23bf5", + "sha256:e3673053b036fd161ae7a5a33358ccae6793ee89fd499000204676baafd7b3aa", + "sha256:e54578fa8838ddc722539a752adfce9372474114f8c127bb316db5392d942f8b", + "sha256:eb0142f6f10f57598655340a3b2c70ed4646cbe674191da195eb0985a9813b83", + "sha256:efeddf950fb15a832376c0c01d8d7713479fbeceaed1eaecb2665aa62c305aec", + "sha256:f26629ac531d712f93192c233a74888bc8b8212558bd7d04c349125f10199fcf", + "sha256:f2e385a7679b9088d7bc43a64811a7713cc7c33d032d020f757c54e7d41931ae", + "sha256:f3554eaadffe416c6f543af442066afa6549edbc34fe6a7719818c3e72ebfe95", + "sha256:f4511560d75b15ecb367eef561554959b9d49b6ec3b8d5634212f9fed74a6df1", + "sha256:f504117a39cb98abba4153bf0b46b4954cc5d62f6351a14660201500ba31fe7f", + "sha256:fb87decf38cc82bcdea1d7511e73629e651bdec3a43ab40985167ab8449b769c" + ], + "version": "==5.8.0" + }, + "uvicorn": { + "extras": [ + "standard" + ], + "hashes": [ + "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53", + "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a" + ], + "version": "==0.23.2" + }, + "watchfiles": { + "hashes": [ + "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc", + "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365", + "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0", + "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e", + "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124", + "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c", + "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317", + "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094", + "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7", + "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235", + "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c", + "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c", + "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c", + "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235", + "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293", + "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa", + "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef", + "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19", + "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8", + "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d", + "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915", + "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429", + "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097", + "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe", + "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0", + "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d", + "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99", + "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1", + "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a", + "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895", + "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94", + "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562", + "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab", + "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360", + "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1", + "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7", + "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f", + "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03", + "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01", + "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58", + "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052", + "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e", + "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765", + "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6", + "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137", + "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85", + "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca", + "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f", + "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214", + "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7", + "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7", + "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3", + "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b", + "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7", + "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", + "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994", + "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9", + "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec", + "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128", + "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c", + "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2", + "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078", + "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3", + "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e", + "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a", + "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6", + "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49", + "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b", + "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28", + "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9", + "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586", + "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400", + "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165", + "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303", + "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d" + ], + "version": "==0.21.0" + }, + "websockets": { + "hashes": [ + "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b", + "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6", + "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", + "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", + "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205", + "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892", + "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53", + "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", + "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", + "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c", + "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", + "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", + "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931", + "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", + "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370", + "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", + "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec", + "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", + "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62", + "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", + "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", + "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", + "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123", + "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9", + "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", + "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", + "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", + "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", + "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438", + "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137", + "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", + "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", + "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", + "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", + "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", + "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", + "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967", + "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", + "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d", + "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def", + "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", + "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", + "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2", + "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", + "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b", + "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28", + "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7", + "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d", + "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", + "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468", + "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8", + "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae", + "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611", + "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", + "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9", + "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", + "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", + "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2", + "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", + "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", + "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6", + "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", + "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", + "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", + "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", + "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399", + "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", + "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", + "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", + "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", + "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8", + "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" + ], + "version": "==12.0" + } + }, + "develop": { + "anyio": { + "hashes": [ + "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780", + "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5" + ], + "markers": "python_version >= '3.7'", + "version": "==3.7.1" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "platform_system == 'Windows'", + "version": "==0.4.6" + }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httptools": { + "hashes": [ + "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563", + "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142", + "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d", + "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b", + "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4", + "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb", + "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658", + "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084", + "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2", + "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97", + "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837", + "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3", + "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58", + "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da", + "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d", + "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90", + "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0", + "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1", + "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2", + "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e", + "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0", + "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf", + "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc", + "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3", + "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503", + "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a", + "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3", + "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949", + "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84", + "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb", + "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", + "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f", + "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e", + "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81", + "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185", + "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3" + ], + "version": "==0.6.1" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "mypy": { + "hashes": [ + "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7", + "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e", + "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c", + "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169", + "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208", + "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0", + "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1", + "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1", + "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7", + "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45", + "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143", + "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5", + "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f", + "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd", + "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245", + "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f", + "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332", + "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30", + "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183", + "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f", + "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85", + "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46", + "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71", + "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660", + "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb", + "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c", + "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.6.1" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", + "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + ], + "markers": "python_version >= '3.8'", + "version": "==1.3.0" + }, + "pytest": { + "hashes": [ + "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac", + "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.3" + }, + "python-dotenv": { + "hashes": [ + "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", + "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.0" + }, + "pyyaml": { + "hashes": [ + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + ], + "version": "==6.0.1" + }, + "sniffio": { + "hashes": [ + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", + "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" + ], + "markers": "python_version >= '3.8'", + "version": "==4.8.0" + }, + "uvicorn": { + "extras": [ + "standard" + ], + "hashes": [ + "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53", + "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a" + ], + "version": "==0.23.2" + }, + "watchfiles": { + "hashes": [ + "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc", + "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365", + "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0", + "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e", + "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124", + "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c", + "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317", + "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094", + "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7", + "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235", + "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c", + "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c", + "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c", + "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235", + "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293", + "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa", + "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef", + "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19", + "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8", + "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d", + "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915", + "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429", + "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097", + "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe", + "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0", + "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d", + "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99", + "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1", + "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a", + "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895", + "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94", + "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562", + "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab", + "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360", + "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1", + "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7", + "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f", + "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03", + "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01", + "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58", + "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052", + "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e", + "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765", + "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6", + "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137", + "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85", + "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca", + "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f", + "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214", + "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7", + "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7", + "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3", + "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b", + "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7", + "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", + "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994", + "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9", + "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec", + "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128", + "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c", + "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2", + "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078", + "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3", + "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e", + "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a", + "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6", + "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49", + "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b", + "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28", + "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9", + "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586", + "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400", + "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165", + "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303", + "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d" + ], + "version": "==0.21.0" + }, + "websockets": { + "hashes": [ + "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b", + "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6", + "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", + "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", + "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205", + "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892", + "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53", + "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", + "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", + "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c", + "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", + "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", + "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931", + "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", + "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370", + "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", + "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec", + "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", + "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62", + "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", + "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", + "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", + "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123", + "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9", + "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", + "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", + "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", + "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", + "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438", + "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137", + "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", + "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", + "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", + "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", + "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", + "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", + "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967", + "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", + "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d", + "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def", + "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", + "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", + "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2", + "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", + "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b", + "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28", + "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7", + "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d", + "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", + "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468", + "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8", + "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae", + "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611", + "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", + "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9", + "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", + "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", + "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2", + "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", + "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", + "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6", + "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", + "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", + "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", + "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", + "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399", + "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", + "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", + "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", + "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", + "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8", + "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" + ], + "version": "==12.0" + } + } +} diff --git a/backend/api/__init__.py b/backend/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/api_router.py b/backend/api/api_router.py new file mode 100644 index 0000000..2671760 --- /dev/null +++ b/backend/api/api_router.py @@ -0,0 +1,32 @@ +""" +routes for different api versions +""" + +import sys + +from api.api_v1.api import api_router as api_v1 +from core.config import config +from fastapi import APIRouter + +API_V1 = config.API.API_V1 +API_V2 = config.API.API_V2 + +api_main_router = APIRouter() + + +if ( + API_V1.API_V1_ACTIVE == API_V2.API_V2_ACTIVE + and API_V1.API_V1_ROUTE == API_V2.API_V2_ROUTE +): + print("Please fix API paths") + sys.exit(1) + +if API_V1.API_V1_ACTIVE is True: + print(f"api version 1 is available at: {API_V1.API_V1_ROUTE}") + api_main_router.include_router(api_v1, prefix=API_V1.API_V1_ROUTE) + + +# will add as soon as there is a version2 +# if API_V2.API_V2_ACTIVE == True: +# print(f"api version 2 is available at: {API_V2.API_V2_ROUTE}") +# api_main_router.include_router(api_v2, prefix=API_V2.API_V2_ROUTE) diff --git a/backend/api/api_v1/__init__.py b/backend/api/api_v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/api_v1/api.py b/backend/api/api_v1/api.py new file mode 100644 index 0000000..92713ab --- /dev/null +++ b/backend/api/api_v1/api.py @@ -0,0 +1,8 @@ +from api.api_v1.user.user import app as user +from api.api_v1.key.api import app as key +from fastapi import APIRouter + +api_router = APIRouter() + +api_router.include_router(user, prefix="/user") +api_router.include_router(key, prefix="/key") diff --git a/backend/api/api_v1/authentication/__init__.py b/backend/api/api_v1/authentication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/api_v1/authentication/logging.py b/backend/api/api_v1/authentication/logging.py new file mode 100644 index 0000000..90d6efc --- /dev/null +++ b/backend/api/api_v1/authentication/logging.py @@ -0,0 +1,68 @@ +from core.db.mongo import MongoDBManager +import datetime +import json +from bson import ObjectId + + +class EventLogger: + def __init__(self, db_name, collection_name, db_url): + self.mongo_client = MongoDBManager( + db_name=db_name, + collection_name=collection_name, + db_url=db_url, + ) + self.db_name = db_name + self.db_url = db_url + self.collection_name = collection_name + + async def log_login( + self, + ip_address, + password, + username, + event_type, + status, + session_duration_minutes, + additional_info, + url, + method, + headers, + body, + ): + log_data = { + "ip_address": ip_address, + "password": password, + "username": username, + "event_type": event_type, + "status": status, + "session_duration_minutes": session_duration_minutes, + "additional_info": additional_info, + "url": url, + "method": method, + "headers": headers, + "body": body.decode(), + "timestamp": datetime.datetime.now(), + } + + try: + write_manager = self.mongo_client.WriteManager( + self.mongo_client.collection + ) + + result = await write_manager.insert_document(data=log_data) + print(result) + return result + except Exception as e: + return f"Error occurred during logging: {e}" + finally: + print(await self.mongo_client.close_connection()) + + async def get_log(self, search): + try: + read_manager = self.mongo_client.ReadManager( + self.mongo_client.collection + ) + logs = await read_manager.find_one(search) + return logs + finally: + await self.mongo_client.close_connection() diff --git a/backend/api/api_v1/authentication/token.py b/backend/api/api_v1/authentication/token.py new file mode 100644 index 0000000..8fa246f --- /dev/null +++ b/backend/api/api_v1/authentication/token.py @@ -0,0 +1,86 @@ +import random +import string +from core.db.redis import AsyncRedisManager +from core.config import config + +# import asyncio + +# follow the instructions on how to install redis +# https://redis.io/docs/install/install-redis/ + +Redis_DB = config.RedisdbSettings.REDIS_DB +Redis_Port = config.RedisdbSettings.REDIS_PORT +Redis_Host = config.RedisdbSettings.REDIS_HOST + + +async def generate_access_token(): + # Generate a random string for the access token + token = "".join(random.choices(string.ascii_letters + string.digits, k=32)) + return token + + +async def check_access_token_exists(redis_manager, user_id): + # Check if the access token already exists in Redis + existing_value = await redis_manager.get(user_id) + if existing_value is not None: + return existing_value # Return the user object ID + else: + return None # Access token does not exist + + +async def get_user_id(redis_manager, token): + user_id = await redis_manager.get(token) + if user_id is not None: + return user_id + else: + return KeyError + + +async def create_token_for_uid(expiration_time_minutes=140): + new_token = await generate_access_token() + store_access_token( + token=new_token, + expiration_time=(expiration_time_minutes * 60), + ) + + +async def store_access_token(redis_manager, token, user_id, expiration_time): + # Store the access token in Redis with the corresponding + # user object ID and an expiration time + + results = await redis_manager.set(token, user_id, expiration_time) + + return results + + +async def main( + user_id, + token, + db=Redis_DB, + port=Redis_Port, + host=Redis_Host, + expiration_time_minutes=140, +): + redis_manager = AsyncRedisManager(db=db, port=port, host=host) + await redis_manager.connect() + + if token: + uid = get_user_id(redis_manager, token) + print("uid:\n") + print(uid) + return uid + + existing_token = await check_access_token_exists(redis_manager, user_id) + + if existing_token: + await redis_manager.delete(existing_token) + + new_token = await generate_access_token() + await store_access_token( + redis_manager, new_token, user_id, expiration_time_minutes + ) + + # Close the Redis connection + await redis_manager.close() + + return new_token diff --git a/backend/api/api_v1/authentication/user_authorization.py b/backend/api/api_v1/authentication/user_authorization.py new file mode 100644 index 0000000..4a312de --- /dev/null +++ b/backend/api/api_v1/authentication/user_authorization.py @@ -0,0 +1,2 @@ +async def check_if_allowed(): + \ No newline at end of file diff --git a/backend/api/api_v1/key/__init__.py b/backend/api/api_v1/key/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/api_v1/key/api.py b/backend/api/api_v1/key/api.py new file mode 100644 index 0000000..c35399f --- /dev/null +++ b/backend/api/api_v1/key/api.py @@ -0,0 +1,89 @@ +""" +main.py +""" + +from fastapi import APIRouter +from pydantic import BaseModel +from typing import List + +from api.api_v1.key.managment import generate_new_key, delete_key +from core.converter import convert_duration_to_minutes + + +app = APIRouter() + + +class KeysResponse(BaseModel): + keys: List[str] + + +@app.get("/{application_id}/{duration}") +async def generate_key_with_options( + application_id: str, + duration: str, + amount: int = 1, + key_format: str = "Authly-%%^[a-zA-Z]{3,}#[0-9]{4}$%%", +): + """ + ## Key Generation + + The system generates keys based on a specified format string with + embedded regular expression patterns using the + `generate_keys_with_format` function. This function leverages the + `exrex` library to ensure that the generated keys adhere to the + defined format and pattern specifications. + + + ### Generate Keys With Format + + Generates keys based on a provided format string with embedded regular + expression patterns using the exrex library. + + #### Arguments + + - `key_format` (str): A string containing the desired format for the + keys. The format string should include placeholders with the + syntax '%%[regular expression pattern]%%', where the regular + expression pattern specifies the format of the random + characters to be generated. + + #### Returns + + A tuple containing the randomly generated key and the formatted key + that follows the specified format based on the embedded regular + expression patterns. + + #### Example Usage + + - Input: "`%%^[a-zA-Z]{3,}#[0-9]{4}$%%`" + + Output: "abc#1234" + + - Input: "`Perma-%%^[a-zA-Z]{2,3}#[0-9]{3,4}$%%`" + + Output: "Perma-xyz#5678" + + - Input: "`Key: %%^[a-zA-Z0-9_]{5,8}%%`" + + Output: "Key: abCdE_12" + + - Input: "`%%^[a-zA-Z]{2,4}_[0-9]{3,5}%%_Code`" + + Output: "xy_7890_Code" + """ + time_in_m = await convert_duration_to_minutes(duration) + + new_keys = await generate_new_key( + key_format=key_format, + num_keys=amount, + creator="Test", + duration=time_in_m, + application_id=application_id, + ) + response = KeysResponse(keys=new_keys) + return response + + +@app.delete("/delete") +async def delete_keys(application_id: str): + return await delete_key(application=application_id) diff --git a/backend/api/api_v1/key/generation.py b/backend/api/api_v1/key/generation.py new file mode 100644 index 0000000..9cdcef6 --- /dev/null +++ b/backend/api/api_v1/key/generation.py @@ -0,0 +1,66 @@ +""" +Generates keys based on a provided format string with embedded regular\ + expression patterns using the exrex library. + +Args: + key_format (str): A string containing the desired format for the keys.\ + The format string should include placeholders + with the syntax '%%[regular expression pattern]%%', where the regular\ + expression pattern specifies the format of the + random characters to be generated. + +Returns: + tuple: A tuple containing the randomly generated key and the formatted\ + key that follows the specified format based + on the embedded regular expression patterns. + +Example Usage: + Input: "%%^[a-zA-Z]{3,}#[0-9]{4}$%%" + Output: ("abc#1234", "abc#1234") + + Input: "Perma-%%^[a-zA-Z]{2,3}#[0-9]{3,4}$%%" + Output: ("Perma-xyz#5678", "Perma-xyz#5678") + + Input: "Key: %%^[a-zA-Z0-9_]{5,8}%%" + Output: ("Key: abCdE_12", "Key: abCdE_12") + + Input: "%%^[a-zA-Z]{2,4}_[0-9]{3,5}%%_Code" + Output: ("xy_7890_Code", "xy_7890_Code") + +The regular expression syntax used in the `key_format` string follows\ + standard conventions. The placeholder syntax +'%%[regular expression pattern]%%' allows for the specification of desired\ + patterns for the random characters to be +generated. For example, the pattern '%%^[a-zA-Z]{3,}#[0-9]{4}$%%' denotes\ + a string that starts with at least 3 or more +letters, followed by '#', and ending with exactly 4 digits. The `exrex`\ + library interprets these regular expression +patterns and generates random strings that match the specified patterns,\ + ensuring that the generated keys adhere to +the defined format. +""" +import re +import exrex + + +async def generate_keys_with_format(key_format, num_keys=1): + pattern = r"%%(.*?)%%" + matches = re.findall(pattern, key_format) + generated_keys = [] + for _ in range(num_keys): + current_key = key_format + for match in matches: + placeholder = "%%" + match + "%%" + generated_value = exrex.getone(match) + current_key = current_key.replace(placeholder, generated_value, 1) + generated_keys.append(current_key) + return generated_keys + + +if __name__ == "__main__": + # Define the key format string + key_format_string = "%%^[a-zA-Z]{5,20}-[0-9]{3,5}%%_Code" + + # Generate and print a key with the specified format + generated_key = generate_keys_with_format(key_format_string) + print(generated_key) diff --git a/backend/api/api_v1/key/managment.py b/backend/api/api_v1/key/managment.py new file mode 100644 index 0000000..d3189c4 --- /dev/null +++ b/backend/api/api_v1/key/managment.py @@ -0,0 +1,83 @@ +from api.api_v1.key.generation import generate_keys_with_format +from core.db.mongo import MongoDBManager +from core.config import config +import datetime + + +async def generate_new_key( + duration, application_id, key_format, creator, num_keys=1 +): + generated_keys = await generate_keys_with_format( + key_format=key_format, num_keys=num_keys + ) + db_status = [] + + try: + mongo_manager = MongoDBManager( + db_url=config.MongodbSettings.MONGODB_URL, + db_name=config.MongodbSettings.MONGODB_NAME, + collection_name="Keys", + ) + + write_manager = mongo_manager.WriteManager( + collection=mongo_manager.collection + ) # Pass the collection object + + for key in generated_keys: + key_data = { + "key_value": key, + "creation_date": datetime.datetime.now(), + "duration": duration, + "application_id": application_id, + "creator": creator, + } + # Store key data in MongoDB + status = await write_manager.insert_document( + data=key_data + ) # Pass the document as a list + db_status.append(status) + + return generated_keys + + finally: + await mongo_manager.close_connection() + + +async def delete_key(application=None, creator=None, key=None): + try: + mongo_manager = MongoDBManager( + db_url=config.MongodbSettings.MONGODB_URL, + db_name=config.MongodbSettings.MONGODB_NAME, + collection_name="Keys", + ) +<<<<<<< HEAD +======= + db_status = {} + + read_manager = mongo_manager.ReadManager( + collection=mongo_manager.collection + ) +>>>>>>> apirouting + + delete_manager = mongo_manager.DeleteManager( + collection=mongo_manager.collection + ) + + delete_query = {} + if application: + delete_query = {"application_id": application} + elif creator: + delete_query = {"creator": creator} + elif key: + delete_query = {"key_value": key} + + elif not any([application, creator, key]): + raise ValueError( + "Please provide either application, creator, or key." + ) + result = await delete_manager.delete_many_documents(query=delete_query) + + return result + + finally: + await mongo_manager.close_connection() diff --git a/backend/api/api_v1/user/__init__.py b/backend/api/api_v1/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/api_v1/user/model.py b/backend/api/api_v1/user/model.py new file mode 100644 index 0000000..e1141a5 --- /dev/null +++ b/backend/api/api_v1/user/model.py @@ -0,0 +1,91 @@ +import base64 +from typing import Dict, Optional + +from core.config import config +from core.password_validation import validate_password_complexity +from core.username_validation import validate_username +from pydantic import BaseModel, EmailStr, Field, constr, validator + + +class UserDataResponse(BaseModel): + user_data: Dict[str, Dict[str, str]] + + class Config: + json_schema_extra = { + "example": { + "user_data": { + "abc#1234": { + "email": "user@example.com", + "username": "abc#1234", + } + } + } + } + + +class UserRegistration(BaseModel): + """ + Represents user registration data, including email, username, and password. + """ + + email: EmailStr = Field(..., example="1337@Allah.com") + username: str = Field(..., example="abc#1234") + password: constr( + min_length=config.PasswordConfig.DEFAULT_PASSWORD_MIN_LENGTH, + max_length=config.PasswordConfig.DEFAULT_PASSWORD_MAX_LENGTH, + ) = Field(..., example="XDFmYyciU3wreHUnOCwiX3VXajMkS1BeKQ==") + # pw as base64 to prevent escaping characters in json + + @validator("password") + def validate_password(cls, value): + """ + Validate the password by decoding it as\ + Base64 and checking its complexity. + + Args: + value (str): The password encoded as Base64. + + Returns: + str: The decoded password if it passes validation. + + Raises: + ValueError: If the decoding fails or the password\ + does not meet complexity requirements. + """ + try: + # Attempt to decode the password as Base64 + decoded_password = base64.b64decode(value.encode(), validate=True) + decoded_password = decoded_password.decode() + + validate_password_complexity(decoded_password) + + # If the checks pass, return the decoded password + return value + + except base64.binascii.Error as err: + # If the decoding fails due to invalid Base64, + # raise a validation error + raise ValueError("Invalid Base64-encoded password") from err + + @validator("username") + def validate_username(cls, value): + # Define the regular expression pattern for the username + validate_username(value) + + return value + + def json(self, *args, **kwargs): + return dict(self) + + +class GetUsersByName(BaseModel): + usernames: list[str] + + +class GetLog(BaseModel): + username: Optional[str] + + +class GetToken(BaseModel): + username: str + password: str diff --git a/backend/api/api_v1/user/user.py b/backend/api/api_v1/user/user.py new file mode 100644 index 0000000..946d663 --- /dev/null +++ b/backend/api/api_v1/user/user.py @@ -0,0 +1,213 @@ +""" +main.py +""" +import time + +from api.api_v1.user import model +from api.api_v1.security.logging import EventLogger +from core.config import config +from core.db.mongo import MongoDBManager +from core.hashing import Hasher +from fastapi import APIRouter, Request + + +# utils + + +def login_log_reponse(data) -> dict: + return { + "id": str(data["_id"]), + "ip_address": data["ip_address"], + "password": data["password"], + "username": data["username"], + "event_type": data["event_type"], + "status": data["status"], + "session_duration_minutes": data["session_duration_minutes"], + "additional_info": data["additional_info"], + "url": data["url"], + "method": data["method"], + "headers": data["headers"], + "body": data["body"], + "timestamp": data["timestamp"], + } + + +def hash_password(password) -> str: + if config.Debug.DebugHashingTime is True: + print(password) + print(f"using: {config.PasswordConfig.HASHING_ALGORITHM}") + start_time = time.time() # Record the start time + + hashed = Hasher.get_password_hash(password=password) + + end_time = time.time() # Record the end time + + if config.Debug.DebugHashingTime is True: + print("Hashed Password:", hashed) + print("Hashing Time:", end_time - start_time, "seconds") + + return hashed + + +Mongo_URL = config.MongodbSettings.MONGODB_URL +app = APIRouter() + + +@app.post("/") +async def register_user(user_data: model.UserRegistration): + """ + ## Register a user. (V1) + + + :param email: user email + + :param username: The user's username. Username must consist of 3 letters,\ + a '#' character, and 4 numbers. (abc#1234) + + :param password: The user's password in Base64\ + must be at least 8 characters long and contain at\ + least one uppercase letter, one lowercase letter,\ + one digit, and one special character (e.g., @$!%*?&). + """ + + password = user_data.password + + # Convert the Pydantic model to a dictionary using dict() + user_data_dict = user_data.dict() + + password_hashed = hash_password(password=password) + + user_data_dict["password"] = password_hashed + + try: + mongo_client = MongoDBManager( + db_url=Mongo_URL, db_name="mydb", collection_name="Users" + ) + + # Insert the dictionary into the MongoDB collection + result = await mongo_client.write_manager.insert_document( + data=user_data_dict + ) + + return result + + finally: + await mongo_client.close_connection() + + +@app.get("/", response_model=model.UserDataResponse) +async def get_users_by_usernam(data: model.GetUsersByName): + """ + # Search for users by usernames. (V1) + + This endpoint takes a list of usernames and\ + returns user data for each username. + + Args: + data (model.GetUsersByName):\ + Request data containing a list of usernames. + + Returns: + dict: A dictionary with usernames as keys and user data as values. + + ### Example Request: + ```json + { + "usernames": ["abc#1234", "abcasdasdc#1224"] + } + ``` + ### Example Response: + ```json + { + "user_data": { + "abc#1234": { + "email": "user@example.com", + "username": "abc#1234", + "password": "$argon2id$..." + }, + "abcasdasdc#1224": { + "email": "user2@example.com", + "username": "abcasdasdc#1224", + "password": "$argon2id$..." + } + } + } + ``` + """ + usernames = data.usernames + + try: + mongo_client = MongoDBManager( + collection_name="Users", + db_name="mydb", + db_url=Mongo_URL, + ) + + response_data = {} # Initialize an empty dictionary + + for username in usernames: + search_dict = {"username": f"{username}"} + print(search_dict) + user_data = await mongo_client.read_manager.find_one( + query=search_dict + ) + + if user_data: + # Convert the ObjectId to a string + user_data["_id"] = str(user_data["_id"]) + response_data[username] = {**user_data, "username": username} + else: + response_data[username] = None # User not found + + return {"user_data": response_data} + finally: + await mongo_client.close_connection() + + +@app.post("/token") +async def login_for_access_token( + request: Request, credentials: model.GetToken +): + ip_address = request.client.host + # user_agent = request.headers["user-agent"] + url = request.url.path + method = request.method + headers = dict(request.headers) + body = await request.body() + event_type = "login_attempt" + status = "failed" + password = credentials.password + username = credentials.username + + log_manager = EventLogger( + db_name="logs", collection_name="log.login", db_url=Mongo_URL + ) + + result = await log_manager.log_login( + ip_address=ip_address, + password=password, + username=username, + event_type=event_type, + status=status, + # user_agent=user_agent, + additional_info=None, + url=url, + session_duration_minutes=12, + method=method, + headers=headers, + body=body, + ) + if result: + return result + return "some error occured" + + +@app.get("/login_log") +async def get_log(data: model.GetLog) -> dict: + log_manager = EventLogger( + db_url=Mongo_URL, collection_name="log.login", db_name="logs" + ) + username = {"username": data.username} + results = await log_manager.get_log(username) + reponse = login_log_reponse(results) + return reponse diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 0000000..904bee1 --- /dev/null +++ b/backend/app.py @@ -0,0 +1,18 @@ +""" +main.py +""" +from api.api_router import api_main_router +from core.config import config +from fastapi import FastAPI + +app = FastAPI() + + +app.include_router(api_main_router, prefix=config.API.API_ROUTE) + + +# debug/dev only +if __name__ == "__main__": + import uvicorn + + uvicorn.run("app:app", host="localhost", port=8000, reload=True) diff --git a/backend/config.json b/backend/config.json new file mode 100644 index 0000000..ec91f6a --- /dev/null +++ b/backend/config.json @@ -0,0 +1,66 @@ +{ + "Debug": { + "DebugHashingTime": true + }, + "API": { + "API_ROUTE": "/api", + "API_V1": { + "API_V1_ACTIVE": true, + "API_V1_ROUTE": "/v1" + }, + "API_V2": { + "API_V2_ACTIVE": false, + "API_V2_ROUTE": "/v2" + } + }, + "PasswordConfig": { + "USE_PASSWORD_HASHING": true, + "HASHING_ALGORITHM": "argon2", + "BcryptHashingAlgorithm": { + "ROUNDS": 12, + "ENCODING": "utf-8" + }, + "ArgonHashingAlgorithm": { + "TIME_COST": 12, + "MEMORY_COST": 512, + "PARALLELISM": 4, + "SALT_LEN": 32, + "HASH_LEN": 64, + "ENCODING": "utf-8" + }, + "DEFAULT_PASSWORD_MIN_LENGTH": 11, + "DEFAULT_PASSWORD_MAX_LENGTH": 50 + }, + "MongodbSettings": { + "MONGODB_URL": "localhost", + "MONGODB_NAME": "mydb", + "MONGODB_USERNAME": "your_username", + "MONGODB_PASSWORD": "your_password", + "MONGODB_USE_SSL": false, + "MONGODB_AUTH_MECHANISM": "SCRAM-SHA-256", + "MONGODB_MAX_POOL_SIZE": 100, + "MONGODB_MIN_POOL_SIZE": 1, + "MONGODB_MAX_IDLE_TIME_MS": 10000, + "MONGODB_WAIT_QUEUE_TIMEOUT_MS": 2000, + "MONGODB_SERVER_SELECTION_TIMEOUT_MS": 30000, + "MONGODB_READ_CONCERN": "local", + "MONGODB_WRITE_CONCERN": "local", + "MONGODB_RETRY_WRITES": true, + "MONGODB_CONNECT_TIMEOUT_MS": 20000, + "MONGODB_SOCKET_TIMEOUT_MS": 30000 + }, + "RedisdbSettings": { + "REDIS_HOST": "localhost", + "REDIS_PORT": 6379, + "REDIS_DB": 0 + }, + "SessionManagerSettings": { + "ALLOW_COOKIE_LOGIN": true, + "DEFAULT_PATH": "/", + "DEFAULT_STORE_IP_WITH_SESSION": true, + "DEFAULT_MAX_NUMBER_OF_IPS": 1, + "DEFAULT_DELETE_ON_LOGIN_ERROR": true, + "DEFAULT_MAX_AGE_SECONDS": 86400, + "DEFAULT_SECURE": true + } +} diff --git a/backend/core/__init__.py b/backend/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/core/config.py b/backend/core/config.py new file mode 100644 index 0000000..3887b2c --- /dev/null +++ b/backend/core/config.py @@ -0,0 +1,300 @@ +""" +This is a Python script for handling configuration settings using Pydantic. +It reads a JSON file and validates it against a predefined Pydantic model. +""" + +import os +import json +from enum import Enum +from pydantic_settings import BaseSettings + + +class HashingAlgorithmTypes(str, Enum): + """Enum class for hashing algorithm types.""" + + BCRYPT = "bcrypt" + ARGON2 = "argon2" # default + + +############################################################ +# JSON FORMAT # +############################################################ + + +class Debug(BaseSettings): + DebugHashingTime: bool = False + + +class API_V1(BaseSettings): + """ + Configuration settings for API version 1. + + Attributes: + API_V1_ACTIVE (bool):\ + Whether to use API version 1. Default is True. + API_V1_ROUTE (str):\ + The route for API version 1. Default is "/v1". + """ + + API_V1_ACTIVE: bool = True + API_V1_ROUTE: str = "/v1" + + +class API_V2(BaseSettings): + """ + Configuration settings for API version 2. + + Attributes: + API_V2_ACTIVE (bool):\ + Whether to use API version 2. Default is False. + API_V2_ROUTE (str):\ + The route for API version 2. Default is "/v2". + """ + + API_V2_ACTIVE: bool = False + API_V2_ROUTE: str = "/v2" + # not yet available + + +class API(BaseSettings): + """ + Configuration settings for the API. + + Attributes: + API_ROUTE (str):\ + The base route where the API is located. Default is "/api". + API_V1 (API_V1):\ + Configuration settings for API version 1. + API_V2 (API_V2):\ + Configuration settings for API version 2. + """ + + API_ROUTE: str = "/api" + API_V1: API_V1 + API_V2: API_V2 + + +class BcryptHashingAlgorithm(BaseSettings): + """ + Configuration settings for the Bcrypt password hashing algorithm. + + Attributes: + ROUNDS (int):\ + The number of rounds for Bcrypt. Default is 12. + ENCODING (str):\ + The character encoding used for hashing, e.g., 'utf-8'. + """ + + ROUNDS: int = 12 + ENCODING: str = "utf-8" + + +class ArgonHashingAlgorithm(BaseSettings): + """ + Configuration settings for the Argon2 password hashing algorithm. + + Attributes: + TIME_COST (int):\ + The number of iterations (time cost) for Argon2.\ + Default is 1048576 (2^20). + MEMORY_COST (int):\ + The memory usage (in MiB) for Argon2.\ + Default is 65536 MiB. + PARALLELISM (int):\ + The number of threads (parallelism) used during Argon2 execution.\ + Default is 4. + HASH_LEN (int):\ + The length of the resulting hash in bytes.\ + Default is 16 bytes. + SALT_LEN (int):\ + The length of the salt used in bytes.\ + Default is 32 bytes. + ENCODING (str):\ + The character encoding used for hashing, e.g., 'utf-8'. + """ + + TIME_COST: int = 12 + MEMORY_COST: int = 256 + PARALLELISM: int = 4 + SALT_LEN: int = 32 + HASH_LEN: int = 64 + ENCODING: str = "utf-8" + + +class PasswordConfig(BaseSettings): + """ + Configuration settings for password hashing and password policies. + + Attributes: + USE_PASSWORD_HASHING (bool):\ + Whether to use password hashing. + HASHING_ALGORITHM (HashingAlgorithmTypes):\ + The selected password hashing algorithm. + BcryptHashingAlgorithm (BcryptHashingAlgorithm):\ + Configuration settings for Bcrypt. + ArgonHashingAlgorithm (ArgonHashingAlgorithm):\ + Configuration settings for Argon2. + DEFAULT_PASSWORD_MIN_LENGTH (int):\ + The default minimum password length. Default is 11. + DEFAULT_PASSWORD_MAX_LENGTH (int):\ + The default maximum password length. Default is 50. + """ + + USE_PASSWORD_HASHING: bool = True + HASHING_ALGORITHM: HashingAlgorithmTypes = "argon2" + BcryptHashingAlgorithm: BcryptHashingAlgorithm + ArgonHashingAlgorithm: ArgonHashingAlgorithm + DEFAULT_PASSWORD_MIN_LENGTH: int = 11 + DEFAULT_PASSWORD_MAX_LENGTH: int = 50 + + +class MongodbSettings(BaseSettings): + """ + Configuration settings for connecting to MongoDB. + + Attributes: + MONGODB_HOST (str):\ + The MongoDB host. + MONGODB_PORT (int):\ + The MongoDB port. + DB_NAME (str):\ + The name of the MongoDB database. + MONGODB_USERNAME (str):\ + Your MongoDB username (if required). + MONGODB_PASSWORD (str):\ + Your MongoDB password (if required). + MONGODB_USE_SSL (bool):\ + Whether to use SSL/TLS for communication. + MONGODB_AUTH_MECHANISM (str):\ + The authentication mechanism. + MONGODB_MAX_POOL_SIZE (int):\ + The maximum connection pool size. + MONGODB_MIN_POOL_SIZE (int):\ + The minimum connection pool size. + MONGODB_MAX_IDLE_TIME_MS (int):\ + The maximum time connections can remain idle. + MONGODB_WAIT_QUEUE_TIMEOUT_MS (int):\ + The wait queue timeout. + MONGODB_SERVER_SELECTION_TIMEOUT_MS (int):\ + The server selection timeout. + MONGODB_READ_CONCERN (str):\ + The read concern. + MONGODB_WRITE_CONCERN (str):\ + The write concern. + MONGODB_RETRY_WRITES (bool):\ + Whether to retry writes. + MONGODB_CONNECT_TIMEOUT_MS (int):\ + The connection timeout. + MONGODB_SOCKET_TIMEOUT_MS (int):\ + The socket timeout. + """ + + MONGODB_URL: str = "mongodb://localhost:27017" + MONGODB_NAME: str = "mydb" + MONGODB_USERNAME: str + MONGODB_PASSWORD: str + MONGODB_USE_SSL: bool = False + MONGODB_AUTH_MECHANISM: str = "SCRAM-SHA-256" + MONGODB_MAX_POOL_SIZE: int = 100 + MONGODB_MIN_POOL_SIZE: int = 1 + MONGODB_MAX_IDLE_TIME_MS: int = 10000 + MONGODB_WAIT_QUEUE_TIMEOUT_MS: int = 2000 + MONGODB_SERVER_SELECTION_TIMEOUT_MS: int = 30000 + MONGODB_READ_CONCERN: str = "local" + MONGODB_WRITE_CONCERN: str = "local" + MONGODB_RETRY_WRITES: bool = True + MONGODB_CONNECT_TIMEOUT_MS: int = 20000 + MONGODB_SOCKET_TIMEOUT_MS: int = 30000 + + +class RedisdbSettings(BaseSettings): + """ + Settings class for configuring the Redis database connection. + + read more about redis: + https://redis.io/docs/ + + Attributes: + REDIS_HOST (str): The Redis host address. Default is "localhost". + REDIS_PORT (int): The Redis port number. Default is 6379. + REDIS_DB (int): The Redis database number or index. Default is 0. + """ + + REDIS_HOST: str = "localhost" + REDIS_PORT: int = 6379 + REDIS_DB: int = 0 + + +class SessionManagerSettings(BaseSettings): + """ + Configuration settings for the session manager. + + Attributes: + ALLOW_COOKIE_LOGIN (bool):\ + Allow login via the cookie field. + DEFAULT_PATH (str):\ + The default path for cookies. + DEFAULT_STORE_IP_WITH_SESSION (bool):\ + Store session with IP restriction. + DEFAULT_MAX_NUMBER_OF_IPS (int):\ + The number of allowed IPs per session. + DEFAULT_DELETE_ON_LOGIN_ERROR (bool):\ + Delete session on malicious login attempts. + DEFAULT_MAX_AGE_SECONDS (int):\ + The maximum age for a session in seconds.\ + Default is 86400 (24 hours). + DEFAULT_SECURE (bool):\ + Whether the cookie should only be sent over HTTPS. + """ + + ALLOW_COOKIE_LOGIN: bool = True + DEFAULT_PATH: str = "/" + DEFAULT_STORE_IP_WITH_SESSION: bool = True + DEFAULT_MAX_NUMBER_OF_IPS: int = 1 + DEFAULT_DELETE_ON_LOGIN_ERROR: bool = True + DEFAULT_MAX_AGE_SECONDS: int = 86400 + DEFAULT_SECURE: bool = True + + +class AppConfig(BaseSettings): + """ + Configuration settings for the application. + + Attributes: + API (API):\ + Configuration settings for the API. + PasswordConfig (PasswordConfig):\ + Configuration settings for password hashing and policies. + MongodbSettings (MongodbSettings):\ + Configuration settings for connecting to MongoDB. + SessionManagerSettings (SessionManagerSettings):\ + Configuration settings for the session manager. + """ + + Debug: Debug + API: API + PasswordConfig: PasswordConfig + MongodbSettings: MongodbSettings + RedisdbSettings: RedisdbSettings + SessionManagerSettings: SessionManagerSettings + + +# Get the directory of the current script or module +script_directory = os.path.dirname(os.path.realpath(__file__)) + +# Move up one directory to reach the parent directory +parent_directory = os.path.abspath(os.path.join(script_directory, os.pardir)) + +# Construct the relative path to the JSON file in the parent directory +json_file_path = os.path.join(parent_directory, "config.json") + + +# Load JSON file +with open( + json_file_path, + encoding="utf-8", +) as f: + config_data = json.load(f) + +# Parse JSON into Pydantic model +config = AppConfig(**config_data) diff --git a/backend/core/converter.py b/backend/core/converter.py new file mode 100644 index 0000000..588bb64 --- /dev/null +++ b/backend/core/converter.py @@ -0,0 +1,68 @@ +async def convert_duration_to_minutes(duration_str): + """ +<<<<<<< HEAD + Convert the input duration string to the corresponding\ + duration in minutes asynchronously. + + Args: + duration_str (str): A string representing the duration in\ + the format 'Xm', 'Xh', or 'Xd', where 'X' is an integer + value and 'm', 'h', and 'd' represent minutes, hours,\ + and days, respectively. + + Returns: + int: The total duration in minutes calculated\ + from the input duration string. + + Raises: + ValueError: If the input duration format is invalid or the duration\ + value is not a valid integer. +======= + Convert the input duration string to the corresponding duration in minutes asynchronously. + + Args: + duration_str (str): A string representing the duration in the format 'Xm', 'Xh', or 'Xd', where 'X' is an integer + value and 'm', 'h', and 'd' represent minutes, hours, and days, respectively. + + Returns: + int: The total duration in minutes calculated from the input duration string. + + Raises: + ValueError: If the input duration format is invalid or the duration value is not a valid integer. +>>>>>>> apirouting + + Example Usage: + Input: '60m' + Output: 60 + + Input: '2h' + Output: 120 + + Input: '1d' + Output: 1440 + """ + duration_mapping = { + "m": 1, + "h": 60, + "d": 1440, + } # Mapping of time units to minutes + unit = duration_str[-1] # Extract the time unit from the duration string + if unit not in duration_mapping: + raise ValueError( + "Invalid duration format. Valid formats include 'm' for minutes,\ + 'h' for hours, and 'd' for days." + ) + + try: + duration = ( + int(duration_str[:-1]) * duration_mapping[unit] + ) # Calculate the total duration in minutes + except ValueError: + raise ValueError( + "Invalid duration value. Please provide a valid integer\ + for the duration." + ) + + return int( + duration + ) # Ensure that the duration is returned as an integer value diff --git a/backend/core/db/__init__.py b/backend/core/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/core/db/mongo.py b/backend/core/db/mongo.py new file mode 100644 index 0000000..b0a6b11 --- /dev/null +++ b/backend/core/db/mongo.py @@ -0,0 +1,232 @@ +""" +Module for managing asynchronous MongoDB operations. + +This module provides a MongoDBManager class that facilitates asynchronous\ + operations for creating, reading, updating, and deleting data in a\ + MongoDB database. It uses the motor library for asynchronous\ + interaction with MongoDB. + +Classes: +- MongoDBManager: A class for managing asynchronous MongoDB operations\ + including Write, read, update, and delete operations. + +Subclasses: +- WriteManager: A class for handling asynchronous creation operations. +- ReadManager: A class for handling asynchronous read operations. +- UpdateManager: A class for handling asynchronous update operations. +- DeleteManager: A class for handling asynchronous delete operations. + +operations are named after the crud operations documentation from mongo: +https://www.mongodb.com/docs/manual/crud/ + +Author: Arteii/wavy42 +Date: 25/10/2023 +""" + +import motor.motor_asyncio +import asyncio + + +class MongoDBManager: + def __init__( + self, + db_url, + db_name, + collection_name, + ): + """ + Initialize the MongoDBManager class. + + Args: + db_url (str): The URL of the MongoDB database. + db_name (str): The name of the MongoDB database. + collection_name (str): The name of the MongoDB collection. + """ + self.client = motor.motor_asyncio.AsyncIOMotorClient(db_url) + self.db = self.client[db_name] + self.collection = self.db[collection_name] + self.update_manager = self.UpdateManager(self.collection) + self.write_manager = self.WriteManager(self.collection) + self.delete_manager = self.DeleteManager(self.collection) + self.read_manager = self.ReadManager(self.collection) + + ##################################### + # Write: # + ##################################### + + class WriteManager: + def __init__(self, collection): + self.collection = collection + + async def insert_document(self, data): + try: + await self.collection.insert_one(data) + print("Document inserted successfully.") + return "Document inserted successfully." + except Exception as e: + print(f"Error occurred while inserting document: {e}") + return f"Error occurred while inserting document: {e}" + + async def insert_many_documents(self, data_list): + try: + await self.collection.insert_many(data_list) + print(f"Documents inserted successfully. ({data_list})") + return "Documents inserted successfully." + except Exception as e: + print(f"Error occurred while inserting documents: {e}") + return f"Error occurred while inserting documents: {e}" + + async def bulk_write(self, requests): + try: + result = await self.collection.bulk_write(requests) + print(f"{result.inserted_count} documents inserted.") + print(f"{result.modified_count} documents updated.") + print(f"{result.deleted_count} documents deleted.") + return ( + result.inserted_count, + result.modified_count, + result.deleted_count, + ) + except Exception as e: + print(f"Error occurred during bulk write operation: {e}") + return f"Error occurred during bulk write operation: {e}" + + ##################################### + # Read: # + ##################################### + + class ReadManager: + def __init__(self, collection): + self.collection = collection + + async def find_one(self, query): + try: + result = await self.collection.find_one(query) + if result: + print("Document found:", result) + return result + else: + print("No document found with the given query.") + return "No document found with the given query." + except Exception as e: + print(f"Error occurred during find_one operation: {e}") + return f"Error occurred during find_one operation: {e}" + + async def find_many(self, query): + try: + result_list = [] + async for doc in self.collection.find(query): + result_list.append(doc) + print(doc) + return result_list + except Exception as e: + print(f"Error occurred during find_many operation: {e}") + return f"Error occurred during find_many operation: {e}" + + ##################################### + # Update: # + ##################################### + + class UpdateManager: + def __init__(self, collection): + self.collection = collection + + async def update_one_document(self, query, update_data): + try: + result = await self.collection.update_one( + query, {"$set": update_data} + ) + if result.modified_count > 0: + print("Document updated successfully.") + return "Document updated successfully." + else: + print("No document found with the given query.") + return "No document found with the given query." + except Exception as e: + print(f"Error occurred during update_one operation: {e}") + return f"Error occurred during update_one operation: {e}" + + async def update_many_documents(self, query, update_data): + try: + result = await self.collection.update_many( + query, {"$set": update_data} + ) + if result.modified_count > 0: + print("Documents updated successfully.") + return "Documents updated successfully." + else: + print("No document found with the given query.") + return "No document found with the given query." + except Exception as e: + print(f"Error occurred during update_many operation: {e}") + return f"Error occurred during update_many operation: {e}" + + async def replace_one_document(self, query, replace_data): + try: + result = await self.collection.replace_one(query, replace_data) + if result.modified_count > 0: + print("Document replaced successfully.") + return "Document replaced successfully." + else: + print("No document found with the given query.") + return "No document found with the given query." + except Exception as e: + print(f"Error occurred during replace_one operation: {e}") + return f"Error occurred during replace_one operation: {e}" + + ##################################### + # Delete: # + ##################################### + class DeleteManager: + def __init__(self, collection): + self.collection = collection + + async def delete_document(self, query): + try: + result = await self.collection.delete_one(query) + if result.deleted_count > 0: + print("Document deleted successfully.") + return "Document deleted successfully." + else: + print("No document found with the given query.") + return "No document found with the given query." + except Exception as e: + print(f"Error occurred during delete_document operation: {e}") + return f"Error occurred during delete_document operation: {e}" + + async def delete_many_documents(self, query): + try: + result = await self.collection.delete_many(query) + if result.deleted_count > 0: + print("Documents deleted successfully.") + return "Documents deleted successfully." + else: + print("No documents found with the given query.") + return "No documents found with the given query." + except Exception as e: + print(f"Error occurred during delete_many operation: {e}") + return f"Error occurred during delete_many operation: {e}" + + async def close_connection(self): + try: + self.client.close() + return "Connection closed." + except Exception as e: + return f"Error occurred during connection closing: {e}" + + +async def example_usage(): + db_url = "mongodb://localhost:27017/" + db_name = "test_database" + collection_name = "test_collection" + + manager = MongoDBManager(db_url, db_name, collection_name) + + query = {"age": {"$gt": 25}} + + manager.read_manager.find_many(query) + + +# Running the asyncio event loop +if __name__ == "__main__": + asyncio.run(example_usage()) diff --git a/backend/core/db/redis.py b/backend/core/db/redis.py new file mode 100644 index 0000000..ceb2183 --- /dev/null +++ b/backend/core/db/redis.py @@ -0,0 +1,55 @@ +import aioredis + + +class AsyncRedisManager: + def __init__(self, host, port, db): + self.redis_client = None + self.host = host + self.port = port + self.db = db + + async def connect(self): + try: + self.redis_client = await aioredis.create_redis_pool( + f"redis://{self.host}:{self.port}/{self.db}" + ) + except aioredis.RedisError as e: + print(f"Error connecting to Redis: {e}") + return e + return True + + async def close(self): + try: + self.redis_client.close() + await self.redis_client.wait_closed() + except aioredis.RedisError as e: + print(f"Error closing Redis connection: {e}") + return e + return True + + async def set(self, key, value, expiration=None): + try: + if expiration: + await self.redis_client.setex(key, expiration, value) + else: + await self.redis_client.set(key, value) + except aioredis.RedisError as e: + print(f"Error setting value in Redis: {e}") + return e + return True + + async def get(self, key): + try: + result = await self.redis_client.get(key) + return result + except aioredis.RedisError as e: + print(f"Error getting value from Redis: {e}") + return e + + async def delete(self, key): + try: + await self.redis_client.delete(key) + except aioredis.RedisError as e: + print(f"Error deleting key from Redis: {e}") + return e + return True diff --git a/backend/core/hashing.py b/backend/core/hashing.py new file mode 100644 index 0000000..f612582 --- /dev/null +++ b/backend/core/hashing.py @@ -0,0 +1,192 @@ +""" +Password Hashing and Verification Module + +This module provides classes and methods for\ + password hashing and verification using +Bcrypt and Argon2 hashing algorithms.\ + It also includes a Hasher class for handling +password hashing and verification using various algorithms. + +Author: Arteii +Date: 24/10/2023 +""" + +import argon2 +import bcrypt +from core.config import config + +argon_config = config.PasswordConfig.ArgonHashingAlgorithm +bcrypt_config = config.PasswordConfig.BcryptHashingAlgorithm + + +class Bcrypt: + """ + A class for handling password hashing and verification using Bcrypt. + """ + + @staticmethod + def verify_password(stored_hash, password): + """ + Verify a password against a stored Bcrypt hash. + + Args: + stored_hash (bytes): The stored Bcrypt hash. + password (str): The password to be verified. + + Returns: + bool: True if the password is valid, False otherwise. + """ + return bcrypt.checkpw( + password.encode(bcrypt_config.ENCODING), + stored_hash, + ) + + @staticmethod + def get_password_hash(password): + """ + Hash a password using Bcrypt. + + Args: + password (str): The password to be hashed. + + Returns: + bytes: The Bcrypt hash of the password. + """ + salt = bcrypt.gensalt(rounds=bcrypt_config.ROUNDS) + hashed_password = bcrypt.hashpw( + password.encode(bcrypt_config.ENCODING), + salt, + ) + return hashed_password + + +class Argon: + """ + A class for handling password hashing and verification using Argon2. + """ + + @staticmethod + def verify_password(password, stored_hash): + """ + Verify a password against a stored Argon2 hash. + + Args: + password (str): The password to be verified. + stored_hash (str): The stored Argon2 hash. + + Returns: + bool: True if the password is valid, False otherwise. + """ + try: + hasher = argon2.PasswordHasher( + time_cost=argon_config.TIME_COST, + memory_cost=argon_config.MEMORY_COST, + parallelism=argon_config.PARALLELISM, + hash_len=argon_config.HASH_LEN, + salt_len=argon_config.SALT_LEN, + encoding=argon_config.ENCODING, + ) + return hasher.verify(stored_hash, password) + except argon2.exceptions.VerifyMismatchError: + return False + + @staticmethod + def get_password_hash(password): + """ + Hash a password using Argon2. + + Args: + password (str): The password to be hashed. + + Returns: + str: The Argon2 hash of the password. + """ + hasher = argon2.PasswordHasher( + time_cost=argon_config.TIME_COST, + memory_cost=argon_config.MEMORY_COST, + parallelism=argon_config.PARALLELISM, + hash_len=argon_config.HASH_LEN, + salt_len=argon_config.SALT_LEN, + encoding=argon_config.ENCODING, + ) + return hasher.hash(password) + + +class Hasher: + """ + A class for handling password hashing and + verification using various algorithms. + """ + + @staticmethod + def verify_password( + password, + stored_hash, + algorithm=config.PasswordConfig.HASHING_ALGORITHM, + ): + """ + Verify a password against a stored hash\ + using the specified hashing algorithm. + + Args: + password (str): The password to be verified. + stored_hash (str): The stored hash. + algorithm (str): The hashing algorithm to use\ + (either "bcrypt" or "argon2"). + + Returns: + bool: True if the password is valid, False otherwise. + """ + if algorithm == "bcrypt": + return Bcrypt.verify_password(stored_hash, password) + elif algorithm == "argon2": + return Argon.verify_password(password, stored_hash) + else: + raise ValueError("Invalid hashing algorithm") + + @staticmethod + def get_password_hash( + password, + algorithm=config.PasswordConfig.HASHING_ALGORITHM, + ): + """ + Hash a password using the specified hashing algorithm. + + Args: + password (str): The password to be hashed. + algorithm (str): The hashing algorithm to use\ + (either "bcrypt" or "argon2"). + + Returns: + str: The hash of the password. + """ + if algorithm == "bcrypt": + return Bcrypt.get_password_hash(password) + elif algorithm == "argon2": + return Argon.get_password_hash(password) + else: + raise ValueError("Invalid hashing algorithm") + + +if __name__ == "__main__": + if input("compare hash with pw?").lower().startswith("y"): + password = input("enter password:") + hash = input("enter hash:") + if input("use Bcrypt?").lower().startswith("y"): + print( + Hasher.verify_password( + password=password, stored_hash=hash, algorithm="bcrypt" + ) + ) + + print( + Hasher.verify_password( + password=password, stored_hash=hash, algorithm="argon2" + ) + ) + + password = input("enter password:") + if input("use Bcrypt?").lower().startswith("y"): + print(Hasher.get_password_hash(password=password, algorithm="bcrypt")) + + print(Hasher.get_password_hash(password=password, algorithm="argon2")) diff --git a/backend/core/password_validation.py b/backend/core/password_validation.py new file mode 100644 index 0000000..7c5df2a --- /dev/null +++ b/backend/core/password_validation.py @@ -0,0 +1,60 @@ +""" +Password Complexity Validation Module + +This module provides a function to validate the complexity of a password based\ + on specific requirements, such as minimum length, uppercase letters,\ + lowercase letters, digits, and special characters. + +Author: Arteii +Date: 24/10/2023 + +Usage: + from validate_password_complexity import validate_password_complexity + + try: + validated_password = validate_password_complexity("Passw0rd!") + print(f"Valid password: {validated_password}") + except ValueError as e: + print(f"Invalid password: {e}") + +The `validate_password_complexity` function checks if a given password meets\ +the specified complexity requirements. In this example,\ + the requirements include: +- A minimum length of 8 characters. +- At least one uppercase letter. +- At least one lowercase letter. +- At least one digit. +- At least one special character from the set: @, $, !, %, *, ?, &. + +Example: + Valid: "Passw0rd!", "S@feP@ss" + Invalid: "12345678", "Password", "special!" +""" + + +import re + + +def validate_password_complexity(password: str): + # Define your complexity requirements using regular expressions + # In this example, the password must have at least one uppercase letter, + # one lowercase letter, one digit, and one special character. + + if len(password) < 8: + raise ValueError("Password must be at least 8 characters long.") + + if not re.search(r"[A-Z]", password): + raise ValueError("Password must contain at least one uppercase letter.") + + if not re.search(r"[a-z]", password): + raise ValueError("Password must contain at least one lowercase letter.") + + if not re.search(r"\d", password): + raise ValueError("Password must contain at least one digit.") + + if not re.search(r"[@$!%*?&]", password): + raise ValueError( + "Password must contain at least one special character (@$!%*?&)." + ) + + return password diff --git a/backend/core/username_validation.py b/backend/core/username_validation.py new file mode 100644 index 0000000..c018fcb --- /dev/null +++ b/backend/core/username_validation.py @@ -0,0 +1,54 @@ +""" +Username Validation Module + +This module provides a function to validate the format of a username\ + based on a regular expression pattern. + +Author: Arteii +Date: 24/10/2023 + +Usage: + from validate_username import validate_username + + try: + validated_username = validate_username("abc#1234") + print(f"Valid username: {validated_username}") + except ValueError as e: + print(f"Invalid username: {e}") + +The `validate_username` function checks if a given username matches the\ + specified format: three or more letters followed by a '#' character and\ + four numbers (e.g., abc#1234). + +Example: + Valid: "abc#1234", "xyz#5678" + Invalid: "abc1234", "12#abcd", "abc#12345" + +""" + +import re + + +def validate_username(value: str): + """ + Validate the username format using a regular expression. + + Args: + value (str): The username to validate. + + Returns: + str: The validated username. + + Raises: + ValueError: If the username format is invalid. + """ + # Define the regular expression pattern for the username + pattern = r"^[a-zA-Z]{3,}#[0-9]{4}$" + + if not re.match(pattern, value): + raise ValueError( + "Invalid username format. Username must consist of 3 letters,\ + a '#' character, and 4 numbers. (abc#1234)" + ) + + return value diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..d28b3f9db810c53583697fe4af589b7c2d104aae GIT binary patch literal 1734 zcmbtV%Wi`}44iYN{sXt3NCVAFef>(61d<{pAp#UNKi_u7%SN<^R=d$^L4bE`kH_Zg zGew02@94OWXz_@9t|QLe6-KDhV9PfxpZ0vWLy245V2J})%~MP65(QG;b*iN83)h&Z zXWU~v(Sr~>`l)&94JTSPhNj(c=sO8^jaI5zRbC^K(zS5tmMpK#2CVPne~~h7=2?*cNYJ|u)>+~ctb7}TXt2| z3g+7WsJ67Ad=+ONSUqVWF|8Hc--_KZ2`OO4N^@2mMXNxBF9w?L|cN Go{w*mzYcN$ literal 0 HcmV?d00001