Login to vault

This commit is contained in:
Deep 2025-09-21 11:35:13 +05:30
parent 31b6af97ba
commit 0112286623
22 changed files with 1449 additions and 18 deletions

2
.cargo/config.toml Normal file
View file

@ -0,0 +1,2 @@
[target.wasm32-unknown-unknown]
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']

552
Cargo.lock generated
View file

@ -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"

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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))
}

View file

@ -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
View 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
View 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
View file

@ -0,0 +1,4 @@
pub mod cipher;
pub mod folder;
pub mod sync;
pub mod user;

46
src/models/sync.rs Normal file
View 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
View 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
View 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)
}

View file

@ -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}"