owo
This commit is contained in:
commit
a81847f10a
11 changed files with 1253 additions and 0 deletions
2
.cargo/config
Normal file
2
.cargo/config
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[build]
|
||||||
|
target = "wasm32-wasi"
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
/bin
|
||||||
|
/pkg
|
||||||
|
fastly.dev.toml
|
511
Cargo.lock
generated
Normal file
511
Cargo.lock
generated
Normal file
|
@ -0,0 +1,511 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake3"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"constant_time_eq",
|
||||||
|
"digest 0.10.7",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer 0.10.4",
|
||||||
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastly"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b59249dde10f901c578a8eb50577e3c6ee062b0e75f70f9d852da05b75c664d"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bytes",
|
||||||
|
"cfg-if",
|
||||||
|
"fastly-macros",
|
||||||
|
"fastly-shared",
|
||||||
|
"fastly-sys",
|
||||||
|
"http",
|
||||||
|
"lazy_static",
|
||||||
|
"mime",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sha2",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastly-macros"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d68ee83e8e1c9613d0448f50687a363b289501952f3c046f6cbdcdd82f29e7c2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastly-shared"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea8c7df017449664ec1ee065dac781b5d4ab405e44533c2483cfb31b3f1c6124"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"http",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastly-sys"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50a5c0dc150d4e1eb2bfdbe169df6bed8d7fafda38ac5a792d800544b35d6b58"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fastly-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.147"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.183"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.183"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.28",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_urlencoded"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer 0.9.0",
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest 0.9.0",
|
||||||
|
"opaque-debug",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "synkronisera"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"blake3",
|
||||||
|
"fastly",
|
||||||
|
"hex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.28",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
|
||||||
|
dependencies = [
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "synkronisera"
|
||||||
|
license = "MIT or Apache-2.0"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["me@skye.vg"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = 1
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64 = "0.21.2"
|
||||||
|
blake3 = "1.4.1"
|
||||||
|
fastly = "0.9.4"
|
||||||
|
hex = "0.4.3"
|
||||||
|
serde = { version = "1.0.183", features = ["derive"] }
|
||||||
|
serde_json = "1.0.104"
|
||||||
|
time = "0.3.25"
|
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
23
LICENSE-MIT
Normal file
23
LICENSE-MIT
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
23
fastly.dev-example.toml
Normal file
23
fastly.dev-example.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[local_server]
|
||||||
|
|
||||||
|
[local_server.backends]
|
||||||
|
|
||||||
|
[local_server.backends.discord]
|
||||||
|
url = "https://discord.com/"
|
||||||
|
|
||||||
|
[local_server.backends.upstash_redis]
|
||||||
|
url = "https://totally-real-upstash-instance.upstash.io/"
|
||||||
|
|
||||||
|
[local_server.config_stores]
|
||||||
|
|
||||||
|
[local_server.config_stores.synkronisera]
|
||||||
|
format = "inline-toml"
|
||||||
|
|
||||||
|
[local_server.config_stores.synkronisera.contents]
|
||||||
|
client_id = "1234567890123456789"
|
||||||
|
client_secret = "TOTALLY_LEGIT_DISCORD_CLIENT_SECRET"
|
||||||
|
mac_secret = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
|
||||||
|
max_config_size = "524288"
|
||||||
|
redirect_url = "http://127.0.0.1:7676/v1/oauth/callback"
|
||||||
|
upstash_redis_key = "TOTALLY_LEGIT_REDIS_KEY"
|
||||||
|
upstash_redis_url = "https://totally-real-upstash-instance.upstash.io"
|
9
fastly.toml
Normal file
9
fastly.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# This file describes a Fastly Compute@Edge package. To learn more visit:
|
||||||
|
# https://developer.fastly.com/reference/fastly-toml/
|
||||||
|
|
||||||
|
authors = ["me@skye.vg"]
|
||||||
|
description = "A Vencord Cloud server implementation using Fastly Compute@Edge"
|
||||||
|
language = "rust"
|
||||||
|
manifest_version = 3
|
||||||
|
name = "synkronisera"
|
||||||
|
service_id = "uaUI7PGPaJEYQJIrMh0056"
|
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "stable"
|
||||||
|
targets = [ "wasm32-wasi" ]
|
15
src/index.html
Normal file
15
src/index.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||||
|
/>
|
||||||
|
<title>synkronisera</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Use this with Vencord not your browser idiot
|
||||||
|
</body>
|
||||||
|
</html>
|
443
src/main.rs
Normal file
443
src/main.rs
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
use base64::Engine;
|
||||||
|
use fastly::error::anyhow;
|
||||||
|
use fastly::http::{header, HeaderValue, Method, StatusCode};
|
||||||
|
use fastly::{mime, ConfigStore, Error, Request, Response};
|
||||||
|
use hex::FromHex;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
struct UpstashRedisBackend {
|
||||||
|
url: String,
|
||||||
|
key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
enum UpstashResponse {
|
||||||
|
#[serde(rename = "result")]
|
||||||
|
Ok(serde_json::Value),
|
||||||
|
#[serde(rename = "error")]
|
||||||
|
Err(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UpstashResponse> for Result<serde_json::Value> {
|
||||||
|
fn from(value: UpstashResponse) -> Self {
|
||||||
|
match value {
|
||||||
|
UpstashResponse::Ok(value) => Ok(value),
|
||||||
|
UpstashResponse::Err(err) => Err(anyhow!("Upstash Redis error: {}", err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageBackend for UpstashRedisBackend {
|
||||||
|
fn from_configstore(store: &ConfigStore) -> Self {
|
||||||
|
Self {
|
||||||
|
url: store.get("upstash_redis_url").unwrap(),
|
||||||
|
key: store.get("upstash_redis_key").unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_modified(&mut self, id: u64) -> Result<Option<u64>> {
|
||||||
|
let id_hex = hex::encode(id.to_le_bytes());
|
||||||
|
let response: Result<serde_json::Value> = Request::new(
|
||||||
|
Method::GET,
|
||||||
|
format!("{}/HGET/{}/last_modified", self.url, id_hex),
|
||||||
|
)
|
||||||
|
.with_header(header::AUTHORIZATION, format!("Bearer {}", self.key))
|
||||||
|
.send("upstash_redis")?
|
||||||
|
.take_body_json::<UpstashResponse>()?
|
||||||
|
.into();
|
||||||
|
let response: Option<String> = serde_json::from_value(response?)?;
|
||||||
|
Ok(if let Some(response) = response {
|
||||||
|
let timestamp = u64::from_le_bytes(<[u8; 8]>::from_hex(response)?);
|
||||||
|
Some(timestamp)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&mut self, id: u64) -> Result<Option<(Vec<u8>, u64)>> {
|
||||||
|
let id_hex = hex::encode(id.to_le_bytes());
|
||||||
|
let response: Result<serde_json::Value> = Request::new(
|
||||||
|
Method::GET,
|
||||||
|
format!("{}/HMGET/{}/last_modified/settings", self.url, id_hex),
|
||||||
|
)
|
||||||
|
.with_header(header::AUTHORIZATION, format!("Bearer {}", self.key))
|
||||||
|
.send("upstash_redis")?
|
||||||
|
.take_body_json::<UpstashResponse>()?
|
||||||
|
.into();
|
||||||
|
let response: (Option<String>, Option<String>) = serde_json::from_value(response?)?;
|
||||||
|
Ok(if let (Some(timestamp), Some(data)) = response {
|
||||||
|
let timestamp = u64::from_le_bytes(<[u8; 8]>::from_hex(timestamp)?);
|
||||||
|
let data = BASE64_ENGINE.decode(data)?;
|
||||||
|
Some((data, timestamp))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, id: u64, data: Vec<u8>) -> Result<u64> {
|
||||||
|
let now = time::OffsetDateTime::now_utc();
|
||||||
|
let now = (now.unix_timestamp_nanos() / 1000000) as u64;
|
||||||
|
|
||||||
|
let id_hex = hex::encode(id.to_le_bytes());
|
||||||
|
let now_hex = hex::encode(now.to_le_bytes());
|
||||||
|
|
||||||
|
let data_base64 = BASE64_ENGINE.encode(data);
|
||||||
|
|
||||||
|
let response: Result<serde_json::Value> = Request::new(
|
||||||
|
Method::GET,
|
||||||
|
format!(
|
||||||
|
"{}/HSET/{}/last_modified/{}/settings",
|
||||||
|
self.url, id_hex, now_hex
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with_header(header::AUTHORIZATION, format!("Bearer {}", self.key))
|
||||||
|
.with_body_octet_stream(data_base64.as_bytes())
|
||||||
|
.send("upstash_redis")?
|
||||||
|
.take_body_json::<UpstashResponse>()?
|
||||||
|
.into();
|
||||||
|
response.map(|_| now)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn del(&mut self, id: u64) -> Result<()> {
|
||||||
|
let id_hex = hex::encode(id.to_le_bytes());
|
||||||
|
let response: Result<serde_json::Value> = Request::new(
|
||||||
|
Method::GET,
|
||||||
|
format!("{}/HDEL/{}/last_modified/settings", self.url, id_hex),
|
||||||
|
)
|
||||||
|
.with_header(header::AUTHORIZATION, format!("Bearer {}", self.key))
|
||||||
|
.send("upstash_redis")?
|
||||||
|
.take_body_json::<UpstashResponse>()?
|
||||||
|
.into();
|
||||||
|
response.map(|_| ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultStorageBackend = UpstashRedisBackend;
|
||||||
|
|
||||||
|
trait StorageBackend {
|
||||||
|
fn from_configstore(store: &ConfigStore) -> Self;
|
||||||
|
fn get_modified(&mut self, id: u64) -> Result<Option<u64>>;
|
||||||
|
fn get(&mut self, id: u64) -> Result<Option<(Vec<u8>, u64)>>;
|
||||||
|
fn set(&mut self, id: u64, data: Vec<u8>) -> Result<u64>;
|
||||||
|
fn del(&mut self, id: u64) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALLOWED_DISCORD_ORIGINS: [&[u8]; 3] = [
|
||||||
|
b"https://discord.com",
|
||||||
|
b"https://ptb.discord.com",
|
||||||
|
b"https://canary.discord.com",
|
||||||
|
];
|
||||||
|
|
||||||
|
const DISCORD_OAUTH_TOKEN_URL: &str = "https://discord.com/api/oauth2/token";
|
||||||
|
const DISCORD_SELF_INFO_URL: &str = "https://discord.com/api/users/@me";
|
||||||
|
|
||||||
|
const BASE64_ENGINE: base64::engine::GeneralPurpose =
|
||||||
|
base64::engine::general_purpose::STANDARD_NO_PAD;
|
||||||
|
|
||||||
|
fn handle_with_cors(
|
||||||
|
request: Request,
|
||||||
|
methods: &'static str,
|
||||||
|
allowed_headers: &'static str,
|
||||||
|
mut handler: impl FnMut(Request) -> Result<Response>,
|
||||||
|
) -> Result<Response> {
|
||||||
|
Ok(if let &Method::OPTIONS = request.get_method() {
|
||||||
|
match request.get_header(header::ORIGIN) {
|
||||||
|
Some(origin) => {
|
||||||
|
if ALLOWED_DISCORD_ORIGINS.contains(&origin.as_bytes()) {
|
||||||
|
Response::from_status(StatusCode::OK)
|
||||||
|
.with_header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin)
|
||||||
|
.with_header(header::ACCESS_CONTROL_ALLOW_HEADERS, allowed_headers)
|
||||||
|
.with_header(header::ACCESS_CONTROL_ALLOW_METHODS, methods)
|
||||||
|
.with_header(header::VARY, "Origin")
|
||||||
|
} else {
|
||||||
|
Response::from_status(StatusCode::FORBIDDEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Response::from_status(StatusCode::OK).with_header(header::ALLOW, methods),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let origin = request.get_header(header::ORIGIN).cloned();
|
||||||
|
let mut response = handler(request)?;
|
||||||
|
if response.get_status() == StatusCode::METHOD_NOT_ALLOWED {
|
||||||
|
response.set_header(header::ALLOW, methods);
|
||||||
|
} else if let Some(origin) = origin {
|
||||||
|
response.set_header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||||
|
response.set_header(header::ACCESS_CONTROL_ALLOW_HEADERS, allowed_headers);
|
||||||
|
response.set_header(header::ACCESS_CONTROL_ALLOW_METHODS, methods);
|
||||||
|
response.set_header(header::VARY, "Origin");
|
||||||
|
}
|
||||||
|
response
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct TokenResponse {
|
||||||
|
access_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UserInfo {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_auth(token: &HeaderValue, secret: &[u8; 32]) -> Option<u64> {
|
||||||
|
let decoded = base64::engine::general_purpose::STANDARD.decode(token).ok()?;
|
||||||
|
let decoded = String::from_utf8(decoded).ok()?;
|
||||||
|
let token = decoded.split_terminator(':').next()?;
|
||||||
|
let decoded = BASE64_ENGINE.decode(token).ok()?;
|
||||||
|
if decoded.len() != 40 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (id, mac) = decoded.split_at(8);
|
||||||
|
let id = u64::from_le_bytes(id.try_into().unwrap());
|
||||||
|
let mac: [u8; 32] = mac.try_into().unwrap();
|
||||||
|
let mac: blake3::Hash = mac.into();
|
||||||
|
let correct_mac = blake3::keyed_hash(secret, &id.to_le_bytes());
|
||||||
|
if mac == correct_mac {
|
||||||
|
Some(id)
|
||||||
|
} else {
|
||||||
|
println!("mac is wrong; got {:?}, expected {:?}", mac, correct_mac);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[fastly::main]
|
||||||
|
fn main(req: Request) -> Result<Response> {
|
||||||
|
let config = ConfigStore::open("synkronisera");
|
||||||
|
|
||||||
|
let client_id = config.get("client_id").unwrap();
|
||||||
|
let client_secret = config.get("client_secret").unwrap();
|
||||||
|
let redirect_url = config.get("redirect_url").unwrap();
|
||||||
|
|
||||||
|
let mac_secret = <[u8; 32]>::from_hex(config.get("mac_secret").unwrap()).unwrap();
|
||||||
|
let max_config_size: usize = config.get("max_config_size").unwrap().parse().unwrap();
|
||||||
|
|
||||||
|
let mut store = DefaultStorageBackend::from_configstore(&config);
|
||||||
|
|
||||||
|
match req.get_path() {
|
||||||
|
"/" => Ok(Response::from_status(StatusCode::OK)
|
||||||
|
.with_content_type(mime::TEXT_HTML_UTF_8)
|
||||||
|
.with_body(include_str!("index.html"))),
|
||||||
|
|
||||||
|
"/v1" => handle_with_cors(req, "OPTIONS, GET, HEAD", "Authorization", |req| {
|
||||||
|
Ok(match *req.get_method() {
|
||||||
|
Method::GET => Response::from_status(StatusCode::OK).with_body_json(&json!({
|
||||||
|
"ping": "pong"
|
||||||
|
}))?,
|
||||||
|
Method::HEAD => Response::from_status(StatusCode::OK),
|
||||||
|
_ => Response::from_status(StatusCode::METHOD_NOT_ALLOWED),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
"/v1/" => handle_with_cors(req, "OPTIONS, DELETE", "Authorization", |req| {
|
||||||
|
Ok(match *req.get_method() {
|
||||||
|
Method::DELETE => {
|
||||||
|
let id = if let Some(id) = req
|
||||||
|
.get_header(header::AUTHORIZATION)
|
||||||
|
.and_then(|token| check_auth(token, &mac_secret))
|
||||||
|
{
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
return Ok(Response::from_status(StatusCode::UNAUTHORIZED)
|
||||||
|
.with_body_json(&json!({
|
||||||
|
"error": "Unauthorized"
|
||||||
|
}))?);
|
||||||
|
};
|
||||||
|
store.del(id)?;
|
||||||
|
Response::from_status(StatusCode::NO_CONTENT)
|
||||||
|
}
|
||||||
|
_ => Response::from_status(StatusCode::METHOD_NOT_ALLOWED),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
"/v1/oauth/settings" => handle_with_cors(req, "OPTIONS, GET, HEAD", "Authorization", |req| {
|
||||||
|
Ok(match *req.get_method() {
|
||||||
|
Method::GET => Response::from_status(StatusCode::OK).with_body_json(&json!({
|
||||||
|
"clientId": client_id,
|
||||||
|
"redirectUri": redirect_url
|
||||||
|
}))?,
|
||||||
|
Method::HEAD => Response::from_status(StatusCode::OK),
|
||||||
|
_ => Response::from_status(StatusCode::METHOD_NOT_ALLOWED),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
"/v1/oauth/callback" => handle_with_cors(req, "OPTIONS, GET", "Authorization", |req| {
|
||||||
|
Ok(match *req.get_method() {
|
||||||
|
Method::GET => {
|
||||||
|
if let Some(code) = req.get_query_parameter("code") {
|
||||||
|
let mut token_response =
|
||||||
|
Request::new(Method::POST, DISCORD_OAUTH_TOKEN_URL)
|
||||||
|
.with_body_form(&json!({
|
||||||
|
"client_id": client_id,
|
||||||
|
"client_secret": client_secret,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": code,
|
||||||
|
"redirect_uri": redirect_url,
|
||||||
|
"scope": "identify"
|
||||||
|
}))?
|
||||||
|
.send("discord")?;
|
||||||
|
if token_response.get_status() != StatusCode::OK {
|
||||||
|
Response::from_status(StatusCode::UNAUTHORIZED).with_body_json(
|
||||||
|
&json!({
|
||||||
|
"error": "Failed to authorize with Discord"
|
||||||
|
}),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
let token = token_response
|
||||||
|
.take_body_json::<TokenResponse>()?
|
||||||
|
.access_token;
|
||||||
|
let user_id: u64 = Request::new(Method::GET, DISCORD_SELF_INFO_URL)
|
||||||
|
.with_header(header::AUTHORIZATION, format!("Bearer {}", token))
|
||||||
|
.send("discord")?
|
||||||
|
.take_body_json::<UserInfo>()?
|
||||||
|
.id
|
||||||
|
.parse()?;
|
||||||
|
let user_id_bytes = user_id.to_le_bytes();
|
||||||
|
let mac: [u8; 32] =
|
||||||
|
blake3::keyed_hash(&mac_secret, &user_id_bytes).into();
|
||||||
|
let combined_token = [
|
||||||
|
user_id_bytes[0],
|
||||||
|
user_id_bytes[1],
|
||||||
|
user_id_bytes[2],
|
||||||
|
user_id_bytes[3],
|
||||||
|
user_id_bytes[4],
|
||||||
|
user_id_bytes[5],
|
||||||
|
user_id_bytes[6],
|
||||||
|
user_id_bytes[7],
|
||||||
|
mac[0],
|
||||||
|
mac[1],
|
||||||
|
mac[2],
|
||||||
|
mac[3],
|
||||||
|
mac[4],
|
||||||
|
mac[5],
|
||||||
|
mac[6],
|
||||||
|
mac[7],
|
||||||
|
mac[8],
|
||||||
|
mac[9],
|
||||||
|
mac[10],
|
||||||
|
mac[11],
|
||||||
|
mac[12],
|
||||||
|
mac[13],
|
||||||
|
mac[14],
|
||||||
|
mac[15],
|
||||||
|
mac[16],
|
||||||
|
mac[17],
|
||||||
|
mac[18],
|
||||||
|
mac[19],
|
||||||
|
mac[20],
|
||||||
|
mac[21],
|
||||||
|
mac[22],
|
||||||
|
mac[23],
|
||||||
|
mac[24],
|
||||||
|
mac[25],
|
||||||
|
mac[26],
|
||||||
|
mac[27],
|
||||||
|
mac[28],
|
||||||
|
mac[29],
|
||||||
|
mac[30],
|
||||||
|
mac[31],
|
||||||
|
];
|
||||||
|
Response::from_status(StatusCode::OK).with_body_json(&json!({
|
||||||
|
"secret": BASE64_ENGINE.encode(combined_token)
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Response::from_status(StatusCode::BAD_REQUEST).with_body_json(&json!({
|
||||||
|
"error": "Missing code"
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Response::from_status(StatusCode::METHOD_NOT_ALLOWED),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
"/v1/settings" => handle_with_cors(req, "OPTIONS, GET, HEAD, PUT, DELETE", "Authorization, Content-Type, If-None-Match", |req| {
|
||||||
|
let id = if let Some(id) = req
|
||||||
|
.get_header(header::AUTHORIZATION)
|
||||||
|
.and_then(|token| check_auth(token, &mac_secret))
|
||||||
|
{
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
return Ok(
|
||||||
|
Response::from_status(StatusCode::UNAUTHORIZED).with_body_json(&json!({
|
||||||
|
"error": "Unauthorized"
|
||||||
|
}))?,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Ok(match *req.get_method() {
|
||||||
|
Method::GET => {
|
||||||
|
if let Some((data, timestamp)) = store.get(id)? {
|
||||||
|
fn get_cached_timestamp(header: Option<&HeaderValue>) -> Option<u64> {
|
||||||
|
header?.to_str().ok()?.parse().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cached_timestamp) =
|
||||||
|
get_cached_timestamp(req.get_header(header::IF_NONE_MATCH))
|
||||||
|
{
|
||||||
|
if cached_timestamp == timestamp {
|
||||||
|
return Ok(Response::from_status(StatusCode::NOT_MODIFIED)
|
||||||
|
.with_header(header::ETAG, format!("{}", timestamp)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::from_status(StatusCode::OK)
|
||||||
|
.with_header(header::ETAG, format!("{}", timestamp))
|
||||||
|
.with_body_octet_stream(&data)
|
||||||
|
} else {
|
||||||
|
Response::from_status(StatusCode::NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Method::HEAD => {
|
||||||
|
if let Some(timestamp) = store.get_modified(id)? {
|
||||||
|
fn get_cached_timestamp(header: Option<&HeaderValue>) -> Option<u64> {
|
||||||
|
header?.to_str().ok()?.parse().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cached_timestamp) =
|
||||||
|
get_cached_timestamp(req.get_header(header::IF_NONE_MATCH))
|
||||||
|
{
|
||||||
|
if cached_timestamp == timestamp {
|
||||||
|
return Ok(Response::from_status(StatusCode::NOT_MODIFIED)
|
||||||
|
.with_header(header::ETAG, format!("{}", timestamp)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::from_status(StatusCode::OK)
|
||||||
|
.with_header(header::ETAG, format!("{}", timestamp))
|
||||||
|
.with_header(header::CONTENT_TYPE, "application/octet-stream")
|
||||||
|
} else {
|
||||||
|
Response::from_status(StatusCode::NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Method::PUT => {
|
||||||
|
if Some(b"application/octet-stream" as &[u8])
|
||||||
|
!= req
|
||||||
|
.get_header(header::CONTENT_TYPE)
|
||||||
|
.map(|header| header.as_bytes())
|
||||||
|
{
|
||||||
|
return Ok(Response::from_status(StatusCode::UNSUPPORTED_MEDIA_TYPE));
|
||||||
|
}
|
||||||
|
let body = req.into_body_bytes();
|
||||||
|
if body.len() > max_config_size {
|
||||||
|
return Ok(Response::from_status(StatusCode::PAYLOAD_TOO_LARGE));
|
||||||
|
}
|
||||||
|
let etag = store.set(id, body)?;
|
||||||
|
Response::from_status(StatusCode::OK).with_body_json(&json!({
|
||||||
|
"written": etag
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
Method::DELETE => {
|
||||||
|
store.del(id)?;
|
||||||
|
Response::from_status(StatusCode::NO_CONTENT)
|
||||||
|
}
|
||||||
|
_ => Response::from_status(StatusCode::METHOD_NOT_ALLOWED),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
_ => Ok(Response::from_status(StatusCode::NOT_FOUND)
|
||||||
|
.with_body_text_plain("The page you requested could not be found\n")),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue