🏡 index : ~doyle/jogre.git

author Jordan Doyle <jordan@doyle.la> 2023-09-17 20:43:08.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2023-09-17 20:43:08.0 +01:00:00
commit
d7b0b1a5a4bb5e191aeac7fb996f62dbb9f02cad [patch]
tree
f7eee2189d1e7149f4560959939d4cd837c7de10
parent
7504ef7a6959829acceba7d069b4de635e9a724d
download
d7b0b1a5a4bb5e191aeac7fb996f62dbb9f02cad.tar.gz

Hack together OAuth2 authorization code flow support

Currently uses blocking APIs of rocksdb & oxide-auth

Diff

 .gitignore                                  |    1 +
 Cargo.lock                                  | 1980 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 Cargo.toml                                  |    3 ++-
 jogre-server/Cargo.toml                     |   31 +++++++++++++++++++++++++++++++
 jogre-server/config.toml                    |    5 +++++
 jogre-server/src/config.rs                  |   20 ++++++++++++++++++++
 jogre-server/src/context.rs                 |   57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jogre-server/src/main.rs                    |   59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jogre-server/src/store.rs                   |   89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jogre-server/src/util.rs                    |   81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jogre-server/src/context/oauth2.rs          |  279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jogre-server/src/layers/auth_required.rs    |   39 +++++++++++++++++++++++++++++++++++++++
 jogre-server/src/layers/logger.rs           |  125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jogre-server/src/layers/mod.rs              |    2 ++
 jogre-server/src/methods/mod.rs             |   27 +++++++++++++++++++++++++++
 jogre-server/src/methods/session.rs         |    3 +++
 jogre-server/src/store/rocksdb.rs           |  103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jogre-server/templates/auth/login.html      |  142 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 jogre-server/src/methods/oauth/authorize.rs |   13 +++++++++++++
 jogre-server/src/methods/oauth/mod.rs       |   19 +++++++++++++++++++
 jogre-server/src/methods/oauth/refresh.rs   |   13 +++++++++++++
 jogre-server/src/methods/oauth/token.rs     |   13 +++++++++++++
 22 files changed, 2939 insertions(+), 165 deletions(-)

diff --git a/.gitignore b/.gitignore
index 2f7896d..2c7428a 100644
--- a/.gitignore
+++ a/.gitignore
@@ -1,1 +1,2 @@
target/
db/
diff --git a/Cargo.lock b/Cargo.lock
index 553d60c..1ceefd8 100644
--- a/Cargo.lock
+++ a/Cargo.lock
@@ -1,8 +1,32 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
 "gimli",
]

[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"

[[package]]
name = "aho-corasick"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
dependencies = [
 "memchr",
]

[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -15,6 +39,124 @@
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
 "libc",
]

[[package]]
name = "anstream"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
 "anstyle",
 "anstyle-parse",
 "anstyle-query",
 "anstyle-wincon",
 "colorchoice",
 "utf8parse",
]

[[package]]
name = "anstyle"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"

[[package]]
name = "anstyle-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
dependencies = [
 "utf8parse",
]

[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
 "windows-sys",
]

[[package]]
name = "anstyle-wincon"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
 "anstyle",
 "windows-sys",
]

[[package]]
name = "argon2"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9"
dependencies = [
 "base64ct",
 "blake2",
 "cpufeatures",
 "password-hash",
]

[[package]]
name = "arrayref"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"

[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"

[[package]]
name = "askama"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47cbc3cf73fa8d9833727bbee4835ba5c421a0d65b72daf9a7b5d0e0f9cfb57e"
dependencies = [
 "askama_derive",
 "askama_escape",
 "humansize",
 "num-traits",
 "percent-encoding",
]

[[package]]
name = "askama_derive"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c22fbe0413545c098358e56966ff22cdd039e10215ae213cfbd65032b119fc94"
dependencies = [
 "basic-toml",
 "mime",
 "mime_guess",
 "nom",
 "proc-macro2",
 "quote",
 "serde",
 "syn",
]

[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"

[[package]]
name = "async-trait"
version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
@@ -22,26 +164,231 @@
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"

[[package]]
name = "axum"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [
 "async-trait",
 "axum-core",
 "bitflags",
 "bytes",
 "futures-util",
 "http",
 "http-body",
 "hyper",
 "itoa",
 "matchit",
 "memchr",
 "mime",
 "percent-encoding",
 "pin-project-lite",
 "rustversion",
 "serde",
 "serde_json",
 "serde_path_to_error",
 "serde_urlencoded",
 "sync_wrapper",
 "tokio",
 "tower",
 "tower-layer",
 "tower-service",
]

[[package]]
name = "axum-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
 "async-trait",
 "bytes",
 "futures-util",
 "http",
 "http-body",
 "mime",
 "rustversion",
 "tower-layer",
 "tower-service",
]

[[package]]
name = "axum-macros"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62"
dependencies = [
 "heck",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
 "addr2line",
 "cc",
 "cfg-if",
 "libc",
 "miniz_oxide",
 "object",
 "rustc-demangle",
]

[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"

[[package]]
name = "base64"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"

[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"

[[package]]
name = "basic-toml"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6"
dependencies = [
 "serde",
]

[[package]]
name = "bincode"
version = "2.0.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95"
dependencies = [
 "bincode_derive",
 "serde",
]

[[package]]
name = "bincode_derive"
version = "2.0.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c"
dependencies = [
 "virtue",
]

[[package]]
name = "bindgen"
version = "0.65.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5"
dependencies = [
 "bitflags",
 "cexpr",
 "clang-sys",
 "lazy_static",
 "lazycell",
 "peeking_take_while",
 "prettyplease",
 "proc-macro2",
 "quote",
 "regex",
 "rustc-hash",
 "shlex",
 "syn",
]

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
 "digest",
]

[[package]]
name = "blake2b_simd"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780"
dependencies = [
 "arrayref",
 "arrayvec",
 "constant_time_eq",
]

[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
 "generic-array",
]

[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"

[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"

[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"

[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
 "cc",
 "libc",
 "pkg-config",
]

[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
 "jobserver",
 "libc",
]

[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
 "nom",
]

[[package]]
@@ -63,6 +410,80 @@
 "serde",
 "wasm-bindgen",
 "windows-targets",
]

[[package]]
name = "clang-sys"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
dependencies = [
 "glob",
 "libc",
 "libloading",
]

[[package]]
name = "clap"
version = "4.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
dependencies = [
 "clap_builder",
 "clap_derive",
]

[[package]]
name = "clap_builder"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
 "anstream",
 "anstyle",
 "clap_lex",
 "strsim",
]

[[package]]
name = "clap_derive"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
dependencies = [
 "heck",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "clap_lex"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"

[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"

[[package]]
name = "constant_time_eq"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"

[[package]]
name = "cookie"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
dependencies = [
 "percent-encoding",
 "time",
 "version_check",
]

[[package]]
@@ -70,6 +491,34 @@
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"

[[package]]
name = "cpufeatures"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
 "libc",
]

[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
 "cfg-if",
]

[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
 "generic-array",
 "typenum",
]

[[package]]
name = "darling"
@@ -83,305 +532,1457 @@

[[package]]
name = "darling_core"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
dependencies = [
 "fnv",
 "ident_case",
 "proc-macro2",
 "quote",
 "strsim",
 "syn",
]

[[package]]
name = "darling_macro"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
 "darling_core",
 "quote",
 "syn",
]

[[package]]
name = "deranged"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
dependencies = [
 "serde",
]

[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
 "block-buffer",
 "crypto-common",
 "subtle",
]

[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"

[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"

[[package]]
name = "form_urlencoded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
 "percent-encoding",
]

[[package]]
name = "futures"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [
 "futures-channel",
 "futures-core",
 "futures-executor",
 "futures-io",
 "futures-sink",
 "futures-task",
 "futures-util",
]

[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
 "futures-core",
 "futures-sink",
]

[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"

[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
 "futures-core",
 "futures-task",
 "futures-util",
]

[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"

[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"

[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"

[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
 "futures-channel",
 "futures-core",
 "futures-io",
 "futures-macro",
 "futures-sink",
 "futures-task",
 "memchr",
 "pin-project-lite",
 "pin-utils",
 "slab",
]

[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
 "typenum",
 "version_check",
]

[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
 "cfg-if",
 "libc",
 "wasi",
]

[[package]]
name = "gimli"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"

[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"

[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"

[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"

[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"

[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"

[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"

[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
 "digest",
]

[[package]]
name = "http"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
 "bytes",
 "fnv",
 "itoa",
]

[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
 "bytes",
 "http",
 "pin-project-lite",
]

[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"

[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"

[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
 "libm",
]

[[package]]
name = "hyper"
version = "0.14.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
dependencies = [
 "bytes",
 "futures-channel",
 "futures-core",
 "futures-util",
 "http",
 "http-body",
 "httparse",
 "httpdate",
 "itoa",
 "pin-project-lite",
 "socket2 0.4.9",
 "tokio",
 "tower-service",
 "tracing",
 "want",
]

[[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]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"

[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
 "unicode-bidi",
 "unicode-normalization",
]

[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
 "autocfg",
 "hashbrown 0.12.3",
 "serde",
]

[[package]]
name = "indexmap"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
 "equivalent",
 "hashbrown 0.14.0",
 "serde",
]

[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"

[[package]]
name = "jmap-proto"
version = "0.1.0"
dependencies = [
 "chrono",
 "serde",
 "serde_json",
 "serde_with",
]

[[package]]
name = "jobserver"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
 "libc",
]

[[package]]
name = "jogre-server"
version = "0.1.0"
dependencies = [
 "argon2",
 "askama",
 "axum",
 "axum-macros",
 "bincode",
 "clap",
 "futures",
 "hex",
 "hmac",
 "oxide-auth",
 "oxide-auth-axum",
 "rand",
 "rocksdb",
 "serde",
 "sha3",
 "tokio",
 "toml",
 "tower",
 "tower-cookies",
 "tracing",
 "tracing-subscriber",
 "url",
 "uuid",
]

[[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 = "keccak"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940"
dependencies = [
 "cpufeatures",
]

[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"

[[package]]
name = "libc"
version = "0.2.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"

[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
 "cfg-if",
 "winapi",
]

[[package]]
name = "libm"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"

[[package]]
name = "librocksdb-sys"
version = "0.11.0+8.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e"
dependencies = [
 "bindgen",
 "bzip2-sys",
 "cc",
 "glob",
 "libc",
 "libz-sys",
 "lz4-sys",
 "zstd-sys",
]

[[package]]
name = "libz-sys"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
dependencies = [
 "cc",
 "pkg-config",
 "vcpkg",
]

[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
 "autocfg",
 "scopeguard",
]

[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"

[[package]]
name = "lz4-sys"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900"
dependencies = [
 "cc",
 "libc",
]

[[package]]
name = "matchit"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef"

[[package]]
name = "memchr"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"

[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"

[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
 "mime",
 "unicase",
]

[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"

[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
 "adler",
]

[[package]]
name = "mio"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
 "libc",
 "wasi",
 "windows-sys",
]

[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
 "memchr",
 "minimal-lexical",
]

[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
 "overload",
 "winapi",
]

[[package]]
name = "num-traits"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
 "autocfg",
]

[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
 "hermit-abi",
 "libc",
]

[[package]]
name = "object"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
 "memchr",
]

[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"

[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"

[[package]]
name = "oxide-auth"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e580c9b15905cc585d44bc6010113a3225c4b664d40d0a175d5a0a4022235d89"
dependencies = [
 "base64 0.13.1",
 "chrono",
 "hmac",
 "once_cell",
 "rand",
 "rmp-serde",
 "rust-argon2",
 "serde",
 "serde_derive",
 "serde_json",
 "sha2",
 "subtle",
 "url",
]

[[package]]
name = "oxide-auth-axum"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5c4549a915268feef20fabcf85acd0172c0840c756a5d3974ca528100e52dcc"
dependencies = [
 "axum",
 "oxide-auth",
]

[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
 "lock_api",
 "parking_lot_core",
]

[[package]]
name = "parking_lot_core"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
 "cfg-if",
 "libc",
 "redox_syscall",
 "smallvec",
 "windows-targets",
]

[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
 "base64ct",
 "rand_core",
 "subtle",
]

[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"

[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"

[[package]]
name = "percent-encoding"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"

[[package]]
name = "pin-project"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [
 "pin-project-internal",
]

[[package]]
name = "pin-project-internal"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"

[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"

[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"

[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"

[[package]]
name = "prettyplease"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d"
dependencies = [
 "proc-macro2",
 "syn",
]

[[package]]
name = "proc-macro2"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
 "libc",
 "rand_chacha",
 "rand_core",
]

[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
 "ppv-lite86",
 "rand_core",
]

[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
 "getrandom",
]

[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
 "bitflags",
]

[[package]]
name = "regex"
version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-automata",
 "regex-syntax",
]

[[package]]
name = "regex-automata"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-syntax",
]

[[package]]
name = "regex-syntax"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"

[[package]]
name = "rmp"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
dependencies = [
 "byteorder",
 "num-traits",
 "paste",
]

[[package]]
name = "rmp-serde"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a"
dependencies = [
 "byteorder",
 "rmp",
 "serde",
]

[[package]]
name = "rocksdb"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe"
dependencies = [
 "libc",
 "librocksdb-sys",
]

[[package]]
name = "rust-argon2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5885493fdf0be6cdff808d1533ce878d21cfa49c7086fa00c66355cd9141bfc"
dependencies = [
 "base64 0.21.4",
 "blake2b_simd",
 "constant_time_eq",
 "crossbeam-utils",
]

[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"

[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"

[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"

[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"

[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"

[[package]]
name = "serde"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "serde_json"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
 "itoa",
 "ryu",
 "serde",
]

[[package]]
name = "serde_path_to_error"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
dependencies = [
 "itoa",
 "serde",
]

[[package]]
name = "serde_spanned"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
dependencies = [
 "serde",
]

[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
 "form_urlencoded",
 "itoa",
 "ryu",
 "serde",
]

[[package]]
name = "serde_with"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237"
dependencies = [
 "base64 0.21.4",
 "chrono",
 "hex",
 "indexmap 1.9.3",
 "indexmap 2.0.0",
 "serde",
 "serde_json",
 "serde_with_macros",
 "time",
]

[[package]]
name = "serde_with_macros"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c"
dependencies = [
 "darling",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "sha2"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
dependencies = [
 "cfg-if",
 "cpufeatures",
 "digest",
]

[[package]]
name = "sha3"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
 "digest",
 "keccak",
]

[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
 "lazy_static",
]

[[package]]
name = "shlex"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"

[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
 "libc",
]

[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
 "autocfg",
]

[[package]]
name = "smallvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"

[[package]]
name = "socket2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
 "libc",
 "winapi",
]

[[package]]
name = "socket2"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
dependencies = [
 "libc",
 "windows-sys",
]

[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"

[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"

[[package]]
name = "syn"
version = "2.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
checksum = "59bf04c28bee9043ed9ea1e41afc0552288d3aba9c6efdd78903b802926f4879"
dependencies = [
 "fnv",
 "ident_case",
 "proc-macro2",
 "quote",
 "strsim",
 "syn",
 "unicode-ident",
]

[[package]]
name = "darling_macro"
version = "0.20.3"
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
 "darling_core",
 "quote",
 "syn",
]
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"

[[package]]
name = "deranged"
version = "0.3.8"
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
 "serde",
 "cfg-if",
 "once_cell",
]

[[package]]
name = "equivalent"
version = "1.0.1"
name = "time"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
dependencies = [
 "deranged",
 "itoa",
 "serde",
 "time-core",
 "time-macros",
]

[[package]]
name = "fnv"
version = "1.0.7"
name = "time-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"

[[package]]
name = "hashbrown"
version = "0.12.3"
name = "time-macros"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
dependencies = [
 "time-core",
]

[[package]]
name = "hashbrown"
version = "0.14.0"
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
 "tinyvec_macros",
]

[[package]]
name = "hex"
version = "0.4.3"
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"

[[package]]
name = "iana-time-zone"
version = "0.1.57"
name = "tokio"
version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [
 "android_system_properties",
 "core-foundation-sys",
 "iana-time-zone-haiku",
 "js-sys",
 "wasm-bindgen",
 "windows",
 "backtrace",
 "bytes",
 "libc",
 "mio",
 "num_cpus",
 "parking_lot",
 "pin-project-lite",
 "signal-hook-registry",
 "socket2 0.5.4",
 "tokio-macros",
 "windows-sys",
]

[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
 "cc",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "ident_case"
version = "1.0.1"
name = "toml"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e"
dependencies = [
 "serde",
 "serde_spanned",
 "toml_datetime",
 "toml_edit",
]

[[package]]
name = "indexmap"
version = "1.9.3"
name = "toml_datetime"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
dependencies = [
 "autocfg",
 "hashbrown 0.12.3",
 "serde",
]

[[package]]
name = "indexmap"
version = "2.0.0"
name = "toml_edit"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95"
dependencies = [
 "equivalent",
 "hashbrown 0.14.0",
 "indexmap 2.0.0",
 "serde",
 "serde_spanned",
 "toml_datetime",
 "winnow",
]

[[package]]
name = "itoa"
version = "1.0.9"
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"

[[package]]
name = "jmap-proto"
version = "0.1.0"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
 "chrono",
 "serde",
 "serde_json",
 "serde_with",
 "futures-core",
 "futures-util",
 "pin-project",
 "pin-project-lite",
 "tokio",
 "tower-layer",
 "tower-service",
 "tracing",
]

[[package]]
name = "js-sys"
version = "0.3.64"
name = "tower-cookies"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
checksum = "40f38d941a2ffd8402b36e02ae407637a9caceb693aaf2edc910437db0f36984"
dependencies = [
 "wasm-bindgen",
 "async-trait",
 "axum-core",
 "cookie",
 "futures-util",
 "http",
 "parking_lot",
 "pin-project-lite",
 "tower-layer",
 "tower-service",
]

[[package]]
name = "libc"
version = "0.2.148"
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"

[[package]]
name = "log"
version = "0.4.20"
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"

[[package]]
name = "num-traits"
version = "0.2.16"
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
 "autocfg",
 "cfg-if",
 "log",
 "pin-project-lite",
 "tracing-attributes",
 "tracing-core",
]

[[package]]
name = "once_cell"
version = "1.18.0"
name = "tracing-attributes"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "proc-macro2"
version = "1.0.67"
name = "tracing-core"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
 "unicode-ident",
 "once_cell",
 "valuable",
]

[[package]]
name = "quote"
version = "1.0.33"
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
 "proc-macro2",
 "lazy_static",
 "log",
 "tracing-core",
]

[[package]]
name = "ryu"
version = "1.0.15"
name = "tracing-subscriber"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
 "nu-ansi-term",
 "sharded-slab",
 "smallvec",
 "thread_local",
 "tracing-core",
 "tracing-log",
]

[[package]]
name = "serde"
version = "1.0.188"
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
 "serde_derive",
]
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"

[[package]]
name = "serde_derive"
version = "1.0.188"
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"

[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "version_check",
]

[[package]]
name = "serde_json"
version = "1.0.107"
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"

[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
 "itoa",
 "ryu",
 "serde",
 "tinyvec",
]

[[package]]
name = "serde_with"
version = "3.3.0"
name = "url"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
 "base64",
 "chrono",
 "hex",
 "indexmap 1.9.3",
 "indexmap 2.0.0",
 "form_urlencoded",
 "idna",
 "percent-encoding",
 "serde",
 "serde_json",
 "serde_with_macros",
 "time",
]

[[package]]
name = "serde_with_macros"
version = "3.3.0"
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"

[[package]]
name = "uuid"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
 "darling",
 "proc-macro2",
 "quote",
 "syn",
 "getrandom",
 "serde",
]

[[package]]
name = "strsim"
version = "0.10.0"
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"

[[package]]
name = "syn"
version = "2.0.35"
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59bf04c28bee9043ed9ea1e41afc0552288d3aba9c6efdd78903b802926f4879"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"

[[package]]
name = "time"
version = "0.3.28"
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
dependencies = [
 "deranged",
 "itoa",
 "serde",
 "time-core",
 "time-macros",
]
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

[[package]]
name = "time-core"
version = "0.1.1"
name = "virtue"
version = "0.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314"

[[package]]
name = "time-macros"
version = "0.2.14"
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
 "time-core",
 "try-lock",
]

[[package]]
name = "unicode-ident"
version = "1.0.12"
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

[[package]]
name = "wasm-bindgen"
@@ -436,12 +2037,43 @@
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"

[[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]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
 "windows-targets",
]
@@ -502,3 +2134,23 @@
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

[[package]]
name = "winnow"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
dependencies = [
 "memchr",
]

[[package]]
name = "zstd-sys"
version = "2.0.8+zstd.1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c"
dependencies = [
 "cc",
 "libc",
 "pkg-config",
]
diff --git a/Cargo.toml b/Cargo.toml
index f7f1b3f..2165f73 100644
--- a/Cargo.toml
+++ a/Cargo.toml
@@ -1,5 +1,6 @@
[workspace]
resolver = "2"
members = [
    "jmap-proto"
    "jmap-proto",
    "jogre-server"
]
diff --git a/jogre-server/Cargo.toml b/jogre-server/Cargo.toml
new file mode 100644
index 0000000..a2bd799 100644
--- /dev/null
+++ a/jogre-server/Cargo.toml
@@ -1,0 +1,31 @@
[package]
name = "jogre-server"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
argon2 = "0.5"
askama = "0.12"
axum = "0.6"
axum-macros = "0.3"
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] }
futures = "0.3.28"
hex = "0.4"
hmac = "0.12"
oxide-auth = "0.5"
oxide-auth-axum = "0.3"
rand = "0.8"
rocksdb = "0.21"
tokio = { version = "1.32", features = ["full"] }
tower = "0.4"
tower-cookies = "0.9"
toml = "0.8"
tracing = "0.1"
tracing-subscriber = "0.3"
url = "2.4"
uuid = { version = "1.4", features = ["v4", "serde"] }
serde = { version = "1.0.188", features = ["derive"] }
sha3 = "0.10"
diff --git a/jogre-server/config.toml b/jogre-server/config.toml
new file mode 100644
index 0000000..127ee6a 100644
--- /dev/null
+++ a/jogre-server/config.toml
@@ -1,0 +1,5 @@
private-key = "mycoolatleast32byteprivatekey"

[store]
type = "rocksdb"
path = "db"
diff --git a/jogre-server/src/config.rs b/jogre-server/src/config.rs
new file mode 100644
index 0000000..0e29908 100644
--- /dev/null
+++ a/jogre-server/src/config.rs
@@ -1,0 +1,20 @@
use serde::Deserialize;

use crate::store::StoreConfig;

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
    /// A private key used for encrypting data at rest, building CSRF tokens,

    /// etc after being fed through Argon2 for key derivation. This key should

    /// be at least 32 bytes long.

    pub private_key: String,
    /// Storage configuration, supported databases are currently `rocksdb`.

    ///

    /// ```toml

    /// [store]

    /// type = "rocksdb"

    /// path = "db"

    /// ```

    pub store: StoreConfig,
}
diff --git a/jogre-server/src/context.rs b/jogre-server/src/context.rs
new file mode 100644
index 0000000..70ffcb2 100644
--- /dev/null
+++ a/jogre-server/src/context.rs
@@ -1,0 +1,57 @@
use std::sync::Arc;

use crate::{config::Config, store::Store};

pub mod oauth2;

pub struct Context {
    pub oauth2: oauth2::OAuth2,
    pub store: Arc<Store>,
}

impl Context {
    pub fn new(config: Config) -> Self {
        let derived_keys = Arc::new(DerivedKeys::new(&config.private_key));
        let store = Arc::new(Store::from_config(config.store));

        Self {
            oauth2: oauth2::OAuth2::new(store.clone(), derived_keys),
            store,
        }
    }
}

pub struct DerivedKeys {
    pub(crate) csrf_hmac_key: [u8; argon2::Params::DEFAULT_OUTPUT_LEN],
}

impl DerivedKeys {
    /// Salt used for deriving the CSRF HMAC key

    const CSRF: &'static [u8] = b"CSRFTOKEN";

    /// Instantiates a new [`DerivedKeys`], dropping the private key.

    fn new(private_key: &str) -> Self {
        let argon2 = argon2::Argon2::new(
            argon2::Algorithm::Argon2id,
            argon2::Version::V0x13,
            argon2::Params::DEFAULT,
        );

        Self {
            csrf_hmac_key: Self::derive_key(&argon2, private_key, Self::CSRF),
        }
    }

    fn derive_key(
        argon2: &argon2::Argon2,
        private_key: &str,
        salt: &[u8],
    ) -> [u8; argon2::Params::DEFAULT_OUTPUT_LEN] {
        let mut out = [0_u8; argon2::Params::DEFAULT_OUTPUT_LEN];
        argon2
            .hash_password_into(private_key.as_bytes(), salt, &mut out)
            .unwrap();

        out
    }
}
diff --git a/jogre-server/src/main.rs b/jogre-server/src/main.rs
new file mode 100644
index 0000000..70dbb0c 100644
--- /dev/null
+++ a/jogre-server/src/main.rs
@@ -1,0 +1,59 @@
mod config;
mod context;
mod layers;
mod methods;
mod store;
mod util;

use std::{path::PathBuf, sync::Arc};

use clap::Parser;
use rand::RngCore;
use tracing::info;

use crate::{context::Context, store::UserProvider};

#[derive(Parser, Debug)]
#[clap(author, version, about)]
pub struct Args {
    /// Path to the config file (eg. config.toml)

    #[clap(long, short)]
    config: PathBuf,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();

    let subscriber = tracing_subscriber::fmt();
    #[cfg(debug_assertions)]
    let subscriber = subscriber.pretty();
    subscriber.init();

    let config = toml::from_str(&tokio::fs::read_to_string(&args.config).await?)?;

    let context = Arc::new(Context::new(config));

    create_root_if_none_exists(&context).await;

    axum::Server::bind(&"0.0.0.0:8888".parse().unwrap())
        .serve(methods::router(context).into_make_service())
        .await?;

    Ok(())
}

async fn create_root_if_none_exists(context: &Context) {
    if context.store.has_any_users().await.unwrap() {
        return;
    }

    let mut password = [0_u8; 32];
    rand::thread_rng().fill_bytes(&mut password);
    let password = hex::encode(password);

    info!("User root created with password {password}");

    let root = store::User::new("root".into(), &password);
    context.store.create_user(root).await.unwrap();
}
diff --git a/jogre-server/src/store.rs b/jogre-server/src/store.rs
new file mode 100644
index 0000000..38f1af6 100644
--- /dev/null
+++ a/jogre-server/src/store.rs
@@ -1,0 +1,89 @@
mod rocksdb;

use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use axum::async_trait;
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Serialize, Deserialize)]
pub struct User {
    id: Uuid,
    pub username: String,
    password: String,
}

impl User {
    pub fn new(username: String, password: &str) -> Self {
        let password = Argon2::default()
            .hash_password(password.as_bytes(), &SaltString::generate(&mut OsRng))
            .unwrap()
            .to_string();

        Self {
            id: Uuid::new_v4(),
            username,
            password,
        }
    }

    pub fn verify_password(&self, password: &str) -> bool {
        let parsed_hash = PasswordHash::new(&self.password).unwrap();
        Argon2::default()
            .verify_password(password.as_bytes(), &parsed_hash)
            .is_ok()
    }
}

#[async_trait]
pub trait UserProvider {
    type Error;

    async fn has_any_users(&self) -> Result<bool, Self::Error>;

    async fn create_user(&self, user: User) -> Result<(), Self::Error>;

    async fn get_by_username(&self, username: &str) -> Result<Option<User>, Self::Error>;
}

#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum StoreConfig {
    #[serde(rename = "rocksdb")]
    RocksDb(rocksdb::Config),
}

pub enum Store {
    RocksDb(rocksdb::RocksDb),
}

impl Store {
    pub fn from_config(config: StoreConfig) -> Self {
        match config {
            StoreConfig::RocksDb(config) => Self::RocksDb(rocksdb::RocksDb::new(config)),
        }
    }
}

#[async_trait]
impl UserProvider for Store {
    type Error = rocksdb::Error;

    async fn has_any_users(&self) -> Result<bool, Self::Error> {
        match self {
            Store::RocksDb(db) => db.has_any_users().await,
        }
    }

    async fn create_user(&self, user: User) -> Result<(), Self::Error> {
        match self {
            Store::RocksDb(db) => db.create_user(user).await,
        }
    }

    async fn get_by_username(&self, username: &str) -> Result<Option<User>, Self::Error> {
        match self {
            Store::RocksDb(db) => db.get_by_username(username).await,
        }
    }
}
diff --git a/jogre-server/src/util.rs b/jogre-server/src/util.rs
new file mode 100644
index 0000000..e9c8376 100644
--- /dev/null
+++ a/jogre-server/src/util.rs
@@ -1,0 +1,81 @@
use hmac::{digest::FixedOutput, Hmac, Mac};
use sha3::Sha3_256;
use tower_cookies::{
    cookie::{time::Duration, CookieBuilder, SameSite},
    Cookies,
};
use tracing::warn;

use crate::context::DerivedKeys;

type HmacSha3 = Hmac<Sha3_256>;

const CSRF_TOKEN_COOKIE_NAME: &str = "csrf_token";

#[derive(Copy, Clone)]
pub struct CsrfToken {
    signed: [u8; 32],
    unsigned: u128,
}

impl CsrfToken {
    pub fn new(derived_keys: &DerivedKeys) -> Self {
        let unsigned = rand::random::<u128>();

        let mut hmac = HmacSha3::new_from_slice(&derived_keys.csrf_hmac_key).unwrap();
        hmac.update(&unsigned.to_be_bytes());
        let signed = hmac.finalize_fixed().into();

        Self { signed, unsigned }
    }

    pub fn write_cookie(&self, cookies: &Cookies) {
        cookies.add(
            CookieBuilder::new(CSRF_TOKEN_COOKIE_NAME, hex::encode(self.signed))
                .http_only(true)
                .max_age(Duration::hours(24))
                .same_site(SameSite::Strict)
                // .secure(true) // TODO
                .finish(),
        );
    }

    #[must_use]
    pub fn verify(derived_keys: &DerivedKeys, cookies: &Cookies, form_value: &str) -> bool {
        let Some(cookie) = cookies.get(CSRF_TOKEN_COOKIE_NAME) else {
            warn!("Missing CSRF token");
            return false;
        };

        let form_value = match hex::decode(form_value) {
            Ok(v) => v,
            Err(error) => {
                warn!(?error, "Invalid form CSRF token");
                return false;
            }
        };

        let cookie_token = match hex::decode(cookie.value()) {
            Ok(v) => v,
            Err(error) => {
                warn!(?error, "Invalid cookie CSRF token");
                return false;
            }
        };

        let mut hmac = HmacSha3::new_from_slice(&derived_keys.csrf_hmac_key).unwrap();
        hmac.update(&form_value);

        match hmac.verify_slice(&cookie_token) {
            Ok(()) => true,
            Err(error) => {
                warn!(?error, "CSRF form value and cookie mismatch");
                false
            }
        }
    }

    pub fn form_value(&self) -> String {
        hex::encode(self.unsigned.to_be_bytes())
    }
}
diff --git a/jogre-server/src/context/oauth2.rs b/jogre-server/src/context/oauth2.rs
new file mode 100644
index 0000000..d409a75 100644
--- /dev/null
+++ a/jogre-server/src/context/oauth2.rs
@@ -1,0 +1,279 @@
use std::{
    borrow::Cow,
    str::FromStr,
    sync::{Arc, Mutex},
};

use askama::Template;
use axum::{
    async_trait,
    body::HttpBody,
    extract::FromRequest,
    http::{Method, Request},
    BoxError, RequestExt,
};
use futures::FutureExt;
use oxide_auth::{
    endpoint::{
        Authorizer, Issuer, OwnerConsent, OwnerSolicitor, QueryParameter, Registrar, Scope,
        Solicitation, WebRequest,
    },
    frontends::simple::{
        endpoint,
        endpoint::{Generic, Vacant},
    },
    primitives::{
        grant::Grant,
        prelude::{AuthMap, Client, ClientMap, RandomGenerator, TokenMap},
        registrar::RegisteredUrl,
    },
};
use oxide_auth_axum::{OAuthRequest, OAuthResponse, WebError};
use tower_cookies::Cookies;
use tracing::info;
use url::Url;

use crate::{
    context::DerivedKeys,
    store::{Store, UserProvider},
    util::CsrfToken,
};

pub struct OAuth2 {
    pub registrar: ClientMap,
    pub authorizer: Mutex<AuthMap<RandomGenerator>>,
    pub issuer: Mutex<TokenMap<RandomGenerator>>,
    pub derived_keys: Arc<DerivedKeys>,
    pub store: Arc<Store>,
}

impl OAuth2 {
    pub fn new(store: Arc<Store>, derived_keys: Arc<DerivedKeys>) -> Self {
        let mut registrar = ClientMap::new();

        registrar.register_client(Client::public(
            "abcdef",
            RegisteredUrl::from("https://google.com/".parse::<Url>().unwrap()),
            "test".parse::<Scope>().unwrap(),
        ));

        let authorizer = Mutex::new(AuthMap::new(RandomGenerator::new(16)));
        let issuer = Mutex::new(TokenMap::new(RandomGenerator::new(16)));

        Self {
            registrar,
            authorizer,
            issuer,
            derived_keys,
            store,
        }
    }

    pub fn resource(
        &self,
        request: OAuthRequest,
    ) -> Result<Grant, Result<OAuthResponse, endpoint::Error<OAuthRequest>>> {
        self.endpoint().resource_flow().execute(request)
    }

    pub fn authorize(
        &self,
        request: OAuthRequestWrapper,
    ) -> Result<OAuthResponse, endpoint::Error<OAuthRequestWrapper>> {
        self.endpoint().authorization_flow().execute(request)
    }

    pub fn token(
        &self,
        request: OAuthRequestWrapper,
    ) -> Result<OAuthResponse, endpoint::Error<OAuthRequestWrapper>> {
        self.endpoint().access_token_flow().execute(request)
    }

    pub fn refresh(
        &self,
        request: OAuthRequestWrapper,
    ) -> Result<OAuthResponse, endpoint::Error<OAuthRequestWrapper>> {
        self.endpoint().refresh_flow().execute(request)
    }

    fn endpoint(
        &self,
    ) -> Generic<
        impl Registrar + '_,
        impl Authorizer + '_,
        impl Issuer + '_,
        Solicitor<'_>,
        Vec<Scope>,
    > {
        Generic {
            registrar: &self.registrar,
            authorizer: self.authorizer.lock().unwrap(),
            issuer: self.issuer.lock().unwrap(),
            solicitor: Solicitor {
                derived_keys: &self.derived_keys,
                store: &self.store,
            },
            scopes: vec![Scope::from_str("test").unwrap()],
            response: Vacant,
        }
    }
}

pub struct Solicitor<'a> {
    derived_keys: &'a DerivedKeys,
    store: &'a Store,
}

impl OwnerSolicitor<OAuthRequest> for Solicitor<'_> {
    fn check_consent(
        &mut self,
        _: &mut OAuthRequest,
        _: Solicitation,
    ) -> OwnerConsent<OAuthResponse> {
        unreachable!("OAuthRequest should only be used for resource requests")
    }
}

impl OwnerSolicitor<OAuthRequestWrapper> for Solicitor<'_> {
    fn check_consent(
        &mut self,
        req: &mut OAuthRequestWrapper,
        solicitation: Solicitation,
    ) -> OwnerConsent<OAuthResponse> {
        let auth_state = if req.method == Method::GET {
            AuthState::Unauthenticated(None)
        } else if let Some(((username, password), csrf_token)) = req.inner.body().and_then(|body| {
            body.unique_value("username")
                .zip(body.unique_value("password"))
                .zip(body.unique_value("csrf_token"))
        }) {
            attempt_authentication(
                self.derived_keys,
                self.store,
                &req.cookie_jar,
                &username,
                &password,
                &csrf_token,
            )
        } else {
            AuthState::Unauthenticated(Some(UnauthenticatedState::MissingUserPass))
        };

        match auth_state {
            AuthState::Unauthenticated(reason) => {
                info!("Soliciting auth from user due to {reason:?}");

                let csrf_token = CsrfToken::new(self.derived_keys);
                csrf_token.write_cookie(&req.cookie_jar);

                let response = OAuthResponse::default()
                    .content_type("text/html")
                    .unwrap()
                    .body(
                        &LoginForm {
                            reason,
                            csrf_token,
                            solicitation,
                        }
                        .render()
                        .unwrap(),
                    );

                OwnerConsent::InProgress(response)
            }
            AuthState::Authenticated(username) => OwnerConsent::Authorized(username),
        }
    }
}

fn attempt_authentication(
    derived_keys: &DerivedKeys,
    store: &Store,
    cookies: &Cookies,
    username: &str,
    password: &str,
    csrf_token: &str,
) -> AuthState {
    if !CsrfToken::verify(derived_keys, cookies, csrf_token) {
        return AuthState::Unauthenticated(Some(UnauthenticatedState::InvalidCsrfToken));
    }

    // TODO: actually await here
    let Some(user) = store
        .get_by_username(username)
        .now_or_never()
        .unwrap()
        .unwrap()
    else {
        return AuthState::Unauthenticated(Some(UnauthenticatedState::InvalidUserPass));
    };

    if user.verify_password(password) {
        AuthState::Authenticated(user.username)
    } else {
        AuthState::Unauthenticated(Some(UnauthenticatedState::InvalidUserPass))
    }
}

#[derive(Template)]
#[template(path = "auth/login.html")]
pub struct LoginForm<'a> {
    reason: Option<UnauthenticatedState>,
    csrf_token: CsrfToken,
    solicitation: Solicitation<'a>,
}

pub enum AuthState {
    Authenticated(String),
    Unauthenticated(Option<UnauthenticatedState>),
}

#[derive(Debug)]
pub enum UnauthenticatedState {
    InvalidUserPass,
    MissingUserPass,
    InvalidCsrfToken,
}

pub struct OAuthRequestWrapper {
    inner: OAuthRequest,
    method: Method,
    cookie_jar: Cookies,
}

impl WebRequest for OAuthRequestWrapper {
    type Error = WebError;
    type Response = OAuthResponse;

    fn query(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
        WebRequest::query(&mut self.inner)
    }

    fn urlbody(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
        WebRequest::urlbody(&mut self.inner)
    }

    fn authheader(&mut self) -> Result<Option<Cow<str>>, Self::Error> {
        WebRequest::authheader(&mut self.inner)
    }
}

#[async_trait]
impl<S, B> FromRequest<S, B> for OAuthRequestWrapper
where
    B: HttpBody + Send + Sync + 'static,
    B::Data: Send,
    B::Error: Into<BoxError>,
    S: Send + Sync,
{
    type Rejection = WebError;

    async fn from_request(mut req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
        Ok(Self {
            method: req.method().clone(),
            cookie_jar: req.extract_parts_with_state(state).await.unwrap(),
            inner: OAuthRequest::from_request(req, state).await?,
        })
    }
}
diff --git a/jogre-server/src/layers/auth_required.rs b/jogre-server/src/layers/auth_required.rs
new file mode 100644
index 0000000..75c68b0 100644
--- /dev/null
+++ a/jogre-server/src/layers/auth_required.rs
@@ -1,0 +1,39 @@
use std::sync::Arc;

use axum::{
    extract::State,
    http::Request,
    middleware::Next,
    response::{IntoResponse, Response},
    RequestExt,
};
use oxide_auth_axum::{OAuthResource, WebError};
use tracing::{debug, error};

use crate::context::Context;

pub async fn auth_required_middleware<B: Send + 'static>(
    State(state): State<Arc<Context>>,
    mut request: Request<B>,
    next: Next<B>,
) -> Response {
    let resource_request = match request.extract_parts::<OAuthResource>().await {
        Ok(v) => v,
        Err(e) => {
            error!("Rejecting request due to invalid Authorization header");
            return e.into_response();
        }
    };

    let grant = match state.oauth2.resource(resource_request.into()) {
        Ok(v) => v,
        Err(e) => {
            error!("Rejecting request due to it being unauthorized");
            return e.map_err(|e| e.pack::<WebError>()).into_response();
        }
    };

    debug!(?grant, "Request authorized");

    next.run(request).await
}
diff --git a/jogre-server/src/layers/logger.rs b/jogre-server/src/layers/logger.rs
new file mode 100644
index 0000000..7a262cd 100644
--- /dev/null
+++ a/jogre-server/src/layers/logger.rs
@@ -1,0 +1,125 @@
//! Logs each and every request out in a format similar to that of Apache's logs.


use std::{
    fmt::Debug,
    net::SocketAddr,
    task::{Context, Poll},
    time::Instant,
};

use axum::{
    extract,
    http::{HeaderValue, Method, Request, Response},
};
use futures::future::{Future, FutureExt, Join, Map, Ready};
use tower::Service;
use tracing::{error, info, instrument::Instrumented, Instrument, Span};
use uuid::Uuid;

pub trait GenericError: std::error::Error + Debug + Send + Sync {}

#[derive(Clone)]
pub struct LoggingMiddleware<S>(pub S);

impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for LoggingMiddleware<S>
where
    S: Service<Request<ReqBody>, Response = Response<ResBody>, Error = std::convert::Infallible>
        + Clone
        + Send
        + 'static,
    S::Future: Send + 'static,
    S::Response: Default + Debug,
    ReqBody: Send + Debug + 'static,
    ResBody: Default + Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = Map<
        Join<Instrumented<S::Future>, Ready<PendingLogMessage>>,
        fn((<S::Future as Future>::Output, PendingLogMessage)) -> <S::Future as Future>::Output,
    >;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.0.poll_ready(cx)
    }

    fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
        let request_id = Uuid::new_v4();
        let span = tracing::info_span!("web", "request_id" = request_id.to_string().as_str());

        let log_message = PendingLogMessage {
            span: span.clone(),
            ip: req
                .extensions()
                .get::<extract::ConnectInfo<std::net::SocketAddr>>()
                .map_or_else(|| "0.0.0.0:0".parse().unwrap(), |v| v.0),
            method: req.method().clone(),
            uri: req.uri().path().to_string(),
            start: Instant::now(),
            user_agent: req.headers().get(axum::http::header::USER_AGENT).cloned(),
        };

        futures::future::join(
            self.0.call(req).instrument(span),
            futures::future::ready(log_message),
        )
        .map(|(response, pending_log_message)| {
            let response = response.unwrap();
            pending_log_message.log(&response);
            Ok(response)
        })
    }
}

pub struct PendingLogMessage {
    span: Span,
    ip: SocketAddr,
    method: Method,
    uri: String,
    start: Instant,
    user_agent: Option<HeaderValue>,
}

impl PendingLogMessage {
    pub fn log<ResBody>(&self, response: &Response<ResBody>) {
        let _enter = self.span.enter();

        if response.status().is_server_error() {
            error!(
                "{ip} - \"{method} {uri}\" {status} {duration:?} \"{user_agent}\" \"{error:?}\"",
                ip = self.ip,
                method = self.method,
                uri = self.uri,
                status = response.status().as_u16(),
                duration = self.start.elapsed(),
                user_agent = self
                    .user_agent
                    .as_ref()
                    .and_then(|v| v.to_str().ok())
                    .unwrap_or("unknown"),
                error = match response.extensions().get::<Box<dyn GenericError>>() {
                    Some(e) => Err(e),
                    None => Ok(()),
                }
            );
        } else {
            info!(
                "{ip} - \"{method} {uri}\" {status} {duration:?} \"{user_agent}\" \"{error:?}\"",
                ip = self.ip,
                method = self.method,
                uri = self.uri,
                status = response.status().as_u16(),
                duration = self.start.elapsed(),
                user_agent = self
                    .user_agent
                    .as_ref()
                    .and_then(|v| v.to_str().ok())
                    .unwrap_or("unknown"),
                error = match response.extensions().get::<Box<dyn GenericError>>() {
                    Some(e) => Err(e),
                    None => Ok(()),
                }
            );
        }
    }
}
diff --git a/jogre-server/src/layers/mod.rs b/jogre-server/src/layers/mod.rs
new file mode 100644
index 0000000..979f74c 100644
--- /dev/null
+++ a/jogre-server/src/layers/mod.rs
@@ -1,0 +1,2 @@
pub mod auth_required;
pub mod logger;
diff --git a/jogre-server/src/methods/mod.rs b/jogre-server/src/methods/mod.rs
new file mode 100644
index 0000000..3d1ee69 100644
--- /dev/null
+++ a/jogre-server/src/methods/mod.rs
@@ -1,0 +1,27 @@
mod oauth;
mod session;

use std::sync::Arc;

use axum::{routing::get, Router};
use tower::layer::layer_fn;
use tower_cookies::CookieManagerLayer;

use crate::{
    context::Context,
    layers::{auth_required::auth_required_middleware, logger::LoggingMiddleware},
};

pub fn router(context: Arc<Context>) -> Router {
    Router::new()
        .route("/.well-known/jmap", get(session::get))
        // only apply auth requirement on endpoints above
        .layer(axum::middleware::from_fn_with_state(
            context.clone(),
            auth_required_middleware,
        ))
        .nest("/oauth", oauth::router())
        .layer(layer_fn(LoggingMiddleware))
        .layer(CookieManagerLayer::new())
        .with_state(context)
}
diff --git a/jogre-server/src/methods/session.rs b/jogre-server/src/methods/session.rs
new file mode 100644
index 0000000..12c1120 100644
--- /dev/null
+++ a/jogre-server/src/methods/session.rs
@@ -1,0 +1,3 @@
pub async fn get() -> &'static str {
    "hello world"
}
diff --git a/jogre-server/src/store/rocksdb.rs b/jogre-server/src/store/rocksdb.rs
new file mode 100644
index 0000000..76fe878 100644
--- /dev/null
+++ a/jogre-server/src/store/rocksdb.rs
@@ -1,0 +1,103 @@
use std::path::PathBuf;

use axum::async_trait;
use rocksdb::{IteratorMode, Options, DB};
use serde::Deserialize;

use crate::store::{User, UserProvider};

#[derive(Debug)]
pub enum Error {}

const USER_BY_USERNAME_CF: &str = "users_by_username";
const USER_BY_UUID_CF: &str = "users_by_uuid";

const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard();

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Config {
    path: PathBuf,
}

// TODO: lots of blocking on async thread
pub struct RocksDb {
    db: DB,
}

impl RocksDb {
    pub fn new(config: Config) -> Self {
        let mut db_options = Options::default();
        db_options.create_if_missing(true);
        db_options.create_missing_column_families(true);

        let db = DB::open_cf(
            &db_options,
            config.path,
            [USER_BY_USERNAME_CF, USER_BY_UUID_CF],
        )
        .unwrap();

        Self { db }
    }
}

#[async_trait]
impl UserProvider for RocksDb {
    type Error = Error;

    async fn has_any_users(&self) -> Result<bool, Self::Error> {
        let by_uuid_handle = self.db.cf_handle(USER_BY_UUID_CF).unwrap();
        Ok(self
            .db
            .full_iterator_cf(by_uuid_handle, IteratorMode::Start)
            .next()
            .is_some())
    }

    async fn create_user(&self, user: User) -> Result<(), Self::Error> {
        let bytes = bincode::serde::encode_to_vec(&user, BINCODE_CONFIG).unwrap();

        let by_uuid_handle = self.db.cf_handle(USER_BY_UUID_CF).unwrap();
        self.db
            .put_cf(by_uuid_handle, user.id.as_bytes(), bytes)
            .unwrap();

        let by_username_handle = self.db.cf_handle(USER_BY_USERNAME_CF).unwrap();
        self.db
            .put_cf(
                by_username_handle,
                user.username.as_bytes(),
                user.id.as_bytes(),
            )
            .unwrap();

        Ok(())
    }

