Added user auth

This commit is contained in:
2023-11-17 09:07:30 -05:00
parent 16543a5709
commit e2bd270d7c
25 changed files with 1268 additions and 450 deletions

View File

@@ -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
View File

@@ -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"

View File

@@ -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"
@@ -25,4 +28,8 @@ serde = {version = "1.0.188", features = ["derive"]}
serde_json = "1.0.107" 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"

View File

@@ -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

View File

@@ -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:

View File

@@ -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[]
);

View 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
);

View File

@@ -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;

View File

@@ -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
View 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
View 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)
);
}

View File

@@ -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);

View File

@@ -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,
} }
} }

View File

@@ -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) => {
DieselError::NotFound => { match kind {
ServiceError::new(404, "The record was not found".to_string()) diesel::result::DatabaseErrorKind::UniqueViolation => {
} ServiceError::new(409, err.message().to_string())
err => ServiceError::new(500, format!("Unknown Diesel error: {}", err)), },
_ => ServiceError::new(500, err.message().to_string())
} }
},
DieselError::NotFound => {
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)),
} }
}
}
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 }))
} }
} }

View File

@@ -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)
server = match listenfd.take_tcp_listener(0)? { .configure(auth::init_routes)
Some(listener) => server.listen(listener)?, .configure(users::init_routes)
None => { })
let host = match std::env::var("SERVICE_HOST") { .bind(format!("{}:{}", host, port)) {
Ok(h) => h, Ok(b) => {
Err(_) => { info!("Binding server to {}:{}", host, port);
warn!("Defaulting to SERVICE_HOST localhost"); b
"localhost".to_string() },
} Err(err) => {
}; error!("Could not bind server: {}", err);
let port = match std::env::var("SERVICE_PORT") { return Err(err);
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
} }

View File

@@ -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);
} }

View File

@@ -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;

View File

@@ -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"))
// })
// }
// }

View File

@@ -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);

View File

@@ -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()),
}
}
}