From 5a06a625fbf0d29a3eed6b75ae5b573eb784b741 Mon Sep 17 00:00:00 2001 From: Timofey Date: Fri, 29 Aug 2025 14:44:26 +0300 Subject: [PATCH] login route --- backend/Pipfile | 1 + backend/Pipfile.lock | 287 +++++++++++++++++- backend/api/auth/serializers.py | 62 ++++ backend/api/auth/urls.py | 28 +- backend/api/auth/views.py | 167 ++++++++++ backend/api/models.py | 55 +++- backend/api/types.py | 9 + backend/api/utils/cookies.py | 65 ++++ backend/base/settings.py | 12 + backend/requirements.txt | 1 + .../sitemanagement/constants/account_types.py | 19 ++ 11 files changed, 701 insertions(+), 5 deletions(-) create mode 100644 backend/api/types.py create mode 100644 backend/api/utils/cookies.py create mode 100644 backend/sitemanagement/constants/account_types.py diff --git a/backend/Pipfile b/backend/Pipfile index 1eb723b..7510ff1 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -15,6 +15,7 @@ djangorestframework-simplejwt = "*" pycodestyle = "*" requests = "*" pyjwt = "*" +drf-spectacular = "*" [dev-packages] diff --git a/backend/Pipfile.lock b/backend/Pipfile.lock index 37338d1..b075d50 100644 --- a/backend/Pipfile.lock +++ b/backend/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "952a50ce3f529481ea1b021ac88ac52462e1b6b7ace31e1f03125e6eca1b281e" + "sha256": "c8a08d8710d5b37141e66db971439bf41996f2ea1330f2d5716f8be2a841a796" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,14 @@ "markers": "python_version >= '3.9'", "version": "==3.9.1" }, + "attrs": { + "hashes": [ + "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", + "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b" + ], + "markers": "python_version >= '3.8'", + "version": "==25.3.0" + }, "certifi": { "hashes": [ "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", @@ -160,6 +168,15 @@ "index": "pypi", "version": "==0.9.9" }, + "drf-spectacular": { + "hashes": [ + "sha256:2c778a47a40ab2f5078a7c42e82baba07397bb35b074ae4680721b2805943061", + "sha256:856e7edf1056e49a4245e87a61e8da4baff46c83dbc25be1da2df77f354c7cb4" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.28.0" + }, "idna": { "hashes": [ "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", @@ -168,6 +185,14 @@ "markers": "python_version >= '3.6'", "version": "==3.10" }, + "inflection": { + "hashes": [ + "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", + "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" + ], + "markers": "python_version >= '3.5'", + "version": "==0.5.1" + }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -176,6 +201,22 @@ "markers": "python_version >= '3.8'", "version": "==2.1.0" }, + "jsonschema": { + "hashes": [ + "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", + "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85" + ], + "markers": "python_version >= '3.9'", + "version": "==4.25.1" + }, + "jsonschema-specifications": { + "hashes": [ + "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", + "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608" + ], + "markers": "python_version >= '3.9'", + "version": "==2025.4.1" + }, "packaging": { "hashes": [ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", @@ -319,6 +360,73 @@ "markers": "python_version >= '3.9'", "version": "==1.1.1" }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, + "referencing": { + "hashes": [ + "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", + "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0" + ], + "markers": "python_version >= '3.9'", + "version": "==0.36.2" + }, "requests": { "hashes": [ "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", @@ -328,6 +436,167 @@ "markers": "python_version >= '3.9'", "version": "==2.32.5" }, + "rpds-py": { + "hashes": [ + "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400", + "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1", + "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e", + "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", + "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", + "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", + "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", + "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", + "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", + "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", + "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", + "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", + "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", + "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", + "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", + "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d", + "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", + "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", + "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", + "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", + "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c", + "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", + "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", + "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905", + "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", + "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", + "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", + "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", + "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", + "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", + "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", + "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", + "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", + "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", + "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", + "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", + "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", + "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", + "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", + "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", + "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", + "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", + "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786", + "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", + "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", + "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859", + "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", + "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125", + "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", + "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", + "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", + "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", + "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", + "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", + "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", + "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", + "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", + "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", + "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", + "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", + "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", + "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3", + "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab", + "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", + "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6", + "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", + "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", + "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", + "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", + "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4", + "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", + "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", + "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8", + "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", + "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", + "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", + "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", + "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5", + "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", + "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", + "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", + "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", + "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", + "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", + "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", + "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", + "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", + "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52", + "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", + "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1", + "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f", + "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", + "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485", + "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", + "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", + "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", + "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475", + "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", + "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", + "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", + "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", + "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", + "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", + "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", + "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", + "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", + "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", + "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8", + "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", + "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", + "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", + "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc", + "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", + "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e", + "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", + "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527", + "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", + "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", + "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", + "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", + "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", + "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", + "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", + "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", + "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", + "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", + "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", + "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", + "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", + "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", + "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", + "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec", + "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3", + "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", + "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", + "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", + "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", + "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", + "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", + "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", + "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", + "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", + "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", + "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", + "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", + "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b", + "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", + "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", + "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", + "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", + "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", + "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", + "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", + "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", + "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21" + ], + "markers": "python_version >= '3.9'", + "version": "==0.27.1" + }, "sqlparse": { "hashes": [ "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", @@ -336,6 +605,22 @@ "markers": "python_version >= '3.8'", "version": "==0.5.3" }, + "typing-extensions": { + "hashes": [ + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + ], + "markers": "python_version >= '3.9'", + "version": "==4.15.0" + }, + "uritemplate": { + "hashes": [ + "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", + "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686" + ], + "markers": "python_version >= '3.9'", + "version": "==4.2.0" + }, "urllib3": { "hashes": [ "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", diff --git a/backend/api/auth/serializers.py b/backend/api/auth/serializers.py index e69de29..63c1962 100644 --- a/backend/api/auth/serializers.py +++ b/backend/api/auth/serializers.py @@ -0,0 +1,62 @@ +from typing import Any, Optional +from rest_framework import serializers +from django.conf import settings +from api.types import User + +class UserResponseSerializer(serializers.Serializer): + id = serializers.IntegerField(read_only=True) + email = serializers.EmailField(read_only=True) + account_type = serializers.CharField( + source='userprofile.account_type', + read_only=True + ) + name = serializers.CharField(source='first_name', read_only=True) + surname = serializers.CharField(source='last_name', read_only=True) + imageURL = serializers.SerializerMethodField() + uuid = serializers.SerializerMethodField() + + class Meta: + ref_name = "UserResponse" # для OpenAPI + + def get_uuid(self, obj: User) -> Optional[str]: + """Получает короткий UUID (первые 6 символов) из профиля пользователя""" + return obj.userprofile.short_uuid if hasattr(obj, 'userprofile') else None + + def get_imageURL(self, obj: User) -> Optional[str]: + """Получает полный URL для изображения профиля пользователя""" + try: + if not hasattr(obj, 'userprofile') or not obj.userprofile.imageURL: + return None + + relative_url = obj.userprofile.imageURL.lstrip('/') + base_url = settings.BASE_URL.rstrip('/') + return f"{base_url}/{relative_url}" + except Exception: + return None + + def to_representation(self, instance: User) -> dict[str, Any]: + """Переопределяется для добавления проверки типа для вывода""" + data = super().to_representation(instance) + return { + 'id': data['id'], # int + 'email': data['email'], # str + 'account_type': data['account_type'], # AccountTypeLiteral + 'name': data['name'], # str + 'surname': data['surname'], # str + 'imageURL': data['imageURL'], # Optional[str] + 'uuid': data['uuid'], # Optional[str] + } + + +class LoginRequestSerializer(serializers.Serializer): + """Сериализатор для запроса авторизации""" + login = serializers.CharField(help_text="Логин пользователя") + password = serializers.CharField(help_text="Пароль пользователя", write_only=True) + + +class LoginResponseSerializer(serializers.Serializer): + """Сериализатор для ответа при успешной авторизации""" + message = serializers.CharField() + access = serializers.CharField() + refresh = serializers.CharField() + user = UserResponseSerializer() \ No newline at end of file diff --git a/backend/api/auth/urls.py b/backend/api/auth/urls.py index 8cd93a9..8862821 100644 --- a/backend/api/auth/urls.py +++ b/backend/api/auth/urls.py @@ -1,6 +1,28 @@ -from django.urls import path -# from . views import () +from django.urls import path, include +from .views import LoginViewSet, LogoutView +from rest_framework.routers import DefaultRouter +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularSwaggerView, + SpectacularRedocView, +) + +router = DefaultRouter() +router.register(r'', LoginViewSet, basename='auth') urlpatterns = [ - + path('', include(router.urls)), + path('logout/', LogoutView.as_view(), name='auth-logout'), + path('schema/', SpectacularAPIView.as_view(), name='schema'), + path( + 'docs/', + SpectacularSwaggerView.as_view(url_name='schema'), + name='swagger-ui', + ), + # ReDoc UI - альтернативный вариант отображения доков: + path( + 'redoc/', + SpectacularRedocView.as_view(url_name='schema'), + name='redoc', + ), ] \ No newline at end of file diff --git a/backend/api/auth/views.py b/backend/api/auth/views.py index e69de29..a04b351 100644 --- a/backend/api/auth/views.py +++ b/backend/api/auth/views.py @@ -0,0 +1,167 @@ +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework_simplejwt.tokens import RefreshToken +from rest_framework.views import APIView +from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiExample +from drf_spectacular.types import OpenApiTypes + +from django.contrib.auth.models import User + +from .serializers import ( + UserResponseSerializer, + LoginRequestSerializer, + LoginResponseSerializer +) + +from api.utils.cookies import AuthBaseViewSet +from api.types import User + + +class LoginViewSet(AuthBaseViewSet): + """ViewSet для авторизации пользователей""" + serializer_class = LoginRequestSerializer + + @extend_schema( + summary="Авторизация пользователя", + description="Эндпоинт для авторизации пользователя по логину и паролю", + request=LoginRequestSerializer, + responses={ + 200: OpenApiResponse( + response=LoginResponseSerializer, + description="Успешная авторизация", + examples=[ + OpenApiExample( + 'Успешный ответ', + value={ + "message": "Успешная авторизация", + "access": "eyJ0eXAiOiJKV1QiLCJhbGc...", + "refresh": "eyJ0eXAiOiJKV1QiLCJhbGc...", + "user": { + "id": 1, + "email": "user@example.com", + "account_type": "engieneer", + "name": "Иван", + "surname": "Иванов", + "imageURL": "https://example.com/avatar.jpg", + "uuid": "abc123" + } + } + ) + ] + ), + 400: OpenApiResponse( + description="Неверные параметры запроса", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Отсутствуют обязательные поля', + value={"error": "Логин и пароль обязательны"} + ) + ] + ), + 403: OpenApiResponse( + description="Неверный пароль", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Неверный пароль', + value={"error": "Неверный пароль"} + ) + ] + ), + 404: OpenApiResponse( + description="Пользователь не найден", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Пользователь не найден', + value={"error": "Пользователь не найден"} + ) + ] + ), + 500: OpenApiResponse( + description="Внутренняя ошибка сервера", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Ошибка сервера', + value={"error": "Ошибка авторизации"} + ) + ] + ) + } + ) + @action(detail=False, methods=['post'], url_path="login") + def login_client(self, request): + try: + login = request.data.get("login") + password = request.data.get("password") + + if not login or not password: + return Response( + {"error": "Логин и пароль обязательны"}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + user = User.objects.get(login=login) + except User.DoesNotExist: + return Response( + {"error": "Пользователь не найден"}, + status=status.HTTP_404_NOT_FOUND + ) + if not user.check_password(password): + return Response( + {"error": "Неверный пароль"}, + status=status.HTTP_403_FORBIDDEN + ) + + refresh = RefreshToken.for_user(user) + + user_data = UserResponseSerializer(user).data + + response = Response({ + "message": "Успешная авторизация", + "access": str(refresh.access_token), + "refresh": str(refresh), + "user": user_data + }, status=status.HTTP_200_OK) + + # сеттим куки + return self._set_auth_cookies(response, refresh) + + except Exception as e: + return Response( + {"error": "Ошибка авторизации"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + +class LogoutView(APIView): + """ViewSet для выхода из системы""" + + @extend_schema( + summary="Выход из системы", + description="Эндпоинт для выхода из системы, очищает все токены и куки", + responses={ + 200: OpenApiResponse( + description="Успешный выход", + response=OpenApiTypes.OBJECT, + examples=[ + OpenApiExample( + 'Успешный выход', + value={"message": "Logged out"} + ) + ] + ) + } + ) + def post(self, request): + response = Response({'message': 'Logged out'}, status=status.HTTP_200_OK) + + # чистим куки и sessionID + response.delete_cookie('access_token') + response.delete_cookie('refresh_token') + response.delete_cookie('sessionid') + + return response \ No newline at end of file diff --git a/backend/api/models.py b/backend/api/models.py index 71a8362..60ac79d 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -1,3 +1,56 @@ +from django.contrib.auth.models import User from django.db import models +from django.db.models.fields.related import OneToOneField +from typing import Optional +import uuid -# Create your models here. +from sitemanagement.constants.account_types import account_types, AccountType, AccountTypeLiteral + +class UserProfile(models.Model): + """ + Профиль пользователя с дополнительной информацией + """ + + def get_account_type_display(self) -> str: + """Автоматически добавляется Django для полей с choices""" + ... + + user: OneToOneField[User] = models.OneToOneField( + User, + on_delete=models.CASCADE, + related_name='userprofile' + ) + uuid: models.UUIDField = models.UUIDField( + default=uuid.uuid4, + editable=False, + unique=True, + null=True + ) + account_type: models.CharField = models.CharField( + max_length=10, + verbose_name="Тип аккаунта", + choices=account_types, + db_index=True + ) + imageURL: models.CharField = models.CharField( + max_length=255, + null=True, + blank=True, + verbose_name="URL изображения профиля" + ) + + class Meta: + verbose_name = "Профиль пользователя" + verbose_name_plural = "Профили пользователей" + + def __str__(self) -> str: + return f"{self.user.first_name} ({self.get_account_type_display()})" + + @property + def short_uuid(self) -> Optional[str]: + """Возвращает первые 6 символов UUID или None, если UUID не установлен""" + return str(self.uuid)[:6] if self.uuid else None + + def get_account_type(self) -> AccountTypeLiteral: + """Возвращает тип аккаунта пользователя""" + return AccountType(self.account_type).value \ No newline at end of file diff --git a/backend/api/types.py b/backend/api/types.py new file mode 100644 index 0000000..0b3c811 --- /dev/null +++ b/backend/api/types.py @@ -0,0 +1,9 @@ +from typing import TYPE_CHECKING +from django.contrib.auth.models import User as DjangoUser + +if TYPE_CHECKING: + from .models import UserProfile + +class User(DjangoUser): + """Тип для Django User с кастомной моделью UserProfile""" + userprofile: 'UserProfile' diff --git a/backend/api/utils/cookies.py b/backend/api/utils/cookies.py new file mode 100644 index 0000000..2eab99f --- /dev/null +++ b/backend/api/utils/cookies.py @@ -0,0 +1,65 @@ +from rest_framework.viewsets import ViewSet +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework import status +from datetime import datetime +from django.conf import settings +from rest_framework_simplejwt.tokens import RefreshToken + +class AuthBaseViewSet(ViewSet): + """Базовый класс для аутентификации с общими методами""" + + def _set_auth_cookies(self, response, refresh): + """Устанавливает куки для токенов аутентификации""" + response.set_cookie( + 'access_token', + str(refresh.access_token), + httponly=True, + secure=True, + samesite='Lax', + max_age=300 + ) + response.set_cookie( + 'refresh_token', + str(refresh), + httponly=True, + secure=True, + samesite='Lax', + max_age=86400 + ) + return response + + @action(detail=False, methods=['post'], url_path="refresh") + def refresh_token(self, request): + try: + refresh_token = request.data.get('refresh') + + if not refresh_token: + return Response( + {'error': 'Refresh token is required'}, + status=status.HTTP_400_BAD_REQUEST + ) + + try: + token = RefreshToken(refresh_token) + response_data = { + 'access': str(token.access_token), + 'refresh': str(token), + 'expires_at': datetime.timestamp( + datetime.now() + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] + ) + } + + return Response(response_data) + + except Exception as e: + return Response( + {'error': f'Invalid refresh token: {str(e)}'}, + status=status.HTTP_400_BAD_REQUEST + ) + + except Exception as e: + return Response( + {'error': f'Token refresh failed: {str(e)}'}, + status=status.HTTP_400_BAD_REQUEST + ) diff --git a/backend/base/settings.py b/backend/base/settings.py index 5f03605..080608d 100644 --- a/backend/base/settings.py +++ b/backend/base/settings.py @@ -39,6 +39,7 @@ REST_FRAMEWORK = { 'rest_framework_simplejwt.authentication.JWTAuthentication', 'rest_framework.authentication.SessionAuthentication', ], + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', } @@ -66,8 +67,19 @@ INSTALLED_APPS = [ 'api.apps.ApiConfig', 'sitemanagement.apps.SitemanagementConfig', 'rest_framework', + 'drf_spectacular', ] +# Настройки OpenAPI +SPECTACULAR_SETTINGS = { + 'TITLE': 'AERBIM API', + 'DESCRIPTION': 'API для работы с AERBIM', + 'VERSION': '1.0.0', + 'SERVE_INCLUDE_SCHEMA': False, + 'COMPONENT_SPLIT_REQUEST': True, + 'SCHEMA_PATH_PREFIX': '/api/v[0-9]', +} + MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', diff --git a/backend/requirements.txt b/backend/requirements.txt index d509463..fb476c5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -20,3 +20,4 @@ python-dotenv==1.1.1 requests==2.32.5 sqlparse==0.5.3 urllib3==2.5.0 +drf-spectacular==0.27.1 diff --git a/backend/sitemanagement/constants/account_types.py b/backend/sitemanagement/constants/account_types.py new file mode 100644 index 0000000..239bd10 --- /dev/null +++ b/backend/sitemanagement/constants/account_types.py @@ -0,0 +1,19 @@ +from typing import Literal, Tuple, List +from enum import Enum + +class AccountType(str, Enum): + ENGINEER = "engineer" + OPERATOR = "operator" + ADMIN = "admin" + + @classmethod + def choices(cls) -> List[Tuple[str, str]]: + return [ + (cls.ENGINEER.value, "Инженер"), + (cls.OPERATOR.value, "Оператор"), + (cls.ADMIN.value, "Администратор"), + ] + +AccountTypeLiteral = Literal["engineer", "operator", "admin"] + +account_types = AccountType.choices() \ No newline at end of file