    async fn get_by_username(&self, username: &str) -> Result<Option<User>, Error> {
        let uuid = {
            let by_username_handle = self.db.cf_handle(USER_BY_USERNAME_CF).unwrap();
            self.db.get_pinned_cf(by_username_handle, username).unwrap()
        };

        let Some(uuid) = uuid else {
            return Ok(None);
        };

        let user_bytes = {
            let by_uuid_handle = self.db.cf_handle(USER_BY_UUID_CF).unwrap();
            self.db.get_pinned_cf(by_uuid_handle, &uuid).unwrap()
        };

        let Some(user_bytes) = user_bytes else {
            return Ok(None);
        };

        Ok(Some(
            bincode::serde::decode_from_slice(&user_bytes, BINCODE_CONFIG)
                .unwrap()
                .0,
        ))
    }
}
diff --git a/jogre-server/templates/auth/login.html b/jogre-server/templates/auth/login.html
new file mode 100644
index 0000000..81d7bc0 100644
--- /dev/null
+++ a/jogre-server/templates/auth/login.html
@@ -1,0 +1,142 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login | Jogre</title>

    <style>
        * {
            box-sizing: border-box;
        }

        code {
            font-family: monospace;
            background: #ecebec;
            border: 1px solid #d1d1d2;
            color: #d81f58;
            padding: .1rem;
            border-radius: .3rem;
        }

        body {
            margin: 0;
            line-height: 1.5rem;
            min-height: 100vh;
            min-width: 100vw;
            display: flex;
            background: #f1f5f9;
            color: #334155;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            font-family: sans-serif;
        }

        .container {
            max-width: 35rem;
        }

        main, section {
            background: #fff;
            padding: 2rem;
            width: 100%;
            border-radius: 2rem;
        }

        section.error {
            background: #ef4444;
            color: white;
            padding: 1.2rem;
            margin-bottom: 1rem;
            width: 100%;
        }

        label, input, button {
            display: block;
            width: 100%;
        }

        label {
            margin-top: .5rem;
            font-size: .85rem;
        }

        input {
            margin-top: .25rem;
            background: #fff;
            border: 1px solid #cbd5e1;
            font-size: .875rem;
            line-height: 1.25rem;
        }

        input:focus {
            border-color: #0EA5E9;
            outline: 2px solid #0000;
            outline-offset: 2px;
            box-shadow: #0EA5E9 0 0 0 1px;
        }

        input, button {
            padding: .5rem .75rem;
            border-radius: .375rem;
        }

        button {
            margin-top: 1rem;
            background: #0EA5E9;
            color: #fff;
            font-weight: 600;
            border: 0;
            padding: 10px 20px;
            cursor: pointer;
        }

        button:hover {
            background-color: #0369A1;
        }
    </style>
