diff --git a/.github/templates/test/action.yml b/.github/templates/test/action.yml new file mode 100644 index 0000000..557b750 --- /dev/null +++ b/.github/templates/test/action.yml @@ -0,0 +1,28 @@ +name: test +description: Run python tests +inputs: + folder: + description: 'The folder to run tests in' + required: true + +runs: + using: "composite" + steps: + - name: Install ${{ inputs.folder }} dependencies + shell: bash + run: | + cd ${{ inputs.folder }} + pip install pipenv + pipenv install --dev + - name: Lint with ruff + shell: bash + run: | + # stop the build if there are Python syntax errors or undefined names + cd ${{ inputs.folder }} + pipenv run ruff . + - name: Run ${{ inputs.folder }} tests + shell: bash + run: | + # stop the build if there are Python syntax errors or undefined names + cd ${{ inputs.folder }} + pipenv run pytest . \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 1fae321..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Python tests - -on: [push] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - cd backend - pip install pipenv - pipenv install --dev - - name: Lint with ruff - run: | - # stop the build if there are Python syntax errors or undefined names - cd backend - pipenv run ruff . - # - name: Run tests - # run: | - # # stop the build if there are Python syntax errors or undefined names - # pipenv run pytest . \ No newline at end of file diff --git a/.github/workflows/test_authentication.yml b/.github/workflows/test_authentication.yml new file mode 100644 index 0000000..465e6e1 --- /dev/null +++ b/.github/workflows/test_authentication.yml @@ -0,0 +1,17 @@ +name: Python tests authentication + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Run tests + uses: ./.github/templates/test/ + with: + folder: authentication \ No newline at end of file diff --git a/.github/workflows/test_resource.yml b/.github/workflows/test_resource.yml new file mode 100644 index 0000000..c6f4666 --- /dev/null +++ b/.github/workflows/test_resource.yml @@ -0,0 +1,17 @@ +name: Python tests resource + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Run tests + uses: ./.github/templates/test/ + with: + folder: resource \ No newline at end of file diff --git a/README.md b/README.md index d7b4763..4ef0bf7 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,36 @@ -# Perseus demo authentication api +# Perseus demo energy provider -Emulates authentication endpoints for the Perseus demo. These endpoints in production will be provided by an energy provider's authentication platform. Api documentation is available at https://perseus-demo-authentication.ib1.org/api-docs. +Emulates authentication and resource api endpoints for the Perseus demo. -The authentication api is responsible for authenticating and identifying its own users, and for handling and passing on requests from the client API to the FAPI API. +## Authentication API -## Run the dev server +The authentication app is in the [authentication](authentication) directory. It provides endpoints for authenticating and identifying users, and for handling and passing on requests from the client API to the FAPI API. + +Authentication API documentation is available at https://perseus-demo-authentication.ib1.org/api-docs. + +## Resource API + +The resource api is in the [resource](resource) directory. It demonstrates how to protect an API endpoint using a certificate bound token obtained from the authentication API's interaction with the FAPI provider. + +Resource API documentation is available at https://perseus-demo-energy.ib1.org/api-docs. + +## Running a dev server ```bash +cd authentication|resource pipenv install --dev pipenv run uvicorn api.main:app --reload ``` ## Running the local docker environment +The included docker compose file will bring up both APIs. It uses nginx to proxy requests to uvicorn, with nginx configuration to pass through client certificates to the backend, using the same header as used by AWS ALB (`x-amzn-mtls-clientcert`). It requires a set of certificates as generated by [scripts/certmaker.sh], which should be available in the `certs` directory + ```bash docker-compose up ``` -The docker environment uses nginx to proxy requests to uvicorn, with nginx configuration to pass through client certificates to the backend, using the same header as used by AWS ALB (`x-amzn-mtls-clientcert`). It requires a set of certificates as generated by [scripts/certmaker.sh], which should be available in the `certs` directory. +The environment variables in the docker compose file point to the FAPI api running on localhost port 8020 (http://host.docker.internal:8020). As the FAPI api is not running in the docker environment, you may need to change these environment variables to match your local environment. It will also work with the live FAPI api by changing these values to "https://perseus-demo-fapi.ib1.org". ## Testing the API with client.py diff --git a/backend/.dockerignore b/authentication/.dockerignore similarity index 100% rename from backend/.dockerignore rename to authentication/.dockerignore diff --git a/backend/Dockerfile b/authentication/Dockerfile similarity index 100% rename from backend/Dockerfile rename to authentication/Dockerfile diff --git a/backend/Pipfile b/authentication/Pipfile similarity index 82% rename from backend/Pipfile rename to authentication/Pipfile index 5b50724..45a3138 100644 --- a/backend/Pipfile +++ b/authentication/Pipfile @@ -9,16 +9,19 @@ pyjwt = "*" uvicorn = "*" requests = "*" python-jose = {extras = ["cryptography"] } -passlib = {extras = ["bcrypt"] } python-multipart = "*" +httpx = "*" +bcrypt = "*" [dev-packages] black = "*" mypy = "*" ruff = "*" types-python-jose = "*" -types-passlib = "*" types-requests = "*" +pytest = "*" +responses = "*" +pytest-mock = "*" [requires] python_version = "3.12" diff --git a/authentication/Pipfile.lock b/authentication/Pipfile.lock new file mode 100644 index 0000000..f6719c5 --- /dev/null +++ b/authentication/Pipfile.lock @@ -0,0 +1,921 @@ +{ + "_meta": { + "hash": { + "sha256": "c30d174e0b0e9f0e9a1c9741c9ea09351481cce0515940f1b0f33c9ea5b8f100" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "annotated-types": { + "hashes": [ + "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", + "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.0" + }, + "anyio": { + "hashes": [ + "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", + "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.0" + }, + "bcrypt": { + "hashes": [ + "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f", + "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5", + "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb", + "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258", + "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4", + "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc", + "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2", + "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326", + "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483", + "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a", + "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966", + "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63", + "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c", + "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551", + "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d", + "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e", + "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0", + "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c", + "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb", + "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1", + "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42", + "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946", + "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab", + "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1", + "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c", + "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7", + "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==4.1.2" + }, + "certifi": { + "hashes": [ + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.2.2" + }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.16.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "cryptography": { + "hashes": [ + "sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129", + "sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe", + "sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20", + "sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec", + "sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3", + "sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd", + "sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5", + "sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b", + "sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46", + "sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504", + "sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306", + "sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead", + "sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e", + "sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938", + "sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a", + "sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b", + "sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a", + "sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd", + "sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54", + "sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c", + "sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857", + "sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f", + "sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f", + "sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef", + "sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c", + "sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548", + "sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65", + "sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4", + "sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4", + "sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a", + "sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151", + "sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396" + ], + "version": "==42.0.3" + }, + "ecdsa": { + "hashes": [ + "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49", + "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.18.0" + }, + "fastapi": { + "hashes": [ + "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d", + "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.109.2" + }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httpcore": { + "hashes": [ + "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544", + "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.3" + }, + "httpx": { + "hashes": [ + "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf", + "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.26.0" + }, + "idna": { + "hashes": [ + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + ], + "markers": "python_version >= '3.5'", + "version": "==3.6" + }, + "pyasn1": { + "hashes": [ + "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58", + "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==0.5.1" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pydantic": { + "hashes": [ + "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f", + "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9" + ], + "markers": "python_version >= '3.8'", + "version": "==2.6.1" + }, + "pydantic-core": { + "hashes": [ + "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379", + "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06", + "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05", + "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7", + "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753", + "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a", + "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731", + "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc", + "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380", + "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3", + "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c", + "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11", + "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990", + "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a", + "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2", + "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8", + "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97", + "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a", + "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8", + "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef", + "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77", + "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33", + "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82", + "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5", + "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b", + "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55", + "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e", + "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b", + "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7", + "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec", + "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc", + "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469", + "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b", + "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20", + "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e", + "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d", + "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f", + "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b", + "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039", + "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e", + "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2", + "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f", + "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b", + "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc", + "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8", + "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522", + "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e", + "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784", + "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a", + "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890", + "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485", + "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545", + "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f", + "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943", + "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878", + "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f", + "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17", + "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7", + "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286", + "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c", + "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb", + "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646", + "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978", + "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8", + "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15", + "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272", + "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2", + "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55", + "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf", + "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545", + "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4", + "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a", + "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804", + "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4", + "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0", + "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a", + "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113", + "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d", + "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25" + ], + "markers": "python_version >= '3.8'", + "version": "==2.16.2" + }, + "pyjwt": { + "hashes": [ + "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", + "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.8.0" + }, + "python-jose": { + "extras": [ + "cryptography" + ], + "hashes": [ + "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a", + "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a" + ], + "version": "==3.3.0" + }, + "python-multipart": { + "hashes": [ + "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026", + "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.0.9" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "rsa": { + "hashes": [ + "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", + "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" + ], + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==4.9" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "sniffio": { + "hashes": [ + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + }, + "starlette": { + "hashes": [ + "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044", + "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080" + ], + "markers": "python_version >= '3.8'", + "version": "==0.36.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", + "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + ], + "markers": "python_version >= '3.8'", + "version": "==4.9.0" + }, + "urllib3": { + "hashes": [ + "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", + "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.1" + }, + "uvicorn": { + "hashes": [ + "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a", + "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.27.1" + } + }, + "develop": { + "black": { + "hashes": [ + "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8", + "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8", + "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd", + "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9", + "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31", + "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92", + "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f", + "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29", + "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4", + "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693", + "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218", + "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a", + "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23", + "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0", + "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982", + "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894", + "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540", + "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430", + "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b", + "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2", + "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6", + "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==24.2.0" + }, + "certifi": { + "hashes": [ + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.2.2" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "idna": { + "hashes": [ + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + ], + "markers": "python_version >= '3.5'", + "version": "==3.6" + }, + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "mypy": { + "hashes": [ + "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6", + "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d", + "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02", + "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d", + "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3", + "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3", + "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3", + "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66", + "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259", + "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835", + "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd", + "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d", + "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8", + "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07", + "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b", + "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e", + "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6", + "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae", + "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9", + "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d", + "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a", + "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592", + "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218", + "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817", + "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4", + "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410", + "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.8.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "platformdirs": { + "hashes": [ + "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", + "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" + ], + "markers": "python_version >= '3.8'", + "version": "==4.2.0" + }, + "pluggy": { + "hashes": [ + "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", + "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.0" + }, + "pytest": { + "hashes": [ + "sha256:267f6563751877d772019b13aacbe4e860d73fe8f651f28112e9ac37de7513ae", + "sha256:3e4f16fe1c0a9dc9d9389161c127c3edc5d810c38d6793042fb81d9f48a59fca" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==8.0.1" + }, + "pytest-mock": { + "hashes": [ + "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f", + "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.12.0" + }, + "pyyaml": { + "hashes": [ + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + ], + "markers": "python_version >= '3.6'", + "version": "==6.0.1" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "responses": { + "hashes": [ + "sha256:01ae6a02b4f34e39bffceb0fc6786b67a25eae919c6368d05eabc8d9576c2a66", + "sha256:2f0b9c2b6437db4b528619a77e5d565e4ec2a9532162ac1a131a83529db7be1a" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.25.0" + }, + "ruff": { + "hashes": [ + "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6", + "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e", + "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c", + "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9", + "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e", + "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3", + "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba", + "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001", + "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726", + "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e", + "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd", + "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d", + "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39", + "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325", + "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d", + "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73", + "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.2.2" + }, + "types-pyasn1": { + "hashes": [ + "sha256:40b205856c6a01d2ce6fa47a0be2a238a5556b04f47a2875a2aba680a65a959f", + "sha256:b42b4e967d2ad780bde2ce47d7627a00dfb11b37a451f3e73b264ec6e97e50c7" + ], + "markers": "python_version >= '3.8'", + "version": "==0.5.0.20240205" + }, + "types-python-jose": { + "hashes": [ + "sha256:b18cf8c5080bbfe1ef7c3b707986435d9efca3e90889acb6a06f65e06bc3405a", + "sha256:b515a6c0c61f5e2a53bc93e3a2b024cbd42563e2e19cbde9fd1c2cc2cfe77ccc" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.3.4.20240106" + }, + "types-requests": { + "hashes": [ + "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b", + "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.31.0.20240218" + }, + "typing-extensions": { + "hashes": [ + "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", + "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + ], + "markers": "python_version >= '3.8'", + "version": "==4.9.0" + }, + "urllib3": { + "hashes": [ + "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", + "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.1" + } + } +} diff --git a/backend/api/__init__.py b/authentication/api/__init__.py similarity index 100% rename from backend/api/__init__.py rename to authentication/api/__init__.py diff --git a/backend/api/authentication.py b/authentication/api/authentication.py similarity index 98% rename from backend/api/authentication.py rename to authentication/api/authentication.py index dca81e2..9eacd28 100644 --- a/backend/api/authentication.py +++ b/authentication/api/authentication.py @@ -44,6 +44,7 @@ def verify_password(plain_password, hashed_password): def get_user(db, username: str): + print("GET USER", username) if username in db: user_dict = db[username] return models.UserInDB(**user_dict) diff --git a/backend/api/conf.py b/authentication/api/conf.py similarity index 100% rename from backend/api/conf.py rename to authentication/api/conf.py diff --git a/backend/api/examples.py b/authentication/api/examples.py similarity index 100% rename from backend/api/examples.py rename to authentication/api/examples.py diff --git a/backend/api/main.py b/authentication/api/main.py similarity index 98% rename from backend/api/main.py rename to authentication/api/main.py index eb50c13..de510b9 100644 --- a/backend/api/main.py +++ b/authentication/api/main.py @@ -34,6 +34,9 @@ async def test(request: Request) -> dict: return dict(request.headers.mutablecopy()) +# TODO: mock responses from FAPI api using the responses library + + @app.post("/api/v1/par", response_model=models.PushedAuthorizationResponse) async def pushed_authorization_request( par: models.ClientPushedAuthorizationRequest, @@ -43,6 +46,7 @@ async def pushed_authorization_request( Pass the request along to the FAPI api, await the response, send it back to the client app """ + payload = { "parameters": urllib.parse.urlencode(par.model_dump()), "client_id": par.client_id, @@ -124,9 +128,9 @@ async def issue( return result -# TODO is it authenticate and authorize or authentication and authorzation? @app.post("/api/v1/authorize/token", response_model=models.FAPITokenResponse) async def token( + request: Request, token_request: models.FAPITokenRequest, x_amzn_mtls_clientcert: Annotated[str | None, Header()] = None, ) -> models.FAPITokenResponse: @@ -139,6 +143,7 @@ async def token( "client_id": token_request.client_id, "client_certificate": x_amzn_mtls_clientcert, } + print(payload) session = requests.Session() session.auth = (conf.CLIENT_ID, conf.CLIENT_SECRET) response = session.post( @@ -146,7 +151,6 @@ async def token( json=payload, ) if response.status_code != 200: - print(response.text) raise HTTPException(status_code=response.status_code, detail=response.text) result = response.json() return models.FAPITokenResponse( diff --git a/backend/api/models.py b/authentication/api/models.py similarity index 100% rename from backend/api/models.py rename to authentication/api/models.py diff --git a/copilot/.workspace b/authentication/copilot/.workspace similarity index 100% rename from copilot/.workspace rename to authentication/copilot/.workspace diff --git a/copilot/backend/manifest.yml b/authentication/copilot/backend/manifest.yml similarity index 98% rename from copilot/backend/manifest.yml rename to authentication/copilot/backend/manifest.yml index 67581b3..db3092d 100644 --- a/copilot/backend/manifest.yml +++ b/authentication/copilot/backend/manifest.yml @@ -20,7 +20,7 @@ http: # Configuration for your containers and service. image: # Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#image-build - build: backend/Dockerfile + build: Dockerfile # Port exposed through your container to route traffic to it. port: 8080 diff --git a/copilot/environments/dev/manifest.yml b/authentication/copilot/environments/dev/manifest.yml similarity index 100% rename from copilot/environments/dev/manifest.yml rename to authentication/copilot/environments/dev/manifest.yml diff --git a/copilot/environments/overrides/README.md b/authentication/copilot/environments/overrides/README.md similarity index 100% rename from copilot/environments/overrides/README.md rename to authentication/copilot/environments/overrides/README.md diff --git a/copilot/environments/overrides/cfn.patches.yml b/authentication/copilot/environments/overrides/cfn.patches.yml similarity index 100% rename from copilot/environments/overrides/cfn.patches.yml rename to authentication/copilot/environments/overrides/cfn.patches.yml diff --git a/authentication/tests/__init__.py b/authentication/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authentication/tests/test_api.py b/authentication/tests/test_api.py new file mode 100644 index 0000000..b3cd90a --- /dev/null +++ b/authentication/tests/test_api.py @@ -0,0 +1,112 @@ +import datetime + +import responses +import pytest +from fastapi.testclient import TestClient +from api.main import app, conf +from api import authentication, examples + +client = TestClient(app) + + +@pytest.fixture +def mock_token(mocker): + access_token_expires = datetime.timedelta( + minutes=authentication.ACCESS_TOKEN_EXPIRE_MINUTES + ) + access_token = authentication.create_access_token( + data={"sub": "platform_user"}, expires_delta=access_token_expires + ) + return access_token + + +@responses.activate +def test_pushed_authorization_request(): + responses.post( + f"{conf.FAPI_API}/auth/par/", + json={ + "response_content": '{"request_uri": "https://some-uri.com?ticket=1", "expires_in": 3600}' + }, + ) + response = client.post( + "/api/v1/par", + json={ + "client_id": 123456, + "redirect_uri": "https://mobile.example.com/cb", + "code_challenge": "W78hCS0q72DfIHa...kgZkEJuAFaT4", + "code_challenge_method": "S256", + "response_type": "code", + }, + headers={"x_amzn_mtls_clientcert": "client-certificate"}, + ) + assert response.status_code == 200 + assert "request_uri" in response.json() + + +@responses.activate +def test_authorization_code(): + responses.post( + f"{conf.FAPI_API}/auth/authorization", + json={"ticket": "abc123"}, + ) + response = client.post( + "/api/v1/authorize", + json={"request_uri": "test-uri", "client_id": 123456}, + headers={"x-amzn-mtls-clientcert": "client-certificate"}, + ) + assert response.status_code == 200 + assert "ticket" in response.json() + + +@responses.activate +def test_issue(mock_token): + # Use responses library to mock the response from the FAPI API + responses.post( + f"{conf.FAPI_API}/auth/authorization/issue", + json=examples.ISSUE_RESPONSE, + ) + response = client.post( + "/api/v1/authorize/issue", + json={"ticket": "test-ticket"}, + headers={"Authorization": f"Bearer {mock_token}"}, + ) + assert response.status_code == 200 + + +@responses.activate +def test_token(): + responses.post( + f"{conf.FAPI_API}/auth/token/", + json={ + "access_token": "ABC123", + "id_token": "ID_TOKEN", + "refresh_token": "REFRESH_TOKEN", + }, + ) + response = client.post( + "/api/v1/authorize/token", + json={ + "parameters": "grant_type=authorization_code&redirect_uri=https://client.example.org/cb/example.com&code=DxiKC0cOc_46nzVjgr41RWBQtMDrAvc0BUbMJ_v7I70", + "client_id": 123456, + }, + headers={"x-amzn-mtls-clientcert": "client-certificate"}, + ) + assert response.status_code == 200 + assert "access_token" in response.json() + assert "id_token" in response.json() + assert "refresh_token" in response.json() + + +def test_introspect(): + # todo + pass + + +def test_login_for_access_token(): + # todo + pass + + +def test_user_consent(): + # todo + pass diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock deleted file mode 100644 index 4b94290..0000000 --- a/backend/Pipfile.lock +++ /dev/null @@ -1,700 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "55d42a837d55c7312554cda5879c3436ae9730e7755ad78f9c02097f37a0c3bc" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.12" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "annotated-types": { - "hashes": [ - "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", - "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" - ], - "markers": "python_version >= '3.8'", - "version": "==0.6.0" - }, - "anyio": { - "hashes": [ - "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", - "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f" - ], - "markers": "python_version >= '3.8'", - "version": "==4.2.0" - }, - "bcrypt": { - "hashes": [ - "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f", - "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5", - "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb", - "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258", - "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4", - "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc", - "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2", - "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326", - "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483", - "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a", - "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966", - "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63", - "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c", - "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551", - "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d", - "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e", - "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0", - "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c", - "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb", - "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1", - "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42", - "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946", - "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab", - "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1", - "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c", - "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7", - "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369" - ], - "version": "==4.1.2" - }, - "certifi": { - "hashes": [ - "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1", - "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474" - ], - "markers": "python_version >= '3.6'", - "version": "==2023.11.17" - }, - "cffi": { - "hashes": [ - "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", - "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", - "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", - "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", - "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", - "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", - "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", - "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", - "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", - "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", - "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", - "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", - "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", - "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", - "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", - "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", - "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", - "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", - "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", - "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", - "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", - "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", - "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", - "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", - "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", - "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", - "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", - "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", - "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", - "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", - "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", - "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", - "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", - "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", - "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", - "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", - "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", - "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", - "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", - "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", - "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", - "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", - "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", - "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", - "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", - "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", - "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", - "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", - "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", - "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", - "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", - "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" - ], - "markers": "platform_python_implementation != 'PyPy'", - "version": "==1.16.0" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" - }, - "click": { - "hashes": [ - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" - ], - "markers": "python_version >= '3.7'", - "version": "==8.1.7" - }, - "cryptography": { - "hashes": [ - "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380", - "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589", - "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea", - "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65", - "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a", - "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3", - "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008", - "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1", - "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2", - "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635", - "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2", - "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90", - "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee", - "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a", - "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242", - "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12", - "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2", - "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d", - "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be", - "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee", - "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6", - "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529", - "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929", - "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1", - "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6", - "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a", - "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446", - "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9", - "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888", - "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4", - "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33", - "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f" - ], - "version": "==42.0.2" - }, - "ecdsa": { - "hashes": [ - "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49", - "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.18.0" - }, - "fastapi": { - "hashes": [ - "sha256:8c77515984cd8e8cfeb58364f8cc7a28f0692088475e2614f7bf03275eba9093", - "sha256:b978095b9ee01a5cf49b19f4bc1ac9b8ca83aa076e770ef8fd9af09a2b88d191" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==0.109.0" - }, - "h11": { - "hashes": [ - "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", - "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" - ], - "markers": "python_version >= '3.7'", - "version": "==0.14.0" - }, - "idna": { - "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" - ], - "markers": "python_version >= '3.5'", - "version": "==3.6" - }, - "passlib": { - "extras": [ - "bcrypt" - ], - "hashes": [ - "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", - "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04" - ], - "version": "==1.7.4" - }, - "pyasn1": { - "hashes": [ - "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58", - "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==0.5.1" - }, - "pycparser": { - "hashes": [ - "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" - ], - "version": "==2.21" - }, - "pydantic": { - "hashes": [ - "sha256:1440966574e1b5b99cf75a13bec7b20e3512e8a61b894ae252f56275e2c465ae", - "sha256:ae887bd94eb404b09d86e4d12f93893bdca79d766e738528c6fa1c849f3c6bcf" - ], - "markers": "python_version >= '3.8'", - "version": "==2.6.0" - }, - "pydantic-core": { - "hashes": [ - "sha256:06f0d5a1d9e1b7932477c172cc720b3b23c18762ed7a8efa8398298a59d177c7", - "sha256:07982b82d121ed3fc1c51faf6e8f57ff09b1325d2efccaa257dd8c0dd937acca", - "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51", - "sha256:102569d371fadc40d8f8598a59379c37ec60164315884467052830b28cc4e9da", - "sha256:10dca874e35bb60ce4f9f6665bfbfad050dd7573596608aeb9e098621ac331dc", - "sha256:150ba5c86f502c040b822777e2e519b5625b47813bd05f9273a8ed169c97d9ae", - "sha256:1661c668c1bb67b7cec96914329d9ab66755911d093bb9063c4c8914188af6d4", - "sha256:1a2fe7b00a49b51047334d84aafd7e39f80b7675cad0083678c58983662da89b", - "sha256:1ae8048cba95f382dba56766525abca438328455e35c283bb202964f41a780b0", - "sha256:20f724a023042588d0f4396bbbcf4cffd0ddd0ad3ed4f0d8e6d4ac4264bae81e", - "sha256:2133b0e412a47868a358713287ff9f9a328879da547dc88be67481cdac529118", - "sha256:21e3298486c4ea4e4d5cc6fb69e06fb02a4e22089304308817035ac006a7f506", - "sha256:21ebaa4bf6386a3b22eec518da7d679c8363fb7fb70cf6972161e5542f470798", - "sha256:23632132f1fd608034f1a56cc3e484be00854db845b3a4a508834be5a6435a6f", - "sha256:2d5bea8012df5bb6dda1e67d0563ac50b7f64a5d5858348b5c8cb5043811c19d", - "sha256:300616102fb71241ff477a2cbbc847321dbec49428434a2f17f37528721c4948", - "sha256:30a8259569fbeec49cfac7fda3ec8123486ef1b729225222f0d41d5f840b476f", - "sha256:399166f24c33a0c5759ecc4801f040dbc87d412c1a6d6292b2349b4c505effc9", - "sha256:3fac641bbfa43d5a1bed99d28aa1fded1984d31c670a95aac1bf1d36ac6ce137", - "sha256:42c29d54ed4501a30cd71015bf982fa95e4a60117b44e1a200290ce687d3e640", - "sha256:462d599299c5971f03c676e2b63aa80fec5ebc572d89ce766cd11ca8bcb56f3f", - "sha256:4eebbd049008eb800f519578e944b8dc8e0f7d59a5abb5924cc2d4ed3a1834ff", - "sha256:502c062a18d84452858f8aea1e520e12a4d5228fc3621ea5061409d666ea1706", - "sha256:5317c04349472e683803da262c781c42c5628a9be73f4750ac7d13040efb5d2d", - "sha256:5511f962dd1b9b553e9534c3b9c6a4b0c9ded3d8c2be96e61d56f933feef9e1f", - "sha256:561be4e3e952c2f9056fba5267b99be4ec2afadc27261505d4992c50b33c513c", - "sha256:601d3e42452cd4f2891c13fa8c70366d71851c1593ed42f57bf37f40f7dca3c8", - "sha256:644904600c15816a1f9a1bafa6aab0d21db2788abcdf4e2a77951280473f33e1", - "sha256:653a5dfd00f601a0ed6654a8b877b18d65ac32c9d9997456e0ab240807be6cf7", - "sha256:694a5e9f1f2c124a17ff2d0be613fd53ba0c26de588eb4bdab8bca855e550d95", - "sha256:71b4a48a7427f14679f0015b13c712863d28bb1ab700bd11776a5368135c7d60", - "sha256:72bf9308a82b75039b8c8edd2be2924c352eda5da14a920551a8b65d5ee89253", - "sha256:735dceec50fa907a3c314b84ed609dec54b76a814aa14eb90da31d1d36873a5e", - "sha256:73802194f10c394c2bedce7a135ba1d8ba6cff23adf4217612bfc5cf060de34c", - "sha256:780daad9e35b18d10d7219d24bfb30148ca2afc309928e1d4d53de86822593dc", - "sha256:8655f55fe68c4685673265a650ef71beb2d31871c049c8b80262026f23605ee3", - "sha256:877045a7969ace04d59516d5d6a7dee13106822f99a5d8df5e6822941f7bedc8", - "sha256:87bce04f09f0552b66fca0c4e10da78d17cb0e71c205864bab4e9595122cb9d9", - "sha256:8d4dfc66abea3ec6d9f83e837a8f8a7d9d3a76d25c9911735c76d6745950e62c", - "sha256:8ec364e280db4235389b5e1e6ee924723c693cbc98e9d28dc1767041ff9bc388", - "sha256:8fa00fa24ffd8c31fac081bf7be7eb495be6d248db127f8776575a746fa55c95", - "sha256:920c4897e55e2881db6a6da151198e5001552c3777cd42b8a4c2f72eedc2ee91", - "sha256:920f4633bee43d7a2818e1a1a788906df5a17b7ab6fe411220ed92b42940f818", - "sha256:9795f56aa6b2296f05ac79d8a424e94056730c0b860a62b0fdcfe6340b658cc8", - "sha256:98f0edee7ee9cc7f9221af2e1b95bd02810e1c7a6d115cfd82698803d385b28f", - "sha256:99c095457eea8550c9fa9a7a992e842aeae1429dab6b6b378710f62bfb70b394", - "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13", - "sha256:99f9a50b56713a598d33bc23a9912224fc5d7f9f292444e6664236ae471ddf17", - "sha256:9c46e556ee266ed3fb7b7a882b53df3c76b45e872fdab8d9cf49ae5e91147fd7", - "sha256:9f5d37ff01edcbace53a402e80793640c25798fb7208f105d87a25e6fcc9ea06", - "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f", - "sha256:a497be217818c318d93f07e14502ef93d44e6a20c72b04c530611e45e54c2196", - "sha256:ac89ccc39cd1d556cc72d6752f252dc869dde41c7c936e86beac5eb555041b66", - "sha256:adf28099d061a25fbcc6531febb7a091e027605385de9fe14dd6a97319d614cf", - "sha256:afa01d25769af33a8dac0d905d5c7bb2d73c7c3d5161b2dd6f8b5b5eea6a3c4c", - "sha256:b1fc07896fc1851558f532dffc8987e526b682ec73140886c831d773cef44b76", - "sha256:b49c604ace7a7aa8af31196abbf8f2193be605db6739ed905ecaf62af31ccae0", - "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212", - "sha256:ba07646f35e4e49376c9831130039d1b478fbfa1215ae62ad62d2ee63cf9c18f", - "sha256:bd88f40f2294440d3f3c6308e50d96a0d3d0973d6f1a5732875d10f569acef49", - "sha256:c0be58529d43d38ae849a91932391eb93275a06b93b79a8ab828b012e916a206", - "sha256:c45f62e4107ebd05166717ac58f6feb44471ed450d07fecd90e5f69d9bf03c48", - "sha256:c56da23034fe66221f2208c813d8aa509eea34d97328ce2add56e219c3a9f41c", - "sha256:c94b5537bf6ce66e4d7830c6993152940a188600f6ae044435287753044a8fe2", - "sha256:cebf8d56fee3b08ad40d332a807ecccd4153d3f1ba8231e111d9759f02edfd05", - "sha256:d0bf6f93a55d3fa7a079d811b29100b019784e2ee6bc06b0bb839538272a5610", - "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd", - "sha256:d25ef0c33f22649b7a088035fd65ac1ce6464fa2876578df1adad9472f918a76", - "sha256:d6cbdf12ef967a6aa401cf5cdf47850559e59eedad10e781471c960583f25aa1", - "sha256:d8c032ccee90b37b44e05948b449a2d6baed7e614df3d3f47fe432c952c21b60", - "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34", - "sha256:e83ebbf020be727d6e0991c1b192a5c2e7113eb66e3def0cd0c62f9f266247e4", - "sha256:ed3025a8a7e5a59817b7494686d449ebfbe301f3e757b852c8d0d1961d6be864", - "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66", - "sha256:f5247a3d74355f8b1d780d0f3b32a23dd9f6d3ff43ef2037c6dcd249f35ecf4c", - "sha256:fa496cd45cda0165d597e9d6f01e36c33c9508f75cf03c0a650018c5048f578e", - "sha256:fb4363e6c9fc87365c2bc777a1f585a22f2f56642501885ffc7942138499bf54", - "sha256:fb4370b15111905bf8b5ba2129b926af9470f014cb0493a67d23e9d7a48348e8", - "sha256:fbec2af0ebafa57eb82c18c304b37c86a8abddf7022955d1742b3d5471a6339e" - ], - "markers": "python_version >= '3.8'", - "version": "==2.16.1" - }, - "pyjwt": { - "hashes": [ - "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de", - "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.8.0" - }, - "python-jose": { - "extras": [ - "cryptography" - ], - "hashes": [ - "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a", - "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a" - ], - "version": "==3.3.0" - }, - "python-multipart": { - "hashes": [ - "sha256:e9925a80bb668529f1b67c7fdb0a5dacdd7cbfc6fb0bff3ea443fe22bdd62132", - "sha256:ee698bab5ef148b0a760751c261902cd096e57e10558e11aca17646b74ee1c18" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==0.0.6" - }, - "requests": { - "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.31.0" - }, - "rsa": { - "hashes": [ - "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", - "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" - ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==4.9" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "sniffio": { - "hashes": [ - "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", - "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" - ], - "markers": "python_version >= '3.7'", - "version": "==1.3.0" - }, - "starlette": { - "hashes": [ - "sha256:3e2639dac3520e4f58734ed22553f950d3f3cb1001cd2eaac4d57e8cdc5f66bc", - "sha256:50bbbda9baa098e361f398fda0928062abbaf1f54f4fadcbe17c092a01eb9a25" - ], - "markers": "python_version >= '3.8'", - "version": "==0.35.1" - }, - "typing-extensions": { - "hashes": [ - "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", - "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" - ], - "markers": "python_version >= '3.8'", - "version": "==4.9.0" - }, - "urllib3": { - "hashes": [ - "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20", - "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224" - ], - "markers": "python_version >= '3.8'", - "version": "==2.2.0" - }, - "uvicorn": { - "hashes": [ - "sha256:4b85ba02b8a20429b9b205d015cbeb788a12da527f731811b643fd739ef90d5f", - "sha256:54898fcd80c13ff1cd28bf77b04ec9dbd8ff60c5259b499b4b12bb0917f22907" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==0.27.0.post1" - } - }, - "develop": { - "black": { - "hashes": [ - "sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8", - "sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6", - "sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62", - "sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445", - "sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c", - "sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a", - "sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9", - "sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2", - "sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6", - "sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b", - "sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4", - "sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168", - "sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d", - "sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5", - "sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024", - "sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e", - "sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b", - "sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161", - "sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717", - "sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8", - "sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac", - "sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==24.1.1" - }, - "click": { - "hashes": [ - "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" - ], - "markers": "python_version >= '3.7'", - "version": "==8.1.7" - }, - "mypy": { - "hashes": [ - "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6", - "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d", - "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02", - "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d", - "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3", - "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3", - "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3", - "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66", - "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259", - "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835", - "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd", - "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d", - "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8", - "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07", - "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b", - "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e", - "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6", - "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae", - "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9", - "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d", - "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a", - "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592", - "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218", - "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817", - "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4", - "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410", - "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.8.0" - }, - "mypy-extensions": { - "hashes": [ - "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", - "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.0" - }, - "packaging": { - "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" - ], - "markers": "python_version >= '3.7'", - "version": "==23.2" - }, - "pathspec": { - "hashes": [ - "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", - "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" - ], - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, - "platformdirs": { - "hashes": [ - "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", - "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" - ], - "markers": "python_version >= '3.8'", - "version": "==4.2.0" - }, - "ruff": { - "hashes": [ - "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447", - "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f", - "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587", - "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df", - "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852", - "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f", - "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5", - "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e", - "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807", - "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360", - "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2", - "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1", - "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec", - "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5", - "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8", - "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e", - "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==0.1.15" - }, - "types-passlib": { - "hashes": [ - "sha256:2231ae83d1dd9e485b7ec6041d81b4f9c66403d1767360e860605a90db48ea27", - "sha256:347aa64d4c2bc239f3765fe38fc79dad3d67f9def7b3ea721daaaaa835a91dad" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.7.7.20240106" - }, - "types-pyasn1": { - "hashes": [ - "sha256:80e38befb1ddbf90c58b6f9e5ce158df66c42c87764a6564b1b7b6dd340535f7", - "sha256:e6ad48f5a58afb32019cba86a0529cdd52c315495a19122b4823ba4ad5f3c45b" - ], - "markers": "python_version >= '3.8'", - "version": "==0.5.0.20240106" - }, - "types-python-jose": { - "hashes": [ - "sha256:b18cf8c5080bbfe1ef7c3b707986435d9efca3e90889acb6a06f65e06bc3405a", - "sha256:b515a6c0c61f5e2a53bc93e3a2b024cbd42563e2e19cbde9fd1c2cc2cfe77ccc" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==3.3.4.20240106" - }, - "types-requests": { - "hashes": [ - "sha256:03a28ce1d7cd54199148e043b2079cdded22d6795d19a2c2a6791a4b2b5e2eb5", - "sha256:9592a9a4cb92d6d75d9b491a41477272b710e021011a2a3061157e2fb1f1a5d1" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.31.0.20240125" - }, - "typing-extensions": { - "hashes": [ - "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", - "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" - ], - "markers": "python_version >= '3.8'", - "version": "==4.9.0" - }, - "urllib3": { - "hashes": [ - "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20", - "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224" - ], - "markers": "python_version >= '3.8'", - "version": "==2.2.0" - } - } -} diff --git a/backend/api/middleware.py b/backend/api/middleware.py deleted file mode 100644 index d2ea4c3..0000000 --- a/backend/api/middleware.py +++ /dev/null @@ -1,36 +0,0 @@ -import base64 - -from fastapi import FastAPI, Request - -app = FastAPI() - - -@app.middleware("http") -async def add_client_certificate(request: Request, call_next): - # Check if the "X-SSL-Client-Cert" header already exists - # Call the next middleware or the main application - response = await call_next(request) - if "X-SSL-Client-Cert" not in request.headers: - # Retrieve the client certificate information from request.scope - tls = request.scope.get("tls", {}) - client_certificate = tls.get("client_certificate") - - # Access certificate information and add it to headers - if client_certificate: - # Convert the client certificate to Base64 - cert_base64 = base64.b64encode(client_certificate).decode("utf-8") - - # Add the certificate information to the headers - response.headers["X-SSL-Client-Cert"] = cert_base64 - # request.scope["headers"] = headers.raw - - return response - - -# @app.middleware("http") -# async def add_process_time_header(request: Request, call_next): -# start_time = time.time() -# response = await call_next(request) -# process_time = time.time() - start_time -# response.headers["X-Process-Time"] = str(process_time) -# return response diff --git a/backend/client.py b/client.py similarity index 63% rename from backend/client.py rename to client.py index 1591dc5..95d3766 100644 --- a/backend/client.py +++ b/client.py @@ -3,14 +3,15 @@ """ import os -import time import requests import jwt +import time -from api import conf +from authentication.api import conf -AUTHENTICATION_API = os.environ.get("AUTHENTICATION_API", "https://0.0.0.0:443") -ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +AUTHENTICATION_API = os.environ.get("AUTHENTICATION_API", "https://0.0.0.0:8000") +RESOURCE_API = os.environ.get("RESOURCE_API", "https://0.0.0.0:8010") +ROOT_PATH = os.path.dirname(os.path.abspath(__file__)) CLIENT_CERTIFICATE = f"{ROOT_PATH}/certs/client-cert.pem" CLIENT_PRIVATE_KEY = f"{ROOT_PATH}/certs/client-key.pem" @@ -23,9 +24,9 @@ def get_session(): def pushed_authorization_request(): - session = get_session() - response = session.post( - f"{AUTHENTICATION_API}/api/v1/par/", + + response = requests.post( + f"{AUTHENTICATION_API}/api/v1/par", json={ "response_type": "code", "client_id": f"{conf.CLIENT_ID}", @@ -34,7 +35,9 @@ def pushed_authorization_request(): "code_challenge_method": "S256", }, verify=False, + cert=(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY), ) + print(response.status_code, response.text) return response.json() @@ -43,7 +46,7 @@ def initiate_authorization(request_uri: str): /as/authorise?request_uri=urn:ietf:params:oauth:request_uri:UymBrux4ZEMrBRKx9UyKyIm98zpX1cHmAPGAGNofmm4 """ response = requests.post( - f"{AUTHENTICATION_API}/api/v1/authorize/", + f"{AUTHENTICATION_API}/api/v1/authorize", json={ "request_uri": request_uri, "client_id": f"{conf.CLIENT_ID}", @@ -111,15 +114,27 @@ def introspect_token(fapi_token: str): return introspection_response.json() -def client_side_decoding(): +def client_side_decoding(token: str): """ Use the jwks to decode the token """ jwks_url = conf.FAPI_API + "/.well-known/jwks.json" jwks_client = jwt.PyJWKClient(jwks_url) - header = jwt.get_unverified_header(fapi_token) + header = jwt.get_unverified_header(token) key = jwks_client.get_signing_key(header["kid"]).key - decoded = jwt.decode(fapi_token, key, [header["alg"]], audience=f"{conf.CLIENT_ID}") + decoded = jwt.decode(token, key, [header["alg"]], audience=f"{conf.CLIENT_ID}") + # Example of tests to apply + print(decoded) + print(conf.FAPI_API) + if decoded["aud"] != conf.CLIENT_ID: + raise ValueError("Invalid audience") + if decoded["iss"] != conf.FAPI_API: + raise ValueError("Invalid issuer") + if decoded["exp"] < int(time.time()): + raise ValueError("Token expired") + if decoded["iat"] > int(time.time()): + raise ValueError("Token issued in the future") + return decoded @@ -128,15 +143,27 @@ def client_side_decoding(): data = pushed_authorization_request() # Take note of the ticket ticket = initiate_authorization(data["request_uri"])["ticket"] - # authenticate the user + # # authenticate the user token = get_user_token()["access_token"] - # Ask for user's consent + # # Ask for user's consent consent = give_consent(token) - # Now we have identified the user, we can use the ticket to request an authorization code - auth_code = authentication_issue_request(token, ticket)["authorization_code"] - # Now we need to exchange the auth code for an access token - result = get_fapi_token(auth_code) + # # Now we have identified the user, we can use the ticket to request an authorization code + issue_response = authentication_issue_request(token, ticket) + # Client side - check the id_token values + print(client_side_decoding(issue_response["id_token"])) + # # Now we need to exchange the auth code for an access token + result = get_fapi_token(issue_response["authorization_code"]) fapi_token = result["access_token"] - # The token can be used to access protected APIs - # The resource server can introspect the token + # # The token can be used to access protected APIs + # # The resource server can introspect the token print(introspect_token(fapi_token)) + # We should now be able to use the token to retrieve data from the resource server + result = requests.get( + f"{RESOURCE_API}/api/v1/consumption", + verify=False, + headers={"Authorization": f"Bearer {fapi_token}"}, + cert=(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY), + ) + print(result.status_code) + print(result.text) + print(result.json()) diff --git a/compose.yml b/compose.yml index 123936c..04b083c 100644 --- a/compose.yml +++ b/compose.yml @@ -1,25 +1,47 @@ services: - web: + authentication_web: image: nginx volumes: - - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf + - ./nginx:/etc/nginx/templates - ./certs/:/etc/nginx/certs/ environment: - - FLASK_SERVER_ADDR=backend:8080 - # command: /bin/bash -c "envsubst '\$FLASK_SERVER_ADDR' < /tmp/nginx.conf > /etc/nginx/conf.d/default.conf && cat /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" + - UPSTREAM=authentication_backend ports: - - 8000:80 - - 443:443 + - 8000:443 + # - 443:443 depends_on: - - backend + - authentication_backend - backend: + authentication_backend: build: - context: backend + context: authentication stop_signal: SIGINT environment: - FAPI_API=http://host.docker.internal:8020 # - FAPI_API=https://perseus-demo-fapi.ib1.org command: ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"] volumes: - - ./backend/api:/code/api + - ./authentication/api:/code/api + + resource_web: + image: nginx + volumes: + - ./nginx:/etc/nginx/templates + - ./certs/:/etc/nginx/certs/ + environment: + - UPSTREAM=resource_backend + ports: + - 8010:443 + depends_on: + - resource_backend + + resource_backend: + build: + context: resource + stop_signal: SIGINT + environment: + - ISSUER_URL=http://host.docker.internal:8020 + # - FAPI_API=https://perseus-demo-fapi.ib1.org + command: ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"] + volumes: + - ./resource/api:/code/api diff --git a/nginx/nginx.conf b/nginx/default.conf.template similarity index 92% rename from nginx/nginx.conf rename to nginx/default.conf.template index 1d6cc94..fcbe623 100644 --- a/nginx/nginx.conf +++ b/nginx/default.conf.template @@ -1,7 +1,7 @@ server { listen 80; location / { - proxy_pass http://backend:8080; + proxy_pass http://${UPSTREAM}:8080; } } @@ -25,6 +25,6 @@ server { # # if the client certificate verified # # will have the value of 'SUCCESS' and 'NONE' otherwise proxy_set_header VERIFIED $ssl_client_verify; - proxy_pass http://backend:8080; + proxy_pass http://${UPSTREAM}:8080; } } \ No newline at end of file diff --git a/resource/.dockerignore b/resource/.dockerignore new file mode 100644 index 0000000..722c34c --- /dev/null +++ b/resource/.dockerignore @@ -0,0 +1,5 @@ +**/* +!Pipfile +!Pipfile.lock +!api/ +!data/ \ No newline at end of file diff --git a/resource/Dockerfile b/resource/Dockerfile new file mode 100644 index 0000000..9ba2a90 --- /dev/null +++ b/resource/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.12-slim +RUN pip install pipenv +COPY Pipfile* /code/ +WORKDIR /code +RUN pipenv install --system --deploy --ignore-pipfile +COPY ./api /code/api +COPY ./data /code/data +EXPOSE 8080 +CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8080"] \ No newline at end of file diff --git a/resource/Pipfile b/resource/Pipfile new file mode 100644 index 0000000..17b48f0 --- /dev/null +++ b/resource/Pipfile @@ -0,0 +1,20 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +fastapi = "*" +requests = "*" +cryptography = "*" +uvicorn = {extras = ["standard"], version = "*"} +typing-extensions = "*" + +[dev-packages] +pytest = "*" +pytest-mock = "*" +httpx = "*" +ruff = "*" + +[requires] +python_version = "3.12" diff --git a/resource/Pipfile.lock b/resource/Pipfile.lock new file mode 100644 index 0000000..05ee563 --- /dev/null +++ b/resource/Pipfile.lock @@ -0,0 +1,845 @@ +{ + "_meta": { + "hash": { + "sha256": "55d7403cc23187b5cb27b2d4c7b177ea3b715a8ab15532fc3abeb2829acef44b" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "annotated-types": { + "hashes": [ + "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", + "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.0" + }, + "anyio": { + "hashes": [ + "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", + "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f" + ], + "markers": "python_version >= '3.8'", + "version": "==4.2.0" + }, + "certifi": { + "hashes": [ + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.2.2" + }, + "cffi": { + "hashes": [ + "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", + "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", + "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", + "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", + "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", + "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", + "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", + "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", + "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", + "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", + "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", + "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", + "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", + "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", + "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", + "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", + "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", + "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", + "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", + "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", + "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", + "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", + "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", + "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", + "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", + "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", + "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", + "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", + "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", + "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", + "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", + "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", + "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", + "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", + "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", + "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", + "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", + "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", + "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", + "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", + "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", + "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", + "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", + "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", + "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", + "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", + "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", + "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", + "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", + "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", + "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", + "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.16.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", + "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", + "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", + "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", + "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", + "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", + "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", + "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", + "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", + "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", + "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", + "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", + "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", + "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", + "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", + "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", + "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", + "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", + "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", + "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", + "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", + "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", + "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", + "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", + "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", + "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", + "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", + "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", + "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", + "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", + "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", + "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", + "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", + "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", + "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", + "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", + "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", + "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", + "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", + "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", + "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", + "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", + "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", + "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", + "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", + "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", + "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", + "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", + "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", + "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", + "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", + "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", + "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", + "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", + "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", + "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", + "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", + "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", + "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", + "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", + "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", + "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", + "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", + "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", + "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", + "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", + "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", + "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", + "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", + "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", + "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", + "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", + "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", + "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", + "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", + "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", + "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", + "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", + "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", + "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", + "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", + "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", + "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", + "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", + "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", + "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", + "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", + "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", + "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", + "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.2" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "cryptography": { + "hashes": [ + "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380", + "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589", + "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea", + "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65", + "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a", + "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3", + "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008", + "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1", + "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2", + "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635", + "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2", + "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90", + "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee", + "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a", + "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242", + "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12", + "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2", + "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d", + "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be", + "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee", + "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6", + "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529", + "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929", + "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1", + "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6", + "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a", + "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446", + "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9", + "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888", + "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4", + "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33", + "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==42.0.2" + }, + "fastapi": { + "hashes": [ + "sha256:2c9bab24667293b501cad8dd388c05240c850b58ec5876ee3283c47d6e1e3a4d", + "sha256:f3817eac96fe4f65a2ebb4baa000f394e55f5fccdaf7f75250804bc58f354f73" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.109.2" + }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httptools": { + "hashes": [ + "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563", + "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142", + "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d", + "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b", + "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4", + "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb", + "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658", + "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084", + "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2", + "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97", + "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837", + "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3", + "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58", + "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da", + "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d", + "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90", + "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0", + "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1", + "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2", + "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e", + "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0", + "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf", + "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc", + "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3", + "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503", + "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a", + "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3", + "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949", + "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84", + "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb", + "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", + "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f", + "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e", + "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81", + "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185", + "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3" + ], + "version": "==0.6.1" + }, + "idna": { + "hashes": [ + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + ], + "markers": "python_version >= '3.5'", + "version": "==3.6" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, + "pydantic": { + "hashes": [ + "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f", + "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9" + ], + "markers": "python_version >= '3.8'", + "version": "==2.6.1" + }, + "pydantic-core": { + "hashes": [ + "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379", + "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06", + "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05", + "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7", + "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753", + "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a", + "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731", + "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc", + "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380", + "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3", + "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c", + "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11", + "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990", + "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a", + "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2", + "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8", + "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97", + "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a", + "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8", + "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef", + "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77", + "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33", + "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82", + "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5", + "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b", + "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55", + "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e", + "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b", + "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7", + "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec", + "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc", + "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469", + "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b", + "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20", + "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e", + "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d", + "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f", + "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b", + "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039", + "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e", + "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2", + "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f", + "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b", + "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc", + "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8", + "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522", + "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e", + "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784", + "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a", + "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890", + "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485", + "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545", + "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f", + "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943", + "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878", + "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f", + "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17", + "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7", + "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286", + "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c", + "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb", + "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646", + "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978", + "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8", + "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15", + "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272", + "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2", + "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55", + "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf", + "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545", + "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4", + "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a", + "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804", + "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4", + "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0", + "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a", + "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113", + "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d", + "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25" + ], + "markers": "python_version >= '3.8'", + "version": "==2.16.2" + }, + "python-dotenv": { + "hashes": [ + "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", + "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" + ], + "version": "==1.0.1" + }, + "pyyaml": { + "hashes": [ + "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", + "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", + "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + ], + "version": "==6.0.1" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "sniffio": { + "hashes": [ + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + }, + "starlette": { + "hashes": [ + "sha256:13d429aa93a61dc40bf503e8c801db1f1bca3dc706b10ef2434a36123568f044", + "sha256:90a671733cfb35771d8cc605e0b679d23b992f8dcfad48cc60b38cb29aeb7080" + ], + "markers": "python_version >= '3.8'", + "version": "==0.36.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", + "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==4.9.0" + }, + "urllib3": { + "hashes": [ + "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20", + "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.0" + }, + "uvicorn": { + "extras": [ + "standard" + ], + "hashes": [ + "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a", + "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4" + ], + "markers": "python_version >= '3.8'", + "version": "==0.27.1" + }, + "uvloop": { + "hashes": [ + "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd", + "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec", + "sha256:13dfdf492af0aa0a0edf66807d2b465607d11c4fa48f4a1fd41cbea5b18e8e8b", + "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc", + "sha256:271718e26b3e17906b28b67314c45d19106112067205119dddbd834c2b7ce797", + "sha256:2df95fca285a9f5bfe730e51945ffe2fa71ccbfdde3b0da5772b4ee4f2e770d5", + "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2", + "sha256:34175c9fd2a4bc3adc1380e1261f60306344e3407c20a4d684fd5f3be010fa3d", + "sha256:45bf4c24c19fb8a50902ae37c5de50da81de4922af65baf760f7c0c42e1088be", + "sha256:472d61143059c84947aa8bb74eabbace30d577a03a1805b77933d6bd13ddebbd", + "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12", + "sha256:492e2c32c2af3f971473bc22f086513cedfc66a130756145a931a90c3958cb17", + "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef", + "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24", + "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428", + "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1", + "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849", + "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593", + "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd", + "sha256:6e3d4e85ac060e2342ff85e90d0c04157acb210b9ce508e784a944f852a40e67", + "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6", + "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3", + "sha256:78ab247f0b5671cc887c31d33f9b3abfb88d2614b84e4303f1a63b46c046c8bd", + "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8", + "sha256:8ca4956c9ab567d87d59d49fa3704cf29e37109ad348f2d5223c9bf761a332e7", + "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533", + "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957", + "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650", + "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e", + "sha256:e27f100e1ff17f6feeb1f33968bc185bf8ce41ca557deee9d9bbbffeb72030b7", + "sha256:f467a5fd23b4fc43ed86342641f3936a68ded707f4627622fa3f82a120e18256" + ], + "version": "==0.19.0" + }, + "watchfiles": { + "hashes": [ + "sha256:02b73130687bc3f6bb79d8a170959042eb56eb3a42df3671c79b428cd73f17cc", + "sha256:02d91cbac553a3ad141db016e3350b03184deaafeba09b9d6439826ee594b365", + "sha256:06247538e8253975bdb328e7683f8515ff5ff041f43be6c40bff62d989b7d0b0", + "sha256:08dca260e85ffae975448e344834d765983237ad6dc308231aa16e7933db763e", + "sha256:0d9ac347653ebd95839a7c607608703b20bc07e577e870d824fa4801bc1cb124", + "sha256:0dd5fad9b9c0dd89904bbdea978ce89a2b692a7ee8a0ce19b940e538c88a809c", + "sha256:11cd0c3100e2233e9c53106265da31d574355c288e15259c0d40a4405cbae317", + "sha256:18722b50783b5e30a18a8a5db3006bab146d2b705c92eb9a94f78c72beb94094", + "sha256:18d5b4da8cf3e41895b34e8c37d13c9ed294954907929aacd95153508d5d89d7", + "sha256:1ad7247d79f9f55bb25ab1778fd47f32d70cf36053941f07de0b7c4e96b5d235", + "sha256:1b8d1eae0f65441963d805f766c7e9cd092f91e0c600c820c764a4ff71a0764c", + "sha256:1bd467213195e76f838caf2c28cd65e58302d0254e636e7c0fca81efa4a2e62c", + "sha256:1c9198c989f47898b2c22201756f73249de3748e0fc9de44adaf54a8b259cc0c", + "sha256:1fd9a5205139f3c6bb60d11f6072e0552f0a20b712c85f43d42342d162be1235", + "sha256:214cee7f9e09150d4fb42e24919a1e74d8c9b8a9306ed1474ecaddcd5479c293", + "sha256:27b4035013f1ea49c6c0b42d983133b136637a527e48c132d368eb19bf1ac6aa", + "sha256:3a23092a992e61c3a6a70f350a56db7197242f3490da9c87b500f389b2d01eef", + "sha256:3ad692bc7792be8c32918c699638b660c0de078a6cbe464c46e1340dadb94c19", + "sha256:3ccceb50c611c433145502735e0370877cced72a6c70fd2410238bcbc7fe51d8", + "sha256:3d0f32ebfaa9c6011f8454994f86108c2eb9c79b8b7de00b36d558cadcedaa3d", + "sha256:3f92944efc564867bbf841c823c8b71bb0be75e06b8ce45c084b46411475a915", + "sha256:40bca549fdc929b470dd1dbfcb47b3295cb46a6d2c90e50588b0a1b3bd98f429", + "sha256:43babacef21c519bc6631c5fce2a61eccdfc011b4bcb9047255e9620732c8097", + "sha256:4566006aa44cb0d21b8ab53baf4b9c667a0ed23efe4aaad8c227bfba0bf15cbe", + "sha256:49f56e6ecc2503e7dbe233fa328b2be1a7797d31548e7a193237dcdf1ad0eee0", + "sha256:4c48a10d17571d1275701e14a601e36959ffada3add8cdbc9e5061a6e3579a5d", + "sha256:4ea10a29aa5de67de02256a28d1bf53d21322295cb00bd2d57fcd19b850ebd99", + "sha256:511f0b034120cd1989932bf1e9081aa9fb00f1f949fbd2d9cab6264916ae89b1", + "sha256:51ddac60b96a42c15d24fbdc7a4bfcd02b5a29c047b7f8bf63d3f6f5a860949a", + "sha256:57d430f5fb63fea141ab71ca9c064e80de3a20b427ca2febcbfcef70ff0ce895", + "sha256:59137c0c6826bd56c710d1d2bda81553b5e6b7c84d5a676747d80caf0409ad94", + "sha256:5a03651352fc20975ee2a707cd2d74a386cd303cc688f407296064ad1e6d1562", + "sha256:5eb86c6acb498208e7663ca22dbe68ca2cf42ab5bf1c776670a50919a56e64ab", + "sha256:642d66b75eda909fd1112d35c53816d59789a4b38c141a96d62f50a3ef9b3360", + "sha256:6674b00b9756b0af620aa2a3346b01f8e2a3dc729d25617e1b89cf6af4a54eb1", + "sha256:668c265d90de8ae914f860d3eeb164534ba2e836811f91fecc7050416ee70aa7", + "sha256:66fac0c238ab9a2e72d026b5fb91cb902c146202bbd29a9a1a44e8db7b710b6f", + "sha256:6c107ea3cf2bd07199d66f156e3ea756d1b84dfd43b542b2d870b77868c98c03", + "sha256:6c889025f59884423428c261f212e04d438de865beda0b1e1babab85ef4c0f01", + "sha256:6cb8fdc044909e2078c248986f2fc76f911f72b51ea4a4fbbf472e01d14faa58", + "sha256:6e9be3ef84e2bb9710f3f777accce25556f4a71e15d2b73223788d528fcc2052", + "sha256:7f762a1a85a12cc3484f77eee7be87b10f8c50b0b787bb02f4e357403cad0c0e", + "sha256:83a696da8922314ff2aec02987eefb03784f473281d740bf9170181829133765", + "sha256:853853cbf7bf9408b404754b92512ebe3e3a83587503d766d23e6bf83d092ee6", + "sha256:8ad3fe0a3567c2f0f629d800409cd528cb6251da12e81a1f765e5c5345fd0137", + "sha256:8c6ed10c2497e5fedadf61e465b3ca12a19f96004c15dcffe4bd442ebadc2d85", + "sha256:8d5f400326840934e3507701f9f7269247f7c026d1b6cfd49477d2be0933cfca", + "sha256:927c589500f9f41e370b0125c12ac9e7d3a2fd166b89e9ee2828b3dda20bfe6f", + "sha256:9a0aa47f94ea9a0b39dd30850b0adf2e1cd32a8b4f9c7aa443d852aacf9ca214", + "sha256:9b37a7ba223b2f26122c148bb8d09a9ff312afca998c48c725ff5a0a632145f7", + "sha256:9c873345680c1b87f1e09e0eaf8cf6c891b9851d8b4d3645e7efe2ec20a20cc7", + "sha256:9d09869f2c5a6f2d9df50ce3064b3391d3ecb6dced708ad64467b9e4f2c9bef3", + "sha256:9d353c4cfda586db2a176ce42c88f2fc31ec25e50212650c89fdd0f560ee507b", + "sha256:a1e3014a625bcf107fbf38eece0e47fa0190e52e45dc6eee5a8265ddc6dc5ea7", + "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", + "sha256:ab03a90b305d2588e8352168e8c5a1520b721d2d367f31e9332c4235b30b8994", + "sha256:aff06b2cac3ef4616e26ba17a9c250c1fe9dd8a5d907d0193f84c499b1b6e6a9", + "sha256:b3cab0e06143768499384a8a5efb9c4dc53e19382952859e4802f294214f36ec", + "sha256:b4a21f71885aa2744719459951819e7bf5a906a6448a6b2bbce8e9cc9f2c8128", + "sha256:b6d45d9b699ecbac6c7bd8e0a2609767491540403610962968d258fd6405c17c", + "sha256:be6dd5d52b73018b21adc1c5d28ac0c68184a64769052dfeb0c5d9998e7f56a2", + "sha256:c550a56bf209a3d987d5a975cdf2063b3389a5d16caf29db4bdddeae49f22078", + "sha256:c76c635fabf542bb78524905718c39f736a98e5ab25b23ec6d4abede1a85a6a3", + "sha256:c81818595eff6e92535ff32825f31c116f867f64ff8cdf6562cd1d6b2e1e8f3e", + "sha256:cfb92d49dbb95ec7a07511bc9efb0faff8fe24ef3805662b8d6808ba8409a71a", + "sha256:d23bcd6c8eaa6324fe109d8cac01b41fe9a54b8c498af9ce464c1aeeb99903d6", + "sha256:d5b1dc0e708fad9f92c296ab2f948af403bf201db8fb2eb4c8179db143732e49", + "sha256:d78f30cbe8b2ce770160d3c08cff01b2ae9306fe66ce899b73f0409dc1846c1b", + "sha256:d8f57c4461cd24fda22493109c45b3980863c58a25b8bec885ca8bea6b8d4b28", + "sha256:d9792dff410f266051025ecfaa927078b94cc7478954b06796a9756ccc7e14a9", + "sha256:e7941bbcfdded9c26b0bf720cb7e6fd803d95a55d2c14b4bd1f6a2772230c586", + "sha256:ebe684d7d26239e23d102a2bad2a358dedf18e462e8808778703427d1f584400", + "sha256:ec8c8900dc5c83650a63dd48c4d1d245343f904c4b64b48798c67a3767d7e165", + "sha256:f564bf68404144ea6b87a78a3f910cc8de216c6b12a4cf0b27718bf4ec38d303", + "sha256:fd7ac678b92b29ba630d8c842d8ad6c555abda1b9ef044d6cc092dacbfc9719d" + ], + "version": "==0.21.0" + }, + "websockets": { + "hashes": [ + "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b", + "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6", + "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", + "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", + "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205", + "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892", + "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53", + "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", + "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", + "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c", + "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd", + "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b", + "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931", + "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30", + "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370", + "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be", + "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec", + "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf", + "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62", + "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b", + "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402", + "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f", + "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123", + "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9", + "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603", + "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45", + "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558", + "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4", + "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438", + "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137", + "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480", + "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447", + "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8", + "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04", + "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c", + "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", + "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967", + "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", + "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d", + "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def", + "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c", + "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", + "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2", + "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", + "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b", + "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28", + "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7", + "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d", + "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", + "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468", + "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8", + "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae", + "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611", + "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", + "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9", + "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca", + "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f", + "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2", + "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077", + "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2", + "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6", + "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374", + "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", + "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", + "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53", + "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399", + "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547", + "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3", + "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870", + "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", + "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8", + "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" + ], + "version": "==12.0" + } + }, + "develop": { + "anyio": { + "hashes": [ + "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", + "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f" + ], + "markers": "python_version >= '3.8'", + "version": "==4.2.0" + }, + "certifi": { + "hashes": [ + "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", + "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + ], + "markers": "python_version >= '3.6'", + "version": "==2024.2.2" + }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httpcore": { + "hashes": [ + "sha256:5c0f9546ad17dac4d0772b0808856eb616eb8b48ce94f49ed819fd6982a8a544", + "sha256:9a6a501c3099307d9fd76ac244e08503427679b1e81ceb1d922485e2f2462ad2" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.3" + }, + "httpx": { + "hashes": [ + "sha256:451b55c30d5185ea6b23c2c793abf9bb237d2a7dfb901ced6ff69ad37ec1dfaf", + "sha256:8915f5a3627c4d47b73e8202457cb28f1266982d1159bd5779d86a80c0eab1cd" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.26.0" + }, + "idna": { + "hashes": [ + "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + ], + "markers": "python_version >= '3.5'", + "version": "==3.6" + }, + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "pluggy": { + "hashes": [ + "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", + "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.0" + }, + "pytest": { + "hashes": [ + "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c", + "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==8.0.0" + }, + "pytest-mock": { + "hashes": [ + "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f", + "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.12.0" + }, + "ruff": { + "hashes": [ + "sha256:0034d5b6323e6e8fe91b2a1e55b02d92d0b582d2953a2b37a67a2d7dedbb7acc", + "sha256:00a818e2db63659570403e44383ab03c529c2b9678ba4ba6c105af7854008105", + "sha256:0a725823cb2a3f08ee743a534cb6935727d9e47409e4ad72c10a3faf042ad5ba", + "sha256:13471684694d41ae0f1e8e3a7497e14cd57ccb7dd72ae08d56a159d6c9c3e30e", + "sha256:3b42b5d8677cd0c72b99fcaf068ffc62abb5a19e71b4a3b9cfa50658a0af02f1", + "sha256:6b95ac9ce49b4fb390634d46d6ece32ace3acdd52814671ccaf20b7f60adb232", + "sha256:7022d66366d6fded4ba3889f73cd791c2d5621b2ccf34befc752cb0df70f5fad", + "sha256:a11567e20ea39d1f51aebd778685582d4c56ccb082c1161ffc10f79bebe6df35", + "sha256:be60592f9d218b52f03384d1325efa9d3b41e4c4d55ea022cd548547cc42cd2b", + "sha256:c92db7101ef5bfc18e96777ed7bc7c822d545fa5977e90a585accac43d22f18a", + "sha256:dc586724a95b7d980aa17f671e173df00f0a2eef23f8babbeee663229a938fec", + "sha256:dd81b911d28925e7e8b323e8d06951554655021df8dd4ac3045d7212ac4ba080", + "sha256:e3affdcbc2afb6f5bd0eb3130139ceedc5e3f28d206fe49f63073cb9e65988e0", + "sha256:e5cb5526d69bb9143c2e4d2a115d08ffca3d8e0fddc84925a7b54931c96f5c02", + "sha256:efababa8e12330aa94a53e90a81eb6e2d55f348bc2e71adbf17d9cad23c03ee6", + "sha256:f3ef052283da7dec1987bba8d8733051c2325654641dfe5877a4022108098683", + "sha256:fbd2288890b88e8aab4499e55148805b58ec711053588cc2f0196a44f6e3d855" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.2.1" + }, + "sniffio": { + "hashes": [ + "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", + "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + } + } +} diff --git a/resource/api/__init__.py b/resource/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/resource/api/auth.py b/resource/api/auth.py new file mode 100644 index 0000000..640bf3c --- /dev/null +++ b/resource/api/auth.py @@ -0,0 +1,96 @@ +import logging +import urllib.parse +import uuid +from typing import Optional, Tuple +import requests + + +from . import conf + + +log = logging.getLogger(__name__) + + +class AccessTokenValidatorError(Exception): + pass + + +class AccessTokenNoCertificateError(AccessTokenValidatorError): + pass + + +class AccessTokenInactiveError(AccessTokenValidatorError): + pass + + +def get_openid_configuration(issuer_url: str) -> dict: + """ + Get the well-known configuration for a given issuer URL + """ + print(urllib.parse.urljoin(issuer_url, "/.well-known/openid-configuration")) + response = requests.get( + url=urllib.parse.urljoin(issuer_url, "/.well-known/openid-configuration"), + ) + response.raise_for_status() + return response.json() + + +def check_token(token: str, client_certificate: str) -> dict: + openid_config = get_openid_configuration(conf.ISSUER_URL) + try: + response = requests.post( + url=openid_config["introspection_endpoint"], + json={"token": token, "client_certificate": client_certificate}, + auth=(conf.CLIENT_ID, conf.CLIENT_SECRET), + ) + if response.status_code != 200: + log.error(f"introspection request failed: {response.text}") + raise AccessTokenValidatorError("Introspection request failed") + + except requests.exceptions.RequestException as e: + log.error(f"introspection request failed: {e}") + raise AccessTokenValidatorError("Introspection request failed") + return response.json() + + +def introspect( + client_certificate: str, token: str, x_fapi_interaction_id: Optional[str] = None +) -> Tuple[dict, dict]: + """ + Introspection fails if: + 1. Querying the token introspection endpoint fails + 2. A token is returned with active: false + 3. Scope is specified, and the required scope is not in the token scopes + 4. No client certificate is presented + If introspection succeeds, return a dict suitable to use as headers + including Date and x-fapi-interaction-id, as well as the introspection response + """ + + # Deny access to non-MTLS connections + if client_certificate is None: + log.warning("no client cert presented") + raise AccessTokenNoCertificateError("No client certificate presented") + introspection_response = check_token(token, client_certificate) + log.debug(f"introspection response {introspection_response}") + + # All valid introspection responses contain 'active', as the default behaviour + # for an invalid token is to create a simple JSON {'active':false} response + if ( + "active" not in introspection_response + or introspection_response["active"] is not True + ): + raise AccessTokenInactiveError( + "Invalid introspection response, does not contain 'active' or is not True" + ) + headers = {} + # FAPI requires that the resource server set the date header in the response + # headers["Date"] = email.utils.formatdate() + + # Get FAPI interaction ID if set, or create a new one otherwise + if x_fapi_interaction_id is None: + x_fapi_interaction_id = str(uuid.uuid4()) + log.debug(f"issuing new interaction ID = {x_fapi_interaction_id}") + else: + log.debug(f"using existing interaction ID = {x_fapi_interaction_id}") + headers["x-fapi-interaction-id"] = x_fapi_interaction_id + return introspection_response, headers diff --git a/resource/api/conf.py b/resource/api/conf.py new file mode 100644 index 0000000..83da13f --- /dev/null +++ b/resource/api/conf.py @@ -0,0 +1,8 @@ +import os + +ENV = os.environ.get("ENV", "dev") +DIRNAME = os.path.dirname(os.path.realpath(__file__)) +ISSUER_URL = os.environ.get("ISSUER_URL", "") +CLIENT_ID = "21653835348762" +CLIENT_SECRET = "uE4NgqeIpuSV_XejQ7Ds3jsgA1yXhjR1MXJ1LbPuyls" +OPEN_API_ROOT = "/dev" if ENV == "prod" else "" diff --git a/resource/api/main.py b/resource/api/main.py new file mode 100644 index 0000000..c96186f --- /dev/null +++ b/resource/api/main.py @@ -0,0 +1,76 @@ +import json +import os +from typing import Annotated + +from fastapi import FastAPI, HTTPException, Response, Depends, Header +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from fastapi import Request + +from . import models +from . import auth +from . import conf + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +security = HTTPBearer(auto_error=False) + + +app = FastAPI( + docs_url="/api-docs", + title="Perseus Energy Demo Resource API", + root_path=conf.OPEN_API_ROOT, +) + + +@app.get("/", response_model=dict) +def root(): + return {"urls": ["/api/v1/"]} + + +@app.get("/api/v1", response_model=dict) +def api_urls(): + return {"urls": ["/api/v1/consumption"]} + + +# @app.get("/api/v1/info") +@app.get("/api/v1/info") +def request_info(request: Request): + """Return full details about the received request, including http and https headers + Useful for testing and debugging + """ + return { + "request": { + "headers": dict(request.headers), + "method": request.method, + "url": request.url, + # "body": request.body().decode("utf-8"), + }, + # "environ": str(request.environ), + } + + +@app.get("/api/v1/consumption", response_model=models.MeterData) +def consumption( + response: Response, + token: HTTPAuthorizationCredentials = Depends(security), + x_amzn_mtls_clientcert: Annotated[str | None, Header()] = None, + x_fapi_interaction_id: Annotated[str | None, Header()] = None, +): + if x_amzn_mtls_clientcert is None: + raise HTTPException(status_code=401, detail="No client certificate provided") + if token and token.credentials: + try: + _, headers = auth.introspect( + x_amzn_mtls_clientcert, token.credentials, x_fapi_interaction_id + ) + print(headers) + except auth.AccessTokenValidatorError as e: + raise HTTPException(status_code=401, detail=str(e)) + else: + for key, value in headers.items(): + response.headers[key] = value + else: + raise HTTPException(status_code=401, detail="No token provided") + with open(f"{ROOT_DIR}/data/7_day_consumption.json") as f: + data = json.load(f) + return {"data": data} diff --git a/resource/api/models.py b/resource/api/models.py new file mode 100644 index 0000000..28be284 --- /dev/null +++ b/resource/api/models.py @@ -0,0 +1,16 @@ +import datetime +from pydantic import BaseModel, Field + +class Consumption(BaseModel): + value: float + unitCode: str + +class Reading(BaseModel): + type: str + from_: datetime.datetime = Field(None, alias="from") + to: datetime.datetime + consumption: Consumption + +class MeterData(BaseModel): + data: list[Reading] + diff --git a/resource/copilot/.workspace b/resource/copilot/.workspace new file mode 100644 index 0000000..3a88727 --- /dev/null +++ b/resource/copilot/.workspace @@ -0,0 +1 @@ +application: perseus-demo-energy diff --git a/resource/copilot/environments/dev/manifest.yml b/resource/copilot/environments/dev/manifest.yml new file mode 100644 index 0000000..d098ca0 --- /dev/null +++ b/resource/copilot/environments/dev/manifest.yml @@ -0,0 +1,20 @@ +# The manifest for the "dev" environment. +# Read the full specification for the "Environment" type at: +# https://aws.github.io/copilot-cli/docs/manifest/environment/ + +# Your environment name will be used in naming your resources like VPC, cluster, etc. +name: dev +type: Environment + +# Import your own VPC and subnets or configure how they should be created. +# network: +# vpc: +# id: + +http: + public: + certificates: + - arn:aws:acm:eu-west-2:232615051732:certificate/fdcc635e-c99a-4ae5-add1-25f1f7de5c74 +# Configure observability for your environment resources. +observability: + container_insights: false diff --git a/resource/copilot/environments/overrides/README.md b/resource/copilot/environments/overrides/README.md new file mode 100644 index 0000000..1e2c99e --- /dev/null +++ b/resource/copilot/environments/overrides/README.md @@ -0,0 +1,17 @@ +# Overriding Copilot generated CloudFormation templates with YAML Patches + +The file `cfn.patches.yml` contains a list of YAML/JSON patches to apply to +your template before AWS Copilot deploys it. + +To view examples and an explanation of how YAML patches work, check out the [documentation](https://aws.github.io/copilot-cli/docs/developing/overrides/yamlpatch). + +Note only [`add`](https://www.rfc-editor.org/rfc/rfc6902#section-4.1), +[`remove`](https://www.rfc-editor.org/rfc/rfc6902#section-4.2), and +[`replace`](https://www.rfc-editor.org/rfc/rfc6902#section-4.3) +operations are supported by Copilot. +Patches are applied in the order specified in the file. + +## Troubleshooting + +* `copilot [noun] package` preview the transformed template by writing to stdout. +* `copilot [noun] package --diff` show the difference against the template deployed in your environment. \ No newline at end of file diff --git a/resource/copilot/environments/overrides/cfn.patches.yml b/resource/copilot/environments/overrides/cfn.patches.yml new file mode 100644 index 0000000..a43d610 --- /dev/null +++ b/resource/copilot/environments/overrides/cfn.patches.yml @@ -0,0 +1,6 @@ + +# Add passthrough MutualAuthentication to the HTTPSListener +- op: add + path: /Resources/HTTPSListener/Properties/MutualAuthentication + value: + Mode: passthrough diff --git a/resource/copilot/perseus-resource/manifest.yml b/resource/copilot/perseus-resource/manifest.yml new file mode 100644 index 0000000..0000892 --- /dev/null +++ b/resource/copilot/perseus-resource/manifest.yml @@ -0,0 +1,53 @@ +# The manifest for the "backend" service. +# Read the full specification for the "Load Balanced Web Service" type at: +# https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/ + +# Your service name will be used in naming your resources like log groups, ECS services, etc. +name: perseus-resource +type: Load Balanced Web Service + +# Distribute traffic to your service. +http: + # Requests to this path will be forwarded to your service. + # To match all requests you can use the "/" path. + path: '/' + alias: perseus-demo-energy.ib1.org + hosted_zone: Z06253313MKVN98JLAQRK + # You can specify a custom health check path. The default is "/". + # healthcheck: '/' +# in copilot/{service name}/manifest.yml + +# Configuration for your containers and service. +image: + # Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#image-build + build: Dockerfile + # Port exposed through your container to route traffic to it. + port: 8080 + +cpu: 256 # Number of CPU units for the task. +memory: 512 # Amount of memory in MiB used by the task. +platform: linux/x86_64 # See https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/#platform +count: 1 # Number of tasks that should be running in your service. +exec: true # Enable running commands in your container. +network: + connect: true # Enable Service Connect for intra-environment traffic between services. + +# storage: + # readonly_fs: true # Limit to read-only access to mounted root filesystems. + +# Optional fields for more advanced use-cases. +# +variables: # Pass environment variables as key value pairs. + LOG_LEVEL: info + +secrets: + PERSEUS_DEMO_ENERGY_PEM: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/softwarestatement.pem + PERSEUS_DEMO_ENERGY_KEY: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/softwarestatement.key + DIRECTORY_CLIENT_ID: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/client_id + ISSUER_URL: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/issuer_url +# You can override any of the values defined above by environment. +#environments: +# test: +# count: 2 # Number of tasks to run for the "test" environment. +# deployment: # The deployment strategy for the "test" environment. +# rolling: 'recreate' # Stops existing tasks before new ones are started for faster deployments. \ No newline at end of file diff --git a/resource/data/7_day_consumption.json b/resource/data/7_day_consumption.json new file mode 100644 index 0000000..9dc32e9 --- /dev/null +++ b/resource/data/7_day_consumption.json @@ -0,0 +1,3026 @@ +[ + { + "type": "Electricity", + "from": "2023-10-25T09:00:00+00:00", + "to": "2023-10-25T09:30:00+00:00", + "consumption": { + "value": 194.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T09:30:00+00:00", + "to": "2023-10-25T10:00:00+00:00", + "consumption": { + "value": 119.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T10:00:00+00:00", + "to": "2023-10-25T10:30:00+00:00", + "consumption": { + "value": 229.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T10:30:00+00:00", + "to": "2023-10-25T11:00:00+00:00", + "consumption": { + "value": 198.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T11:00:00+00:00", + "to": "2023-10-25T11:30:00+00:00", + "consumption": { + "value": 130.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T11:30:00+00:00", + "to": "2023-10-25T12:00:00+00:00", + "consumption": { + "value": 258.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T12:00:00+00:00", + "to": "2023-10-25T12:30:00+00:00", + "consumption": { + "value": 101.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T12:30:00+00:00", + "to": "2023-10-25T13:00:00+00:00", + "consumption": { + "value": 200.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T13:00:00+00:00", + "to": "2023-10-25T13:30:00+00:00", + "consumption": { + "value": 141.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T13:30:00+00:00", + "to": "2023-10-25T14:00:00+00:00", + "consumption": { + "value": 97.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T14:00:00+00:00", + "to": "2023-10-25T14:30:00+00:00", + "consumption": { + "value": 175.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T14:30:00+00:00", + "to": "2023-10-25T15:00:00+00:00", + "consumption": { + "value": 203.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T15:00:00+00:00", + "to": "2023-10-25T15:30:00+00:00", + "consumption": { + "value": 284.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T15:30:00+00:00", + "to": "2023-10-25T16:00:00+00:00", + "consumption": { + "value": 219.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T16:00:00+00:00", + "to": "2023-10-25T16:30:00+00:00", + "consumption": { + "value": 218.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T16:30:00+00:00", + "to": "2023-10-25T17:00:00+00:00", + "consumption": { + "value": 220.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T17:00:00+00:00", + "to": "2023-10-25T17:30:00+00:00", + "consumption": { + "value": 215.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T17:30:00+00:00", + "to": "2023-10-25T18:00:00+00:00", + "consumption": { + "value": 117.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T18:00:00+00:00", + "to": "2023-10-25T18:30:00+00:00", + "consumption": { + "value": 281.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T18:30:00+00:00", + "to": "2023-10-25T19:00:00+00:00", + "consumption": { + "value": 90.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T19:00:00+00:00", + "to": "2023-10-25T19:30:00+00:00", + "consumption": { + "value": 149.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T19:30:00+00:00", + "to": "2023-10-25T20:00:00+00:00", + "consumption": { + "value": 122.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T20:00:00+00:00", + "to": "2023-10-25T20:30:00+00:00", + "consumption": { + "value": 291.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T20:30:00+00:00", + "to": "2023-10-25T21:00:00+00:00", + "consumption": { + "value": 171.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T21:00:00+00:00", + "to": "2023-10-25T21:30:00+00:00", + "consumption": { + "value": 117.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T21:30:00+00:00", + "to": "2023-10-25T22:00:00+00:00", + "consumption": { + "value": 284.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T22:00:00+00:00", + "to": "2023-10-25T22:30:00+00:00", + "consumption": { + "value": 78.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T22:30:00+00:00", + "to": "2023-10-25T23:00:00+00:00", + "consumption": { + "value": 299.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T23:00:00+00:00", + "to": "2023-10-25T23:30:00+00:00", + "consumption": { + "value": 105.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-25T23:30:00+00:00", + "to": "2023-10-26T00:00:00+00:00", + "consumption": { + "value": 55.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T00:00:00+00:00", + "to": "2023-10-26T00:30:00+00:00", + "consumption": { + "value": 170.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T00:30:00+00:00", + "to": "2023-10-26T01:00:00+00:00", + "consumption": { + "value": 156.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T01:00:00+00:00", + "to": "2023-10-26T01:30:00+00:00", + "consumption": { + "value": 183.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T01:30:00+00:00", + "to": "2023-10-26T02:00:00+00:00", + "consumption": { + "value": 123.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T02:00:00+00:00", + "to": "2023-10-26T02:30:00+00:00", + "consumption": { + "value": 53.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T02:30:00+00:00", + "to": "2023-10-26T03:00:00+00:00", + "consumption": { + "value": 111.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T03:00:00+00:00", + "to": "2023-10-26T03:30:00+00:00", + "consumption": { + "value": 298.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T03:30:00+00:00", + "to": "2023-10-26T04:00:00+00:00", + "consumption": { + "value": 200.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T04:00:00+00:00", + "to": "2023-10-26T04:30:00+00:00", + "consumption": { + "value": 146.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T04:30:00+00:00", + "to": "2023-10-26T05:00:00+00:00", + "consumption": { + "value": 98.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T05:00:00+00:00", + "to": "2023-10-26T05:30:00+00:00", + "consumption": { + "value": 216.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T05:30:00+00:00", + "to": "2023-10-26T06:00:00+00:00", + "consumption": { + "value": 50.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T06:00:00+00:00", + "to": "2023-10-26T06:30:00+00:00", + "consumption": { + "value": 230.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T06:30:00+00:00", + "to": "2023-10-26T07:00:00+00:00", + "consumption": { + "value": 235.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T07:00:00+00:00", + "to": "2023-10-26T07:30:00+00:00", + "consumption": { + "value": 73.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T07:30:00+00:00", + "to": "2023-10-26T08:00:00+00:00", + "consumption": { + "value": 65.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T08:00:00+00:00", + "to": "2023-10-26T08:30:00+00:00", + "consumption": { + "value": 271.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T08:30:00+00:00", + "to": "2023-10-26T09:00:00+00:00", + "consumption": { + "value": 142.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T09:00:00+00:00", + "to": "2023-10-26T09:30:00+00:00", + "consumption": { + "value": 88.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T09:30:00+00:00", + "to": "2023-10-26T10:00:00+00:00", + "consumption": { + "value": 286.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T10:00:00+00:00", + "to": "2023-10-26T10:30:00+00:00", + "consumption": { + "value": 264.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T10:30:00+00:00", + "to": "2023-10-26T11:00:00+00:00", + "consumption": { + "value": 244.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T11:00:00+00:00", + "to": "2023-10-26T11:30:00+00:00", + "consumption": { + "value": 61.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T11:30:00+00:00", + "to": "2023-10-26T12:00:00+00:00", + "consumption": { + "value": 170.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T12:00:00+00:00", + "to": "2023-10-26T12:30:00+00:00", + "consumption": { + "value": 116.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T12:30:00+00:00", + "to": "2023-10-26T13:00:00+00:00", + "consumption": { + "value": 231.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T13:00:00+00:00", + "to": "2023-10-26T13:30:00+00:00", + "consumption": { + "value": 182.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T13:30:00+00:00", + "to": "2023-10-26T14:00:00+00:00", + "consumption": { + "value": 57.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T14:00:00+00:00", + "to": "2023-10-26T14:30:00+00:00", + "consumption": { + "value": 160.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T14:30:00+00:00", + "to": "2023-10-26T15:00:00+00:00", + "consumption": { + "value": 147.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T15:00:00+00:00", + "to": "2023-10-26T15:30:00+00:00", + "consumption": { + "value": 227.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T15:30:00+00:00", + "to": "2023-10-26T16:00:00+00:00", + "consumption": { + "value": 72.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T16:00:00+00:00", + "to": "2023-10-26T16:30:00+00:00", + "consumption": { + "value": 74.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T16:30:00+00:00", + "to": "2023-10-26T17:00:00+00:00", + "consumption": { + "value": 107.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T17:00:00+00:00", + "to": "2023-10-26T17:30:00+00:00", + "consumption": { + "value": 230.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T17:30:00+00:00", + "to": "2023-10-26T18:00:00+00:00", + "consumption": { + "value": 129.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T18:00:00+00:00", + "to": "2023-10-26T18:30:00+00:00", + "consumption": { + "value": 98.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T18:30:00+00:00", + "to": "2023-10-26T19:00:00+00:00", + "consumption": { + "value": 109.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T19:00:00+00:00", + "to": "2023-10-26T19:30:00+00:00", + "consumption": { + "value": 112.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T19:30:00+00:00", + "to": "2023-10-26T20:00:00+00:00", + "consumption": { + "value": 110.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T20:00:00+00:00", + "to": "2023-10-26T20:30:00+00:00", + "consumption": { + "value": 291.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T20:30:00+00:00", + "to": "2023-10-26T21:00:00+00:00", + "consumption": { + "value": 256.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T21:00:00+00:00", + "to": "2023-10-26T21:30:00+00:00", + "consumption": { + "value": 105.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T21:30:00+00:00", + "to": "2023-10-26T22:00:00+00:00", + "consumption": { + "value": 261.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T22:00:00+00:00", + "to": "2023-10-26T22:30:00+00:00", + "consumption": { + "value": 170.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T22:30:00+00:00", + "to": "2023-10-26T23:00:00+00:00", + "consumption": { + "value": 295.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T23:00:00+00:00", + "to": "2023-10-26T23:30:00+00:00", + "consumption": { + "value": 213.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-26T23:30:00+00:00", + "to": "2023-10-27T00:00:00+00:00", + "consumption": { + "value": 108.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T00:00:00+00:00", + "to": "2023-10-27T00:30:00+00:00", + "consumption": { + "value": 124.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T00:30:00+00:00", + "to": "2023-10-27T01:00:00+00:00", + "consumption": { + "value": 251.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T01:00:00+00:00", + "to": "2023-10-27T01:30:00+00:00", + "consumption": { + "value": 142.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T01:30:00+00:00", + "to": "2023-10-27T02:00:00+00:00", + "consumption": { + "value": 72.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T02:00:00+00:00", + "to": "2023-10-27T02:30:00+00:00", + "consumption": { + "value": 183.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T02:30:00+00:00", + "to": "2023-10-27T03:00:00+00:00", + "consumption": { + "value": 280.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T03:00:00+00:00", + "to": "2023-10-27T03:30:00+00:00", + "consumption": { + "value": 91.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T03:30:00+00:00", + "to": "2023-10-27T04:00:00+00:00", + "consumption": { + "value": 270.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T04:00:00+00:00", + "to": "2023-10-27T04:30:00+00:00", + "consumption": { + "value": 251.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T04:30:00+00:00", + "to": "2023-10-27T05:00:00+00:00", + "consumption": { + "value": 157.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T05:00:00+00:00", + "to": "2023-10-27T05:30:00+00:00", + "consumption": { + "value": 273.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T05:30:00+00:00", + "to": "2023-10-27T06:00:00+00:00", + "consumption": { + "value": 166.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T06:00:00+00:00", + "to": "2023-10-27T06:30:00+00:00", + "consumption": { + "value": 178.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T06:30:00+00:00", + "to": "2023-10-27T07:00:00+00:00", + "consumption": { + "value": 104.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T07:00:00+00:00", + "to": "2023-10-27T07:30:00+00:00", + "consumption": { + "value": 96.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T07:30:00+00:00", + "to": "2023-10-27T08:00:00+00:00", + "consumption": { + "value": 106.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T08:00:00+00:00", + "to": "2023-10-27T08:30:00+00:00", + "consumption": { + "value": 238.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T08:30:00+00:00", + "to": "2023-10-27T09:00:00+00:00", + "consumption": { + "value": 188.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T09:00:00+00:00", + "to": "2023-10-27T09:30:00+00:00", + "consumption": { + "value": 286.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T09:30:00+00:00", + "to": "2023-10-27T10:00:00+00:00", + "consumption": { + "value": 68.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T10:00:00+00:00", + "to": "2023-10-27T10:30:00+00:00", + "consumption": { + "value": 50.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T10:30:00+00:00", + "to": "2023-10-27T11:00:00+00:00", + "consumption": { + "value": 276.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T11:00:00+00:00", + "to": "2023-10-27T11:30:00+00:00", + "consumption": { + "value": 142.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T11:30:00+00:00", + "to": "2023-10-27T12:00:00+00:00", + "consumption": { + "value": 263.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T12:00:00+00:00", + "to": "2023-10-27T12:30:00+00:00", + "consumption": { + "value": 105.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T12:30:00+00:00", + "to": "2023-10-27T13:00:00+00:00", + "consumption": { + "value": 150.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T13:00:00+00:00", + "to": "2023-10-27T13:30:00+00:00", + "consumption": { + "value": 101.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T13:30:00+00:00", + "to": "2023-10-27T14:00:00+00:00", + "consumption": { + "value": 109.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T14:00:00+00:00", + "to": "2023-10-27T14:30:00+00:00", + "consumption": { + "value": 198.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T14:30:00+00:00", + "to": "2023-10-27T15:00:00+00:00", + "consumption": { + "value": 169.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T15:00:00+00:00", + "to": "2023-10-27T15:30:00+00:00", + "consumption": { + "value": 226.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T15:30:00+00:00", + "to": "2023-10-27T16:00:00+00:00", + "consumption": { + "value": 265.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T16:00:00+00:00", + "to": "2023-10-27T16:30:00+00:00", + "consumption": { + "value": 188.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T16:30:00+00:00", + "to": "2023-10-27T17:00:00+00:00", + "consumption": { + "value": 148.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T17:00:00+00:00", + "to": "2023-10-27T17:30:00+00:00", + "consumption": { + "value": 257.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T17:30:00+00:00", + "to": "2023-10-27T18:00:00+00:00", + "consumption": { + "value": 222.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T18:00:00+00:00", + "to": "2023-10-27T18:30:00+00:00", + "consumption": { + "value": 129.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T18:30:00+00:00", + "to": "2023-10-27T19:00:00+00:00", + "consumption": { + "value": 66.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T19:00:00+00:00", + "to": "2023-10-27T19:30:00+00:00", + "consumption": { + "value": 199.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T19:30:00+00:00", + "to": "2023-10-27T20:00:00+00:00", + "consumption": { + "value": 60.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T20:00:00+00:00", + "to": "2023-10-27T20:30:00+00:00", + "consumption": { + "value": 117.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T20:30:00+00:00", + "to": "2023-10-27T21:00:00+00:00", + "consumption": { + "value": 247.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T21:00:00+00:00", + "to": "2023-10-27T21:30:00+00:00", + "consumption": { + "value": 216.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T21:30:00+00:00", + "to": "2023-10-27T22:00:00+00:00", + "consumption": { + "value": 242.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T22:00:00+00:00", + "to": "2023-10-27T22:30:00+00:00", + "consumption": { + "value": 155.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T22:30:00+00:00", + "to": "2023-10-27T23:00:00+00:00", + "consumption": { + "value": 291.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T23:00:00+00:00", + "to": "2023-10-27T23:30:00+00:00", + "consumption": { + "value": 176.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-27T23:30:00+00:00", + "to": "2023-10-28T00:00:00+00:00", + "consumption": { + "value": 54.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T00:00:00+00:00", + "to": "2023-10-28T00:30:00+00:00", + "consumption": { + "value": 66.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T00:30:00+00:00", + "to": "2023-10-28T01:00:00+00:00", + "consumption": { + "value": 273.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T01:00:00+00:00", + "to": "2023-10-28T01:30:00+00:00", + "consumption": { + "value": 210.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T01:30:00+00:00", + "to": "2023-10-28T02:00:00+00:00", + "consumption": { + "value": 79.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T02:00:00+00:00", + "to": "2023-10-28T02:30:00+00:00", + "consumption": { + "value": 168.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T02:30:00+00:00", + "to": "2023-10-28T03:00:00+00:00", + "consumption": { + "value": 83.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T03:00:00+00:00", + "to": "2023-10-28T03:30:00+00:00", + "consumption": { + "value": 92.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T03:30:00+00:00", + "to": "2023-10-28T04:00:00+00:00", + "consumption": { + "value": 165.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T04:00:00+00:00", + "to": "2023-10-28T04:30:00+00:00", + "consumption": { + "value": 168.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T04:30:00+00:00", + "to": "2023-10-28T05:00:00+00:00", + "consumption": { + "value": 220.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T05:00:00+00:00", + "to": "2023-10-28T05:30:00+00:00", + "consumption": { + "value": 235.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T05:30:00+00:00", + "to": "2023-10-28T06:00:00+00:00", + "consumption": { + "value": 199.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T06:00:00+00:00", + "to": "2023-10-28T06:30:00+00:00", + "consumption": { + "value": 251.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T06:30:00+00:00", + "to": "2023-10-28T07:00:00+00:00", + "consumption": { + "value": 76.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T07:00:00+00:00", + "to": "2023-10-28T07:30:00+00:00", + "consumption": { + "value": 179.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T07:30:00+00:00", + "to": "2023-10-28T08:00:00+00:00", + "consumption": { + "value": 104.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T08:00:00+00:00", + "to": "2023-10-28T08:30:00+00:00", + "consumption": { + "value": 246.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T08:30:00+00:00", + "to": "2023-10-28T09:00:00+00:00", + "consumption": { + "value": 91.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T09:00:00+00:00", + "to": "2023-10-28T09:30:00+00:00", + "consumption": { + "value": 171.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T09:30:00+00:00", + "to": "2023-10-28T10:00:00+00:00", + "consumption": { + "value": 152.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T10:00:00+00:00", + "to": "2023-10-28T10:30:00+00:00", + "consumption": { + "value": 208.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T10:30:00+00:00", + "to": "2023-10-28T11:00:00+00:00", + "consumption": { + "value": 56.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T11:00:00+00:00", + "to": "2023-10-28T11:30:00+00:00", + "consumption": { + "value": 135.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T11:30:00+00:00", + "to": "2023-10-28T12:00:00+00:00", + "consumption": { + "value": 227.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T12:00:00+00:00", + "to": "2023-10-28T12:30:00+00:00", + "consumption": { + "value": 203.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T12:30:00+00:00", + "to": "2023-10-28T13:00:00+00:00", + "consumption": { + "value": 128.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T13:00:00+00:00", + "to": "2023-10-28T13:30:00+00:00", + "consumption": { + "value": 202.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T13:30:00+00:00", + "to": "2023-10-28T14:00:00+00:00", + "consumption": { + "value": 239.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T14:00:00+00:00", + "to": "2023-10-28T14:30:00+00:00", + "consumption": { + "value": 272.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T14:30:00+00:00", + "to": "2023-10-28T15:00:00+00:00", + "consumption": { + "value": 224.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T15:00:00+00:00", + "to": "2023-10-28T15:30:00+00:00", + "consumption": { + "value": 173.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T15:30:00+00:00", + "to": "2023-10-28T16:00:00+00:00", + "consumption": { + "value": 88.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T16:00:00+00:00", + "to": "2023-10-28T16:30:00+00:00", + "consumption": { + "value": 154.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T16:30:00+00:00", + "to": "2023-10-28T17:00:00+00:00", + "consumption": { + "value": 114.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T17:00:00+00:00", + "to": "2023-10-28T17:30:00+00:00", + "consumption": { + "value": 52.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T17:30:00+00:00", + "to": "2023-10-28T18:00:00+00:00", + "consumption": { + "value": 156.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T18:00:00+00:00", + "to": "2023-10-28T18:30:00+00:00", + "consumption": { + "value": 86.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T18:30:00+00:00", + "to": "2023-10-28T19:00:00+00:00", + "consumption": { + "value": 250.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T19:00:00+00:00", + "to": "2023-10-28T19:30:00+00:00", + "consumption": { + "value": 75.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T19:30:00+00:00", + "to": "2023-10-28T20:00:00+00:00", + "consumption": { + "value": 232.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T20:00:00+00:00", + "to": "2023-10-28T20:30:00+00:00", + "consumption": { + "value": 245.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T20:30:00+00:00", + "to": "2023-10-28T21:00:00+00:00", + "consumption": { + "value": 173.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T21:00:00+00:00", + "to": "2023-10-28T21:30:00+00:00", + "consumption": { + "value": 233.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T21:30:00+00:00", + "to": "2023-10-28T22:00:00+00:00", + "consumption": { + "value": 157.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T22:00:00+00:00", + "to": "2023-10-28T22:30:00+00:00", + "consumption": { + "value": 50.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T22:30:00+00:00", + "to": "2023-10-28T23:00:00+00:00", + "consumption": { + "value": 257.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T23:00:00+00:00", + "to": "2023-10-28T23:30:00+00:00", + "consumption": { + "value": 52.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-28T23:30:00+00:00", + "to": "2023-10-29T00:00:00+00:00", + "consumption": { + "value": 181.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T00:00:00+00:00", + "to": "2023-10-29T00:30:00+00:00", + "consumption": { + "value": 105.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T00:30:00+00:00", + "to": "2023-10-29T01:00:00+00:00", + "consumption": { + "value": 186.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T01:00:00+00:00", + "to": "2023-10-29T01:30:00+00:00", + "consumption": { + "value": 153.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T01:30:00+00:00", + "to": "2023-10-29T02:00:00+00:00", + "consumption": { + "value": 151.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T02:00:00+00:00", + "to": "2023-10-29T02:30:00+00:00", + "consumption": { + "value": 100.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T02:30:00+00:00", + "to": "2023-10-29T03:00:00+00:00", + "consumption": { + "value": 81.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T03:00:00+00:00", + "to": "2023-10-29T03:30:00+00:00", + "consumption": { + "value": 274.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T03:30:00+00:00", + "to": "2023-10-29T04:00:00+00:00", + "consumption": { + "value": 81.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T04:00:00+00:00", + "to": "2023-10-29T04:30:00+00:00", + "consumption": { + "value": 285.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T04:30:00+00:00", + "to": "2023-10-29T05:00:00+00:00", + "consumption": { + "value": 205.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T05:00:00+00:00", + "to": "2023-10-29T05:30:00+00:00", + "consumption": { + "value": 154.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T05:30:00+00:00", + "to": "2023-10-29T06:00:00+00:00", + "consumption": { + "value": 295.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T06:00:00+00:00", + "to": "2023-10-29T06:30:00+00:00", + "consumption": { + "value": 233.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T06:30:00+00:00", + "to": "2023-10-29T07:00:00+00:00", + "consumption": { + "value": 244.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T07:00:00+00:00", + "to": "2023-10-29T07:30:00+00:00", + "consumption": { + "value": 63.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T07:30:00+00:00", + "to": "2023-10-29T08:00:00+00:00", + "consumption": { + "value": 269.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T08:00:00+00:00", + "to": "2023-10-29T08:30:00+00:00", + "consumption": { + "value": 58.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T08:30:00+00:00", + "to": "2023-10-29T09:00:00+00:00", + "consumption": { + "value": 182.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T09:00:00+00:00", + "to": "2023-10-29T09:30:00+00:00", + "consumption": { + "value": 118.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T09:30:00+00:00", + "to": "2023-10-29T10:00:00+00:00", + "consumption": { + "value": 276.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T10:00:00+00:00", + "to": "2023-10-29T10:30:00+00:00", + "consumption": { + "value": 57.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T10:30:00+00:00", + "to": "2023-10-29T11:00:00+00:00", + "consumption": { + "value": 287.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T11:00:00+00:00", + "to": "2023-10-29T11:30:00+00:00", + "consumption": { + "value": 55.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T11:30:00+00:00", + "to": "2023-10-29T12:00:00+00:00", + "consumption": { + "value": 223.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T12:00:00+00:00", + "to": "2023-10-29T12:30:00+00:00", + "consumption": { + "value": 155.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T12:30:00+00:00", + "to": "2023-10-29T13:00:00+00:00", + "consumption": { + "value": 206.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T13:00:00+00:00", + "to": "2023-10-29T13:30:00+00:00", + "consumption": { + "value": 274.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T13:30:00+00:00", + "to": "2023-10-29T14:00:00+00:00", + "consumption": { + "value": 221.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T14:00:00+00:00", + "to": "2023-10-29T14:30:00+00:00", + "consumption": { + "value": 53.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T14:30:00+00:00", + "to": "2023-10-29T15:00:00+00:00", + "consumption": { + "value": 281.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T15:00:00+00:00", + "to": "2023-10-29T15:30:00+00:00", + "consumption": { + "value": 247.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T15:30:00+00:00", + "to": "2023-10-29T16:00:00+00:00", + "consumption": { + "value": 237.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T16:00:00+00:00", + "to": "2023-10-29T16:30:00+00:00", + "consumption": { + "value": 111.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T16:30:00+00:00", + "to": "2023-10-29T17:00:00+00:00", + "consumption": { + "value": 205.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T17:00:00+00:00", + "to": "2023-10-29T17:30:00+00:00", + "consumption": { + "value": 62.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T17:30:00+00:00", + "to": "2023-10-29T18:00:00+00:00", + "consumption": { + "value": 79.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T18:00:00+00:00", + "to": "2023-10-29T18:30:00+00:00", + "consumption": { + "value": 100.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T18:30:00+00:00", + "to": "2023-10-29T19:00:00+00:00", + "consumption": { + "value": 99.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T19:00:00+00:00", + "to": "2023-10-29T19:30:00+00:00", + "consumption": { + "value": 67.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T19:30:00+00:00", + "to": "2023-10-29T20:00:00+00:00", + "consumption": { + "value": 83.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T20:00:00+00:00", + "to": "2023-10-29T20:30:00+00:00", + "consumption": { + "value": 249.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T20:30:00+00:00", + "to": "2023-10-29T21:00:00+00:00", + "consumption": { + "value": 198.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T21:00:00+00:00", + "to": "2023-10-29T21:30:00+00:00", + "consumption": { + "value": 206.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T21:30:00+00:00", + "to": "2023-10-29T22:00:00+00:00", + "consumption": { + "value": 184.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T22:00:00+00:00", + "to": "2023-10-29T22:30:00+00:00", + "consumption": { + "value": 62.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T22:30:00+00:00", + "to": "2023-10-29T23:00:00+00:00", + "consumption": { + "value": 88.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T23:00:00+00:00", + "to": "2023-10-29T23:30:00+00:00", + "consumption": { + "value": 293.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-29T23:30:00+00:00", + "to": "2023-10-30T00:00:00+00:00", + "consumption": { + "value": 148.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T00:00:00+00:00", + "to": "2023-10-30T00:30:00+00:00", + "consumption": { + "value": 68.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T00:30:00+00:00", + "to": "2023-10-30T01:00:00+00:00", + "consumption": { + "value": 280.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T01:00:00+00:00", + "to": "2023-10-30T01:30:00+00:00", + "consumption": { + "value": 50.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T01:30:00+00:00", + "to": "2023-10-30T02:00:00+00:00", + "consumption": { + "value": 182.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T02:00:00+00:00", + "to": "2023-10-30T02:30:00+00:00", + "consumption": { + "value": 117.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T02:30:00+00:00", + "to": "2023-10-30T03:00:00+00:00", + "consumption": { + "value": 222.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T03:00:00+00:00", + "to": "2023-10-30T03:30:00+00:00", + "consumption": { + "value": 94.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T03:30:00+00:00", + "to": "2023-10-30T04:00:00+00:00", + "consumption": { + "value": 187.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T04:00:00+00:00", + "to": "2023-10-30T04:30:00+00:00", + "consumption": { + "value": 258.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T04:30:00+00:00", + "to": "2023-10-30T05:00:00+00:00", + "consumption": { + "value": 249.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T05:00:00+00:00", + "to": "2023-10-30T05:30:00+00:00", + "consumption": { + "value": 251.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T05:30:00+00:00", + "to": "2023-10-30T06:00:00+00:00", + "consumption": { + "value": 201.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T06:00:00+00:00", + "to": "2023-10-30T06:30:00+00:00", + "consumption": { + "value": 262.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T06:30:00+00:00", + "to": "2023-10-30T07:00:00+00:00", + "consumption": { + "value": 127.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T07:00:00+00:00", + "to": "2023-10-30T07:30:00+00:00", + "consumption": { + "value": 210.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T07:30:00+00:00", + "to": "2023-10-30T08:00:00+00:00", + "consumption": { + "value": 95.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T08:00:00+00:00", + "to": "2023-10-30T08:30:00+00:00", + "consumption": { + "value": 50.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T08:30:00+00:00", + "to": "2023-10-30T09:00:00+00:00", + "consumption": { + "value": 138.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T09:00:00+00:00", + "to": "2023-10-30T09:30:00+00:00", + "consumption": { + "value": 86.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T09:30:00+00:00", + "to": "2023-10-30T10:00:00+00:00", + "consumption": { + "value": 145.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T10:00:00+00:00", + "to": "2023-10-30T10:30:00+00:00", + "consumption": { + "value": 191.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T10:30:00+00:00", + "to": "2023-10-30T11:00:00+00:00", + "consumption": { + "value": 61.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T11:00:00+00:00", + "to": "2023-10-30T11:30:00+00:00", + "consumption": { + "value": 241.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T11:30:00+00:00", + "to": "2023-10-30T12:00:00+00:00", + "consumption": { + "value": 102.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T12:00:00+00:00", + "to": "2023-10-30T12:30:00+00:00", + "consumption": { + "value": 163.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T12:30:00+00:00", + "to": "2023-10-30T13:00:00+00:00", + "consumption": { + "value": 197.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T13:00:00+00:00", + "to": "2023-10-30T13:30:00+00:00", + "consumption": { + "value": 144.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T13:30:00+00:00", + "to": "2023-10-30T14:00:00+00:00", + "consumption": { + "value": 260.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T14:00:00+00:00", + "to": "2023-10-30T14:30:00+00:00", + "consumption": { + "value": 203.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T14:30:00+00:00", + "to": "2023-10-30T15:00:00+00:00", + "consumption": { + "value": 128.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T15:00:00+00:00", + "to": "2023-10-30T15:30:00+00:00", + "consumption": { + "value": 195.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T15:30:00+00:00", + "to": "2023-10-30T16:00:00+00:00", + "consumption": { + "value": 160.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T16:00:00+00:00", + "to": "2023-10-30T16:30:00+00:00", + "consumption": { + "value": 206.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T16:30:00+00:00", + "to": "2023-10-30T17:00:00+00:00", + "consumption": { + "value": 210.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T17:00:00+00:00", + "to": "2023-10-30T17:30:00+00:00", + "consumption": { + "value": 58.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T17:30:00+00:00", + "to": "2023-10-30T18:00:00+00:00", + "consumption": { + "value": 211.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T18:00:00+00:00", + "to": "2023-10-30T18:30:00+00:00", + "consumption": { + "value": 74.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T18:30:00+00:00", + "to": "2023-10-30T19:00:00+00:00", + "consumption": { + "value": 81.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T19:00:00+00:00", + "to": "2023-10-30T19:30:00+00:00", + "consumption": { + "value": 110.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T19:30:00+00:00", + "to": "2023-10-30T20:00:00+00:00", + "consumption": { + "value": 110.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T20:00:00+00:00", + "to": "2023-10-30T20:30:00+00:00", + "consumption": { + "value": 145.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T20:30:00+00:00", + "to": "2023-10-30T21:00:00+00:00", + "consumption": { + "value": 126.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T21:00:00+00:00", + "to": "2023-10-30T21:30:00+00:00", + "consumption": { + "value": 82.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T21:30:00+00:00", + "to": "2023-10-30T22:00:00+00:00", + "consumption": { + "value": 54.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T22:00:00+00:00", + "to": "2023-10-30T22:30:00+00:00", + "consumption": { + "value": 251.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T22:30:00+00:00", + "to": "2023-10-30T23:00:00+00:00", + "consumption": { + "value": 229.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T23:00:00+00:00", + "to": "2023-10-30T23:30:00+00:00", + "consumption": { + "value": 164.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-30T23:30:00+00:00", + "to": "2023-10-31T00:00:00+00:00", + "consumption": { + "value": 280.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T00:00:00+00:00", + "to": "2023-10-31T00:30:00+00:00", + "consumption": { + "value": 97.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T00:30:00+00:00", + "to": "2023-10-31T01:00:00+00:00", + "consumption": { + "value": 110.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T01:00:00+00:00", + "to": "2023-10-31T01:30:00+00:00", + "consumption": { + "value": 286.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T01:30:00+00:00", + "to": "2023-10-31T02:00:00+00:00", + "consumption": { + "value": 129.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T02:00:00+00:00", + "to": "2023-10-31T02:30:00+00:00", + "consumption": { + "value": 243.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T02:30:00+00:00", + "to": "2023-10-31T03:00:00+00:00", + "consumption": { + "value": 204.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T03:00:00+00:00", + "to": "2023-10-31T03:30:00+00:00", + "consumption": { + "value": 136.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T03:30:00+00:00", + "to": "2023-10-31T04:00:00+00:00", + "consumption": { + "value": 204.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T04:00:00+00:00", + "to": "2023-10-31T04:30:00+00:00", + "consumption": { + "value": 67.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T04:30:00+00:00", + "to": "2023-10-31T05:00:00+00:00", + "consumption": { + "value": 207.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T05:00:00+00:00", + "to": "2023-10-31T05:30:00+00:00", + "consumption": { + "value": 186.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T05:30:00+00:00", + "to": "2023-10-31T06:00:00+00:00", + "consumption": { + "value": 157.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T06:00:00+00:00", + "to": "2023-10-31T06:30:00+00:00", + "consumption": { + "value": 95.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T06:30:00+00:00", + "to": "2023-10-31T07:00:00+00:00", + "consumption": { + "value": 81.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T07:00:00+00:00", + "to": "2023-10-31T07:30:00+00:00", + "consumption": { + "value": 241.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T07:30:00+00:00", + "to": "2023-10-31T08:00:00+00:00", + "consumption": { + "value": 235.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T08:00:00+00:00", + "to": "2023-10-31T08:30:00+00:00", + "consumption": { + "value": 150.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T08:30:00+00:00", + "to": "2023-10-31T09:00:00+00:00", + "consumption": { + "value": 253.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T09:00:00+00:00", + "to": "2023-10-31T09:30:00+00:00", + "consumption": { + "value": 225.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T09:30:00+00:00", + "to": "2023-10-31T10:00:00+00:00", + "consumption": { + "value": 224.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T10:00:00+00:00", + "to": "2023-10-31T10:30:00+00:00", + "consumption": { + "value": 224.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T10:30:00+00:00", + "to": "2023-10-31T11:00:00+00:00", + "consumption": { + "value": 208.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T11:00:00+00:00", + "to": "2023-10-31T11:30:00+00:00", + "consumption": { + "value": 268.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T11:30:00+00:00", + "to": "2023-10-31T12:00:00+00:00", + "consumption": { + "value": 282.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T12:00:00+00:00", + "to": "2023-10-31T12:30:00+00:00", + "consumption": { + "value": 86.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T12:30:00+00:00", + "to": "2023-10-31T13:00:00+00:00", + "consumption": { + "value": 142.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T13:00:00+00:00", + "to": "2023-10-31T13:30:00+00:00", + "consumption": { + "value": 88.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T13:30:00+00:00", + "to": "2023-10-31T14:00:00+00:00", + "consumption": { + "value": 185.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T14:00:00+00:00", + "to": "2023-10-31T14:30:00+00:00", + "consumption": { + "value": 210.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T14:30:00+00:00", + "to": "2023-10-31T15:00:00+00:00", + "consumption": { + "value": 205.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T15:00:00+00:00", + "to": "2023-10-31T15:30:00+00:00", + "consumption": { + "value": 130.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T15:30:00+00:00", + "to": "2023-10-31T16:00:00+00:00", + "consumption": { + "value": 71.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T16:00:00+00:00", + "to": "2023-10-31T16:30:00+00:00", + "consumption": { + "value": 66.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T16:30:00+00:00", + "to": "2023-10-31T17:00:00+00:00", + "consumption": { + "value": 157.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T17:00:00+00:00", + "to": "2023-10-31T17:30:00+00:00", + "consumption": { + "value": 212.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T17:30:00+00:00", + "to": "2023-10-31T18:00:00+00:00", + "consumption": { + "value": 152.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T18:00:00+00:00", + "to": "2023-10-31T18:30:00+00:00", + "consumption": { + "value": 130.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T18:30:00+00:00", + "to": "2023-10-31T19:00:00+00:00", + "consumption": { + "value": 93.7, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T19:00:00+00:00", + "to": "2023-10-31T19:30:00+00:00", + "consumption": { + "value": 162.6, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T19:30:00+00:00", + "to": "2023-10-31T20:00:00+00:00", + "consumption": { + "value": 261.8, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T20:00:00+00:00", + "to": "2023-10-31T20:30:00+00:00", + "consumption": { + "value": 190.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T20:30:00+00:00", + "to": "2023-10-31T21:00:00+00:00", + "consumption": { + "value": 261.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T21:00:00+00:00", + "to": "2023-10-31T21:30:00+00:00", + "consumption": { + "value": 206.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T21:30:00+00:00", + "to": "2023-10-31T22:00:00+00:00", + "consumption": { + "value": 123.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T22:00:00+00:00", + "to": "2023-10-31T22:30:00+00:00", + "consumption": { + "value": 221.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T22:30:00+00:00", + "to": "2023-10-31T23:00:00+00:00", + "consumption": { + "value": 174.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T23:00:00+00:00", + "to": "2023-10-31T23:30:00+00:00", + "consumption": { + "value": 80.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-10-31T23:30:00+00:00", + "to": "2023-11-01T00:00:00+00:00", + "consumption": { + "value": 66.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T00:00:00+00:00", + "to": "2023-11-01T00:30:00+00:00", + "consumption": { + "value": 136.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T00:30:00+00:00", + "to": "2023-11-01T01:00:00+00:00", + "consumption": { + "value": 67.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T01:00:00+00:00", + "to": "2023-11-01T01:30:00+00:00", + "consumption": { + "value": 273.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T01:30:00+00:00", + "to": "2023-11-01T02:00:00+00:00", + "consumption": { + "value": 184.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T02:00:00+00:00", + "to": "2023-11-01T02:30:00+00:00", + "consumption": { + "value": 97.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T02:30:00+00:00", + "to": "2023-11-01T03:00:00+00:00", + "consumption": { + "value": 56.3, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T03:00:00+00:00", + "to": "2023-11-01T03:30:00+00:00", + "consumption": { + "value": 207.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T03:30:00+00:00", + "to": "2023-11-01T04:00:00+00:00", + "consumption": { + "value": 177.0, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T04:00:00+00:00", + "to": "2023-11-01T04:30:00+00:00", + "consumption": { + "value": 74.2, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T04:30:00+00:00", + "to": "2023-11-01T05:00:00+00:00", + "consumption": { + "value": 191.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T05:00:00+00:00", + "to": "2023-11-01T05:30:00+00:00", + "consumption": { + "value": 216.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T05:30:00+00:00", + "to": "2023-11-01T06:00:00+00:00", + "consumption": { + "value": 192.4, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T06:00:00+00:00", + "to": "2023-11-01T06:30:00+00:00", + "consumption": { + "value": 100.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T06:30:00+00:00", + "to": "2023-11-01T07:00:00+00:00", + "consumption": { + "value": 257.9, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T07:00:00+00:00", + "to": "2023-11-01T07:30:00+00:00", + "consumption": { + "value": 237.1, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T07:30:00+00:00", + "to": "2023-11-01T08:00:00+00:00", + "consumption": { + "value": 118.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T08:00:00+00:00", + "to": "2023-11-01T08:30:00+00:00", + "consumption": { + "value": 140.5, + "unitCode": "WHR" + } + }, + { + "type": "Electricity", + "from": "2023-11-01T08:30:00+00:00", + "to": "2023-11-01T09:00:00+00:00", + "consumption": { + "value": 196.3, + "unitCode": "WHR" + } + } +] \ No newline at end of file diff --git a/resource/data/data_maker.py b/resource/data/data_maker.py new file mode 100644 index 0000000..cbdb649 --- /dev/null +++ b/resource/data/data_maker.py @@ -0,0 +1,57 @@ +import datetime +import random +import json + + +class simple_utc(datetime.tzinfo): + def tzname(self, **kwargs): + return "UTC" + + def utcoffset(self, dt): + return datetime.timedelta(0) + + +def create_data(): + """ " + Returns a list of consumption objects with random values for consumption + value between 50 and 300, and datestamps every 30 minutes between now and 7 days ago + + Use the following format: + + { + "type": "Electricity", + "from": "2023-10-18T00:00:00Z", + "to": "2023-10-18T00:30:00Z", + "consumption": { + "value": 123.45, + "unitCode": "WHR" + } + } + """ + now = datetime.datetime.utcnow().replace( + tzinfo=simple_utc(), minute=0, second=0, microsecond=0 + ) + earlier = now - datetime.timedelta(days=7) + delta = datetime.timedelta(minutes=30) + data = [] + while earlier < now: + data.append( + { + "type": "Electricity", + "from": earlier.isoformat(), + "to": (earlier + delta).isoformat(), + "consumption": { + "value": random.randint(500, 3000) / 10, + "unitCode": "WHR", + }, + } + ) + earlier += delta + return data + + +if __name__ == "__main__": + data = create_data() + json_object = {"data": data} + with open("data/7_day_consumption.json", "w") as outfile: + json.dump(json_object, outfile, indent=2) diff --git a/resource/tests/__init__.py b/resource/tests/__init__.py new file mode 100644 index 0000000..643b52c --- /dev/null +++ b/resource/tests/__init__.py @@ -0,0 +1,35 @@ +import json +import os +from typing import Optional +import time +from urllib.parse import quote + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend + +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) + +def cert_response(cert_file="cert.pem", urlencoded=False): + with open(f"{ROOT_DIR}/fixtures/{cert_file}", "rb") as cert_content: + cert_data = cert_content.read() + if urlencoded: + cert = quote(cert_data.decode()) + else: + cert = x509.load_pem_x509_certificate(cert_data, default_backend()) + return cert + +def introspection_response( + exp: Optional[int] = None, iat: Optional[int] = None, active=True +): + with open(f"{ROOT_DIR}/fixtures/jwt.json", "r") as jwt_file: + introspection_response = json.load(jwt_file) + if exp: + introspection_response["exp"] = exp + else: + introspection_response["exp"] = time.time() + 20 + if iat: + introspection_response["iat"] = iat + else: + introspection_response["iat"] = time.time() - 20 + introspection_response["active"] = active + return introspection_response diff --git a/resource/tests/fixtures/cert.pem b/resource/tests/fixtures/cert.pem new file mode 100644 index 0000000..619525f --- /dev/null +++ b/resource/tests/fixtures/cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFfTCCA2WgAwIBAgIUb68EZVILgce3VHQgVGYxwmKAENYwDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCR0IxHDAaBgNVBAoME0lCMSBUcnVzdCBGcmFtZXdvcmsx +DTALBgNVBAsMBHRlc3QxEjAQBgNVBAMMCWNsaWVudC1pZDAeFw0yMzEyMDQxMjA3 +MzVaFw0yNDEyMDMxMjA3MzVaME4xCzAJBgNVBAYTAkdCMRwwGgYDVQQKDBNJQjEg +VHJ1c3QgRnJhbWV3b3JrMQ0wCwYDVQQLDAR0ZXN0MRIwEAYDVQQDDAljbGllbnQt +aWQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVP5zvI/EJA3JgEg3G +iVyHTPOdnpB+A9sFGrTZL0iuRQHOCYrMLrhyRmCNWDmiPlyyfN8UGpjqYGmcj25T +pnx47OLfVX8pa4ILZMmZL08y6fWgVOjWQNiq9jLbqrSMD0O3eauG3froDw1168qo +Gh4kdsN+MF6eg0mYaU6vGekIsUIKX8Mhvu+E7m2CXJrJugCh1BkpOeSebNP8ztE1 +rymBK1uo1W41cY/LSrKQap343Kamfcc8ZbCsiy5yLiDqdVaWpR05zTkI2/znj+9l +1Lu/9YtJ6l08kD1FPrnG5dAJYZRqGKjXd1kFbsi1BTPap9BZUSszTjfLI2wtMz3N +7AFetbVrFb/KLIMX3hZ+8XnxlXmIT9fVLGEIH1s6DZgmXilHYrUM0FIrIjqanYCc +rKcoDCdVu63ohZIVpPK9LKALUAaTXTnLonSQMICbZ7s4wRF5ALRaArrq6PEmYYSQ +mCCOgcwM3qeXOWjYqVt5EmNAW69CCmLIlMFbKFhWDnmuhx6wJ9ZkmRV4ucbq0o4A +53nNhHSRDrzSAFly+Zl3n83EwzS4vhnKyb9rYPAw3fxwRupma996uQb+mJltALH+ +v+P/wCqhyPLtFQ6VhTXD27CQ5B6HNcGjVNNgU0GDv536Q8Ku8P6MONs4nave7CK+ +WiXcHBjz+KLUpsqOcxPVRY2iHwIDAQABo1MwUTAdBgNVHQ4EFgQUwmDvGdyOPK8u +MJVvGxtS2nuLkB8wHwYDVR0jBBgwFoAUwmDvGdyOPK8uMJVvGxtS2nuLkB8wDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAv9fPLO06rXDUsovw6ycE +DI7iNdDLMzyQYiyhQuPHbzq6pO1MyePen4wwrpDHExDBg3aFvFtv648EUOuUgtum +qxtpiDDNvZUBiUqSg7/HCGPTHBJW71Sp34O7elvNptx7rtANyeS4wblm4xrcXutj +iZgxoGtNhShZMfBPu/Fp6oQHyCNzhIFYVpWCwD6zIRV4zvn3CO+WgTEnrrjHRLKl +a3HxYVcYKVCox8DsP1X57D+mBb3aSsxrqd8jqs0kjpKedTQBl6U2DU+17pYDotAW +aGbI3/EJhRy2+3IE0ouMaFLUqjwCKJDI7H81Vif5S8yNBIzPo+eN+YKRioQxSDQT +0XiogZeojLitL7Kx8y6HxUWenGddMA49+CV5325nloV+rnL49yhXEuwJ4LxS5JGE +35Ms3yocWvdLYlfPsfbRlUpApbzZmwClu8X+/RDq6u0l2Wj66L8vQDVrKILW1cvV +T07H7Cb+ba/5ngMtUgccBbSNGAggXZEXDXDxYugmNBaFbOnOGpjVYrCL7+Ku+k7C +MciqnJG2PQa82gU4bNVLlTOqQkwd54LDiah/L9D80RYhKtMSeJn0+qJxquQ1kdSt +iFbEm5SW02sFrhu+g3Vabj3q1EAvXOWozjNv5DyuQWskCnH2qnkqjBUrfUcBp6oL +SX2j4YbgcVfxIiRyHsZHUgc= +-----END CERTIFICATE----- diff --git a/resource/tests/fixtures/fail.pem b/resource/tests/fixtures/fail.pem new file mode 100644 index 0000000..116c224 --- /dev/null +++ b/resource/tests/fixtures/fail.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFfTCCA2WgAwIBAgIUAcO0xhaQzGuCqqzhOyuFvlesm6kwDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCR0IxHDAaBgNVBAoME0lCMSBUcnVzdCBGcmFtZXdvcmsx +DTALBgNVBAsMBHRlc3QxEjAQBgNVBAMMCWNsaWVudC1pZDAeFw0yMzEyMDQxMjA4 +MTZaFw0yNDEyMDMxMjA4MTZaME4xCzAJBgNVBAYTAkdCMRwwGgYDVQQKDBNJQjEg +VHJ1c3QgRnJhbWV3b3JrMQ0wCwYDVQQLDAR0ZXN0MRIwEAYDVQQDDAljbGllbnQt +aWQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDoHqeFMICh7ckwNauu +ta7YkCehsAvQ6wV6rNkcNo07cN9MV2lxmOY/O3sZrysZuG1dT9JZ3tElsMD7RmGa +C64wWIbJ/zniXuWRliRHHhzoOpe/had/lYPY0OT3TZz0aXXzYZtOirb0J1xHzPlA +rZ282ciWYAKx5a6JyFrYjlNwV4wE/xRYSM+S015OwKkLw8rDJvFUYbmz7Wyi53Rh +ATluYTzqcoXL/m4IzzY3pV8+oJclE+qZV9h4rl04NUX/RVrx+0kzYXeTMO0Jkfnp +C6kL/3YQT3jpbv8R1ClgR6uMJGpdWQFH+Ut9RoM1AdRhMPPLfQvXpJgfl/ZBKHe2 +cUS9GopCGYQjzjM3swmTkZUIV6f9M2OSLpy6inEPaQpfEA7eQCB1+enKZUgHKA1Z +4D6AWT3JAf9yC4NfdNeQ8BFTrXlu4JsUZtE0Zr/OwODbzn8DFi3HMywj8phd3FmU +f9a6s0vDy7AIQlclxCePXZr3vkLDCEZe5ssqFsr728RnmoaZ0WymQyjRP74e0lw0 +nz7worAYmH8FxyUAwm+wEDcBRsZ4GzicbydswvIQQWIsmJq/1oLnPw20pmWwZ2Hy +5gh0cD8dQsjrTO3aaLRI/m5LWe/mUxkNQQbb/MCoDu7ieVZevfgpErQmCCma2VeD +VOQKF0P4eE7RzbZItWrPvWDhvQIDAQABo1MwUTAdBgNVHQ4EFgQUIua7GI+3K2ej +hjQG66Lp1D6HAycwHwYDVR0jBBgwFoAUIua7GI+3K2ejhjQG66Lp1D6HAycwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAlfC56qtF+KZJKelLKi30 +1e+Q9e/2ZJqFCywgXRslODx2s6uEObuDCDDwyQI0qeBsir6NUMM4V7mDX/iEcfab +2IKdbbtmcpfn04j1jKpimhn49CCtKy0xA8G0oPm8cr31JGX76fHgpdEQaEg6vtXW +aAZDDawJbPLmQkVZj7mi+uxeJ6w9NcxjQeH1hr5e6m9L5rSW0FQoKfpSiipCvX72 +AI8anPcQFrSh0KJNv3+evioEIJq49bKqMPJl4YnJlbqYb7XscCtYL5Z7OXE7chyQ +jdGXLfZj/z6lhaL9/RX8J7tcV+ixytgcSXtyk9o5P8j5b/k/Nm0/zdMRmTnDWioR +teqz/9mRPOE9iAc7+5t9xPMvnoieocNJt47Nam8PPRDiYNQaTEyhm0enAD+CyCHI +qRyqXlSuHxPF0tlAr/S2TUTtHIe+86OFFUCDY8swFcne3fy/FtJ2pW5JDB9Jr7xj +u3raW3rgd2sJqdrzAjyVS8NGfZxlkuBXQzo1Bon0zoZ2OEJo37zAmH+JCq03L53E +T/BgYWRdkYuyL6Hn4N64QPRtScHGo9it0PBsH5aP7Gu4m+sUFqXmaT/31Wv11lhN +4we9mQDZJ4eQTABr7wktE/el3GtL7kVEUFUADpVbVEWOwkgoVKfoQNuIeQmKJpcR +XbpwzQnn7kOZh6DlGpvp6xk= +-----END CERTIFICATE----- diff --git a/resource/tests/fixtures/jwt.json b/resource/tests/fixtures/jwt.json new file mode 100644 index 0000000..6562bf8 --- /dev/null +++ b/resource/tests/fixtures/jwt.json @@ -0,0 +1,31 @@ +{ + "active": true, + "sub": "test@icebreakerone.org", + "organisation_id": "perseusdemoenergy", + "acr": "urn:mace:incommon:iap:silver", + "amr": [ + "kba", + "email_verification", + "phone_verification", + "two_factor" + ], + "auth_time": 1701358177, + "organisation_name": "Perseus Demo Energy", + "organisation_number": "01234567", + "software_name": "Perseus Demo Accounting Client", + "software_roles": [ + "Test Role 1" + ], + "additional_software_metadata": { + "metadata": {} + }, + "client_id": "fa877db2-345a-43d0-bb4d-7ad0138003c9", + "exp": 1701361795, + "iat": 1701358195, + "iss": "https://auth.directory.sandbox.trust.icebreakerone.org", + "scope": "directory:software", + "cnf": { + "x5t#S256": "XCK75zLZH5zm2_bnNfBNxW7UGcvXshgASoJ01T6uVO8" + }, + "token_type": "Bearer" +} \ No newline at end of file diff --git a/resource/tests/fixtures/make_cert.sh b/resource/tests/fixtures/make_cert.sh new file mode 100644 index 0000000..161cc60 --- /dev/null +++ b/resource/tests/fixtures/make_cert.sh @@ -0,0 +1,4 @@ +# Create a key and pem. The pem file is suitable for the test suite. +# Create a cert.pem and a fail.pem. The test jwt will need to be updated with the cert's thumbprint. + +openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes -subj "/C=GB/O=IB1 Trust Framework/OU=test/CN=client-id" \ No newline at end of file diff --git a/resource/tests/test_auth.py b/resource/tests/test_auth.py new file mode 100644 index 0000000..8c802a3 --- /dev/null +++ b/resource/tests/test_auth.py @@ -0,0 +1,34 @@ +import pytest + +import api.auth + +from tests import cert_response, introspection_response + + +@pytest.fixture +def mock_parse_request(mocker): + return mocker.patch("api.auth.parse_cert") + + +@pytest.fixture +def mock_check_token(mocker): + return mocker.patch("api.auth.check_token") + + +def test_introspect(mock_check_token): + # mock_parse_request.return_value = cert_response() + mock_check_token.return_value = introspection_response() + result = api.auth.introspect(cert_response(urlencoded=True), "any-token") + assert "x-fapi-interaction-id" in result[1] + + +def test_introspect_certificate_fails(mock_check_token): + mock_check_token.return_value = introspection_response() + with pytest.raises(api.auth.AccessTokenNoCertificateError): + api.auth.introspect(None, "any-token") + + +def test_introspect_active_fail(mock_check_token): + mock_check_token.return_value = introspection_response(active=False) + with pytest.raises(api.auth.AccessTokenInactiveError): + api.auth.introspect(cert_response(urlencoded=True), "any-token") diff --git a/resource/tests/test_routes.py b/resource/tests/test_routes.py new file mode 100644 index 0000000..47e9e77 --- /dev/null +++ b/resource/tests/test_routes.py @@ -0,0 +1,30 @@ + +import pytest +from fastapi.testclient import TestClient +from tests import cert_response +from api.main import app + +client = TestClient(app) + + + + +@pytest.fixture +def mock_introspect(mocker): + return mocker.patch("api.main.auth.introspect") + +def test_consumption_no_token(): + response = client.get("/api/v1/consumption") + assert response.status_code == 401 + +def test_consumption_bad_token(): + response = client.get("/api/v1/consumption", headers={'Authorization': 'Bearer'}) + assert response.status_code == 401 + +def test_consumption(mock_introspect): + """ + If introspection is successful, return data and 200 + """ + mock_introspect.return_value = ({}, {}) + response = client.get("/api/v1/consumption", headers={'Authorization': 'Bearer abc123', 'x-amzn-mtls-clientcert': cert_response(urlencoded=True)}) + assert response.status_code == 200 \ No newline at end of file