Added user auth
This commit is contained in:
@@ -1,11 +1,21 @@
|
|||||||
RUST_LOG=debug,actix=warn,diesel_migrations=warn,reqwest=warn,hyper=warn,tracing=warn,mio=warn
|
RUST_LOG=waren,service=info
|
||||||
|
|
||||||
DATABASE_CONTAINER=weather-db
|
|
||||||
DATABASE_HOST=db
|
|
||||||
DATABASE_USER=weather
|
DATABASE_USER=weather
|
||||||
DATABASE_PASSWORD=
|
DATABASE_PASSWORD=
|
||||||
DATABASE_NAME=weather
|
DATABASE_NAME=weather
|
||||||
|
DATABASE_HOST=localhost
|
||||||
DATABASE_PORT=5432
|
DATABASE_PORT=5432
|
||||||
|
|
||||||
SERVICE_HOST=service
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
SERVICE_HOST=localhost
|
||||||
SERVICE_PORT=5000
|
SERVICE_PORT=5000
|
||||||
|
|
||||||
|
ACCESS_TOKEN_PRIVATE_KEY=
|
||||||
|
ACCESS_TOKEN_PUBLIC_KEY=
|
||||||
|
ACCESS_TOKEN_MAXAGE=5
|
||||||
|
|
||||||
|
REFRESH_TOKEN_PRIVATE_KEY=
|
||||||
|
REFRESH_TOKEN_PUBLIC_KEY=
|
||||||
|
REFRESH_TOKEN_MAXAGE=30
|
||||||
|
|||||||
612
service/Cargo.lock
generated
612
service/Cargo.lock
generated
@@ -45,7 +45,7 @@ dependencies = [
|
|||||||
"actix-service",
|
"actix-service",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"ahash",
|
"ahash",
|
||||||
"base64 0.21.4",
|
"base64",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -73,22 +73,6 @@ dependencies = [
|
|||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "actix-identity"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "36e1cc6f95e245b2f3c6995df4e1c0c697704c48c28ec325d135a3ca039d4952"
|
|
||||||
dependencies = [
|
|
||||||
"actix-service",
|
|
||||||
"actix-session",
|
|
||||||
"actix-utils",
|
|
||||||
"actix-web",
|
|
||||||
"derive_more",
|
|
||||||
"futures-core",
|
|
||||||
"serde",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-macros"
|
name = "actix-macros"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -99,6 +83,44 @@ dependencies = [
|
|||||||
"syn 2.0.32",
|
"syn 2.0.32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-multipart"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b960e2aea75f49c8f069108063d12a48d329fc8b60b786dfc7552a9d5918d2d"
|
||||||
|
dependencies = [
|
||||||
|
"actix-multipart-derive",
|
||||||
|
"actix-utils",
|
||||||
|
"actix-web",
|
||||||
|
"bytes",
|
||||||
|
"derive_more",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"httparse",
|
||||||
|
"local-waker",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_plain",
|
||||||
|
"tempfile",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-multipart-derive"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a0a77f836d869f700e5b47ac7c3c8b9c8bc82e4aec861954c6198abee3ebd4d"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"parse-size",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -118,7 +140,6 @@ version = "2.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d"
|
checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-macros",
|
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
@@ -151,23 +172,6 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "actix-session"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2e6a28f813a6671e1847d005cad0be36ae4d016287690f765c303379837c13d6"
|
|
||||||
dependencies = [
|
|
||||||
"actix-service",
|
|
||||||
"actix-utils",
|
|
||||||
"actix-web",
|
|
||||||
"anyhow",
|
|
||||||
"async-trait",
|
|
||||||
"derive_more",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-utils"
|
name = "actix-utils"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
@@ -230,6 +234,21 @@ dependencies = [
|
|||||||
"syn 2.0.32",
|
"syn 2.0.32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-web-httpauth"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
|
||||||
|
dependencies = [
|
||||||
|
"actix-utils",
|
||||||
|
"actix-web",
|
||||||
|
"base64",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
@@ -245,41 +264,6 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aead"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
|
||||||
dependencies = [
|
|
||||||
"crypto-common",
|
|
||||||
"generic-array",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aes"
|
|
||||||
version = "0.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cipher",
|
|
||||||
"cpufeatures",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aes-gcm"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237"
|
|
||||||
dependencies = [
|
|
||||||
"aead",
|
|
||||||
"aes",
|
|
||||||
"cipher",
|
|
||||||
"ctr",
|
|
||||||
"ghash",
|
|
||||||
"subtle",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@@ -332,16 +316,28 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "arc-swap"
|
||||||
version = "1.0.75"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"cpufeatures",
|
||||||
|
"password-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.73"
|
version = "0.1.74"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -369,18 +365,18 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.20.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.4"
|
version = "0.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64ct"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -393,6 +389,15 @@ version = "2.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@@ -482,13 +487,17 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "combine"
|
||||||
version = "0.4.4"
|
version = "4.6.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-common",
|
"bytes",
|
||||||
"inout",
|
"futures-core",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -503,14 +512,7 @@ version = "0.16.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
|
||||||
"base64 0.20.0",
|
|
||||||
"hkdf",
|
|
||||||
"hmac",
|
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand",
|
|
||||||
"sha2",
|
|
||||||
"subtle",
|
|
||||||
"time",
|
"time",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
@@ -556,17 +558,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"rand_core",
|
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ctr"
|
name = "darling"
|
||||||
version = "0.9.2"
|
version = "0.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cipher",
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn 2.0.32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -748,6 +775,21 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
@@ -755,6 +797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -763,6 +806,34 @@ version = "0.3.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
@@ -781,10 +852,16 @@ version = "0.3.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -808,16 +885,6 @@ dependencies = [
|
|||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ghash"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
|
|
||||||
dependencies = [
|
|
||||||
"opaque-debug",
|
|
||||||
"polyval",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.28.0"
|
version = "0.28.0"
|
||||||
@@ -861,24 +928,6 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hkdf"
|
|
||||||
version = "0.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437"
|
|
||||||
dependencies = [
|
|
||||||
"hmac",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hmac"
|
|
||||||
version = "0.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
|
||||||
dependencies = [
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
@@ -979,6 +1028,12 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -1009,15 +1064,6 @@ dependencies = [
|
|||||||
"hashbrown 0.14.0",
|
"hashbrown 0.14.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "inout"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -1059,6 +1105,20 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "9.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "155c4d7e39ad04c172c5e3a99c434ea3b4a7ba7960b38ecd562b270b097cce09"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "language-tags"
|
name = "language-tags"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -1073,9 +1133,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.147"
|
version = "0.2.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
@@ -1083,17 +1143,6 @@ version = "0.4.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
|
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "listenfd"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e0500463acd96259d219abb05dc57e5a076ef04b2db9a2112846929b5f174c96"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"uuid",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "local-channel"
|
name = "local-channel"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
@@ -1200,6 +1249,27 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@@ -1224,12 +1294,6 @@ version = "1.18.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "opaque-debug"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.57"
|
version = "0.10.57"
|
||||||
@@ -1297,18 +1361,65 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parse-size"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-internal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-internal"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
@@ -1327,18 +1438,6 @@ version = "0.3.27"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "polyval"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cpufeatures",
|
|
||||||
"opaque-debug",
|
|
||||||
"universal-hash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postgis_diesel"
|
name = "postgis_diesel"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
@@ -1434,6 +1533,31 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redis"
|
||||||
|
version = "0.23.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
|
"combine",
|
||||||
|
"futures",
|
||||||
|
"futures-util",
|
||||||
|
"itoa",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"r2d2",
|
||||||
|
"ryu",
|
||||||
|
"sha1_smol",
|
||||||
|
"socket2 0.4.9",
|
||||||
|
"tokio",
|
||||||
|
"tokio-retry",
|
||||||
|
"tokio-util",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@@ -1478,7 +1602,7 @@ version = "0.11.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78fdbab6a7e1d7b13cc8ff10197f47986b41c639300cc3c8158cac7847c9bbef"
|
checksum = "78fdbab6a7e1d7b13cc8ff10197f47986b41c639300cc3c8158cac7847c9bbef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.4",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -1510,6 +1634,20 @@ dependencies = [
|
|||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"getrandom",
|
||||||
|
"libc",
|
||||||
|
"spin",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
@@ -1628,6 +1766,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_plain"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
@@ -1649,6 +1796,35 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "service"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"actix-cors",
|
||||||
|
"actix-multipart",
|
||||||
|
"actix-web",
|
||||||
|
"actix-web-httpauth",
|
||||||
|
"argon2",
|
||||||
|
"base64",
|
||||||
|
"chrono",
|
||||||
|
"diesel",
|
||||||
|
"diesel_migrations",
|
||||||
|
"dotenv",
|
||||||
|
"env_logger",
|
||||||
|
"jsonwebtoken",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"postgis_diesel",
|
||||||
|
"quick-xml",
|
||||||
|
"r2d2",
|
||||||
|
"redis",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@@ -1661,15 +1837,10 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha1_smol"
|
||||||
version = "0.10.7"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
|
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cpufeatures",
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
@@ -1680,6 +1851,18 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -1715,6 +1898,18 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.9.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -1786,6 +1981,26 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
@@ -1868,6 +2083,17 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-retry"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project",
|
||||||
|
"rand",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
@@ -1977,14 +2203,10 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-hash"
|
name = "untrusted"
|
||||||
version = "0.5.1"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
dependencies = [
|
|
||||||
"crypto-common",
|
|
||||||
"subtle",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
@@ -2100,32 +2322,6 @@ version = "0.2.87"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "weather-service"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"actix-cors",
|
|
||||||
"actix-identity",
|
|
||||||
"actix-rt",
|
|
||||||
"actix-web",
|
|
||||||
"chrono",
|
|
||||||
"diesel",
|
|
||||||
"diesel_migrations",
|
|
||||||
"dotenv",
|
|
||||||
"env_logger",
|
|
||||||
"lazy_static",
|
|
||||||
"listenfd",
|
|
||||||
"log",
|
|
||||||
"postgis_diesel",
|
|
||||||
"quick-xml",
|
|
||||||
"r2d2",
|
|
||||||
"reqwest",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tokio",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.64"
|
version = "0.3.64"
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "weather-service"
|
name = "service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
authors = ["Ben Sherriff <hello@bensherriff.com>"]
|
||||||
|
repository = "https://github.com/bensherriff/aviation-weather"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.4.0"
|
actix-web = "4.4.0"
|
||||||
actix-rt = "2.9.0"
|
|
||||||
actix-cors = "0.6.4"
|
actix-cors = "0.6.4"
|
||||||
actix-identity = "0.6.0"
|
actix-web-httpauth = "0.8.1"
|
||||||
|
actix-multipart = "0.6.1"
|
||||||
chrono = { version = "0.4.31", features = ["serde"] }
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
diesel = { version = "2.1.2", features = ["postgres", "r2d2", "uuid", "chrono"] }
|
diesel = { version = "2.1.2", features = ["postgres", "r2d2", "uuid", "chrono"] }
|
||||||
@@ -17,7 +21,6 @@ postgis_diesel = { version = "2.2.1", features = ["serde"] }
|
|||||||
diesel_migrations = { version = "2.1.0", features = ["postgres"] }
|
diesel_migrations = { version = "2.1.0", features = ["postgres"] }
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
listenfd = "1.0.1"
|
|
||||||
quick-xml = { version = "0.30.0", features = ["serialize"] }
|
quick-xml = { version = "0.30.0", features = ["serialize"] }
|
||||||
r2d2 = "0.8.10"
|
r2d2 = "0.8.10"
|
||||||
reqwest = "0.11.21"
|
reqwest = "0.11.21"
|
||||||
@@ -26,3 +29,7 @@ serde_json = "1.0.107"
|
|||||||
tokio = { version = "1.32.0", features = ["macros", "rt", "time"] }
|
tokio = { version = "1.32.0", features = ["macros", "rt", "time"] }
|
||||||
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
argon2 = "0.5.2"
|
||||||
|
jsonwebtoken = "9.0.0"
|
||||||
|
redis = { version = "0.23.3", features = ["tokio-comp", "connection-manager", "r2d2"] }
|
||||||
|
base64 = "0.21.4"
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ help: ## This info
|
|||||||
build: ## Build Docker containers
|
build: ## Build Docker containers
|
||||||
docker compose build
|
docker compose build
|
||||||
|
|
||||||
|
utils: ## Start the utils
|
||||||
|
docker compose up -d db
|
||||||
|
docker compose up -d redis
|
||||||
|
|
||||||
up: ## Start Docker containers
|
up: ## Start Docker containers
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,15 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${DATABASE_PORT:-5432}:5432"
|
- "${DATABASE_PORT:-5432}:5432"
|
||||||
networks:
|
networks:
|
||||||
- weather-backend
|
- backend
|
||||||
|
restart: unless-stopped
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
container_name: weather-redis
|
||||||
|
ports:
|
||||||
|
- ${REDIS_PORT:-6379}:6379
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
service:
|
service:
|
||||||
@@ -27,6 +35,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: 5432
|
DATABASE_PORT: 5432
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
SERVICE_HOST: service
|
SERVICE_HOST: service
|
||||||
SERVICE_PORT: 5000
|
SERVICE_PORT: 5000
|
||||||
ports:
|
ports:
|
||||||
@@ -35,9 +45,10 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
- redis
|
||||||
networks:
|
networks:
|
||||||
- weather-frontend
|
- frontend
|
||||||
- weather-backend
|
- backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
@@ -45,5 +56,5 @@ volumes:
|
|||||||
db_logs:
|
db_logs:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
weather-frontend: {}
|
frontend:
|
||||||
weather-backend: {}
|
backend:
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
|
|
||||||
email TEXT NOT NULL,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
password TEXT NOT NULL,
|
|
||||||
created_at TIMESTAMP NOT NULL,
|
|
||||||
first_name TEXT NOT NULL,
|
|
||||||
last_name TEXT NOT NULL,
|
|
||||||
favorites TEXT[]
|
|
||||||
);
|
|
||||||
11
service/migrations/000002_users/up.sql
Normal file
11
service/migrations/000002_users/up.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
email TEXT PRIMARY KEY NOT NULL,
|
||||||
|
hash TEXT NOT NULL,
|
||||||
|
role TEXT NOT NULL,
|
||||||
|
first_name TEXT NOT NULL,
|
||||||
|
last_name TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
profile_picture TEXT,
|
||||||
|
verified BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::error_handler::ServiceError;
|
use crate::error_handler::ServiceError;
|
||||||
use crate::schema::airports;
|
use crate::db::schema::airports;
|
||||||
use diesel::dsl::count_star;
|
use diesel::dsl::count_star;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
// use log::trace;
|
// use log::trace;
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
use argon2::{password_hash::{rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString, Error as HashError}, Argon2, PasswordHash};
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
use jsonwebtoken::{DecodingKey, EncodingKey, Header, encode, decode, Validation, Algorithm};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod model;
|
||||||
|
mod routes;
|
||||||
|
|
||||||
|
pub use model::*;
|
||||||
|
pub use routes::init_routes;
|
||||||
|
use crate::error_handler::ServiceError;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct TokenClaims {
|
||||||
|
sub: String, // Subject
|
||||||
|
token_uuid: String, // Token UUID
|
||||||
|
iss: String, // Issuer
|
||||||
|
exp: i64, // Expiration time
|
||||||
|
iat: i64, // Issued At
|
||||||
|
nbf: i64 // Not Before
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct TokenDetails {
|
||||||
|
pub token: Option<String>,
|
||||||
|
pub token_uuid: uuid::Uuid,
|
||||||
|
pub email: String,
|
||||||
|
pub expires_in: Option<i64>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_token(token: &str, public_key: &str) -> Result<TokenDetails, ServiceError> {
|
||||||
|
let bytes_public_key = general_purpose::STANDARD.decode(public_key).unwrap();
|
||||||
|
let decoded_public_key = String::from_utf8(bytes_public_key).unwrap();
|
||||||
|
let key = DecodingKey::from_rsa_pem(decoded_public_key.as_bytes())?;
|
||||||
|
let validation = Validation::new(Algorithm::RS256);
|
||||||
|
let decoded = decode::<TokenClaims>(token, &key, &validation)?;
|
||||||
|
let email = decoded.claims.sub;
|
||||||
|
let token_uuid = uuid::Uuid::parse_str(decoded.claims.token_uuid.as_str()).unwrap();
|
||||||
|
Ok(TokenDetails { token: None, token_uuid, email, expires_in: None })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_access_token(email: &str) -> Result<TokenDetails, ServiceError> {
|
||||||
|
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
|
||||||
|
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||||
|
.parse::<i64>()
|
||||||
|
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||||
|
let access_private_key = env::var("ACCESS_TOKEN_PRIVATE_KEY")
|
||||||
|
.expect("ACCESS_TOKEN_PRIVATE_KEY must be set");
|
||||||
|
generate_token(&email, access_token_max_age, &access_private_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_refresh_token(email: &str) -> Result<TokenDetails, ServiceError> {
|
||||||
|
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
|
||||||
|
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||||
|
.parse::<i64>()
|
||||||
|
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||||
|
let refresh_private_key = env::var("REFRESH_TOKEN_PRIVATE_KEY")
|
||||||
|
.expect("REFRESH_TOKEN_PRIVATE_KEY must be set");
|
||||||
|
generate_token(&email, refresh_token_max_age, &refresh_private_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_token(email: &str, ttl: i64, private_key: &str) -> Result<TokenDetails, ServiceError> {
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
let mut token_details = TokenDetails {
|
||||||
|
token: None,
|
||||||
|
token_uuid: uuid::Uuid::new_v4(),
|
||||||
|
email: email.to_string(),
|
||||||
|
expires_in: Some((now + chrono::Duration::minutes(ttl)).timestamp())
|
||||||
|
};
|
||||||
|
let claims = TokenClaims {
|
||||||
|
sub: token_details.email.clone(),
|
||||||
|
iss: "aviation-weather".to_string(),
|
||||||
|
token_uuid: token_details.token_uuid.to_string(),
|
||||||
|
exp: token_details.expires_in.unwrap(),
|
||||||
|
iat: now.timestamp(),
|
||||||
|
nbf: now.timestamp()
|
||||||
|
};
|
||||||
|
let header = Header::new(Algorithm::RS256);
|
||||||
|
let bytes_private_key = general_purpose::STANDARD.decode(private_key).unwrap();
|
||||||
|
let decoded_private_key = String::from_utf8(bytes_private_key).unwrap();
|
||||||
|
let key = EncodingKey::from_rsa_pem(decoded_private_key.as_bytes())?;
|
||||||
|
let token = encode(&header, &claims, &key)?;
|
||||||
|
token_details.token = Some(token);
|
||||||
|
Ok(token_details)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_password(password: &[u8]) -> Result<String, HashError> {
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
Ok(Argon2::default().hash_password(password, &salt)?.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_password(hash: &str, password: &[u8]) -> Result<(), HashError> {
|
||||||
|
let parsed_hash = PasswordHash::new(hash)?;
|
||||||
|
Ok(Argon2::default().verify_password(password, &parsed_hash)?)
|
||||||
|
}
|
||||||
205
service/src/auth/model.rs
Normal file
205
service/src/auth/model.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
use std::{future::{ready, Ready}, env};
|
||||||
|
use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, http};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use log::error;
|
||||||
|
use redis::Commands;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::error_handler::ServiceError;
|
||||||
|
|
||||||
|
use crate::db::{schema::users, connection};
|
||||||
|
|
||||||
|
use super::{hash_password, verify_token};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct RegisterUser {
|
||||||
|
pub email: String,
|
||||||
|
pub password: String,
|
||||||
|
pub first_name: String,
|
||||||
|
pub last_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisterUser {
|
||||||
|
pub fn convert_to_insert(self) -> Result<InsertUser, ServiceError> {
|
||||||
|
let hash = hash_password(self.password.as_bytes())?;
|
||||||
|
Ok(InsertUser {
|
||||||
|
email: self.email.to_lowercase(),
|
||||||
|
hash,
|
||||||
|
role: "user".to_string(),
|
||||||
|
first_name: self.first_name,
|
||||||
|
last_name: self.last_name,
|
||||||
|
updated_at: chrono::Utc::now().naive_utc(),
|
||||||
|
created_at: chrono::Utc::now().naive_utc(),
|
||||||
|
profile_picture: None,
|
||||||
|
verified: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct LoginRequest {
|
||||||
|
pub email: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Queryable, QueryableByName, Serialize, Deserialize)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
pub struct QueryUser {
|
||||||
|
pub email: String,
|
||||||
|
pub hash: String,
|
||||||
|
pub role: String,
|
||||||
|
pub first_name: String,
|
||||||
|
pub last_name: String,
|
||||||
|
pub updated_at: chrono::NaiveDateTime,
|
||||||
|
pub created_at: chrono::NaiveDateTime,
|
||||||
|
pub profile_picture: Option<String>,
|
||||||
|
pub verified: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryUser {
|
||||||
|
pub fn get_by_email(email: &str) -> Result<QueryUser, ServiceError> {
|
||||||
|
let mut conn = connection()?;
|
||||||
|
// Check if the user exists by email, case insensitive
|
||||||
|
|
||||||
|
let user = users::table
|
||||||
|
.filter(users::email.eq(email.to_lowercase()))
|
||||||
|
.first(&mut conn)?;
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Insertable, AsChangeset, Serialize, Deserialize)]
|
||||||
|
#[diesel(table_name = users)]
|
||||||
|
pub struct InsertUser {
|
||||||
|
pub email: String,
|
||||||
|
pub hash: String,
|
||||||
|
pub role: String,
|
||||||
|
pub first_name: String,
|
||||||
|
pub last_name: String,
|
||||||
|
pub updated_at: chrono::NaiveDateTime,
|
||||||
|
pub created_at: chrono::NaiveDateTime,
|
||||||
|
pub profile_picture: Option<String>,
|
||||||
|
pub verified: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InsertUser {
|
||||||
|
pub fn insert(user: Self) -> Result<QueryUser, ServiceError> {
|
||||||
|
let mut conn = connection()?;
|
||||||
|
let user = diesel::insert_into(users::table)
|
||||||
|
.values(user)
|
||||||
|
.get_result(&mut conn)?;
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_profile(email: &str, profile_picture: Option<&str>) -> Result<QueryUser, ServiceError> {
|
||||||
|
let mut conn = connection()?;
|
||||||
|
let user = diesel::update(users::table)
|
||||||
|
.filter(users::email.eq(&email))
|
||||||
|
.set(users::profile_picture.eq(profile_picture))
|
||||||
|
.get_result(&mut conn)?;
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ResponseUser {
|
||||||
|
pub email: String,
|
||||||
|
pub role: String,
|
||||||
|
pub first_name: String,
|
||||||
|
pub last_name: String,
|
||||||
|
pub profile_picture: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<QueryUser> for ResponseUser {
|
||||||
|
fn from(user: QueryUser) -> Self {
|
||||||
|
ResponseUser {
|
||||||
|
email: user.email,
|
||||||
|
role: user.role,
|
||||||
|
first_name: user.first_name,
|
||||||
|
last_name: user.last_name,
|
||||||
|
profile_picture: user.profile_picture,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct JwtAuth {
|
||||||
|
pub token: uuid::Uuid,
|
||||||
|
pub user: ResponseUser
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for JwtAuth {
|
||||||
|
type Error = ActixError;
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
let access_token = match req
|
||||||
|
.cookie("access_token")
|
||||||
|
.map(|c| c.value().to_string())
|
||||||
|
.or_else(|| {
|
||||||
|
req.headers().get(http::header::AUTHORIZATION)
|
||||||
|
.map(|h| h.to_str().unwrap().split_at(7).1.to_string())
|
||||||
|
}) {
|
||||||
|
Some(token) => token,
|
||||||
|
None => return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: "Unauthorized".to_string()
|
||||||
|
})))
|
||||||
|
};
|
||||||
|
|
||||||
|
let public_key = env::var("ACCESS_TOKEN_PUBLIC_KEY")
|
||||||
|
.expect("ACCESS_TOKEN_PUBLIC_KEY must be set");
|
||||||
|
|
||||||
|
let access_token_details = match verify_token(&access_token, &public_key) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to verify access token: {}", err);
|
||||||
|
return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: format!("Failed to verify access token: {}", err)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap();
|
||||||
|
|
||||||
|
let mut conn = match crate::db::redis_connection() {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to get redis connection: {}", err);
|
||||||
|
return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to get redis connection: {}", err)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let user_email = match conn.get::<_, String>(access_token_uuid.clone().to_string()) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => {
|
||||||
|
return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: format!("Access token was not found")
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match QueryUser::get_by_email(&user_email) {
|
||||||
|
Ok(user) => {
|
||||||
|
ready(Ok(JwtAuth { token: access_token_uuid, user: user.into() }))
|
||||||
|
}
|
||||||
|
Err(_) => return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: format!("User was not found")
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_role(auth: &JwtAuth, role: &str) -> Result<(), ServiceError> {
|
||||||
|
if auth.user.role == role {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ServiceError {
|
||||||
|
status: 403,
|
||||||
|
message: "Forbidden".to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
367
service/src/auth/routes.rs
Normal file
367
service/src/auth/routes.rs
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
use actix_web::{get, post, web, HttpResponse, ResponseError, cookie::{Cookie, time::Duration}, HttpRequest};
|
||||||
|
use log::error;
|
||||||
|
use redis::AsyncCommands;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::error_handler::ServiceError;
|
||||||
|
|
||||||
|
use crate::{auth::{LoginRequest, RegisterUser, InsertUser, QueryUser, verify_password, JwtAuth, verify_token, generate_access_token, generate_refresh_token}, db};
|
||||||
|
|
||||||
|
#[post("/register")]
|
||||||
|
async fn register(user: web::Json<RegisterUser>) -> HttpResponse {
|
||||||
|
let register_user = user.0;
|
||||||
|
let insert_user: InsertUser = match register_user.convert_to_insert() {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(err) => return ResponseError::error_response(&err)
|
||||||
|
};
|
||||||
|
match InsertUser::insert(insert_user) {
|
||||||
|
Ok(_) => {
|
||||||
|
HttpResponse::Created().finish()
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
// Obfuscate the service error message to prevent leaking database details
|
||||||
|
if err.status == 409 {
|
||||||
|
return HttpResponse::Conflict().finish();
|
||||||
|
} else {
|
||||||
|
return ResponseError::error_response(&err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/login")]
|
||||||
|
async fn login(request: web::Json<LoginRequest>) -> HttpResponse {
|
||||||
|
let email = request.email.clone();
|
||||||
|
|
||||||
|
let query_user = match QueryUser::get_by_email(&email) {
|
||||||
|
Ok(query_user) => query_user,
|
||||||
|
Err(err) => return ResponseError::error_response(&err)
|
||||||
|
};
|
||||||
|
let hash = &query_user.hash;
|
||||||
|
let password = request.password.as_bytes();
|
||||||
|
match verify_password(hash, password) {
|
||||||
|
Ok(_) => {
|
||||||
|
let access_token_details = match generate_access_token(&email) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to generate access token: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let refresh_token_details = match generate_refresh_token(&email) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to generate refresh token: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conn = match db::redis_async_connection().await {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to get redis connection: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
|
||||||
|
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||||
|
.parse::<i64>()
|
||||||
|
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||||
|
|
||||||
|
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
|
||||||
|
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||||
|
.parse::<i64>()
|
||||||
|
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||||
|
|
||||||
|
let access_result: redis::RedisResult<()> = conn.set_ex(access_token_details.token_uuid.to_string(), &email, (access_token_max_age * 60) as usize).await;
|
||||||
|
if let Err(err) = access_result {
|
||||||
|
error!("Failed to set access token in redis: {}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to set access token in redis: {}", err)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token_details.token_uuid.to_string(), &email, (refresh_token_max_age * 60) as usize).await;
|
||||||
|
if let Err(err) = refresh_result {
|
||||||
|
error!("Failed to set refresh token in redis: {}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to set refresh token in redis: {}", err)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_cookie = Cookie::build("access_token", access_token_details.token.clone().unwrap())
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(access_token_max_age * 60, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.secure(true)
|
||||||
|
.finish();
|
||||||
|
let refresh_cookie = Cookie::build("refresh_token", refresh_token_details.token.clone().unwrap())
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(refresh_token_max_age * 60, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.secure(true)
|
||||||
|
.finish();
|
||||||
|
let logged_in_cookie = Cookie::build("logged_in", "true")
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(access_token_max_age * 60, 0))
|
||||||
|
.http_only(false)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap();
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.cookie(access_cookie)
|
||||||
|
.cookie(refresh_cookie)
|
||||||
|
.cookie(logged_in_cookie)
|
||||||
|
.json(JwtAuth { token: access_token_uuid, user: query_user.into() })
|
||||||
|
},
|
||||||
|
Err(err) => ResponseError::error_response(&ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: err.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct RefreshParams {
|
||||||
|
refresh_token_rotation: Option<bool>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/refresh")]
|
||||||
|
async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||||
|
let params = match web::Query::<RefreshParams>::from_query(req.query_string()) {
|
||||||
|
Ok(params) => params,
|
||||||
|
Err(err) => return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 422,
|
||||||
|
message: err.to_string()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let refresh_token = match req.cookie("refresh_token") {
|
||||||
|
Some(cookie) => cookie.value().to_string(),
|
||||||
|
None => return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: "Refresh token not found".to_string()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let public_key = env::var("REFRESH_TOKEN_PUBLIC_KEY")
|
||||||
|
.expect("REFRESH_TOKEN_PUBLIC_KEY must be set");
|
||||||
|
let refresh_token_details = match verify_token(&refresh_token, &public_key) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => return ResponseError::error_response(&err)
|
||||||
|
};
|
||||||
|
|
||||||
|
let email = refresh_token_details.email.clone();
|
||||||
|
|
||||||
|
match QueryUser::get_by_email(&email) {
|
||||||
|
Ok(query_user) => {
|
||||||
|
let access_token_details = match generate_access_token(&email) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to generate access token: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conn = match db::redis_async_connection().await {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to get redis connection: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete old auth token if it exists
|
||||||
|
match req.cookie("access_token") {
|
||||||
|
Some(cookie) => {
|
||||||
|
let access_token = cookie.value().to_string();
|
||||||
|
let public_key = env::var("ACCESS_TOKEN_PUBLIC_KEY")
|
||||||
|
.expect("ACCESS_TOKEN_PUBLIC_KEY must be set");
|
||||||
|
match verify_token(&access_token, &public_key) {
|
||||||
|
Ok(token_details) => {
|
||||||
|
let _: redis::RedisResult<()> = conn.del(token_details.token_uuid.to_string()).await;
|
||||||
|
|
||||||
|
},
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
|
||||||
|
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||||
|
.parse::<i64>()
|
||||||
|
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||||
|
|
||||||
|
let access_result: redis::RedisResult<()> = conn.set_ex(access_token_details.token_uuid.to_string(), &email, (access_token_max_age * 60) as usize).await;
|
||||||
|
if let Err(err) = access_result {
|
||||||
|
error!("Failed to set access token in redis: {}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to set access token in redis: {}", err)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_cookie = Cookie::build("access_token", access_token_details.token.clone().unwrap())
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(access_token_max_age * 60, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.secure(true)
|
||||||
|
.finish();
|
||||||
|
let logged_in_cookie = Cookie::build("logged_in", "true")
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(access_token_max_age * 60, 0))
|
||||||
|
.http_only(false)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap();
|
||||||
|
|
||||||
|
// Refresh the refresh token if requested
|
||||||
|
let refresh_token_rotation = match params.refresh_token_rotation {
|
||||||
|
Some(refresh_token_rotation) => refresh_token_rotation,
|
||||||
|
None => false
|
||||||
|
};
|
||||||
|
if refresh_token_rotation {
|
||||||
|
// Delete the old refresh token
|
||||||
|
let _: redis::RedisResult<()> = conn.del(refresh_token_details.token_uuid.to_string()).await;
|
||||||
|
|
||||||
|
let refresh_token_details = match generate_refresh_token(&refresh_token_details.email) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to generate refresh token: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
|
||||||
|
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||||
|
.parse::<i64>()
|
||||||
|
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||||
|
|
||||||
|
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token_details.token_uuid.to_string(), &refresh_token_details.email, (refresh_token_max_age * 60) as usize).await;
|
||||||
|
if let Err(err) = refresh_result {
|
||||||
|
error!("Failed to set refresh token in redis: {}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to set refresh token in redis: {}", err)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let refresh_cookie = Cookie::build("refresh_token", refresh_token_details.token.clone().unwrap())
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(refresh_token_max_age * 60, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.secure(true)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.cookie(refresh_cookie)
|
||||||
|
.cookie(access_cookie)
|
||||||
|
.cookie(logged_in_cookie)
|
||||||
|
.json(JwtAuth { token: access_token_uuid, user: query_user.into() })
|
||||||
|
} else {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.cookie(access_cookie)
|
||||||
|
.cookie(logged_in_cookie)
|
||||||
|
.json(JwtAuth { token: access_token_uuid, user: query_user.into() })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/logout")]
|
||||||
|
async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
|
||||||
|
let refresh_token = match req.cookie("refresh_token") {
|
||||||
|
Some(cookie) => cookie.value().to_string(),
|
||||||
|
None => return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: "Refresh token not found".to_string()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let public_key = env::var("REFRESH_TOKEN_PUBLIC_KEY")
|
||||||
|
.expect("REFRESH_TOKEN_PUBLIC_KEY must be set");
|
||||||
|
let refresh_token_details = match verify_token(&refresh_token, &public_key) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => return ResponseError::error_response(&err)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conn = match db::redis_async_connection().await {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to get redis connection: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_result: redis::RedisResult<()> = conn.del(&[
|
||||||
|
refresh_token_details.token_uuid.to_string(),
|
||||||
|
auth.token.to_string()
|
||||||
|
]).await;
|
||||||
|
if let Err(err) = access_result {
|
||||||
|
error!("Failed to set access token in redis: {}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to set access token in redis: {}", err)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_cookie = Cookie::build("access_token", "")
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(-1, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.finish();
|
||||||
|
let refresh_cookie = Cookie::build("refresh_token", "")
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(-1, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.finish();
|
||||||
|
let logged_in_cookie = Cookie::build("logged_in", "")
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(-1, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.cookie(access_cookie)
|
||||||
|
.cookie(refresh_cookie)
|
||||||
|
.cookie(logged_in_cookie)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/me")]
|
||||||
|
async fn me(auth: JwtAuth) -> HttpResponse {
|
||||||
|
HttpResponse::Ok().json(auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/roles")]
|
||||||
|
async fn roles() -> HttpResponse {
|
||||||
|
HttpResponse::Ok().json(vec!["admin", "user"])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_routes(config: &mut web::ServiceConfig) {
|
||||||
|
let r = RegisterUser {
|
||||||
|
email: "admin".to_string(),
|
||||||
|
password: "admin".to_string(),
|
||||||
|
first_name: "Admin".to_string(),
|
||||||
|
last_name: "Admin".to_string(),
|
||||||
|
};
|
||||||
|
let mut u = r.convert_to_insert().unwrap();
|
||||||
|
u.role = "admin".to_string();
|
||||||
|
u.verified = true;
|
||||||
|
let _ = InsertUser::insert(u);
|
||||||
|
config.service(web::scope("auth")
|
||||||
|
.service(register)
|
||||||
|
.service(login)
|
||||||
|
.service(refresh)
|
||||||
|
.service(logout)
|
||||||
|
.service(me)
|
||||||
|
.service(roles)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::{error_handler::ServiceError, airports::{InsertAirport, QueryAirport}};
|
use crate::{error_handler::ServiceError, airports::{InsertAirport, QueryAirport}};
|
||||||
use diesel::{r2d2::ConnectionManager, PgConnection};
|
use diesel::{r2d2::ConnectionManager, PgConnection};
|
||||||
|
use redis::{Client as RedisClient, aio::Connection as RedisConnection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::diesel_migrations::MigrationHarness;
|
use crate::diesel_migrations::MigrationHarness;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
@@ -7,6 +8,8 @@ use log::{error, debug, info, warn};
|
|||||||
use r2d2;
|
use r2d2;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
|
type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
|
||||||
pub type DbConnection = r2d2::PooledConnection<ConnectionManager<PgConnection>>;
|
pub type DbConnection = r2d2::PooledConnection<ConnectionManager<PgConnection>>;
|
||||||
|
|
||||||
@@ -16,29 +19,24 @@ lazy_static! {
|
|||||||
static ref POOL: Pool = {
|
static ref POOL: Pool = {
|
||||||
let username = env::var("DATABASE_USER").expect("Database username is not set");
|
let username = env::var("DATABASE_USER").expect("Database username is not set");
|
||||||
let password = env::var("DATABASE_PASSWORD").expect("Database password is not set");
|
let password = env::var("DATABASE_PASSWORD").expect("Database password is not set");
|
||||||
let host = match env::var("DATABASE_HOST") {
|
let host = env::var("DATABASE_HOST").unwrap_or("localhost".to_string());
|
||||||
Ok(h) => h,
|
|
||||||
Err(_) => {
|
|
||||||
warn!("Defaulting to DATABASE_HOST localhost");
|
|
||||||
"localhost".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let name = env::var("DATABASE_NAME").expect("Database name is not set");
|
let name = env::var("DATABASE_NAME").expect("Database name is not set");
|
||||||
let port = match env::var("DATABASE_PORT") {
|
let port = env::var("DATABASE_PORT").unwrap_or("5432".to_string());
|
||||||
Ok(p) => p,
|
|
||||||
Err(_) => {
|
|
||||||
warn!("Defaulting to DATABASE_PORT 5432");
|
|
||||||
"5432".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let url = format!("postgres://{}:{}@{}:{}/{}", username, password, host, port, name);
|
let url = format!("postgres://{}:{}@{}:{}/{}", username, password, host, port, name);
|
||||||
let manager = ConnectionManager::<PgConnection>::new(url);
|
let manager = ConnectionManager::<PgConnection>::new(url);
|
||||||
Pool::builder().test_on_check_out(true).build(manager).expect("Failed to create db pool")
|
Pool::builder().test_on_check_out(true).build(manager).expect("Failed to create db pool")
|
||||||
};
|
};
|
||||||
|
static ref REDIS: RedisClient = {
|
||||||
|
let host = env::var("REDIS_HOST").unwrap_or("localhost".to_string());
|
||||||
|
let port = env::var("REDIS_PORT").unwrap_or("6379".to_string());
|
||||||
|
let url = format!("redis://{}:{}", host, port);
|
||||||
|
RedisClient::open(url).expect("Failed to create redis client")
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
lazy_static::initialize(&POOL);
|
lazy_static::initialize(&POOL);
|
||||||
|
lazy_static::initialize(&REDIS);
|
||||||
let mut pool: DbConnection = connection().expect("Failed to get db connection");
|
let mut pool: DbConnection = connection().expect("Failed to get db connection");
|
||||||
match pool.run_pending_migrations(MIGRATIONS) {
|
match pool.run_pending_migrations(MIGRATIONS) {
|
||||||
Ok(_) => info!("Database initialized"),
|
Ok(_) => info!("Database initialized"),
|
||||||
@@ -51,6 +49,16 @@ pub fn connection() -> Result<DbConnection, ServiceError> {
|
|||||||
.map_err(|e| ServiceError::new(500, format!("Failed getting db connection: {}", e)))
|
.map_err(|e| ServiceError::new(500, format!("Failed getting db connection: {}", e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn redis_connection() -> Result<redis::Connection, ServiceError> {
|
||||||
|
let conn = REDIS.get_connection()?;
|
||||||
|
Ok(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn redis_async_connection() -> Result<RedisConnection, ServiceError> {
|
||||||
|
let conn = REDIS.get_async_connection().await?;
|
||||||
|
Ok(conn)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn import_data() {
|
pub fn import_data() {
|
||||||
let path = "airport-codes.json";
|
let path = "airport-codes.json";
|
||||||
debug!("Importing data from {}", path);
|
debug!("Importing data from {}", path);
|
||||||
@@ -48,13 +48,15 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
use diesel::sql_types::*;
|
users (email) {
|
||||||
use crate::users::PgUserType;
|
email -> Text,
|
||||||
users (id) {
|
hash -> Text,
|
||||||
id -> Uuid,
|
role -> Text,
|
||||||
first_name -> Text,
|
first_name -> Text,
|
||||||
last_name -> Text,
|
last_name -> Text,
|
||||||
user_type -> PgUserType,
|
updated_at -> Timestamp,
|
||||||
favorites -> Array<Text>
|
created_at -> Timestamp,
|
||||||
|
profile_picture -> Nullable<Text>,
|
||||||
|
verified -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,60 +8,100 @@ use std::fmt;
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ServiceError {
|
pub struct ServiceError {
|
||||||
pub error_status_code: u16,
|
pub status: u16,
|
||||||
pub error_message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceError {
|
impl ServiceError {
|
||||||
pub fn new(error_status_code: u16, error_message: String) -> ServiceError {
|
pub fn new(status: u16, message: String) -> ServiceError {
|
||||||
ServiceError {
|
ServiceError {
|
||||||
error_status_code,
|
status,
|
||||||
error_message,
|
message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_http_response(&self) -> HttpResponse {
|
pub fn to_http_response(&self) -> HttpResponse {
|
||||||
let status_code = match StatusCode::from_u16(self.error_status_code) {
|
let status = match StatusCode::from_u16(self.status) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("{}", err);
|
warn!("{}", err);
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
HttpResponse::build(status_code).body(self.error_message.to_string())
|
HttpResponse::build(status).body(self.message.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ServiceError {
|
impl fmt::Display for ServiceError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
f.write_str(self.error_message.as_str())
|
f.write_str(self.message.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DieselError> for ServiceError {
|
impl From<DieselError> for ServiceError {
|
||||||
fn from(error: DieselError) -> ServiceError {
|
fn from(error: DieselError) -> ServiceError {
|
||||||
match error {
|
match error {
|
||||||
DieselError::DatabaseError(_, err) => ServiceError::new(409, err.message().to_string()),
|
DieselError::DatabaseError(kind, err) => {
|
||||||
|
match kind {
|
||||||
|
diesel::result::DatabaseErrorKind::UniqueViolation => {
|
||||||
|
ServiceError::new(409, err.message().to_string())
|
||||||
|
},
|
||||||
|
_ => ServiceError::new(500, err.message().to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
DieselError::NotFound => {
|
DieselError::NotFound => {
|
||||||
ServiceError::new(404, "The record was not found".to_string())
|
ServiceError::new(404, "The record was not found".to_string())
|
||||||
}
|
},
|
||||||
|
DieselError::SerializationError(err) => {
|
||||||
|
ServiceError::new(422, err.to_string())
|
||||||
|
},
|
||||||
err => ServiceError::new(500, format!("Unknown Diesel error: {}", err)),
|
err => ServiceError::new(500, format!("Unknown Diesel error: {}", err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for ServiceError {
|
||||||
|
fn from(error: reqwest::Error) -> ServiceError {
|
||||||
|
ServiceError::new(500, format!("Unknown reqwest error: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for ServiceError {
|
||||||
|
fn from(error: serde_json::Error) -> ServiceError {
|
||||||
|
ServiceError::new(500, format!("Unknown serde_json error: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<argon2::password_hash::Error> for ServiceError {
|
||||||
|
fn from(error: argon2::password_hash::Error) -> ServiceError {
|
||||||
|
ServiceError::new(500, format!("Unknown argon2 error: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<jsonwebtoken::errors::Error> for ServiceError {
|
||||||
|
fn from(error: jsonwebtoken::errors::Error) -> ServiceError {
|
||||||
|
ServiceError::new(500, format!("Unknown jsonwebtoken error: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<redis::RedisError> for ServiceError {
|
||||||
|
fn from(error: redis::RedisError) -> ServiceError {
|
||||||
|
ServiceError::new(500, format!("Unknown redis error: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ResponseError for ServiceError {
|
impl ResponseError for ServiceError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
let status_code = match StatusCode::from_u16(self.error_status_code) {
|
let status = match StatusCode::from_u16(self.status) {
|
||||||
Ok(status_code) => status_code,
|
Ok(status) => status,
|
||||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
let error_message = match status_code.as_u16() < 500 {
|
let message = match status.as_u16() < 500 {
|
||||||
true => self.error_message.clone(),
|
true => self.message.clone(),
|
||||||
false => "Internal server error".to_string(),
|
false => "Internal server error".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpResponse::build(status_code).json(json!({ "message": error_message }))
|
HttpResponse::build(status).json(json!({ "status": status.as_u16(), "message": message }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
extern crate actix_web;
|
|
||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate diesel_migrations;
|
extern crate diesel_migrations;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_web::{App, HttpServer, middleware::Logger};
|
use actix_web::{App, HttpServer, middleware::Logger};
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use env_logger::Env;
|
use log::{info, error};
|
||||||
use listenfd::ListenFd;
|
|
||||||
use log::{debug, warn};
|
|
||||||
|
|
||||||
mod airports;
|
mod airports;
|
||||||
mod auth;
|
mod auth;
|
||||||
@@ -16,53 +15,43 @@ mod db;
|
|||||||
mod error_handler;
|
mod error_handler;
|
||||||
mod metars;
|
mod metars;
|
||||||
mod users;
|
mod users;
|
||||||
mod schema;
|
|
||||||
mod scheduler;
|
mod scheduler;
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
if std::env::var_os("RUST_LOG").is_none() {
|
env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "warn,siren=info"));
|
||||||
std::env::set_var("RUST_LOG", "info,actix=info,diesel_migrations=warn,reqwest=warn,hyper=warn");
|
|
||||||
}
|
|
||||||
env_logger::init_from_env(Env::default().default_filter_or("info"));
|
|
||||||
db::init();
|
db::init();
|
||||||
scheduler::update_airports();
|
scheduler::update_airports();
|
||||||
|
|
||||||
let mut listenfd = ListenFd::from_env();
|
let host = env::var("SERVICE_HOST").unwrap_or("localhost".to_string());
|
||||||
let mut server = HttpServer::new(|| {
|
let port = env::var("SERVICE_PORT").unwrap_or("5000".to_string());
|
||||||
|
|
||||||
|
let server = match HttpServer::new(move || {
|
||||||
let cors = Cors::default()
|
let cors = Cors::default()
|
||||||
.allow_any_origin()
|
.allow_any_origin()
|
||||||
.allow_any_method()
|
.allow_any_method()
|
||||||
.allow_any_header();
|
.allow_any_header()
|
||||||
|
.supports_credentials()
|
||||||
|
.max_age(3600);
|
||||||
App::new()
|
App::new()
|
||||||
.configure(airports::init_routes)
|
|
||||||
.configure(metars::init_routes)
|
|
||||||
.configure(users::init_routes)
|
|
||||||
.wrap(cors)
|
.wrap(cors)
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
});
|
.configure(airports::init_routes)
|
||||||
|
.configure(metars::init_routes)
|
||||||
|
.configure(auth::init_routes)
|
||||||
|
.configure(users::init_routes)
|
||||||
|
})
|
||||||
|
.bind(format!("{}:{}", host, port)) {
|
||||||
|
Ok(b) => {
|
||||||
|
info!("Binding server to {}:{}", host, port);
|
||||||
|
b
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
error!("Could not bind server: {}", err);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
server = match listenfd.take_tcp_listener(0)? {
|
|
||||||
Some(listener) => server.listen(listener)?,
|
|
||||||
None => {
|
|
||||||
let host = match std::env::var("SERVICE_HOST") {
|
|
||||||
Ok(h) => h,
|
|
||||||
Err(_) => {
|
|
||||||
warn!("Defaulting to SERVICE_HOST localhost");
|
|
||||||
"localhost".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let port = match std::env::var("SERVICE_PORT") {
|
|
||||||
Ok(p) => p,
|
|
||||||
Err(_) => {
|
|
||||||
warn!("Defaulting to SERVICE_PORT 5000");
|
|
||||||
"5000".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
debug!("Binding server to {}:{}", host, port);
|
|
||||||
server.bind(format!("{}:{}", host, port))?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
server.run().await
|
server.run().await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{error_handler::ServiceError, db};
|
use crate::{error_handler::ServiceError, db};
|
||||||
use crate::schema::metars::{self};
|
use crate::db::schema::metars::{self};
|
||||||
use diesel::{prelude::*, sql_query};
|
use diesel::{prelude::*, sql_query};
|
||||||
use log::{warn, trace};
|
use log::{warn, trace};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -375,7 +375,7 @@ impl QueryMetar {
|
|||||||
let mut conn = db::connection()?;
|
let mut conn = db::connection()?;
|
||||||
let db_metars: Vec<Self> = match sql_query(format!("SELECT DISTINCT ON (station_id) * FROM metars WHERE station_id IN ({}) ORDER BY station_id, observation_time DESC", station_query.join(","))).load(&mut conn) {
|
let db_metars: Vec<Self> = match sql_query(format!("SELECT DISTINCT ON (station_id) * FROM metars WHERE station_id IN ({}) ORDER BY station_id, observation_time DESC", station_query.join(","))).load(&mut conn) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(err) => return Err(ServiceError { error_status_code: 500, error_message: format!("{}", err) })
|
Err(err) => return Err(ServiceError { status: 500, message: format!("{}", err) })
|
||||||
};
|
};
|
||||||
return Ok(db_metars);
|
return Ok(db_metars);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
mod model;
|
|
||||||
mod routes;
|
mod routes;
|
||||||
mod user_type;
|
|
||||||
|
|
||||||
pub use user_type::PgUserType;
|
|
||||||
pub use model::*;
|
|
||||||
pub use routes::init_routes;
|
pub use routes::init_routes;
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
use std::{future::Future, pin::Pin, sync::RwLock};
|
|
||||||
|
|
||||||
use actix_web::{dev::Payload, error::ErrorUnauthorized, web, Error, FromRequest, HttpRequest};
|
|
||||||
use actix_identity::Identity;
|
|
||||||
use diesel::{query_builder::AsChangeset, prelude::Insertable};
|
|
||||||
use log::warn;
|
|
||||||
use crate::schema::users;
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
use super::user_type::UserType;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, AsChangeset, Insertable)]
|
|
||||||
#[diesel(table_name = users)]
|
|
||||||
pub struct InsertUser {
|
|
||||||
first_name: String,
|
|
||||||
last_name: String,
|
|
||||||
user_type: UserType,
|
|
||||||
favorites: Vec<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl FromRequest for InsertUser {
|
|
||||||
// type Config = ();
|
|
||||||
// type Error = Error;
|
|
||||||
// type Future = Pin<Box<dyn Future<Output = Result<Self, Error>>>>;
|
|
||||||
|
|
||||||
// fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
|
|
||||||
// let fut = Identity::from_request(req, pl);
|
|
||||||
// let sessions: Option<&web::Data<RwLock<Sessions>>> = req.app_data();
|
|
||||||
// if sessions.is_none() {
|
|
||||||
// warn!("sessions is empty(none)!");
|
|
||||||
// return Box::pin(async { Err(ErrorUnauthorized("unauthorized")) });
|
|
||||||
// }
|
|
||||||
// let sessions = sessions.unwrap().clone();
|
|
||||||
// Box::pin(async move {
|
|
||||||
// if let Some(identity) = fut.await?.identity() {
|
|
||||||
// if let Some(user) = sessions
|
|
||||||
// .read()
|
|
||||||
// .unwrap()
|
|
||||||
// .map
|
|
||||||
// .get(&identity)
|
|
||||||
// .map(|x| x.clone())
|
|
||||||
// {
|
|
||||||
// return Ok(user);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Err(ErrorUnauthorized("unauthorized"))
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -1,29 +1,4 @@
|
|||||||
use actix_web::{get, post, delete, put, web, HttpResponse};
|
use actix_web::{get, post, delete, web, HttpResponse};
|
||||||
|
|
||||||
#[get("users")]
|
|
||||||
async fn get() -> HttpResponse {
|
|
||||||
HttpResponse::NotImplemented().finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("users/{id}")]
|
|
||||||
async fn get_all() -> HttpResponse {
|
|
||||||
HttpResponse::NotImplemented().finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("users")]
|
|
||||||
async fn create() -> HttpResponse {
|
|
||||||
HttpResponse::NotImplemented().finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[delete("users")]
|
|
||||||
async fn delete() -> HttpResponse {
|
|
||||||
HttpResponse::NotImplemented().finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[put("users")]
|
|
||||||
async fn update() -> HttpResponse {
|
|
||||||
HttpResponse::NotImplemented().finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("users/favorites")]
|
#[get("users/favorites")]
|
||||||
async fn get_favorites() -> HttpResponse {
|
async fn get_favorites() -> HttpResponse {
|
||||||
@@ -41,10 +16,6 @@ async fn delete_favorite() -> HttpResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_routes(config: &mut web::ServiceConfig) {
|
pub fn init_routes(config: &mut web::ServiceConfig) {
|
||||||
config.service(get);
|
|
||||||
config.service(create);
|
|
||||||
config.service(delete);
|
|
||||||
config.service(update);
|
|
||||||
config.service(get_favorites);
|
config.service(get_favorites);
|
||||||
config.service(add_favorite);
|
config.service(add_favorite);
|
||||||
config.service(delete_favorite);
|
config.service(delete_favorite);
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use diesel::{sql_types::SqlType, deserialize::{FromSqlRow, FromSql, self}, expression::AsExpression, serialize::{ToSql, Output, self, IsNull}, pg::{Pg, PgValue}};
|
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
#[derive(SqlType)]
|
|
||||||
#[diesel(postgres_type(name = "User_Type"))]
|
|
||||||
pub struct PgUserType;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, FromSqlRow, AsExpression, Eq)]
|
|
||||||
#[diesel(sql_type = PgUserType)]
|
|
||||||
pub enum UserType {
|
|
||||||
Admin,
|
|
||||||
User,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql<PgUserType, Pg> for UserType {
|
|
||||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
|
|
||||||
match *self {
|
|
||||||
Self::Admin => out.write_all(b"admin")?,
|
|
||||||
Self::User => out.write_all(b"user")?,
|
|
||||||
}
|
|
||||||
Ok(IsNull::No)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql<PgUserType, Pg> for UserType {
|
|
||||||
fn from_sql(bytes: PgValue<'_>) -> deserialize::Result<Self> {
|
|
||||||
match bytes.as_bytes() {
|
|
||||||
b"admin" => Ok(Self::Admin),
|
|
||||||
b"user" => Ok(Self::User),
|
|
||||||
_ => Err("Unrecognized enum variant".into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user