Token validation
This commit is contained in:
parent
ad046bc389
commit
2c0104ec2d
2
.env
2
.env
@ -2,3 +2,5 @@ DATABASE_URL=postgres://postgres@localhost/bazzar
|
||||
PASS_SALT=18CHwV7eGFAea16z+qMKZg
|
||||
RUST_LOG=debug
|
||||
KEY_SECRET="NEPJs#8jjn8SK8GC7QEC^*P844UgsyEbQB8mRWXkT%3mPrwewZoc25MMby9H#R*w2KzaQgMkk#Pif$kxrLy*N5L!Ch%jxbWoa%gb"
|
||||
JWT_SECRET="42^iFq&ZnQbUf!hwGWXd&CpyY6QQyJmkPU%esFCvne5&Ejcb3nJ4&GyHZp!MArZLf^9*5c6!!VgM$iZ8T%d#&bWTi&xbZk2S@4RN"
|
||||
PGDATESTYLE=
|
||||
|
320
Cargo.lock
generated
320
Cargo.lock
generated
@ -693,6 +693,21 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-httpauth"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c25a48b4684f90520183cd1a688e5f4f7e9905835fa75d02c0fe4f60fcdbe6"
|
||||
dependencies = [
|
||||
"actix-service 2.0.2",
|
||||
"actix-utils 3.0.0",
|
||||
"actix-web 4.0.1",
|
||||
"base64 0.13.0",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-opentelemetry"
|
||||
version = "0.12.0"
|
||||
@ -958,6 +973,7 @@ dependencies = [
|
||||
"actix-rt 2.7.0",
|
||||
"actix-session",
|
||||
"actix-web 4.0.1",
|
||||
"actix-web-httpauth",
|
||||
"actix-web-opentelemetry",
|
||||
"argon2",
|
||||
"chrono",
|
||||
@ -966,13 +982,17 @@ dependencies = [
|
||||
"futures",
|
||||
"futures-util",
|
||||
"gumdrop",
|
||||
"hmac",
|
||||
"jwt",
|
||||
"log",
|
||||
"oauth2",
|
||||
"parking_lot 0.12.0",
|
||||
"password-hash",
|
||||
"pretty_env_logger",
|
||||
"rand_core 0.6.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.2",
|
||||
"sqlx",
|
||||
"sqlx-core",
|
||||
"tera",
|
||||
@ -1028,6 +1048,15 @@ dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
@ -1225,7 +1254,7 @@ dependencies = [
|
||||
"hmac",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"sha2",
|
||||
"sha2 0.10.2",
|
||||
"subtle",
|
||||
"time 0.3.9",
|
||||
"version_check",
|
||||
@ -1372,6 +1401,15 @@ dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.3"
|
||||
@ -1691,8 +1729,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1889,6 +1929,17 @@ dependencies = [
|
||||
"itoa 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"http",
|
||||
"pin-project-lite 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range"
|
||||
version = "0.1.5"
|
||||
@ -1922,6 +1973,43 @@ dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.13",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa 1.0.1",
|
||||
"pin-project-lite 0.2.8",
|
||||
"socket2 0.4.4",
|
||||
"tokio 1.17.0",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"rustls 0.20.4",
|
||||
"tokio 1.17.0",
|
||||
"tokio-rustls 0.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
@ -1988,9 +2076,15 @@ dependencies = [
|
||||
"socket2 0.3.19",
|
||||
"widestring",
|
||||
"winapi 0.3.9",
|
||||
"winreg",
|
||||
"winreg 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
||||
|
||||
[[package]]
|
||||
name = "ipnetwork"
|
||||
version = "0.17.0"
|
||||
@ -2036,6 +2130,21 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jwt"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"crypto-common",
|
||||
"digest 0.10.3",
|
||||
"hmac",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
@ -2396,6 +2505,26 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oauth2"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80e47cfc4c0a1a519d9a025ebfbac3a2439d1b5cdf397d72dcb79b11d9920dab"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"chrono",
|
||||
"getrandom 0.2.6",
|
||||
"http",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sha2 0.9.9",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.27.1"
|
||||
@ -2884,6 +3013,44 @@ version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"bytes 1.1.0",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.13",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.8",
|
||||
"rustls 0.20.4",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded 0.7.1",
|
||||
"tokio 1.17.0",
|
||||
"tokio-rustls 0.23.3",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.22.3",
|
||||
"winreg 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resolv-conf"
|
||||
version = "0.6.3"
|
||||
@ -2953,8 +3120,29 @@ dependencies = [
|
||||
"base64 0.13.0",
|
||||
"log",
|
||||
"ring",
|
||||
"sct",
|
||||
"webpki",
|
||||
"sct 0.6.1",
|
||||
"webpki 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"sct 0.7.0",
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2988,6 +3176,16 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
@ -3040,6 +3238,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7868ad3b8196a8a0aea99a8220b124278ee5320a55e4fde97794b6f85b1a377"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.6.1"
|
||||
@ -3102,6 +3309,19 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.2"
|
||||
@ -3241,11 +3461,11 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rust_decimal",
|
||||
"rustls",
|
||||
"rustls 0.19.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha-1 0.10.0",
|
||||
"sha2",
|
||||
"sha2 0.10.2",
|
||||
"smallvec",
|
||||
"sqlformat",
|
||||
"sqlx-rt",
|
||||
@ -3255,8 +3475,8 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"uuid",
|
||||
"webpki",
|
||||
"webpki-roots",
|
||||
"webpki 0.21.4",
|
||||
"webpki-roots 0.21.1",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
@ -3273,7 +3493,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha2 0.10.2",
|
||||
"sqlx-core",
|
||||
"sqlx-rt",
|
||||
"syn",
|
||||
@ -3289,7 +3509,7 @@ dependencies = [
|
||||
"actix-rt 2.7.0",
|
||||
"once_cell",
|
||||
"tokio 1.17.0",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3597,9 +3817,20 @@ version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"rustls 0.19.1",
|
||||
"tokio 1.17.0",
|
||||
"webpki",
|
||||
"webpki 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e"
|
||||
dependencies = [
|
||||
"rustls 0.20.4",
|
||||
"tokio 1.17.0",
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3678,6 +3909,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.34"
|
||||
@ -3760,6 +3997,12 @@ dependencies = [
|
||||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "twoway"
|
||||
version = "0.2.2"
|
||||
@ -3915,6 +4158,7 @@ dependencies = [
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3975,6 +4219,16 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
@ -4018,6 +4272,18 @@ dependencies = [
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.80"
|
||||
@ -4067,13 +4333,32 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
"webpki 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf"
|
||||
dependencies = [
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4187,6 +4472,15 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||
actix = { version = "0.13.0" }
|
||||
actix-rt = { version = "2.7.0" }
|
||||
actix-web = { version = "4.0.1" }
|
||||
actix-web-httpauth = { version = "0.6.0" }
|
||||
actix-auth = { version = "0.1.0" }
|
||||
actix-cors = { version = "0.6.1" }
|
||||
actix-files = { version = "0.6.0" }
|
||||
@ -52,3 +53,9 @@ rand_core = { version = "0.6", features = ["std"] }
|
||||
tokio = { version = "1.17.0", features = ["full"] }
|
||||
futures = { version = "0.3.21" }
|
||||
futures-util = { version = "0.3.21" }
|
||||
|
||||
jwt = { version = "0.16.0", features = [] }
|
||||
hmac = { version = "0.12.1" }
|
||||
sha2 = { version = "0.10.2" }
|
||||
|
||||
oauth2 = { version = "4.1.0" }
|
||||
|
@ -34,6 +34,10 @@
|
||||
<div style="display: flex;justify-content: space-between;">
|
||||
<div style="width: 49%">
|
||||
<form style="width: 100%">
|
||||
<fieldset>
|
||||
<label for="bearer">bearer</label>
|
||||
<input name="bearer" id="bearer" />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label for="op">Operation</label>
|
||||
<select name="op" id="op">
|
||||
@ -77,11 +81,20 @@
|
||||
const paramsEl = form.querySelector('#params');
|
||||
const mthEl = form.querySelector('#method');
|
||||
const opEL = form.querySelector('#op');
|
||||
const bearerEl = form.querySelector('#bearer');
|
||||
|
||||
const send = (method, path, params) => {
|
||||
const bearer = bearerEl.value || '';
|
||||
|
||||
const rest = method === 'GET'
|
||||
? {}
|
||||
: { body: JSON.stringify(params), headers: { 'Content-Type': 'application/json' } };
|
||||
|
||||
if (bearer.length) {
|
||||
if (!rest.headers) rest.headers = {};
|
||||
rest.headers["Authorization"] = `Bearer ${bearer}`;
|
||||
}
|
||||
|
||||
path = method === 'GET'
|
||||
? `${ path }?${ JSON.stringify(params) }`
|
||||
: path;
|
||||
|
@ -1,10 +1,11 @@
|
||||
use actix::{Actor, Addr, Context, Message};
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::{
|
||||
AccountId, ProductId, Quantity, QuantityUnit, ShoppingCartId, ShoppingCartItem,
|
||||
ShoppingCartItemId, ShoppingCartState,
|
||||
};
|
||||
use crate::{cart_async_handler, database};
|
||||
use actix::{Actor, Addr, Context, Handler, Message, ResponseActFuture, WrapFuture};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
@ -18,6 +19,10 @@ pub enum Error {
|
||||
Db(#[from] database::Error),
|
||||
#[error("Unable to update cart item")]
|
||||
UpdateFailed,
|
||||
#[error("Failed to change quantity")]
|
||||
ChangeQuantity,
|
||||
#[error("Shopping cart item {0} does not exists")]
|
||||
NotExists(ShoppingCartItemId),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@ -48,7 +53,12 @@ pub struct AddItem {
|
||||
cart_async_handler!(AddItem, add_item, ShoppingCartItem);
|
||||
|
||||
async fn add_item(msg: AddItem, db: Addr<Database>) -> Result<ShoppingCartItem> {
|
||||
match db.send(database::EnsureActiveShoppingCart { buyer_id: msg.buyer_id }).await {
|
||||
match db
|
||||
.send(database::EnsureActiveShoppingCart {
|
||||
buyer_id: msg.buyer_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => {}
|
||||
_ => return Err(Error::ShoppingCartFailed),
|
||||
};
|
||||
@ -83,6 +93,7 @@ async fn add_item(msg: AddItem, db: Addr<Database>) -> Result<ShoppingCartItem>
|
||||
_ => Err(Error::CantAddItem),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<Option<ShoppingCartItem>>")]
|
||||
pub struct RemoveProduct {
|
||||
@ -115,3 +126,66 @@ pub(crate) async fn remove_product(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<Option<ShoppingCartItem>>")]
|
||||
pub struct ChangeQuantity {
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
pub shopping_cart_item_id: ShoppingCartItemId,
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
cart_async_handler!(ChangeQuantity, change_quantity, Option<ShoppingCartItem>);
|
||||
|
||||
pub(crate) async fn change_quantity(
|
||||
msg: ChangeQuantity,
|
||||
db: Addr<Database>,
|
||||
) -> Result<Option<ShoppingCartItem>> {
|
||||
if **msg.quantity == 0 {
|
||||
return remove_product(
|
||||
RemoveProduct {
|
||||
shopping_cart_id: msg.shopping_cart_id,
|
||||
shopping_cart_item_id: msg.shopping_cart_item_id,
|
||||
},
|
||||
db,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let item: ShoppingCartItem = match db
|
||||
.send(database::FindShoppingCartItem {
|
||||
id: msg.shopping_cart_item_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(row)) => row,
|
||||
Ok(Err(db_err)) => {
|
||||
log::error!("{db_err}");
|
||||
return Err(Error::NotExists(msg.shopping_cart_item_id));
|
||||
}
|
||||
Err(act_err) => {
|
||||
log::error!("{act_err:?}");
|
||||
return Err(Error::NotExists(msg.shopping_cart_item_id));
|
||||
}
|
||||
};
|
||||
match db
|
||||
.send(database::UpdateShoppingCartItem {
|
||||
id: msg.shopping_cart_item_id,
|
||||
product_id: item.product_id,
|
||||
shopping_cart_id: item.shopping_cart_id,
|
||||
quantity: msg.quantity,
|
||||
quantity_unit: msg.quantity_unit,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(row)) => Ok(Some(row)),
|
||||
Ok(Err(db_err)) => {
|
||||
log::error!("{db_err}");
|
||||
Err(Error::ChangeQuantity)
|
||||
}
|
||||
Err(act_err) => {
|
||||
log::error!("{act_err:?}");
|
||||
Err(Error::ChangeQuantity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
use actix::{Actor, Context};
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub use account_orders::*;
|
||||
pub use accounts::*;
|
||||
use actix::{Actor, Context};
|
||||
pub use order_items::*;
|
||||
pub use products::*;
|
||||
pub use shopping_cart_items::*;
|
||||
pub use shopping_carts::*;
|
||||
use sqlx::PgPool;
|
||||
pub use stocks::*;
|
||||
pub use tokens::*;
|
||||
|
||||
pub mod account_orders;
|
||||
pub mod accounts;
|
||||
@ -16,25 +16,28 @@ pub mod products;
|
||||
pub mod shopping_cart_items;
|
||||
pub mod shopping_carts;
|
||||
pub mod stocks;
|
||||
pub mod tokens;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Failed to connect to database. {0:?}")]
|
||||
Connect(sqlx::Error),
|
||||
Connect(#[from] sqlx::Error),
|
||||
#[error("{0}")]
|
||||
Account(accounts::Error),
|
||||
Account(#[from] accounts::Error),
|
||||
#[error("{0}")]
|
||||
AccountOrder(account_orders::Error),
|
||||
AccountOrder(#[from] account_orders::Error),
|
||||
#[error("{0}")]
|
||||
Product(products::Error),
|
||||
Product(#[from] products::Error),
|
||||
#[error("{0}")]
|
||||
Stock(stocks::Error),
|
||||
Stock(#[from] stocks::Error),
|
||||
#[error("{0}")]
|
||||
OrderItem(order_items::Error),
|
||||
OrderItem(#[from] order_items::Error),
|
||||
#[error("{0}")]
|
||||
ShoppingCart(shopping_carts::Error),
|
||||
ShoppingCart(#[from] shopping_carts::Error),
|
||||
#[error("{0}")]
|
||||
ShoppingCartItem(shopping_cart_items::Error),
|
||||
ShoppingCartItem(#[from] shopping_cart_items::Error),
|
||||
#[error("{0}")]
|
||||
Token(#[from] tokens::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@ -45,7 +48,9 @@ pub struct Database {
|
||||
|
||||
impl Clone for Database {
|
||||
fn clone(&self) -> Self {
|
||||
Self { pool: self.pool.clone() }
|
||||
Self {
|
||||
pool: self.pool.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
use crate::db_async_handler;
|
||||
use actix::{Handler, ResponseActFuture, WrapFuture};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::*;
|
||||
|
||||
use super::Result;
|
||||
use crate::database::Database;
|
||||
use crate::db_async_handler;
|
||||
use crate::model::*;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
|
@ -1,11 +1,9 @@
|
||||
use crate::db_async_handler;
|
||||
use actix::{Handler, ResponseActFuture, WrapFuture};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::{AccountId, Email, FullAccount, Login, PassHash, Role};
|
||||
|
||||
use super::Result;
|
||||
use crate::database::Database;
|
||||
use crate::db_async_handler;
|
||||
use crate::model::{AccountId, Email, FullAccount, Login, PassHash, Role};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
|
@ -1,11 +1,9 @@
|
||||
use crate::db_async_handler;
|
||||
use actix::{Handler, ResponseActFuture, WrapFuture};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::*;
|
||||
|
||||
use super::Result;
|
||||
use crate::database::Database;
|
||||
use crate::db_async_handler;
|
||||
use crate::model::*;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use actix::{Handler, Message, ResponseActFuture, WrapFuture};
|
||||
use actix::Message;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::Result;
|
||||
|
@ -1,11 +1,9 @@
|
||||
use crate::{database, db_async_handler};
|
||||
use actix::{Handler, ResponseActFuture, WrapFuture};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::Result;
|
||||
use crate::database::Database;
|
||||
use crate::model::*;
|
||||
|
||||
use super::Result;
|
||||
use crate::{database, db_async_handler};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
@ -24,14 +22,21 @@ pub enum Error {
|
||||
#[error("Can't find shopping cart item doe to lack of identity")]
|
||||
NoIdentity,
|
||||
#[error("Failed to update shopping cart item with id {shopping_cart_item_id:?} and/or product id {product_id:?}")]
|
||||
Update { shopping_cart_item_id: Option<ShoppingCartItemId>, product_id: Option<ProductId> },
|
||||
Update {
|
||||
shopping_cart_item_id: Option<ShoppingCartItemId>,
|
||||
product_id: Option<ProductId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<Vec<ShoppingCartItem>>")]
|
||||
pub struct AllShoppingCartItems;
|
||||
|
||||
db_async_handler!(AllShoppingCartItems, all_shopping_cart_items, Vec<ShoppingCartItem>);
|
||||
db_async_handler!(
|
||||
AllShoppingCartItems,
|
||||
all_shopping_cart_items,
|
||||
Vec<ShoppingCartItem>
|
||||
);
|
||||
|
||||
pub(crate) async fn all_shopping_cart_items(
|
||||
_msg: AllShoppingCartItems,
|
||||
@ -57,7 +62,11 @@ pub struct AccountShoppingCartItems {
|
||||
pub account_id: AccountId,
|
||||
}
|
||||
|
||||
db_async_handler!(AccountShoppingCartItems, account_shopping_cart_items, Vec<ShoppingCartItem>);
|
||||
db_async_handler!(
|
||||
AccountShoppingCartItems,
|
||||
account_shopping_cart_items,
|
||||
Vec<ShoppingCartItem>
|
||||
);
|
||||
|
||||
pub(crate) async fn account_shopping_cart_items(
|
||||
msg: AccountShoppingCartItems,
|
||||
@ -88,7 +97,11 @@ pub struct CreateShoppingCartItem {
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
db_async_handler!(CreateShoppingCartItem, create_shopping_cart_item, ShoppingCartItem);
|
||||
db_async_handler!(
|
||||
CreateShoppingCartItem,
|
||||
create_shopping_cart_item,
|
||||
ShoppingCartItem
|
||||
);
|
||||
|
||||
pub(crate) async fn create_shopping_cart_item(
|
||||
msg: CreateShoppingCartItem,
|
||||
@ -123,7 +136,11 @@ pub struct UpdateShoppingCartItem {
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
db_async_handler!(UpdateShoppingCartItem, update_shopping_cart_item, ShoppingCartItem);
|
||||
db_async_handler!(
|
||||
UpdateShoppingCartItem,
|
||||
update_shopping_cart_item,
|
||||
ShoppingCartItem
|
||||
);
|
||||
|
||||
pub(crate) async fn update_shopping_cart_item(
|
||||
msg: UpdateShoppingCartItem,
|
||||
@ -156,7 +173,11 @@ pub struct FindShoppingCartItem {
|
||||
pub id: ShoppingCartItemId,
|
||||
}
|
||||
|
||||
db_async_handler!(FindShoppingCartItem, find_shopping_cart_item, ShoppingCartItem);
|
||||
db_async_handler!(
|
||||
FindShoppingCartItem,
|
||||
find_shopping_cart_item,
|
||||
ShoppingCartItem
|
||||
);
|
||||
|
||||
pub(crate) async fn find_shopping_cart_item(
|
||||
msg: FindShoppingCartItem,
|
||||
|
@ -1,11 +1,9 @@
|
||||
use crate::db_async_handler;
|
||||
use actix::{Handler, ResponseActFuture, WrapFuture};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::*;
|
||||
|
||||
use super::Result;
|
||||
use crate::database::Database;
|
||||
use crate::db_async_handler;
|
||||
use crate::model::*;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
@ -52,7 +50,11 @@ pub struct AccountShoppingCarts {
|
||||
pub state: Option<ShoppingCartState>,
|
||||
}
|
||||
|
||||
db_async_handler!(AccountShoppingCarts, account_shopping_carts, Vec<ShoppingCart>);
|
||||
db_async_handler!(
|
||||
AccountShoppingCarts,
|
||||
account_shopping_carts,
|
||||
Vec<ShoppingCart>
|
||||
);
|
||||
|
||||
pub(crate) async fn account_shopping_carts(
|
||||
msg: AccountShoppingCarts,
|
||||
@ -177,18 +179,22 @@ WHERE id = $1
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "Result<Option<ShoppingCart>>")]
|
||||
#[rtype(result = "Result<ShoppingCart>")]
|
||||
pub struct EnsureActiveShoppingCart {
|
||||
pub buyer_id: AccountId,
|
||||
}
|
||||
|
||||
db_async_handler!(EnsureActiveShoppingCart, ensure_active_shopping_cart, Option<ShoppingCart>);
|
||||
db_async_handler!(
|
||||
EnsureActiveShoppingCart,
|
||||
ensure_active_shopping_cart,
|
||||
ShoppingCart
|
||||
);
|
||||
|
||||
pub(crate) async fn ensure_active_shopping_cart(
|
||||
msg: EnsureActiveShoppingCart,
|
||||
pool: PgPool,
|
||||
) -> Result<Option<ShoppingCart>> {
|
||||
sqlx::query_as(
|
||||
) -> Result<ShoppingCart> {
|
||||
if let Ok(Some(cart)) = sqlx::query_as(
|
||||
r#"
|
||||
INSERT INTO shopping_carts (buyer_id, state)
|
||||
VALUES ($1, 'active')
|
||||
@ -200,6 +206,22 @@ RETURNING id, buyer_id, payment_method, state;
|
||||
.bind(msg.buyer_id)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
super::Error::ShoppingCart(Error::NotExists)
|
||||
}) {
|
||||
return Ok(cart);
|
||||
};
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, buyer_id, payment_method, state
|
||||
FROM shopping_carts
|
||||
WHERE buyer_id = $1 AND state = 'active'
|
||||
"#,
|
||||
)
|
||||
.bind(msg.buyer_id)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
super::Error::ShoppingCart(Error::NotExists)
|
||||
|
@ -1,4 +1,4 @@
|
||||
use actix::{Handler, Message, ResponseActFuture, WrapFuture};
|
||||
use actix::Message;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::Result;
|
||||
|
73
api/src/actors/database/tokens.rs
Normal file
73
api/src/actors/database/tokens.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use actix::Message;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use super::Result;
|
||||
use crate::database::Database;
|
||||
use crate::model::{AccountId, Audience, Token};
|
||||
use crate::{database, db_async_handler, Role};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Failed to save new token")]
|
||||
Create,
|
||||
#[error("Failed to find token by jti")]
|
||||
Jti,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<Token>")]
|
||||
pub struct TokenByJti {
|
||||
pub jti: String,
|
||||
}
|
||||
|
||||
db_async_handler!(TokenByJti, token_by_jti, Token);
|
||||
|
||||
pub(crate) async fn token_by_jti(msg: TokenByJti, pool: PgPool) -> Result<Token> {
|
||||
sqlx::query_as(r#"
|
||||
SELECT id, customer_id, role, issuer, subject, audience, expiration_time, not_before_time, issued_at_time, jwt_id
|
||||
FROM tokens
|
||||
WHERE jwt_id = $1
|
||||
"#)
|
||||
.bind(msg.jti)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
database::Error::Token(Error::Jti)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<Token>")]
|
||||
pub struct CreateToken {
|
||||
pub customer_id: uuid::Uuid,
|
||||
pub role: Role,
|
||||
pub subject: AccountId,
|
||||
pub audience: Audience,
|
||||
}
|
||||
|
||||
db_async_handler!(CreateToken, create_token, Token);
|
||||
|
||||
pub(crate) async fn create_token(msg: CreateToken, pool: PgPool) -> Result<Token> {
|
||||
let CreateToken {
|
||||
customer_id,
|
||||
role,
|
||||
subject,
|
||||
audience,
|
||||
} = msg;
|
||||
sqlx::query_as(r#"
|
||||
INSERT INTO tokens (customer_id, role, subject, audience)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, customer_id, role, issuer, subject, audience, expiration_time, not_before_time, issued_at_time, jwt_id
|
||||
"#)
|
||||
.bind(customer_id)
|
||||
.bind(role)
|
||||
.bind(subject)
|
||||
.bind(audience)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
log::error!("{e:?}");
|
||||
database::Error::Token(Error::Create)
|
||||
})
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
pub mod cart_manager;
|
||||
pub mod database;
|
||||
pub mod token_manager;
|
||||
|
267
api/src/actors/token_manager.rs
Normal file
267
api/src/actors/token_manager.rs
Normal file
@ -0,0 +1,267 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix::{Addr, Message};
|
||||
use chrono::prelude::*;
|
||||
|
||||
use crate::database::{Database, TokenByJti};
|
||||
use crate::model::{AccountId, Audience, Token, TokenString};
|
||||
use crate::{database, token_async_handler, Role};
|
||||
|
||||
struct Jwt {
|
||||
/// cti (customer id): Customer uuid identifier used by payment service
|
||||
pub cti: uuid::Uuid,
|
||||
/// arl (account role): account role
|
||||
pub arl: Role,
|
||||
/// iss (issuer): Issuer of the JWT
|
||||
pub iss: String,
|
||||
/// sub (subject): Subject of the JWT (the user)
|
||||
pub sub: i32,
|
||||
/// aud (audience): Recipient for which the JWT is intended
|
||||
pub aud: Audience,
|
||||
/// exp (expiration time): Time after which the JWT expires
|
||||
pub exp: chrono::NaiveDateTime,
|
||||
/// nbt (not before time): Time before which the JWT must not be accepted
|
||||
/// for processing
|
||||
pub nbt: chrono::NaiveDateTime,
|
||||
/// iat (issued at time): Time at which the JWT was issued; can be used to
|
||||
/// determine age of the JWT,
|
||||
pub iat: chrono::NaiveDateTime,
|
||||
/// jti (JWT ID): Unique identifier; can be used to prevent the JWT from
|
||||
/// being replayed (allows a token to be used only once)
|
||||
pub jti: uuid::Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Unable to save new token")]
|
||||
Save,
|
||||
#[error("Unable to save new token. Can't connect to database")]
|
||||
SaveInternal,
|
||||
#[error("Unable to validate token")]
|
||||
Validate,
|
||||
#[error("Unable to validate token. Can't connect to database")]
|
||||
ValidateInternal,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub struct TokenManager {
|
||||
db: Addr<Database>,
|
||||
secret: Arc<String>,
|
||||
}
|
||||
|
||||
impl actix::Actor for TokenManager {
|
||||
type Context = actix::Context<Self>;
|
||||
}
|
||||
|
||||
impl TokenManager {
|
||||
pub fn new(db: Addr<Database>) -> Self {
|
||||
let secret = Arc::new(std::env::var("JWT_SECRET").expect("JWT_SECRET is required"));
|
||||
Self { db, secret }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<(Token, TokenString)>")]
|
||||
pub struct CreateToken {
|
||||
pub customer_id: uuid::Uuid,
|
||||
pub role: Role,
|
||||
pub subject: AccountId,
|
||||
pub audience: Option<Audience>,
|
||||
}
|
||||
|
||||
token_async_handler!(CreateToken, create_token, (Token, TokenString));
|
||||
|
||||
async fn create_token(
|
||||
msg: CreateToken,
|
||||
db: Addr<Database>,
|
||||
secret: Arc<String>,
|
||||
) -> Result<(Token, TokenString)> {
|
||||
let CreateToken {
|
||||
customer_id,
|
||||
role,
|
||||
subject,
|
||||
audience,
|
||||
} = msg;
|
||||
let audience = audience.unwrap_or_default();
|
||||
|
||||
let token: Token = match db
|
||||
.send(database::CreateToken {
|
||||
customer_id,
|
||||
role,
|
||||
subject,
|
||||
audience,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(token)) => token,
|
||||
Ok(Err(db_err)) => {
|
||||
log::error!("{db_err}");
|
||||
return Err(Error::Save);
|
||||
}
|
||||
Err(act_err) => {
|
||||
log::error!("{act_err:?}");
|
||||
return Err(Error::SaveInternal);
|
||||
}
|
||||
};
|
||||
|
||||
let token_string = {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use jwt::SignWithKey;
|
||||
use sha2::Sha256;
|
||||
|
||||
let key: Hmac<Sha256> = match Hmac::new_from_slice(secret.as_bytes()) {
|
||||
Ok(key) => key,
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::SaveInternal);
|
||||
}
|
||||
};
|
||||
let mut claims = BTreeMap::new();
|
||||
|
||||
// cti (customer id): Customer uuid identifier used by payment service
|
||||
claims.insert("cti", format!("{}", token.customer_id));
|
||||
// arl (account role): account role
|
||||
claims.insert("arl", format!("{}", token.role.as_str()));
|
||||
// iss (issuer): Issuer of the JWT
|
||||
claims.insert("iss", format!("{}", token.issuer));
|
||||
// sub (subject): Subject of the JWT (the user)
|
||||
claims.insert("sub", format!("{}", token.subject));
|
||||
// aud (audience): Recipient for which the JWT is intended
|
||||
claims.insert("aud", format!("{}", token.audience.as_str()));
|
||||
// exp (expiration time): Time after which the JWT expires
|
||||
claims.insert("exp", format!("{}", token.expiration_time.format("%+")));
|
||||
// nbt (not before time): Time before which the JWT must not be accepted
|
||||
// for processing
|
||||
claims.insert("nbt", format!("{}", token.not_before_time.format("%+")));
|
||||
// iat (issued at time): Time at which the JWT was issued; can be used
|
||||
// to determine age of the JWT,
|
||||
claims.insert("iat", format!("{}", token.issued_at_time.format("%+")));
|
||||
// jti (JWT ID): Unique identifier; can be used to prevent the JWT from
|
||||
// being replayed (allows a token to be used only once)
|
||||
claims.insert("jti", format!("{}", token.jwt_id));
|
||||
|
||||
TokenString::from(match claims.sign_with_key(&key) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::SaveInternal);
|
||||
}
|
||||
})
|
||||
};
|
||||
Ok((token, token_string))
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<(Token, bool)>")]
|
||||
pub struct Validate {
|
||||
pub token: TokenString,
|
||||
}
|
||||
|
||||
token_async_handler!(Validate, validate, (Token, bool));
|
||||
|
||||
pub(crate) async fn validate(
|
||||
msg: Validate,
|
||||
db: Addr<Database>,
|
||||
secret: Arc<String>,
|
||||
) -> Result<(Token, bool)> {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use jwt::VerifyWithKey;
|
||||
use sha2::Sha256;
|
||||
|
||||
log::info!("Validating token {:?}", msg.token);
|
||||
|
||||
let key: Hmac<Sha256> = match Hmac::new_from_slice(secret.as_bytes()) {
|
||||
Ok(key) => key,
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::ValidateInternal);
|
||||
}
|
||||
};
|
||||
let claims: BTreeMap<String, String> = match msg.token.verify_with_key(&key) {
|
||||
Ok(claims) => claims,
|
||||
_ => return Err(Error::Validate),
|
||||
};
|
||||
let jti = match claims.get("jti") {
|
||||
Some(jti) => jti,
|
||||
_ => return Err(Error::Validate),
|
||||
};
|
||||
|
||||
let token: Token = match db
|
||||
.send(TokenByJti {
|
||||
jti: String::from(jti),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(token)) => token,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
return Err(Error::Validate);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::ValidateInternal);
|
||||
}
|
||||
};
|
||||
|
||||
match (claims.get("cti"), &token.customer_id) {
|
||||
(Some(cti), id) => {
|
||||
if !uuid::Uuid::from_str(cti)
|
||||
.map(|u| u == *id)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return Ok((token, false));
|
||||
}
|
||||
}
|
||||
_ => return Ok((token, false)),
|
||||
}
|
||||
match (claims.get("arl"), &token.role) {
|
||||
(Some(arl), role) if arl == role.as_str() => {}
|
||||
_ => return Ok((token, false)),
|
||||
}
|
||||
match (claims.get("iss"), &token.issuer) {
|
||||
(Some(iss), issuer) if iss == issuer => {}
|
||||
_ => return Ok((token, false)),
|
||||
}
|
||||
match (claims.get("sub"), &token.subject) {
|
||||
(Some(sub), subject) => {
|
||||
if !sub
|
||||
.parse::<i32>()
|
||||
.map(|n| n == *subject)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return Ok((token, false));
|
||||
}
|
||||
}
|
||||
_ => return Ok((token, false)),
|
||||
}
|
||||
match (claims.get("aud"), &token.audience) {
|
||||
(Some(aud), audience) if aud == audience.as_str() => {}
|
||||
_ => return Ok((token, false)),
|
||||
}
|
||||
match (claims.get("exp"), &token.expiration_time) {
|
||||
(Some(left), right) if validate_time(left, right) => {}
|
||||
_ => return Ok((token, false)),
|
||||
}
|
||||
match (claims.get("nbt"), &token.not_before_time) {
|
||||
(Some(left), right) if validate_time(left, right) => {}
|
||||
_ => return Ok((token, false)),
|
||||
}
|
||||
match (claims.get("iat"), &token.issued_at_time) {
|
||||
(Some(left), right) if validate_time(left, right) => {}
|
||||
_ => return Ok((token, false)),
|
||||
}
|
||||
|
||||
Ok((token, true))
|
||||
}
|
||||
|
||||
fn validate_time(left: &str, right: &NaiveDateTime) -> bool {
|
||||
chrono::DateTime::parse_from_str(left, "%+")
|
||||
.map(|t| t.naive_utc() == *right)
|
||||
.unwrap_or_default()
|
||||
}
|
@ -8,9 +8,11 @@ mod order_state;
|
||||
|
||||
pub fn encrypt_password(pass: &Password, salt: &SaltString) -> password_hash::Result<String> {
|
||||
log::debug!("Hashing password {:?}", pass);
|
||||
Ok(Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default())
|
||||
.hash_password(pass.as_bytes(), &salt)?
|
||||
.to_string())
|
||||
Ok(
|
||||
Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default())
|
||||
.hash_password(pass.as_bytes(), &salt)?
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn validate_password(pass: &Password, pass_hash: &PassHash) -> password_hash::Result<()> {
|
||||
|
@ -1,7 +1,11 @@
|
||||
#![feature(stdio_locked)]
|
||||
|
||||
use std::io::{BufRead, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix::Actor;
|
||||
use actix_session::{storage::RedisActorSessionStore, SessionMiddleware};
|
||||
use actix_session::storage::RedisActorSessionStore;
|
||||
use actix_session::SessionMiddleware;
|
||||
use actix_web::cookie::Key;
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::web::Data;
|
||||
@ -10,7 +14,7 @@ use gumdrop::Options;
|
||||
use password_hash::SaltString;
|
||||
use validator::{validate_email, validate_length};
|
||||
|
||||
use crate::actors::database;
|
||||
use crate::actors::{database, token_manager};
|
||||
use crate::logic::encrypt_password;
|
||||
use crate::model::{Email, Login, PassHash, Password, Role};
|
||||
|
||||
@ -84,7 +88,12 @@ struct ServerOpts {
|
||||
|
||||
impl Default for ServerOpts {
|
||||
fn default() -> Self {
|
||||
Self { help: false, bind: "0.0.0.0".to_string(), port: 8080, db_url: None }
|
||||
Self {
|
||||
help: false,
|
||||
bind: "0.0.0.0".to_string(),
|
||||
port: 8080,
|
||||
db_url: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +170,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
||||
|
||||
let config = Arc::new(Config::load());
|
||||
let db = database::Database::build(&opts.db_url()).await?.start();
|
||||
let token_manager = token_manager::TokenManager::new(db.clone()).start();
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
@ -173,6 +183,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
||||
))
|
||||
.app_data(Data::new(config.clone()))
|
||||
.app_data(Data::new(db.clone()))
|
||||
.app_data(Data::new(token_manager.clone()))
|
||||
.configure(routes::configure)
|
||||
// .default_service(web::to(HttpResponse::Ok))
|
||||
})
|
||||
@ -221,13 +232,26 @@ async fn create_account(opts: CreateAccountOpts) -> Result<()> {
|
||||
Some(path) => std::fs::read_to_string(path).map_err(Error::PassFile)?,
|
||||
None => {
|
||||
let mut s = String::with_capacity(100);
|
||||
std::io::stdin().read_line(&mut s).map_err(Error::ReadPass)?;
|
||||
{
|
||||
let mut std_out = std::io::stdout_locked();
|
||||
let mut std_in = std::io::stdin_locked();
|
||||
|
||||
std_out
|
||||
.write_all(b"PASS > ")
|
||||
.expect("Failed to write to stdout");
|
||||
std_out.flush().expect("Failed to write to stdout");
|
||||
|
||||
std_in.read_line(&mut s).map_err(Error::ReadPass)?;
|
||||
}
|
||||
if let Some(pos) = s.chars().position(|c| c == '\n') {
|
||||
s.remove(pos);
|
||||
}
|
||||
s
|
||||
}
|
||||
};
|
||||
if pass.trim().is_empty() {
|
||||
panic!("Password cannot be empty!");
|
||||
}
|
||||
let config = Config::load();
|
||||
let hash = encrypt_password(&Password(pass), &config.pass_salt).unwrap();
|
||||
|
||||
|
102
api/src/model.rs
102
api/src/model.rs
@ -15,7 +15,7 @@ pub enum TransformError {
|
||||
pub type RecordId = i32;
|
||||
|
||||
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
#[sqlx(rename_all = "snake_case")]
|
||||
pub enum OrderStatus {
|
||||
#[display(fmt = "Potwierdzone")]
|
||||
Confirmed,
|
||||
@ -32,7 +32,7 @@ pub enum OrderStatus {
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
#[sqlx(rename_all = "snake_case")]
|
||||
pub enum Role {
|
||||
#[display(fmt = "Adminitrator")]
|
||||
Admin,
|
||||
@ -40,8 +40,17 @@ pub enum Role {
|
||||
User,
|
||||
}
|
||||
|
||||
impl Role {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Role::Admin => "Admin",
|
||||
Role::User => "User",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
#[sqlx(rename_all = "snake_case")]
|
||||
pub enum QuantityUnit {
|
||||
Gram,
|
||||
Decagram,
|
||||
@ -50,19 +59,45 @@ pub enum QuantityUnit {
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
#[sqlx(rename_all = "snake_case")]
|
||||
pub enum PaymentMethod {
|
||||
PayU,
|
||||
PaymentOnTheSpot,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
#[sqlx(rename_all = "snake_case")]
|
||||
pub enum ShoppingCartState {
|
||||
Active,
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
|
||||
#[sqlx(rename_all = "snake_case")]
|
||||
pub enum Audience {
|
||||
Web,
|
||||
Mobile,
|
||||
Feed,
|
||||
AdminPanel,
|
||||
}
|
||||
|
||||
impl Audience {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Audience::Web => "Web",
|
||||
Audience::Mobile => "Mobile",
|
||||
Audience::Feed => "Feed",
|
||||
Audience::AdminPanel => "AdminPanel",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Audience {
|
||||
fn default() -> Self {
|
||||
Self::Web
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Deref, From)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
@ -167,7 +202,9 @@ impl<'de> serde::Deserialize<'de> for NonNegative {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(NonNegative(deserializer.deserialize_i32(NonNegativeVisitor)?))
|
||||
Ok(NonNegative(
|
||||
deserializer.deserialize_i32(NonNegativeVisitor)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,7 +331,7 @@ impl PartialEq<PasswordConfirmation> for Password {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display)]
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, From)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct AccountId(RecordId);
|
||||
@ -320,9 +357,22 @@ pub struct Account {
|
||||
|
||||
impl From<FullAccount> for Account {
|
||||
fn from(
|
||||
FullAccount { id, email, login, pass_hash: _, role, customer_id }: FullAccount,
|
||||
FullAccount {
|
||||
id,
|
||||
email,
|
||||
login,
|
||||
pass_hash: _,
|
||||
role,
|
||||
customer_id,
|
||||
}: FullAccount,
|
||||
) -> Self {
|
||||
Self { id, email, login, role, customer_id }
|
||||
Self {
|
||||
id,
|
||||
email,
|
||||
login,
|
||||
role,
|
||||
customer_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,7 +474,7 @@ pub struct ShoppingCart {
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct ShoppingCartItemId(pub RecordId);
|
||||
pub struct ShoppingCartItemId(RecordId);
|
||||
|
||||
#[derive(sqlx::FromRow, Serialize, Deserialize)]
|
||||
pub struct ShoppingCartItem {
|
||||
@ -434,3 +484,35 @@ pub struct ShoppingCartItem {
|
||||
pub quantity: Quantity,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct TokenId(RecordId);
|
||||
|
||||
#[derive(sqlx::FromRow, Serialize, Deserialize)]
|
||||
pub struct Token {
|
||||
pub id: TokenId,
|
||||
pub customer_id: uuid::Uuid,
|
||||
pub role: Role,
|
||||
/// iss (issuer): Issuer of the JWT
|
||||
pub issuer: String,
|
||||
/// sub (subject): Subject of the JWT (the user)
|
||||
pub subject: i32,
|
||||
/// aud (audience): Recipient for which the JWT is intended
|
||||
pub audience: Audience,
|
||||
/// exp (expiration time): Time after which the JWT expires
|
||||
pub expiration_time: chrono::NaiveDateTime,
|
||||
/// nbt (not before time): Time before which the JWT must not be accepted
|
||||
/// for processing
|
||||
pub not_before_time: chrono::NaiveDateTime,
|
||||
/// iat (issued at time): Time at which the JWT was issued; can be used to
|
||||
/// determine age of the JWT,
|
||||
pub issued_at_time: chrono::NaiveDateTime,
|
||||
/// jti (JWT ID): Unique identifier; can be used to prevent the JWT from
|
||||
/// being replayed (allows a token to be used only once)
|
||||
pub jwt_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Deref, Display, From)]
|
||||
pub struct TokenString(String);
|
||||
|
@ -4,5 +4,9 @@ mod stocks;
|
||||
use actix_web::web::{scope, ServiceConfig};
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
config.service(scope("/api/v1").configure(products::configure).configure(stocks::configure));
|
||||
config.service(
|
||||
scope("/api/v1")
|
||||
.configure(products::configure)
|
||||
.configure(stocks::configure),
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
use crate::database;
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, get, patch, post, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::{
|
||||
Days, PriceMajor, PriceMinor, ProductCategory, ProductId, ProductLongDesc, ProductName,
|
||||
@ -6,13 +11,7 @@ use crate::model::{
|
||||
};
|
||||
use crate::routes::admin::Error;
|
||||
use crate::routes::RequireLogin;
|
||||
use crate::{admin_send_db, routes};
|
||||
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, get, patch, post, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
use crate::{admin_send_db, database, routes};
|
||||
|
||||
#[get("/products")]
|
||||
async fn products(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
||||
@ -100,9 +99,14 @@ async fn delete_product(
|
||||
db: Data<Addr<Database>>,
|
||||
Json(payload): Json<DeleteProduct>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
let _ = session.require_admin()?;
|
||||
|
||||
admin_send_db!(db, database::DeleteProduct { product_id: payload.id });
|
||||
admin_send_db!(
|
||||
db,
|
||||
database::DeleteProduct {
|
||||
product_id: payload.id
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
|
@ -1,16 +1,15 @@
|
||||
use crate::database;
|
||||
use crate::database::Database;
|
||||
use crate::model::{ProductId, Quantity, QuantityUnit, StockId};
|
||||
use crate::routes::admin::Error;
|
||||
use crate::routes::RequireLogin;
|
||||
use crate::{admin_send_db, routes};
|
||||
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, get, patch, post, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::{ProductId, Quantity, QuantityUnit, StockId};
|
||||
use crate::routes::admin::Error;
|
||||
use crate::routes::RequireLogin;
|
||||
use crate::{admin_send_db, database, routes};
|
||||
|
||||
#[get("/stocks")]
|
||||
async fn stocks(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
@ -83,9 +82,18 @@ async fn delete_stock(
|
||||
) -> routes::Result<HttpResponse> {
|
||||
session.require_admin()?;
|
||||
|
||||
admin_send_db!(db, database::DeleteStock { stock_id: payload.id });
|
||||
admin_send_db!(
|
||||
db,
|
||||
database::DeleteStock {
|
||||
stock_id: payload.id
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
config.service(stocks).service(create_stock).service(update_stock).service(delete_stock);
|
||||
config
|
||||
.service(stocks)
|
||||
.service(create_stock)
|
||||
.service(update_stock)
|
||||
.service(delete_stock);
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
mod api_v1;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::web::{scope, Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, get, post, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::database::{AccountByIdentity, Database};
|
||||
use crate::logic::encrypt_password;
|
||||
@ -69,18 +70,23 @@ async fn sign_in(
|
||||
) -> Result<HttpResponse> {
|
||||
log::debug!("{:?}", payload);
|
||||
let db = db.into_inner();
|
||||
let user: model::FullAccount =
|
||||
match db.send(AccountByIdentity { email: payload.email, login: payload.login }).await {
|
||||
Ok(Ok(user)) => user,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{}", e);
|
||||
return Err(routes::Error::Unauthorized);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
return Err(routes::Error::Unauthorized);
|
||||
}
|
||||
};
|
||||
let user: model::FullAccount = match db
|
||||
.send(AccountByIdentity {
|
||||
email: payload.email,
|
||||
login: payload.login,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(user)) => user,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{}", e);
|
||||
return Err(routes::Error::Unauthorized);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
return Err(routes::Error::Unauthorized);
|
||||
}
|
||||
};
|
||||
if let Err(e) = crate::logic::validate_password(&payload.password, &user.pass_hash) {
|
||||
log::error!("Password validation failed. {}", e);
|
||||
Err(routes::Error::Unauthorized)
|
||||
|
@ -1,13 +1,17 @@
|
||||
pub mod admin;
|
||||
pub mod public;
|
||||
|
||||
use crate::model::RecordId;
|
||||
use crate::routes;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use actix_session::Session;
|
||||
use actix_web::body::BoxBody;
|
||||
use actix_web::web::ServiceConfig;
|
||||
use actix_web::{HttpRequest, HttpResponse, Responder, ResponseError};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
pub use admin::Error as AdminError;
|
||||
pub use public::{Error as PublicError, V1Error, V1ShoppingCartError};
|
||||
|
||||
use crate::model::RecordId;
|
||||
use crate::routes;
|
||||
|
||||
pub trait RequireLogin {
|
||||
fn require_admin(&self) -> Result<RecordId>;
|
||||
@ -25,14 +29,23 @@ impl RequireLogin for Session {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::From)]
|
||||
pub enum Error {
|
||||
#[from(ignore)]
|
||||
Unauthorized,
|
||||
Admin(routes::admin::Error),
|
||||
Public(routes::public::Error),
|
||||
}
|
||||
|
||||
impl Debug for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
impl From<V1Error> for Error {
|
||||
fn from(v1: V1Error) -> Self {
|
||||
Self::Public(PublicError::ApiV1(v1))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<V1ShoppingCartError> for Error {
|
||||
fn from(sv1: V1ShoppingCartError) -> Self {
|
||||
Self::Public(PublicError::ApiV1(V1Error::ShoppingCart(sv1)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +58,8 @@ impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let msg = match self {
|
||||
Error::Unauthorized => String::from("Unauthorized"),
|
||||
Error::Admin(e) => format!("{}", e),
|
||||
Error::Admin(e) => format!("{e}"),
|
||||
Error::Public(e) => format!("{e}"),
|
||||
};
|
||||
f.write_str(&serde_json::to_string(&Failure { errors: vec![msg] }).unwrap())
|
||||
}
|
||||
@ -61,9 +75,16 @@ impl Responder for Error {
|
||||
Error::Unauthorized => HttpResponse::Unauthorized()
|
||||
.content_type("application/json")
|
||||
.body(format!("{}", self)),
|
||||
Error::Admin(_) => HttpResponse::InternalServerError()
|
||||
Error::Public(PublicError::DatabaseConnection)
|
||||
| Error::Public(PublicError::Database(..))
|
||||
| Error::Admin(..) => HttpResponse::InternalServerError()
|
||||
.content_type("application/json")
|
||||
.body(format!("{}", self)),
|
||||
Error::Public(PublicError::ApiV1(V1Error::ShoppingCart(ref e))) => match e {
|
||||
V1ShoppingCartError::Ensure => HttpResponse::InternalServerError()
|
||||
.content_type("application/json")
|
||||
.body(format!("{}", self)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,5 +92,7 @@ impl Responder for Error {
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
config.configure(public::configure).configure(admin::configure);
|
||||
config
|
||||
.configure(public::configure)
|
||||
.configure(admin::configure);
|
||||
}
|
||||
|
@ -1,26 +1,43 @@
|
||||
mod api_v1;
|
||||
pub mod api_v1;
|
||||
|
||||
use actix_web::web::ServiceConfig;
|
||||
use actix_web::{get, HttpResponse};
|
||||
pub use api_v1::{Error as V1Error, ShoppingCartError as V1ShoppingCartError};
|
||||
|
||||
use crate::database;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! public_send_db {
|
||||
($db: expr, $msg: expr) => {{
|
||||
use crate::routes::PublicError;
|
||||
|
||||
let db = $db;
|
||||
return match db.send($msg).await {
|
||||
Ok(Ok(res)) => Ok(HttpResponse::Ok().json(res)),
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{}", e);
|
||||
Err(crate::routes::Error::Admin(Error::Database(e)))
|
||||
Err(crate::routes::Error::Public(PublicError::Database(e)))
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
Err(crate::routes::Error::Admin(Error::DatabaseConnection))
|
||||
Err(crate::routes::Error::Public(
|
||||
PublicError::DatabaseConnection,
|
||||
))
|
||||
}
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
ApiV1(#[from] api_v1::Error),
|
||||
#[error("Internal server error")]
|
||||
DatabaseConnection,
|
||||
#[error("{0}")]
|
||||
Database(#[from] database::Error),
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn landing() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().body("")
|
||||
|
@ -1,12 +1,28 @@
|
||||
use actix::Addr;
|
||||
use actix_web::dev::ServiceRequest;
|
||||
use actix_web::web::{scope, Data, ServiceConfig};
|
||||
use actix_web::{get, HttpResponse};
|
||||
use actix_web_httpauth::extractors::bearer::{BearerAuth, Config};
|
||||
use actix_web_httpauth::extractors::AuthenticationError;
|
||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||
|
||||
use crate::database;
|
||||
use crate::database::Database;
|
||||
use crate::public_send_db;
|
||||
use crate::routes::admin::Error;
|
||||
use crate::model::{AccountId, TokenString};
|
||||
use crate::routes::Result;
|
||||
use crate::token_manager::TokenManager;
|
||||
use crate::{database, public_send_db, token_manager};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ShoppingCartError {
|
||||
#[error("Shopping cart can't be found or created")]
|
||||
Ensure,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
ShoppingCart(ShoppingCartError),
|
||||
}
|
||||
|
||||
#[get("/products")]
|
||||
async fn products(db: Data<Addr<Database>>) -> Result<HttpResponse> {
|
||||
@ -18,6 +34,60 @@ async fn stocks(db: Data<Addr<Database>>) -> Result<HttpResponse> {
|
||||
public_send_db!(db.into_inner(), database::AllStocks)
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
config.service(scope("/api/v1").service(products).service(stocks));
|
||||
#[get("/shopping_cart")]
|
||||
async fn shopping_cart(db: Data<Addr<Database>>, credentials: BearerAuth) -> Result<HttpResponse> {
|
||||
let _t = credentials.token();
|
||||
match db
|
||||
.send(database::EnsureActiveShoppingCart {
|
||||
buyer_id: AccountId::from(1),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(cart)) => Ok(HttpResponse::Ok().json(cart)),
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
Err(ShoppingCartError::Ensure.into())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
Err(ShoppingCartError::Ensure.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
let bearer_auth = HttpAuthentication::bearer(validator);
|
||||
config.service(
|
||||
scope("/api/v1")
|
||||
.service(products)
|
||||
.service(stocks)
|
||||
.service(scope("").wrap(bearer_auth).service(shopping_cart)),
|
||||
);
|
||||
}
|
||||
|
||||
async fn validator(
|
||||
req: ServiceRequest,
|
||||
credentials: BearerAuth,
|
||||
) -> std::result::Result<ServiceRequest, actix_web::Error> {
|
||||
let tm = match req.app_data::<Data<Addr<TokenManager>>>() {
|
||||
Some(db) => db,
|
||||
_ => panic!("DB must be configured"),
|
||||
};
|
||||
|
||||
if let Ok(Ok((_, true))) = tm
|
||||
.send(token_manager::Validate {
|
||||
token: TokenString::from(String::from(credentials.token())),
|
||||
})
|
||||
.await
|
||||
{
|
||||
return Ok(req);
|
||||
};
|
||||
|
||||
let config = req
|
||||
.app_data::<Config>()
|
||||
.map(|data| data.clone())
|
||||
.unwrap_or_else(Default::default)
|
||||
.scope("account=user");
|
||||
|
||||
Err(AuthenticationError::from(config).into())
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
#[macro_export]
|
||||
macro_rules! db_async_handler {
|
||||
($msg: ty, $async: ident, $res: ty) => {
|
||||
impl Handler<$msg> for Database {
|
||||
type Result = ResponseActFuture<Self, Result<$res>>;
|
||||
impl actix::Handler<$msg> for Database {
|
||||
type Result = actix::ResponseActFuture<Self, Result<$res>>;
|
||||
|
||||
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use actix::WrapFuture;
|
||||
let pool = self.pool.clone();
|
||||
Box::pin(async { $async(msg, pool).await }.into_actor(self))
|
||||
}
|
||||
@ -15,13 +16,30 @@ macro_rules! db_async_handler {
|
||||
#[macro_export]
|
||||
macro_rules! cart_async_handler {
|
||||
($msg: ty, $async: ident, $res: ty) => {
|
||||
impl Handler<$msg> for CartManager {
|
||||
type Result = ResponseActFuture<Self, Result<$res>>;
|
||||
impl actix::Handler<$msg> for CartManager {
|
||||
type Result = actix::ResponseActFuture<Self, Result<$res>>;
|
||||
|
||||
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use actix::WrapFuture;
|
||||
let db = self.db.clone();
|
||||
Box::pin(async { $async(msg, db).await }.into_actor(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! token_async_handler {
|
||||
($msg: ty, $async: ident, $res: ty) => {
|
||||
impl actix::Handler<$msg> for TokenManager {
|
||||
type Result = actix::ResponseActFuture<Self, Result<$res>>;
|
||||
|
||||
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use actix::WrapFuture;
|
||||
let db = self.db.clone();
|
||||
let secret = self.secret.clone();
|
||||
Box::pin(async { $async(msg, db, secret).await }.into_actor(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
27
db/migrate/202204181325_create_tokens.sql
Normal file
27
db/migrate/202204181325_create_tokens.sql
Normal file
@ -0,0 +1,27 @@
|
||||
CREATE TYPE "Audience" AS ENUM (
|
||||
'web',
|
||||
'mobile',
|
||||
'feed',
|
||||
'admin_panel'
|
||||
);
|
||||
|
||||
CREATE TABLE tokens (
|
||||
id serial not null primary key,
|
||||
customer_id uuid not null,
|
||||
role "Role" not null,
|
||||
-- standard fields
|
||||
-- iss (issuer): Issuer of the JWT
|
||||
issuer varchar not null default 'bazzar',
|
||||
-- sub (subject): Subject of the JWT (the user)
|
||||
subject int not null /* account_id */ ,
|
||||
-- aud (audience): Recipient for which the JWT is intended
|
||||
audience "Audience" not null default 'web',
|
||||
-- exp (expiration time): Time after which the JWT expires
|
||||
expiration_time timestamp not null default now() + interval '2 weeks',
|
||||
-- nbt (not before time): Time before which the JWT must not be accepted for processing
|
||||
not_before_time timestamp not null default now() - interval '1 minute',
|
||||
-- iat (issued at time): Time at which the JWT was issued; can be used to determine age of the JWT,
|
||||
issued_at_time timestamp not null default now(),
|
||||
-- jti (JWT ID): Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only once)
|
||||
jwt_id uuid not null default gen_random_uuid()
|
||||
);
|
4
db/migrate/20220418215_add_uniq_add_time_format.sql
Normal file
4
db/migrate/20220418215_add_uniq_add_time_format.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE tokens
|
||||
ADD CONSTRAINT unit_jit UNIQUE (jti);
|
||||
|
||||
--SET datestyle = '';
|
@ -1,2 +1,7 @@
|
||||
max_width = 100
|
||||
use_small_heuristics = "Max"
|
||||
imports_granularity = "Module"
|
||||
group_imports = "StdExternalCrate"
|
||||
reorder_modules = true
|
||||
reorder_imports = true
|
||||
use_field_init_shorthand = true
|
||||
wrap_comments = true
|
||||
edition = "2021"
|
||||
|
Loading…
Reference in New Issue
Block a user