</head>

<body>
    <div class="container">
        {% if let Some(reason) = reason %}
            <section class="error">
                {% match reason %}
                    {% when UnauthenticatedState::InvalidUserPass %}
                        Invalid username or password
                    {% when UnauthenticatedState::MissingUserPass %}
                        You must enter a username and password
                    {% when UnauthenticatedState::InvalidCsrfToken %}
                        Invalid CSRF token
                {% endmatch %}
            </section>
        {% endif %}

        <main>
            <div>
                <strong>
                    Please login to confirm you would like to allow
                    <code>{{ solicitation.pre_grant().client_id }}</code>
                    access
                    {% if solicitation.pre_grant().scope.iter().next().is_some() %}
                        to <code>{{ solicitation.pre_grant().scope }}</code>
                    {% endif %}
                    via <code>{{ solicitation.pre_grant().redirect_uri }}</code>.
                </strong>
            </div>

            <form method="POST">
                <input type="hidden" name="csrf_token" value="{{ csrf_token.form_value() }}" />

                <label for="username">Username</label>
                <input type="text" name="username" id="username" />

                <label for="password">Password</label>
                <input type="password" name="password" id="password" />

                <button type="submit">Login</button>
            </form>
        </main>
    </div>
</body>
</html>
diff --git a/jogre-server/src/methods/oauth/authorize.rs b/jogre-server/src/methods/oauth/authorize.rs
new file mode 100644
index 0000000..0cf85da 100644
--- /dev/null
+++ a/jogre-server/src/methods/oauth/authorize.rs
@@ -1,0 +1,13 @@
use std::sync::Arc;

