Feat(API): Added rate-limiting and json error handling

This commit is contained in:
minhtrannhat 2022-11-19 15:13:38 -05:00
parent 7815894d9f
commit 10b9f90524
No known key found for this signature in database
GPG Key ID: 894C6A5801E01CA9
8 changed files with 169 additions and 3 deletions

View File

@ -10,13 +10,19 @@
### Development dependencies ### Development dependencies
#### Python Dev-deps #### Python dependencies
- `quart`: a micro-webframework, async version of Flask.
- `black`: Code formatter - `black`: Code formatter
- `isort`: Import formatter - `isort`: Import formatter
- `mypy`: Type checking - `mypy`: Type checking
- `flake8`: General Python bugs - `flake8`: General Python bugs
- `vulture`: Find unused code in Python programs - `vulture`: Find unused code in Python programs
- `pytest`: For testing (turbocharged with `async`)
- `bcrypt`: Hashing and salting password.
- `zxcvbn`: Test password strength.
- `freezegun`: Check for expired token.
- `quart-rate-limiter`: rate limiting
#### SQL Dev-deps #### SQL Dev-deps

View File

@ -1,3 +1,7 @@
TODO_BASE_URL="localhost:5050" TODO_BASE_URL="localhost:5050"
TODO_DEBUG=true TODO_DEBUG=true
TODO_SECRET_KEY="secret key" TODO_SECRET_KEY="secret key"
# disable for when we not using HTTPS
TODO_QUART_AUTH_COOKIE_SECURE=false
TODO_QUART_AUTH_COOKIE_SAMESITE="Strict"

93
backend/pdm.lock generated
View File

@ -22,6 +22,12 @@ dependencies = [
"stevedore>=1.20.0", "stevedore>=1.20.0",
] ]
[[package]]
name = "bcrypt"
version = "4.0.1"
requires_python = ">=3.6"
summary = "Modern password hashing for your software and your servers"
[[package]] [[package]]
name = "black" name = "black"
version = "22.10.0" version = "22.10.0"
@ -78,6 +84,15 @@ dependencies = [
"pyflakes<2.6.0,>=2.5.0", "pyflakes<2.6.0,>=2.5.0",
] ]
[[package]]
name = "freezegun"
version = "1.2.2"
requires_python = ">=3.6"
summary = "Let your Python tests travel through time"
dependencies = [
"python-dateutil>=2.7",
]
[[package]] [[package]]
name = "gitdb" name = "gitdb"
version = "4.0.9" version = "4.0.9"
@ -272,6 +287,15 @@ dependencies = [
"pytest>=6.1.0", "pytest>=6.1.0",
] ]
[[package]]
name = "python-dateutil"
version = "2.8.2"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
summary = "Extensions to the standard Python datetime module"
dependencies = [
"six>=1.5",
]
[[package]] [[package]]
name = "pyyaml" name = "pyyaml"
version = "6.0" version = "6.0"
@ -294,6 +318,30 @@ dependencies = [
"werkzeug>=2.2.0", "werkzeug>=2.2.0",
] ]
[[package]]
name = "quart-auth"
version = "0.7.0"
requires_python = ">=3.7"
summary = "A Quart extension to provide secure cookie authentication"
dependencies = [
"quart>=0.18",
]
[[package]]
name = "quart-rate-limiter"
version = "0.7.0"
requires_python = ">=3.7"
summary = "A Quart extension to provide rate limiting support"
dependencies = [
"quart>=0.15",
]
[[package]]
name = "six"
version = "1.16.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
summary = "Python 2 and 3 compatibility utilities"
[[package]] [[package]]
name = "smmap" name = "smmap"
version = "5.0.0" version = "5.0.0"
@ -356,7 +404,7 @@ dependencies = [
[metadata] [metadata]
lock_version = "4.0" lock_version = "4.0"
content_hash = "sha256:f0267acbf584dfd2411f20fc3f63a5d42aa0bc85b37355aa528c0df8dda99498" content_hash = "sha256:827a57e737ffa5147e1957e97f693d911838395691fd9fec9c2d93387e08cfb5"
[metadata.files] [metadata.files]
"aiofiles 22.1.0" = [ "aiofiles 22.1.0" = [
@ -371,6 +419,29 @@ content_hash = "sha256:f0267acbf584dfd2411f20fc3f63a5d42aa0bc85b37355aa528c0df8d
{url = "https://files.pythonhosted.org/packages/39/36/a37a2f6f8d0ed8c3bc616616ed5019e1df2680bd8b7df49ceae80fd457de/bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"}, {url = "https://files.pythonhosted.org/packages/39/36/a37a2f6f8d0ed8c3bc616616ed5019e1df2680bd8b7df49ceae80fd457de/bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"},
{url = "https://files.pythonhosted.org/packages/da/eb/ff828f4ec32c85e10d9c344e6b7f11bcacfb5d70f2fd16bea6fc1ae6df06/bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"}, {url = "https://files.pythonhosted.org/packages/da/eb/ff828f4ec32c85e10d9c344e6b7f11bcacfb5d70f2fd16bea6fc1ae6df06/bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"},
] ]
"bcrypt 4.0.1" = [
{url = "https://files.pythonhosted.org/packages/13/68/f3184c1f15581ebd936125b4da04cba0995f97ecd5ee8f4262c8ebba2646/bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"},
{url = "https://files.pythonhosted.org/packages/28/ed/3c443bfbfdb37cd7c0d055b961311f49049ab4a00f45ba3bfd10d33a9443/bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"},
{url = "https://files.pythonhosted.org/packages/2c/be/376341b47e1e3fc424c9df1af60b5aedbd5ab04f73ccdf4107e42d92ef09/bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"},
{url = "https://files.pythonhosted.org/packages/41/16/49ff5146fb815742ad58cafb5034907aa7f166b1344d0ddd7fd1c818bd17/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"},
{url = "https://files.pythonhosted.org/packages/41/86/05248719aa42a4fe1ca379d45794198700e992b91d389bfaa69533fc3331/bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"},
{url = "https://files.pythonhosted.org/packages/46/81/d8c22cd7e5e1c6a7d48e41a1d1d46c92f17dae70a54d9814f746e6027dec/bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"},
{url = "https://files.pythonhosted.org/packages/5e/01/098b798dc6c6984f2d5026269e80d7cad22d6ecacd5989bdf35a9c99a03d/bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"},
{url = "https://files.pythonhosted.org/packages/64/fe/da28a5916128d541da0993328dc5cf4b43dfbf6655f2c7a2abe26ca2dc88/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"},
{url = "https://files.pythonhosted.org/packages/77/2c/53c17079898584306eafdc937e0c7cc1bf8e2fe17e9909716ef3f9d6555d/bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"},
{url = "https://files.pythonhosted.org/packages/78/d4/3b2657bd58ef02b23a07729b0df26f21af97169dbd0b5797afa9e97ebb49/bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"},
{url = "https://files.pythonhosted.org/packages/7d/50/e683d8418974a602ba40899c8a5c38b3decaf5a4d36c32fc65dce454d8a8/bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"},
{url = "https://files.pythonhosted.org/packages/87/69/edacb37481d360d06fc947dab5734aaf511acb7d1a1f9e2849454376c0f8/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"},
{url = "https://files.pythonhosted.org/packages/8c/ae/3af7d006aacf513975fd1948a6b4d6f8b4a307f8a244e1a3d3774b297aad/bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"},
{url = "https://files.pythonhosted.org/packages/99/a5/ff4aaf2adbefb2c9808d49cec37f65e0572c4ce856b13b194fd87a6cbd14/bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"},
{url = "https://files.pythonhosted.org/packages/aa/48/fd2b197a9741fa790ba0b88a9b10b5e88e62ff5cf3e1bc96d8354d7ce613/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"},
{url = "https://files.pythonhosted.org/packages/aa/ca/6a534669890725cbb8c1fb4622019be31813c8edaa7b6d5b62fc9360a17e/bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"},
{url = "https://files.pythonhosted.org/packages/d8/f6/43ade4d37a3319baee9aec53f636411e70c18f0e4add9cc44a18f517af5f/bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"},
{url = "https://files.pythonhosted.org/packages/dd/4f/3632a69ce344c1551f7c9803196b191a8181c6a1ad2362c225581ef0d383/bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"},
{url = "https://files.pythonhosted.org/packages/ec/0a/1582790232fef6c2aa201f345577306b8bfe465c2c665dec04c86a016879/bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"},
{url = "https://files.pythonhosted.org/packages/fb/4b/e255df2000c2de4df524740b5f1d0a31157a1f7715b3eaf2e8f9c5c0acbb/bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"},
{url = "https://files.pythonhosted.org/packages/fb/a7/ee4561fd9b78ca23c8e5591c150cc58626a5dfb169345ab18e1c2c664ee0/bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"},
]
"black 22.10.0" = [ "black 22.10.0" = [
{url = "https://files.pythonhosted.org/packages/2c/11/f2737cd3b458d91401801e83a014e87c63e8904dc063200f77826c352f54/black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, {url = "https://files.pythonhosted.org/packages/2c/11/f2737cd3b458d91401801e83a014e87c63e8904dc063200f77826c352f54/black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
{url = "https://files.pythonhosted.org/packages/3d/c5/b3ab9b563f35fb284d37ab2b14acaed9a27d8cdea9c31364766eb54946a7/black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, {url = "https://files.pythonhosted.org/packages/3d/c5/b3ab9b563f35fb284d37ab2b14acaed9a27d8cdea9c31364766eb54946a7/black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
@ -417,6 +488,10 @@ content_hash = "sha256:f0267acbf584dfd2411f20fc3f63a5d42aa0bc85b37355aa528c0df8d
{url = "https://files.pythonhosted.org/packages/ad/00/9808c62b2d529cefc69ce4e4a1ea42c0f855effa55817b7327ec5b75e60a/flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, {url = "https://files.pythonhosted.org/packages/ad/00/9808c62b2d529cefc69ce4e4a1ea42c0f855effa55817b7327ec5b75e60a/flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
{url = "https://files.pythonhosted.org/packages/cf/a0/b881b63a17a59d9d07f5c0cc91a29182c8e8a9aa2bde5b3b2b16519c02f4/flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, {url = "https://files.pythonhosted.org/packages/cf/a0/b881b63a17a59d9d07f5c0cc91a29182c8e8a9aa2bde5b3b2b16519c02f4/flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
] ]
"freezegun 1.2.2" = [
{url = "https://files.pythonhosted.org/packages/1d/97/002ac49ec52858538b4aa6f6831f83c2af562c17340bdf6043be695f39ac/freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"},
{url = "https://files.pythonhosted.org/packages/50/cd/ba1c8319c002727ccfa03049127218d1767232a77219924d03ba170e0601/freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"},
]
"gitdb 4.0.9" = [ "gitdb 4.0.9" = [
{url = "https://files.pythonhosted.org/packages/a3/7c/5d747655049bfbf75b5fcec57c8115896cb78d6fafa84f6d3ef4c0f13a98/gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, {url = "https://files.pythonhosted.org/packages/a3/7c/5d747655049bfbf75b5fcec57c8115896cb78d6fafa84f6d3ef4c0f13a98/gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"},
{url = "https://files.pythonhosted.org/packages/fc/44/64e02ef96f20b347385f0e9c03098659cb5a1285d36c3d17c56e534d80cf/gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, {url = "https://files.pythonhosted.org/packages/fc/44/64e02ef96f20b347385f0e9c03098659cb5a1285d36c3d17c56e534d80cf/gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"},
@ -587,6 +662,10 @@ content_hash = "sha256:f0267acbf584dfd2411f20fc3f63a5d42aa0bc85b37355aa528c0df8d
{url = "https://files.pythonhosted.org/packages/65/57/fb0d22ee0d9cd8d8b277cdf4fd7efa3e245c9577d2c2d54c6c2c18b535a2/pytest_asyncio-0.20.2-py3-none-any.whl", hash = "sha256:07e0abf9e6e6b95894a39f688a4a875d63c2128f76c02d03d16ccbc35bcc0f8a"}, {url = "https://files.pythonhosted.org/packages/65/57/fb0d22ee0d9cd8d8b277cdf4fd7efa3e245c9577d2c2d54c6c2c18b535a2/pytest_asyncio-0.20.2-py3-none-any.whl", hash = "sha256:07e0abf9e6e6b95894a39f688a4a875d63c2128f76c02d03d16ccbc35bcc0f8a"},
{url = "https://files.pythonhosted.org/packages/eb/db/479f1c43df26c42c9797dfc866dc98bcf28d3765ae49bf2da681ab344b72/pytest-asyncio-0.20.2.tar.gz", hash = "sha256:32a87a9836298a881c0ec637ebcc952cfe23a56436bdc0d09d1511941dd8a812"}, {url = "https://files.pythonhosted.org/packages/eb/db/479f1c43df26c42c9797dfc866dc98bcf28d3765ae49bf2da681ab344b72/pytest-asyncio-0.20.2.tar.gz", hash = "sha256:32a87a9836298a881c0ec637ebcc952cfe23a56436bdc0d09d1511941dd8a812"},
] ]
"python-dateutil 2.8.2" = [
{url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
{url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
]
"pyyaml 6.0" = [ "pyyaml 6.0" = [
{url = "https://files.pythonhosted.org/packages/02/25/6ba9f6bb50a3d4fbe22c1a02554dc670682a07c8701d1716d19ddea2c940/PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {url = "https://files.pythonhosted.org/packages/02/25/6ba9f6bb50a3d4fbe22c1a02554dc670682a07c8701d1716d19ddea2c940/PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{url = "https://files.pythonhosted.org/packages/08/f4/ffa743f860f34a5e8c60abaaa686f82c9ac7a2b50e5a1c3b1eb564d59159/PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {url = "https://files.pythonhosted.org/packages/08/f4/ffa743f860f34a5e8c60abaaa686f82c9ac7a2b50e5a1c3b1eb564d59159/PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
@ -633,6 +712,18 @@ content_hash = "sha256:f0267acbf584dfd2411f20fc3f63a5d42aa0bc85b37355aa528c0df8d
{url = "https://files.pythonhosted.org/packages/3d/3b/4fd974fef00ad26d9b078700e932776126528eb384aaa1e20a0867af53e5/Quart-0.18.3.tar.gz", hash = "sha256:dc4de597d5d4693627c90904b233d729531f6b27c1164f760476d3967aee2a4a"}, {url = "https://files.pythonhosted.org/packages/3d/3b/4fd974fef00ad26d9b078700e932776126528eb384aaa1e20a0867af53e5/Quart-0.18.3.tar.gz", hash = "sha256:dc4de597d5d4693627c90904b233d729531f6b27c1164f760476d3967aee2a4a"},
{url = "https://files.pythonhosted.org/packages/5c/05/64e318eaf6dbeb8db79402cb71d6ecda676c9510f382b9243efe67b9ba50/Quart-0.18.3-py3-none-any.whl", hash = "sha256:c221a7deb83a014dee040108d654b6141fe37f59e249c5caa0fdcf6506caf50b"}, {url = "https://files.pythonhosted.org/packages/5c/05/64e318eaf6dbeb8db79402cb71d6ecda676c9510f382b9243efe67b9ba50/Quart-0.18.3-py3-none-any.whl", hash = "sha256:c221a7deb83a014dee040108d654b6141fe37f59e249c5caa0fdcf6506caf50b"},
] ]
"quart-auth 0.7.0" = [
{url = "https://files.pythonhosted.org/packages/5b/50/d98d5ba030f87bc73e55973ad90e8ea61dbbdd252715e03b8395c5a374b3/quart_auth-0.7.0-py3-none-any.whl", hash = "sha256:7729fccded2d821179fb3005116672d129ce17aa3f248ad8991587132b323a35"},
{url = "https://files.pythonhosted.org/packages/d6/9b/5da686100b031b549e1bc98019888cd1e95b8bee26d7efcd864e3fd537b1/quart-auth-0.7.0.tar.gz", hash = "sha256:e288f43789f694981e3695325cfe565361fdad252c5e1a663c5ca6ea41405a05"},
]
"quart-rate-limiter 0.7.0" = [
{url = "https://files.pythonhosted.org/packages/49/1d/c5567a0128e84dec57bf283eb9708b8e0c91ca80488e4652ae82b3015348/quart_rate_limiter-0.7.0-py3-none-any.whl", hash = "sha256:7d8d0a72051cf60368810364676c151128f77120274d60e8df4e9c94419b4ac4"},
{url = "https://files.pythonhosted.org/packages/ab/90/55e48a9f0ce1628db103161351d65feeb15287b81ca570db66a4e6f4c0c5/quart-rate-limiter-0.7.0.tar.gz", hash = "sha256:e4cec1a0d476cf7fe2815e722d884e2cee8365bdd08f55480ed190ae07075a5d"},
]
"six 1.16.0" = [
{url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
{url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
]
"smmap 5.0.0" = [ "smmap 5.0.0" = [
{url = "https://files.pythonhosted.org/packages/21/2d/39c6c57032f786f1965022563eec60623bb3e1409ade6ad834ff703724f3/smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, {url = "https://files.pythonhosted.org/packages/21/2d/39c6c57032f786f1965022563eec60623bb3e1409ade6ad834ff703724f3/smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
{url = "https://files.pythonhosted.org/packages/6d/01/7caa71608bc29952ae09b0be63a539e50d2484bc37747797a66a60679856/smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, {url = "https://files.pythonhosted.org/packages/6d/01/7caa71608bc29952ae09b0be63a539e50d2484bc37747797a66a60679856/smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},

View File

@ -9,6 +9,10 @@ requires-python = ">=3.10"
license = {text = "Private"} license = {text = "Private"}
dependencies = [ dependencies = [
"quart>=0.18.3", "quart>=0.18.3",
"quart-auth>=0.7.0",
"bcrypt>=4.0.1",
"itsdangerous>=2.1.2",
"quart-rate-limiter>=0.7.0",
] ]
[project.optional-dependencies] [project.optional-dependencies]
@ -28,6 +32,7 @@ dev = [
"pytest>=7.1.2", "pytest>=7.1.2",
"pytest-asyncio>=0.19.0", "pytest-asyncio>=0.19.0",
"djhtml>=1.5.2", "djhtml>=1.5.2",
"freezegun>=1.2.2",
] ]
[tool.black] [tool.black]

View File

@ -1,9 +1,11 @@
from quart import Blueprint, ResponseReturnValue from quart import Blueprint, ResponseReturnValue
from quart_rate_limiter import rate_exempt
blueprint = Blueprint("control", __name__) blueprint = Blueprint("control", __name__)
@blueprint.get("/control/ping") @blueprint.get("/control/ping")
@rate_exempt
async def ping() -> ResponseReturnValue: async def ping() -> ResponseReturnValue:
return {"ping": "pong"} return {"ping": "pong"}

View File

@ -1,16 +1,41 @@
from quart import Quart, ResponseReturnValue from quart import Quart, ResponseReturnValue
# Each blueprint is a logical collection of features in our web app
from backend.blueprints.control import blueprint as control_blueprint from backend.blueprints.control import blueprint as control_blueprint
# For making sure error responses are in JSON format
from backend.lib.api_error import APIError from backend.lib.api_error import APIError
# Rate limiting
from quart_rate_limiter import RateLimiter
from quart_rate_limiter import RateLimitExceeded
from datetime import timedelta
app = Quart(__name__) # Authentication
from quart_auth import AuthManager
app: Quart = Quart(__name__)
auth_manager: AuthManager = AuthManager(app)
rate_limiter: RateLimiter = RateLimiter(app)
# Configure the web app
# Either in DEV/DEBUG mode or TEST mode
app.config.from_prefixed_env(prefix="TODO") app.config.from_prefixed_env(prefix="TODO")
app.register_blueprint(control_blueprint) app.register_blueprint(control_blueprint)
# rate limiting
@app.errorhandler(RateLimitExceeded) # type: ignore
async def handle_rate_limit_exceeded_error(
error: RateLimitExceeded,
) -> ResponseReturnValue:
return {}, error.get_headers(), 429
# handles errors
@app.errorhandler(APIError) # type: ignore @app.errorhandler(APIError) # type: ignore
async def handle_api_error(error: APIError) -> ResponseReturnValue: async def handle_api_error(error: APIError) -> ResponseReturnValue:
return {"code": error.code}, error.status_code return {"code": error.code}, error.status_code

View File

@ -2,3 +2,7 @@ TODO_BASE_URL="localhost:5050"
TODO_DEBUG=true TODO_DEBUG=true
TODO_SECRET_KEY="secret key" TODO_SECRET_KEY="secret key"
TODO_TESTING=true TODO_TESTING=true
# disable for when we not using HTTPS
TODO_QUART_AUTH_COOKIE_SECURE=false
TODO_QUART_AUTH_COOKIE_SAMESITE="Strict"

View File

@ -0,0 +1,29 @@
from quart_rate_limiter import (
QUART_RATE_LIMITER_EXEMPT_ATTRIBUTE,
QUART_RATE_LIMITER_LIMITS_ATTRIBUTE,
)
from backend.run import app
IGNORED_ENDPOINTS = {"static"}
# Check if all api routes are rate limited
async def test_routes_have_rate_limits() -> None:
for rule in app.url_map.iter_rules():
endpoint = rule.endpoint
exempt = getattr(
app.view_functions[endpoint],
QUART_RATE_LIMITER_EXEMPT_ATTRIBUTE,
False,
)
if not exempt and endpoint not in IGNORED_ENDPOINTS:
rate_limits = getattr(
app.view_functions[endpoint],
QUART_RATE_LIMITER_LIMITS_ATTRIBUTE,
[],
)
assert rate_limits != []