From 21a7f2255cc8dcba735f61693d8b336b661f07c4 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 7 Oct 2021 11:01:20 -0400
Subject: [PATCH 01/26] Add Authlib package dependency
---
requirements/dev.txt | 129 ++++++++++++++++++------------------------
requirements/main.in | 1 +
requirements/main.txt | 89 +++++++++++++++++++++++++++--
3 files changed, 140 insertions(+), 79 deletions(-)
diff --git a/requirements/dev.txt b/requirements/dev.txt
index c117a40..6eab160 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -4,9 +4,9 @@
#
# pip-compile --generate-hashes --output-file=requirements/dev.txt requirements/dev.in
#
-anyio==3.3.1 \
- --hash=sha256:85913b4e2fec030e8c72a8f9f98092eeb9e25847a6e00d567751b77e34f856fe \
- --hash=sha256:d7c604dd491eca70e19c78664d685d5e4337612d574419d503e76f5d7d1590bd
+anyio==3.3.2 \
+ --hash=sha256:0b993a2ef6c1dc456815c2b5ca2819f382f20af98087cc2090a4afed3a501436 \
+ --hash=sha256:c32da314c510b34a862f5afeaf8a446ffed2c2fde21583e654bd71ecfb5b744b
# via
# -c requirements/main.txt
# httpcore
@@ -50,59 +50,40 @@ click==8.0.1 \
# via
# -c requirements/main.txt
# uvicorn
-coverage[toml]==5.5 \
- --hash=sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c \
- --hash=sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6 \
- --hash=sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45 \
- --hash=sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a \
- --hash=sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03 \
- --hash=sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529 \
- --hash=sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a \
- --hash=sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a \
- --hash=sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2 \
- --hash=sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6 \
- --hash=sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759 \
- --hash=sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53 \
- --hash=sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a \
- --hash=sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4 \
- --hash=sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff \
- --hash=sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502 \
- --hash=sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793 \
- --hash=sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb \
- --hash=sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905 \
- --hash=sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821 \
- --hash=sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b \
- --hash=sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81 \
- --hash=sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0 \
- --hash=sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b \
- --hash=sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3 \
- --hash=sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184 \
- --hash=sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701 \
- --hash=sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a \
- --hash=sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82 \
- --hash=sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638 \
- --hash=sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5 \
- --hash=sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083 \
- --hash=sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6 \
- --hash=sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90 \
- --hash=sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465 \
- --hash=sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a \
- --hash=sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3 \
- --hash=sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e \
- --hash=sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066 \
- --hash=sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf \
- --hash=sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b \
- --hash=sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae \
- --hash=sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669 \
- --hash=sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873 \
- --hash=sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b \
- --hash=sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6 \
- --hash=sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb \
- --hash=sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160 \
- --hash=sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c \
- --hash=sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079 \
- --hash=sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d \
- --hash=sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6
+coverage[toml]==6.0.1 \
+ --hash=sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26 \
+ --hash=sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666 \
+ --hash=sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee \
+ --hash=sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345 \
+ --hash=sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5 \
+ --hash=sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad \
+ --hash=sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f \
+ --hash=sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854 \
+ --hash=sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32 \
+ --hash=sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7 \
+ --hash=sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12 \
+ --hash=sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec \
+ --hash=sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc \
+ --hash=sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5 \
+ --hash=sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724 \
+ --hash=sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7 \
+ --hash=sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d \
+ --hash=sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194 \
+ --hash=sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e \
+ --hash=sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a \
+ --hash=sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b \
+ --hash=sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a \
+ --hash=sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e \
+ --hash=sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22 \
+ --hash=sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b \
+ --hash=sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793 \
+ --hash=sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397 \
+ --hash=sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815 \
+ --hash=sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827 \
+ --hash=sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb \
+ --hash=sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43 \
+ --hash=sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf \
+ --hash=sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16
# via
# -r requirements/dev.in
# pytest-cov
@@ -110,9 +91,9 @@ distlib==0.3.3 \
--hash=sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31 \
--hash=sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05
# via virtualenv
-filelock==3.0.12 \
- --hash=sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59 \
- --hash=sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836
+filelock==3.3.0 \
+ --hash=sha256:8c7eab13dc442dc249e95158bcc12dec724465919bdc9831fdbf0660f03d1785 \
+ --hash=sha256:bbc6a0382fe8ec4744ecdf6683a2e07f65eb10ff1aff53fc02a202565446cde0
# via virtualenv
h11==0.12.0 \
--hash=sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6 \
@@ -152,9 +133,9 @@ httpx==0.19.0 \
# via
# -c requirements/main.txt
# -r requirements/dev.in
-identify==2.2.15 \
- --hash=sha256:528a88021749035d5a39fe2ba67c0642b8341aaf71889da0e1ed669a429b87f0 \
- --hash=sha256:de83a84d774921669774a2000bf87ebba46b4d1c04775f4a5d37deff0cf39f73
+identify==2.3.0 \
+ --hash=sha256:d1e82c83d063571bb88087676f81261a4eae913c492dafde184067c584bc7c05 \
+ --hash=sha256:fd08c97f23ceee72784081f1ce5125c8f53a02d3f2716dde79a6ab8f1039fea5
# via pre-commit
idna==3.2 \
--hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \
@@ -204,9 +185,9 @@ packaging==21.0 \
--hash=sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7 \
--hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14
# via pytest
-platformdirs==2.3.0 \
- --hash=sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f \
- --hash=sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648
+platformdirs==2.4.0 \
+ --hash=sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2 \
+ --hash=sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d
# via virtualenv
pluggy==1.0.0 \
--hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \
@@ -235,9 +216,9 @@ pytest-asyncio==0.15.1 \
--hash=sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f \
--hash=sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea
# via -r requirements/dev.in
-pytest-cov==2.12.1 \
- --hash=sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a \
- --hash=sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7
+pytest-cov==3.0.0 \
+ --hash=sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6 \
+ --hash=sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470
# via -r requirements/dev.in
python-dotenv==0.19.0 \
--hash=sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1 \
@@ -302,11 +283,13 @@ toml==0.10.2 \
--hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
--hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
# via
- # coverage
# mypy
# pre-commit
# pytest
- # pytest-cov
+tomli==1.2.1 \
+ --hash=sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f \
+ --hash=sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442
+ # via coverage
typing-extensions==3.10.0.2 \
--hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
--hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \
@@ -340,9 +323,9 @@ uvloop==0.16.0 \
# via
# -c requirements/main.txt
# uvicorn
-virtualenv==20.8.0 \
- --hash=sha256:4da4ac43888e97de9cf4fdd870f48ed864bbfd133d2c46cbdec941fed4a25aef \
- --hash=sha256:a4b987ec31c3c9996cf1bc865332f967fe4a0512c41b39652d6224f696e69da5
+virtualenv==20.8.1 \
+ --hash=sha256:10062e34c204b5e4ec5f62e6ef2473f8ba76513a9a617e873f1f8fb4a519d300 \
+ --hash=sha256:bcc17f0b3a29670dd777d6f0755a4c04f28815395bca279cdcb213b97199a6b8
# via pre-commit
watchgod==0.7 \
--hash=sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29 \
diff --git a/requirements/main.in b/requirements/main.in
index b924a84..7c5b138 100644
--- a/requirements/main.in
+++ b/requirements/main.in
@@ -16,3 +16,4 @@ safir
httpx
aws-request-signer
python-dotenv
+Authlib
diff --git a/requirements/main.txt b/requirements/main.txt
index 735d95f..788b4a1 100644
--- a/requirements/main.txt
+++ b/requirements/main.txt
@@ -4,14 +4,18 @@
#
# pip-compile --generate-hashes --output-file=requirements/main.txt requirements/main.in
#
-anyio==3.3.1 \
- --hash=sha256:85913b4e2fec030e8c72a8f9f98092eeb9e25847a6e00d567751b77e34f856fe \
- --hash=sha256:d7c604dd491eca70e19c78664d685d5e4337612d574419d503e76f5d7d1590bd
+anyio==3.3.2 \
+ --hash=sha256:0b993a2ef6c1dc456815c2b5ca2819f382f20af98087cc2090a4afed3a501436 \
+ --hash=sha256:c32da314c510b34a862f5afeaf8a446ffed2c2fde21583e654bd71ecfb5b744b
# via httpcore
asgiref==3.4.1 \
--hash=sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9 \
--hash=sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214
# via uvicorn
+authlib==0.15.4 \
+ --hash=sha256:37df3a2554bc6fe0da3cc6848c44fac2ae40634a7f8fc72543947f4330b26464 \
+ --hash=sha256:d9fe5edb59801b16583faa86f88d798d99d952979b9616d5c735b9170b41ae2c
+ # via -r requirements/main.in
aws-request-signer==1.1.0 \
--hash=sha256:5e04f30bf815c58f4e776a659ce6009f11ff97d66af1352e8794bddc0b3a1e0c \
--hash=sha256:da60cfeb19410243550b2a441c014f8543c639c6aec4c047cc35807ecd259e31
@@ -20,6 +24,53 @@ certifi==2021.5.30 \
--hash=sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee \
--hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8
# via httpx
+cffi==1.14.6 \
+ --hash=sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d \
+ --hash=sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771 \
+ --hash=sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872 \
+ --hash=sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c \
+ --hash=sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc \
+ --hash=sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762 \
+ --hash=sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202 \
+ --hash=sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5 \
+ --hash=sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548 \
+ --hash=sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a \
+ --hash=sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f \
+ --hash=sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20 \
+ --hash=sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218 \
+ --hash=sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c \
+ --hash=sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e \
+ --hash=sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56 \
+ --hash=sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224 \
+ --hash=sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a \
+ --hash=sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2 \
+ --hash=sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a \
+ --hash=sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819 \
+ --hash=sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346 \
+ --hash=sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b \
+ --hash=sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e \
+ --hash=sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534 \
+ --hash=sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb \
+ --hash=sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0 \
+ --hash=sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156 \
+ --hash=sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd \
+ --hash=sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87 \
+ --hash=sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc \
+ --hash=sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195 \
+ --hash=sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33 \
+ --hash=sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f \
+ --hash=sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d \
+ --hash=sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd \
+ --hash=sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728 \
+ --hash=sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7 \
+ --hash=sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca \
+ --hash=sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99 \
+ --hash=sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf \
+ --hash=sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e \
+ --hash=sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c \
+ --hash=sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5 \
+ --hash=sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69
+ # via cryptography
charset-normalizer==2.0.6 \
--hash=sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6 \
--hash=sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f
@@ -28,9 +79,31 @@ click==8.0.1 \
--hash=sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a \
--hash=sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6
# via uvicorn
-fastapi==0.68.1 \
- --hash=sha256:644bb815bae326575c4b2842469fb83053a4b974b82fa792ff9283d17fbbd99d \
- --hash=sha256:94d2820906c36b9b8303796fb7271337ec89c74223229e3cfcf056b5a7d59e23
+cryptography==35.0.0 \
+ --hash=sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6 \
+ --hash=sha256:18d90f4711bf63e2fb21e8c8e51ed8189438e6b35a6d996201ebd98a26abbbe6 \
+ --hash=sha256:1ed82abf16df40a60942a8c211251ae72858b25b7421ce2497c2eb7a1cee817c \
+ --hash=sha256:22a38e96118a4ce3b97509443feace1d1011d0571fae81fc3ad35f25ba3ea999 \
+ --hash=sha256:2d69645f535f4b2c722cfb07a8eab916265545b3475fdb34e0be2f4ee8b0b15e \
+ --hash=sha256:4a2d0e0acc20ede0f06ef7aa58546eee96d2592c00f450c9acb89c5879b61992 \
+ --hash=sha256:54b2605e5475944e2213258e0ab8696f4f357a31371e538ef21e8d61c843c28d \
+ --hash=sha256:7075b304cd567694dc692ffc9747f3e9cb393cc4aa4fb7b9f3abd6f5c4e43588 \
+ --hash=sha256:7b7ceeff114c31f285528ba8b390d3e9cfa2da17b56f11d366769a807f17cbaa \
+ --hash=sha256:7eba2cebca600a7806b893cb1d541a6e910afa87e97acf2021a22b32da1df52d \
+ --hash=sha256:928185a6d1ccdb816e883f56ebe92e975a262d31cc536429041921f8cb5a62fd \
+ --hash=sha256:9933f28f70d0517686bd7de36166dda42094eac49415459d9bdf5e7df3e0086d \
+ --hash=sha256:a688ebcd08250eab5bb5bca318cc05a8c66de5e4171a65ca51db6bd753ff8953 \
+ --hash=sha256:abb5a361d2585bb95012a19ed9b2c8f412c5d723a9836418fab7aaa0243e67d2 \
+ --hash=sha256:c10c797ac89c746e488d2ee92bd4abd593615694ee17b2500578b63cad6b93a8 \
+ --hash=sha256:ced40344e811d6abba00295ced98c01aecf0c2de39481792d87af4fa58b7b4d6 \
+ --hash=sha256:d57e0cdc1b44b6cdf8af1d01807db06886f10177469312fbde8f44ccbb284bc9 \
+ --hash=sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6 \
+ --hash=sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad \
+ --hash=sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76
+ # via authlib
+fastapi==0.68.2 \
+ --hash=sha256:36bcdd3dbea87c586061005e4a40b9bd0145afd766655b4e0ec1d8870b32555c \
+ --hash=sha256:38526fc46bda73f7ec92033952677323c16061e70a91d15c95f18b11895da494
# via
# -r requirements/main.in
# safir
@@ -77,6 +150,10 @@ idna==3.2 \
# via
# anyio
# rfc3986
+pycparser==2.20 \
+ --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \
+ --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705
+ # via cffi
pydantic==1.8.2 \
--hash=sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd \
--hash=sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739 \
From e3eca8bb4319d2f9782695b6eb2f21a850caaaf0 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 7 Oct 2021 13:47:26 -0400
Subject: [PATCH 02/26] Add credential configs for GitHub OAuth client
---
src/ltdproxy/config.py | 6 ++++++
tox.ini | 2 ++
2 files changed, 8 insertions(+)
diff --git a/src/ltdproxy/config.py b/src/ltdproxy/config.py
index 3d51158..8f2e1bf 100644
--- a/src/ltdproxy/config.py
+++ b/src/ltdproxy/config.py
@@ -53,6 +53,12 @@ class Configuration(BaseSettings):
..., env="LTDPROXY_AWS_SECRET_ACCESS_KEY"
)
+ github_oauth_client_id: str = Field(env="LTDPROXY_GITHUB_OAUTH_ID")
+
+ github_oauth_client_secret: SecretStr = Field(
+ env="LTDPROXY_GITHUB_OAUTH_SECRET"
+ )
+
config = Configuration(_env_file=os.getenv("LTD_PROXY_ENV"))
"""Configuration for ltd-proxy."""
diff --git a/tox.ini b/tox.ini
index a62c17a..8b9f968 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,6 +10,8 @@ deps =
setenv =
LTDPROXY_AWS_ACCESS_KEY_ID = foo
LTDPROXY_AWS_SECRET_ACCESS_KEY = bar
+ LTDPROXY_GITHUB_OAUTH_ID = foo
+ LTDPROXY_GITHUB_OAUTH_SECRET = bar
commands =
pytest --cov=ltdproxy --cov-branch --cov-report= {posargs}
From 4d69bf8fa589a8fcab645268c6e4da4ccb6fb7e1 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 7 Oct 2021 14:50:47 -0400
Subject: [PATCH 03/26] Add itsdangeorus for sessions middleware
---
requirements/main.in | 1 +
requirements/main.txt | 20 +++++++++++++-------
2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/requirements/main.in b/requirements/main.in
index 7c5b138..d602a74 100644
--- a/requirements/main.in
+++ b/requirements/main.in
@@ -17,3 +17,4 @@ httpx
aws-request-signer
python-dotenv
Authlib
+itsdangerous
diff --git a/requirements/main.txt b/requirements/main.txt
index 788b4a1..1092ef0 100644
--- a/requirements/main.txt
+++ b/requirements/main.txt
@@ -7,7 +7,9 @@
anyio==3.3.2 \
--hash=sha256:0b993a2ef6c1dc456815c2b5ca2819f382f20af98087cc2090a4afed3a501436 \
--hash=sha256:c32da314c510b34a862f5afeaf8a446ffed2c2fde21583e654bd71ecfb5b744b
- # via httpcore
+ # via
+ # httpcore
+ # starlette
asgiref==3.4.1 \
--hash=sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9 \
--hash=sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214
@@ -101,9 +103,9 @@ cryptography==35.0.0 \
--hash=sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad \
--hash=sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76
# via authlib
-fastapi==0.68.2 \
- --hash=sha256:36bcdd3dbea87c586061005e4a40b9bd0145afd766655b4e0ec1d8870b32555c \
- --hash=sha256:38526fc46bda73f7ec92033952677323c16061e70a91d15c95f18b11895da494
+fastapi==0.70.0 \
+ --hash=sha256:66da43cfe5185ea1df99552acffd201f1832c6b364e0f4136c0a99f933466ced \
+ --hash=sha256:a36d5f2fad931aa3575c07a3472c784e81f3e664e3bb5c8b9c88d0ec1104f59c
# via
# -r requirements/main.in
# safir
@@ -150,6 +152,10 @@ idna==3.2 \
# via
# anyio
# rfc3986
+itsdangerous==2.0.1 \
+ --hash=sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c \
+ --hash=sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0
+ # via -r requirements/main.in
pycparser==2.20 \
--hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \
--hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705
@@ -232,9 +238,9 @@ sniffio==1.2.0 \
# anyio
# httpcore
# httpx
-starlette==0.14.2 \
- --hash=sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed \
- --hash=sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa
+starlette==0.16.0 \
+ --hash=sha256:38eb24bf705a2c317e15868e384c1b8a12ca396e5a3c3a003db7e667c43f939f \
+ --hash=sha256:e1904b5d0007aee24bdd3c43994be9b3b729f4f58e740200de1d623f8c3a8870
# via
# -r requirements/main.in
# fastapi
From d0d648bf3e69b6c04ffa41eb09f550c28fba5607 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 7 Oct 2021 14:51:21 -0400
Subject: [PATCH 04/26] Fix env var name for S3 prefix config
---
src/ltdproxy/config.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/ltdproxy/config.py b/src/ltdproxy/config.py
index 8f2e1bf..c0443fc 100644
--- a/src/ltdproxy/config.py
+++ b/src/ltdproxy/config.py
@@ -43,7 +43,7 @@ class Configuration(BaseSettings):
s3_bucket: str = Field("test", env="LTDPROXY_S3_BUCKET")
- s3_bucket_prefix: str = Field("", env="LTD_PROXY_S3_PREFIX")
+ s3_bucket_prefix: str = Field("", env="LTDPROXY_S3_PREFIX")
aws_region: str = Field("us-central-1", env="LTDPROXY_AWS_REGION")
From acd3424cd41c2f6a0fc424a285f605efab37bb91 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 7 Oct 2021 14:52:24 -0400
Subject: [PATCH 05/26] Add configuration for session key
---
src/ltdproxy/config.py | 2 ++
tox.ini | 1 +
2 files changed, 3 insertions(+)
diff --git a/src/ltdproxy/config.py b/src/ltdproxy/config.py
index c0443fc..4afdff6 100644
--- a/src/ltdproxy/config.py
+++ b/src/ltdproxy/config.py
@@ -59,6 +59,8 @@ class Configuration(BaseSettings):
env="LTDPROXY_GITHUB_OAUTH_SECRET"
)
+ session_key: SecretStr = Field(env="LTDPROXY_SESSION_KEY")
+
config = Configuration(_env_file=os.getenv("LTD_PROXY_ENV"))
"""Configuration for ltd-proxy."""
diff --git a/tox.ini b/tox.ini
index 8b9f968..9c7b93b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,6 +12,7 @@ setenv =
LTDPROXY_AWS_SECRET_ACCESS_KEY = bar
LTDPROXY_GITHUB_OAUTH_ID = foo
LTDPROXY_GITHUB_OAUTH_SECRET = bar
+ LTDPROXY_SESSION_KEY = 1234
commands =
pytest --cov=ltdproxy --cov-branch --cov-report= {posargs}
From 4fef6b409ca856fde07656f86bbb0970d8a07c8c Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 7 Oct 2021 14:53:12 -0400
Subject: [PATCH 06/26] Add sessions middleware
This is needed for persistent sessions in the oauth flow.
---
src/ltdproxy/main.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/ltdproxy/main.py b/src/ltdproxy/main.py
index feaa735..f494806 100644
--- a/src/ltdproxy/main.py
+++ b/src/ltdproxy/main.py
@@ -13,6 +13,7 @@
from safir.dependencies.http_client import http_client_dependency
from safir.logging import configure_logging
from safir.middleware.x_forwarded import XForwardedMiddleware
+from starlette.middleware.sessions import SessionMiddleware
from .config import config
from .handlers.external import external_router
@@ -32,16 +33,19 @@
# Define the external routes in a subapp so that it will serve its own OpenAPI
# interface definition and documentation URLs under the external URL.
-_subapp = FastAPI(
+external_app = FastAPI(
title="ltd-proxy",
description=metadata("ltd-proxy").get("Summary", ""),
version=metadata("ltd-proxy").get("Version", "0.0.0"),
)
-_subapp.include_router(external_router)
+external_app.include_router(external_router)
+external_app.add_middleware(
+ SessionMiddleware, secret_key=config.session_key.get_secret_value()
+)
# Attach the internal routes and subapp to the main application.
app.include_router(internal_router)
-app.mount(f"/{config.name}", _subapp)
+app.mount(f"/{config.name}", external_app)
@app.on_event("startup")
From 21b021a51af452f536997f44e6f438258c264feb Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Tue, 12 Oct 2021 13:51:34 -0400
Subject: [PATCH 07/26] Improve S3 bucket dependency naming
This clarifies what is the dependency, and what is the class providing
the singleton.
---
src/ltdproxy/s3.py | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/ltdproxy/s3.py b/src/ltdproxy/s3.py
index 28c35ef..8dcf242 100644
--- a/src/ltdproxy/s3.py
+++ b/src/ltdproxy/s3.py
@@ -58,7 +58,11 @@ async def stream_object(
)
-class BucketDependency:
+class ConfiguredBucket:
+ """This class maintains a configured Bucket instance; an instance of this
+ class can be used as a FastAPI path dependency.
+ """
+
def __init__(self) -> None:
self.bucket = Bucket(
bucket=config.s3_bucket,
@@ -67,8 +71,10 @@ def __init__(self) -> None:
secret_access_key=config.aws_secret_access_key.get_secret_value(),
)
- def __call__(self) -> Bucket:
+ async def __call__(self) -> Bucket:
+ # This method is async so that FastAPI does not create an extra thread
+ # when calling this.
return self.bucket
-bucket_dependency = BucketDependency()
+bucket_dependency = ConfiguredBucket()
From b23cb371bc23e6ce90173d6acf925fc96cc98796 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Tue, 12 Oct 2021 13:53:17 -0400
Subject: [PATCH 08/26] Demonstrate GitHub OAuth
We're using authlib via their starlette integration to implement a
GitHub OAuth client.
- GitHubOAuth configures/registers the client, and
github_oauth_dependency provides an instance of that GitHub client as a
path operation depenency.
- The GitHub access token is stored in the Cookie session.
Note that the endpoints for logging in and logging out are just stubs;
we'll develop those further so they're configured more naturally and
redirect to the right place.
---
src/ltdproxy/githubauth.py | 51 +++++++++++++++++++++++++
src/ltdproxy/handlers/external.py | 63 ++++++++++++++++++++++++++++++-
2 files changed, 113 insertions(+), 1 deletion(-)
create mode 100644 src/ltdproxy/githubauth.py
diff --git a/src/ltdproxy/githubauth.py b/src/ltdproxy/githubauth.py
new file mode 100644
index 0000000..9465b1d
--- /dev/null
+++ b/src/ltdproxy/githubauth.py
@@ -0,0 +1,51 @@
+"""The GitHub OAuth dependency for path operations."""
+
+from __future__ import annotations
+
+from typing import TypeVar
+
+import authlib.integrations.starlette_client.integration
+from authlib.integrations.starlette_client import OAuth
+
+from ltdproxy.config import config
+
+GitHubOAuthType = TypeVar(
+ "GitHubOAuthType",
+ bound=authlib.integrations.starlette_client.integration.StarletteRemoteApp,
+)
+"""Type alias from the authlib GitHub OAuth client."""
+
+
+class GitHubOAuth:
+ """This class maintains an OAuth instance that is registered for GitHub
+ OAuth with the applications configurations.
+
+ The instance of this class, ``github_oauth_dependency`` is a FastAPI
+ path operation dependency that provides the configured OAuth instance
+ to endpoint handlers.
+ """
+
+ def __init__(self) -> None:
+ self.oauth = OAuth()
+ self.oauth.register(
+ name="github",
+ client_id=config.github_oauth_client_id,
+ client_secret=config.github_oauth_client_secret.get_secret_value(),
+ access_token_url="https://github.com/login/oauth/access_token",
+ access_token_params=None,
+ authorize_url="https://github.com/login/oauth/authorize",
+ authorize_params=None,
+ api_base_url="https://api.github.com/",
+ client_kwargs={"scope": "user:email,read:org"},
+ )
+
+ async def __call__(self) -> GitHubOAuthType:
+ # This method is async so that FastAPI does not create an extra thread
+ # when calling this.
+ return self.oauth.github
+
+
+github_oauth_dependency = GitHubOAuth()
+"""Path dependency that returns a configured
+`authlib.integrations.starlette_client.OAuth` instance for GitHub OAuth.
+"""
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index 0b2c97f..1807b94 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -1,14 +1,23 @@
"""Handlers for the app's external root, ``/ltdproxy/``."""
+from typing import Union
+
import httpx
+from authlib.integrations.starlette_client import OAuthError
from fastapi import APIRouter, Depends
from safir.dependencies.http_client import http_client_dependency
from safir.dependencies.logger import logger_dependency
from starlette.background import BackgroundTask
-from starlette.responses import StreamingResponse
+from starlette.requests import Request
+from starlette.responses import (
+ HTMLResponse,
+ RedirectResponse,
+ StreamingResponse,
+)
from structlog.stdlib import BoundLogger
from ltdproxy.config import config
+from ltdproxy.githubauth import GitHubOAuthType, github_oauth_dependency
from ltdproxy.s3 import Bucket, bucket_dependency
__all__ = ["get_s3", "external_router"]
@@ -17,6 +26,58 @@
"""FastAPI router for all external handlers."""
+@external_router.get("/")
+async def homepage(request: Request) -> HTMLResponse:
+ github_token = request.session.get("github_token")
+ if github_token:
+ html = "hello!
" 'logout'
+ return HTMLResponse(html)
+ return HTMLResponse('login')
+
+
+@external_router.get("/auth")
+async def get_oauth_callback(
+ request: Request,
+ logger: BoundLogger = Depends(logger_dependency),
+ github_oauth: GitHubOAuthType = Depends(github_oauth_dependency),
+) -> Union[RedirectResponse, HTMLResponse]:
+ try:
+ token = await github_oauth.authorize_access_token(request)
+ except OAuthError as error:
+ return HTMLResponse(f"{error.error}
")
+ print(token)
+ print(type(token))
+ github_token = token.get("access_token")
+ logger.info(
+ "Got github oauth token", token=token, access_token=github_token
+ )
+ if github_token:
+ request.session["github_token"] = github_token
+ return RedirectResponse(url="/ltdproxy/")
+
+
+@external_router.get("/login")
+async def login(
+ request: Request,
+ logger: BoundLogger = Depends(logger_dependency),
+ github_oauth: GitHubOAuthType = Depends(github_oauth_dependency),
+) -> RedirectResponse:
+ # redirect_uri = request.url_for('get_oauth_callback')
+ redirect_uri = "http://127.0.0.1:8000/ltdproxy/auth"
+ logger.info("Redirecting to GitHub auth", callback_url=redirect_uri)
+ return await github_oauth.authorize_redirect(request, redirect_uri)
+
+
+@external_router.get("/logout")
+async def logout(
+ request: Request,
+ logger: BoundLogger = Depends(logger_dependency),
+) -> RedirectResponse:
+ request.session.pop("github_token", None)
+ logger.info("Logged out")
+ return RedirectResponse(url="/ltdproxy/")
+
+
@external_router.get(
"/{path:path}",
description="The S3 front-end proxy.",
From bc612a3364244f31fae000dbf30820d1204f706d Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Mon, 25 Oct 2021 13:14:36 -0400
Subject: [PATCH 09/26] Add gidgethub dependency
---
requirements/dev.txt | 210 +++++++++++++++++++-------------------
requirements/main.in | 1 +
requirements/main.txt | 231 +++++++++++++++++++++++-------------------
3 files changed, 235 insertions(+), 207 deletions(-)
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 6eab160..97c3198 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -4,9 +4,9 @@
#
# pip-compile --generate-hashes --output-file=requirements/dev.txt requirements/dev.in
#
-anyio==3.3.2 \
- --hash=sha256:0b993a2ef6c1dc456815c2b5ca2819f382f20af98087cc2090a4afed3a501436 \
- --hash=sha256:c32da314c510b34a862f5afeaf8a446ffed2c2fde21583e654bd71ecfb5b744b
+anyio==3.3.4 \
+ --hash=sha256:4fd09a25ab7fa01d34512b7249e366cd10358cdafc95022c7ff8c8f8a5026d66 \
+ --hash=sha256:67da67b5b21f96b9d3d65daa6ea99f5d5282cb09f50eb4456f8fb51dffefc3ff
# via
# -c requirements/main.txt
# httpcore
@@ -28,9 +28,9 @@ backports.entry-points-selectable==1.1.0 \
--hash=sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a \
--hash=sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc
# via virtualenv
-certifi==2021.5.30 \
- --hash=sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee \
- --hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8
+certifi==2021.10.8 \
+ --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
+ --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569
# via
# -c requirements/main.txt
# httpx
@@ -38,52 +38,52 @@ cfgv==3.3.1 \
--hash=sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426 \
--hash=sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736
# via pre-commit
-charset-normalizer==2.0.6 \
- --hash=sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6 \
- --hash=sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f
+charset-normalizer==2.0.7 \
+ --hash=sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0 \
+ --hash=sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b
# via
# -c requirements/main.txt
# httpx
-click==8.0.1 \
- --hash=sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a \
- --hash=sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6
+click==8.0.3 \
+ --hash=sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3 \
+ --hash=sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b
# via
# -c requirements/main.txt
# uvicorn
-coverage[toml]==6.0.1 \
- --hash=sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26 \
- --hash=sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666 \
- --hash=sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee \
- --hash=sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345 \
- --hash=sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5 \
- --hash=sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad \
- --hash=sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f \
- --hash=sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854 \
- --hash=sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32 \
- --hash=sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7 \
- --hash=sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12 \
- --hash=sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec \
- --hash=sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc \
- --hash=sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5 \
- --hash=sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724 \
- --hash=sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7 \
- --hash=sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d \
- --hash=sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194 \
- --hash=sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e \
- --hash=sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a \
- --hash=sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b \
- --hash=sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a \
- --hash=sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e \
- --hash=sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22 \
- --hash=sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b \
- --hash=sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793 \
- --hash=sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397 \
- --hash=sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815 \
- --hash=sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827 \
- --hash=sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb \
- --hash=sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43 \
- --hash=sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf \
- --hash=sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16
+coverage[toml]==6.0.2 \
+ --hash=sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1 \
+ --hash=sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0 \
+ --hash=sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9 \
+ --hash=sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895 \
+ --hash=sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d \
+ --hash=sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe \
+ --hash=sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2 \
+ --hash=sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4 \
+ --hash=sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce \
+ --hash=sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9 \
+ --hash=sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122 \
+ --hash=sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7 \
+ --hash=sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3 \
+ --hash=sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff \
+ --hash=sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149 \
+ --hash=sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a \
+ --hash=sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164 \
+ --hash=sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1 \
+ --hash=sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd \
+ --hash=sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc \
+ --hash=sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f \
+ --hash=sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9 \
+ --hash=sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9 \
+ --hash=sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0 \
+ --hash=sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d \
+ --hash=sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa \
+ --hash=sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7 \
+ --hash=sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822 \
+ --hash=sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc \
+ --hash=sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7 \
+ --hash=sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330 \
+ --hash=sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb \
+ --hash=sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24
# via
# -r requirements/dev.in
# pytest-cov
@@ -91,9 +91,9 @@ distlib==0.3.3 \
--hash=sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31 \
--hash=sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05
# via virtualenv
-filelock==3.3.0 \
- --hash=sha256:8c7eab13dc442dc249e95158bcc12dec724465919bdc9831fdbf0660f03d1785 \
- --hash=sha256:bbc6a0382fe8ec4744ecdf6683a2e07f65eb10ff1aff53fc02a202565446cde0
+filelock==3.3.1 \
+ --hash=sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f \
+ --hash=sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f
# via virtualenv
h11==0.12.0 \
--hash=sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6 \
@@ -127,19 +127,19 @@ httptools==0.2.0 \
# via
# -c requirements/main.txt
# uvicorn
-httpx==0.19.0 \
- --hash=sha256:92ecd2c00c688b529eda11cedb15161eaf02dee9116712f621c70d9a40b2cdd0 \
- --hash=sha256:9bd728a6c5ec0a9e243932a9983d57d3cc4a87bb4f554e1360fce407f78f9435
+httpx==0.20.0 \
+ --hash=sha256:09606d630f070d07f9ff28104fbcea429ea0014c1e89ac90b4d8de8286c40e7b \
+ --hash=sha256:33af5aad9bdc82ef1fc89219c1e36f5693bf9cd0ebe330884df563445682c0f8
# via
# -c requirements/main.txt
# -r requirements/dev.in
-identify==2.3.0 \
- --hash=sha256:d1e82c83d063571bb88087676f81261a4eae913c492dafde184067c584bc7c05 \
- --hash=sha256:fd08c97f23ceee72784081f1ce5125c8f53a02d3f2716dde79a6ab8f1039fea5
+identify==2.3.1 \
+ --hash=sha256:5a5000bd3293950d992843c0ef3d82b90a582de2161557bda7f493c8c8864f26 \
+ --hash=sha256:8a92c56893e9a4ce951f09a50489986615e3eba7b4c60610e0b25f93ca4487ba
# via pre-commit
-idna==3.2 \
- --hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \
- --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3
+idna==3.3 \
+ --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
+ --hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d
# via
# -c requirements/main.txt
# anyio
@@ -201,9 +201,9 @@ py==1.10.0 \
--hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \
--hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a
# via pytest
-pyparsing==2.4.7 \
- --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \
- --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b
+pyparsing==3.0.1 \
+ --hash=sha256:84196357aa3566d64ad123d7a3c67b0e597a115c4934b097580e5ce220b91531 \
+ --hash=sha256:fd93fc45c47893c300bd98f5dd1b41c0e783eaeb727e7cea210dcc09d64ce7c3
# via packaging
pytest==6.2.5 \
--hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 \
@@ -212,50 +212,54 @@ pytest==6.2.5 \
# -r requirements/dev.in
# pytest-asyncio
# pytest-cov
-pytest-asyncio==0.15.1 \
- --hash=sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f \
- --hash=sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea
+pytest-asyncio==0.16.0 \
+ --hash=sha256:5f2a21273c47b331ae6aa5b36087047b4899e40f03f18397c0e65fa5cca54e9b \
+ --hash=sha256:7496c5977ce88c34379df64a66459fe395cd05543f0a2f837016e7144391fcfb
# via -r requirements/dev.in
pytest-cov==3.0.0 \
--hash=sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6 \
--hash=sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470
# via -r requirements/dev.in
-python-dotenv==0.19.0 \
- --hash=sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1 \
- --hash=sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172
+python-dotenv==0.19.1 \
+ --hash=sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8 \
+ --hash=sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a
# via
# -c requirements/main.txt
# uvicorn
-pyyaml==5.4.1 \
- --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \
- --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \
- --hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \
- --hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \
- --hash=sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922 \
- --hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \
- --hash=sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8 \
- --hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \
- --hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \
- --hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \
- --hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e \
- --hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \
- --hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \
- --hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \
- --hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \
- --hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \
- --hash=sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185 \
- --hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \
- --hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \
- --hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \
- --hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \
- --hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \
- --hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \
- --hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \
- --hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \
- --hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \
- --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \
- --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \
- --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0
+pyyaml==6.0 \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
# via
# -c requirements/main.txt
# pre-commit
@@ -286,9 +290,9 @@ toml==0.10.2 \
# mypy
# pre-commit
# pytest
-tomli==1.2.1 \
- --hash=sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f \
- --hash=sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442
+tomli==1.2.2 \
+ --hash=sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee \
+ --hash=sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade
# via coverage
typing-extensions==3.10.0.2 \
--hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
@@ -323,9 +327,9 @@ uvloop==0.16.0 \
# via
# -c requirements/main.txt
# uvicorn
-virtualenv==20.8.1 \
- --hash=sha256:10062e34c204b5e4ec5f62e6ef2473f8ba76513a9a617e873f1f8fb4a519d300 \
- --hash=sha256:bcc17f0b3a29670dd777d6f0755a4c04f28815395bca279cdcb213b97199a6b8
+virtualenv==20.9.0 \
+ --hash=sha256:1d145deec2da86b29026be49c775cc5a9aab434f85f7efef98307fb3965165de \
+ --hash=sha256:bb55ace18de14593947354e5e6cd1be75fb32c3329651da62e92bf5d0aab7213
# via pre-commit
watchgod==0.7 \
--hash=sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29 \
diff --git a/requirements/main.in b/requirements/main.in
index d602a74..4241513 100644
--- a/requirements/main.in
+++ b/requirements/main.in
@@ -18,3 +18,4 @@ aws-request-signer
python-dotenv
Authlib
itsdangerous
+gidgethub
diff --git a/requirements/main.txt b/requirements/main.txt
index 1092ef0..283f073 100644
--- a/requirements/main.txt
+++ b/requirements/main.txt
@@ -4,9 +4,9 @@
#
# pip-compile --generate-hashes --output-file=requirements/main.txt requirements/main.in
#
-anyio==3.3.2 \
- --hash=sha256:0b993a2ef6c1dc456815c2b5ca2819f382f20af98087cc2090a4afed3a501436 \
- --hash=sha256:c32da314c510b34a862f5afeaf8a446ffed2c2fde21583e654bd71ecfb5b744b
+anyio==3.3.4 \
+ --hash=sha256:4fd09a25ab7fa01d34512b7249e366cd10358cdafc95022c7ff8c8f8a5026d66 \
+ --hash=sha256:67da67b5b21f96b9d3d65daa6ea99f5d5282cb09f50eb4456f8fb51dffefc3ff
# via
# httpcore
# starlette
@@ -14,72 +14,77 @@ asgiref==3.4.1 \
--hash=sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9 \
--hash=sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214
# via uvicorn
-authlib==0.15.4 \
- --hash=sha256:37df3a2554bc6fe0da3cc6848c44fac2ae40634a7f8fc72543947f4330b26464 \
- --hash=sha256:d9fe5edb59801b16583faa86f88d798d99d952979b9616d5c735b9170b41ae2c
+authlib==0.15.5 \
+ --hash=sha256:b83cf6360c8e92b0e9df0d1f32d675790bcc4e3c03977499b1eed24dcdef4252 \
+ --hash=sha256:ecf4a7a9f2508c0bb07e93a752dd3c495cfaffc20e864ef0ffc95e3f40d2abaf
# via -r requirements/main.in
aws-request-signer==1.1.0 \
--hash=sha256:5e04f30bf815c58f4e776a659ce6009f11ff97d66af1352e8794bddc0b3a1e0c \
--hash=sha256:da60cfeb19410243550b2a441c014f8543c639c6aec4c047cc35807ecd259e31
# via -r requirements/main.in
-certifi==2021.5.30 \
- --hash=sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee \
- --hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8
+certifi==2021.10.8 \
+ --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
+ --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569
# via httpx
-cffi==1.14.6 \
- --hash=sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d \
- --hash=sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771 \
- --hash=sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872 \
- --hash=sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c \
- --hash=sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc \
- --hash=sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762 \
- --hash=sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202 \
- --hash=sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5 \
- --hash=sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548 \
- --hash=sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a \
- --hash=sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f \
- --hash=sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20 \
- --hash=sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218 \
- --hash=sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c \
- --hash=sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e \
- --hash=sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56 \
- --hash=sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224 \
- --hash=sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a \
- --hash=sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2 \
- --hash=sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a \
- --hash=sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819 \
- --hash=sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346 \
- --hash=sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b \
- --hash=sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e \
- --hash=sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534 \
- --hash=sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb \
- --hash=sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0 \
- --hash=sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156 \
- --hash=sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd \
- --hash=sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87 \
- --hash=sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc \
- --hash=sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195 \
- --hash=sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33 \
- --hash=sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f \
- --hash=sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d \
- --hash=sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd \
- --hash=sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728 \
- --hash=sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7 \
- --hash=sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca \
- --hash=sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99 \
- --hash=sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf \
- --hash=sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e \
- --hash=sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c \
- --hash=sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5 \
- --hash=sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69
+cffi==1.15.0 \
+ --hash=sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3 \
+ --hash=sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2 \
+ --hash=sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636 \
+ --hash=sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20 \
+ --hash=sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728 \
+ --hash=sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27 \
+ --hash=sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66 \
+ --hash=sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443 \
+ --hash=sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0 \
+ --hash=sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7 \
+ --hash=sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39 \
+ --hash=sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605 \
+ --hash=sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a \
+ --hash=sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37 \
+ --hash=sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029 \
+ --hash=sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139 \
+ --hash=sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc \
+ --hash=sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df \
+ --hash=sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14 \
+ --hash=sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880 \
+ --hash=sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2 \
+ --hash=sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a \
+ --hash=sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e \
+ --hash=sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474 \
+ --hash=sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024 \
+ --hash=sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8 \
+ --hash=sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0 \
+ --hash=sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e \
+ --hash=sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a \
+ --hash=sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e \
+ --hash=sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032 \
+ --hash=sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6 \
+ --hash=sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e \
+ --hash=sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b \
+ --hash=sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e \
+ --hash=sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954 \
+ --hash=sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962 \
+ --hash=sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c \
+ --hash=sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4 \
+ --hash=sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55 \
+ --hash=sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962 \
+ --hash=sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023 \
+ --hash=sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c \
+ --hash=sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6 \
+ --hash=sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8 \
+ --hash=sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382 \
+ --hash=sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7 \
+ --hash=sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc \
+ --hash=sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997 \
+ --hash=sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796
# via cryptography
-charset-normalizer==2.0.6 \
- --hash=sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6 \
- --hash=sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f
+charset-normalizer==2.0.7 \
+ --hash=sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0 \
+ --hash=sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b
# via httpx
-click==8.0.1 \
- --hash=sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a \
- --hash=sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6
+click==8.0.3 \
+ --hash=sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3 \
+ --hash=sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b
# via uvicorn
cryptography==35.0.0 \
--hash=sha256:07bb7fbfb5de0980590ddfc7f13081520def06dc9ed214000ad4372fb4e3c7f6 \
@@ -102,13 +107,19 @@ cryptography==35.0.0 \
--hash=sha256:d99915d6ab265c22873f1b4d6ea5ef462ef797b4140be4c9d8b179915e0985c6 \
--hash=sha256:eb80e8a1f91e4b7ef8b33041591e6d89b2b8e122d787e87eeb2b08da71bb16ad \
--hash=sha256:ebeddd119f526bcf323a89f853afb12e225902a24d29b55fe18dd6fcb2838a76
- # via authlib
+ # via
+ # authlib
+ # pyjwt
fastapi==0.70.0 \
--hash=sha256:66da43cfe5185ea1df99552acffd201f1832c6b364e0f4136c0a99f933466ced \
--hash=sha256:a36d5f2fad931aa3575c07a3472c784e81f3e664e3bb5c8b9c88d0ec1104f59c
# via
# -r requirements/main.in
# safir
+gidgethub==5.0.1 \
+ --hash=sha256:3efbd6998600254ec7a2869318bd3ffde38edc3a0d37be0c14bc46b45947b682 \
+ --hash=sha256:67245e93eb0918b37df038148af675df43b62e832c529d7f859f6b90d9f3e70d
+ # via -r requirements/main.in
gunicorn==20.1.0 \
--hash=sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e \
--hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8
@@ -140,15 +151,15 @@ httptools==0.2.0 \
--hash=sha256:d5682eeb10cca0606c4a8286a3391d4c3c5a36f0c448e71b8bd05be4e1694bfb \
--hash=sha256:fd3b8905e21431ad306eeaf56644a68fdd621bf8f3097eff54d0f6bdf7262065
# via uvicorn
-httpx==0.19.0 \
- --hash=sha256:92ecd2c00c688b529eda11cedb15161eaf02dee9116712f621c70d9a40b2cdd0 \
- --hash=sha256:9bd728a6c5ec0a9e243932a9983d57d3cc4a87bb4f554e1360fce407f78f9435
+httpx==0.20.0 \
+ --hash=sha256:09606d630f070d07f9ff28104fbcea429ea0014c1e89ac90b4d8de8286c40e7b \
+ --hash=sha256:33af5aad9bdc82ef1fc89219c1e36f5693bf9cd0ebe330884df563445682c0f8
# via
# -r requirements/main.in
# safir
-idna==3.2 \
- --hash=sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a \
- --hash=sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3
+idna==3.3 \
+ --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
+ --hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d
# via
# anyio
# rfc3986
@@ -186,42 +197,50 @@ pydantic==1.8.2 \
# via
# fastapi
# safir
-python-dotenv==0.19.0 \
- --hash=sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1 \
- --hash=sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172
+pyjwt[crypto]==2.3.0 \
+ --hash=sha256:b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41 \
+ --hash=sha256:e0c4bb8d9f0af0c7f5b1ec4c5036309617d03d56932877f2f7a0beeb5318322f
+ # via gidgethub
+python-dotenv==0.19.1 \
+ --hash=sha256:14f8185cc8d494662683e6914addcb7e95374771e707601dfc70166946b4c4b8 \
+ --hash=sha256:bbd3da593fc49c249397cbfbcc449cf36cb02e75afc8157fcc6a81df6fb7750a
# via
# -r requirements/main.in
# uvicorn
-pyyaml==5.4.1 \
- --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \
- --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \
- --hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \
- --hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \
- --hash=sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922 \
- --hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \
- --hash=sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8 \
- --hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \
- --hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \
- --hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \
- --hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e \
- --hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \
- --hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \
- --hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \
- --hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \
- --hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \
- --hash=sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185 \
- --hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \
- --hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \
- --hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \
- --hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \
- --hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \
- --hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \
- --hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \
- --hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \
- --hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \
- --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \
- --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \
- --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0
+pyyaml==6.0 \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
# via uvicorn
rfc3986[idna2008]==1.5.0 \
--hash=sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835 \
@@ -245,15 +264,19 @@ starlette==0.16.0 \
# -r requirements/main.in
# fastapi
# safir
-structlog==21.1.0 \
- --hash=sha256:62f06fc0ee32fb8580f0715eea66cb87271eb7efb0eaf9af6b639cba8981de47 \
- --hash=sha256:d9d2d890532e8db83c6977a2a676fb1889922ff0c26ad4dc0ecac26f9fafbc57
+structlog==21.2.0 \
+ --hash=sha256:63a7111a32e5b615671536bb745692ea02cebfea2b39dcb7d2617eed19437cfe \
+ --hash=sha256:7ac42b565e1295712313f91edbcb64e0840a9037d888c8954f11fa6c43270e99
# via safir
typing-extensions==3.10.0.2 \
--hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
--hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \
--hash=sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34
# via pydantic
+uritemplate==4.1.1 \
+ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \
+ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e
+ # via gidgethub
uvicorn[standard]==0.15.0 \
--hash=sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1 \
--hash=sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff
From 6a067ad1ea5822288b677370b6a8d27735644808 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Mon, 25 Oct 2021 13:15:15 -0400
Subject: [PATCH 10/26] Add GitHub org and team memberships to session
After login, we cache the relevant org and team memberships in the
cookie (JSON serialized). This will allow endpoints to quickly determine
if the user is in the correct endpoints for a given path authorization
rule.
---
src/ltdproxy/githubauth.py | 50 ++++++++++++++++++++++++++++++-
src/ltdproxy/handlers/external.py | 12 +++++++-
2 files changed, 60 insertions(+), 2 deletions(-)
diff --git a/src/ltdproxy/githubauth.py b/src/ltdproxy/githubauth.py
index 9465b1d..dd0c9c0 100644
--- a/src/ltdproxy/githubauth.py
+++ b/src/ltdproxy/githubauth.py
@@ -2,13 +2,25 @@
from __future__ import annotations
-from typing import TypeVar
+import json
+from typing import TYPE_CHECKING, Any, Dict, List, Tuple, TypeVar
import authlib.integrations.starlette_client.integration
+import gidgethub.httpx
from authlib.integrations.starlette_client import OAuth
from ltdproxy.config import config
+if TYPE_CHECKING:
+ import httpx
+
+__all__ = [
+ "GitHubOAuthType",
+ "GitHubOAuth",
+ "github_oauth_dependency",
+ "set_serialized_github_memberships",
+]
+
GitHubOAuthType = TypeVar(
"GitHubOAuthType",
bound=authlib.integrations.starlette_client.integration.StarletteRemoteApp,
@@ -49,3 +61,39 @@ async def __call__(self) -> GitHubOAuthType:
"""Path dependency that returns a configured
`authlib.integrations.starlette_client.OAuth` instance for GitHub OAuth.
"""
+
+
+async def set_serialized_github_memberships(
+ *,
+ http_client: httpx.AsyncClient,
+ session: Dict[Any, Any],
+ github_token: str,
+) -> None:
+ """Add JSON-serialized GitHub organization and team memberships to the
+ request session.
+ """
+ # Stubs for configuration of GitHub teams and organizations relevant to
+ # authorization settings
+ relevant_orgs = ["jsickcodes"]
+ relevant_teams = [("jsickcodes", "proxy-team")]
+
+ github_client = gidgethub.httpx.GitHubAPI(
+ http_client, "ltd-proxy", oauth_token=github_token
+ )
+
+ # Get all relevant organization memberships for the user
+ user_orgs: List[str] = []
+ async for org in github_client.getiter("/user/memberships/orgs"):
+ if org["organization"]["login"] in relevant_orgs:
+ user_orgs.append(org["organization"]["login"])
+
+ # Get all relevant team memberships for the user
+ user_teams: List[Tuple[str, str]] = []
+ async for team in github_client.getiter("/user/teams"):
+ team_id = (team["organization"]["login"], team["name"])
+ if team_id in relevant_teams:
+ user_teams.append(team_id)
+
+ # Serialize memberships to JSON to pack inside the session cookie
+ memberships = json.dumps({"orgs": user_orgs, "teams": user_teams})
+ session["github_memberships"] = memberships
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index 1807b94..bc81531 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -17,7 +17,11 @@
from structlog.stdlib import BoundLogger
from ltdproxy.config import config
-from ltdproxy.githubauth import GitHubOAuthType, github_oauth_dependency
+from ltdproxy.githubauth import (
+ GitHubOAuthType,
+ github_oauth_dependency,
+ set_serialized_github_memberships,
+)
from ltdproxy.s3 import Bucket, bucket_dependency
__all__ = ["get_s3", "external_router"]
@@ -40,6 +44,7 @@ async def get_oauth_callback(
request: Request,
logger: BoundLogger = Depends(logger_dependency),
github_oauth: GitHubOAuthType = Depends(github_oauth_dependency),
+ http_client: httpx.AsyncClient = Depends(http_client_dependency),
) -> Union[RedirectResponse, HTMLResponse]:
try:
token = await github_oauth.authorize_access_token(request)
@@ -53,6 +58,11 @@ async def get_oauth_callback(
)
if github_token:
request.session["github_token"] = github_token
+ set_serialized_github_memberships(
+ http_client=http_client,
+ session=request.session,
+ github_token=github_token,
+ )
return RedirectResponse(url="/ltdproxy/")
From 2818ccbfa03d2a0d1a087acbd34d44f2b9c09c66 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Tue, 2 Nov 2021 16:39:02 -0400
Subject: [PATCH 11/26] Add pyyaml dependency
Also add types-PyYAML dependency for mypy
---
requirements/dev.in | 1 +
requirements/dev.txt | 115 ++++++++++++++++++++++++------------------
requirements/main.in | 1 +
requirements/main.txt | 10 ++--
4 files changed, 74 insertions(+), 53 deletions(-)
diff --git a/requirements/dev.in b/requirements/dev.in
index 9e4b414..a7c1ca5 100644
--- a/requirements/dev.in
+++ b/requirements/dev.in
@@ -16,3 +16,4 @@ pytest
pytest-asyncio
pytest-cov
uvicorn
+types-PyYAML
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 97c3198..c40f8a5 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -50,40 +50,53 @@ click==8.0.3 \
# via
# -c requirements/main.txt
# uvicorn
-coverage[toml]==6.0.2 \
- --hash=sha256:04560539c19ec26995ecfb3d9307ff154fbb9a172cb57e3b3cfc4ced673103d1 \
- --hash=sha256:1549e1d08ce38259de2bc3e9a0d5f3642ff4a8f500ffc1b2df73fd621a6cdfc0 \
- --hash=sha256:1db67c497688fd4ba85b373b37cc52c50d437fd7267520ecd77bddbd89ea22c9 \
- --hash=sha256:30922626ce6f7a5a30bdba984ad21021529d3d05a68b4f71ea3b16bda35b8895 \
- --hash=sha256:36e9040a43d2017f2787b28d365a4bb33fcd792c7ff46a047a04094dc0e2a30d \
- --hash=sha256:381d773d896cc7f8ba4ff3b92dee4ed740fb88dfe33b6e42efc5e8ab6dfa1cfe \
- --hash=sha256:3bbda1b550e70fa6ac40533d3f23acd4f4e9cb4e6e77251ce77fdf41b3309fb2 \
- --hash=sha256:3be1206dc09fb6298de3fce70593e27436862331a85daee36270b6d0e1c251c4 \
- --hash=sha256:424c44f65e8be58b54e2b0bd1515e434b940679624b1b72726147cfc6a9fc7ce \
- --hash=sha256:4b34ae4f51bbfa5f96b758b55a163d502be3dcb24f505d0227858c2b3f94f5b9 \
- --hash=sha256:4e28d2a195c533b58fc94a12826f4431726d8eb029ac21d874345f943530c122 \
- --hash=sha256:53a294dc53cfb39c74758edaa6305193fb4258a30b1f6af24b360a6c8bd0ffa7 \
- --hash=sha256:60e51a3dd55540bec686d7fff61b05048ca31e804c1f32cbb44533e6372d9cc3 \
- --hash=sha256:61b598cbdbaae22d9e34e3f675997194342f866bb1d781da5d0be54783dce1ff \
- --hash=sha256:6807947a09510dc31fa86f43595bf3a14017cd60bf633cc746d52141bfa6b149 \
- --hash=sha256:6a6a9409223a27d5ef3cca57dd7cd4dfcb64aadf2fad5c3b787830ac9223e01a \
- --hash=sha256:7092eab374346121805fb637572483270324407bf150c30a3b161fc0c4ca5164 \
- --hash=sha256:77b1da5767ed2f44611bc9bc019bc93c03fa495728ec389759b6e9e5039ac6b1 \
- --hash=sha256:8251b37be1f2cd9c0e5ccd9ae0380909c24d2a5ed2162a41fcdbafaf59a85ebd \
- --hash=sha256:9f1627e162e3864a596486774876415a7410021f4b67fd2d9efdf93ade681afc \
- --hash=sha256:a1b73c7c4d2a42b9d37dd43199c5711d91424ff3c6c22681bc132db4a4afec6f \
- --hash=sha256:a82d79586a0a4f5fd1cf153e647464ced402938fbccb3ffc358c7babd4da1dd9 \
- --hash=sha256:abbff240f77347d17306d3201e14431519bf64495648ca5a49571f988f88dee9 \
- --hash=sha256:ad9b8c1206ae41d46ec7380b78ba735ebb77758a650643e841dd3894966c31d0 \
- --hash=sha256:bbffde2a68398682623d9dd8c0ca3f46fda074709b26fcf08ae7a4c431a6ab2d \
- --hash=sha256:bcae10fccb27ca2a5f456bf64d84110a5a74144be3136a5e598f9d9fb48c0caa \
- --hash=sha256:c9cd3828bbe1a40070c11fe16a51df733fd2f0cb0d745fb83b7b5c1f05967df7 \
- --hash=sha256:cd1cf1deb3d5544bd942356364a2fdc8959bad2b6cf6eb17f47d301ea34ae822 \
- --hash=sha256:d036dc1ed8e1388e995833c62325df3f996675779541f682677efc6af71e96cc \
- --hash=sha256:db42baa892cba723326284490283a68d4de516bfb5aaba369b4e3b2787a778b7 \
- --hash=sha256:e4fb7ced4d9dec77d6cf533acfbf8e1415fe799430366affb18d69ee8a3c6330 \
- --hash=sha256:e7a0b42db2a47ecb488cde14e0f6c7679a2c5a9f44814393b162ff6397fcdfbb \
- --hash=sha256:f2f184bf38e74f152eed7f87e345b51f3ab0b703842f447c22efe35e59942c24
+coverage[toml]==6.1.1 \
+ --hash=sha256:0147f7833c41927d84f5af9219d9b32f875c0689e5e74ac8ca3cb61e73a698f9 \
+ --hash=sha256:04a92a6cf9afd99f9979c61348ec79725a9f9342fb45e63c889e33c04610d97b \
+ --hash=sha256:10ab138b153e4cc408b43792cb7f518f9ee02f4ff55cd1ab67ad6fd7e9905c7e \
+ --hash=sha256:2e5b9c17a56b8bf0c0a9477fcd30d357deb486e4e1b389ed154f608f18556c8a \
+ --hash=sha256:326d944aad0189603733d646e8d4a7d952f7145684da973c463ec2eefe1387c2 \
+ --hash=sha256:359a32515e94e398a5c0fa057e5887a42e647a9502d8e41165cf5cb8d3d1ca67 \
+ --hash=sha256:35cd2230e1ed76df7d0081a997f0fe705be1f7d8696264eb508076e0d0b5a685 \
+ --hash=sha256:3b270c6b48d3ff5a35deb3648028ba2643ad8434b07836782b1139cf9c66313f \
+ --hash=sha256:42a1fb5dee3355df90b635906bb99126faa7936d87dfc97eacc5293397618cb7 \
+ --hash=sha256:479228e1b798d3c246ac89b09897ee706c51b3e5f8f8d778067f38db73ccc717 \
+ --hash=sha256:4cd919057636f63ab299ccb86ea0e78b87812400c76abab245ca385f17d19fb5 \
+ --hash=sha256:4d8b453764b9b26b0dd2afb83086a7c3f9379134e340288d2a52f8a91592394b \
+ --hash=sha256:51a441011a30d693e71dea198b2a6f53ba029afc39f8e2aeb5b77245c1b282ef \
+ --hash=sha256:557594a50bfe3fb0b1b57460f6789affe8850ad19c1acf2d14a3e12b2757d489 \
+ --hash=sha256:572f917267f363101eec375c109c9c1118037c7cc98041440b5eabda3185ac7b \
+ --hash=sha256:62512c0ec5d307f56d86504c58eace11c1bc2afcdf44e3ff20de8ca427ca1d0e \
+ --hash=sha256:65ad3ff837c89a229d626b8004f0ee32110f9bfdb6a88b76a80df36ccc60d926 \
+ --hash=sha256:666c6b32b69e56221ad1551d377f718ed00e6167c7a1b9257f780b105a101271 \
+ --hash=sha256:6e994003e719458420e14ffb43c08f4c14990e20d9e077cb5cad7a3e419bbb54 \
+ --hash=sha256:72bf437d54186d104388cbae73c9f2b0f8a3e11b6e8d7deb593bd14625c96026 \
+ --hash=sha256:738e823a746841248b56f0f3bd6abf3b73af191d1fd65e4c723b9c456216f0ad \
+ --hash=sha256:78287731e3601ea5ce9d6468c82d88a12ef8fe625d6b7bdec9b45d96c1ad6533 \
+ --hash=sha256:7833c872718dc913f18e51ee97ea0dece61d9930893a58b20b3daf09bb1af6b6 \
+ --hash=sha256:7e083d32965d2eb6638a77e65b622be32a094fdc0250f28ce6039b0732fbcaa8 \
+ --hash=sha256:8186b5a4730c896cbe1e4b645bdc524e62d874351ae50e1db7c3e9f5dc81dc26 \
+ --hash=sha256:8605add58e6a960729aa40c0fd9a20a55909dd9b586d3e8104cc7f45869e4c6b \
+ --hash=sha256:977ce557d79577a3dd510844904d5d968bfef9489f512be65e2882e1c6eed7d8 \
+ --hash=sha256:994ce5a7b3d20981b81d83618aa4882f955bfa573efdbef033d5632b58597ba9 \
+ --hash=sha256:9ad5895938a894c368d49d8470fe9f519909e5ebc6b8f8ea5190bd0df6aa4271 \
+ --hash=sha256:9eb0a1923354e0fdd1c8a6f53f5db2e6180d670e2b587914bf2e79fa8acfd003 \
+ --hash=sha256:a00284dbfb53b42e35c7dd99fc0e26ef89b4a34efff68078ed29d03ccb28402a \
+ --hash=sha256:a11a2c019324fc111485e79d55907e7289e53d0031275a6c8daed30690bc50c0 \
+ --hash=sha256:ab6a0fe4c96f8058d41948ddf134420d3ef8c42d5508b5a341a440cce7a37a1d \
+ --hash=sha256:b1d0a1bce919de0dd8da5cff4e616b2d9e6ebf3bd1410ff645318c3dd615010a \
+ --hash=sha256:b8e4f15b672c9156c1154249a9c5746e86ac9ae9edc3799ee3afebc323d9d9e0 \
+ --hash=sha256:bbca34dca5a2d60f81326d908d77313816fad23d11b6069031a3d6b8c97a54f9 \
+ --hash=sha256:bf656cd74ff7b4ed7006cdb2a6728150aaad69c7242b42a2a532f77b63ea233f \
+ --hash=sha256:c95257aa2ccf75d3d91d772060538d5fea7f625e48157f8ca44594f94d41cb33 \
+ --hash=sha256:dc5023be1c2a8b0a0ab5e31389e62c28b2453eb31dd069f4b8d1a0f9814d951a \
+ --hash=sha256:e14bceb1f3ae8a14374be2b2d7bc12a59226872285f91d66d301e5f41705d4d6 \
+ --hash=sha256:e3c4f5211394cd0bf6874ac5d29684a495f9c374919833dcfff0bd6d37f96201 \
+ --hash=sha256:e76f017b6d4140a038c5ff12be1581183d7874e41f1c0af58ecf07748d36a336 \
+ --hash=sha256:e7d5606b9240ed4def9cbdf35be4308047d11e858b9c88a6c26974758d6225ce \
+ --hash=sha256:f0f80e323a17af63eac6a9db0c9188c10f1fd815c3ab299727150cc0eb92c7a4 \
+ --hash=sha256:fb2fa2f6506c03c48ca42e3fe5a692d7470d290c047ee6de7c0f3e5fa7639ac9 \
+ --hash=sha256:ffa8fee2b1b9e60b531c4c27cf528d6b5d5da46b1730db1f4d6eee56ff282e07
# via
# -r requirements/dev.in
# pytest-cov
@@ -91,9 +104,9 @@ distlib==0.3.3 \
--hash=sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31 \
--hash=sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05
# via virtualenv
-filelock==3.3.1 \
- --hash=sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f \
- --hash=sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f
+filelock==3.3.2 \
+ --hash=sha256:7afc856f74fa7006a289fd10fa840e1eebd8bbff6bffb69c26c54a0512ea8cf8 \
+ --hash=sha256:bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b
# via virtualenv
h11==0.12.0 \
--hash=sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6 \
@@ -133,9 +146,9 @@ httpx==0.20.0 \
# via
# -c requirements/main.txt
# -r requirements/dev.in
-identify==2.3.1 \
- --hash=sha256:5a5000bd3293950d992843c0ef3d82b90a582de2161557bda7f493c8c8864f26 \
- --hash=sha256:8a92c56893e9a4ce951f09a50489986615e3eba7b4c60610e0b25f93ca4487ba
+identify==2.3.3 \
+ --hash=sha256:b9ffbeb7ed87e96ce017c66b80ca04fda3adbceb5c74e54fc7d99281d27d0859 \
+ --hash=sha256:ffab539d9121b386ffdea84628ff3eefda15f520f392ce11b393b0a909632cdf
# via pre-commit
idna==3.3 \
--hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
@@ -181,9 +194,9 @@ nodeenv==1.6.0 \
--hash=sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b \
--hash=sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7
# via pre-commit
-packaging==21.0 \
- --hash=sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7 \
- --hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14
+packaging==21.2 \
+ --hash=sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966 \
+ --hash=sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0
# via pytest
platformdirs==2.4.0 \
--hash=sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2 \
@@ -201,9 +214,9 @@ py==1.10.0 \
--hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \
--hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a
# via pytest
-pyparsing==3.0.1 \
- --hash=sha256:84196357aa3566d64ad123d7a3c67b0e597a115c4934b097580e5ce220b91531 \
- --hash=sha256:fd93fc45c47893c300bd98f5dd1b41c0e783eaeb727e7cea210dcc09d64ce7c3
+pyparsing==2.4.7 \
+ --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \
+ --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b
# via packaging
pytest==6.2.5 \
--hash=sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89 \
@@ -294,6 +307,10 @@ tomli==1.2.2 \
--hash=sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee \
--hash=sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade
# via coverage
+types-pyyaml==6.0.0 \
+ --hash=sha256:3d3591ddfc488fc30be3c506a0c0fe54da968fe98d8b76ab12e59d455330ffca \
+ --hash=sha256:746f23d351245d176d7bc89eef79e2ee94b4e7306f7d23bfefb3dc946c0fb58d
+ # via -r requirements/dev.in
typing-extensions==3.10.0.2 \
--hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
--hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \
@@ -327,9 +344,9 @@ uvloop==0.16.0 \
# via
# -c requirements/main.txt
# uvicorn
-virtualenv==20.9.0 \
- --hash=sha256:1d145deec2da86b29026be49c775cc5a9aab434f85f7efef98307fb3965165de \
- --hash=sha256:bb55ace18de14593947354e5e6cd1be75fb32c3329651da62e92bf5d0aab7213
+virtualenv==20.10.0 \
+ --hash=sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814 \
+ --hash=sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218
# via pre-commit
watchgod==0.7 \
--hash=sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29 \
diff --git a/requirements/main.in b/requirements/main.in
index 4241513..68b278a 100644
--- a/requirements/main.in
+++ b/requirements/main.in
@@ -10,6 +10,7 @@ fastapi
gunicorn
starlette
uvicorn[standard]
+PyYAML
# Other dependencies.
safir
diff --git a/requirements/main.txt b/requirements/main.txt
index 283f073..a41b6f2 100644
--- a/requirements/main.txt
+++ b/requirements/main.txt
@@ -241,14 +241,16 @@ pyyaml==6.0 \
--hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
--hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
--hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
- # via uvicorn
+ # via
+ # -r requirements/main.in
+ # uvicorn
rfc3986[idna2008]==1.5.0 \
--hash=sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835 \
--hash=sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97
# via httpx
-safir==2.1.0 \
- --hash=sha256:4e8094f58f61d0cc2ee75e3c17651735350dfcda3e389986c887fb2c5be34fd2 \
- --hash=sha256:e92b2e9226d185e34b11c18c00e38a1d9b362cbf1ba128379eab730cdfce4939
+safir==2.1.1 \
+ --hash=sha256:6c33d7dfe9509fc6a14637c7078c107a941824d084bcd51a652dd7065d87efa4 \
+ --hash=sha256:8de37d4e0d1de0175bb803ad0b9ae7810841241f4697ca4ffd35e3da77d10f83
# via -r requirements/main.in
sniffio==1.2.0 \
--hash=sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663 \
From 17edd1c7e35fc5590223bfa6bb9ea1fac5a51c64 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Tue, 2 Nov 2021 21:20:11 -0400
Subject: [PATCH 12/26] Configure path to file with GitHub auth rules
---
src/ltdproxy/config.py | 4 +++-
tests/githubauth.example.yaml | 11 +++++++++++
tox.ini | 1 +
3 files changed, 15 insertions(+), 1 deletion(-)
create mode 100644 tests/githubauth.example.yaml
diff --git a/src/ltdproxy/config.py b/src/ltdproxy/config.py
index 4afdff6..b60b179 100644
--- a/src/ltdproxy/config.py
+++ b/src/ltdproxy/config.py
@@ -5,7 +5,7 @@
import os
from enum import Enum
-from pydantic import BaseSettings, Field, SecretStr
+from pydantic import BaseSettings, Field, FilePath, SecretStr
__all__ = ["Configuration", "config", "Profile", "LogLevel"]
@@ -61,6 +61,8 @@ class Configuration(BaseSettings):
session_key: SecretStr = Field(env="LTDPROXY_SESSION_KEY")
+ github_auth_config_path: FilePath = Field(env="LTDPROXY_AUTH_CONFIG")
+
config = Configuration(_env_file=os.getenv("LTD_PROXY_ENV"))
"""Configuration for ltd-proxy."""
diff --git a/tests/githubauth.example.yaml b/tests/githubauth.example.yaml
new file mode 100644
index 0000000..e0a7cce
--- /dev/null
+++ b/tests/githubauth.example.yaml
@@ -0,0 +1,11 @@
+default:
+ - org: "jsickcodes"
+paths:
+ - pattern: "\/a\/"
+ authorized:
+ - org: "jsickcodes"
+ team: "Red Team"
+ - pattern: "\/b\/"
+ authorized:
+ - org: "jsickcodes"
+ team: "Blue Team"
diff --git a/tox.ini b/tox.ini
index 9c7b93b..68c6d4b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,6 +13,7 @@ setenv =
LTDPROXY_GITHUB_OAUTH_ID = foo
LTDPROXY_GITHUB_OAUTH_SECRET = bar
LTDPROXY_SESSION_KEY = 1234
+ LTDPROXY_AUTH_CONFIG = tests/githubauth.example.yaml
commands =
pytest --cov=ltdproxy --cov-branch --cov-report= {posargs}
From d4de567fabc0642b8bed6a7bc1b100891ba05eec Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Wed, 3 Nov 2021 13:42:11 -0400
Subject: [PATCH 13/26] Add GitHubAuth
The purpose of this class is to parse the github auth configuration
file, and then judge if a user's cookie has the appropriate memberships
to allow a user to access a given URL path.
---
src/ltdproxy/githubauth.py | 205 ++++++++++++++++++++++++++++++++++++-
tests/githubauth_test.py | 135 ++++++++++++++++++++++++
2 files changed, 335 insertions(+), 5 deletions(-)
create mode 100644 tests/githubauth_test.py
diff --git a/src/ltdproxy/githubauth.py b/src/ltdproxy/githubauth.py
index dd0c9c0..23166f2 100644
--- a/src/ltdproxy/githubauth.py
+++ b/src/ltdproxy/githubauth.py
@@ -3,15 +3,30 @@
from __future__ import annotations
import json
-from typing import TYPE_CHECKING, Any, Dict, List, Tuple, TypeVar
+from enum import Enum
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ List,
+ Optional,
+ Pattern,
+ Set,
+ Tuple,
+ TypeVar,
+)
import authlib.integrations.starlette_client.integration
import gidgethub.httpx
+import yaml
from authlib.integrations.starlette_client import OAuth
+from pydantic import BaseModel
from ltdproxy.config import config
if TYPE_CHECKING:
+ from pathlib import Path
+
import httpx
__all__ = [
@@ -72,10 +87,10 @@ async def set_serialized_github_memberships(
"""Add JSON-serialized GitHub organization and team memberships to the
request session.
"""
- # Stubs for configuration of GitHub teams and organizations relevant to
- # authorization settings
- relevant_orgs = ["jsickcodes"]
- relevant_teams = [("jsickcodes", "proxy-team")]
+ # These orgs and teams are mentioned in the GitHub Auth configuration,
+ # and therefore are ones to pay attention to in the cookie.
+ relevant_orgs = github_auth.relevant_orgs
+ relevant_teams = github_auth.relevant_teams
github_client = gidgethub.httpx.GitHubAPI(
http_client, "ltd-proxy", oauth_token=github_token
@@ -97,3 +112,183 @@ async def set_serialized_github_memberships(
# Serialize memberships to JSON to pack inside the session cookie
memberships = json.dumps({"orgs": user_orgs, "teams": user_teams})
session["github_memberships"] = memberships
+
+
+class GitHubGroup(BaseModel):
+ """A model for a GitHub group configuration, either an entire organization
+ or a team within an organization.
+ """
+
+ org: str
+ """A GitHub organization's slug."""
+
+ team: Optional[str] = None
+ """The name of a team within an organization."""
+
+ @property
+ def is_team(self) -> bool:
+ if self.team:
+ return True
+ else:
+ return False
+
+
+class PathRule(BaseModel):
+ """A model for a URL path and authorized entities."""
+
+ pattern: Pattern
+ """Regular expression pattern that matches a path."""
+
+ authorized: List[GitHubGroup]
+ """A list fo GitHub groups (teams and/or organizations) that are
+ authorized to access this path.
+ """
+
+ def path_matches(self, url_path: str) -> bool:
+ """Test if a URL path matches the rule's patten."""
+ if self.pattern.match(url_path):
+ return True
+ else:
+ return False
+
+ def is_user_authorized(
+ self, *, user_orgs: List[str], user_teams: List[Tuple[str, str]]
+ ) -> bool:
+ """Test if a user is authorized for this path.
+
+ The parameters come from the ``github_memberships`` attribute of
+ the session cookie, after parsing from JSON.
+ """
+ for authorized_group in self.authorized:
+ if authorized_group.is_team:
+ authorized_team_id = (
+ authorized_group.org,
+ authorized_group.team,
+ )
+ if authorized_team_id in user_teams:
+ return True
+ else:
+ if authorized_group.org in user_orgs:
+ return True
+
+ # no matches
+ return False
+
+
+class AuthResult(str, Enum):
+ """The authentication/authorization result."""
+
+ authorized = "authorized"
+ unauthorized = "unauthorized"
+ unauthenticated = "unauthenticated"
+
+
+class GitHubAuth(BaseModel):
+ """A model for the GitHubAuth configuration file, with methods for
+ determining if a requester is authorized to view a given path.
+ """
+
+ default: List[GitHubGroup]
+ """Default authorized groups if a path does not match."""
+
+ paths: List[PathRule]
+ """A list of path expressions and the groups that are authorized to
+ access those paths.
+ """
+
+ @classmethod
+ def parse_yaml(cls, path: Path) -> GitHubAuth:
+ """Parse the YAML representation of this configuration model."""
+ data = yaml.safe_load(path.read_text())
+ return cls.parse_obj(data)
+
+ def is_user_authorized(
+ self,
+ *,
+ url_path: str,
+ user_orgs: List[str],
+ user_teams: List[Tuple[str, str]],
+ ) -> bool:
+ for path_rule in self.paths:
+ if path_rule.path_matches(url_path):
+ if path_rule.is_user_authorized(
+ user_orgs=user_orgs, user_teams=user_teams
+ ):
+ return True
+ else:
+ return False
+
+ # Fallback to the default authorizations
+ for authed_group in self.default:
+ if authed_group.is_team:
+ authorized_team_id = (authed_group.org, authed_group.team)
+ if authorized_team_id in user_teams:
+ return True
+ else:
+ if authed_group.org in user_orgs:
+ return True
+
+ return False
+
+ def is_session_authorized(
+ self, *, path: str, session: Dict[Any, Any]
+ ) -> AuthResult:
+ try:
+ github_memberships_data = session["github_memberships"]
+ except KeyError:
+ return AuthResult.unauthenticated
+
+ parsed_memberships = json.loads(github_memberships_data)
+ user_orgs = parsed_memberships["orgs"]
+ # This typechecks/validates the teams data structure
+ user_teams = [
+ (str(t[0]), str(t[1])) for t in parsed_memberships["teams"]
+ ]
+ if self.is_user_authorized(
+ url_path=path, user_orgs=user_orgs, user_teams=user_teams
+ ):
+ return AuthResult.authorized
+ else:
+ return AuthResult.unauthorized
+
+ @property
+ def relevant_orgs(self) -> Set[str]:
+ """Get all GitHub organizations mentioned in the configuration."""
+ all_orgs: Set[str] = set()
+
+ for github_group in self.default:
+ if not github_group.is_team:
+ all_orgs.add(github_group.org)
+
+ for path_rule in self.paths:
+ for github_group in path_rule.authorized:
+ if not github_group.is_team:
+ all_orgs.add(github_group.org)
+
+ return all_orgs
+
+ @property
+ def relevant_teams(self) -> Set[Tuple[str, str]]:
+ """Get all GitHub teams mentioned in the configuration."""
+ all_teams: Set[Tuple[str, str]] = set()
+
+ for github_group in self.default:
+ if github_group.is_team:
+ assert isinstance(github_group.team, str) # mypy cue
+ all_teams.add((github_group.org, github_group.team))
+
+ for path_rule in self.paths:
+ for github_group in path_rule.authorized:
+ if github_group.is_team:
+ assert isinstance(github_group.team, str) # mypy cue
+ all_teams.add((github_group.org, github_group.team))
+
+ return all_teams
+
+
+github_auth = GitHubAuth.parse_yaml(config.github_auth_config_path)
+"""FastAPI dependency providing the GitHub auth rules for different paths."""
+
+
+async def github_auth_dependency() -> GitHubAuth:
+ return github_auth
diff --git a/tests/githubauth_test.py b/tests/githubauth_test.py
new file mode 100644
index 0000000..08e7357
--- /dev/null
+++ b/tests/githubauth_test.py
@@ -0,0 +1,135 @@
+"""Test the githubauth module."""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+from ltdproxy.githubauth import AuthResult, GitHubAuth, GitHubGroup, PathRule
+
+
+def test_path_rule() -> None:
+ org_group = GitHubGroup(org="jsickcodes")
+ rule1 = PathRule(pattern=r"\/a\/", authorized=[org_group])
+ assert rule1.path_matches("/a/hello-world.html")
+ assert not rule1.path_matches("/b/hello-world.html")
+
+
+def test_githubauth_example_yaml() -> None:
+ """Test GitHubAuth class with the example YAML file,
+ tests/githubauth.example.yaml.
+ """
+ example_path = Path(__file__).parent / "githubauth.example.yaml"
+ assert example_path.is_file()
+
+ github_auth = GitHubAuth.parse_yaml(example_path)
+
+ assert github_auth.relevant_orgs == set(["jsickcodes"])
+ assert github_auth.relevant_teams == set(
+ [
+ ("jsickcodes", "Red Team"),
+ ("jsickcodes", "Blue Team"),
+ ]
+ )
+
+ # Testing the default rule
+ assert (
+ github_auth.is_user_authorized(
+ url_path="/xyz", user_orgs=["jsickcodes"], user_teams=[]
+ )
+ is True
+ )
+
+ # Testing the default rule
+ assert (
+ github_auth.is_user_authorized(
+ url_path="/xyz", user_orgs=["jsickwrites"], user_teams=[]
+ )
+ is False
+ )
+
+ # Testing the path rule for /a/
+ assert (
+ github_auth.is_user_authorized(
+ url_path="/a/index.html", user_orgs=["jsickcodes"], user_teams=[]
+ )
+ is False
+ )
+ assert (
+ github_auth.is_user_authorized(
+ url_path="/a/index.html",
+ user_orgs=["jsickcodes"],
+ user_teams=[("jsickcodes", "Blue Team")],
+ )
+ is False
+ )
+ assert (
+ github_auth.is_user_authorized(
+ url_path="/a/index.html",
+ user_orgs=["jsickcodes"],
+ user_teams=[
+ ("jsickcodes", "Red Team"),
+ ("jsickcodes", "Blue Team"),
+ ],
+ )
+ is True
+ )
+
+ # Test if the session auth cookie is empty
+ assert (
+ github_auth.is_session_authorized(path="/xyz", session={})
+ is AuthResult.unauthenticated
+ )
+
+ # Test if the session auth cookie doesn't have the right membership
+ assert (
+ github_auth.is_session_authorized(
+ path="/xyz",
+ session={
+ "github_memberships": json.dumps(
+ {"orgs": "acompany", "teams": []}
+ )
+ },
+ )
+ is AuthResult.unauthorized
+ )
+
+ # Test if the session auth cookie *does* have the right membership
+ assert (
+ github_auth.is_session_authorized(
+ path="/xyz",
+ session={
+ "github_memberships": json.dumps(
+ {"orgs": "jsickcodes", "teams": []}
+ )
+ },
+ )
+ is AuthResult.authorized
+ )
+
+ assert (
+ github_auth.is_session_authorized(
+ path="/a/hello",
+ session={
+ "github_memberships": json.dumps(
+ {
+ "orgs": "jsickcodes",
+ "teams": [["jsickcodes", "Red Team"]],
+ }
+ )
+ },
+ )
+ is AuthResult.authorized
+ )
+
+ assert (
+ github_auth.is_session_authorized(
+ path="/a/hello",
+ session={
+ "github_memberships": json.dumps(
+ {"orgs": "jsickcodes", "teams": []}
+ )
+ },
+ )
+ is AuthResult.unauthorized
+ )
From f8bad487e8344b33b18fad88ae482e494088999d Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Wed, 3 Nov 2021 15:37:41 -0400
Subject: [PATCH 14/26] Implement GitHub auth on the proxy handler
---
src/ltdproxy/handlers/external.py | 57 +++++++++++++++++++------------
1 file changed, 36 insertions(+), 21 deletions(-)
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index bc81531..61fabad 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -4,7 +4,7 @@
import httpx
from authlib.integrations.starlette_client import OAuthError
-from fastapi import APIRouter, Depends
+from fastapi import APIRouter, Depends, HTTPException
from safir.dependencies.http_client import http_client_dependency
from safir.dependencies.logger import logger_dependency
from starlette.background import BackgroundTask
@@ -18,7 +18,10 @@
from ltdproxy.config import config
from ltdproxy.githubauth import (
+ AuthResult,
+ GitHubAuth,
GitHubOAuthType,
+ github_auth_dependency,
github_oauth_dependency,
set_serialized_github_memberships,
)
@@ -30,7 +33,7 @@
"""FastAPI router for all external handlers."""
-@external_router.get("/")
+@external_router.get("/", name="homepage")
async def homepage(request: Request) -> HTMLResponse:
github_token = request.session.get("github_token")
if github_token:
@@ -39,7 +42,7 @@ async def homepage(request: Request) -> HTMLResponse:
return HTMLResponse('login')
-@external_router.get("/auth")
+@external_router.get("/auth", name="get_oauth_callback")
async def get_oauth_callback(
request: Request,
logger: BoundLogger = Depends(logger_dependency),
@@ -58,7 +61,7 @@ async def get_oauth_callback(
)
if github_token:
request.session["github_token"] = github_token
- set_serialized_github_memberships(
+ await set_serialized_github_memberships(
http_client=http_client,
session=request.session,
github_token=github_token,
@@ -66,7 +69,7 @@ async def get_oauth_callback(
return RedirectResponse(url="/ltdproxy/")
-@external_router.get("/login")
+@external_router.get("/login", name="login")
async def login(
request: Request,
logger: BoundLogger = Depends(logger_dependency),
@@ -78,37 +81,49 @@ async def login(
return await github_oauth.authorize_redirect(request, redirect_uri)
-@external_router.get("/logout")
+@external_router.get("/logout", name="logout")
async def logout(
request: Request,
logger: BoundLogger = Depends(logger_dependency),
) -> RedirectResponse:
request.session.pop("github_token", None)
+ request.session.pop("github_memberships", None)
logger.info("Logged out")
return RedirectResponse(url="/ltdproxy/")
@external_router.get(
- "/{path:path}",
- description="The S3 front-end proxy.",
+ "/{path:path}", description="The S3 front-end proxy.", name="proxy"
)
async def get_s3(
path: str,
+ request: Request,
logger: BoundLogger = Depends(logger_dependency),
bucket: Bucket = Depends(bucket_dependency),
http_client: httpx.AsyncClient = Depends(http_client_dependency),
-) -> StreamingResponse:
+ github_auth: GitHubAuth = Depends(github_auth_dependency),
+) -> Union[StreamingResponse, RedirectResponse]:
"""The S3 proxy endpoint."""
- bucket_path = f"{config.s3_bucket_prefix}{path}"
- stream = await bucket.stream_object(http_client, bucket_path)
- logger.info("stream headers", headers=stream.headers)
- response_headers = {
- "Content-type": stream.headers["Content-type"],
- "Content-length": stream.headers["Content-length"],
- "Etag": stream.headers["Etag"],
- }
- return StreamingResponse(
- stream.aiter_raw(),
- background=BackgroundTask(stream.aclose),
- headers=response_headers,
+ github_auth_result = github_auth.is_session_authorized(
+ path=f"/{path}", session=request.session
)
+ if github_auth_result == AuthResult.unauthenticated:
+ return RedirectResponse(url=request.url_for("login"))
+ elif github_auth_result == AuthResult.unauthorized:
+ raise HTTPException(status_code=403, detail="Not authorized")
+ elif github_auth_result == AuthResult.authorized:
+ bucket_path = f"{config.s3_bucket_prefix}{path}"
+ stream = await bucket.stream_object(http_client, bucket_path)
+ logger.info("stream headers", headers=stream.headers)
+ response_headers = {
+ "Content-type": stream.headers["Content-type"],
+ "Content-length": stream.headers["Content-length"],
+ "Etag": stream.headers["Etag"],
+ }
+ return StreamingResponse(
+ stream.aiter_raw(),
+ background=BackgroundTask(stream.aclose),
+ headers=response_headers,
+ )
+ else:
+ raise HTTPException(status_code=500, detail="Internal auth error")
From e96a1a45c303753a97615c53a8cb167410368f74 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Wed, 3 Nov 2021 16:36:43 -0400
Subject: [PATCH 15/26] Drop tests for internal handlers
We'll make these internal handlers optional to make it possible to serve
from the application root (and hence the internal handlers won't exist).
We'll re-add these types of tests once the health check endpoints are
added again.
---
tests/handlers/__init__.py | 0
tests/handlers/internal_test.py | 25 -------------------------
2 files changed, 25 deletions(-)
delete mode 100644 tests/handlers/__init__.py
delete mode 100644 tests/handlers/internal_test.py
diff --git a/tests/handlers/__init__.py b/tests/handlers/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/handlers/internal_test.py b/tests/handlers/internal_test.py
deleted file mode 100644
index bc818fc..0000000
--- a/tests/handlers/internal_test.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""Tests for the ltdproxy.handlers.internal module and routes."""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import pytest
-
-from ltdproxy.config import config
-
-if TYPE_CHECKING:
- from httpx import AsyncClient
-
-
-@pytest.mark.asyncio
-async def test_get_index(client: AsyncClient) -> None:
- """Test ``GET /``"""
- response = await client.get("/")
- assert response.status_code == 200
- data = response.json()
- assert data["name"] == config.name
- assert isinstance(data["version"], str)
- assert isinstance(data["description"], str)
- assert isinstance(data["repository_url"], str)
- assert isinstance(data["documentation_url"], str)
From 935bec0b986867d6a8f8929ec7c55bec1b8699eb Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Wed, 3 Nov 2021 16:39:51 -0400
Subject: [PATCH 16/26] Add path_prefix configuration to serve from root
This configuration lets us break from the "Safir" app pattern and now
serve from the application root path with only the "external" routes, or
serve both internal and external routes if a non-trivial prefix path is
set.
---
src/ltdproxy/appsetup.py | 29 +++++++++++++++++++++++++++++
src/ltdproxy/config.py | 2 ++
src/ltdproxy/main.py | 22 ++++------------------
3 files changed, 35 insertions(+), 18 deletions(-)
create mode 100644 src/ltdproxy/appsetup.py
diff --git a/src/ltdproxy/appsetup.py b/src/ltdproxy/appsetup.py
new file mode 100644
index 0000000..42f1896
--- /dev/null
+++ b/src/ltdproxy/appsetup.py
@@ -0,0 +1,29 @@
+"""Configuration for the app."""
+
+from __future__ import annotations
+
+from importlib.metadata import metadata
+from typing import TYPE_CHECKING
+
+from .handlers.external import external_router
+from .handlers.internal import internal_router
+
+if TYPE_CHECKING:
+ from fastapi import FastAPI
+
+ from ltdproxy.config import Configuration
+
+
+def add_handlers(*, config: Configuration, app: FastAPI) -> None:
+ if config.path_prefix == "/":
+ app.include_router(external_router)
+ else:
+ external_app = FastAPI(
+ title="ltd-proxy",
+ description=metadata("ltd-proxy").get("Summary", ""),
+ version=metadata("ltd-proxy").get("Version", "0.0.0"),
+ )
+ external_app.include_router(external_router)
+
+ app.include_router(internal_router)
+ app.mount(f"{config.path_prefix}", external_app)
diff --git a/src/ltdproxy/config.py b/src/ltdproxy/config.py
index b60b179..df6095e 100644
--- a/src/ltdproxy/config.py
+++ b/src/ltdproxy/config.py
@@ -63,6 +63,8 @@ class Configuration(BaseSettings):
github_auth_config_path: FilePath = Field(env="LTDPROXY_AUTH_CONFIG")
+ path_prefix: str = Field("/", env="LTDPROXY_PATH_PREFIX")
+
config = Configuration(_env_file=os.getenv("LTD_PROXY_ENV"))
"""Configuration for ltd-proxy."""
diff --git a/src/ltdproxy/main.py b/src/ltdproxy/main.py
index f494806..4833fef 100644
--- a/src/ltdproxy/main.py
+++ b/src/ltdproxy/main.py
@@ -7,21 +7,17 @@
called.
"""
-from importlib.metadata import metadata
-
from fastapi import FastAPI
from safir.dependencies.http_client import http_client_dependency
from safir.logging import configure_logging
from safir.middleware.x_forwarded import XForwardedMiddleware
from starlette.middleware.sessions import SessionMiddleware
+from .appsetup import add_handlers
from .config import config
-from .handlers.external import external_router
-from .handlers.internal import internal_router
__all__ = ["app", "config"]
-
configure_logging(
profile=config.profile,
log_level=config.log_level,
@@ -31,22 +27,12 @@
app = FastAPI()
"""The main FastAPI application for ltd-proxy."""
-# Define the external routes in a subapp so that it will serve its own OpenAPI
-# interface definition and documentation URLs under the external URL.
-external_app = FastAPI(
- title="ltd-proxy",
- description=metadata("ltd-proxy").get("Summary", ""),
- version=metadata("ltd-proxy").get("Version", "0.0.0"),
-)
-external_app.include_router(external_router)
-external_app.add_middleware(
+add_handlers(app=app, config=config)
+
+app.add_middleware(
SessionMiddleware, secret_key=config.session_key.get_secret_value()
)
-# Attach the internal routes and subapp to the main application.
-app.include_router(internal_router)
-app.mount(f"/{config.name}", external_app)
-
@app.on_event("startup")
async def startup_event() -> None:
From 2d4faad8fd0be16a705129a471432269e0e5df15 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Wed, 3 Nov 2021 16:41:25 -0400
Subject: [PATCH 17/26] Compute redirect URLs using request.url_for
Needed since the app can now be served from both a prefix path or the
root.
---
src/ltdproxy/handlers/external.py | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index 61fabad..be00356 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -37,9 +37,12 @@
async def homepage(request: Request) -> HTMLResponse:
github_token = request.session.get("github_token")
if github_token:
- html = "hello!" 'logout'
+ html = (
+ "hello!"
+ f'logout'
+ )
return HTMLResponse(html)
- return HTMLResponse('login')
+ return HTMLResponse(f'login')
@external_router.get("/auth", name="get_oauth_callback")
@@ -53,8 +56,6 @@ async def get_oauth_callback(
token = await github_oauth.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f"{error.error}
")
- print(token)
- print(type(token))
github_token = token.get("access_token")
logger.info(
"Got github oauth token", token=token, access_token=github_token
@@ -66,7 +67,7 @@ async def get_oauth_callback(
session=request.session,
github_token=github_token,
)
- return RedirectResponse(url="/ltdproxy/")
+ return RedirectResponse(url=request.url_for("homepage"))
@external_router.get("/login", name="login")
@@ -76,7 +77,7 @@ async def login(
github_oauth: GitHubOAuthType = Depends(github_oauth_dependency),
) -> RedirectResponse:
# redirect_uri = request.url_for('get_oauth_callback')
- redirect_uri = "http://127.0.0.1:8000/ltdproxy/auth"
+ redirect_uri = "http://127.0.0.1:8000/auth" # FIXME add config
logger.info("Redirecting to GitHub auth", callback_url=redirect_uri)
return await github_oauth.authorize_redirect(request, redirect_uri)
@@ -89,7 +90,7 @@ async def logout(
request.session.pop("github_token", None)
request.session.pop("github_memberships", None)
logger.info("Logged out")
- return RedirectResponse(url="/ltdproxy/")
+ return RedirectResponse(url=request.url_for("homepage"))
@external_router.get(
From bbe8b33ebd300be57af9ee9d519627f22ffe69fc Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Wed, 3 Nov 2021 16:48:46 -0400
Subject: [PATCH 18/26] Add configuration for the GitHub OAuth callback
---
src/ltdproxy/config.py | 6 +++++-
src/ltdproxy/handlers/external.py | 3 +--
tox.ini | 1 +
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/ltdproxy/config.py b/src/ltdproxy/config.py
index df6095e..5ca1c2c 100644
--- a/src/ltdproxy/config.py
+++ b/src/ltdproxy/config.py
@@ -5,7 +5,7 @@
import os
from enum import Enum
-from pydantic import BaseSettings, Field, FilePath, SecretStr
+from pydantic import BaseSettings, Field, FilePath, HttpUrl, SecretStr
__all__ = ["Configuration", "config", "Profile", "LogLevel"]
@@ -59,6 +59,10 @@ class Configuration(BaseSettings):
env="LTDPROXY_GITHUB_OAUTH_SECRET"
)
+ github_oauth_callback_url: HttpUrl = Field(
+ env="LTDPROXY_GITHUB_CALLBACK_URL"
+ )
+
session_key: SecretStr = Field(env="LTDPROXY_SESSION_KEY")
github_auth_config_path: FilePath = Field(env="LTDPROXY_AUTH_CONFIG")
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index be00356..fb22b8c 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -76,8 +76,7 @@ async def login(
logger: BoundLogger = Depends(logger_dependency),
github_oauth: GitHubOAuthType = Depends(github_oauth_dependency),
) -> RedirectResponse:
- # redirect_uri = request.url_for('get_oauth_callback')
- redirect_uri = "http://127.0.0.1:8000/auth" # FIXME add config
+ redirect_uri = str(config.github_oauth_callback_url)
logger.info("Redirecting to GitHub auth", callback_url=redirect_uri)
return await github_oauth.authorize_redirect(request, redirect_uri)
diff --git a/tox.ini b/tox.ini
index 68c6d4b..8b69a31 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,6 +12,7 @@ setenv =
LTDPROXY_AWS_SECRET_ACCESS_KEY = bar
LTDPROXY_GITHUB_OAUTH_ID = foo
LTDPROXY_GITHUB_OAUTH_SECRET = bar
+ LTDPROXY_GITHUB_CALLBACK_URL = http://127.0.0.1:8000/auth
LTDPROXY_SESSION_KEY = 1234
LTDPROXY_AUTH_CONFIG = tests/githubauth.example.yaml
commands =
From b9cdb58daaa7dbf2bce415b121477123e1360a52 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 4 Nov 2021 12:35:53 -0400
Subject: [PATCH 19/26] Redirect users to the original page after login
---
src/ltdproxy/handlers/external.py | 49 +++++++++++++++++++++++++++----
1 file changed, 44 insertions(+), 5 deletions(-)
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index fb22b8c..950539c 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -1,6 +1,7 @@
"""Handlers for the app's external root, ``/ltdproxy/``."""
-from typing import Union
+from typing import Optional, Union
+from urllib.parse import urlencode, urlparse
import httpx
from authlib.integrations.starlette_client import OAuthError
@@ -47,6 +48,7 @@ async def homepage(request: Request) -> HTMLResponse:
@external_router.get("/auth", name="get_oauth_callback")
async def get_oauth_callback(
+ ref: Optional[str],
request: Request,
logger: BoundLogger = Depends(logger_dependency),
github_oauth: GitHubOAuthType = Depends(github_oauth_dependency),
@@ -67,22 +69,46 @@ async def get_oauth_callback(
session=request.session,
github_token=github_token,
)
- return RedirectResponse(url=request.url_for("homepage"))
+
+ # Compute redirect URL
+ if ref:
+ # The original callback URL included the "ref" query parameter with
+ # the referring page's URL. We'll redirect to that.
+ redirect_url = ref
+ else:
+ # Default redirect.
+ redirect_url = request.url_for("homepage")
+
+ return RedirectResponse(url=redirect_url)
@external_router.get("/login", name="login")
-async def login(
+async def get_login(
+ ref: Optional[str],
request: Request,
logger: BoundLogger = Depends(logger_dependency),
github_oauth: GitHubOAuthType = Depends(github_oauth_dependency),
) -> RedirectResponse:
+ """Log a user in by redirecting to GitHub OAuth."""
redirect_uri = str(config.github_oauth_callback_url)
+ if ref:
+ # The ref query string can be set to point to the page that
+ # asked for the login.
+ # Make sure return return url is in same domain as this request
+ # (i.e., only redirect when on same site)
+ if urlparse(ref).netloc == request.url.netloc:
+ redirect_uri = (
+ urlparse(redirect_uri)
+ ._replace(query=urlencode({"ref": ref}, doseq=True))
+ .geturl()
+ )
+
logger.info("Redirecting to GitHub auth", callback_url=redirect_uri)
return await github_oauth.authorize_redirect(request, redirect_uri)
@external_router.get("/logout", name="logout")
-async def logout(
+async def get_logout(
request: Request,
logger: BoundLogger = Depends(logger_dependency),
) -> RedirectResponse:
@@ -107,11 +133,22 @@ async def get_s3(
github_auth_result = github_auth.is_session_authorized(
path=f"/{path}", session=request.session
)
+
if github_auth_result == AuthResult.unauthenticated:
- return RedirectResponse(url=request.url_for("login"))
+ # User is not authenticated so redirect to the login page with
+ # this page's URL as the ref query string so they'll get redirect
+ # back here after login.
+ login_url = request.url_for("login")
+ ref_qs = urlencode({"ref": request.url}, doseq=True)
+ login_url = urlparse(login_url)._replace(query=ref_qs).geturl()
+ return RedirectResponse(url=login_url)
+
elif github_auth_result == AuthResult.unauthorized:
+ # User is not authorized.
raise HTTPException(status_code=403, detail="Not authorized")
+
elif github_auth_result == AuthResult.authorized:
+ # User is authorized; stream from S3.
bucket_path = f"{config.s3_bucket_prefix}{path}"
stream = await bucket.stream_object(http_client, bucket_path)
logger.info("stream headers", headers=stream.headers)
@@ -125,5 +162,7 @@ async def get_s3(
background=BackgroundTask(stream.aclose),
headers=response_headers,
)
+
else:
+ # Catch-all error
raise HTTPException(status_code=500, detail="Internal auth error")
From 02b8234d5f1408d85e4cc42b9d9c65096b60fea3 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 4 Nov 2021 14:31:17 -0400
Subject: [PATCH 20/26] Remove default homepage
Now / is send to the proxy endpoint.
Since there isn't an explicit homepage that's publicly available:
- Make logout redirect to a special logout page that is publicly
available.
- Make the default redirect from the oauth callback just redirect to "/"
if a ref page isn't set.
---
src/ltdproxy/handlers/external.py | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index 950539c..5f7c380 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -34,18 +34,6 @@
"""FastAPI router for all external handlers."""
-@external_router.get("/", name="homepage")
-async def homepage(request: Request) -> HTMLResponse:
- github_token = request.session.get("github_token")
- if github_token:
- html = (
- "hello!"
- f'logout'
- )
- return HTMLResponse(html)
- return HTMLResponse(f'login')
-
-
@external_router.get("/auth", name="get_oauth_callback")
async def get_oauth_callback(
ref: Optional[str],
@@ -77,7 +65,7 @@ async def get_oauth_callback(
redirect_url = ref
else:
# Default redirect.
- redirect_url = request.url_for("homepage")
+ redirect_url = request.url_for("/")
return RedirectResponse(url=redirect_url)
@@ -115,7 +103,17 @@ async def get_logout(
request.session.pop("github_token", None)
request.session.pop("github_memberships", None)
logger.info("Logged out")
- return RedirectResponse(url=request.url_for("homepage"))
+ return RedirectResponse(url=request.url_for("logged-out"))
+
+
+@external_router.get("/logged-out", name="logged-out")
+async def get_logged_out(
+ request: Request,
+) -> Union[HTMLResponse, RedirectResponse]:
+ if "github_memberships" in request.session:
+ # Not actually logged out yet so redirect to /logout first
+ return RedirectResponse(url=request.url_for("logout"))
+ return HTMLResponse("You're logged out.
")
@external_router.get(
From 20530d34b6d74861b2875b4aee102e97b6a9fac2 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 4 Nov 2021 14:34:11 -0400
Subject: [PATCH 21/26] Implement implicit index.html rewrites on S3
Make */ URLs get the */index.html object from the S3 bucket.
---
src/ltdproxy/handlers/external.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index 5f7c380..de6f6a1 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -147,7 +147,11 @@ async def get_s3(
elif github_auth_result == AuthResult.authorized:
# User is authorized; stream from S3.
- bucket_path = f"{config.s3_bucket_prefix}{path}"
+ if path == "" or path.endswith("/"):
+ # redwrite "*/" as "*/index.html" for static sites in S3
+ bucket_path = f"{config.s3_bucket_prefix}{path}index.html"
+ else:
+ bucket_path = f"{config.s3_bucket_prefix}{path}"
stream = await bucket.stream_object(http_client, bucket_path)
logger.info("stream headers", headers=stream.headers)
response_headers = {
From ffe2597da5071bf26b1df5b5c46b90c1d2115fc0 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 4 Nov 2021 14:37:00 -0400
Subject: [PATCH 22/26] Handle 404 status from S3
Eventually we should implement templated error pages, but this works for
now.
---
src/ltdproxy/handlers/external.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index de6f6a1..edfbf69 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -153,6 +153,8 @@ async def get_s3(
else:
bucket_path = f"{config.s3_bucket_prefix}{path}"
stream = await bucket.stream_object(http_client, bucket_path)
+ if stream.status_code == 404:
+ raise HTTPException(status_code=404, detail="Does not exist.")
logger.info("stream headers", headers=stream.headers)
response_headers = {
"Content-type": stream.headers["Content-type"],
From 543cbb9c4c80cd231ef43571990fae2911e89cf9 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 4 Nov 2021 14:38:36 -0400
Subject: [PATCH 23/26] Shift logging to debug level
---
src/ltdproxy/handlers/external.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/ltdproxy/handlers/external.py b/src/ltdproxy/handlers/external.py
index edfbf69..c5a257e 100644
--- a/src/ltdproxy/handlers/external.py
+++ b/src/ltdproxy/handlers/external.py
@@ -47,7 +47,7 @@ async def get_oauth_callback(
except OAuthError as error:
return HTMLResponse(f"{error.error}
")
github_token = token.get("access_token")
- logger.info(
+ logger.debug(
"Got github oauth token", token=token, access_token=github_token
)
if github_token:
@@ -91,7 +91,7 @@ async def get_login(
.geturl()
)
- logger.info("Redirecting to GitHub auth", callback_url=redirect_uri)
+ logger.debug("Redirecting to GitHub auth", callback_url=redirect_uri)
return await github_oauth.authorize_redirect(request, redirect_uri)
@@ -102,7 +102,7 @@ async def get_logout(
) -> RedirectResponse:
request.session.pop("github_token", None)
request.session.pop("github_memberships", None)
- logger.info("Logged out")
+ logger.debug("Logged out")
return RedirectResponse(url=request.url_for("logged-out"))
@@ -155,7 +155,7 @@ async def get_s3(
stream = await bucket.stream_object(http_client, bucket_path)
if stream.status_code == 404:
raise HTTPException(status_code=404, detail="Does not exist.")
- logger.info("stream headers", headers=stream.headers)
+ logger.debug("stream headers", headers=stream.headers)
response_headers = {
"Content-type": stream.headers["Content-type"],
"Content-length": stream.headers["Content-length"],
From a373867698d27b006ba64b4c38fc2c2ee3531c10 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 4 Nov 2021 14:41:26 -0400
Subject: [PATCH 24/26] Disable OpenAPI docs
These aren't needed for this application.
---
src/ltdproxy/appsetup.py | 1 +
src/ltdproxy/main.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/ltdproxy/appsetup.py b/src/ltdproxy/appsetup.py
index 42f1896..196a49c 100644
--- a/src/ltdproxy/appsetup.py
+++ b/src/ltdproxy/appsetup.py
@@ -22,6 +22,7 @@ def add_handlers(*, config: Configuration, app: FastAPI) -> None:
title="ltd-proxy",
description=metadata("ltd-proxy").get("Summary", ""),
version=metadata("ltd-proxy").get("Version", "0.0.0"),
+ openapi_url=None,
)
external_app.include_router(external_router)
diff --git a/src/ltdproxy/main.py b/src/ltdproxy/main.py
index 4833fef..a3d12b9 100644
--- a/src/ltdproxy/main.py
+++ b/src/ltdproxy/main.py
@@ -24,7 +24,7 @@
name=config.logger_name,
)
-app = FastAPI()
+app = FastAPI(openapi_url=None)
"""The main FastAPI application for ltd-proxy."""
add_handlers(app=app, config=config)
From 6695cdfae2efa6d4ce6ae6d54c3843347c22d1bf Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 4 Nov 2021 15:19:08 -0400
Subject: [PATCH 25/26] Add a health check endpoint for Kubernetes
This endpoint is always at the /__healthz path, regardless of whether
the app is serving from a path prefix or not.
---
src/ltdproxy/appsetup.py | 3 +++
src/ltdproxy/handlers/healthcheck.py | 11 +++++++++++
tests/handlers/__init__.py | 1 +
tests/handlers/healthcheck_test.py | 17 +++++++++++++++++
4 files changed, 32 insertions(+)
create mode 100644 src/ltdproxy/handlers/healthcheck.py
create mode 100644 tests/handlers/__init__.py
create mode 100644 tests/handlers/healthcheck_test.py
diff --git a/src/ltdproxy/appsetup.py b/src/ltdproxy/appsetup.py
index 196a49c..7502ca3 100644
--- a/src/ltdproxy/appsetup.py
+++ b/src/ltdproxy/appsetup.py
@@ -6,6 +6,7 @@
from typing import TYPE_CHECKING
from .handlers.external import external_router
+from .handlers.healthcheck import health_router
from .handlers.internal import internal_router
if TYPE_CHECKING:
@@ -16,6 +17,7 @@
def add_handlers(*, config: Configuration, app: FastAPI) -> None:
if config.path_prefix == "/":
+ app.include_router(health_router)
app.include_router(external_router)
else:
external_app = FastAPI(
@@ -27,4 +29,5 @@ def add_handlers(*, config: Configuration, app: FastAPI) -> None:
external_app.include_router(external_router)
app.include_router(internal_router)
+ app.include_router(health_router)
app.mount(f"{config.path_prefix}", external_app)
diff --git a/src/ltdproxy/handlers/healthcheck.py b/src/ltdproxy/handlers/healthcheck.py
new file mode 100644
index 0000000..bea2efc
--- /dev/null
+++ b/src/ltdproxy/handlers/healthcheck.py
@@ -0,0 +1,11 @@
+"""Handler for the Kubernetes health check."""
+
+from fastapi import APIRouter
+from starlette.responses import PlainTextResponse
+
+health_router = APIRouter()
+
+
+@health_router.get("/__healthz", name="healthz")
+def healthy() -> PlainTextResponse:
+ return PlainTextResponse("OK", status_code=200)
diff --git a/tests/handlers/__init__.py b/tests/handlers/__init__.py
new file mode 100644
index 0000000..661b93e
--- /dev/null
+++ b/tests/handlers/__init__.py
@@ -0,0 +1 @@
+"""Tests for handlers."""
diff --git a/tests/handlers/healthcheck_test.py b/tests/handlers/healthcheck_test.py
new file mode 100644
index 0000000..eeb3abb
--- /dev/null
+++ b/tests/handlers/healthcheck_test.py
@@ -0,0 +1,17 @@
+"""Test the healthcheck endpoint."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+if TYPE_CHECKING:
+ from httpx import AsyncClient
+
+
+@pytest.mark.asyncio
+async def test_get_healthz(client: AsyncClient) -> None:
+ """Test ``GET /__healthz``"""
+ response = await client.get("/__healthz")
+ assert response.status_code == 200
From 4945071f439a5c2e6f17c47194f6fe0b62381585 Mon Sep 17 00:00:00 2001
From: Jonathan Sick
Date: Thu, 4 Nov 2021 16:04:43 -0400
Subject: [PATCH 26/26] Add additional configurations manifests
This includes stub for the auth rules YAML file config map, which is
mounted onto the pod's filesystem.
---
README.rst | 26 +++++++++++++++++++++++++-
manifests/base/auth-configmap.yaml | 16 ++++++++++++++++
manifests/base/configmap.yaml | 4 ++++
manifests/base/deployment.yaml | 10 +++++++++-
manifests/base/kustomization.yaml | 1 +
5 files changed, 55 insertions(+), 2 deletions(-)
create mode 100644 manifests/base/auth-configmap.yaml
diff --git a/README.rst b/README.rst
index da90d48..d9ddce8 100644
--- a/README.rst
+++ b/README.rst
@@ -2,4 +2,28 @@
ltd-proxy
#########
-LTD Proxy is a secure front-end proxy for LTD projects.
+LTD Proxy is a secure front-end proxy for LTD projects that are hosted on Amazon AWS.
+It uses GitHub OAuth to authenticate visitors and GitHub organization and/or team memberships to authorize access to pages at specific URL path prefixes.
+
+Kubernetes deployment
+=====================
+
+Secret resource
+---------------
+
+Besides the ConfigMaps, your kustomized deployment needs to include a secret resource that is referenced as environment variables from the deployment's ``app`` container.
+This secret could be generated from a Vault secret or an AWS Secret, or could be a plain Kubernetes Secret, such as:
+
+.. code-block:: yaml
+
+ apiVersion: v1
+ kind: Secret
+ type: Opaque
+ metadata:
+ name: ltdproxy
+ data:
+ LTDPROXY_AWS_ACCESS_KEY_ID: ...
+ LTDPROXY_AWS_SECRET_ACCESS_KEY: ...
+ LTDPROXY_GITHUB_OAUTH_ID: ...
+ LTDPROXY_GITHUB_OAUTH_SECRET: ...
+ LTDPROXY_SESSION_KEY: ...
diff --git a/manifests/base/auth-configmap.yaml b/manifests/base/auth-configmap.yaml
new file mode 100644
index 0000000..0b24789
--- /dev/null
+++ b/manifests/base/auth-configmap.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: "ltdproxy-auth"
+ labels:
+ app.kubernetes.io/name: "ltd-proxy"
+data:
+ authrules.yaml: |
+ default:
+ - org: "jsickcodes"
+ paths: []
+ - pattern: "\/a\/"
+ authorized:
+ - org: "jsickcodes"
+ team: "Red Team"
+
diff --git a/manifests/base/configmap.yaml b/manifests/base/configmap.yaml
index 454ed73..cf24aa2 100644
--- a/manifests/base/configmap.yaml
+++ b/manifests/base/configmap.yaml
@@ -11,5 +11,9 @@ data:
SAFIR_LOGGER: "ltdproxy"
SAFIR_LOG_LEVEL: "INFO"
SAFIR_PROFILE: "production"
+ LTDPROXY_AUTH_CONFIG: "/opt/ltdproxy/auth/authrules.yaml"
+ LTDPROXY_PATH_PREFIX: "/"
LTDPROXY_S3_BUCKET: ""
LTDPROXY_S3_PREFIX: ""
+ LTDPROXY_AWS_REGION: ""
+ LTDPROXY_GITHUB_CALLBACK_URL: ""
diff --git a/manifests/base/deployment.yaml b/manifests/base/deployment.yaml
index 05e389f..ac08d46 100644
--- a/manifests/base/deployment.yaml
+++ b/manifests/base/deployment.yaml
@@ -19,7 +19,7 @@ spec:
- name: app
imagePullPolicy: "IfNotPresent"
# Use images field in a Kustomization to set/update image tag
- image: "lsstsqre/ltdproxy"
+ image: "ghcr.io/jsickcodes/ltd-proxy"
ports:
- containerPort: 8080
name: "app"
@@ -32,6 +32,14 @@ spec:
drop:
- "all"
readOnlyRootFilesystem: true
+ volumeMounts:
+ - name: "auth-config"
+ mountPath: "/opt/ltdproxy/auth/"
+ readOnly: true
+ volumes:
+ - name: "auth-config"
+ configMap:
+ name: "ltdproxy-auth"
securityContext:
runAsNonRoot: true
runAsUser: 1000
diff --git a/manifests/base/kustomization.yaml b/manifests/base/kustomization.yaml
index 2637f6d..1fda23c 100644
--- a/manifests/base/kustomization.yaml
+++ b/manifests/base/kustomization.yaml
@@ -7,5 +7,6 @@ images:
resources:
- configmap.yaml
+ - auth-configmap.yaml
- deployment.yaml
- service.yaml