feat: authentication system
This commit is contained in:
parent
d8c29e1ec8
commit
37153c6e36
15 changed files with 852 additions and 18 deletions
422
Cargo.lock
generated
422
Cargo.lock
generated
|
|
@ -41,11 +41,18 @@ dependencies = [
|
||||||
name = "alexandria"
|
name = "alexandria"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"argon2",
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-extra",
|
||||||
|
"clap",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"inquire",
|
||||||
|
"jsonwebtoken",
|
||||||
"log",
|
"log",
|
||||||
|
"password-hash",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"rand_core 0.9.3",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -84,6 +91,56 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
|
@ -93,6 +150,18 @@ dependencies = [
|
||||||
"derive_arbitrary",
|
"derive_arbitrary",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argon2"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"blake2",
|
||||||
|
"cpufeatures",
|
||||||
|
"password-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
|
@ -211,6 +280,29 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"headers",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustversion",
|
||||||
|
"serde",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -263,6 +355,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
|
|
@ -284,6 +382,15 @@ dependencies = [
|
||||||
"wyz",
|
"wyz",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
|
@ -390,6 +497,52 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|
@ -469,6 +622,31 @@ version = "0.8.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
|
@ -546,6 +724,12 @@ version = "0.15.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
@ -769,6 +953,24 @@ dependencies = [
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuzzy-matcher"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
|
||||||
|
dependencies = [
|
||||||
|
"thread_local",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fxhash"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
|
|
@ -860,6 +1062,30 @@ dependencies = [
|
||||||
"hashbrown 0.15.4",
|
"hashbrown 0.15.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bytes",
|
||||||
|
"headers-core",
|
||||||
|
"http",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"sha1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "headers-core"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -1196,13 +1422,30 @@ dependencies = [
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inquire"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.1",
|
||||||
|
"crossterm",
|
||||||
|
"dyn-clone",
|
||||||
|
"fuzzy-matcher",
|
||||||
|
"fxhash",
|
||||||
|
"newline-converter",
|
||||||
|
"once_cell",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-uring"
|
name = "io-uring"
|
||||||
version = "0.7.9"
|
version = "0.7.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
|
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
@ -1234,6 +1477,12 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
|
|
@ -1250,6 +1499,21 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "9.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"js-sys",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -1372,6 +1636,18 @@ dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -1400,6 +1676,15 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "newline-converter"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
|
@ -1478,13 +1763,19 @@ version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.73"
|
version = "0.10.73"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1584,12 +1875,33 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "password-hash"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
@ -1901,7 +2213,7 @@ version = "0.5.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2119,7 +2431,7 @@ version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
|
|
@ -2297,7 +2609,7 @@ version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -2396,6 +2708,27 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.5"
|
version = "1.4.5"
|
||||||
|
|
@ -2427,6 +2760,18 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.10"
|
version = "0.4.10"
|
||||||
|
|
@ -2582,7 +2927,7 @@ dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64",
|
"base64",
|
||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
@ -2629,7 +2974,7 @@ dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64",
|
"base64",
|
||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"chrono",
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
|
|
@ -2713,6 +3058,12 @@ dependencies = [
|
||||||
"unicode-properties",
|
"unicode-properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.26.3"
|
version = "0.26.3"
|
||||||
|
|
@ -2773,7 +3124,7 @@ version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys",
|
||||||
]
|
]
|
||||||
|
|
@ -2836,6 +3187,15 @@ dependencies = [
|
||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.41"
|
version = "0.3.41"
|
||||||
|
|
@ -2902,7 +3262,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"io-uring",
|
"io-uring",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 1.0.4",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
|
|
@ -3018,7 +3378,7 @@ version = "0.6.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
|
|
@ -3136,6 +3496,18 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
@ -3165,6 +3537,12 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utoipa"
|
name = "utoipa"
|
||||||
version = "5.4.0"
|
version = "5.4.0"
|
||||||
|
|
@ -3414,6 +3792,22 @@ dependencies = [
|
||||||
"wasite",
|
"wasite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
|
|
@ -3423,6 +3817,12 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
|
|
@ -3730,7 +4130,7 @@ version = "0.39.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.4", features = [ "macros", "ws", "tokio" ] }
|
axum = { version = "0.8.4", features = [ "macros", "ws", "tokio" ] }
|
||||||
|
axum-extra = { version = "0.10.1", features = ["typed-header"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
reqwest = "0.12.22"
|
reqwest = "0.12.22"
|
||||||
sea-orm = { version = "1.1.13", features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros" ] }
|
sea-orm = { version = "1.1.13", features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros" ] }
|
||||||
|
|
@ -18,4 +19,10 @@ utoipa-redoc = { version = "6", features = ["axum"] }
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
|
argon2 = { version = "0.5.3", features = ["password-hash", "alloc", "rand"] }
|
||||||
|
clap = { version = "4.5.42", features = ["derive"] }
|
||||||
|
inquire = "0.7.5"
|
||||||
|
rand_core = { version = "0.9.3", features = ["os_rng"] }
|
||||||
|
password-hash = { version = "0.5.0", features = ["getrandom"] }
|
||||||
|
jsonwebtoken = "9.3.1"
|
||||||
|
|
||||||
|
|
|
||||||
1
scripts/generate_secret.sh
Executable file
1
scripts/generate_secret.sh
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
tr -dc A-Za-z0-9 </dev/urandom | head -c 256; echo
|
||||||
|
|
@ -3,4 +3,5 @@ pub mod prelude;
|
||||||
pub mod book;
|
pub mod book;
|
||||||
pub mod book_instance;
|
pub mod book_instance;
|
||||||
pub mod owner;
|
pub mod owner;
|
||||||
|
pub mod user;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
pub use super::book::Entity as Book;
|
pub use super::book::Entity as Book;
|
||||||
pub use super::book_instance::Entity as BookInstance;
|
pub use super::book_instance::Entity as BookInstance;
|
||||||
pub use super::owner::Entity as Owner;
|
pub use super::owner::Entity as Owner;
|
||||||
|
pub use super::user::Entity as User;
|
||||||
|
|
|
||||||
26
src/entities/user.rs
Normal file
26
src/entities/user.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use argon2::{Argon2, PasswordHash, PasswordVerifier};
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "User")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = true)]
|
||||||
|
pub id: u32,
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub username: String,
|
||||||
|
pub hashed_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn verify_password(&self, password: String) -> bool {
|
||||||
|
let parsed_hash = PasswordHash::new(&self.hashed_password).unwrap();
|
||||||
|
Argon2::default().verify_password(password.as_bytes(), &parsed_hash).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
105
src/main.rs
105
src/main.rs
|
|
@ -1,21 +1,40 @@
|
||||||
use std::{net::SocketAddr, sync::Arc};
|
use std::{net::SocketAddr, path::PathBuf, sync::{Arc, LazyLock}};
|
||||||
|
|
||||||
use axum::{extract::State, http::HeaderMap, routing::get};
|
use axum::{extract::State, http::HeaderMap, middleware, routing::get};
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
use reqwest::{header::USER_AGENT};
|
use reqwest::{header::USER_AGENT};
|
||||||
use sea_orm::{ConnectionTrait, Database, DatabaseConnection, EntityTrait, PaginatorTrait, Schema};
|
use sea_orm::{ConnectionTrait, Database, DatabaseConnection, EntityTrait, PaginatorTrait, Schema};
|
||||||
use tokio::{sync::broadcast::{self, Sender}};
|
use tokio::{sync::broadcast::{self, Sender}};
|
||||||
use utoipa::openapi::{ContactBuilder, InfoBuilder, LicenseBuilder};
|
use utoipa::{openapi::{security::{HttpAuthScheme, HttpBuilder, SecurityScheme}, ContactBuilder, InfoBuilder, LicenseBuilder}, Modify, OpenApi};
|
||||||
use utoipa_axum::router::OpenApiRouter;
|
use utoipa_axum::router::OpenApiRouter;
|
||||||
use utoipa_redoc::{Redoc, Servable};
|
use utoipa_redoc::{Redoc, Servable};
|
||||||
use utoipa_swagger_ui::{Config, SwaggerUi};
|
use utoipa_swagger_ui::{Config, SwaggerUi};
|
||||||
use utoipa_axum::routes;
|
use utoipa_axum::routes;
|
||||||
|
|
||||||
use crate::{entities::prelude::BookInstance, utils::events::Event};
|
use crate::{entities::prelude::BookInstance, routes::auth::Keys, utils::events::Event};
|
||||||
|
|
||||||
pub mod entities;
|
pub mod entities;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "Alexandria")]
|
||||||
|
#[command(version = "1.0")]
|
||||||
|
#[command(about = "BAL management server", long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(long, short, value_name = "FILE")]
|
||||||
|
database: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
Run,
|
||||||
|
User
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
app_name: String,
|
app_name: String,
|
||||||
db_conn: Arc<DatabaseConnection>,
|
db_conn: Arc<DatabaseConnection>,
|
||||||
|
|
@ -36,11 +55,40 @@ async fn index(
|
||||||
format!("Hello from {app_name}! Database is {status}. We currently have {book_count} books in stock !")
|
format!("Hello from {app_name}! Database is {status}. We currently have {book_count} books in stock !")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static KEYS: LazyLock<Keys> = LazyLock::new(|| {
|
||||||
|
let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
||||||
|
Keys::new(secret.as_bytes())
|
||||||
|
});
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let db: Arc<DatabaseConnection> = Arc::new(Database::connect(String::from("sqlite://./alexandria.db?mode=rwc")).await.unwrap());
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
let db_path = match cli.database {
|
||||||
|
Some(path) => {
|
||||||
|
if path.is_dir() {
|
||||||
|
log::error!("{path:?} is a directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let None = path.parent() {
|
||||||
|
log::error!("Invalid path: {path:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
path.to_string_lossy().into_owned()
|
||||||
|
},
|
||||||
|
None => "./alexandria.db".to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
let db: Arc<DatabaseConnection> = Arc::new(
|
||||||
|
match Database::connect(format!("sqlite://{db_path}?mode=rwc")).await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(target: "database", "Error while opening database: {}", e.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let builder = db.get_database_backend();
|
let builder = db.get_database_backend();
|
||||||
let schema = Schema::new(builder);
|
let schema = Schema::new(builder);
|
||||||
|
|
@ -56,9 +104,25 @@ async fn main() {
|
||||||
log::error!(target: "database", "Error while creating owner table: {err:?}");
|
log::error!(target: "database", "Error while creating owner table: {err:?}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if let Err(err) = db.execute(builder.build(schema.create_table_from_entity(crate::entities::prelude::User).if_not_exists())).await {
|
||||||
|
log::error!(target: "database", "Error while creating user table: {err:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
Commands::Run => run_server(db).await,
|
||||||
|
Commands::User => utils::cli::manage_users(db).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_server(db: Arc<DatabaseConnection>) {
|
||||||
let (event_bus, _) = broadcast::channel(16);
|
let (event_bus, _) = broadcast::channel(16);
|
||||||
|
|
||||||
|
if std::env::var("JWT_SECRET").is_err() {
|
||||||
|
log::error!("JWT_SECRET is not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut default_headers = HeaderMap::new();
|
let mut default_headers = HeaderMap::new();
|
||||||
default_headers.append(USER_AGENT, "Alexandria/1.0 (unionetudianteauvergne@gmail.com)".parse().unwrap());
|
default_headers.append(USER_AGENT, "Alexandria/1.0 (unionetudianteauvergne@gmail.com)".parse().unwrap());
|
||||||
let shared_state = Arc::new(AppState {
|
let shared_state = Arc::new(AppState {
|
||||||
|
|
@ -68,6 +132,30 @@ async fn main() {
|
||||||
web_client: reqwest::Client::builder().default_headers(default_headers).build().expect("creating the reqwest client failed")
|
web_client: reqwest::Client::builder().default_headers(default_headers).build().expect("creating the reqwest client failed")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
tags(
|
||||||
|
(name = "book-api", description = "Book management endpoints.")
|
||||||
|
),
|
||||||
|
modifiers(&SecurityAddon)
|
||||||
|
)]
|
||||||
|
struct ApiDoc;
|
||||||
|
|
||||||
|
struct SecurityAddon;
|
||||||
|
|
||||||
|
impl Modify for SecurityAddon {
|
||||||
|
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||||
|
let components = openapi.components.as_mut().unwrap();
|
||||||
|
components.add_security_scheme(
|
||||||
|
"jwt",
|
||||||
|
SecurityScheme::Http(
|
||||||
|
HttpBuilder::new().scheme(HttpAuthScheme::Bearer).bearer_format("JWT").build()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (router, mut api) = OpenApiRouter::new()
|
let (router, mut api) = OpenApiRouter::new()
|
||||||
// Book API
|
// Book API
|
||||||
.routes(routes!(routes::book::get_book_by_ean))
|
.routes(routes!(routes::book::get_book_by_ean))
|
||||||
|
|
@ -85,6 +173,10 @@ async fn main() {
|
||||||
.routes(routes!(routes::owner::get_owners))
|
.routes(routes!(routes::owner::get_owners))
|
||||||
// Misc
|
// Misc
|
||||||
.routes(routes!(routes::websocket::ws_handler))
|
.routes(routes!(routes::websocket::ws_handler))
|
||||||
|
// Authentication
|
||||||
|
.route_layer(middleware::from_fn_with_state(shared_state.clone(), routes::auth::auth_middleware))
|
||||||
|
.routes(routes!(routes::auth::auth))
|
||||||
|
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
.with_state(shared_state)
|
.with_state(shared_state)
|
||||||
.split_for_parts();
|
.split_for_parts();
|
||||||
|
|
@ -101,6 +193,8 @@ async fn main() {
|
||||||
.version("1.0.0")
|
.version("1.0.0")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
api.merge(ApiDoc::openapi());
|
||||||
|
|
||||||
let redoc = Redoc::with_url("/docs/", api.clone());
|
let redoc = Redoc::with_url("/docs/", api.clone());
|
||||||
let swagger = SwaggerUi::new("/docs2/")
|
let swagger = SwaggerUi::new("/docs2/")
|
||||||
.url("/docs2/openapi.json", api)
|
.url("/docs2/openapi.json", api)
|
||||||
|
|
@ -118,4 +212,3 @@ async fn main() {
|
||||||
router.into_make_service_with_connect_info::<SocketAddr>()
|
router.into_make_service_with_connect_info::<SocketAddr>()
|
||||||
).await.unwrap();
|
).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
144
src/routes/auth.rs
Normal file
144
src/routes/auth.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
use std::sync::{Arc, LazyLock};
|
||||||
|
|
||||||
|
use argon2::PasswordHash;
|
||||||
|
use axum::{
|
||||||
|
extract::{FromRequestParts, Request, State}, http::{request::Parts, HeaderMap, StatusCode}, middleware::Next, response::{IntoResponse, Response}, routing::{get, post}, Json, RequestPartsExt, Router
|
||||||
|
};
|
||||||
|
use axum_extra::{
|
||||||
|
headers::{authorization::Bearer, Authorization},
|
||||||
|
TypedHeader,
|
||||||
|
};
|
||||||
|
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||||
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{entities::user, AppState, KEYS};
|
||||||
|
|
||||||
|
pub async fn auth_middleware(
|
||||||
|
_claims: Claims,
|
||||||
|
request: Request,
|
||||||
|
next: Next,
|
||||||
|
) -> Result<Response, StatusCode> {
|
||||||
|
let response = next.run(request).await;
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/auth",
|
||||||
|
request_body = AuthPayload,
|
||||||
|
responses(
|
||||||
|
(status = OK, body = AuthBody, description = "Successfully authenticated"),
|
||||||
|
(status = UNAUTHORIZED, description = "Wrong credentials"),
|
||||||
|
(status = BAD_REQUEST, description = "Missing credentials"),
|
||||||
|
(status = INTERNAL_SERVER_ERROR, description = "Token creation error"),
|
||||||
|
(status = BAD_REQUEST, description = "Invalid token")
|
||||||
|
),
|
||||||
|
summary = "Authenticate to access the API",
|
||||||
|
tag = "auth-api",
|
||||||
|
)]
|
||||||
|
pub async fn auth(State(state): State<Arc<AppState>>, Json(payload): Json<AuthPayload>) -> Result<Json<AuthBody>, AuthError> {
|
||||||
|
log::debug!("Payload: {payload:?}");
|
||||||
|
if payload.username.is_empty() || payload.password.is_empty() {
|
||||||
|
return Err(AuthError::MissingCredentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
match user::Entity::find().filter(user::Column::Username.eq(payload.username)).one(state.db_conn.as_ref()).await {
|
||||||
|
Err(_) | Ok(None) => return Err(AuthError::WrongCredentials),
|
||||||
|
Ok(Some(user)) => {
|
||||||
|
user.verify_password(payload.password);
|
||||||
|
|
||||||
|
let claims = Claims {
|
||||||
|
sub: user.username,
|
||||||
|
exp: 2000000000,
|
||||||
|
};
|
||||||
|
let token = encode(&Header::default(), &claims, &KEYS.encoding)
|
||||||
|
.map_err(|_| AuthError::TokenCreation)?;
|
||||||
|
|
||||||
|
Ok(Json(AuthBody::new(token)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthBody {
|
||||||
|
fn new(access_token: String) -> Self {
|
||||||
|
Self {
|
||||||
|
access_token,
|
||||||
|
token_type: "Bearer".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> FromRequestParts<S> for Claims
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = AuthError;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, _s: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
let TypedHeader(Authorization(bearer)) = parts
|
||||||
|
.extract::<TypedHeader<Authorization<Bearer>>>()
|
||||||
|
.await
|
||||||
|
.map_err(|_| AuthError::InvalidToken)?;
|
||||||
|
let token_data = decode::<Claims>(bearer.token(), &KEYS.decoding, &Validation::default())
|
||||||
|
.map_err(|_| AuthError::InvalidToken)?;
|
||||||
|
|
||||||
|
Ok(token_data.claims)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for AuthError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
let (status, error_message) = match self {
|
||||||
|
AuthError::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"),
|
||||||
|
AuthError::MissingCredentials => (StatusCode::BAD_REQUEST, "Missing credentials"),
|
||||||
|
AuthError::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"),
|
||||||
|
AuthError::InvalidToken => (StatusCode::BAD_REQUEST, "Invalid token"),
|
||||||
|
};
|
||||||
|
let body = Json(json!({
|
||||||
|
"error": error_message,
|
||||||
|
}));
|
||||||
|
(status, body).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Keys {
|
||||||
|
encoding: EncodingKey,
|
||||||
|
decoding: DecodingKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keys {
|
||||||
|
pub fn new(secret: &[u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
encoding: EncodingKey::from_secret(secret),
|
||||||
|
decoding: DecodingKey::from_secret(secret),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Claims {
|
||||||
|
sub: String,
|
||||||
|
exp: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, utoipa::ToSchema)]
|
||||||
|
pub struct AuthBody {
|
||||||
|
access_token: String,
|
||||||
|
token_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||||
|
pub struct AuthPayload {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AuthError {
|
||||||
|
WrongCredentials,
|
||||||
|
MissingCredentials,
|
||||||
|
TokenCreation,
|
||||||
|
InvalidToken,
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ struct BookByIdParams(u32);
|
||||||
get,
|
get,
|
||||||
path = "/book/id/{id}",
|
path = "/book/id/{id}",
|
||||||
params(BookByIdParams),
|
params(BookByIdParams),
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = book::Model, description = "Found book with corresponding ID in the database", examples(
|
(status = OK, body = book::Model, description = "Found book with corresponding ID in the database", examples(
|
||||||
("Found regular book" = (value = json!({"author": "Pierre Bottero", "ean": "9782700234015", "id": 5642, "title": "Ellana l'envol"}))),
|
("Found regular book" = (value = json!({"author": "Pierre Bottero", "ean": "9782700234015", "id": 5642, "title": "Ellana l'envol"}))),
|
||||||
|
|
@ -52,6 +53,7 @@ struct BookByEanParams(String);
|
||||||
get,
|
get,
|
||||||
path = "/book/ean/{ean}",
|
path = "/book/ean/{ean}",
|
||||||
params(BookByEanParams),
|
params(BookByEanParams),
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = book::Model, description = "Found book with corresponding EAN", examples(
|
(status = OK, body = book::Model, description = "Found book with corresponding EAN", examples(
|
||||||
("Found regular book" = (value = json!({"author": "Pierre Bottero", "ean": "9782700234015", "id": 5642, "title": "Ellana l'envol"})))
|
("Found regular book" = (value = json!({"author": "Pierre Bottero", "ean": "9782700234015", "id": 5642, "title": "Ellana l'envol"})))
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ struct BookInstanceByIdParams(u32);
|
||||||
get,
|
get,
|
||||||
path = "/book_instance/{id}",
|
path = "/book_instance/{id}",
|
||||||
params(BookInstanceByIdParams),
|
params(BookInstanceByIdParams),
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = book_instance::Model, description = "Found book instance with corresponding ID in the database"),
|
(status = OK, body = book_instance::Model, description = "Found book instance with corresponding ID in the database"),
|
||||||
(status = NOT_FOUND, description = "No book instance with this id exists in the database")
|
(status = NOT_FOUND, description = "No book instance with this id exists in the database")
|
||||||
|
|
@ -50,6 +51,7 @@ pub struct BookInstanceCreateParams {
|
||||||
post,
|
post,
|
||||||
path = "/book_instance",
|
path = "/book_instance",
|
||||||
request_body = BookInstanceCreateParams,
|
request_body = BookInstanceCreateParams,
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = book_instance::Model, description = "Successfully created book instance"),
|
(status = OK, body = book_instance::Model, description = "Successfully created book instance"),
|
||||||
),
|
),
|
||||||
|
|
@ -93,6 +95,7 @@ pub struct BookInstanceUpdateParams {
|
||||||
path = "/book_instance/{id}",
|
path = "/book_instance/{id}",
|
||||||
params(BookInstanceByIdParams),
|
params(BookInstanceByIdParams),
|
||||||
request_body = BookInstanceUpdateParams,
|
request_body = BookInstanceUpdateParams,
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = book_instance::Model, description = "Successfully updated book instance"),
|
(status = OK, body = book_instance::Model, description = "Successfully updated book instance"),
|
||||||
(status = NOT_FOUND, description = "No book instance with specified id was found"),
|
(status = NOT_FOUND, description = "No book instance with specified id was found"),
|
||||||
|
|
@ -148,6 +151,7 @@ pub struct BookInstanceSaleParams {
|
||||||
path = "/book_instance/{id}/sell",
|
path = "/book_instance/{id}/sell",
|
||||||
params(BookInstanceByIdParams),
|
params(BookInstanceByIdParams),
|
||||||
request_body = BookInstanceSaleParams,
|
request_body = BookInstanceSaleParams,
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = book_instance::Model, description = "Successfully sold book instance"),
|
(status = OK, body = book_instance::Model, description = "Successfully sold book instance"),
|
||||||
(status = NOT_FOUND, description = "No book instance with specified id was found"),
|
(status = NOT_FOUND, description = "No book instance with specified id was found"),
|
||||||
|
|
@ -186,6 +190,7 @@ pub async fn sell_book_instance(
|
||||||
post,
|
post,
|
||||||
path = "/book_instance/bulk",
|
path = "/book_instance/bulk",
|
||||||
request_body = Vec<BookInstanceCreateParams>,
|
request_body = Vec<BookInstanceCreateParams>,
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, description = "Successfully created book instances"),
|
(status = OK, description = "Successfully created book instances"),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod auth;
|
||||||
pub mod book;
|
pub mod book;
|
||||||
pub mod book_instance;
|
pub mod book_instance;
|
||||||
pub mod owner;
|
pub mod owner;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ struct OwnerByIdParams(u32);
|
||||||
get,
|
get,
|
||||||
path = "/owner/{id}",
|
path = "/owner/{id}",
|
||||||
params(OwnerByIdParams),
|
params(OwnerByIdParams),
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = owner::Model, description = "Found owner with corresponding ID in the database"),
|
(status = OK, body = owner::Model, description = "Found owner with corresponding ID in the database"),
|
||||||
(status = NOT_FOUND, description = "No owner with this id exists in the database")
|
(status = NOT_FOUND, description = "No owner with this id exists in the database")
|
||||||
|
|
@ -50,6 +51,7 @@ pub struct OwnerCreateParams {
|
||||||
post,
|
post,
|
||||||
path = "/owner",
|
path = "/owner",
|
||||||
request_body = OwnerCreateParams,
|
request_body = OwnerCreateParams,
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = owner::Model, description = "Successfully created owner"),
|
(status = OK, body = owner::Model, description = "Successfully created owner"),
|
||||||
),
|
),
|
||||||
|
|
@ -96,6 +98,7 @@ pub struct OwnerUpdateParams {
|
||||||
path = "/owner/{id}",
|
path = "/owner/{id}",
|
||||||
params(OwnerByIdParams),
|
params(OwnerByIdParams),
|
||||||
request_body = OwnerUpdateParams,
|
request_body = OwnerUpdateParams,
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = owner::Model, description = "Successfully updated owner"),
|
(status = OK, body = owner::Model, description = "Successfully updated owner"),
|
||||||
(status = NOT_FOUND, description = "No owner with this id exists in the database")
|
(status = NOT_FOUND, description = "No owner with this id exists in the database")
|
||||||
|
|
@ -145,6 +148,7 @@ pub async fn update_owner(
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/owners",
|
path = "/owners",
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = OK, body = Vec<owner::Model>, description = "List of owners"),
|
(status = OK, body = Vec<owner::Model>, description = "List of owners"),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use futures_util::{sink::SinkExt, stream::StreamExt};
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/ws",
|
path = "/ws",
|
||||||
|
security(("jwt" = [])),
|
||||||
responses(
|
responses(
|
||||||
(status = SWITCHING_PROTOCOLS, description = "Succesfully reached the websocket, now upgrade to establish the connection"),
|
(status = SWITCHING_PROTOCOLS, description = "Succesfully reached the websocket, now upgrade to establish the connection"),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
147
src/utils/cli.rs
Normal file
147
src/utils/cli.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
use std::{fmt::Display, sync::Arc};
|
||||||
|
|
||||||
|
use argon2::{password_hash::{SaltString}, Argon2, PasswordHasher};
|
||||||
|
use inquire::{min_length, prompt_text, Confirm, Password, Select, Text};
|
||||||
|
use password_hash::rand_core::OsRng;
|
||||||
|
use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter};
|
||||||
|
|
||||||
|
use crate::entities::{prelude::User, user::{self, ActiveModel}};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
enum Action {
|
||||||
|
Add,
|
||||||
|
Delete,
|
||||||
|
Update
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Action {
|
||||||
|
const VARIANTS: &'static [Action] = &[
|
||||||
|
Self::Add,
|
||||||
|
Self::Delete,
|
||||||
|
Self::Update,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Action {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
enum Update {
|
||||||
|
Username,
|
||||||
|
Password
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Update {
|
||||||
|
const VARIANTS: &'static [Update] = &[
|
||||||
|
Self::Username,
|
||||||
|
Self::Password,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Update {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn manage_users(db: Arc<DatabaseConnection>) {
|
||||||
|
loop {
|
||||||
|
match Select::new("User Manager (ESC to quit)", Action::VARIANTS.to_vec()).prompt_skippable() {
|
||||||
|
Ok(Some(action)) => {
|
||||||
|
match action {
|
||||||
|
Action::Add => {
|
||||||
|
let username = Text::new("Username").with_validator(min_length!(3)).prompt().unwrap();
|
||||||
|
if User::find().filter(user::Column::Username.eq(username.clone())).one(db.as_ref()).await.is_ok_and(|r| r.is_some()) {
|
||||||
|
println!("Username already in use !");
|
||||||
|
} else {
|
||||||
|
let password = Password::new("Password")
|
||||||
|
.with_validator(min_length!(10))
|
||||||
|
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||||
|
.prompt().unwrap();
|
||||||
|
let new_user = user::ActiveModel {
|
||||||
|
id: NotSet,
|
||||||
|
username: Set(username),
|
||||||
|
hashed_password: Set(hash_password(password))
|
||||||
|
};
|
||||||
|
let _ = new_user.insert(db.as_ref()).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Action::Delete => {
|
||||||
|
match select_user(db.clone()).await {
|
||||||
|
Some(user) => {
|
||||||
|
let username = user.username.clone();
|
||||||
|
|
||||||
|
match Confirm::new(format!("Delete {username} ?").as_ref())
|
||||||
|
.with_default(false)
|
||||||
|
.with_help_message("The user and all associated data will *permanently* be deleted")
|
||||||
|
.prompt() {
|
||||||
|
Ok(true) => {
|
||||||
|
let _ = user.delete(db.as_ref()).await;
|
||||||
|
println!("{username} has been permanently deleted")
|
||||||
|
},
|
||||||
|
Ok(false) | Err(_) => println!("Cancelled deletion of {username}"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => println!("Could not find user")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Action::Update => {
|
||||||
|
match select_user(db.clone()).await {
|
||||||
|
Some(user) => {
|
||||||
|
match Select::new(format!("Editing {}", user.username).as_ref(), Update::VARIANTS.to_vec()).prompt() {
|
||||||
|
Ok(v) => {
|
||||||
|
let mut updated_user = ActiveModel::from(user);
|
||||||
|
match v {
|
||||||
|
Update::Username => {
|
||||||
|
updated_user.username = Set(Text::new("New username")
|
||||||
|
.with_initial_value(updated_user.username.unwrap().as_ref())
|
||||||
|
.prompt().unwrap()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Update::Password => {
|
||||||
|
match Password::new("Password")
|
||||||
|
.with_validator(min_length!(10))
|
||||||
|
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||||
|
.prompt() {
|
||||||
|
Ok(new_password) => updated_user.hashed_password = Set(hash_password(new_password)),
|
||||||
|
Err(_) => println!("Cancelled user update")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = updated_user.save(db.as_ref()).await;
|
||||||
|
},
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => println!("Could not find user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(None) | Err(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_password(password: String) -> String {
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
Argon2::default().hash_password(&password.clone().into_bytes(), &salt).unwrap().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn select_user(db: Arc<DatabaseConnection>) -> Option<user::Model> {
|
||||||
|
let users = User::find().all(db.as_ref()).await.unwrap();
|
||||||
|
if users.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match Select::new("Select a user", users.iter().map(|u| u.username.clone()).collect()).prompt() {
|
||||||
|
Ok(selection) => User::find().filter(user::Column::Username.eq(selection)).one(db.as_ref()).await.unwrap(),
|
||||||
|
Err(_) => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
|
pub mod cli;
|
||||||
|
pub mod events;
|
||||||
pub mod open_library;
|
pub mod open_library;
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
pub mod events;
|
|
||||||
|
|
||||||
|
|
|
||||||
Reference in a new issue