Login to vault
This commit is contained in:
parent
31b6af97ba
commit
0112286623
22 changed files with 1449 additions and 18 deletions
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']
|
||||
552
Cargo.lock
generated
552
Cargo.lock
generated
|
|
@ -17,6 +17,15 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
|
|
@ -41,7 +50,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
|
|
@ -54,6 +65,9 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
|
|
@ -79,6 +93,17 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
|
|
@ -94,6 +119,18 @@ dependencies = [
|
|||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.4"
|
||||
|
|
@ -112,6 +149,16 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
|
|
@ -124,9 +171,12 @@ version = "0.4.42"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -139,6 +189,37 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_log"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
|
||||
dependencies = [
|
||||
"log",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
|
@ -150,6 +231,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
|
@ -226,12 +313,45 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.7+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
|
|
@ -266,6 +386,30 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[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 = "icu_collections"
|
||||
version = "2.0.0"
|
||||
|
|
@ -400,6 +544,21 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "9.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"js-sys",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
|
|
@ -458,8 +617,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -486,6 +670,16 @@ version = "1.21.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
|
|
@ -533,6 +727,21 @@ dependencies = [
|
|||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
|
|
@ -551,6 +760,85 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[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 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
|
|
@ -623,6 +911,17 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
|
@ -635,6 +934,24 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror 2.0.16",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
|
|
@ -681,13 +998,33 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -701,6 +1038,37 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
|
|
@ -739,6 +1107,22 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
|
|
@ -757,6 +1141,12 @@ version = "1.0.19"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.7"
|
||||
|
|
@ -775,13 +1165,55 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"uuid-rng-internal",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid-rng-internal"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23426b4394875bbc29a3074f94e1b52cd0eed2c8410c21a6edbfb033daef0aa1"
|
||||
dependencies = [
|
||||
"rand 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "warden-worker"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"constant_time_eq",
|
||||
"getrandom 0.3.3",
|
||||
"hex",
|
||||
"js-sys",
|
||||
"jsonwebtoken",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"worker",
|
||||
"worker-macros",
|
||||
]
|
||||
|
|
@ -792,6 +1224,24 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.7+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.103"
|
||||
|
|
@ -887,6 +1337,74 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
|
@ -960,6 +1478,12 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "worker"
|
||||
version = "0.6.5"
|
||||
|
|
@ -1002,7 +1526,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"thiserror 2.0.16",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
|
@ -1065,6 +1589,26 @@ dependencies = [
|
|||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
|
|
|
|||
34
Cargo.toml
34
Cargo.toml
|
|
@ -11,8 +11,36 @@ release = false
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
worker = { version = "0.6", features = ['http', 'axum'] }
|
||||
# Worker & Web APIs
|
||||
worker = { version = "0.6", features = ["axum", "http", "d1"] }
|
||||
worker-macros = { version = "0.6", features = ['http'] }
|
||||
axum = { version = "0.8", default-features = false }
|
||||
tower-service = "0.3.3"
|
||||
wasm-bindgen = "0.2"
|
||||
js-sys = "0.3"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = { version = "0.3", features = ["Crypto", "CryptoKey", "SubtleCrypto", "WorkerGlobalScope", "Pbkdf2Params"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
# Axum and Routing
|
||||
axum = { version = "0.8", default-features = false, features=["json", "macros", "form"] }
|
||||
tower-service = "0.3"
|
||||
tower-http = { version = "0.5", features = ["cors"] }
|
||||
|
||||
# Data & Serialization
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# Crypto & Encoding
|
||||
jsonwebtoken = "9.2"
|
||||
base64 = "0.21"
|
||||
hex = "0.4"
|
||||
rand = { version = "0.8" }
|
||||
getrandom = { version = "0.3", features = ["wasm_js"] }
|
||||
constant_time_eq = "0.3"
|
||||
|
||||
# Utilities
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
uuid = { version = "1.6", features = ["v4", "serde", "rng-rand"] }
|
||||
log = "0.4"
|
||||
thiserror = "1.0"
|
||||
once_cell = "1.19"
|
||||
console_log = "1.0.0"
|
||||
|
|
|
|||
48
sql/schema.sql
Normal file
48
sql/schema.sql
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
-- Drop tables if they exist to ensure a clean slate
|
||||
DROP TABLE IF EXISTS folders;
|
||||
DROP TABLE IF EXISTS ciphers;
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
-- Users table to store user accounts and their master keys/hashes
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
email_verified BOOLEAN NOT NULL DEFAULT 0,
|
||||
master_password_hash TEXT NOT NULL,
|
||||
master_password_hint TEXT,
|
||||
key TEXT NOT NULL, -- The encrypted symmetric key
|
||||
private_key TEXT NOT NULL, -- encrypted asymmetric private_key
|
||||
public_key TEXT NOT NULL, -- asymmetric public_key
|
||||
kdf_type INTEGER NOT NULL DEFAULT 0, -- 0 for PBKDF2
|
||||
kdf_iterations INTEGER NOT NULL DEFAULT 600000,
|
||||
security_stamp TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Ciphers table for storing encrypted vault items
|
||||
CREATE TABLE IF NOT EXISTS ciphers (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
user_id TEXT,
|
||||
organization_id TEXT,
|
||||
type INTEGER NOT NULL,
|
||||
data TEXT NOT NULL, -- JSON blob of all encrypted fields (name, notes, login, etc.)
|
||||
favorite BOOLEAN NOT NULL DEFAULT 0,
|
||||
folder_id TEXT,
|
||||
deleted_at TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (folder_id) REFERENCES folders(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Folders table for organizing ciphers
|
||||
CREATE TABLE IF NOT EXISTS folders (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL, -- Encrypted folder name
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
57
src/auth.rs
Normal file
57
src/auth.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use axum::{
|
||||
extract::{FromRequestParts, State},
|
||||
http::{header, request::Parts, StatusCode},
|
||||
};
|
||||
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use worker::Env;
|
||||
|
||||
use crate::error::AppError;
|
||||
|
||||
// Secret key for signing JWTs. In a real application, this should be a strong,
|
||||
// securely stored secret from the Worker's environment variables.
|
||||
const JWT_SECRET: &str = "a-very-secure-secret-key-that-should-be-in-env";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub sub: String, // User ID
|
||||
pub exp: usize, // Expiration time
|
||||
pub nbf: usize, // Not before time
|
||||
|
||||
pub premium: bool,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub email_verified: bool,
|
||||
pub amr: Vec<String>,
|
||||
}
|
||||
|
||||
impl<S> FromRequestParts<S> for Claims
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = AppError;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
// Extract the token from the authorization header
|
||||
let token = parts
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.and_then(|auth_header| auth_header.to_str().ok())
|
||||
.and_then(|auth_value| {
|
||||
if auth_value.starts_with("Bearer ") {
|
||||
Some(auth_value[7..].to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| AppError::Unauthorized("Missing or invalid token".to_string()))?;
|
||||
|
||||
// Decode and validate the token
|
||||
let decoding_key = DecodingKey::from_secret(JWT_SECRET.as_ref());
|
||||
let token_data = decode::<Claims>(&token, &decoding_key, &Validation::default())
|
||||
.map_err(|_| AppError::Unauthorized("Invalid token".to_string()))?;
|
||||
|
||||
Ok(token_data.claims)
|
||||
}
|
||||
}
|
||||
81
src/crypto.rs
Normal file
81
src/crypto.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use js_sys::Uint8Array;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{CryptoKey, SubtleCrypto, WorkerGlobalScope};
|
||||
use worker::js_sys;
|
||||
|
||||
use crate::error::AppError;
|
||||
|
||||
pub fn worker_global() -> Option<WorkerGlobalScope> {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
js_sys::global().dyn_into::<WorkerGlobalScope>().ok()
|
||||
}
|
||||
|
||||
/// Gets the SubtleCrypto interface from the global scope.
|
||||
fn subtle_crypto() -> Result<SubtleCrypto, AppError> {
|
||||
Ok(worker_global()
|
||||
.ok_or_else(|| AppError::Crypto("Could not get worker global scope".to_string()))?
|
||||
.crypto()
|
||||
.map_err(|e| AppError::Crypto(format!("Failed to get crypto: {:?}", e)))?
|
||||
.subtle())
|
||||
}
|
||||
|
||||
/// Derives a key using PBKDF2-SHA256.
|
||||
pub async fn pbkdf2_sha256(
|
||||
password: &[u8],
|
||||
salt: &[u8],
|
||||
iterations: u32,
|
||||
key_length_bits: u32,
|
||||
) -> Result<Vec<u8>, AppError> {
|
||||
let subtle = subtle_crypto()?;
|
||||
|
||||
// Import the password as a raw key material
|
||||
let password_array = Uint8Array::new_from_slice(password);
|
||||
let password_obj = password_array.as_ref();
|
||||
let key_material = JsFuture::from(
|
||||
subtle
|
||||
.import_key_with_str(
|
||||
"raw",
|
||||
password_obj,
|
||||
"PBKDF2",
|
||||
false,
|
||||
&js_sys::Array::of1(&JsValue::from_str("deriveBits")),
|
||||
)
|
||||
.map_err(|e| AppError::Crypto(format!("PBKDF2 import_key failed: {:?}", e)))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppError::Crypto(format!("PBKDF2 import_key await failed: {:?}", e)))?;
|
||||
|
||||
let salt_array = Uint8Array::new_from_slice(salt);
|
||||
// Define PBKDF2 parameters
|
||||
let mut params = web_sys::Pbkdf2Params::new(
|
||||
"PBKDF2",
|
||||
JsValue::from_str("SHA-256").as_ref(),
|
||||
iterations,
|
||||
salt_array.as_ref(),
|
||||
);
|
||||
|
||||
// Derive the bits
|
||||
let derived_bits = JsFuture::from(
|
||||
subtle
|
||||
.derive_bits_with_object(
|
||||
params.as_ref(),
|
||||
&CryptoKey::from(key_material),
|
||||
key_length_bits,
|
||||
)
|
||||
.map_err(|e| AppError::Crypto(format!("PBKDF2 derive_bits failed: {:?}", e)))?,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppError::Crypto(format!("PBKDF2 derive_bits await failed: {:?}", e)))?;
|
||||
|
||||
Ok(js_sys::Uint8Array::new(&derived_bits).to_vec())
|
||||
}
|
||||
|
||||
/// Generates a hash of the master key for password verification.
|
||||
pub async fn hash_master_key(
|
||||
master_key: &[u8],
|
||||
master_password: &[u8],
|
||||
) -> Result<Vec<u8>, AppError> {
|
||||
pbkdf2_sha256(master_key, master_password, 1, 256).await
|
||||
}
|
||||
7
src/db.rs
Normal file
7
src/db.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
use crate::error::AppError;
|
||||
use std::sync::Arc;
|
||||
use worker::{D1Database, Env};
|
||||
|
||||
pub fn get_db(env: &Arc<Env>) -> Result<D1Database, AppError> {
|
||||
env.d1("vault1").map_err(AppError::Worker)
|
||||
}
|
||||
62
src/error.rs
Normal file
62
src/error.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Json;
|
||||
use serde_json::json;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AppError {
|
||||
#[error("Worker error: {0}")]
|
||||
Worker(#[from] worker::Error),
|
||||
|
||||
#[error("Database query failed")]
|
||||
Database,
|
||||
|
||||
#[error("Not found: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("Invalid request: {0}")]
|
||||
BadRequest(String),
|
||||
|
||||
#[error("Unauthorized: {0}")]
|
||||
Unauthorized(String),
|
||||
|
||||
#[error("Cryptography error: {0}")]
|
||||
Crypto(String),
|
||||
|
||||
#[error(transparent)]
|
||||
JsonWebToken(#[from] jsonwebtoken::errors::Error),
|
||||
|
||||
#[error("Internal server error")]
|
||||
Internal,
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, error_message) = match self {
|
||||
AppError::Worker(e) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Worker error: {}", e),
|
||||
),
|
||||
AppError::Database => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Database error".to_string(),
|
||||
),
|
||||
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
|
||||
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
|
||||
AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
|
||||
AppError::Crypto(msg) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Crypto error: {}", msg),
|
||||
),
|
||||
AppError::JsonWebToken(_) => (StatusCode::UNAUTHORIZED, "Invalid token".to_string()),
|
||||
AppError::Internal => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Internal server error".to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
let body = Json(json!({ "error": error_message }));
|
||||
(status, body).into_response()
|
||||
}
|
||||
}
|
||||
92
src/handlers/accounts.rs
Normal file
92
src/handlers/accounts.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
use axum::{extract::State, Json};
|
||||
use chrono::Utc;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
use worker::{query, D1Database, Env};
|
||||
|
||||
use crate::{
|
||||
db,
|
||||
error::AppError,
|
||||
models::user::{PreloginResponse, RegisterRequest, User},
|
||||
};
|
||||
|
||||
#[worker::send]
|
||||
pub async fn prelogin(
|
||||
State(env): State<Arc<Env>>,
|
||||
Json(payload): Json<serde_json::Value>,
|
||||
) -> Result<Json<PreloginResponse>, AppError> {
|
||||
let email = payload["email"]
|
||||
.as_str()
|
||||
.ok_or_else(|| AppError::BadRequest("Missing email".to_string()))?;
|
||||
let db = db::get_db(&env)?;
|
||||
|
||||
log::info!("Getting kdf for user {email:?}");
|
||||
let stmt = db.prepare("SELECT kdf_iterations FROM users WHERE email = ?1");
|
||||
let query = stmt.bind(&[email.into()])?;
|
||||
let kdf_iterations: Option<i32> = query
|
||||
.first(Some("kdf_iterations"))
|
||||
.await
|
||||
.map_err(|_| AppError::Database)?;
|
||||
|
||||
log::info!("Returning {kdf_iterations:?}");
|
||||
Ok(Json(PreloginResponse {
|
||||
kdf: 0, // PBKDF2
|
||||
kdf_iterations: kdf_iterations.unwrap_or(600_000),
|
||||
}))
|
||||
}
|
||||
|
||||
#[worker::send]
|
||||
pub async fn register(
|
||||
State(env): State<Arc<Env>>,
|
||||
Json(payload): Json<RegisterRequest>,
|
||||
) -> Result<Json<()>, AppError> {
|
||||
log::info!("Get db");
|
||||
let db = db::get_db(&env)?;
|
||||
log::info!("db got");
|
||||
let now = Utc::now().to_rfc3339();
|
||||
let user = User {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
name: payload.name,
|
||||
email: payload.email.to_lowercase(),
|
||||
email_verified: false,
|
||||
master_password_hash: payload.master_password_hash,
|
||||
master_password_hint: payload.master_password_hint,
|
||||
key: payload.user_symmetric_key,
|
||||
private_key: payload.user_asymmetric_keys.encrypted_private_key,
|
||||
public_key: payload.user_asymmetric_keys.public_key,
|
||||
kdf_type: payload.kdf,
|
||||
kdf_iterations: payload.kdf_iterations,
|
||||
security_stamp: Uuid::new_v4().to_string(),
|
||||
created_at: now.clone(),
|
||||
updated_at: now,
|
||||
};
|
||||
|
||||
log::info!("User {user:?}");
|
||||
let query = query!(
|
||||
&db,
|
||||
"INSERT INTO users (id, name, email, master_password_hash, key, private_key, public_key, kdf_iterations, security_stamp, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
|
||||
user.id,
|
||||
user.name,
|
||||
user.email,
|
||||
user.master_password_hash,
|
||||
user.key,
|
||||
user.private_key,
|
||||
user.public_key,
|
||||
user.kdf_iterations,
|
||||
user.security_stamp,
|
||||
user.created_at,
|
||||
user.updated_at
|
||||
).map_err(|error|{
|
||||
log::error!("failed {error:?}");
|
||||
AppError::Database
|
||||
})?
|
||||
.run()
|
||||
.await
|
||||
.map_err(|error|{
|
||||
log::error!("failed {error:?}");
|
||||
AppError::Database
|
||||
})?;
|
||||
|
||||
Ok(Json(()))
|
||||
}
|
||||
41
src/handlers/ciphers.rs
Normal file
41
src/handlers/ciphers.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use axum::{extract::State, Json};
|
||||
use chrono::Utc;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
use worker::Env;
|
||||
|
||||
use crate::{auth::Claims, db, error::AppError, models::cipher::Cipher};
|
||||
|
||||
#[worker::send]
|
||||
pub async fn create_cipher(
|
||||
claims: Claims,
|
||||
State(env): State<Arc<Env>>,
|
||||
Json(mut payload): Json<Cipher>,
|
||||
) -> Result<Json<Cipher>, AppError> {
|
||||
let db = db::get_db(&env)?;
|
||||
let now = Utc::now().to_rfc3339();
|
||||
|
||||
payload.id = Uuid::new_v4().to_string();
|
||||
payload.user_id = Some(claims.sub);
|
||||
payload.created_at = now.clone();
|
||||
payload.updated_at = now;
|
||||
|
||||
db.prepare(
|
||||
"INSERT INTO ciphers (id, user_id, type, data, favorite, folder_id, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
||||
)
|
||||
.bind(&[
|
||||
payload.id.clone().into(),
|
||||
payload.user_id.clone().into(),
|
||||
payload.r#type.into(),
|
||||
serde_json::to_string(&payload.data).unwrap().into(),
|
||||
payload.favorite.into(),
|
||||
payload.folder_id.clone().into(),
|
||||
payload.created_at.clone().into(),
|
||||
payload.updated_at.clone().into(),
|
||||
])?
|
||||
.run()
|
||||
.await?;
|
||||
|
||||
Ok(Json(payload))
|
||||
}
|
||||
55
src/handlers/config.rs
Normal file
55
src/handlers/config.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use axum::Json;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[worker::send]
|
||||
pub async fn config() -> Json<Value> {
|
||||
// let domain = crate::CONFIG.domain();
|
||||
// Official available feature flags can be found here:
|
||||
// Server (v2025.6.2): https://github.com/bitwarden/server/blob/d094be3267f2030bd0dc62106bc6871cf82682f5/src/Core/Constants.cs#L103
|
||||
// Client (web-v2025.6.1): https://github.com/bitwarden/clients/blob/747c2fd6a1c348a57a76e4a7de8128466ffd3c01/libs/common/src/enums/feature-flag.enum.ts#L12
|
||||
// Android (v2025.6.0): https://github.com/bitwarden/android/blob/b5b022caaad33390c31b3021b2c1205925b0e1a2/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/model/FlagKey.kt#L22
|
||||
// iOS (v2025.6.0): https://github.com/bitwarden/ios/blob/ff06d9c6cc8da89f78f37f376495800201d7261a/BitwardenShared/Core/Platform/Models/Enum/FeatureFlag.swift#L7
|
||||
// let mut feature_states =
|
||||
// parse_experimental_client_feature_flags(&crate::CONFIG.experimental_client_feature_flags());
|
||||
// feature_states.insert("duo-redirect".to_string(), true);
|
||||
// feature_states.insert("email-verification".to_string(), true);
|
||||
// feature_states.insert("unauth-ui-refresh".to_string(), true);
|
||||
// feature_states.insert("enable-pm-flight-recorder".to_string(), true);
|
||||
// feature_states.insert("mobile-error-reporting".to_string(), true);
|
||||
|
||||
let domain = "https://warden-worker.deepgauravraj.workers.dev";
|
||||
Json(json!({
|
||||
// Note: The clients use this version to handle backwards compatibility concerns
|
||||
// This means they expect a version that closely matches the Bitwarden server version
|
||||
// We should make sure that we keep this updated when we support the new server features
|
||||
// Version history:
|
||||
// - Individual cipher key encryption: 2024.2.0
|
||||
"version": "2024.7.0",
|
||||
"gitHash": "25cf6119-dirty",
|
||||
"server": {
|
||||
"name": "Vaultwarden",
|
||||
"url": "https://github.com/dani-garcia/vaultwarden"
|
||||
},
|
||||
"settings": {
|
||||
"disableUserRegistration": true,
|
||||
},
|
||||
"environment": {
|
||||
"vault": domain,
|
||||
"api": format!("{domain}/api"),
|
||||
"identity": format!("{domain}/identity"),
|
||||
"notifications": format!("{domain}/notifications"),
|
||||
"sso": format!("{domain}/sso"),
|
||||
"cloudRegion": null,
|
||||
},
|
||||
// Bitwarden uses this for the self-hosted servers to indicate the default push technology
|
||||
"push": {
|
||||
"pushTechnology": 0,
|
||||
"vapidPublicKey": null
|
||||
},
|
||||
"featureStates": {
|
||||
// "duo-redirect": true,
|
||||
// "flexible-collections-v-1": false
|
||||
},
|
||||
"object": "config",
|
||||
}))
|
||||
}
|
||||
127
src/handlers/identity.rs
Normal file
127
src/handlers/identity.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
use axum::{extract::State, Form, Json};
|
||||
use chrono::{Duration, Utc};
|
||||
use constant_time_eq::constant_time_eq;
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use worker::{D1Database, Env};
|
||||
|
||||
use crate::{auth::Claims, db, error::AppError, models::user::User};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TokenRequest {
|
||||
grant_type: String,
|
||||
username: String,
|
||||
password: Option<String>, // This is the masterPasswordHash
|
||||
// Other fields like scope, client_id, device info are ignored for this basic implementation
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct TokenResponse {
|
||||
#[serde(rename = "access_token")]
|
||||
access_token: String,
|
||||
#[serde(rename = "expires_in")]
|
||||
expires_in: i64,
|
||||
#[serde(rename = "token_type")]
|
||||
token_type: String,
|
||||
#[serde(rename = "refresh_token")]
|
||||
refresh_token: String,
|
||||
#[serde(rename = "Key")]
|
||||
key: String,
|
||||
#[serde(rename = "PrivateKey")]
|
||||
private_key: String,
|
||||
#[serde(rename = "Kdf")]
|
||||
kdf: i32,
|
||||
#[serde(rename = "ResetMasterPassword")]
|
||||
reset_master_password: bool,
|
||||
#[serde(rename = "ForcePasswordReset")]
|
||||
force_password_reset: bool,
|
||||
#[serde(rename = "UserDecryptionOptions")]
|
||||
user_decryption_options: UserDecryptionOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct UserDecryptionOptions {
|
||||
pub has_master_password: bool,
|
||||
pub object: String,
|
||||
}
|
||||
|
||||
#[worker::send]
|
||||
pub async fn token(
|
||||
State(env): State<Arc<Env>>,
|
||||
Form(payload): Form<TokenRequest>,
|
||||
) -> Result<Json<TokenResponse>, AppError> {
|
||||
log::info!("Token payload {payload:?}");
|
||||
if payload.grant_type != "password" {
|
||||
return Err(AppError::BadRequest("Unsupported grant_type".to_string()));
|
||||
}
|
||||
let password_hash = payload
|
||||
.password
|
||||
.ok_or_else(|| AppError::BadRequest("Missing password".to_string()))?;
|
||||
|
||||
let db = db::get_db(&env)?;
|
||||
|
||||
log::info!("Lookup user");
|
||||
let user: Value = db
|
||||
.prepare("SELECT * FROM users WHERE email = ?1")
|
||||
.bind(&[payload.username.to_lowercase().into()])?
|
||||
.first(None)
|
||||
.await
|
||||
.map_err(|_| AppError::Unauthorized("Invalid credentials".to_string()))?
|
||||
.ok_or_else(|| AppError::Unauthorized("Invalid credentials".to_string()))?;
|
||||
log::info!("User is {user:?}");
|
||||
let user: User = serde_json::from_value(user).map_err(|_| AppError::Internal)?;
|
||||
// Securely compare the provided hash with the stored hash
|
||||
if !constant_time_eq(
|
||||
user.master_password_hash.as_bytes(),
|
||||
password_hash.as_bytes(),
|
||||
) {
|
||||
log::info!("Password is incorrect");
|
||||
return Err(AppError::Unauthorized("Invalid credentials".to_string()));
|
||||
}
|
||||
log::info!("Password matched");
|
||||
|
||||
// Create JWT claims
|
||||
let now = Utc::now();
|
||||
let expires_in = Duration::hours(1);
|
||||
let exp = (now + expires_in).timestamp() as usize;
|
||||
|
||||
let claims = Claims {
|
||||
sub: user.id.clone(),
|
||||
exp,
|
||||
nbf: now.timestamp() as usize,
|
||||
premium: true,
|
||||
name: user.name.unwrap_or("User".to_string()),
|
||||
email: user.email,
|
||||
email_verified: true,
|
||||
amr: vec!["Application".into()],
|
||||
};
|
||||
|
||||
let token = encode(
|
||||
&Header::default(),
|
||||
&claims,
|
||||
&EncodingKey::from_secret("a-very-secure-secret-key-that-should-be-in-env".as_ref()),
|
||||
)?;
|
||||
|
||||
// In a real implementation, the refresh token would be securely generated and stored
|
||||
let refresh_token = "static-refresh-token-for-this-example".to_string();
|
||||
|
||||
Ok(Json(TokenResponse {
|
||||
access_token: token,
|
||||
expires_in: expires_in.num_seconds(),
|
||||
token_type: "Bearer".to_string(),
|
||||
refresh_token,
|
||||
key: user.key,
|
||||
private_key: user.private_key,
|
||||
kdf: user.kdf_type,
|
||||
force_password_reset: false,
|
||||
reset_master_password: false,
|
||||
user_decryption_options: UserDecryptionOptions {
|
||||
has_master_password: true,
|
||||
object: "userDecryptionOptions".to_string(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
5
src/handlers/mod.rs
Normal file
5
src/handlers/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub mod accounts;
|
||||
pub mod ciphers;
|
||||
pub mod config;
|
||||
pub mod identity;
|
||||
pub mod sync;
|
||||
79
src/handlers/sync.rs
Normal file
79
src/handlers/sync.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
use worker::Env;
|
||||
|
||||
use crate::{
|
||||
auth::Claims,
|
||||
db,
|
||||
error::AppError,
|
||||
models::{
|
||||
cipher::Cipher,
|
||||
folder::Folder,
|
||||
sync::{Profile, SyncResponse},
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
|
||||
#[worker::send]
|
||||
pub async fn get_sync_data(
|
||||
claims: Claims,
|
||||
State(env): State<Arc<Env>>,
|
||||
) -> Result<Json<SyncResponse>, AppError> {
|
||||
let user_id = claims.sub;
|
||||
let db = db::get_db(&env)?;
|
||||
|
||||
// Fetch profile
|
||||
let user: User = db
|
||||
.prepare("SELECT * FROM users WHERE id = ?1")
|
||||
.bind(&[user_id.clone().into()])?
|
||||
.first(None)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound("User not found".to_string()))?;
|
||||
|
||||
// Fetch folders
|
||||
let folders: Vec<Folder> = db
|
||||
.prepare("SELECT * FROM folders WHERE user_id = ?1")
|
||||
.bind(&[user_id.clone().into()])?
|
||||
.all()
|
||||
.await?
|
||||
.results()?;
|
||||
|
||||
// Fetch ciphers
|
||||
let ciphers: Vec<Cipher> = db
|
||||
.prepare("SELECT * FROM ciphers WHERE user_id = ?1")
|
||||
.bind(&[user_id.clone().into()])?
|
||||
.all()
|
||||
.await?
|
||||
.results()?;
|
||||
|
||||
let time = chrono::DateTime::parse_from_rfc3339(&user.created_at)
|
||||
.map_err(|_| AppError::Internal)?
|
||||
.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
|
||||
let profile = Profile {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
master_password_hint: user.master_password_hint,
|
||||
security_stamp: user.security_stamp,
|
||||
object: "profile".to_string(),
|
||||
premium: true,
|
||||
premium_from_organization: false,
|
||||
email_verified: true,
|
||||
force_password_reset: false,
|
||||
two_factor_enabled: false,
|
||||
uses_key_connector: false,
|
||||
creation_date: time,
|
||||
key: user.key,
|
||||
private_key: user.private_key,
|
||||
};
|
||||
|
||||
let response = SyncResponse {
|
||||
profile,
|
||||
folders,
|
||||
ciphers,
|
||||
domains: serde_json::Value::Null, // Ignored for basic implementation
|
||||
object: "sync".to_string(),
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
32
src/lib.rs
32
src/lib.rs
|
|
@ -1,21 +1,33 @@
|
|||
use axum::{routing::get, Router};
|
||||
use axum::Router;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tower_service::Service;
|
||||
use worker::*;
|
||||
|
||||
fn router() -> Router {
|
||||
Router::new().route("/", get(root))
|
||||
}
|
||||
mod auth;
|
||||
mod crypto;
|
||||
mod db;
|
||||
mod error;
|
||||
mod handlers;
|
||||
mod models;
|
||||
mod router;
|
||||
|
||||
#[event(fetch)]
|
||||
async fn fetch(
|
||||
pub async fn main(
|
||||
req: HttpRequest,
|
||||
_env: Env,
|
||||
env: Env,
|
||||
_ctx: Context,
|
||||
) -> Result<axum::http::Response<axum::body::Body>> {
|
||||
// Set up logging
|
||||
console_error_panic_hook::set_once();
|
||||
Ok(router().call(req).await?)
|
||||
}
|
||||
console_log::init_with_level(log::Level::Debug);
|
||||
|
||||
pub async fn root() -> &'static str {
|
||||
"Hello Axum!"
|
||||
// Allow all origins for CORS, which is typical for a public API like Bitwarden's.
|
||||
let cors = CorsLayer::new()
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any)
|
||||
.allow_origin(Any);
|
||||
|
||||
let mut app = router::api_router(env).layer(cors);
|
||||
|
||||
Ok(app.call(req).await?)
|
||||
}
|
||||
|
|
|
|||
24
src/models/cipher.rs
Normal file
24
src/models/cipher.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Cipher {
|
||||
pub id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub user_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub organization_id: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub r#type: i32,
|
||||
// Store all other fields as a raw JSON value.
|
||||
// The server doesn't need to understand the encrypted contents.
|
||||
pub data: Value,
|
||||
pub favorite: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub folder_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deleted_at: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
12
src/models/folder.rs
Normal file
12
src/models/folder.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Folder {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
// The name is encrypted client-side
|
||||
pub name: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
4
src/models/mod.rs
Normal file
4
src/models/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod cipher;
|
||||
pub mod folder;
|
||||
pub mod sync;
|
||||
pub mod user;
|
||||
46
src/models/sync.rs
Normal file
46
src/models/sync.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use super::{cipher::Cipher, folder::Folder, user::User};
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Profile {
|
||||
pub name: Option<String>,
|
||||
pub email: String,
|
||||
pub id: String,
|
||||
#[serde(rename = "masterPasswordHint")]
|
||||
pub master_password_hint: Option<String>,
|
||||
#[serde(rename = "securityStamp")]
|
||||
pub security_stamp: String,
|
||||
#[serde(rename = "Object")]
|
||||
pub object: String,
|
||||
#[serde(rename = "premiumFromOrganization")]
|
||||
pub premium_from_organization: bool,
|
||||
#[serde(rename = "forcePasswordReset")]
|
||||
pub force_password_reset: bool,
|
||||
#[serde(rename = "emailVerified")]
|
||||
pub email_verified: bool,
|
||||
#[serde(rename = "twoFactorEnabled")]
|
||||
pub two_factor_enabled: bool,
|
||||
pub premium: bool,
|
||||
#[serde(rename = "usesKeyConnector")]
|
||||
pub uses_key_connector: bool,
|
||||
#[serde(rename = "creationDate")]
|
||||
pub creation_date: String,
|
||||
#[serde(rename = "privateKey")]
|
||||
pub private_key: String,
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SyncResponse {
|
||||
#[serde(rename = "Profile")]
|
||||
pub profile: Profile,
|
||||
#[serde(rename = "Folders")]
|
||||
pub folders: Vec<Folder>,
|
||||
#[serde(rename = "Ciphers")]
|
||||
pub ciphers: Vec<Cipher>,
|
||||
#[serde(rename = "Domains")]
|
||||
pub domains: Value,
|
||||
#[serde(rename = "Object")]
|
||||
pub object: String,
|
||||
}
|
||||
76
src/models/user.rs
Normal file
76
src/models/user.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub name: Option<String>,
|
||||
pub email: String,
|
||||
#[serde(with = "bool_from_int")]
|
||||
pub email_verified: bool,
|
||||
pub master_password_hash: String,
|
||||
pub master_password_hint: Option<String>,
|
||||
pub key: String,
|
||||
pub private_key: String,
|
||||
pub public_key: String,
|
||||
pub kdf_type: i32,
|
||||
pub kdf_iterations: i32,
|
||||
pub security_stamp: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
mod bool_from_int {
|
||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = i64::deserialize(deserializer)?;
|
||||
match value {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => Err(serde::de::Error::custom("expected integer 0 or 1")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if *value {
|
||||
serializer.serialize_i64(1)
|
||||
} else {
|
||||
serializer.serialize_i64(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For /accounts/prelogin response
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PreloginResponse {
|
||||
pub kdf: i32,
|
||||
pub kdf_iterations: i32,
|
||||
}
|
||||
|
||||
// For /accounts/register request
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RegisterRequest {
|
||||
pub name: Option<String>,
|
||||
pub email: String,
|
||||
pub master_password_hash: String,
|
||||
pub master_password_hint: Option<String>,
|
||||
pub user_symmetric_key: String,
|
||||
pub user_asymmetric_keys: KeyData,
|
||||
pub kdf: i32,
|
||||
pub kdf_iterations: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct KeyData {
|
||||
pub public_key: String,
|
||||
pub encrypted_private_key: String,
|
||||
}
|
||||
24
src/router.rs
Normal file
24
src/router.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use axum::{
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use worker::Env;
|
||||
|
||||
use crate::handlers::{accounts, ciphers, config, identity, sync};
|
||||
|
||||
pub fn api_router(env: Env) -> Router {
|
||||
let app_state = Arc::new(env);
|
||||
|
||||
Router::new()
|
||||
// Identity/Auth routes
|
||||
.route("/identity/accounts/prelogin", post(accounts::prelogin))
|
||||
.route("/identity/accounts/register", post(accounts::register))
|
||||
.route("/identity/connect/token", post(identity::token))
|
||||
// Main data sync route
|
||||
.route("/api/sync", get(sync::get_sync_data))
|
||||
// Ciphers CRUD
|
||||
.route("/api/ciphers/create", post(ciphers::create_cipher))
|
||||
.route("/api/config", get(config::config))
|
||||
.with_state(app_state)
|
||||
}
|
||||
|
|
@ -4,3 +4,8 @@ compatibility_date = "2025-09-19"
|
|||
|
||||
[build]
|
||||
command = "cargo install -q worker-build && worker-build --release"
|
||||
|
||||
[[d1_databases]]
|
||||
binding = "vault1"
|
||||
database_name = "vault1"
|
||||
database_id = "${D1_DATABASE_ID}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue