mirror of
https://github.com/n08i40k/schedule-parser-rusted.git
synced 2025-12-06 09:47:50 +03:00
0.4.0
Авторизация через токен вк Слияние schedule_parser с проектом Перенос схемы запросов/ответов в файлы эндпоинтов Переход с библиотеки jwt на jsonwebtokens
This commit is contained in:
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Create .env.test
|
- name: Create .env.test
|
||||||
run: touch .env.test
|
run: touch .env.test
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test -p schedule-parser-rusted -p schedule_parser
|
run: cargo test
|
||||||
env:
|
env:
|
||||||
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
|
||||||
JWT_SECRET: "test-secret-at-least-256-bits-used"
|
JWT_SECRET: "test-secret-at-least-256-bits-used"
|
||||||
14
.idea/discord.xml
generated
Normal file
14
.idea/discord.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="PROJECT_FILES" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
<option name="applicationTheme" value="default" />
|
||||||
|
<option name="iconsTheme" value="default" />
|
||||||
|
<option name="button1Title" value="" />
|
||||||
|
<option name="button1Url" value="" />
|
||||||
|
<option name="button2Title" value="" />
|
||||||
|
<option name="button2Url" value="" />
|
||||||
|
<option name="customApplicationId" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
2
.idea/schedule-parser-rusted.iml
generated
2
.idea/schedule-parser-rusted.iml
generated
@@ -2,9 +2,9 @@
|
|||||||
<module type="EMPTY_MODULE" version="4">
|
<module type="EMPTY_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/lib/schedule_parser/benches" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/lib/schedule_parser/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/lib/schedule_parser/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|||||||
230
Cargo.lock
generated
230
Cargo.lock
generated
@@ -29,7 +29,7 @@ dependencies = [
|
|||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"base64 0.22.1",
|
"base64",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -287,12 +287,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.13.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -305,7 +299,7 @@ version = "0.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f"
|
checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64",
|
||||||
"blowfish",
|
"blowfish",
|
||||||
"getrandom 0.3.2",
|
"getrandom 0.3.2",
|
||||||
"subtle",
|
"subtle",
|
||||||
@@ -327,6 +321,15 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.11.0-rc.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blowfish"
|
name = "blowfish"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -477,7 +480,7 @@ version = "0.4.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-common",
|
"crypto-common 0.1.6",
|
||||||
"inout",
|
"inout",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -515,6 +518,12 @@ dependencies = [
|
|||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-oid"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cb3c4a0d3776f7535c32793be81d6d5fec0d48ac70955d9834e643aa249a52f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.16.2"
|
version = "0.16.2"
|
||||||
@@ -637,6 +646,15 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.2.0-rc.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "170d71b5b14dec99db7739f6fc7d6ec2db80b78c3acb77db48392ccc3d8a9ea0"
|
||||||
|
dependencies = [
|
||||||
|
"hybrid-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.10"
|
version = "0.20.10"
|
||||||
@@ -679,6 +697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -765,9 +784,19 @@ version = "0.10.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer 0.10.4",
|
||||||
"crypto-common",
|
"crypto-common 0.1.6",
|
||||||
"subtle",
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.11.0-pre.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer 0.11.0-rc.4",
|
||||||
|
"const-oid",
|
||||||
|
"crypto-common 0.2.0-rc.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -961,8 +990,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -995,7 +1026,7 @@ dependencies = [
|
|||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
"indexmap",
|
"indexmap 2.8.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -1014,7 +1045,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"indexmap",
|
"indexmap 2.8.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -1031,6 +1062,12 @@ dependencies = [
|
|||||||
"crunchy",
|
"crunchy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
@@ -1050,13 +1087,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
|
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hex"
|
||||||
version = "0.12.1"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
dependencies = [
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hostname"
|
name = "hostname"
|
||||||
@@ -1125,6 +1159,15 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hybrid-array"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4dab50e193aebe510fe0e40230145820e02f48dae0cf339ea4204e6e708ff7bd"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@@ -1371,6 +1414,17 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown 0.12.3",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -1378,7 +1432,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.15.2",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1442,18 +1497,18 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jwt"
|
name = "jsonwebtoken"
|
||||||
version = "0.16.0"
|
version = "9.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
|
checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.1",
|
"base64",
|
||||||
"crypto-common",
|
"js-sys",
|
||||||
"digest",
|
"pem",
|
||||||
"hmac",
|
"ring",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"simple_asn1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1575,12 +1630,31 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -1694,6 +1768,16 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@@ -1958,7 +2042,7 @@ version = "0.12.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -2119,38 +2203,30 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schedule-parser-rusted"
|
name = "schedule-parser-rusted"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-http",
|
"actix-http",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"chrono",
|
|
||||||
"diesel",
|
|
||||||
"diesel-derive-enum",
|
|
||||||
"dotenvy",
|
|
||||||
"hmac",
|
|
||||||
"jwt",
|
|
||||||
"mime",
|
|
||||||
"objectid",
|
|
||||||
"reqwest",
|
|
||||||
"schedule_parser",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schedule_parser"
|
|
||||||
version = "0.2.0"
|
|
||||||
dependencies = [
|
|
||||||
"calamine",
|
"calamine",
|
||||||
"chrono",
|
"chrono",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"diesel",
|
||||||
|
"diesel-derive-enum",
|
||||||
|
"dotenvy",
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
|
"jsonwebtoken",
|
||||||
|
"mime",
|
||||||
|
"objectid",
|
||||||
|
"rand 0.9.0",
|
||||||
"regex",
|
"regex",
|
||||||
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
|
"serde_with",
|
||||||
|
"sha2",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2237,6 +2313,36 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with"
|
||||||
|
version = "3.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"chrono",
|
||||||
|
"hex",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"indexmap 2.8.0",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"serde_with_macros",
|
||||||
|
"time 0.3.40",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with_macros"
|
||||||
|
version = "3.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -2245,18 +2351,18 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.8"
|
version = "0.11.0-pre.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
checksum = "19b4241d1a56954dce82cecda5c8e9c794eef6f53abe5e5216bac0a0ea71ffa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"digest",
|
"digest 0.11.0-pre.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2280,6 +2386,18 @@ version = "0.3.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror",
|
||||||
|
"time 0.3.40",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -3142,7 +3260,7 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"flate2",
|
"flate2",
|
||||||
"indexmap",
|
"indexmap 2.8.0",
|
||||||
"memchr",
|
"memchr",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"zopfli",
|
"zopfli",
|
||||||
|
|||||||
28
Cargo.toml
28
Cargo.toml
@@ -1,27 +1,35 @@
|
|||||||
[workspace]
|
|
||||||
members = ["lib/schedule_parser"]
|
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "schedule-parser-rusted"
|
name = "schedule-parser-rusted"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-http = "3.10.0"
|
|
||||||
actix-web = "4.10.2"
|
actix-web = "4.10.2"
|
||||||
bcrypt = "0.17.0"
|
bcrypt = "0.17.0"
|
||||||
chrono = "0.4.40"
|
calamine = "0.26.1"
|
||||||
|
chrono = { version = "0.4.40", features = ["serde"] }
|
||||||
diesel = { version = "2.2.8", features = ["postgres"] }
|
diesel = { version = "2.2.8", features = ["postgres"] }
|
||||||
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
|
diesel-derive-enum = { git = "https://github.com/Havunen/diesel-derive-enum.git", features = ["postgres"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
hmac = "0.12.1"
|
fuzzy-matcher = "0.3.7"
|
||||||
jwt = "0.16.0"
|
jsonwebtoken = { version = "9.3.1", features = ["use_pem"] }
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
objectid = "0.2.0"
|
objectid = "0.2.0"
|
||||||
|
regex = "1.11.1"
|
||||||
reqwest = "0.12.15"
|
reqwest = "0.12.15"
|
||||||
schedule_parser = { path = "./lib/schedule_parser" }
|
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
sha2 = "0.10.8"
|
serde_with = "3.12.0"
|
||||||
|
serde_repr = "0.1.20"
|
||||||
|
sha2 = "0.11.0-pre.5"
|
||||||
tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
rand = "0.9.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
actix-http = "3.10.0"
|
||||||
|
criterion = "0.5.1"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "parse"
|
||||||
|
harness = false
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use criterion::{Criterion, criterion_group, criterion_main};
|
use criterion::{Criterion, criterion_group, criterion_main};
|
||||||
use schedule_parser::parse_xls;
|
|
||||||
|
use schedule_parser_rusted::parser::parse_xls;
|
||||||
|
|
||||||
pub fn bench_parse_xls(c: &mut Criterion) {
|
pub fn bench_parse_xls(c: &mut Criterion) {
|
||||||
let buffer: Vec<u8> = include_bytes!("../../../schedule.xls").to_vec();
|
let buffer: Vec<u8> = include_bytes!("../schedule.xls").to_vec();
|
||||||
|
|
||||||
c.bench_function("parse_xls", |b| b.iter(|| parse_xls(&buffer)));
|
c.bench_function("parse_xls", |b| b.iter(|| parse_xls(&buffer)));
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "schedule_parser"
|
|
||||||
version = "0.2.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "schedule_parser"
|
|
||||||
path = "src/lib/lib.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
|
||||||
serde_repr = "0.1.20"
|
|
||||||
chrono = { version = "0.4.40", features = ["serde"] }
|
|
||||||
calamine = "0.26.1"
|
|
||||||
regex = "1.11.1"
|
|
||||||
fuzzy-matcher = "0.3.7"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
criterion = "0.5.1"
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "parse"
|
|
||||||
harness = false
|
|
||||||
@@ -2,9 +2,9 @@ use crate::xls_downloader::basic_impl::BasicXlsDownloader;
|
|||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::{Connection, PgConnection};
|
use diesel::{Connection, PgConnection};
|
||||||
use schedule_parser::schema::ParseResult;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
use crate::parser::schema::ParseResult;
|
||||||
|
|
||||||
pub struct Schedule {
|
pub struct Schedule {
|
||||||
pub etag: String,
|
pub etag: String,
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ pub mod users {
|
|||||||
use crate::database::models::User;
|
use crate::database::models::User;
|
||||||
use crate::database::schema::users::dsl::users;
|
use crate::database::schema::users::dsl::users;
|
||||||
use crate::database::schema::users::dsl::*;
|
use crate::database::schema::users::dsl::*;
|
||||||
use diesel::{insert_into, ExpressionMethods, QueryResult};
|
use diesel::{ExpressionMethods, QueryResult, insert_into};
|
||||||
use diesel::{PgConnection, SelectableHelper};
|
use diesel::{PgConnection, SelectableHelper};
|
||||||
use diesel::{QueryDsl, RunQueryDsl};
|
use diesel::{QueryDsl, RunQueryDsl};
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
pub fn get(connection: &Mutex<PgConnection>, _id: String) -> QueryResult<User> {
|
pub fn get(connection: &Mutex<PgConnection>, _id: &String) -> QueryResult<User> {
|
||||||
let mut lock = connection.lock().unwrap();
|
let mut lock = connection.lock().unwrap();
|
||||||
let con = lock.deref_mut();
|
let con = lock.deref_mut();
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ pub mod users {
|
|||||||
|
|
||||||
pub fn get_by_username(
|
pub fn get_by_username(
|
||||||
connection: &Mutex<PgConnection>,
|
connection: &Mutex<PgConnection>,
|
||||||
_username: String,
|
_username: &String,
|
||||||
) -> QueryResult<User> {
|
) -> QueryResult<User> {
|
||||||
let mut lock = connection.lock().unwrap();
|
let mut lock = connection.lock().unwrap();
|
||||||
let con = lock.deref_mut();
|
let con = lock.deref_mut();
|
||||||
@@ -30,8 +30,21 @@ pub mod users {
|
|||||||
.select(User::as_select())
|
.select(User::as_select())
|
||||||
.first(con)
|
.first(con)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_by_vk_id(
|
||||||
|
connection: &Mutex<PgConnection>,
|
||||||
|
_vk_id: i32,
|
||||||
|
) -> QueryResult<User> {
|
||||||
|
let mut lock = connection.lock().unwrap();
|
||||||
|
let con = lock.deref_mut();
|
||||||
|
|
||||||
pub fn contains_by_username(connection: &Mutex<PgConnection>, _username: String) -> bool {
|
users
|
||||||
|
.filter(vk_id.eq(_vk_id))
|
||||||
|
.select(User::as_select())
|
||||||
|
.first(con)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_by_username(connection: &Mutex<PgConnection>, _username: &String) -> bool {
|
||||||
let mut lock = connection.lock().unwrap();
|
let mut lock = connection.lock().unwrap();
|
||||||
let con = lock.deref_mut();
|
let con = lock.deref_mut();
|
||||||
|
|
||||||
@@ -45,7 +58,21 @@ pub mod users {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_by_username(connection: &Mutex<PgConnection>, _username: String) -> bool {
|
pub fn contains_by_vk_id(connection: &Mutex<PgConnection>, _vk_id: i32) -> bool {
|
||||||
|
let mut lock = connection.lock().unwrap();
|
||||||
|
let con = lock.deref_mut();
|
||||||
|
|
||||||
|
match users
|
||||||
|
.filter(vk_id.eq(_vk_id))
|
||||||
|
.count()
|
||||||
|
.get_result::<i64>(con)
|
||||||
|
{
|
||||||
|
Ok(count) => count > 0,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_by_username(connection: &Mutex<PgConnection>, _username: &String) -> bool {
|
||||||
let mut lock = connection.lock().unwrap();
|
let mut lock = connection.lock().unwrap();
|
||||||
let con = lock.deref_mut();
|
let con = lock.deref_mut();
|
||||||
|
|
||||||
@@ -61,11 +88,14 @@ pub mod users {
|
|||||||
|
|
||||||
insert_into(users).values(user).execute(con)
|
insert_into(users).values(user).execute(con)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_or_ignore(connection: &Mutex<PgConnection>, user: &User) -> QueryResult<usize> {
|
pub fn insert_or_ignore(connection: &Mutex<PgConnection>, user: &User) -> QueryResult<usize> {
|
||||||
let mut lock = connection.lock().unwrap();
|
let mut lock = connection.lock().unwrap();
|
||||||
let con = lock.deref_mut();
|
let con = lock.deref_mut();
|
||||||
|
|
||||||
insert_into(users).values(user).on_conflict_do_nothing().execute(con)
|
insert_into(users)
|
||||||
|
.values(user)
|
||||||
|
.on_conflict_do_nothing()
|
||||||
|
.execute(con)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ pub struct User {
|
|||||||
pub group: String,
|
pub group: String,
|
||||||
pub role: UserRole,
|
pub role: UserRole,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
}
|
}
|
||||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod parser;
|
||||||
14
src/main.rs
14
src/main.rs
@@ -1,5 +1,6 @@
|
|||||||
use crate::app_state::{AppState, app_state};
|
use crate::app_state::{AppState, app_state};
|
||||||
use crate::routes::auth::sign_in::sign_in;
|
use crate::routes::auth::sign_in::{sign_in_default, sign_in_vk};
|
||||||
|
use crate::routes::auth::sign_up::{sign_up_default, sign_up_vk};
|
||||||
use actix_web::{App, HttpServer, web};
|
use actix_web::{App, HttpServer, web};
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
|
|
||||||
@@ -7,19 +8,24 @@ mod app_state;
|
|||||||
mod database;
|
mod database;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_env;
|
mod test_env;
|
||||||
|
|
||||||
mod utility;
|
mod utility;
|
||||||
mod xls_downloader;
|
mod xls_downloader;
|
||||||
|
|
||||||
|
mod parser;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let schedule_scope = web::scope("/auth").service(sign_in);
|
let auth_scope = web::scope("/auth")
|
||||||
let api_scope = web::scope("/api/v1").service(schedule_scope);
|
.service(sign_in_default)
|
||||||
|
.service(sign_in_vk)
|
||||||
|
.service(sign_up_default)
|
||||||
|
.service(sign_up_vk);
|
||||||
|
let api_scope = web::scope("/api/v1").service(auth_scope);
|
||||||
|
|
||||||
App::new().app_data(move || app_state()).service(api_scope)
|
App::new().app_data(move || app_state()).service(api_scope)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::LessonParseResult::{Lessons, Street};
|
use crate::parser::LessonParseResult::{Lessons, Street};
|
||||||
use crate::schema::LessonType::Break;
|
use crate::parser::schema::LessonType::Break;
|
||||||
use crate::schema::{
|
use crate::parser::schema::{
|
||||||
Day, Lesson, LessonSubGroup, LessonTime, LessonType, ParseResult, ScheduleEntry,
|
Day, Lesson, LessonSubGroup, LessonTime, LessonType, ParseResult, ScheduleEntry,
|
||||||
};
|
};
|
||||||
use calamine::{Reader, Xls, open_workbook_from_rs};
|
use calamine::{Reader, Xls, open_workbook_from_rs};
|
||||||
@@ -690,13 +690,18 @@ pub fn parse_xls(buffer: &Vec<u8>) -> ParseResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
pub fn test_result() -> ParseResult {
|
||||||
|
let buffer: Vec<u8> = include_bytes!("../../schedule.xls").to_vec();
|
||||||
|
|
||||||
|
parse_xls(&buffer)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read() {
|
fn read() {
|
||||||
let buffer: Vec<u8> = include_bytes!("../../../../schedule.xls").to_vec();
|
let result = test_result();
|
||||||
let result = parse_xls(&buffer);
|
|
||||||
|
|
||||||
assert_ne!(result.groups.len(), 0);
|
assert_ne!(result.groups.len(), 0);
|
||||||
assert_ne!(result.teachers.len(), 0);
|
assert_ne!(result.teachers.len(), 0);
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
mod schema;
|
|
||||||
pub mod sign_in;
|
pub mod sign_in;
|
||||||
pub mod sign_up;
|
pub mod sign_up;
|
||||||
|
mod shared;
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
pub mod sign_in {
|
|
||||||
use crate::database::models::User;
|
|
||||||
use crate::routes::schema::shared::{ErrorToHttpCode, IResponse};
|
|
||||||
use crate::routes::schema::user;
|
|
||||||
use actix_web::http::StatusCode;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct Request {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Response = IResponse<user::ResponseOk, ResponseErr>;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct ResponseErr {
|
|
||||||
code: ErrorCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
||||||
pub enum ErrorCode {
|
|
||||||
IncorrectCredentials,
|
|
||||||
InvalidVkAccessToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ResponseExt {
|
|
||||||
fn ok(user: &User) -> Self;
|
|
||||||
fn err(code: ErrorCode) -> Response;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResponseExt for Response {
|
|
||||||
fn ok(user: &User) -> Self {
|
|
||||||
IResponse(Ok(user::ResponseOk::from_user(&user)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn err(code: ErrorCode) -> Response {
|
|
||||||
IResponse(Err(ResponseErr { code }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrorToHttpCode for ResponseErr {
|
|
||||||
fn to_http_status_code(&self) -> StatusCode {
|
|
||||||
StatusCode::NOT_ACCEPTABLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod sign_up {
|
|
||||||
use crate::database::models::{User, UserRole};
|
|
||||||
use crate::routes::schema::shared::{ErrorToHttpCode, IResponse};
|
|
||||||
use crate::routes::schema::user;
|
|
||||||
use actix_web::http::StatusCode;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Request {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
pub group: String,
|
|
||||||
pub role: UserRole,
|
|
||||||
pub version: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Response = IResponse<user::ResponseOk, ResponseErr>;
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ResponseOk {
|
|
||||||
id: String,
|
|
||||||
access_token: String,
|
|
||||||
group: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
pub struct ResponseErr {
|
|
||||||
code: ErrorCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
||||||
pub enum ErrorCode {
|
|
||||||
DisallowedRole,
|
|
||||||
InvalidGroupName,
|
|
||||||
UsernameAlreadyExists,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ResponseExt {
|
|
||||||
fn ok(user: &User) -> Self;
|
|
||||||
fn err(code: ErrorCode) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResponseExt for Response {
|
|
||||||
fn ok(user: &User) -> Self {
|
|
||||||
IResponse(Ok(user::ResponseOk::from_user(&user)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn err(code: ErrorCode) -> Response {
|
|
||||||
Self(Err(ResponseErr { code }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrorToHttpCode for ResponseErr {
|
|
||||||
fn to_http_status_code(&self) -> StatusCode {
|
|
||||||
StatusCode::NOT_ACCEPTABLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
96
src/routes/auth/shared.rs
Normal file
96
src/routes/auth/shared.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use crate::utility::jwt::DEFAULT_ALGORITHM;
|
||||||
|
use jsonwebtoken::errors::ErrorKind;
|
||||||
|
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::env;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
struct TokenData {
|
||||||
|
iis: String,
|
||||||
|
sub: i32,
|
||||||
|
app: i32,
|
||||||
|
exp: i32,
|
||||||
|
iat: i32,
|
||||||
|
jti: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct Claims {
|
||||||
|
sub: String,
|
||||||
|
iis: String,
|
||||||
|
jti: i32,
|
||||||
|
app: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
JwtError(ErrorKind),
|
||||||
|
InvalidSignature,
|
||||||
|
InvalidToken,
|
||||||
|
Expired,
|
||||||
|
UnknownIssuer(String),
|
||||||
|
UnknownType(i32),
|
||||||
|
UnknownClientId(i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection SpellCheckingInspection
|
||||||
|
const VK_PUBLIC_KEY: &str = concat!(
|
||||||
|
"-----BEGIN PUBLIC KEY-----\n",
|
||||||
|
"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvsvJlhFX9Ju/pvCz1frB\n",
|
||||||
|
"DgJs592VjdwQuRAmnlJAItyHkoiDIOEocPzgcUBTbDf1plDcTyO2RCkUt0pz0WK6\n",
|
||||||
|
"6HNhpJyIfARjaWHeUlv4TpuHXAJJsBKklkU2gf1cjID+40sWWYjtq5dAkXnSJUVA\n",
|
||||||
|
"UR+sq0lJ7GmTdJtAr8hzESqGEcSP15PTs7VUdHZ1nkC2XgkuR8KmKAUb388ji1Q4\n",
|
||||||
|
"n02rJNOPQgd9r0ac4N2v/yTAFPXumO78N25bpcuWf5vcL9e8THk/U2zt7wf+aAWL\n",
|
||||||
|
"748e0pREqNluTBJNZfmhC79Xx6GHtwqHyyduiqfPmejmiujNM/rqnA4e30Tg86Yn\n",
|
||||||
|
"cNZ6vLJyF72Eva1wXchukH/aLispbY+EqNPxxn4zzCWaLKHG87gaCxpVv9Tm0jSD\n",
|
||||||
|
"2es22NjrUbtb+2pAGnXbyDp2eGUqw0RrTQFZqt/VcmmSCE45FlcZMT28otrwG1ZB\n",
|
||||||
|
"kZAb5Js3wLEch3ZfYL8sjhyNRPBmJBrAvzrd8qa3rdUjkC9sKyjGAaHu2MNmFl1Y\n",
|
||||||
|
"JFQ3J54tGpkGgJjD7Kz3w0K6OiPDlVCNQN5sqXm24fCw85Pbi8SJiaLTp/CImrs1\n",
|
||||||
|
"Z3nHW5q8hljA7OGmqfOP0nZS/5zW9GHPyepsI1rW6CympYLJ15WeNzePxYS5KEX9\n",
|
||||||
|
"EncmkSD9b45ge95hJeJZteUCAwEAAQ==\n",
|
||||||
|
"-----END PUBLIC KEY-----"
|
||||||
|
);
|
||||||
|
|
||||||
|
static VK_ID_CLIENT_ID: LazyLock<i32> = LazyLock::new(|| {
|
||||||
|
env::var("VK_ID_CLIENT_ID")
|
||||||
|
.expect("VK_ID_CLIENT_ID must be set")
|
||||||
|
.parse::<i32>()
|
||||||
|
.expect("VK_ID_CLIENT_ID must be i32")
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn parse_vk_id(token_str: &String) -> Result<i32, Error> {
|
||||||
|
let dkey = DecodingKey::from_rsa_pem(VK_PUBLIC_KEY.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
match decode::<Claims>(&token_str, &dkey, &Validation::new(DEFAULT_ALGORITHM)) {
|
||||||
|
Ok(token_data) => {
|
||||||
|
let claims = token_data.claims;
|
||||||
|
|
||||||
|
if claims.iis != "VK" {
|
||||||
|
Err(Error::UnknownIssuer(claims.iis))
|
||||||
|
} else if claims.jti != 21 {
|
||||||
|
Err(Error::UnknownType(claims.jti))
|
||||||
|
} else if claims.app != *VK_ID_CLIENT_ID {
|
||||||
|
Err(Error::UnknownClientId(claims.app))
|
||||||
|
} else {
|
||||||
|
match claims.sub.parse::<i32>() {
|
||||||
|
Ok(sub) => Ok(sub),
|
||||||
|
Err(_) => Err(Error::InvalidToken),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Err(match err.into_kind() {
|
||||||
|
ErrorKind::InvalidToken => Error::InvalidToken,
|
||||||
|
ErrorKind::InvalidSignature => Error::InvalidSignature,
|
||||||
|
ErrorKind::InvalidAlgorithmName => Error::InvalidToken,
|
||||||
|
ErrorKind::MissingRequiredClaim(_) => Error::InvalidToken,
|
||||||
|
ErrorKind::ExpiredSignature => Error::Expired,
|
||||||
|
ErrorKind::InvalidAlgorithm => Error::InvalidToken,
|
||||||
|
ErrorKind::MissingAlgorithm => Error::InvalidToken,
|
||||||
|
ErrorKind::Base64(_) => Error::InvalidToken,
|
||||||
|
ErrorKind::Json(_) => Error::InvalidToken,
|
||||||
|
ErrorKind::Utf8(_) => Error::InvalidToken,
|
||||||
|
kind => Error::JwtError(kind),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,47 +1,138 @@
|
|||||||
|
use self::schema::*;
|
||||||
use crate::database::driver;
|
use crate::database::driver;
|
||||||
use crate::database::models::User;
|
use crate::database::models::User;
|
||||||
use crate::routes::auth::schema;
|
use crate::routes::auth::shared::parse_vk_id;
|
||||||
use crate::{AppState, utility};
|
use crate::routes::auth::sign_in::schema::ErrorCode;
|
||||||
|
use crate::routes::auth::sign_in::schema::SignInData::{Default, Vk};
|
||||||
|
use crate::{utility, AppState};
|
||||||
use actix_web::{post, web};
|
use actix_web::{post, web};
|
||||||
use diesel::SaveChangesDsl;
|
use diesel::SaveChangesDsl;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
use web::Json;
|
use web::Json;
|
||||||
|
|
||||||
#[post("/sign-in")]
|
async fn sign_in(data: SignInData, app_state: &web::Data<AppState>) -> Response {
|
||||||
pub async fn sign_in(
|
let user = match &data {
|
||||||
data: Json<schema::sign_in::Request>,
|
Default(data) => driver::users::get_by_username(&app_state.database, &data.username),
|
||||||
app_state: web::Data<AppState>,
|
Vk(id) => driver::users::get_by_vk_id(&app_state.database, *id),
|
||||||
) -> schema::sign_in::Response {
|
};
|
||||||
use schema::sign_in::*;
|
|
||||||
|
|
||||||
match driver::users::get_by_username(&app_state.database, data.username.clone()) {
|
match user {
|
||||||
Ok(mut user) => match bcrypt::verify(&data.password, &user.password) {
|
Ok(mut user) => {
|
||||||
Ok(true) => {
|
if let Default(data) = data {
|
||||||
let mut lock = app_state.connection();
|
match bcrypt::verify(&data.password, &user.password) {
|
||||||
let conn = lock.deref_mut();
|
Ok(result) => {
|
||||||
|
if !result {
|
||||||
user.access_token = utility::jwt::encode(&user.id);
|
return Response::err(ErrorCode::IncorrectCredentials);
|
||||||
|
}
|
||||||
user.save_changes::<User>(conn)
|
}
|
||||||
.expect("Failed to update user");
|
Err(_) => {
|
||||||
|
return Response::err(ErrorCode::IncorrectCredentials);
|
||||||
Response::ok(&user)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(false) | Err(_) => Response::err(ErrorCode::IncorrectCredentials),
|
|
||||||
},
|
let mut lock = app_state.connection();
|
||||||
|
let conn = lock.deref_mut();
|
||||||
|
|
||||||
|
user.access_token = utility::jwt::encode(&user.id);
|
||||||
|
|
||||||
|
user.save_changes::<User>(conn)
|
||||||
|
.expect("Failed to update user");
|
||||||
|
|
||||||
|
Response::ok(&user)
|
||||||
|
}
|
||||||
|
|
||||||
Err(_) => Response::err(ErrorCode::IncorrectCredentials),
|
Err(_) => Response::err(ErrorCode::IncorrectCredentials),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/sign-in")]
|
||||||
|
pub async fn sign_in_default(data: Json<Request>, app_state: web::Data<AppState>) -> Response {
|
||||||
|
sign_in(Default(data.into_inner()), &app_state).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/sign-in-vk")]
|
||||||
|
pub async fn sign_in_vk(data_json: Json<vk::Request>, app_state: web::Data<AppState>) -> Response {
|
||||||
|
let data = data_json.into_inner();
|
||||||
|
|
||||||
|
match parse_vk_id(&data.access_token) {
|
||||||
|
Ok(id) => sign_in(Vk(id), &app_state).await,
|
||||||
|
Err(_) => Response::err(ErrorCode::InvalidVkAccessToken),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod schema {
|
||||||
|
use crate::database::models::User;
|
||||||
|
use crate::routes::schema::{user, ErrorToHttpCode, IResponse};
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct Request {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod vk {
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Request {
|
||||||
|
pub access_token: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Response = IResponse<user::ResponseOk, ResponseErr>;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ResponseErr {
|
||||||
|
code: ErrorCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
IncorrectCredentials,
|
||||||
|
InvalidVkAccessToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ResponseExt {
|
||||||
|
fn ok(user: &User) -> Self;
|
||||||
|
fn err(code: ErrorCode) -> Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseExt for Response {
|
||||||
|
fn ok(user: &User) -> Self {
|
||||||
|
IResponse(Ok(user::ResponseOk::from_user(&user)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn err(code: ErrorCode) -> Response {
|
||||||
|
IResponse(Err(ResponseErr { code }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorToHttpCode for ResponseErr {
|
||||||
|
fn to_http_status_code(&self) -> StatusCode {
|
||||||
|
StatusCode::NOT_ACCEPTABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal
|
||||||
|
|
||||||
|
pub enum SignInData {
|
||||||
|
Default(Request),
|
||||||
|
Vk(i32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::app_state::app_state;
|
use super::schema::*;
|
||||||
use crate::database::driver;
|
use crate::database::driver;
|
||||||
use crate::database::models::{User, UserRole};
|
use crate::database::models::{User, UserRole};
|
||||||
use crate::routes::auth::schema;
|
use crate::routes::auth::sign_in::sign_in_default;
|
||||||
use crate::routes::auth::sign_in::sign_in;
|
use crate::test_env::tests::{static_app_state, test_app, test_app_state, test_env};
|
||||||
use crate::test_env::tests::{static_app_state, test_app, test_env};
|
|
||||||
use crate::utility;
|
use crate::utility;
|
||||||
use actix_http::StatusCode;
|
use actix_http::StatusCode;
|
||||||
use actix_web::dev::ServiceResponse;
|
use actix_web::dev::ServiceResponse;
|
||||||
@@ -50,8 +141,8 @@ mod tests {
|
|||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
async fn sign_in_client(data: schema::sign_in::Request) -> ServiceResponse {
|
async fn sign_in_client(data: Request) -> ServiceResponse {
|
||||||
let app = test_app(app_state(), sign_in).await;
|
let app = test_app(test_app_state(), sign_in_default).await;
|
||||||
|
|
||||||
let req = test::TestRequest::with_uri("/sign-in")
|
let req = test::TestRequest::with_uri("/sign-in")
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
@@ -100,7 +191,7 @@ mod tests {
|
|||||||
async fn sign_in_ok() {
|
async fn sign_in_ok() {
|
||||||
prepare("test::sign_in_ok".to_string());
|
prepare("test::sign_in_ok".to_string());
|
||||||
|
|
||||||
let resp = sign_in_client(schema::sign_in::Request {
|
let resp = sign_in_client(Request {
|
||||||
username: "test::sign_in_ok".to_string(),
|
username: "test::sign_in_ok".to_string(),
|
||||||
password: "example".to_string(),
|
password: "example".to_string(),
|
||||||
})
|
})
|
||||||
@@ -113,7 +204,7 @@ mod tests {
|
|||||||
async fn sign_in_err() {
|
async fn sign_in_err() {
|
||||||
prepare("test::sign_in_err".to_string());
|
prepare("test::sign_in_err".to_string());
|
||||||
|
|
||||||
let invalid_username = sign_in_client(schema::sign_in::Request {
|
let invalid_username = sign_in_client(Request {
|
||||||
username: "test::sign_in_err::username".to_string(),
|
username: "test::sign_in_err::username".to_string(),
|
||||||
password: "example".to_string(),
|
password: "example".to_string(),
|
||||||
})
|
})
|
||||||
@@ -121,7 +212,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(invalid_username.status(), StatusCode::NOT_ACCEPTABLE);
|
assert_eq!(invalid_username.status(), StatusCode::NOT_ACCEPTABLE);
|
||||||
|
|
||||||
let invalid_password = sign_in_client(schema::sign_in::Request {
|
let invalid_password = sign_in_client(Request {
|
||||||
username: "test::sign_in_err".to_string(),
|
username: "test::sign_in_err".to_string(),
|
||||||
password: "bad_password".to_string(),
|
password: "bad_password".to_string(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
|
use self::schema::*;
|
||||||
|
use crate::AppState;
|
||||||
use crate::database::driver;
|
use crate::database::driver;
|
||||||
use crate::database::models::{User, UserRole};
|
use crate::database::models::UserRole;
|
||||||
use crate::routes::auth::schema;
|
use crate::routes::auth::shared::{Error, parse_vk_id};
|
||||||
use crate::{utility, AppState};
|
|
||||||
use actix_web::{post, web};
|
use actix_web::{post, web};
|
||||||
use objectid::ObjectId;
|
use rand::{Rng, rng};
|
||||||
use web::Json;
|
use web::Json;
|
||||||
|
|
||||||
#[post("/sign-up")]
|
async fn sign_up(data: SignUpData, app_state: &web::Data<AppState>) -> Response {
|
||||||
pub async fn sign_up(
|
// If user selected forbidden role.
|
||||||
data: Json<schema::sign_up::Request>,
|
|
||||||
app_state: web::Data<AppState>,
|
|
||||||
) -> schema::sign_up::Response {
|
|
||||||
use schema::sign_up::*;
|
|
||||||
|
|
||||||
if data.role == UserRole::Admin {
|
if data.role == UserRole::Admin {
|
||||||
return Response::err(ErrorCode::DisallowedRole);
|
return Response::err(ErrorCode::DisallowedRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If specified group doesn't exist in schedule.
|
||||||
let schedule_opt = app_state.schedule.lock().unwrap();
|
let schedule_opt = app_state.schedule.lock().unwrap();
|
||||||
|
|
||||||
if let Some(schedule) = &*schedule_opt {
|
if let Some(schedule) = &*schedule_opt {
|
||||||
@@ -25,37 +22,193 @@ pub async fn sign_up(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if driver::users::contains_by_username(&app_state.database, data.username.clone()) {
|
// If user with specified username already exists.
|
||||||
|
if driver::users::contains_by_username(&app_state.database, &data.username) {
|
||||||
return Response::err(ErrorCode::UsernameAlreadyExists);
|
return Response::err(ErrorCode::UsernameAlreadyExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = ObjectId::new().unwrap().to_string();
|
// If user with specified VKID already exists.
|
||||||
let access_token = utility::jwt::encode(&id);
|
if let Some(id) = data.vk_id {
|
||||||
|
if driver::users::contains_by_vk_id(&app_state.database, id) {
|
||||||
let user = User {
|
return Response::err(ErrorCode::VkAlreadyExists);
|
||||||
id,
|
}
|
||||||
username: data.username.clone(),
|
}
|
||||||
password: bcrypt::hash(data.password.as_str(), bcrypt::DEFAULT_COST).unwrap(),
|
|
||||||
vk_id: None,
|
|
||||||
access_token,
|
|
||||||
group: data.group.clone(),
|
|
||||||
role: data.role.clone(),
|
|
||||||
version: data.version.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let user = data.to_user();
|
||||||
driver::users::insert(&app_state.database, &user).unwrap();
|
driver::users::insert(&app_state.database, &user).unwrap();
|
||||||
|
|
||||||
Response::ok(&user)
|
Response::ok(&user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/sign-up")]
|
||||||
|
pub async fn sign_up_default(data_json: Json<Request>, app_state: web::Data<AppState>) -> Response {
|
||||||
|
let data = data_json.into_inner();
|
||||||
|
|
||||||
|
sign_up(
|
||||||
|
SignUpData {
|
||||||
|
username: data.username,
|
||||||
|
password: data.password,
|
||||||
|
vk_id: None,
|
||||||
|
group: data.group,
|
||||||
|
role: data.role,
|
||||||
|
version: data.version,
|
||||||
|
},
|
||||||
|
&app_state,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/sign-up-vk")]
|
||||||
|
pub async fn sign_up_vk(data_json: Json<vk::Request>, app_state: web::Data<AppState>) -> Response {
|
||||||
|
let data = data_json.into_inner();
|
||||||
|
|
||||||
|
match parse_vk_id(&data.access_token) {
|
||||||
|
Ok(id) => {
|
||||||
|
sign_up(
|
||||||
|
SignUpData {
|
||||||
|
username: data.username,
|
||||||
|
password: rng()
|
||||||
|
.sample_iter(&rand::distr::Alphanumeric)
|
||||||
|
.take(16)
|
||||||
|
.map(char::from)
|
||||||
|
.collect(),
|
||||||
|
vk_id: Some(id),
|
||||||
|
group: data.group,
|
||||||
|
role: data.role,
|
||||||
|
version: data.version,
|
||||||
|
},
|
||||||
|
&app_state,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if err != Error::Expired {
|
||||||
|
eprintln!("Failed to parse vk id token!");
|
||||||
|
eprintln!("{:?}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::err(ErrorCode::InvalidVkAccessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod schema {
|
||||||
|
use crate::database::models::{User, UserRole};
|
||||||
|
use crate::routes::schema::{ErrorToHttpCode, IResponse, user};
|
||||||
|
use crate::utility;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use objectid::ObjectId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// WEB
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Request {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
pub group: String,
|
||||||
|
pub role: UserRole,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod vk {
|
||||||
|
use crate::database::models::UserRole;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Request {
|
||||||
|
pub access_token: String,
|
||||||
|
pub username: String,
|
||||||
|
pub group: String,
|
||||||
|
pub role: UserRole,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Response = IResponse<user::ResponseOk, ResponseErr>;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ResponseOk {
|
||||||
|
id: String,
|
||||||
|
access_token: String,
|
||||||
|
group: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ResponseErr {
|
||||||
|
code: ErrorCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
DisallowedRole,
|
||||||
|
InvalidGroupName,
|
||||||
|
UsernameAlreadyExists,
|
||||||
|
InvalidVkAccessToken,
|
||||||
|
VkAlreadyExists,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ResponseExt {
|
||||||
|
fn ok(user: &User) -> Self;
|
||||||
|
fn err(code: ErrorCode) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseExt for Response {
|
||||||
|
fn ok(user: &User) -> Self {
|
||||||
|
IResponse(Ok(user::ResponseOk::from_user(&user)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn err(code: ErrorCode) -> Response {
|
||||||
|
Self(Err(ResponseErr { code }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorToHttpCode for ResponseErr {
|
||||||
|
fn to_http_status_code(&self) -> StatusCode {
|
||||||
|
StatusCode::NOT_ACCEPTABLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal
|
||||||
|
|
||||||
|
pub struct SignUpData {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
pub vk_id: Option<i32>,
|
||||||
|
pub group: String,
|
||||||
|
pub role: UserRole,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignUpData {
|
||||||
|
pub fn to_user(self) -> User {
|
||||||
|
let id = ObjectId::new().unwrap().to_string();
|
||||||
|
let access_token = utility::jwt::encode(&id);
|
||||||
|
|
||||||
|
User {
|
||||||
|
id,
|
||||||
|
username: self.username,
|
||||||
|
password: bcrypt::hash(self.password, bcrypt::DEFAULT_COST).unwrap(),
|
||||||
|
vk_id: self.vk_id,
|
||||||
|
access_token,
|
||||||
|
group: self.group,
|
||||||
|
role: self.role,
|
||||||
|
version: self.version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::app_state::app_state;
|
|
||||||
use crate::database::driver;
|
use crate::database::driver;
|
||||||
use crate::database::models::UserRole;
|
use crate::database::models::UserRole;
|
||||||
use crate::routes::auth::schema;
|
use crate::routes::auth::sign_up::schema::Request;
|
||||||
use crate::routes::auth::sign_up::sign_up;
|
use crate::routes::auth::sign_up::sign_up_default;
|
||||||
use crate::test_env::tests::{static_app_state, test_app, test_env};
|
use crate::test_env::tests::{static_app_state, test_app, test_app_state, test_env};
|
||||||
use actix_http::StatusCode;
|
use actix_http::StatusCode;
|
||||||
use actix_web::dev::ServiceResponse;
|
use actix_web::dev::ServiceResponse;
|
||||||
use actix_web::http::Method;
|
use actix_web::http::Method;
|
||||||
@@ -68,11 +221,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn sign_up_client(data: SignUpPartial) -> ServiceResponse {
|
async fn sign_up_client(data: SignUpPartial) -> ServiceResponse {
|
||||||
let app = test_app(app_state(), sign_up).await;
|
let app = test_app(test_app_state(), sign_up_default).await;
|
||||||
|
|
||||||
let req = test::TestRequest::with_uri("/sign-up")
|
let req = test::TestRequest::with_uri("/sign-up")
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.set_json(schema::sign_up::Request {
|
.set_json(Request {
|
||||||
username: data.username.clone(),
|
username: data.username.clone(),
|
||||||
password: "example".to_string(),
|
password: "example".to_string(),
|
||||||
group: data.group.clone(),
|
group: data.group.clone(),
|
||||||
@@ -91,7 +244,7 @@ mod tests {
|
|||||||
test_env();
|
test_env();
|
||||||
|
|
||||||
let app_state = static_app_state();
|
let app_state = static_app_state();
|
||||||
driver::users::delete_by_username(&app_state.database, "test::sign_up_valid".to_string());
|
driver::users::delete_by_username(&app_state.database, &"test::sign_up_valid".to_string());
|
||||||
|
|
||||||
// test
|
// test
|
||||||
|
|
||||||
@@ -114,7 +267,7 @@ mod tests {
|
|||||||
let app_state = static_app_state();
|
let app_state = static_app_state();
|
||||||
driver::users::delete_by_username(
|
driver::users::delete_by_username(
|
||||||
&app_state.database,
|
&app_state.database,
|
||||||
"test::sign_up_multiple".to_string(),
|
&"test::sign_up_multiple".to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let create = sign_up_client(SignUpPartial {
|
let create = sign_up_client(SignUpPartial {
|
||||||
@@ -150,4 +303,19 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(resp.status(), StatusCode::NOT_ACCEPTABLE);
|
assert_eq!(resp.status(), StatusCode::NOT_ACCEPTABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn sign_up_invalid_group() {
|
||||||
|
test_env();
|
||||||
|
|
||||||
|
// test
|
||||||
|
let resp = sign_up_client(SignUpPartial {
|
||||||
|
username: "test::sign_up_invalid_group".to_string(),
|
||||||
|
group: "invalid_group".to_string(),
|
||||||
|
role: UserRole::Student,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(resp.status(), StatusCode::NOT_ACCEPTABLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,37 @@
|
|||||||
pub mod shared {
|
use actix_web::body::EitherBody;
|
||||||
use actix_web::body::EitherBody;
|
use actix_web::error::JsonPayloadError;
|
||||||
use actix_web::error::JsonPayloadError;
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::{HttpRequest, HttpResponse, Responder};
|
||||||
use actix_web::{HttpRequest, HttpResponse, Responder};
|
use serde::Serialize;
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
pub struct IResponse<T: Serialize, E: Serialize>(pub Result<T, E>);
|
pub struct IResponse<T: Serialize, E: Serialize>(pub Result<T, E>);
|
||||||
|
|
||||||
pub trait ErrorToHttpCode {
|
pub trait ErrorToHttpCode {
|
||||||
fn to_http_status_code(&self) -> StatusCode;
|
fn to_http_status_code(&self) -> StatusCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Serialize, E: Serialize + ErrorToHttpCode> Responder for IResponse<T, E> {
|
impl<T: Serialize, E: Serialize + ErrorToHttpCode> Responder for IResponse<T, E> {
|
||||||
type Body = EitherBody<String>;
|
type Body = EitherBody<String>;
|
||||||
|
|
||||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||||
match serde_json::to_string(&self.0) {
|
match serde_json::to_string(&self.0) {
|
||||||
Ok(body) => {
|
Ok(body) => {
|
||||||
let code = match &self.0 {
|
let code = match &self.0 {
|
||||||
Ok(_) => StatusCode::OK,
|
Ok(_) => StatusCode::OK,
|
||||||
Err(e) => e.to_http_status_code(),
|
Err(e) => e.to_http_status_code(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match HttpResponse::build(code)
|
match HttpResponse::build(code)
|
||||||
.content_type(mime::APPLICATION_JSON)
|
.content_type(mime::APPLICATION_JSON)
|
||||||
.message_body(body)
|
.message_body(body)
|
||||||
{
|
{
|
||||||
Ok(res) => res.map_into_left_body(),
|
Ok(res) => res.map_into_left_body(),
|
||||||
Err(err) => HttpResponse::from_error(err).map_into_right_body(),
|
Err(err) => HttpResponse::from_error(err).map_into_right_body(),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body()
|
HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use crate::app_state::{AppState, app_state};
|
use crate::app_state::{app_state, AppState, Schedule};
|
||||||
use actix_web::dev::{HttpServiceFactory, Service, ServiceResponse};
|
use actix_web::dev::{HttpServiceFactory, Service, ServiceResponse};
|
||||||
use actix_web::{App, test, web};
|
use actix_web::{test, web, App};
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
use crate::parser::tests::test_result;
|
||||||
|
|
||||||
pub fn test_env() {
|
pub fn test_env() {
|
||||||
dotenvy::from_path(".env.test").expect("Failed to load test environment file");
|
dotenvy::from_path(".env.test").expect("Failed to load test environment file");
|
||||||
@@ -19,8 +20,22 @@ pub(crate) mod tests {
|
|||||||
test::init_service(App::new().app_data(app_state).service(factory)).await
|
test::init_service(App::new().app_data(app_state).service(factory)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test_app_state() -> web::Data<AppState> {
|
||||||
|
let state = app_state();
|
||||||
|
let mut schedule_lock = state.schedule.lock().unwrap();
|
||||||
|
|
||||||
|
*schedule_lock = Some(Schedule {
|
||||||
|
etag: "".to_string(),
|
||||||
|
updated_at: Default::default(),
|
||||||
|
parsed_at: Default::default(),
|
||||||
|
data: test_result(),
|
||||||
|
});
|
||||||
|
|
||||||
|
state.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn static_app_state() -> web::Data<AppState> {
|
pub fn static_app_state() -> web::Data<AppState> {
|
||||||
static STATE: LazyLock<web::Data<AppState>> = LazyLock::new(|| app_state());
|
static STATE: LazyLock<web::Data<AppState>> = LazyLock::new(|| test_app_state());
|
||||||
|
|
||||||
STATE.clone()
|
STATE.clone()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +1,91 @@
|
|||||||
use chrono::DateTime;
|
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use chrono::TimeZone;
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use hmac::{Hmac, Mac};
|
use jsonwebtoken::errors::ErrorKind;
|
||||||
use jwt::{SignWithKey, Token, VerifyWithKey};
|
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode};
|
||||||
use sha2::Sha256;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use serde_with::DisplayFromStr;
|
||||||
|
use serde_with::serde_as;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::mem::discriminant;
|
use std::mem::discriminant;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
static JWT_SECRET: LazyLock<Hmac<Sha256>> = LazyLock::new(|| {
|
static DECODING_KEY: LazyLock<DecodingKey> = LazyLock::new(|| {
|
||||||
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
||||||
|
|
||||||
Hmac::new_from_slice(secret.as_bytes()).expect("Hmac::new_from_slice failed")
|
DecodingKey::from_secret(secret.as_bytes())
|
||||||
|
});
|
||||||
|
|
||||||
|
static ENCODING_KEY: LazyLock<EncodingKey> = LazyLock::new(|| {
|
||||||
|
let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
||||||
|
|
||||||
|
EncodingKey::from_secret(secret.as_bytes())
|
||||||
});
|
});
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum VerifyError {
|
pub enum Error {
|
||||||
JwtError(jwt::Error),
|
|
||||||
InvalidSignature,
|
InvalidSignature,
|
||||||
InvalidToken,
|
InvalidToken(ErrorKind),
|
||||||
Expired,
|
Expired,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for VerifyError {
|
impl PartialEq for Error {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
discriminant(self) == discriminant(other)
|
discriminant(self) == discriminant(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_and_decode(token: &String) -> Result<String, VerifyError> {
|
#[serde_as]
|
||||||
let jwt = &*JWT_SECRET;
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct Claims {
|
||||||
|
id: String,
|
||||||
|
#[serde_as(as = "DisplayFromStr")]
|
||||||
|
iat: u64,
|
||||||
|
#[serde_as(as = "DisplayFromStr")]
|
||||||
|
exp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
let result: Result<BTreeMap<String, String>, jwt::Error> = token.verify_with_key(jwt);
|
pub(crate) const DEFAULT_ALGORITHM: Algorithm = Algorithm::HS256;
|
||||||
|
|
||||||
|
pub fn verify_and_decode(token: &String) -> Result<String, Error> {
|
||||||
|
let mut validation = Validation::new(DEFAULT_ALGORITHM);
|
||||||
|
|
||||||
|
validation.required_spec_claims.remove("exp");
|
||||||
|
validation.validate_exp = false;
|
||||||
|
|
||||||
|
let result = decode::<Claims>(&token, &*DECODING_KEY, &validation);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(claims) => {
|
Ok(token_data) => {
|
||||||
let exp = claims.get("exp").unwrap();
|
if token_data.claims.exp < Utc::now().timestamp().unsigned_abs() {
|
||||||
let exp_date = DateTime::from_timestamp(exp.parse::<i64>().unwrap(), 0)
|
Err(Error::Expired)
|
||||||
.expect("Failed to parse expiration time");
|
} else {
|
||||||
|
Ok(token_data.claims.id)
|
||||||
if Utc::now() > exp_date {
|
|
||||||
return Err(VerifyError::Expired);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(claims.get("id").cloned().unwrap())
|
|
||||||
}
|
}
|
||||||
Err(err) => Err(match err {
|
Err(err) => Err(match err.into_kind() {
|
||||||
jwt::Error::InvalidSignature | jwt::Error::RustCryptoMac(_) => {
|
ErrorKind::InvalidSignature => Error::InvalidSignature,
|
||||||
VerifyError::InvalidSignature
|
ErrorKind::ExpiredSignature => Error::Expired,
|
||||||
}
|
kind => Error::InvalidToken(kind),
|
||||||
jwt::Error::Format | jwt::Error::Base64(_) | jwt::Error::NoClaimsComponent => {
|
|
||||||
VerifyError::InvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => VerifyError::JwtError(err),
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode(id: &String) -> String {
|
pub fn encode(id: &String) -> String {
|
||||||
let header = jwt::Header {
|
let header = Header {
|
||||||
type_: Some(jwt::header::HeaderType::JsonWebToken),
|
typ: Some(String::from("JWT")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut claims = BTreeMap::new();
|
|
||||||
|
|
||||||
let iat = Utc::now();
|
let iat = Utc::now();
|
||||||
let exp = iat + Duration::days(365 * 4);
|
let exp = iat + Duration::days(365 * 4);
|
||||||
|
|
||||||
let iat_str = iat.timestamp().to_string();
|
let claims = Claims {
|
||||||
let exp_str = exp.timestamp().to_string();
|
id: id.clone(),
|
||||||
|
iat: iat.timestamp().unsigned_abs(),
|
||||||
|
exp: exp.timestamp().unsigned_abs(),
|
||||||
|
};
|
||||||
|
|
||||||
claims.insert("id", id.as_str());
|
jsonwebtoken::encode(&header, &claims, &*ENCODING_KEY).unwrap()
|
||||||
claims.insert("iat", iat_str.as_str());
|
|
||||||
claims.insert("exp", exp_str.as_str());
|
|
||||||
|
|
||||||
Token::new(header, claims)
|
|
||||||
.sign_with_key(&*JWT_SECRET)
|
|
||||||
.unwrap()
|
|
||||||
.as_str()
|
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -105,7 +108,10 @@ mod tests {
|
|||||||
let result = verify_and_decode(&token);
|
let result = verify_and_decode(&token);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(result.err().unwrap(), VerifyError::InvalidToken);
|
assert_eq!(
|
||||||
|
result.err().unwrap(),
|
||||||
|
Error::InvalidToken(ErrorKind::InvalidToken)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -116,7 +122,7 @@ mod tests {
|
|||||||
let result = verify_and_decode(&token);
|
let result = verify_and_decode(&token);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(result.err().unwrap(), VerifyError::InvalidSignature);
|
assert_eq!(result.err().unwrap(), Error::InvalidSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -127,7 +133,7 @@ mod tests {
|
|||||||
let result = verify_and_decode(&token);
|
let result = verify_and_decode(&token);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(result.err().unwrap(), VerifyError::Expired);
|
assert_eq!(result.err().unwrap(), Error::Expired);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user