Working example
This commit is contained in:
parent
83345afe04
commit
f514fd741c
10 changed files with 446 additions and 81 deletions
228
server/Cargo.lock
generated
228
server/Cargo.lock
generated
|
@ -30,7 +30,7 @@ dependencies = [
|
||||||
"actix-service",
|
"actix-service",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"ahash",
|
"ahash",
|
||||||
"base64",
|
"base64 0.21.4",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -238,6 +238,21 @@ version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -329,6 +344,12 @@ dependencies = [
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.4"
|
version = "0.21.4"
|
||||||
|
@ -395,6 +416,12 @@ dependencies = [
|
||||||
"alloc-stdlib",
|
"alloc-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.4.3"
|
version = "1.4.3"
|
||||||
|
@ -432,6 +459,21 @@ 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 = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.4.5"
|
version = "4.4.5"
|
||||||
|
@ -501,6 +543,12 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -986,6 +1034,29 @@ version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -1060,6 +1131,29 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "8.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.4",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "language-tags"
|
name = "language-tags"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -1205,6 +1299,17 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "num-bigint-dig"
|
name = "num-bigint-dig"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
@ -1333,6 +1438,15 @@ 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 = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -1488,6 +1602,21 @@ version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.16.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"spin 0.5.2",
|
||||||
|
"untrusted",
|
||||||
|
"web-sys",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
@ -1605,8 +1734,10 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"jsonwebtoken",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1656,6 +1787,18 @@ dependencies = [
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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"
|
||||||
|
@ -1831,7 +1974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db"
|
checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64",
|
"base64 0.21.4",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1873,7 +2016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624"
|
checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64",
|
"base64 0.21.4",
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"crc",
|
"crc",
|
||||||
|
@ -2171,6 +2314,12 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
|
@ -2216,6 +2365,70 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.37",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.37",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.87"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
@ -2253,6 +2466,15 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
|
|
@ -8,8 +8,10 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.4.0"
|
actix-web = "4.4.0"
|
||||||
argon2 = { version = "0.5.2", features = ["zeroize"] }
|
argon2 = { version = "0.5.2", features = ["zeroize"] }
|
||||||
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
clap = { version = "4.4.5", features = ["derive"] }
|
clap = { version = "4.4.5", features = ["derive"] }
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
|
jsonwebtoken = "8.3.0"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
|
|
60
server/src/jwt.rs
Executable file
60
server/src/jwt.rs
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
// use crate::{
|
||||||
|
// config::{DAYS_VALID, JWT_SECRET},
|
||||||
|
// Claims,
|
||||||
|
// };
|
||||||
|
use jsonwebtoken::{
|
||||||
|
decode, encode, errors::Result as JwtResult, DecodingKey, EncodingKey, Header, Validation,
|
||||||
|
};
|
||||||
|
use log::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const DAYS_VALID: i64 = 7;
|
||||||
|
const JWT_SECRET: &[u8] = "secret".as_bytes();
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Claims {
|
||||||
|
pub sub: String,
|
||||||
|
pub iss: String,
|
||||||
|
pub aud: String,
|
||||||
|
pub iat: usize,
|
||||||
|
pub exp: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// JwtResult is just a predefined error from the jsonwebtoken crate
|
||||||
|
pub fn token_factory(user: &str) -> JwtResult<String> {
|
||||||
|
info!("Issuing JWT token for {}", user);
|
||||||
|
|
||||||
|
let token = encode(
|
||||||
|
&Header::default(),
|
||||||
|
&Claims {
|
||||||
|
sub: user.to_string(),
|
||||||
|
iss: "localhost".to_string(),
|
||||||
|
aud: "localhost".to_string(),
|
||||||
|
iat: chrono::Utc::now().timestamp() as usize,
|
||||||
|
exp: (chrono::Utc::now() + chrono::Duration::days(DAYS_VALID)).timestamp() as usize,
|
||||||
|
},
|
||||||
|
&EncodingKey::from_secret(JWT_SECRET),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_token(token: &str) -> JwtResult<Claims> {
|
||||||
|
let token_data = decode::<Claims>(
|
||||||
|
token,
|
||||||
|
&DecodingKey::from_secret(JWT_SECRET),
|
||||||
|
&Validation::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
match token_data {
|
||||||
|
Ok(token_data) => {
|
||||||
|
info!("Token validated for {}", token_data.claims.sub);
|
||||||
|
Ok(token_data.claims)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Token validation failed: {}", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,12 @@ use actix_web::{web::scope, App, HttpServer};
|
||||||
use log::info;
|
use log::info;
|
||||||
// use uuid::Uuid;
|
// use uuid::Uuid;
|
||||||
|
|
||||||
|
mod jwt;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod state;
|
mod state;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
use routes::{get_posts, new_post, register, test};
|
use routes::{get_posts, login, new_post, register, test};
|
||||||
use sqlx::ConnectOptions;
|
use sqlx::ConnectOptions;
|
||||||
use state::AppState;
|
use state::AppState;
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
.service(new_post)
|
.service(new_post)
|
||||||
.service(routes::vote)
|
.service(routes::vote)
|
||||||
.service(test)
|
.service(test)
|
||||||
|
.service(login)
|
||||||
.service(register)
|
.service(register)
|
||||||
.app_data(Data::new(data.clone())),
|
.app_data(Data::new(data.clone())),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::types::{NewPost, Post};
|
use crate::types::{NewPost, Post};
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
use actix_web::web::{Data, Path};
|
use actix_web::web::{to, Data, Path};
|
||||||
use actix_web::{get, post, web::Json, HttpResponse, Responder, Result};
|
use actix_web::{get, post, web::Json, HttpResponse, Responder, Result};
|
||||||
use argon2::password_hash::SaltString;
|
use argon2::password_hash::SaltString;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
@ -137,3 +137,55 @@ pub async fn register(data: Json<RegisterData>, state: Data<AppState>) -> Result
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json("User registered"))
|
Ok(HttpResponse::Ok().json("User registered"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct LoginData {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
use sqlx::Row;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct LoginResponse {
|
||||||
|
username: String,
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::jwt::token_factory;
|
||||||
|
|
||||||
|
#[post("/login")]
|
||||||
|
pub async fn login(data: Json<LoginData>, state: Data<AppState>) -> Result<impl Responder> {
|
||||||
|
let q = "SELECT password FROM users WHERE username = ?";
|
||||||
|
let query = sqlx::query(q).bind(&data.username);
|
||||||
|
let result = query.fetch_one(&state.pool).await.ok();
|
||||||
|
if let Some(row) = result {
|
||||||
|
let phc_from_db = row.get::<String, _>("password");
|
||||||
|
let pwhash = PasswordHash::new(&phc_from_db).unwrap_or_else(|_| {
|
||||||
|
warn!(
|
||||||
|
"Invalid hash for user {} fetched from database (not a valid PHC string)",
|
||||||
|
data.username
|
||||||
|
);
|
||||||
|
panic!();
|
||||||
|
});
|
||||||
|
|
||||||
|
match Argon2::default().verify_password(data.password.as_bytes(), &pwhash) {
|
||||||
|
Ok(some) => {
|
||||||
|
info!("User {} logged in", data.username);
|
||||||
|
let token = token_factory(&data.username).unwrap();
|
||||||
|
println!("{:?}", token);
|
||||||
|
return Ok(HttpResponse::Ok().json(LoginResponse {
|
||||||
|
username: data.username.clone(),
|
||||||
|
token: token,
|
||||||
|
}));
|
||||||
|
// Sign jwt with user claims
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
info!("User \"{}\" failed to log in", data.username);
|
||||||
|
return Ok(HttpResponse::BadRequest().json("Error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json("What happens here???"))
|
||||||
|
}
|
||||||
|
|
16
src/App.tsx
16
src/App.tsx
|
@ -10,12 +10,24 @@ import { LoginContext } from './Context.tsx';
|
||||||
|
|
||||||
// JSX.Element is the return type of every React component
|
// JSX.Element is the return type of every React component
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
const [open, setOpen] = useState(false);
|
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
||||||
|
const [currentUser, setCurrentUser] = useState<string | undefined>(undefined);
|
||||||
|
const [userToken, setUserToken] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
const loginContextData = {
|
||||||
|
loginModalOpen: loginModalOpen,
|
||||||
|
currentUser: currentUser,
|
||||||
|
userToken: userToken,
|
||||||
|
setOpen: setLoginModalOpen,
|
||||||
|
setCurrentUser: setCurrentUser,
|
||||||
|
setUserToken: setUserToken,
|
||||||
|
};
|
||||||
|
|
||||||
// const loginContextData = { open, setOpen };
|
// const loginContextData = { open, setOpen };
|
||||||
// const loginContext = createContext(loginContextData);
|
// const loginContext = createContext(loginContextData);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoginContext.Provider value={{ loginModalOpen: open, setOpen }} >
|
<LoginContext.Provider value={loginContextData} >
|
||||||
<Box flexDirection={"column"} display={"flex"} sx={{ width: "100%", minHeight: "100vh", backgroundColor:"background.default"}}>
|
<Box flexDirection={"column"} display={"flex"} sx={{ width: "100%", minHeight: "100vh", backgroundColor:"background.default"}}>
|
||||||
<Header />
|
<Header />
|
||||||
<LoginDialog />
|
<LoginDialog />
|
||||||
|
|
|
@ -2,15 +2,22 @@ import { createContext } from "react"
|
||||||
|
|
||||||
export const TestContext = createContext("Test123")
|
export const TestContext = createContext("Test123")
|
||||||
|
|
||||||
// export const loginContextData = { open: false, setOpen: (open: boolean): void => {} };
|
|
||||||
|
|
||||||
interface LoginCTX {
|
interface LoginCTX {
|
||||||
loginModalOpen: boolean;
|
loginModalOpen: boolean;
|
||||||
setOpen?: (open: boolean) => void;
|
|
||||||
currentUser?: string;
|
currentUser?: string;
|
||||||
userToken?: string;
|
userToken?: string;
|
||||||
|
setOpen?: (open: boolean) => void;
|
||||||
|
setCurrentUser?: (username: string) => void;
|
||||||
|
setUserToken?: (token: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginContextData = { loginModalOpen: false };
|
const loginContextData = {
|
||||||
|
loginModalOpen: false,
|
||||||
|
currentUser: undefined,
|
||||||
|
userToken: undefined,
|
||||||
|
setOpen: undefined,
|
||||||
|
setCurrentUser: undefined,
|
||||||
|
setUserToken: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export const LoginContext = createContext<LoginCTX>(loginContextData);
|
export const LoginContext = createContext<LoginCTX>(loginContextData);
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppBar, Button, ButtonGroup, Link, Typography } from "@mui/material";
|
import { AppBar, Button, ButtonGroup, Grid, Link, Typography } from "@mui/material";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import AcUnitIcon from '@mui/icons-material/AcUnit';
|
import AcUnitIcon from '@mui/icons-material/AcUnit';
|
||||||
import { cyan } from "@mui/material/colors";
|
import { cyan } from "@mui/material/colors";
|
||||||
|
@ -13,14 +13,17 @@ function LoginDisplay({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
||||||
|
|
||||||
const handleLogin = (): void => {
|
const handleLogin = (): void => {
|
||||||
console.log("Login button pressed")
|
console.log("Login button pressed")
|
||||||
console.log(loginCtx);
|
if (loginCtx.currentUser == undefined) {
|
||||||
loginCtx.setOpen?.(true); // If the loginCtx.setOpen is defined, call it with true as the argument
|
loginCtx.setOpen?.(true); // If the loginCtx.setOpen is defined, call it with true as the argument
|
||||||
console.log(loginCtx);
|
} else {
|
||||||
|
loginCtx.setCurrentUser?.(undefined);
|
||||||
|
loginCtx.setUserToken?.(undefined);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginCtx.currentUser != undefined) {
|
if (loginCtx.currentUser != undefined) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: 1, display: "flex", flexDirection: "column", justifyContent: "center", ...sx }}>
|
<Box sx={{ textAlign: "right", ...sx }}>
|
||||||
<Typography color={"#808080"} sx={{ textAlign: "right" }}>
|
<Typography color={"#808080"} sx={{ textAlign: "right" }}>
|
||||||
Logged in as:
|
Logged in as:
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -34,7 +37,7 @@ function LoginDisplay({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: 1, display: "flex", flexDirection: "column", justifyContent: "center", ...sx }}>
|
<Box sx={{ textAlign: "right", ...sx }}>
|
||||||
<Button variant="text" startIcon={<AccountCircleIcon />} onClick={(): void => handleLogin()}>Login</Button>
|
<Button variant="text" startIcon={<AccountCircleIcon />} onClick={(): void => handleLogin()}>Login</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
@ -43,11 +46,11 @@ function LoginDisplay({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
||||||
function Header({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
function Header({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<AppBar position='static' sx={{ p: 1, px: 3, display: 'flex', flexDirection: 'row', justifyContent: 'space-evenly', ...sx }}>
|
<AppBar position='static' sx={{ p: 1, px: 3, display: 'flex', flexDirection: 'row', justifyContent: 'space-evenly', ...sx }}>
|
||||||
<Box sx={{ width: 1, px: 3, display: "flex", flexDirection: "row", flexGrow: 1, maxWidth: "1200px", justifyContent: "space-between" }}>
|
<Grid container px={2} spacing={2} sx={{ flexGrow: 1, display: "flex", maxWidth: "1200px", flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}>
|
||||||
<HeaderLogo clickable/>
|
<Grid item xs={12} sm={6} md={4} lg={4}><HeaderLogo clickable /></Grid>
|
||||||
<NavButtons />
|
<Grid item xs={12} sm={6} md={4} lg={4}><NavButtons /></Grid>
|
||||||
<LoginDisplay />
|
<Grid item xs={12} sm={6} md={4} lg={4}><LoginDisplay /></Grid>
|
||||||
</Box>
|
</Grid>
|
||||||
</AppBar >)
|
</AppBar >)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ function HeaderLogo({ clickable, sx }: { clickable?: boolean, sx?: React.CSSProp
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
width: 1,
|
width: "fit-content",
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -80,7 +83,7 @@ function NavButtons({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
||||||
<ButtonGroup variant="text" aria-label="text button group" sx={{ width: "100%" }}>
|
<ButtonGroup variant="text" aria-label="text button group" sx={{ width: "100%" }}>
|
||||||
{["Home", "New"].map((typename): JSX.Element => {
|
{["Home", "New"].map((typename): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Button startIcon={typename === "Home" ? <HomeIcon /> : <PostAddIcon />} key={typename} sx={{ px:2, bgcolor: "#FFFFFF15", width: 1}}>{typename}</Button>
|
<Button startIcon={typename === "Home" ? <HomeIcon /> : <PostAddIcon />} key={typename} sx={{ px: 2, bgcolor: "#FFFFFF15", width: 1 }}>{typename}</Button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { DialogContentText } from "@mui/material";
|
||||||
import { DialogActions } from "@mui/material";
|
import { DialogActions } from "@mui/material";
|
||||||
import { Button } from "@mui/material";
|
import { Button } from "@mui/material";
|
||||||
import { TextField } from "@mui/material";
|
import { TextField } from "@mui/material";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { LoginContext } from "./Context";
|
import { LoginContext } from "./Context";
|
||||||
// import { TestContext } from "./Context";
|
// import { TestContext } from "./Context";
|
||||||
|
@ -16,6 +16,11 @@ interface RegisterData {
|
||||||
captcha: string,
|
captcha: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginData {
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
}
|
||||||
|
|
||||||
function sendRegister(data: RegisterData): void {
|
function sendRegister(data: RegisterData): void {
|
||||||
console.log(JSON.stringify(data));
|
console.log(JSON.stringify(data));
|
||||||
fetch("/api/register", {
|
fetch("/api/register", {
|
||||||
|
@ -25,23 +30,65 @@ function sendRegister(data: RegisterData): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginResponse {
|
||||||
|
username: string,
|
||||||
|
token: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendLogin(data: LoginData): Promise<LoginResponse> {
|
||||||
|
// console.log(JSON.stringify(data));
|
||||||
|
const response_promise = await fetch("/api/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const logindata = await response_promise.json();
|
||||||
|
return (logindata as LoginResponse);
|
||||||
|
}
|
||||||
|
|
||||||
function LoginDialog(): JSX.Element {
|
function LoginDialog(): JSX.Element {
|
||||||
// const [open, setOpen] = useState(openState);
|
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
// const test = useContext(TestContext);
|
|
||||||
|
|
||||||
const loginCTX = useContext(LoginContext);
|
const loginCTX = useContext(LoginContext);
|
||||||
|
|
||||||
|
const setLoggedInAs = (username: string, token: string): void => {
|
||||||
|
loginCTX.setCurrentUser?.(username);
|
||||||
|
loginCTX.setUserToken?.(token);
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// const setLogOut = (): void => {
|
||||||
|
// loginCTX.setCurrentUser?.(undefined);
|
||||||
|
// loginCTX.setUserToken?.(undefined);
|
||||||
|
// localStorage.removeItem("loginState");
|
||||||
|
// }
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
loginCTX.setOpen?.(false);
|
loginCTX.setOpen?.(false);
|
||||||
|
setUsername("");
|
||||||
|
setPassword("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogin = (): void => {
|
// Check for localStorage login state
|
||||||
console.log(username, password);
|
useEffect((): void => {
|
||||||
|
const loginState = JSON.parse(localStorage.getItem("loginState")||"{}");
|
||||||
|
if (loginState.username && loginState.token) {
|
||||||
|
setLoggedInAs(loginState.username, loginState.token);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleLogin = async (): Promise<void> => {
|
||||||
|
const response = await sendLogin({ username: username, password: password });
|
||||||
|
if (response && response.username && response.token) {
|
||||||
|
setLoggedInAs(response.username, response.token);
|
||||||
|
localStorage.setItem("loginState", JSON.stringify(response));
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegister = (): void => {
|
const handleRegister = (): void => {
|
||||||
sendRegister({ username: username, password: password, captcha: "test" });
|
sendRegister({ username: username, password: password, captcha: "test" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1 @@
|
||||||
// :root {
|
// No free standing CSS
|
||||||
// font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
// line-height: 1.5;
|
|
||||||
// font-weight: 400;
|
|
||||||
|
|
||||||
// color-scheme: light dark;
|
|
||||||
// color: rgba(255, 255, 255, 0.87);
|
|
||||||
// background-color: #242424;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// body * {
|
|
||||||
// box-sizing: border-box;
|
|
||||||
// margin: 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// max-width: 1280px;
|
|
||||||
// margin: 0 auto;
|
|
||||||
// padding: 2rem;
|
|
||||||
// text-align: center;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // @media (prefers-reduced-motion: no-preference) {}
|
|
||||||
|
|
||||||
// body {
|
|
||||||
// margin: 0;
|
|
||||||
// // display: flex;
|
|
||||||
// min-width: 320px;
|
|
||||||
// min-height: 100vh;
|
|
||||||
// place-items: center;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @media (prefers-color-scheme: light) {
|
|
||||||
// :root {
|
|
||||||
// color: #213547;
|
|
||||||
// background-color: #ffffff;
|
|
||||||
// }
|
|
||||||
// a:hover {
|
|
||||||
// color: #747bff;
|
|
||||||
// }
|
|
||||||
// button {
|
|
||||||
// background-color: #f9f9f9;
|
|
||||||
// }
|
|
||||||
// }
|
|
Loading…
Reference in a new issue