diff --git a/.gitignore b/.gitignore index 0f8643b2..12bd793e 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,4 @@ dmypy.json temp.* *.sqlite3 -dump.rdb \ No newline at end of file +dump.rdb diff --git a/Dockerfile b/Dockerfile index fb8de3e8..1bb28e4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ -FROM python:3.9 - -RUN pip3 install pipenv - -WORKDIR /usr/src/app - -COPY Pipfile ./ -COPY Pipfile.lock ./ - -RUN set -ex && pipenv install --deploy --system - +FROM python:3.9 + +RUN pip3 install pipenv + +WORKDIR /usr/src/app + +COPY Pipfile ./ +COPY Pipfile.lock ./ + +RUN set -ex && pipenv install --deploy --system + COPY . . \ No newline at end of file diff --git a/Pipfile b/Pipfile index c3c459b4..8362e750 100644 --- a/Pipfile +++ b/Pipfile @@ -1,19 +1,19 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -aioredis = "*" -ariadne = "*" -starlette = "*" -uvicorn = "*" -pydantic = "*" -passlib = "*" -PyJWT = "*" -SQLAlchemy = "*" - -[dev-packages] - -[requires] -python_version = "3.9" +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +aioredis = "*" +ariadne = "*" +starlette = "*" +uvicorn = "*" +pydantic = "*" +passlib = "*" +PyJWT = "*" +SQLAlchemy = "*" + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index a1a7eca6..51cf8457 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,283 +1,283 @@ -{ - "_meta": { - "hash": { - "sha256": "94d9a9de7e56cb5c5781a85d19fd688e64f5cbab46e8d12d194e58ba8d246612" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "aioredis": { - "hashes": [ - "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a", - "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3" - ], - "index": "pypi", - "version": "==1.3.1" - }, - "ariadne": { - "hashes": [ - "sha256:56bc3609a0512920f06e9312f8ea6db3c8e4a7cd77f31fbed388f5dba6d589c0", - "sha256:e00abd7eb5869b59a638f1e3a7743445bf387236048cf1b0eb9d7c506dcd37c5" - ], - "index": "pypi", - "version": "==0.13.0" - }, - "asgiref": { - "hashes": [ - "sha256:05914d0fa65a21711e732adc6572edad6c8da5f1435c3f0c060689ced5e85195", - "sha256:d36fa91dd90e3aa3c81a6bd426ccc8fb20bd3d22b0cf14a12800289e9c3e2563" - ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" - }, - "async-timeout": { - "hashes": [ - "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", - "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" - ], - "markers": "python_full_version >= '3.5.3'", - "version": "==3.0.1" - }, - "click": { - "hashes": [ - "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", - "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" - ], - "markers": "python_version >= '3.6'", - "version": "==8.0.1" - }, - "graphql-core": { - "hashes": [ - "sha256:91d96ef0e86665777bb7115d3bbb6b0326f43dc7dbcdd60da5486a27a50cfb11", - "sha256:a755635d1d364a17e8d270347000722351aaa03f1ab7d280878aae82fc68b1f3" - ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==3.1.5" - }, - "greenlet": { - "hashes": [ - "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c", - "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832", - "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08", - "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e", - "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22", - "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f", - "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c", - "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea", - "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8", - "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad", - "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc", - "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16", - "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8", - "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5", - "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99", - "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e", - "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a", - "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56", - "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c", - "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed", - "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959", - "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922", - "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927", - "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e", - "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a", - "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131", - "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919", - "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319", - "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae", - "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535", - "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505", - "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11", - "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47", - "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821", - "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857", - "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da", - "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc", - "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5", - "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb", - "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05", - "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5", - "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee", - "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e", - "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831", - "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f", - "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3", - "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6", - "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3", - "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f" - ], - "markers": "python_version >= '3'", - "version": "==1.1.0" - }, - "h11": { - "hashes": [ - "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", - "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" - ], - "markers": "python_version >= '3.6'", - "version": "==0.12.0" - }, - "hiredis": { - "hashes": [ - "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e", - "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27", - "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163", - "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc", - "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26", - "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e", - "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579", - "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a", - "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048", - "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87", - "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63", - "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54", - "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05", - "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb", - "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea", - "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5", - "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e", - "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc", - "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99", - "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a", - "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581", - "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426", - "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db", - "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a", - "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a", - "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d", - "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443", - "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79", - "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d", - "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9", - "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d", - "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485", - "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5", - "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048", - "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0", - "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6", - "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41", - "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298", - "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce", - "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0", - "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a" - ], - "markers": "python_version >= '3.6'", - "version": "==2.0.0" - }, - "passlib": { - "hashes": [ - "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", - "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04" - ], - "index": "pypi", - "version": "==1.7.4" - }, - "pydantic": { - "hashes": [ - "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd", - "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739", - "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f", - "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840", - "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23", - "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287", - "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62", - "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b", - "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb", - "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820", - "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3", - "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b", - "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e", - "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3", - "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316", - "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b", - "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4", - "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20", - "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e", - "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505", - "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1", - "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833" - ], - "index": "pypi", - "version": "==1.8.2" - }, - "pyjwt": { - "hashes": [ - "sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1", - "sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130" - ], - "index": "pypi", - "version": "==2.1.0" - }, - "sqlalchemy": { - "hashes": [ - "sha256:0f6d467b67a7e5048f1408e8ea60d6caa70be5b386d0eebbf1185ab49cb8c7e4", - "sha256:238d78b3110b7f7cffdb70bf9cda686e0d876a849bc78ba4d471aa7b1461f306", - "sha256:25c0e0f3a7e8c19350086b3c0fe93c4def045cec053d749ef15da710c4d54c81", - "sha256:2f60a2e599cf5cf5e5327ce60f2918b897e42ad9f405d10dd01e37869c0ce6fc", - "sha256:38ee3a266afef2978e82824650457f70c5d74ec0cadec1b10fe5ed6f038eb5d0", - "sha256:46361690f1e1c5385994a4caeb6e8126063ff593a5c635700bbc1245de793c1e", - "sha256:46b99eab618cdc1c871ea707b7c52edc23cfea6c750740cd242ba62b5c84de7f", - "sha256:4a67371752fd86d1d03a3b82d4e75404608f6f4d579b9676124079a22a40c79f", - "sha256:525dd3c2205b11a2bc6d770bf1ec63bde0253fd754b4c19c399d27ddc9dad0d3", - "sha256:6c8406c3d8c1c7d15da454de15d77f7bb48d14ede5db994f74226c348cf1050e", - "sha256:6da83225a23eaf7b3f48f3d5f53c91b2cf00fbfa48b24a7a758160112dd3e123", - "sha256:7150e5b543b466f45f668b352f7abda27998cc8035f051d1b7e9524ca9eb2f5f", - "sha256:76fbc24311a3d039d6cd147d396719f606d96d1413f3816c028a48e29367f646", - "sha256:854a7b15750e617e16f8d65dbc004f065a7963544b253b923f16109557648777", - "sha256:86c079732328f1add097b0b8079cd532b5d28e207fac93e9d6ea5f487506deef", - "sha256:8d860c62e3f51623ccd528d8fac44580501df557d4b467cc5581587fcf057719", - "sha256:9675d5bc7e4f96a7bb2b54d14e9b269a5fb6e5d36ecc7d01f0f65bb9af3185f9", - "sha256:9841762d114018c49483c089fa2d47f7e612e57666323f615913d7d7f46e9606", - "sha256:9eb25bcf9161e2fcbe9eebe8e829719b2334e849183f0e496bf4b83722bcccfa", - "sha256:aad3234a41340e9cf6184e621694e2a7233ba3f8aef9b1e6de8cba431b45ebd2", - "sha256:b502b5e2f08500cc4b8d29bfc4f51d805adcbc00f8d149e98fda8aae85ddb644", - "sha256:b86d83fefc8a8c394f3490c37e1953bc16c311a3d1d1cf91518793bfb9847fb4", - "sha256:c0eb2cd3ad4967fcbdd9e066e8cd91fe2c23c671dbae9952f0b4d3d42832cc5f", - "sha256:e0d48456e1aa4f0537f9c9af7be71e1f0659ff68bc1cd538ebc785f6b007bd0d", - "sha256:eaee5dd378f6f0d7c3ec49aeeb26564d55ac0ad73b9b4688bf29e66deabddf73", - "sha256:f14acb0fd16d404fda9370f93aace682f284340c89c3442ac747c5466ac7e2b5", - "sha256:f6fc526bd70898489d02bf52c8f0632ab377592ae954d0c0a5bb38d618dddaa9", - "sha256:fcd84e4d46a86291495d131a7824ba38d2e8278bda9425c50661a04633174319", - "sha256:ff38ecf89c69a531a7326c2dae71982edfe2f805f3c016cdc5bfd1a04ebf80cb", - "sha256:ff8bebc7a9d297dff2003460e01db2c20c63818b45fb19170f388b1a72fe5a14" - ], - "index": "pypi", - "version": "==1.4.20" - }, - "starlette": { - "hashes": [ - "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed", - "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa" - ], - "index": "pypi", - "version": "==0.14.2" - }, - "typing-extensions": { - "hashes": [ - "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", - "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", - "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" - ], - "version": "==3.10.0.0" - }, - "uvicorn": { - "hashes": [ - "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae", - "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292" - ], - "index": "pypi", - "version": "==0.14.0" - } - }, - "develop": {} -} +{ + "_meta": { + "hash": { + "sha256": "94d9a9de7e56cb5c5781a85d19fd688e64f5cbab46e8d12d194e58ba8d246612" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aioredis": { + "hashes": [ + "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a", + "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3" + ], + "index": "pypi", + "version": "==1.3.1" + }, + "ariadne": { + "hashes": [ + "sha256:56bc3609a0512920f06e9312f8ea6db3c8e4a7cd77f31fbed388f5dba6d589c0", + "sha256:e00abd7eb5869b59a638f1e3a7743445bf387236048cf1b0eb9d7c506dcd37c5" + ], + "index": "pypi", + "version": "==0.13.0" + }, + "asgiref": { + "hashes": [ + "sha256:05914d0fa65a21711e732adc6572edad6c8da5f1435c3f0c060689ced5e85195", + "sha256:d36fa91dd90e3aa3c81a6bd426ccc8fb20bd3d22b0cf14a12800289e9c3e2563" + ], + "markers": "python_version >= '3.6'", + "version": "==3.4.0" + }, + "async-timeout": { + "hashes": [ + "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", + "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" + ], + "markers": "python_full_version >= '3.5.3'", + "version": "==3.0.1" + }, + "click": { + "hashes": [ + "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", + "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.1" + }, + "graphql-core": { + "hashes": [ + "sha256:91d96ef0e86665777bb7115d3bbb6b0326f43dc7dbcdd60da5486a27a50cfb11", + "sha256:a755635d1d364a17e8d270347000722351aaa03f1ab7d280878aae82fc68b1f3" + ], + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==3.1.5" + }, + "greenlet": { + "hashes": [ + "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c", + "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832", + "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08", + "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e", + "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22", + "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f", + "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c", + "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea", + "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8", + "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad", + "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc", + "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16", + "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8", + "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5", + "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99", + "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e", + "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a", + "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56", + "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c", + "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed", + "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959", + "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922", + "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927", + "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e", + "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a", + "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131", + "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919", + "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319", + "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae", + "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535", + "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505", + "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11", + "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47", + "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821", + "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857", + "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da", + "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc", + "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5", + "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb", + "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05", + "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5", + "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee", + "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e", + "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831", + "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f", + "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3", + "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6", + "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3", + "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f" + ], + "markers": "python_version >= '3'", + "version": "==1.1.0" + }, + "h11": { + "hashes": [ + "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", + "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" + ], + "markers": "python_version >= '3.6'", + "version": "==0.12.0" + }, + "hiredis": { + "hashes": [ + "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e", + "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27", + "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163", + "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc", + "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26", + "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e", + "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579", + "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a", + "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048", + "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87", + "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63", + "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54", + "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05", + "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb", + "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea", + "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5", + "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e", + "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc", + "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99", + "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a", + "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581", + "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426", + "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db", + "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a", + "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a", + "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d", + "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443", + "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79", + "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d", + "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9", + "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d", + "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485", + "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5", + "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048", + "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0", + "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6", + "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41", + "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298", + "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce", + "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0", + "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a" + ], + "markers": "python_version >= '3.6'", + "version": "==2.0.0" + }, + "passlib": { + "hashes": [ + "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", + "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04" + ], + "index": "pypi", + "version": "==1.7.4" + }, + "pydantic": { + "hashes": [ + "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd", + "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739", + "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f", + "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840", + "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23", + "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287", + "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62", + "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b", + "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb", + "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820", + "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3", + "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b", + "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e", + "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3", + "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316", + "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b", + "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4", + "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20", + "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e", + "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505", + "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1", + "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833" + ], + "index": "pypi", + "version": "==1.8.2" + }, + "pyjwt": { + "hashes": [ + "sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1", + "sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130" + ], + "index": "pypi", + "version": "==2.1.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:0f6d467b67a7e5048f1408e8ea60d6caa70be5b386d0eebbf1185ab49cb8c7e4", + "sha256:238d78b3110b7f7cffdb70bf9cda686e0d876a849bc78ba4d471aa7b1461f306", + "sha256:25c0e0f3a7e8c19350086b3c0fe93c4def045cec053d749ef15da710c4d54c81", + "sha256:2f60a2e599cf5cf5e5327ce60f2918b897e42ad9f405d10dd01e37869c0ce6fc", + "sha256:38ee3a266afef2978e82824650457f70c5d74ec0cadec1b10fe5ed6f038eb5d0", + "sha256:46361690f1e1c5385994a4caeb6e8126063ff593a5c635700bbc1245de793c1e", + "sha256:46b99eab618cdc1c871ea707b7c52edc23cfea6c750740cd242ba62b5c84de7f", + "sha256:4a67371752fd86d1d03a3b82d4e75404608f6f4d579b9676124079a22a40c79f", + "sha256:525dd3c2205b11a2bc6d770bf1ec63bde0253fd754b4c19c399d27ddc9dad0d3", + "sha256:6c8406c3d8c1c7d15da454de15d77f7bb48d14ede5db994f74226c348cf1050e", + "sha256:6da83225a23eaf7b3f48f3d5f53c91b2cf00fbfa48b24a7a758160112dd3e123", + "sha256:7150e5b543b466f45f668b352f7abda27998cc8035f051d1b7e9524ca9eb2f5f", + "sha256:76fbc24311a3d039d6cd147d396719f606d96d1413f3816c028a48e29367f646", + "sha256:854a7b15750e617e16f8d65dbc004f065a7963544b253b923f16109557648777", + "sha256:86c079732328f1add097b0b8079cd532b5d28e207fac93e9d6ea5f487506deef", + "sha256:8d860c62e3f51623ccd528d8fac44580501df557d4b467cc5581587fcf057719", + "sha256:9675d5bc7e4f96a7bb2b54d14e9b269a5fb6e5d36ecc7d01f0f65bb9af3185f9", + "sha256:9841762d114018c49483c089fa2d47f7e612e57666323f615913d7d7f46e9606", + "sha256:9eb25bcf9161e2fcbe9eebe8e829719b2334e849183f0e496bf4b83722bcccfa", + "sha256:aad3234a41340e9cf6184e621694e2a7233ba3f8aef9b1e6de8cba431b45ebd2", + "sha256:b502b5e2f08500cc4b8d29bfc4f51d805adcbc00f8d149e98fda8aae85ddb644", + "sha256:b86d83fefc8a8c394f3490c37e1953bc16c311a3d1d1cf91518793bfb9847fb4", + "sha256:c0eb2cd3ad4967fcbdd9e066e8cd91fe2c23c671dbae9952f0b4d3d42832cc5f", + "sha256:e0d48456e1aa4f0537f9c9af7be71e1f0659ff68bc1cd538ebc785f6b007bd0d", + "sha256:eaee5dd378f6f0d7c3ec49aeeb26564d55ac0ad73b9b4688bf29e66deabddf73", + "sha256:f14acb0fd16d404fda9370f93aace682f284340c89c3442ac747c5466ac7e2b5", + "sha256:f6fc526bd70898489d02bf52c8f0632ab377592ae954d0c0a5bb38d618dddaa9", + "sha256:fcd84e4d46a86291495d131a7824ba38d2e8278bda9425c50661a04633174319", + "sha256:ff38ecf89c69a531a7326c2dae71982edfe2f805f3c016cdc5bfd1a04ebf80cb", + "sha256:ff8bebc7a9d297dff2003460e01db2c20c63818b45fb19170f388b1a72fe5a14" + ], + "index": "pypi", + "version": "==1.4.20" + }, + "starlette": { + "hashes": [ + "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed", + "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa" + ], + "index": "pypi", + "version": "==0.14.2" + }, + "typing-extensions": { + "hashes": [ + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + ], + "version": "==3.10.0.0" + }, + "uvicorn": { + "hashes": [ + "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae", + "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292" + ], + "index": "pypi", + "version": "==0.14.0" + } + }, + "develop": {} +} diff --git a/README.md b/README.md index 3e5b5fd3..3e653805 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,33 @@ -# discours-backend-next - -Tech stack: - - - pyjwt - - redis - - ariadne - - starlette - -# Local development - -Install redis and pipenv first - -``` -brew install redis pipenv -brew services start redis -``` - -Then run API server - -``` -pipenv shell -python3 server.py -``` - -# With Docker - -TODO - - -# How to do an authorized request - +# discours-backend-next + +Tech stack: + + - pyjwt + - redis + - ariadne + - starlette + +# Local development + +Install redis and pipenv first + +``` +brew install redis pipenv +brew services start redis +``` + +Then run API server + +``` +pipenv shell +python3 server.py +``` + +# With Docker + +TODO + + +# How to do an authorized request + Put the header 'Auth' with token from signInQuery in requests. \ No newline at end of file diff --git a/auth/authenticate.py b/auth/authenticate.py index 42aeee1a..2637d302 100644 --- a/auth/authenticate.py +++ b/auth/authenticate.py @@ -1,77 +1,77 @@ -from functools import wraps -from typing import Optional, Tuple - -from graphql import GraphQLResolveInfo -import jwt -from starlette.authentication import AuthenticationBackend -from starlette.requests import HTTPConnection - -from auth.credentials import AuthCredentials, AuthUser -from auth.token import Token -from exceptions import InvalidToken, OperationNotAllowed -from orm import User -from redis import redis -from settings import JWT_AUTH_HEADER - - -class _Authenticate: - @classmethod - async def verify(cls, token: str): - """ - Rules for a token to be valid. - 1. token format is legal && - token exists in redis database && - token is not expired - 2. token format is legal && - token exists in redis database && - token is expired && - token is of specified type - """ - try: - payload = Token.decode(token) - except exceptions.ExpiredSignatureError: - payload = Token.decode(token, verify_exp=False) - if not await cls.exists(payload.user_id, token): - raise InvalidToken("Login expired, please login again") - if payload.device == "mobile": # noqa - "we cat set mobile token to be valid forever" - return payload - except exceptions.JWTDecodeError as e: - raise InvalidToken("token format error") from e - else: - if not await cls.exists(payload.user_id, token): - raise InvalidToken("Login expired, please login again") - return payload - - @classmethod - async def exists(cls, user_id, token): - token = await redis.execute("GET", f"{user_id}-{token}") - return token is not None - - -class JWTAuthenticate(AuthenticationBackend): - async def authenticate( - self, request: HTTPConnection - ) -> Optional[Tuple[AuthCredentials, AuthUser]]: - if JWT_AUTH_HEADER not in request.headers: - return AuthCredentials(scopes=[]), AuthUser(user_id=None) - - token = request.headers[JWT_AUTH_HEADER] - try: - payload = await _Authenticate.verify(token) - except Exception as exc: - return AuthCredentials(scopes=[], error_message=str(exc)), AuthUser(user_id=None) - - scopes = User.get_permission(user_id=payload.user_id) - return AuthCredentials(user_id=payload.user_id, scopes=scopes, logged_in=True), AuthUser(user_id=payload.user_id) - - -def login_required(func): - @wraps(func) - async def wrap(parent, info: GraphQLResolveInfo, *args, **kwargs): - auth: AuthCredentials = info.context["request"].auth - if not auth.logged_in: - raise OperationNotAllowed(auth.error_message or "Please login") - return await func(parent, info, *args, **kwargs) - - return wrap +from functools import wraps +from typing import Optional, Tuple + +from graphql import GraphQLResolveInfo +import jwt +from starlette.authentication import AuthenticationBackend +from starlette.requests import HTTPConnection + +from auth.credentials import AuthCredentials, AuthUser +from auth.token import Token +from exceptions import InvalidToken, OperationNotAllowed +from orm import User +from redis import redis +from settings import JWT_AUTH_HEADER + + +class _Authenticate: + @classmethod + async def verify(cls, token: str): + """ + Rules for a token to be valid. + 1. token format is legal && + token exists in redis database && + token is not expired + 2. token format is legal && + token exists in redis database && + token is expired && + token is of specified type + """ + try: + payload = Token.decode(token) + except exceptions.ExpiredSignatureError: + payload = Token.decode(token, verify_exp=False) + if not await cls.exists(payload.user_id, token): + raise InvalidToken("Login expired, please login again") + if payload.device == "mobile": # noqa + "we cat set mobile token to be valid forever" + return payload + except exceptions.JWTDecodeError as e: + raise InvalidToken("token format error") from e + else: + if not await cls.exists(payload.user_id, token): + raise InvalidToken("Login expired, please login again") + return payload + + @classmethod + async def exists(cls, user_id, token): + token = await redis.execute("GET", f"{user_id}-{token}") + return token is not None + + +class JWTAuthenticate(AuthenticationBackend): + async def authenticate( + self, request: HTTPConnection + ) -> Optional[Tuple[AuthCredentials, AuthUser]]: + if JWT_AUTH_HEADER not in request.headers: + return AuthCredentials(scopes=[]), AuthUser(user_id=None) + + token = request.headers[JWT_AUTH_HEADER] + try: + payload = await _Authenticate.verify(token) + except Exception as exc: + return AuthCredentials(scopes=[], error_message=str(exc)), AuthUser(user_id=None) + + scopes = User.get_permission(user_id=payload.user_id) + return AuthCredentials(user_id=payload.user_id, scopes=scopes, logged_in=True), AuthUser(user_id=payload.user_id) + + +def login_required(func): + @wraps(func) + async def wrap(parent, info: GraphQLResolveInfo, *args, **kwargs): + auth: AuthCredentials = info.context["request"].auth + if not auth.logged_in: + raise OperationNotAllowed(auth.error_message or "Please login") + return await func(parent, info, *args, **kwargs) + + return wrap diff --git a/auth/identity.py b/auth/identity.py index 8af26a2c..1eefe773 100644 --- a/auth/identity.py +++ b/auth/identity.py @@ -6,12 +6,20 @@ from auth.validations import User class Identity: - @staticmethod - def identity(user_id: int, password: str) -> User: - user = global_session.query(OrmUser).filter_by(id=user_id).first() - if not user: - raise ObjectNotExist("User does not exist") - user = User(**user.dict()) - if not Password.verify(password, user.password): - raise InvalidPassword("Wrong user password") - return user + @staticmethod + def identity(user_id: int, password: str) -> User: + user = global_session.query(OrmUser).filter_by(id=user_id).first() + if not user: + raise ObjectNotExist("User does not exist") + user = User(**user.dict()) + if not Password.verify(password, user.password): + raise InvalidPassword("Wrong user password") + return user + + @staticmethod + def identity_oauth(oauth_id, input) -> User: + user = global_session.query(OrmUser).filter_by(oauth_id=oauth_id).first() + if not user: + user = OrmUser.create(**input) + user = User(**user.dict()) + return user diff --git a/auth/oauth.py b/auth/oauth.py new file mode 100644 index 00000000..8fc6ade7 --- /dev/null +++ b/auth/oauth.py @@ -0,0 +1,68 @@ +from authlib.integrations.starlette_client import OAuth +from starlette.responses import PlainTextResponse + +from auth.authorize import Authorize +from auth.identity import Identity + +from sensitive_settings import CLIENT_ID, CLIENT_SECRET + +oauth = OAuth() + +oauth.register( + name='facebook', + client_id=CLIENT_ID["FACEBOOK"], + client_secret=CLIENT_SECRET["FACEBOOK"], + access_token_url='https://graph.facebook.com/v11.0/oauth/access_token', + access_token_params=None, + authorize_url='https://www.facebook.com/v11.0/dialog/oauth', + authorize_params=None, + api_base_url='https://graph.facebook.com/', + client_kwargs={'scope': 'user:email'}, +) + +oauth.register( + name='github', + client_id=CLIENT_ID["GITHUB"], + client_secret=CLIENT_SECRET["GITHUB"], + access_token_url='https://github.com/login/oauth/access_token', + access_token_params=None, + authorize_url='https://github.com/login/oauth/authorize', + authorize_params=None, + api_base_url='https://api.github.com/', + client_kwargs={'scope': 'user:email'}, +) + +oauth.register( + name='google', + client_id=CLIENT_ID["GOOGLE"], + client_secret=CLIENT_SECRET["GOOGLE"], + access_token_url='https://oauth2.googleapis.com/token', + access_token_params=None, + authorize_url='https://accounts.google.com/o/oauth2/v2/auth', + authorize_params=None, + api_base_url='https://oauth2.googleapis.com/', + client_kwargs={'scope': 'openid email profile'} +) + +async def oauth_login(request): + provider = request.path_params['provider'] + request.session['provider'] = provider + client = oauth.create_client(provider) + redirect_uri = request.url_for('oauth_authorize') + return await client.authorize_redirect(request, redirect_uri) + +async def oauth_authorize(request): + provider = request.session['provider'] + client = oauth.create_client(provider) + token = await client.authorize_access_token(request) + resp = await client.get('user', token=token) + profile = resp.json() + oauth_id = profile["id"] + user_input = { + "oauth_id" : oauth_id, + "email" : profile["email"], + "username" : profile["name"] + } + user = Identity.identity_oauth(oauth_id=oauth_id, input=user_input) + token = await Authorize.authorize(user, device="pc", auto_delete=False) + return PlainTextResponse(token) diff --git a/create_crt.sh b/create_crt.sh new file mode 100644 index 00000000..ee5b19a8 --- /dev/null +++ b/create_crt.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +openssl req -newkey rsa:4096 \ + -x509 \ + -sha256 \ + -days 3650 \ + -nodes \ + -out discours.crt \ + -keyout discours.key \ + -subj "/C=RU/ST=Moscow/L=Moscow/O=Discours/OU=Site/CN=10.0.0.187" diff --git a/main.py b/main.py index b044f248..8c0de51b 100644 --- a/main.py +++ b/main.py @@ -1,28 +1,37 @@ -from importlib import import_module - -from ariadne import load_schema_from_path, make_executable_schema -from ariadne.asgi import GraphQL -from starlette.applications import Starlette -from starlette.middleware import Middleware -from starlette.middleware.authentication import AuthenticationMiddleware - -from auth.authenticate import JWTAuthenticate -from redis import redis -from resolvers.base import resolvers - -import_module('resolvers') -schema = make_executable_schema(load_schema_from_path("schema.graphql"), resolvers) - -middleware = [Middleware(AuthenticationMiddleware, backend=JWTAuthenticate())] - - -async def start_up(): - await redis.connect() - - -async def shutdown(): - await redis.disconnect() - - -app = Starlette(debug=True, on_startup=[start_up], on_shutdown=[shutdown], middleware=middleware) -app.mount("/", GraphQL(schema, debug=True)) +from importlib import import_module + +from ariadne import load_schema_from_path, make_executable_schema +from ariadne.asgi import GraphQL +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.authentication import AuthenticationMiddleware +from starlette.middleware.sessions import SessionMiddleware +from starlette.routing import Route + +from auth.authenticate import JWTAuthenticate +from auth.oauth import oauth_login, oauth_authorize +from redis import redis +from resolvers.base import resolvers + +import_module('resolvers') +schema = make_executable_schema(load_schema_from_path("schema.graphql"), resolvers) + +middleware = [ + Middleware(AuthenticationMiddleware, backend=JWTAuthenticate()), + Middleware(SessionMiddleware, secret_key="!secret") +] + +async def start_up(): + await redis.connect() + + +async def shutdown(): + await redis.disconnect() + +routes = [ + Route("/oauth/{provider}", endpoint=oauth_login), + Route("/authorize", endpoint=oauth_authorize) +] + +app = Starlette(debug=True, on_startup=[start_up], on_shutdown=[shutdown], middleware=middleware, routes=routes) +app.mount("/", GraphQL(schema, debug=True)) diff --git a/orm/__init__.py b/orm/__init__.py index b8d196e7..0a22acb3 100644 --- a/orm/__init__.py +++ b/orm/__init__.py @@ -1,8 +1,8 @@ -from orm.rbac import Operation, Permission, Role -from orm.user import User -from orm.message import Message -from orm.base import Base, engine - -__all__ = ["User", "Role", "Operation", "Permission", "Message"] - -Base.metadata.create_all(engine) +from orm.rbac import Operation, Permission, Role +from orm.user import User +from orm.message import Message +from orm.base import Base, engine + +__all__ = ["User", "Role", "Operation", "Permission", "Message"] + +Base.metadata.create_all(engine) diff --git a/orm/user.py b/orm/user.py index be6db8d2..4cf99f4b 100644 --- a/orm/user.py +++ b/orm/user.py @@ -11,9 +11,11 @@ class User(Base): email: str = Column(String, nullable=False) username: str = Column(String, nullable=False, comment="Name") - password: str = Column(String, nullable=False, comment="Password") + password: str = Column(String, nullable=True, comment="Password") role_id: int = Column(ForeignKey("role.id"), nullable=True, comment="Role") + + oauth_id: str = Column(String, nullable=True) @classmethod def get_permission(cls, user_id): diff --git a/requirements.txt b/requirements.txt index be76a4e9..f6f545f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ -aioredis -ariadne -pyjwt -starlette -sqlalchemy -uvicorn -pydantic +aioredis +ariadne +pyjwt +starlette +sqlalchemy +uvicorn +pydantic passlib \ No newline at end of file diff --git a/resolvers/__init__.py b/resolvers/__init__.py index 2aaf9432..10d9dfa0 100644 --- a/resolvers/__init__.py +++ b/resolvers/__init__.py @@ -1,14 +1,14 @@ -from resolvers.auth import sign_in, sign_out, register -from resolvers.inbox import create_message, delete_message, update_message, get_messages - -__all__ = [ - "sign_in", - "sign_out", - "register", - # TODO: "reset_password_code", - # TODO: "reset_password_confirm", - "create_message", - "delete_message", - "get_messages", - "update_messages" - ] +from resolvers.auth import sign_in, sign_out, register +from resolvers.inbox import create_message, delete_message, update_message, get_messages + +__all__ = [ + "sign_in", + "sign_out", + "register", + # TODO: "reset_password_code", + # TODO: "reset_password_confirm", + "create_message", + "delete_message", + "get_messages", + "update_messages" + ] diff --git a/resolvers/auth.py b/resolvers/auth.py index 1b1b38b8..cfc7f1a1 100644 --- a/resolvers/auth.py +++ b/resolvers/auth.py @@ -13,34 +13,34 @@ from settings import JWT_AUTH_HEADER @mutation.field("registerUser") async def register(*_, input: dict = None) -> User: - create_user = CreateUser(**input) - create_user.password = Password.encode(create_user.password) - return User.create(**create_user.dict()) + create_user = CreateUser(**input) + create_user.password = Password.encode(create_user.password) + return User.create(**create_user.dict()) @query.field("signIn") async def sign_in(_, info: GraphQLResolveInfo, id: int, password: str): - try: - device = info.context["request"].headers['device'] - except KeyError: - device = "pc" - auto_delete = False if device == "mobile" else True - user = Identity.identity(user_id=id, password=password) - token = await Authorize.authorize(user, device=device, auto_delete=auto_delete) - return {"status" : True, "token" : token} + try: + device = info.context["request"].headers['device'] + except KeyError: + device = "pc" + auto_delete = False if device == "mobile" else True + user = Identity.identity(user_id=id, password=password) + token = await Authorize.authorize(user, device=device, auto_delete=auto_delete) + return {"status" : True, "token" : token} @query.field("signOut") @login_required async def sign_out(_, info: GraphQLResolveInfo): - token = info.context["request"].headers[JWT_AUTH_HEADER] - status = await Authorize.revoke(token) - return {"status" : status} + token = info.context["request"].headers[JWT_AUTH_HEADER] + status = await Authorize.revoke(token) + return {"status" : status} #@query.field("getUser") #@login_required async def get_user(*_, id: int): - return global_session.query(User).filter(User.id == id).first() + return global_session.query(User).filter(User.id == id).first() diff --git a/resolvers/base.py b/resolvers/base.py index 4803faa4..3175f99e 100644 --- a/resolvers/base.py +++ b/resolvers/base.py @@ -1,6 +1,15 @@ -from ariadne import MutationType, QueryType +from ariadne import MutationType, QueryType, SubscriptionType, ScalarType query = QueryType() mutation = MutationType() +subscription = SubscriptionType() -resolvers = [query, mutation] + +datetime_scalar = ScalarType("DateTime") + +@datetime_scalar.serializer +def serialize_datetime(value): + return value.isoformat() + + +resolvers = [query, mutation, subscription, datetime_scalar] diff --git a/resolvers/inbox.py b/resolvers/inbox.py index c4b87735..c2e39255 100644 --- a/resolvers/inbox.py +++ b/resolvers/inbox.py @@ -1,10 +1,20 @@ from orm import Message, User from orm.base import global_session -from resolvers.base import mutation, query +from resolvers.base import mutation, query, subscription from auth.authenticate import login_required +import asyncio + + +class MessageQueue: + + new_message = asyncio.Queue() + updated_message = asyncio.Queue() + deleted_message = asyncio.Queue() + + @mutation.field("createMessage") @login_required async def create_message(_, info, input): @@ -17,6 +27,8 @@ async def create_message(_, info, input): replyTo = input.get("replyTo") ) + MessageQueue.new_message.put_nowait(new_message) + return { "status": True, "message" : new_message @@ -61,6 +73,8 @@ async def update_message(_, info, input): message.body = input["body"] global_session.commit() + MessageQueue.updated_message.put_nowait(message) + return { "status" : True, "message" : message @@ -83,6 +97,33 @@ async def delete_message(_, info, id): global_session.delete(message) global_session.commit() + MessageQueue.deleted_message.put_nowait(message) + return { "status" : True } + + +@subscription.source("messageCreated") +async def new_message_generator(obj, info): + while True: + new_message = await MessageQueue.new_message.get() + yield new_message + +@subscription.source("messageUpdated") +async def updated_message_generator(obj, info): + while True: + message = await MessageQueue.updated_message.get() + yield message + +@subscription.source("messageDeleted") +async def deleted_message_generator(obj, info): + while True: + message = await MessageQueue.deleted_message.get() + yield new_message + +@subscription.field("messageCreated") +@subscription.field("messageUpdated") +@subscription.field("messageDeleted") +def message_resolver(message, info): + return message diff --git a/schema.graphql b/schema.graphql index 0dc7093e..73e10313 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,167 +1,167 @@ -scalar DateTime - -input registerUserInput { - email: String! - username: String! - password: String! -} - -type signInPayload { - status: Boolean! - error: String - token: String -} - -type ResultPayload { - status: Boolean! - error: String -} - -type Like { - author: Int! - id: Int! - shout: Int - user: Int - value: Int! -} - -type createMessagePayload { - status: Boolean! - error: String - message: Message -} - -input MessageInput { - body: String! - replyTo: Int -} - -input updateMessageInput { - id: Int! - body: String! -} - -type Message { - author: Int! - body: String! - createdAt: DateTime! - id: Int! - replyTo: Int - updatedAt: DateTime! - visibleForUsers: [Int] -} - -type Mutation { - # message - createMessage(input: MessageInput!): createMessagePayload! - updateMessage(input: updateMessageInput!): createMessagePayload! - deleteMessage(messageId: Int!): ResultPayload! - - # auth - confirmEmail(token: String!): Token! - invalidateAllTokens: Boolean! - invalidateTokenById(id: Int!): Boolean! - requestEmailConfirmation: User! - requestPasswordReset(email: String!): Boolean! - resetPassword(password: String!, token: String!): Token! - signIn(email: String!, password: String!): Token! - # signUp(email: String!, password: String!, username: String): User! - registerUser(input: registerUserInput!): User! - - # shout - createShout(body: String!, replyTo: [Int], title: String, versionOf: [Int], visibleForRoles: [Int], visibleForUsers: [Int]): Message! - deleteShout(shoutId: Int!): Message! - rateShout(value: Int!): Boolean! - - # profile - rateUser(value: Int!): Boolean! - updateOnlineStatus: Boolean! - updateUsername(username: String!): User! -} - -type Query { - # auth / user - signIn(id: Int!, password: String!): signInPayload! - signOut: ResultPayload! - getCurrentUser: User! - getTokens: [Token!]! - isUsernameFree(username: String!): Boolean! - getOnline: [User!]! - getUserById(id: Int!): User! - getUserRating(shout: Int): Int! - - # messages - getMessages(count: Int = 100, page: Int = 1): [Message!]! - - # shouts - getShoutRating(shout: Int): Int! - shoutsByAuthor(author: Int): [Shout]! - shoutsByReplyTo(shout: Int): [Shout]! - shoutsByTags(tags: [String]): [Shout]! - shoutsByTime(time: DateTime): [Shout]! - topAuthors: [User]! - topShouts: [Shout]! -} - -type Role { - id: Int! - name: String! -} - -type Shout { - author: Int! - body: String! - createdAt: DateTime! - deletedAt: DateTime - deletedBy: Int - id: Int! - rating: Int - published: DateTime! # if there is no published field - it is not published - replyTo: Int # another shout - tags: [String] - title: String - updatedAt: DateTime! - versionOf: Int - visibleForRoles: [Role]! - visibleForUsers: [Int] -} - -type Proposal { - body: String! - shout: Int! - range: String # full / 0:2340 - author: Int! - createdAt: DateTime! -} - -type Subscription { - messageCreated: Message! - messageDeleted: Message! - onlineUpdated: [User!]! - shoutUpdated: Shout! - userUpdated: User! -} - -type Token { - createdAt: DateTime! - expiresAt: DateTime - id: Int! - ownerId: Int! - usedAt: DateTime - value: String! -} - -type User { - createdAt: DateTime! - email: String - emailConfirmed: Boolean - id: Int! - muted: Boolean - rating: Int - roles: [Role!]! - updatedAt: DateTime! - username: String - userpic: String - userpicId: String - wasOnlineAt: DateTime -} \ No newline at end of file +scalar DateTime + +input registerUserInput { + email: String! + username: String! + password: String! +} + +type signInPayload { + status: Boolean! + error: String + token: String +} + +type ResultPayload { + status: Boolean! + error: String +} + +type Like { + author: Int! + id: Int! + shout: Int + user: Int + value: Int! +} + +type createMessagePayload { + status: Boolean! + error: String + message: Message +} + +input MessageInput { + body: String! + replyTo: Int +} + +input updateMessageInput { + id: Int! + body: String! +} + +type Message { + author: Int! + body: String! + createdAt: DateTime! + id: Int! + replyTo: Int + updatedAt: DateTime! + visibleForUsers: [Int] +} + +type Mutation { + # message + createMessage(input: MessageInput!): createMessagePayload! + updateMessage(input: updateMessageInput!): createMessagePayload! + deleteMessage(messageId: Int!): ResultPayload! + + # auth + confirmEmail(token: String!): Token! + invalidateAllTokens: Boolean! + invalidateTokenById(id: Int!): Boolean! + requestEmailConfirmation: User! + requestPasswordReset(email: String!): Boolean! + resetPassword(password: String!, token: String!): Token! + registerUser(input: registerUserInput!): User! + + # shout + createShout(body: String!, replyTo: [Int], title: String, versionOf: [Int], visibleForRoles: [Int], visibleForUsers: [Int]): Message! + deleteShout(shoutId: Int!): Message! + rateShout(value: Int!): Boolean! + + # profile + rateUser(value: Int!): Boolean! + updateOnlineStatus: Boolean! + updateUsername(username: String!): User! +} + +type Query { + # auth / user + signIn(id: Int!, password: String!): signInPayload! + signOut: ResultPayload! + getCurrentUser: User! + getTokens: [Token!]! + isUsernameFree(username: String!): Boolean! + getOnline: [User!]! + getUserById(id: Int!): User! + getUserRating(shout: Int): Int! + + # messages + getMessages(count: Int = 100, page: Int = 1): [Message!]! + + # shouts + getShoutRating(shout: Int): Int! + shoutsByAuthor(author: Int): [Shout]! + shoutsByReplyTo(shout: Int): [Shout]! + shoutsByTags(tags: [String]): [Shout]! + shoutsByTime(time: DateTime): [Shout]! + topAuthors: [User]! + topShouts: [Shout]! +} + +type Role { + id: Int! + name: String! +} + +type Shout { + author: Int! + body: String! + createdAt: DateTime! + deletedAt: DateTime + deletedBy: Int + id: Int! + rating: Int + published: DateTime! # if there is no published field - it is not published + replyTo: Int # another shout + tags: [String] + title: String + updatedAt: DateTime! + versionOf: Int + visibleForRoles: [Role]! + visibleForUsers: [Int] +} + +type Proposal { + body: String! + shout: Int! + range: String # full / 0:2340 + author: Int! + createdAt: DateTime! +} + +type Subscription { + messageCreated: Message! + messageUpdated: Message! + messageDeleted: Message! + + onlineUpdated: [User!]! + shoutUpdated: Shout! + userUpdated: User! +} + +type Token { + createdAt: DateTime! + expiresAt: DateTime + id: Int! + ownerId: Int! + usedAt: DateTime + value: String! +} + +type User { + createdAt: DateTime! + email: String + emailConfirmed: Boolean + id: Int! + muted: Boolean + rating: Int + roles: [Role!]! + updatedAt: DateTime! + username: String + userpic: String + userpicId: String + wasOnlineAt: DateTime +} diff --git a/server.py b/server.py index fcee596f..969330cb 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,5 @@ -import uvicorn -from settings import PORT - -if __name__ == '__main__': - uvicorn.run("main:app", host="0.0.0.0", port=PORT, reload=True) +import uvicorn +from settings import PORT + +if __name__ == '__main__': + uvicorn.run("main:app", host="0.0.0.0", port=PORT, ssl_keyfile="discours.key", ssl_certfile="discours.crt", reload=True) diff --git a/settings.py b/settings.py index 54eef00f..84bd4eaa 100644 --- a/settings.py +++ b/settings.py @@ -1,10 +1,10 @@ -from pathlib import Path - -PORT = 24579 - -SQLITE_URI = Path(__file__).parent / "database.sqlite3" -JWT_ALGORITHM = "HS256" -JWT_SECRET_KEY = "8f1bd7696ffb482d8486dfbc6e7d16dd-secret-key" -JWT_LIFE_SPAN = 24 * 60 * 60 # seconds -JWT_AUTH_HEADER = "Auth" -REDIS_URL = "redis://127.0.0.1" +from pathlib import Path + +PORT = 8081 + +SQLITE_URI = Path(__file__).parent / "database.sqlite3" +JWT_ALGORITHM = "HS256" +JWT_SECRET_KEY = "8f1bd7696ffb482d8486dfbc6e7d16dd-secret-key" +JWT_LIFE_SPAN = 24 * 60 * 60 # seconds +JWT_AUTH_HEADER = "Auth" +REDIS_URL = "redis://127.0.0.1"