use axum::extract::State;
use oxide_auth_axum::{OAuthResponse, WebError};

use crate::context::{oauth2::OAuthRequestWrapper, Context};

pub async fn handle(
    State(context): State<Arc<Context>>,
    request: OAuthRequestWrapper,
) -> Result<OAuthResponse, WebError> {
    context.oauth2.authorize(request).map_err(|e| e.pack())
}
diff --git a/jogre-server/src/methods/oauth/mod.rs b/jogre-server/src/methods/oauth/mod.rs
new file mode 100644
index 0000000..b90749e 100644
--- /dev/null
+++ a/jogre-server/src/methods/oauth/mod.rs
@@ -1,0 +1,19 @@
mod authorize;
mod refresh;
mod token;

use std::sync::Arc;

use axum::{
    routing::{get, post},
    Router,
};

use crate::context::Context;

pub fn router() -> Router<Arc<Context>> {
    Router::new()
        .route("/authorize", get(authorize::handle).post(authorize::handle))
        .route("/token", post(token::handle))
        .route("/refresh", post(refresh::handle))
}
diff --git a/jogre-server/src/methods/oauth/refresh.rs b/jogre-server/src/methods/oauth/refresh.rs
new file mode 100644
index 0000000..0c4fe6a 100644
--- /dev/null
+++ a/jogre-server/src/methods/oauth/refresh.rs
@@ -1,0 +1,13 @@
use std::sync::Arc;

use axum::extract::State;
use oxide_auth_axum::{OAuthResponse, WebError};

use crate::context::{oauth2::OAuthRequestWrapper, Context};

pub async fn handle(
    State(context): State<Arc<Context>>,
    request: OAuthRequestWrapper,
) -> Result<OAuthResponse, WebError> {
    context.oauth2.refresh(request).map_err(|e| e.pack())
}
diff --git a/jogre-server/src/methods/oauth/token.rs b/jogre-server/src/methods/oauth/token.rs
new file mode 100644
index 0000000..36720c5 100644
--- /dev/null
+++ a/jogre-server/src/methods/oauth/token.rs
@@ -1,0 +1,13 @@
use std::sync::Arc;

use axum::extract::State;
use oxide_auth_axum::{OAuthResponse, WebError};

use crate::context::{oauth2::OAuthRequestWrapper, Context};

pub async fn handle(
    State(context): State<Arc<Context>>,
    request: OAuthRequestWrapper,
) -> Result<OAuthResponse, WebError> {
    context.oauth2.token(request).map_err(|e| e.pack())
}