├── Pipfile ├── Pipfile.lock ├── README.md ├── api.py ├── auth.py ├── jwt_generator.py ├── oas.yaml ├── private_key.pem ├── public_key.pem ├── schemas.py └── server.py /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | black = "*" 8 | 9 | [packages] 10 | fastapi = "*" 11 | uvicorn = "*" 12 | pyyaml = "*" 13 | pyjwt = "*" 14 | cryptography = "*" 15 | 16 | [requires] 17 | python_version = "3.9" 18 | 19 | [pipenv] 20 | allow_prereleases = true 21 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "e4761fc7a7565978810fc9c53d867f96158f69a951255a076e456537b404c4cd" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.9" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "anyio": { 20 | "hashes": [ 21 | "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6", 22 | "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e" 23 | ], 24 | "version": "==3.5.0" 25 | }, 26 | "asgiref": { 27 | "hashes": [ 28 | "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9", 29 | "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214" 30 | ], 31 | "version": "==3.4.1" 32 | }, 33 | "cffi": { 34 | "hashes": [ 35 | "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", 36 | "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", 37 | "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", 38 | "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", 39 | "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", 40 | "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", 41 | "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", 42 | "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", 43 | "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", 44 | "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", 45 | "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", 46 | "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", 47 | "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", 48 | "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", 49 | "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", 50 | "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", 51 | "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", 52 | "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", 53 | "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", 54 | "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", 55 | "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", 56 | "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", 57 | "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", 58 | "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", 59 | "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", 60 | "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", 61 | "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", 62 | "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", 63 | "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", 64 | "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", 65 | "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", 66 | "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", 67 | "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", 68 | "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", 69 | "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", 70 | "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", 71 | "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", 72 | "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", 73 | "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", 74 | "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", 75 | "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", 76 | "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", 77 | "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", 78 | "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", 79 | "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", 80 | "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", 81 | "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", 82 | "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", 83 | "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", 84 | "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" 85 | ], 86 | "version": "==1.15.0" 87 | }, 88 | "click": { 89 | "hashes": [ 90 | "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", 91 | "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" 92 | ], 93 | "version": "==8.0.3" 94 | }, 95 | "cryptography": { 96 | "hashes": [ 97 | "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3", 98 | "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31", 99 | "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac", 100 | "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf", 101 | "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316", 102 | "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca", 103 | "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638", 104 | "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94", 105 | "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12", 106 | "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173", 107 | "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b", 108 | "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a", 109 | "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f", 110 | "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2", 111 | "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9", 112 | "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46", 113 | "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903", 114 | "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3", 115 | "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1", 116 | "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee" 117 | ], 118 | "index": "pypi", 119 | "version": "==36.0.1" 120 | }, 121 | "fastapi": { 122 | "hashes": [ 123 | "sha256:019ec52c00581bc055e6dfb621aaa9c2a56007c283839305412e1073a777eaf1", 124 | "sha256:7421a2f30e9ed1866874cff089733d4f9a0cd4f49b6ea3995c0de75e32bbb52f" 125 | ], 126 | "index": "pypi", 127 | "version": "==0.72.0" 128 | }, 129 | "h11": { 130 | "hashes": [ 131 | "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06", 132 | "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442" 133 | ], 134 | "version": "==0.13.0" 135 | }, 136 | "idna": { 137 | "hashes": [ 138 | "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", 139 | "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" 140 | ], 141 | "version": "==3.3" 142 | }, 143 | "pycparser": { 144 | "hashes": [ 145 | "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", 146 | "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" 147 | ], 148 | "version": "==2.21" 149 | }, 150 | "pydantic": { 151 | "hashes": [ 152 | "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3", 153 | "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398", 154 | "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1", 155 | "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65", 156 | "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4", 157 | "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16", 158 | "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2", 159 | "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c", 160 | "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6", 161 | "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce", 162 | "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9", 163 | "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3", 164 | "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034", 165 | "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c", 166 | "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a", 167 | "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77", 168 | "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b", 169 | "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6", 170 | "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f", 171 | "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721", 172 | "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37", 173 | "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032", 174 | "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d", 175 | "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed", 176 | "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6", 177 | "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054", 178 | "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25", 179 | "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46", 180 | "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5", 181 | "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c", 182 | "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070", 183 | "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1", 184 | "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7", 185 | "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d", 186 | "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145" 187 | ], 188 | "version": "==1.9.0" 189 | }, 190 | "pyjwt": { 191 | "hashes": [ 192 | "sha256:b888b4d56f06f6dcd777210c334e69c737be74755d3e5e9ee3fe67dc18a0ee41", 193 | "sha256:e0c4bb8d9f0af0c7f5b1ec4c5036309617d03d56932877f2f7a0beeb5318322f" 194 | ], 195 | "index": "pypi", 196 | "version": "==2.3.0" 197 | }, 198 | "pyyaml": { 199 | "hashes": [ 200 | "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", 201 | "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", 202 | "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", 203 | "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", 204 | "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", 205 | "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", 206 | "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", 207 | "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", 208 | "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", 209 | "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", 210 | "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", 211 | "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", 212 | "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", 213 | "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", 214 | "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", 215 | "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", 216 | "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", 217 | "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", 218 | "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", 219 | "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", 220 | "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", 221 | "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", 222 | "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", 223 | "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", 224 | "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", 225 | "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", 226 | "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", 227 | "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", 228 | "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", 229 | "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", 230 | "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", 231 | "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", 232 | "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" 233 | ], 234 | "index": "pypi", 235 | "version": "==6.0" 236 | }, 237 | "sniffio": { 238 | "hashes": [ 239 | "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663", 240 | "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de" 241 | ], 242 | "version": "==1.2.0" 243 | }, 244 | "starlette": { 245 | "hashes": [ 246 | "sha256:26a18cbda5e6b651c964c12c88b36d9898481cd428ed6e063f5f29c418f73050", 247 | "sha256:57eab3cc975a28af62f6faec94d355a410634940f10b30d68d31cb5ec1b44ae8" 248 | ], 249 | "version": "==0.17.1" 250 | }, 251 | "typing-extensions": { 252 | "hashes": [ 253 | "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", 254 | "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" 255 | ], 256 | "version": "==4.0.1" 257 | }, 258 | "uvicorn": { 259 | "hashes": [ 260 | "sha256:0b89c91bb8fe84c4bded9996af13c4b8c0de799d29bffeaa0c8ad298f2be0934", 261 | "sha256:192c2422b056a3beb512c6c260bf77a7a884204a4ae41856719c1913ead63bbb" 262 | ], 263 | "index": "pypi", 264 | "version": "==0.17.0" 265 | } 266 | }, 267 | "develop": { 268 | "black": { 269 | "hashes": [ 270 | "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3", 271 | "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f" 272 | ], 273 | "index": "pypi", 274 | "version": "==21.12b0" 275 | }, 276 | "click": { 277 | "hashes": [ 278 | "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", 279 | "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" 280 | ], 281 | "version": "==8.0.3" 282 | }, 283 | "mypy-extensions": { 284 | "hashes": [ 285 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 286 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 287 | ], 288 | "version": "==0.4.3" 289 | }, 290 | "pathspec": { 291 | "hashes": [ 292 | "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", 293 | "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" 294 | ], 295 | "version": "==0.9.0" 296 | }, 297 | "platformdirs": { 298 | "hashes": [ 299 | "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca", 300 | "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda" 301 | ], 302 | "version": "==2.4.1" 303 | }, 304 | "tomli": { 305 | "hashes": [ 306 | "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", 307 | "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c" 308 | ], 309 | "version": "==1.2.3" 310 | }, 311 | "typing-extensions": { 312 | "hashes": [ 313 | "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", 314 | "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" 315 | ], 316 | "version": "==4.0.1" 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastAPI tutorial 2 | 3 | Code for my video tutorial [FastAPI tutorial](https://youtu.be/GRD3z95vs-A) 4 | 5 | ## What is FastAPI? 6 | 7 | FastAPI is a high-performant REST API framework for Python. It's built on top of 8 | [Starlette](https://www.starlette.io/) and it uses [Pydantic](https://pydantic-docs.helpmanual.io/) 9 | for data validation. It can generate OpenAPI documentation from your code and also produces 10 | a Swagger UI that you can use to test your application. 11 | 12 | Check out FastAPI's GitHub [repository](https://github.com/tiangolo/fastapi) and give it a 13 | star! Also make sure to check out its excellent [documentation](https://fastapi.tiangolo.com/) online. 14 | 15 | ## What is JWT authorization? 16 | 17 | JWT stands for JSON Web Token and it's the standard way to add authorization to an API. Notice that we say 18 | authorization and not authentication: JWT tokens are used to authorize access to an API. JWTs are not used 19 | for authentication. Authentication is the process of verifying a user identity, and as part of this process 20 | we issue a JWT token. 21 | 22 | If you don't know what JWTs are and how they work, take a look at my tutorial "Working with JWTs in Python" 23 | (https://youtu.be/VRn8cPc7B_w). 24 | -------------------------------------------------------------------------------- /api.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import uuid 3 | 4 | from fastapi import HTTPException 5 | from starlette import status 6 | from starlette.requests import Request 7 | from starlette.responses import Response 8 | 9 | from schemas import ListTasksSchema, GetTaskSchema, CreateTaskSchema 10 | from server import server 11 | 12 | 13 | TODO = [] 14 | 15 | 16 | @server.get("/todo", response_model=ListTasksSchema) 17 | def get_tasks(request: Request): 18 | user_id = request.state.user_id 19 | return {"tasks": TODO} 20 | 21 | 22 | @server.post("/todo", response_model=GetTaskSchema, status_code=status.HTTP_201_CREATED) 23 | def create_task(payload: CreateTaskSchema): 24 | task = payload.dict() 25 | task["id"] = uuid.uuid4() 26 | task["created"] = datetime.datetime.utcnow() 27 | task["priority"] = task["priority"].value 28 | task["status"] = task["status"].value 29 | TODO.append(task) 30 | return task 31 | 32 | 33 | @server.get("/todo/{task_id}", response_model=GetTaskSchema) 34 | def get_task(task_id: uuid.UUID): 35 | for task in TODO: 36 | if task["id"] == task_id: 37 | return task 38 | raise HTTPException(status_code=404, detail=f"Task with ID {task_id} not found") 39 | 40 | 41 | @server.put("/todo/{task_id}", response_model=GetTaskSchema) 42 | def update_task(task_id: uuid.UUID, payload: CreateTaskSchema): 43 | for task in TODO: 44 | if task["id"] == task_id: 45 | task.update(payload.dict()) 46 | task["status"] = task["status"].value 47 | task["priority"] = task["priority"].value 48 | return task 49 | raise HTTPException(status_code=404, detail=f"Task with ID {task_id} not found") 50 | 51 | 52 | @server.delete( 53 | "/todo/{task_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response 54 | ) 55 | def delete_task(task_id: uuid.UUID): 56 | for index, task in enumerate(TODO): 57 | if task["id"] == task_id: 58 | TODO.pop(index) 59 | return 60 | raise HTTPException(status_code=404, detail=f"Task with ID {task_id} not found") 61 | -------------------------------------------------------------------------------- /auth.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import jwt 4 | from cryptography.x509 import load_pem_x509_certificate 5 | 6 | 7 | def decode_and_validate_token(access_token): 8 | unverified_headers = jwt.get_unverified_header(access_token) 9 | public_key = load_pem_x509_certificate( 10 | (Path(__file__).parent / "public_key.pem").read_text().encode("utf-8") 11 | ).public_key() 12 | return jwt.decode( 13 | access_token, 14 | key=public_key, 15 | algorithms=unverified_headers["alg"], 16 | audience="http://127.0.0.1:8000/todo", 17 | ) 18 | -------------------------------------------------------------------------------- /jwt_generator.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from datetime import datetime, timedelta 3 | from pathlib import Path 4 | 5 | import jwt 6 | from cryptography.hazmat.primitives import serialization 7 | 8 | 9 | def generate_jwt(): 10 | now = datetime.utcnow() 11 | payload = { 12 | "iss": "https://auth.coffeemesh.io/", 13 | "sub": str(uuid.uuid4()), 14 | "aud": "http://127.0.0.1:8000/todo", 15 | "iat": now.timestamp(), 16 | "exp": (now + timedelta(hours=24)).timestamp(), 17 | "scope": "openid", 18 | } 19 | 20 | private_key_text = Path("private_key.pem").read_text() 21 | private_key = serialization.load_pem_private_key( 22 | private_key_text.encode(), 23 | password=None, 24 | ) 25 | return jwt.encode(payload=payload, key=private_key, algorithm="RS256") 26 | 27 | 28 | print(generate_jwt()) 29 | -------------------------------------------------------------------------------- /oas.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | 3 | info: 4 | title: TODO API 5 | description: API that allows you to manage a to-do list 6 | version: 1.0.0 7 | 8 | paths: 9 | /todo: 10 | get: 11 | summary: Returns a list of to-do items 12 | operationId: getTasks 13 | responses: 14 | '200': 15 | description: A JSON array of tasks 16 | content: 17 | application/json: 18 | schema: 19 | $ref: '#/components/schemas/ListTasksSchema' 20 | 21 | post: 22 | summary: Creates a task 23 | operationId: createTask 24 | requestBody: 25 | required: true 26 | content: 27 | application/json: 28 | schema: 29 | $ref: '#/components/schemas/CreateTaskSchema' 30 | responses: 31 | '201': 32 | description: A JSON representation of the created task 33 | content: 34 | application/json: 35 | schema: 36 | $ref: '#/components/schemas/GetTaskSchema' 37 | links: 38 | GetTask: 39 | operationId: getTask 40 | parameters: 41 | task_id: '$response.body#/id' 42 | description: > 43 | The `id` value returned in the response can be used as 44 | the `task_id` parameter in `GET /todo/{task_id} 45 | UpdateTask: 46 | operationId: updateTask 47 | parameters: 48 | task_id: '$response.body#/id' 49 | description: > 50 | The `id` value returned in the response can be used as 51 | the `task_id` parameter in `PUT /todo/{task_id} 52 | DeleteTask: 53 | operationId: deleteTask 54 | parameters: 55 | task_id: '$response.body#/id' 56 | description: > 57 | The `id` value returned in the response can be used as 58 | the `task_id` parameter in `DELETE /todo/{task_id} 59 | 60 | /todo/{task_id}: 61 | parameters: 62 | - in: path 63 | name: task_id 64 | required: true 65 | schema: 66 | type: string 67 | format: uuid 68 | example: d222e7a3-6afb-463a-9709-38eb70cc670d 69 | get: 70 | summary: Returns the details of a task 71 | operationId: getTask 72 | responses: 73 | '200': 74 | description: A JSON representation of a task 75 | content: 76 | application/json: 77 | schema: 78 | $ref: '#/components/schemas/GetTaskSchema' 79 | '404': 80 | $ref: '#/components/responses/NotFound' 81 | 82 | put: 83 | summary: Replaces an existing task 84 | operationId: updateTask 85 | requestBody: 86 | required: true 87 | content: 88 | application/json: 89 | schema: 90 | $ref: '#/components/schemas/CreateTaskSchema' 91 | responses: 92 | '200': 93 | description: A JSON representation of a task 94 | content: 95 | application/json: 96 | schema: 97 | $ref: '#/components/schemas/GetTaskSchema' 98 | '404': 99 | $ref: '#/components/responses/NotFound' 100 | 101 | delete: 102 | summary: Deletes an existing task 103 | operationId: deleteTask 104 | responses: 105 | '204': 106 | description: The resource was deleted successfully 107 | '404': 108 | $ref: '#/components/responses/NotFound' 109 | 110 | components: 111 | responses: 112 | NotFound: 113 | description: The specified resource was not found. 114 | content: 115 | application/json: 116 | schema: 117 | $ref: '#/components/schemas/Error' 118 | 119 | securitySchemes: 120 | bearerAuth: 121 | type: http 122 | scheme: bearer 123 | bearerFormat: JWT 124 | 125 | schemas: 126 | Error: 127 | type: object 128 | properties: 129 | detail: 130 | type: string 131 | 132 | CreateTaskSchema: 133 | type: object 134 | required: 135 | - task 136 | additionalProperties: false 137 | properties: 138 | priority: 139 | type: string 140 | enum: 141 | - low 142 | - medium 143 | - high 144 | default: low 145 | status: 146 | type: string 147 | enum: 148 | - pending 149 | - progress 150 | - completed 151 | default: pending 152 | task: 153 | type: string 154 | 155 | GetTaskSchema: 156 | type: object 157 | required: 158 | - created 159 | - id 160 | - priority 161 | - status 162 | - task 163 | additionalProperties: false 164 | properties: 165 | id: 166 | type: string 167 | format: uuid 168 | created: 169 | type: string 170 | format: date-time 171 | priority: 172 | type: string 173 | enum: 174 | - low 175 | - medium 176 | - high 177 | default: low 178 | status: 179 | type: string 180 | enum: 181 | - pending 182 | - progress 183 | - completed 184 | task: 185 | type: string 186 | ListTasksSchema: 187 | type: object 188 | required: 189 | - tasks 190 | properties: 191 | tasks: 192 | type: array 193 | items: 194 | $ref: '#/components/schemas/GetTaskSchema' 195 | 196 | security: 197 | - bearerAuth: 198 | - getTasks 199 | - createTask 200 | - getTask 201 | - updateTask 202 | - deleteTask 203 | -------------------------------------------------------------------------------- /private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD3alnZqD7W5ZEE 3 | rkmwKpTha2P868rgbZHTh7jiJqm28dkp57RxgFCBf92OwhhC8iC46WjTXo14Juc0 4 | c/Cu6YLf7et2ghdCiXU0nfmrI2vThQ+LmjGXvFBJ/JWKRzLt/xjrPikwFjfJi2Vl 5 | eaR6FTEqsaggXpg+8VhIKzIP0ZgB0QeqfajTkNXiHOaXnpInieGxppRS5Yp1nSD+ 6 | c7W37kTNyicB/kVy1R3YTxPuD2nBUoSjHUwQNnXc0t9e2I1BvcydXOhzADi2ioRf 7 | Wentm5zTkgt15s+XmWcvEBuc1UeiTVi6xdlRo0yE06LLf69SSyfbbqsKKLFUlj1T 8 | u6dcKQxlAgMBAAECggEAWYV4p0GyIw2DcF8juINfxUSXNC+ZA6WBqzkjk2iL5e8S 9 | NZBqANt9xa2fXAUsxCRZ5K5HyJsgswXYiGkfABrryNyMYx/lDKUx7GzwppS1ch5m 10 | JowPP6jaoDqtXRAwBF9WA+BFBOo1G069zH68HZRHavxHKhcwHS40VECU55Zd2uGy 11 | McnrCSTbhhuvaAVh0EXLSxWAijubADddLszwtDxUjWKI/z92lIwz0KHNv/EunFYX 12 | fYFnGusJUEKXjJNstgkYB/1NWfxsQEeZ6HJTSeI8DGSZW/MdICZJaFlaj+/LRm0d 13 | KcpWxlebaQPy8s1FJirEW2Wmmz2VHI0vikzm3/ENAQKBgQD+zZdLTnTMSgkMMEIN 14 | 30GzHmoiZgpp7RVwVsFQsG8+QOxQZCqH8hqjbE9uKAEoOJLF+s/zbIagqziksNtq 15 | Cz/IPT0ciBwLs9B0Fa954mVeqStji2zt3TNNqY1qcEJkwjvPUENURNoiT6oe13pf 16 | 4ver/PkQo9v042bQTlvT8KOOJQKBgQD4k+BHROtGznFZhu41Fp1ojLp9WZmjL4PX 17 | q0/mQLEZQGj1kgAy9e37G/O/Gm+mQMmis2P6dueMF6lH07spWcZhYU7iTgrEo/EL 18 | YAr57NDvpCjSCPrWG+mbkj3F2w0UyuIM9qexueyWpPhUj/7gZUw5NA8WJFBhmuhd 19 | /PAkSvORQQKBgGVnqMidproO1N727bLboSgJ+K9L8OULJ7cXr8PPE9awu9uaoZqM 20 | 7bEQT+RFI4DAUxlbohr8m++hdN+GSyw+4dltFb8fJBo8K7+nSbi2MyWjKdNqD6Xy 21 | nJJR4if0GVhEPM1a4hDNfgqdrdSsaNV0XKEkAnNu4wDo52gLZ1xHrq/JAoGBAKyC 22 | o8GSON34431IQQSi3zbxg3L+vbscfJkuENMwzjdCFhVm77BuMGx2p6BWjmmIyOab 23 | LKVj46m7ugVZEajO3vgx+fbgRmTVibFlQ/jSuuW0vYeWJ85zrJoE0c4ACYIGkv6A 24 | Jz0WVPTvJFA7Zp0Ab/e7A0VQimBSizS/F0F4qrSBAoGBANQPp0eMPwxIQ8RLpSX3 25 | pnZB2D87Bk08zyK8iPEwLP9LxeJCO9r9mEWkM1Upere6AhMfWvN+sM6DZo9JGvX6 26 | 1hIUhB9NXNt+TUF84sDkhJU8XohP6sNAnPa5HI5O3lDnp1DpjH+OYkWKMPsZADQp 27 | ikU1RIkJ50WjLVW+tJknJudi 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqjCCAZICCQChfFrIswTT2TANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxq 3 | d3QtdHVyb3JpYWwwHhcNMjIwMTA2MTcxNzQzWhcNMjIwMjA1MTcxNzQzWjAXMRUw 4 | EwYDVQQDDAxqd3QtdHVyb3JpYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 5 | AoIBAQD3alnZqD7W5ZEErkmwKpTha2P868rgbZHTh7jiJqm28dkp57RxgFCBf92O 6 | whhC8iC46WjTXo14Juc0c/Cu6YLf7et2ghdCiXU0nfmrI2vThQ+LmjGXvFBJ/JWK 7 | RzLt/xjrPikwFjfJi2VleaR6FTEqsaggXpg+8VhIKzIP0ZgB0QeqfajTkNXiHOaX 8 | npInieGxppRS5Yp1nSD+c7W37kTNyicB/kVy1R3YTxPuD2nBUoSjHUwQNnXc0t9e 9 | 2I1BvcydXOhzADi2ioRfWentm5zTkgt15s+XmWcvEBuc1UeiTVi6xdlRo0yE06LL 10 | f69SSyfbbqsKKLFUlj1Tu6dcKQxlAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFBh 11 | UgSTz4i/LE9Gqn2py5UFMTBjugnVcIHR0Aq2izyKAw4d/jPbN9OEhTRJQT9pH/db 12 | Q87q0f0GmJO6YsZ/3zMz43/vfCDfM9hBERt//Cf6CsnelqurIExMgif89WJqHASO 13 | IGbRCiVJdHX7RwxAXfOj0kUePkh0QHWDGy9HjjNHVi5v/380jNjSmH2i/rM+Og6n 14 | dYXQ0QPoT0JvcQ2XMPZIpbnYVi3l1mPZHrt4g3MxNNH138GUHevU+9b5TdJp1MyL 15 | WpPwAPSB6xI+cN45VAx/dWauiptCklACrZuwq0XLGAL/niGO4+Gzpz0nRzNZErmR 16 | 5nFjePUzKOaZQ4gUYqI= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /schemas.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from enum import Enum 3 | from typing import Optional, List 4 | from uuid import UUID 5 | 6 | from pydantic import BaseModel 7 | 8 | 9 | class Error(BaseModel): 10 | detail: Optional[str] = None 11 | 12 | 13 | class Priority(Enum): 14 | low = "low" 15 | medium = "medium" 16 | high = "high" 17 | 18 | 19 | class Status(Enum): 20 | progress = "progress" 21 | pending = "pending" 22 | completed = "completed" 23 | 24 | 25 | class CreateTaskSchema(BaseModel): 26 | priority: Optional[Priority] = "low" 27 | status: Optional[Status] = "pending" 28 | task: str 29 | 30 | 31 | class GetTaskSchema(BaseModel): 32 | id: UUID 33 | created: datetime 34 | priority: Priority 35 | status: Status 36 | task: str 37 | 38 | 39 | class ListTasksSchema(BaseModel): 40 | tasks: List[GetTaskSchema] 41 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import yaml 4 | from fastapi import FastAPI 5 | from jwt import ( 6 | ExpiredSignatureError, 7 | ImmatureSignatureError, 8 | InvalidAlgorithmError, 9 | InvalidAudienceError, 10 | InvalidKeyError, 11 | InvalidSignatureError, 12 | InvalidTokenError, 13 | MissingRequiredClaimError, 14 | ) 15 | from starlette import status 16 | from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint 17 | from starlette.middleware.cors import CORSMiddleware 18 | from starlette.requests import Request 19 | from starlette.responses import Response, JSONResponse 20 | 21 | from auth import decode_and_validate_token 22 | 23 | server = FastAPI(debug=True) 24 | 25 | oas_doc = yaml.safe_load((Path(__file__).parent / "oas.yaml").read_text()) 26 | 27 | server.openapi = lambda: oas_doc 28 | 29 | 30 | class AuthorizeRequestMiddleware(BaseHTTPMiddleware): 31 | async def dispatch( 32 | self, request: Request, call_next: RequestResponseEndpoint 33 | ) -> Response: 34 | if request.url.path in ["/docs", "/openapi.json"]: 35 | return await call_next(request) 36 | if request.method == "OPTIONS": 37 | return await call_next(request) 38 | 39 | bearer_token = request.headers.get("Authorization") 40 | if not bearer_token: 41 | return JSONResponse( 42 | status_code=status.HTTP_401_UNAUTHORIZED, 43 | content={ 44 | "detail": "Missing access token", 45 | "body": "Missing access token", 46 | }, 47 | ) 48 | try: 49 | auth_token = bearer_token.split(" ")[1].strip() 50 | token_payload = decode_and_validate_token(auth_token) 51 | except ( 52 | ExpiredSignatureError, 53 | ImmatureSignatureError, 54 | InvalidAlgorithmError, 55 | InvalidAudienceError, 56 | InvalidKeyError, 57 | InvalidSignatureError, 58 | InvalidTokenError, 59 | MissingRequiredClaimError, 60 | ) as error: 61 | return JSONResponse( 62 | status_code=status.HTTP_401_UNAUTHORIZED, 63 | content={"detail": str(error), "body": str(error)}, 64 | ) 65 | else: 66 | request.state.user_id = token_payload["sub"] 67 | return await call_next(request) 68 | 69 | 70 | server.add_middleware(AuthorizeRequestMiddleware) 71 | 72 | 73 | server.add_middleware( 74 | CORSMiddleware, 75 | allow_origins=["*"], 76 | allow_credentials=True, 77 | allow_methods=["*"], 78 | allow_headers=["*"], 79 | ) 80 | 81 | import api 82 | --------------------------------------------------------------------------------