diff --git a/Cargo.lock b/Cargo.lock index 17483d38b4..536531e9fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -176,6 +194,17 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "argon2" version = "0.5.3" @@ -189,6 +218,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "asn1-rs" version = "0.6.2" @@ -310,6 +354,49 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +dependencies = [ + "arrayvec", +] + [[package]] name = "aws-lc-rs" version = "1.15.4" @@ -463,20 +550,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitfields" -version = "0.12.4" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d84268bbf9b487d31fe4b849edbefcd3911422d7a07de855a2da1f70ab3d1c" +checksum = "d866f92dc1574aa8da443eacb06ad8fbe4056dbc1b7c3aae508cbccd46c7e706" dependencies = [ "bitfields-impl", ] [[package]] name = "bitfields-impl" -version = "0.9.4" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c93edde7bb4416c35c85048e34f78999dcb47d199bde3b1d79286156f3e2fb" +checksum = "c09459e6af3016ea58af8332e31d5da117d33a621bad7019355eefccc4a567d4" dependencies = [ "proc-macro2", "quote", @@ -492,13 +585,22 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] +[[package]] +name = "bitstream-io" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +dependencies = [ + "core2", +] + [[package]] name = "bitvec" version = "1.0.1" @@ -576,18 +678,36 @@ dependencies = [ "memchr", ] +[[package]] +name = "built" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" + [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -618,9 +738,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -771,6 +891,12 @@ dependencies = [ "cc", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.4" @@ -888,6 +1014,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1354,9 +1489,11 @@ dependencies = [ "claims", "defguard_common", "humantime", + "image", "lettre", "mrml", "pulldown-cmark", + "qrforge", "reqwest", "serde", "serde_json", @@ -1640,7 +1777,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", ] @@ -1857,6 +1994,26 @@ dependencies = [ "syn", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1895,12 +2052,56 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fax" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" +dependencies = [ + "fax_derive", +] + +[[package]] +name = "fax_derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "ff" version = "0.13.1" @@ -2022,9 +2223,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -2037,9 +2238,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -2047,15 +2248,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -2075,15 +2276,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -2092,21 +2293,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -2116,7 +2317,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -2190,13 +2390,23 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "git2" version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "libgit2-sys", "log", @@ -2222,7 +2432,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "ignore", "walkdir", ] @@ -2715,6 +2925,46 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "image" +version = "0.25.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core 0.5.1", + "zune-jpeg 0.5.12", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + [[package]] name = "indexmap" version = "1.9.3" @@ -2747,6 +2997,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -2829,7 +3090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba781c43eb46c3bbf5bfda541139eed9a52b78d7c3aa74d516918885ecd63c40" dependencies = [ "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "num-bigint", "serde", "serde_json", @@ -2877,9 +3138,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -2932,6 +3193,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "lettre" version = "0.11.19" @@ -2962,9 +3229,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] [[package]] name = "libgit2-sys" @@ -2990,7 +3267,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "redox_syscall 0.7.1", ] @@ -3050,6 +3327,15 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -3111,6 +3397,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "md-5" version = "0.10.6" @@ -3187,6 +3483,16 @@ dependencies = [ "syn", ] +[[package]] +name = "moxcms" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "mrml" version = "5.1.0" @@ -3212,17 +3518,17 @@ checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.1.6", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -3239,7 +3545,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -3270,6 +3576,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -3343,6 +3655,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3419,7 +3742,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-foundation", ] @@ -3440,7 +3763,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "dispatch2", "objc2", ] @@ -3451,7 +3774,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "dispatch2", "objc2", "objc2-core-foundation", @@ -3484,7 +3807,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -3502,7 +3825,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "block2", "libc", "objc2", @@ -3515,7 +3838,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", ] @@ -3526,7 +3849,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -3538,7 +3861,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "block2", "objc2", "objc2-cloud-kit", @@ -3657,7 +3980,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -3677,12 +4000,6 @@ dependencies = [ "syn", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -3927,9 +4244,9 @@ dependencies = [ [[package]] name = "pgp" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91d320242d9b686612b15526fe38711afdf856e112eaa4775ce25b0d9b12b11" +checksum = "eaffe1ec22db286599c30ae6be75b37493b558735d86c8e59ec5c38794415fe4" dependencies = [ "aead", "aes", @@ -3946,7 +4263,6 @@ dependencies = [ "camellia", "cast5", "cfb-mode", - "chrono", "cipher", "const-oid", "crc24", @@ -3969,7 +4285,7 @@ dependencies = [ "k256", "log", "md-5", - "nom 7.1.3", + "nom 8.0.0", "num-bigint-dig", "num-traits", "num_enum", @@ -3979,6 +4295,7 @@ dependencies = [ "p521", "rand 0.8.5", "regex", + "replace_with", "ripemd", "rsa", "sha1", @@ -4104,6 +4421,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polyval" version = "0.6.2" @@ -4183,6 +4513,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "prost" version = "0.14.3" @@ -4268,7 +4617,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "getopts", "memchr", "pulldown-cmark-escape", @@ -4290,6 +4639,39 @@ dependencies = [ "pulldown-cmark", ] +[[package]] +name = "pxfm" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "qrforge" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532d8e082f339eadd546d04e61d809be4b3ca469a75ce86016f89bc5482d5ebb" +dependencies = [ + "image", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quinn" version = "0.11.9" @@ -4431,6 +4813,76 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.2", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rcgen" version = "0.14.7" @@ -4451,7 +4903,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -4460,7 +4912,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -4512,6 +4964,12 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +[[package]] +name = "replace_with" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51743d3e274e2b18df81c4dc6caf8a5b8e15dbe799e0dca05c7617380094e884" + [[package]] name = "reqwest" version = "0.12.28" @@ -4572,6 +5030,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "rgb" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" + [[package]] name = "ring" version = "0.17.14" @@ -4691,7 +5155,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -4720,10 +5184,10 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -4835,24 +5299,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -4861,9 +5312,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -4987,13 +5438,14 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.15.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" +checksum = "ac22439301a0b6f45a037681518e3169e8db1db76080e2e9600a08d1027df037" dependencies = [ + "itoa", "percent-encoding", + "ryu", "serde", - "thiserror 2.0.18", ] [[package]] @@ -5170,11 +5622,20 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", @@ -5360,7 +5821,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "bytes", "chrono", @@ -5404,7 +5865,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "chrono", "crc", @@ -5613,9 +6074,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.115" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -5648,7 +6109,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5764,6 +6225,20 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "tiff" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.4.21", +] + [[package]] name = "time" version = "0.3.47" @@ -5926,18 +6401,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.7+spec-1.1.0" +version = "1.0.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" dependencies = [ "winnow", ] [[package]] name = "tonic" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" +checksum = "7f32a6f80051a4111560201420c7885d0082ba9efe2ab61875c587bb6b18b9a0" dependencies = [ "async-trait", "axum", @@ -5967,9 +6442,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27aac809edf60b741e2d7db6367214d078856b8a5bff0087e94ff330fb97b6fc" +checksum = "ce6d8958ed3be404120ca43ffa0fb1e1fc7be214e96c8d33bd43a131b6eebc9e" dependencies = [ "prettyplease", "proc-macro2", @@ -5979,9 +6454,9 @@ dependencies = [ [[package]] name = "tonic-health" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbde2c702c4be12b9b2f6f7e6c824a84a7b7be177070cada8ee575a581af359" +checksum = "163e5ad9be2924d9cef75f02fcd44c1803a5af250f4ef7e085992270ac51fb9b" dependencies = [ "prost", "tokio", @@ -5992,9 +6467,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" +checksum = "9f86539c0089bfd09b1f8c0ab0239d80392af74c21bc9e0f15e1b4aca4c1647f" dependencies = [ "bytes", "prost", @@ -6003,9 +6478,9 @@ dependencies = [ [[package]] name = "tonic-prost-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4556786613791cfef4ed134aa670b61a85cfcacf71543ef33e8d801abae988f" +checksum = "65873ace111e90344b8973e94a1fc817c924473affff24629281f90daed1cd2e" dependencies = [ "prettyplease", "proc-macro2", @@ -6054,7 +6529,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", @@ -6216,9 +6691,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -6358,16 +6833,27 @@ checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", ] +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -6574,7 +7060,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap 2.13.0", "semver", @@ -6722,6 +7208,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + [[package]] name = "whoami" version = "1.6.1" @@ -7109,7 +7601,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "indexmap 2.13.0", "log", "serde", @@ -7201,6 +7693,12 @@ dependencies = [ "time", ] +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "yasna" version = "0.5.2" @@ -7365,3 +7863,42 @@ dependencies = [ "log", "simd-adler32", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe" +dependencies = [ + "zune-core 0.5.1", +] diff --git a/Cargo.toml b/Cargo.toml index c816dcc7ce..4da76766c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ openidconnect = { version = "4.0", default-features = false, features = [ ] } parse_link_header = "0.4" paste = "1.0" -pgp = { version = "0.16", default-features = false } +pgp = { version = "0.19", default-features = false } prost = "0.14" pulldown-cmark = "0.13" # match version used by sqlx diff --git a/crates/defguard_core/Cargo.toml b/crates/defguard_core/Cargo.toml index c6d2f82b7d..193eb80d18 100644 --- a/crates/defguard_core/Cargo.toml +++ b/crates/defguard_core/Cargo.toml @@ -94,7 +94,7 @@ reqwest = { version = "0.12", features = [ "rustls-tls", "stream", ], default-features = false } -serde_qs = "0.15" +serde_qs = "1.0" webauthn-authenticator-rs = { version = "0.5", features = ["softpasskey"] } [build-dependencies] diff --git a/crates/defguard_core/src/enrollment_management.rs b/crates/defguard_core/src/enrollment_management.rs index ef0ec3ca3b..90f96d8d57 100644 --- a/crates/defguard_core/src/enrollment_management.rs +++ b/crates/defguard_core/src/enrollment_management.rs @@ -1,18 +1,16 @@ use defguard_common::db::{Id, models::user::User}; -use defguard_mail::{Mail, templates}; +use defguard_mail::templates::{desktop_start_mail, new_account_mail}; use reqwest::Url; use sqlx::{PgConnection, PgExecutor}; use crate::db::models::enrollment::{ENROLLMENT_TOKEN_TYPE, Token, TokenError}; -static ENROLLMENT_START_MAIL_SUBJECT: &str = "Defguard user enrollment"; - /// Start user enrollment process /// This creates a new enrollment token valid for 24h /// and optionally sends enrollment email notification to user pub async fn start_user_enrollment( user: &mut User, - transaction: &mut PgConnection, + conn: &mut PgConnection, admin: &User, email: Option, token_timeout_seconds: u64, @@ -45,7 +43,7 @@ pub async fn start_user_enrollment( return Err(TokenError::UserDisabled); } - clear_unused_enrollment_tokens(user, &mut *transaction).await?; + clear_unused_enrollment_tokens(user, &mut *conn).await?; debug!("Create a new enrollment token for user {}.", user.username); let enrollment = Token::new( @@ -56,7 +54,7 @@ pub async fn start_user_enrollment( Some(ENROLLMENT_TOKEN_TYPE.to_string()), ); debug!("Saving a new enrollment token..."); - enrollment.save(&mut *transaction).await?; + enrollment.save(&mut *conn).await?; debug!( "Saved a new enrollment token with id {} for user {}.", enrollment.id, user.username @@ -65,7 +63,7 @@ pub async fn start_user_enrollment( // Mark the user with enrollment-pending flag. // https://github.com/DefGuard/client/issues/647 user.enrollment_pending = true; - user.save(&mut *transaction).await?; + user.save(&mut *conn).await?; if send_user_notification { if let Some(email) = email { @@ -73,27 +71,14 @@ pub async fn start_user_enrollment( "Sending an enrollment mail for user {} to {email}.", user.username ); - let base_message_context = enrollment - .get_welcome_message_context(&mut *transaction) - .await?; - let result = Mail::new( + let base_message_context = enrollment.get_welcome_message_context(&mut *conn).await?; + let result = new_account_mail( &email, - ENROLLMENT_START_MAIL_SUBJECT, - templates::enrollment_start_mail( - base_message_context, - enrollment_service_url, - &enrollment.id, - ) - .map_err(|err| { - debug!( - "Cannot send an email to the user {} due to the error {}.", - user.username, - err.to_string() - ); - TokenError::NotificationError(err.to_string()) - })?, + conn, + base_message_context, + enrollment_service_url, + &enrollment.id, ) - .send() .await; match result { Ok(()) => { @@ -122,7 +107,7 @@ pub async fn start_user_enrollment( /// and optionally sends email notification to user pub async fn start_desktop_configuration( user: &User, - transaction: &mut PgConnection, + conn: &mut PgConnection, admin: &User, email: Option, token_timeout_seconds: u64, @@ -150,7 +135,7 @@ pub async fn start_desktop_configuration( return Err(TokenError::UserDisabled); } - clear_unused_enrollment_tokens(user, &mut *transaction).await?; + clear_unused_enrollment_tokens(user, &mut *conn).await?; debug!("Cleared unused tokens for {}.", user.username); debug!( @@ -168,7 +153,7 @@ pub async fn start_desktop_configuration( desktop_configuration.device_id = Some(device_id); } debug!("Saving a new desktop configuration token..."); - desktop_configuration.save(&mut *transaction).await?; + desktop_configuration.save(&mut *conn).await?; debug!( "Saved a new desktop activation token with id {} for user {}.", desktop_configuration.id, user.username @@ -181,23 +166,22 @@ pub async fn start_desktop_configuration( user.username ); let base_message_context = desktop_configuration - .get_welcome_message_context(&mut *transaction) + .get_welcome_message_context(&mut *conn) .await?; - let _ = templates::desktop_start_mail( + let result = desktop_start_mail( &email, - &mut *transaction, + conn, base_message_context, &enrollment_service_url, &desktop_configuration.id, ) - .await - .map_err(|err| { + .await; + if let Err(err) = result { debug!( "Cannot send an email to the user {} due to the error {err}.", user.username, ); - TokenError::NotificationError(err.to_string()) - }); + } } } info!( diff --git a/crates/defguard_core/src/enterprise/license.rs b/crates/defguard_core/src/enterprise/license.rs index 2f0f8b67e9..dd20f61256 100644 --- a/crates/defguard_core/src/enterprise/license.rs +++ b/crates/defguard_core/src/enterprise/license.rs @@ -11,8 +11,8 @@ use defguard_common::{ }; use humantime::format_duration; use pgp::{ - composed::{Deserializable, SignedPublicKey, StandaloneSignature}, - types::{KeyDetails, PublicKeyTrait}, + composed::{Deserializable, DetachedSignature, SignedPublicKey}, + types::KeyDetails, }; use prost::Message; use sqlx::{PgPool, error::Error as SqlxError}; @@ -269,8 +269,8 @@ impl License { } fn verify_signature(data: &[u8], signature: &[u8]) -> Result<(), LicenseError> { - let sig = StandaloneSignature::from_bytes(signature) - .map_err(|_| LicenseError::InvalidSignature)?; + let sig = + DetachedSignature::from_bytes(signature).map_err(|_| LicenseError::InvalidSignature)?; let (public_key, _headers_public) = SignedPublicKey::from_string(PUBLIC_KEY).expect("Failed to parse the public key"); @@ -279,21 +279,21 @@ impl License { if public_key.public_subkeys.is_empty() { debug!( "Using the public key's primary key {:?} to verify the signature...", - public_key.key_id() + public_key.legacy_key_id() ); sig.verify(&public_key, data) .map_err(|_| LicenseError::SignatureMismatch) } else { - let signing_key = public_key - .public_subkeys - .into_iter() - .find(PublicKeyTrait::is_signing_key) - .ok_or(LicenseError::LicenseServerError( - "Failed to find a signing key in the provided public key".to_string(), - ))?; + let signing_key = + public_key + .public_subkeys + .first() + .ok_or(LicenseError::LicenseServerError( + "Failed to find a signing key in the provided public key".to_string(), + ))?; debug!( "Using the public key's subkey {:?} to verify the signature...", - signing_key.key_id() + signing_key.legacy_key_id() ); sig.verify(&signing_key, data) .map_err(|_| LicenseError::SignatureMismatch) diff --git a/crates/defguard_mail/Cargo.toml b/crates/defguard_mail/Cargo.toml index 75d5f4a5c5..9ada5ddf2e 100644 --- a/crates/defguard_mail/Cargo.toml +++ b/crates/defguard_mail/Cargo.toml @@ -23,7 +23,9 @@ tokio.workspace = true tracing.workspace = true humantime.workspace = true +image = "0.25" # match with qrforge mrml = "5.1" +qrforge = {version = "0.1", default-features = false, features = ["image"]} [dev-dependencies] claims.workspace = true diff --git a/crates/defguard_mail/assets/android_store.png b/crates/defguard_mail/assets/google_play.png similarity index 100% rename from crates/defguard_mail/assets/android_store.png rename to crates/defguard_mail/assets/google_play.png diff --git a/crates/defguard_mail/assets/new_account_1.png b/crates/defguard_mail/assets/new_account_1.png new file mode 100644 index 0000000000..49a9e19df1 Binary files /dev/null and b/crates/defguard_mail/assets/new_account_1.png differ diff --git a/crates/defguard_mail/assets/new_account_2.png b/crates/defguard_mail/assets/new_account_2.png new file mode 100644 index 0000000000..23c3aeabc2 Binary files /dev/null and b/crates/defguard_mail/assets/new_account_2.png differ diff --git a/crates/defguard_mail/src/lib.rs b/crates/defguard_mail/src/lib.rs index 152ad93664..7fe038abdb 100644 --- a/crates/defguard_mail/src/lib.rs +++ b/crates/defguard_mail/src/lib.rs @@ -11,6 +11,7 @@ pub use crate::mail::{Attachment, Mail}; pub mod mail; pub(crate) mod mail_context; +mod qr; pub mod templates; #[cfg(test)] mod tests; diff --git a/crates/defguard_mail/src/mail.rs b/crates/defguard_mail/src/mail.rs index 8cbc00fc10..de92c002c6 100644 --- a/crates/defguard_mail/src/mail.rs +++ b/crates/defguard_mail/src/mail.rs @@ -7,18 +7,54 @@ use lettre::{ transport::smtp::authentication::Credentials, }; use serde::Serialize; -use tera::Context; +use sqlx::PgConnection; +use tera::{Context, Tera, Value}; use thiserror::Error; use tracing::{debug, error, info, warn}; +use crate::{ + mail_context::MailContext, + qr::qr_png, + templates::{DEFAULT_LANG, TemplateError}, +}; + use super::SmtpSettings; +#[derive(Debug)] +pub struct Attachment { + filename: String, + content: Vec, +} + +impl Attachment { + /// Create new [`Attachement`]. + #[must_use] + pub fn new(filename: String, content: Vec) -> Self { + Self { filename, content } + } +} + +impl From for SinglePart { + fn from(attachment: Attachment) -> Self { + lettre::message::Attachment::new(attachment.filename) + .body(attachment.content, ContentType::TEXT_PLAIN) + } +} + const SMTP_TIMEOUT: Duration = Duration::from_secs(15); // Template images. static DEFGUARD_LOGO: &[u8] = include_bytes!("../assets/defguard.png"); static GITHUB_LOGO: &[u8] = include_bytes!("../assets/github.png"); static MASTODON_LOGO: &[u8] = include_bytes!("../assets/mastodon.png"); static X_LOGO: &[u8] = include_bytes!("../assets/x.png"); +// MFA code +static DATE_ICON: &[u8] = include_bytes!("../assets/date.png"); +static OTP_ICON: &[u8] = include_bytes!("../assets/otp.png"); +// New account +static NEW_ACCOUNT_1: &[u8] = include_bytes!("../assets/new_account_1.png"); +static NEW_ACCOUNT_2: &[u8] = include_bytes!("../assets/new_account_2.png"); +static GOOGLE_PLAY: &[u8] = include_bytes!("../assets/google_play.png"); +static APPLE: &[u8] = include_bytes!("../assets/apple.png"); #[derive(Debug, Error)] pub enum MailError { @@ -47,7 +83,8 @@ pub struct Mail { pub(crate) subject: String, content: String, context: Context, - attachments: Vec, + attachments: Vec, // text/plain + images: Vec<(String, Vec)>, // image/png } impl Mail { @@ -58,12 +95,21 @@ impl Mail { T: Into, S: Into, { + // Append images used in all templates. + let images = vec![ + (String::from("defguard"), Vec::from(DEFGUARD_LOGO)), + (String::from("github"), Vec::from(GITHUB_LOGO)), + (String::from("mastodon"), Vec::from(MASTODON_LOGO)), + (String::from("x"), Vec::from(X_LOGO)), + ]; + Self { to: to.into(), subject: subject.into(), content, context: Context::new(), attachments: Vec::new(), + images, } } @@ -100,35 +146,14 @@ impl Mail { self.attachments = attachments; self } -} - -#[derive(Debug)] -pub struct Attachment { - filename: String, - content: Vec, - content_type: ContentType, -} - -impl Attachment { - /// Create new [`Attachement`]. - #[must_use] - pub fn new(filename: String, content: Vec) -> Self { - Self { - filename, - content, - content_type: ContentType::TEXT_PLAIN, - } - } -} -impl From for SinglePart { - fn from(attachment: Attachment) -> Self { - lettre::message::Attachment::new(attachment.filename) - .body(attachment.content, attachment.content_type) + pub fn add_png_image(&mut self, name: S, bytes: &[u8]) + where + S: Into, + { + self.images.push((name.into(), Vec::from(bytes))); } -} -impl Mail { /// Converts Mail to lettre Message. /// Message structure should look like this: /// - multipart mixed @@ -148,25 +173,13 @@ impl Mail { let plain = SinglePart::plain("PLAIN IS NOT AVAILABLE AT THE MOMENT.".to_string()); let html = SinglePart::html(self.content); let image_png = "image/png".parse::().unwrap(); - let related = MultiPart::related() - .singlepart(html) - .singlepart( - lettre::message::Attachment::new_inline(String::from("defguard")) - .body(Body::new(Vec::from(DEFGUARD_LOGO)), image_png.clone()), - ) - .singlepart( - lettre::message::Attachment::new_inline(String::from("github")) - .body(Body::new(Vec::from(GITHUB_LOGO)), image_png.clone()), - ) - .singlepart( - lettre::message::Attachment::new_inline(String::from("mastodon")) - .body(Body::new(Vec::from(MASTODON_LOGO)), image_png.clone()), - ) - .singlepart( - lettre::message::Attachment::new_inline(String::from("x")) - .body(Body::new(Vec::from(X_LOGO)), image_png), + let mut related = MultiPart::related().singlepart(html); + for (name, bytes) in self.images { + related = related.singlepart( + lettre::message::Attachment::new_inline(name) + .body(Body::new(bytes), image_png.clone()), ); - + } let alternative = MultiPart::alternative() .singlepart(plain) .multipart(related); @@ -257,3 +270,144 @@ impl Mail { Ok(builder.build()) } } + +/// Email messages. +pub enum MailMessage { + /// Test email to check if SMTP configuration works correctly. + Test, + Welcome, + /// Information for Defguard support. + Support, + DesktopStart, + /// Information after starting an enrollment. + NewAccount, + NewDevice, + NewDeviceLogin, + NewDeviceOCIDLogin, + /// Gateway has disconnected. + GatewayDisconnect, + /// Gateway has reconnected. + GatewayReconnect, + MFAActivation, + MFAConfigured, + /// MFA code. + MFACode, + PasswordReset, + PasswordResetDone, +} + +impl MailMessage { + /// Email subject. + pub(crate) const fn subject(&self) -> &'static str { + match self { + Self::Test => "Test message", + Self::Welcome => "Welcome message after enrollment", + Self::Support => "Support data", + Self::DesktopStart => "Defguard: Desktop client configuration", + Self::NewAccount => "Defguard: User enrollment", + Self::NewDevice => "Defguard: new device added to your account", + Self::NewDeviceLogin => "New device logged in to your account", + Self::NewDeviceOCIDLogin => "New login to OCID application", + Self::GatewayDisconnect => "Gateway disconnected", + Self::GatewayReconnect => "Gateway reconnected", + Self::MFAActivation => "Multi-Factor Authentication activation", + Self::MFAConfigured => "Multi-Factor Authentication {method} has been activated", + Self::MFACode => "Defguard: Multi-Factor Authentication code for login", + Self::PasswordReset => "Password reset", + Self::PasswordResetDone => "Password reset success", + } + } + + pub(crate) const fn template_name(&self) -> &str { + match self { + Self::Test => "test", + Self::Welcome => "welcome", + Self::Support => "support", + Self::DesktopStart => "desktop-start", + Self::NewAccount => "new-account", + Self::NewDevice => "new-device", + Self::NewDeviceLogin => "new-device-loin", + Self::NewDeviceOCIDLogin => "new-device-login-ocid", + Self::GatewayDisconnect => "gateway-disconnect", + Self::GatewayReconnect => "gateway-reconnect", + Self::MFAActivation => "mfa-activation", + Self::MFAConfigured => "mfa-configure", + Self::MFACode => "mfa-code", + Self::PasswordReset => "password-reset", + Self::PasswordResetDone => "password-reset-done", + } + } + + pub(crate) const fn mjml_template(&self) -> &str { + match self { + // Self::Test => "", + // Self::Welcome => "", + // Self::Support => "", + Self::DesktopStart => include_str!("../templates/desktop-start.mjml"), + Self::NewAccount => include_str!("../templates/new-account.mjml"), + Self::NewDevice => include_str!("../templates/new-device.mjml"), + // Self::NewDeviceLogin => "", + // Self::NewDeviceOCIDLogin => "", + // Self::GatewayDisconnect => "", + // Self::GatewayReconnect => "", + // Self::MFAActivation => "", + // Self::MFAConfigured => "", + Self::MFACode => include_str!("../templates/mfa-code.mjml"), + // Self::PasswordReset => "", + // Self::PasswordResetDone => "", + _ => "", + } + } + + /// Fill `Context` from database. + pub(crate) async fn fill_context( + &self, + conn: &mut PgConnection, + context: &mut Context, + ) -> Result<(), sqlx::Error> { + let db_context = + MailContext::all_for_template(conn, self.template_name(), DEFAULT_LANG).await?; + for row in db_context { + context.insert(row.section, &row.text); + } + + Ok(()) + } + + /// Build `Mail`. + pub(crate) fn mail( + &self, + tera: &mut Tera, + context: &Context, + to: &str, + ) -> Result { + tera.add_raw_template(self.template_name(), self.mjml_template())?; + let processed = tera.render(self.template_name(), context)?; + let parsed = mrml::parse(processed)?; + let opts = mrml::prelude::render::RenderOptions::default(); + let html = parsed.element.render(&opts)?; + + let mut mail = Mail::new(to, self.subject(), html); + // Add PNG images. + match self { + Self::NewAccount => { + mail.add_png_image("new_account_1", NEW_ACCOUNT_1); + mail.add_png_image("new_account_2", NEW_ACCOUNT_2); + mail.add_png_image("google_play", GOOGLE_PLAY); + mail.add_png_image("apple", APPLE); + if let Some(Value::String(url)) = context.get("url") { + if let Ok(qr) = qr_png(url.as_bytes()) { + mail.add_png_image("qr", &qr); + } + } + } + Self::MFACode => { + mail.add_png_image("date", DATE_ICON); + mail.add_png_image("otp", OTP_ICON); + } + _ => (), + } + + Ok(mail) + } +} diff --git a/crates/defguard_mail/src/qr.rs b/crates/defguard_mail/src/qr.rs new file mode 100644 index 0000000000..1957f96b10 --- /dev/null +++ b/crates/defguard_mail/src/qr.rs @@ -0,0 +1,28 @@ +use std::io::Cursor; + +use image::ImageFormat; +use qrforge::{ErrorCorrection, Mode, QRCode, QRError, Version}; + +/// Construct QR with content bytes and return a buffer of PNG image. +pub(crate) fn qr_png(content: &[u8]) -> Result, QRError> { + let qr = QRCode::builder() + .add_segment(Some(Mode::Byte), content) + .error_correction(ErrorCorrection::M) + .version(Version::V(5)) + .build()?; + + let image_buffer = qr + .image_builder() + .set_width(200) + .set_height(200) + .set_border(4) + .build_image()?; + + let mut buffer = Cursor::new(Vec::new()); + + image_buffer + .write_to(&mut buffer, ImageFormat::Png) + .map_err(|_| QRError::new("image write error"))?; + + Ok(buffer.into_inner()) +} diff --git a/crates/defguard_mail/src/templates.rs b/crates/defguard_mail/src/templates.rs index 9dd5315a83..9b779376f7 100644 --- a/crates/defguard_mail/src/templates.rs +++ b/crates/defguard_mail/src/templates.rs @@ -20,29 +20,16 @@ use tera::{Context, Function, Tera}; use thiserror::Error; use tracing::debug; -use crate::{Mail, mail_context::MailContext}; +use crate::mail::MailMessage; -const DEFAULT_LANG: &str = "en_US"; +pub(crate) const DEFAULT_LANG: &str = "en_US"; static BASE_MJML: &str = include_str!("../templates/base.mjml"); static MACROS_MJML: &str = include_str!("../templates/macros.mjml"); -static DESKTOP_START_SUBJECT: &str = "Defguard desktop client configuration"; -static DESKTOP_START_MJML: &str = include_str!("../templates/desktop-start.mjml"); -// static DESKTOP_START_TEXT: &str = include_str!("../templates/desktop-start.text"); - -static NEW_DEVICE_SUBJECT: &str = "Defguard: new device added to your account"; -static NEW_DEVICE_MJML: &str = include_str!("../templates/new-device.mjml"); -// static NEW_DEVICE_TEXT: &str = include_str!("../templates/new-device.text"); - -static MFA_CODE_SUBJECT: &str = "Defguard: Multi-Factor Authentication code for login"; -static MFA_CODE_MJML: &str = include_str!("../templates/mfa-code.mjml"); -// static MFA_CODE_TEXT: &str = include_str!("../templates/mfa-code.text"); - static MAIL_BASE: &str = include_str!("../templates/base.tera"); static MAIL_MACROS: &str = include_str!("../templates/macros.tera"); -static MAIL_TEST: &str = include_str!("../templates/mail_test.mjml"); -static MAIL_ENROLLMENT_START: &str = include_str!("../templates/mail_enrollment_start.tera"); +static MAIL_TEST: &str = include_str!("../templates/test.mjml"); static MAIL_ENROLLMENT_WELCOME: &str = include_str!("../templates/mail_enrollment_welcome.tera"); static MAIL_ENROLLMENT_ADMIN_NOTIFICATION: &str = include_str!("../templates/mail_enrollment_admin_notification.tera"); @@ -67,6 +54,8 @@ pub enum TemplateError { #[error("Failed to generate email MFA code")] MfaError, #[error(transparent)] + DatabaseError(#[from] sqlx::Error), + #[error(transparent)] TemplateError(#[from] tera::Error), #[error(transparent)] UrlParseError(#[from] UrlParseError), @@ -202,36 +191,38 @@ pub fn test_mail(session: Option<&SessionContext>) -> Result Result { +) -> Result<(), TemplateError> { debug!("Render an enrollment start mail template for the user."); - let (mut tera, mut context) = get_base_tera(context, None, None, None)?; + let (mut tera, mut context) = get_base_tera_mjml(context, None, None, None)?; // add required context - context.insert("enrollment_url", &enrollment_service_url); context.insert("defguard_url", &Settings::url()?); + context.insert("url", &enrollment_service_url); context.insert("token", enrollment_token); // prepare enrollment service URL enrollment_service_url .query_pairs_mut() .append_pair("token", enrollment_token); - context.insert("link_url", &enrollment_service_url); - tera.add_raw_template("mail_enrollment_start", MAIL_ENROLLMENT_START)?; + let message = MailMessage::NewAccount; + message.fill_context(conn, &mut context).await?; + message.mail(&mut tera, &context, to)?.send_and_forget(); - let processed = tera.render("mail_enrollment_start", &context)?; - Ok(processed) + Ok(()) } // Mail with link to enrollment service. pub async fn desktop_start_mail( to: &str, - transaction: &mut PgConnection, + conn: &mut PgConnection, context: Context, enrollment_service_url: &Url, enrollment_token: &str, @@ -239,25 +230,12 @@ pub async fn desktop_start_mail( debug!("Render a mail template for desktop activation."); let (mut tera, mut context) = get_base_tera_mjml(context, None, None, None)?; - let template = "desktop-start"; - tera.add_raw_template(template, DESKTOP_START_MJML)?; - let db_context = MailContext::all_for_template(transaction, template, DEFAULT_LANG) - .await - .unwrap(); - for c in db_context { - context.insert(c.section, &c.text); - } - context.insert("url", &enrollment_service_url); context.insert("token", enrollment_token); - // TODO: Move to Mail once every message is converted to MJML. - let processed = tera.render(template, &context)?; - let parsed = mrml::parse(processed)?; - let opts = mrml::prelude::render::RenderOptions::default(); - let html = parsed.element.render(&opts)?; - - Mail::new(to, DESKTOP_START_SUBJECT, html).send_and_forget(); + let message = MailMessage::DesktopStart; + message.fill_context(conn, &mut context).await?; + message.mail(&mut tera, &context, to)?.send_and_forget(); Ok(()) } @@ -321,7 +299,7 @@ pub struct TemplateLocation { pub async fn new_device_added_mail( to: &str, - transaction: &mut PgConnection, + conn: &mut PgConnection, device_name: &str, public_key: &str, template_locations: &[TemplateLocation], @@ -335,22 +313,9 @@ pub async fn new_device_added_mail( context.insert("public_key", public_key); context.insert("locations", template_locations); - let template = "new-device"; - tera.add_raw_template(template, NEW_DEVICE_MJML)?; - let db_context = MailContext::all_for_template(transaction, template, DEFAULT_LANG) - .await - .unwrap(); - for c in db_context { - context.insert(c.section, &c.text); - } - - // TODO: Move to Mail once every message is converted to MJML. - let processed = tera.render(template, &context)?; - let parsed = mrml::parse(processed)?; - let opts = mrml::prelude::render::RenderOptions::default(); - let html = parsed.element.render(&opts)?; - - Mail::new(to, NEW_DEVICE_SUBJECT, html).send_and_forget(); + let message = MailMessage::NewDevice; + message.fill_context(conn, &mut context).await?; + message.mail(&mut tera, &context, to)?.send_and_forget(); Ok(()) } @@ -444,7 +409,7 @@ pub fn email_mfa_activation_mail( pub async fn mfa_code_mail( to: &str, - transaction: &mut PgConnection, + conn: &mut PgConnection, first_name: &str, code: &str, session: Option<&SessionContext>, @@ -462,22 +427,9 @@ pub async fn mfa_code_mail( &Utc::now().format(MAIL_DATETIME_FORMAT).to_string(), ); - let template = "mfa-code"; - tera.add_raw_template(template, MFA_CODE_MJML)?; - let db_context = MailContext::all_for_template(transaction, template, DEFAULT_LANG) - .await - .unwrap(); - for c in db_context { - context.insert(c.section, &c.text); - } - - // TODO: Move to Mail once every message is converted to MJML. - let processed = tera.render(template, &context)?; - let parsed = mrml::parse(processed)?; - let opts = mrml::prelude::render::RenderOptions::default(); - let html = parsed.element.render(&opts)?; - - Mail::new(to, MFA_CODE_SUBJECT, html).send_and_forget(); + let message = MailMessage::MFACode; + message.fill_context(conn, &mut context).await?; + message.mail(&mut tera, &context, to)?.send_and_forget(); Ok(()) } @@ -567,16 +519,16 @@ mod test { assert_ok!(test_mail(None)); } - #[sqlx::test] - async fn test_enrollment_start_mail(_: PgPoolOptions, options: PgConnectOptions) { - let pool = setup_pool(options).await; - init_config(&pool).await; - assert_ok!(enrollment_start_mail( - Context::new(), - Url::parse("http://localhost:8080").unwrap(), - "test_token" - )); - } + // #[sqlx::test] + // async fn test_enrollment_start_mail(_: PgPoolOptions, options: PgConnectOptions) { + // let pool = setup_pool(options).await; + // init_config(&pool).await; + // assert_ok!(enrollment_start_mail( + // Context::new(), + // Url::parse("http://localhost:8080").unwrap(), + // "test_token" + // )); + // } #[sqlx::test] async fn test_enrollment_welcome_mail(_: PgPoolOptions, options: PgConnectOptions) { diff --git a/crates/defguard_mail/src/tests.rs b/crates/defguard_mail/src/tests.rs index 1ae8d1affa..79f70b0391 100644 --- a/crates/defguard_mail/src/tests.rs +++ b/crates/defguard_mail/src/tests.rs @@ -19,7 +19,7 @@ use sqlx::{ use tera::Context; use super::templates::{ - TemplateLocation, desktop_start_mail, mfa_code_mail, new_device_added_mail, + TemplateLocation, desktop_start_mail, mfa_code_mail, new_account_mail, new_device_added_mail, }; /// Set SMTP settings from environment variables. @@ -120,3 +120,27 @@ fn send_mfa_code(_: PgPoolOptions, options: PgConnectOptions) { // Delay, so send_and_forget() can process the message. tokio::time::sleep(Duration::from_secs(2)).await; } + +#[ignore] +#[sqlx::test] +fn send_new_account(_: PgPoolOptions, options: PgConnectOptions) { + let pool = setup_pool(options).await; + set_smtp_settings(&pool).await; + + let mut transaction = pool.begin().await.unwrap(); + let url = Url::parse("http://localhost:8000").unwrap(); + let context = Context::new(); + let token = "zXc6N1ndXpWFeyBuogiFp1bD1UomAbZc"; + new_account_mail( + &env::var("SMTP_TO").unwrap(), + &mut transaction, + context, + url, + token, + ) + .await + .unwrap(); + + // Delay, so send_and_forget() can process the message. + tokio::time::sleep(Duration::from_secs(2)).await; +} diff --git a/crates/defguard_mail/templates/mail_enrollment_start.tera b/crates/defguard_mail/templates/mail_enrollment_start.tera deleted file mode 100644 index 289cbf9a6e..0000000000 --- a/crates/defguard_mail/templates/mail_enrollment_start.tera +++ /dev/null @@ -1,46 +0,0 @@ -{# Requires context -enrollment_url -> URL of the enrollment service -link_url -> URL of the enrollment service with the token query param included -defguard_url -> URL of defguard core Web UI -token -> enrollment token -#} -{% extends "base" %} -{% import "macros" as macros %} -{% block mail_content %} -{% set client_docs_url="https://docs.defguard.net/help/desktop-client" %} -{% set client_docs_link=macros::link(content=client_docs_url, href=client_docs_url) %} -{% set release_url="https://defguard.net/download/" %} -{% set release_link=macros::link(content=release_url, href=release_url) %} -{# intro #} -{% set section_content = [ -macros::paragraph(content="You're receiving this email because a new account has been created for you."), -macros::paragraph(content="In order to start the enrollment process please choose one of the following options:"), -] %} -{{ macros::text_section(content_array=section_content)}} -{# desktop client enrollment #} -{% set enrollment_link=macros::link(content=enrollment_url, href=enrollment_url) %} -{% set section_content = [ -macros::paragraph(content="1. Enrollment by desktop client"), -macros::paragraph(content="Download the official defguard desktop client for Windows, macOS or Linux: " ~ release_link), -macros::paragraph(content="After installation, please add a defguard instance by entering:"), -macros::paragraph(content="
  • Instance URL: " ~ enrollment_link ~ "
  • Enrollment token: " ~ token ~ "
"), -macros::paragraph(content="Please note that: the token is only valid for 24 hours after receiving this email. When the enrollment process starts user will have 10 minutes to complete the process."), -macros::paragraph(content="For more details go to the desktop client documentation: " ~ client_docs_link), -] %} -{{ macros::text_section(content_array=section_content)}} -{# web enrollment #} -{% set defguard_link=macros::link(content=defguard_url, href=defguard_url) %} -{% set section_content = [ -macros::paragraph(content="2. Enrollment via Web Browser"), -macros::paragraph(content="If you choose this option, you will be able to change your account details, set a password, and only configure a standard WireGuard client device - but not the official defguard desktop client. -Desktop client can still be activated later, by accessing your profile in defguard: " ~ defguard_link ~ "."), -macros::paragraph(content= "If you wish to do enrollment via Web, please copy & paste the following URL in your browser: "), -macros::link(content=link_url, href=link_url), -macros::paragraph(content="Please note that: this option is only valid for 24 hours after receiving this email. When the enrollment process starts user will have 10 minutes to complete the process."), -macros::paragraph(content="You can also click the buttons below to start the enrollment on website or within desktop client:"), -macros::button_link(href=link_url, text="Start enrollment"), -macros::spacer(height="20px"), -macros::button_link(href="defguard://addinstance?token=" ~ token ~ "&url=" ~ enrollment_url, text="Enroll with desktop client"), -] %} -{{ macros::text_section(content_array=section_content)}} -{% endblock %} diff --git a/crates/defguard_mail/templates/new-account.mjml b/crates/defguard_mail/templates/new-account.mjml new file mode 100644 index 0000000000..17f81e094d --- /dev/null +++ b/crates/defguard_mail/templates/new-account.mjml @@ -0,0 +1,115 @@ +{% import "macros.mjml" as macros %} +{% extends "base.mjml" %} +{% block content %} + +{{ macros::email_header() }} + + + + + + + + + + + + Desktop client + + + {{ download }} + + {{ macros::action_link(text="https://defguard.net/download/", href="https://defguard.net/download/", mj_class="s-3xl") }} + + {{ after_install }} + + + + + {{ label_url }} + + + {{ url }} + + + + + {{ label_token }} + + + {{ token }} + + + + + {{ token_info }} + + + {{ label_enroll }} + + + + + + + + + + + + + + + + + + + {{ label_mobile }} + + + {{ scan_qr }} + + + + {{ mobile_install }} + + + + + + + +
+ Android store + + + {{ download_google }} + +
+
+ + + + + + + + + +
+ + + + {{ download_apple }} + +
+
+
+
+
+
+ + + + +{% endblock content %} diff --git a/crates/defguard_mail/templates/mail_test.mjml b/crates/defguard_mail/templates/test.mjml similarity index 100% rename from crates/defguard_mail/templates/mail_test.mjml rename to crates/defguard_mail/templates/test.mjml diff --git a/deny.toml b/deny.toml index 14216c9e9e..ae7303ded6 100644 --- a/deny.toml +++ b/deny.toml @@ -91,6 +91,7 @@ allow = [ "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "MPL-2.0", + "BSD-2-Clause", "BSD-3-Clause", "Unicode-3.0", "Zlib", @@ -100,6 +101,7 @@ allow = [ "CC0-1.0", "OpenSSL", "CDLA-Permissive-2.0", + "NCSA", ] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the diff --git a/migrations/20260209083940_[2.0.0]_mjml.up.sql b/migrations/20260209083940_[2.0.0]_mjml.up.sql index ca8b9b5053..9242eaf940 100644 --- a/migrations/20260209083940_[2.0.0]_mjml.up.sql +++ b/migrations/20260209083940_[2.0.0]_mjml.up.sql @@ -6,13 +6,26 @@ CREATE TABLE mail_context ( CONSTRAINT template_section_language UNIQUE (template, section, language_tag) ); INSERT INTO mail_context (template, section, language_tag, text) VALUES - ('desktop-start', 'title', 'en_US', 'You are receiving this email to configure a new desktop client.'), + ('desktop-start', 'title', 'en_US', 'You''re receiving this email to configure a new desktop client.'), ('desktop-start', 'subtitle', 'en_US', 'Please paste this URL and token in your desktop client:'), ('desktop-start', 'label_url', 'en_US', 'URL'), ('desktop-start', 'label_token', 'en_US', 'Token'), ('desktop-start', 'configure', 'en_US', 'Configure your desktop client'), ('desktop-start', 'click', 'en_US', 'Click the button or use link below'), - ('new-device', 'title', 'en_US', 'A new device has been add to your account:'), + ('new-account', 'title', 'en_US', 'New account has been created for you.'), + ('new-account', 'subtitle', 'en_US', 'To start the enrollment process please use credentials below.'), + ('new-account', 'download', 'en_US', 'Download the official Defguard desktop client for your system.'), + ('new-account', 'after_install', 'en_US', 'After installation, please add a Defguard instance by entering:'), + ('new-account', 'label_url', 'en_US', 'URL'), + ('new-account', 'label_token', 'en_US', 'Token'), + ('new-account', 'token_info', 'en_US', 'The token is valid for 24 hours. Once the enrollment process starts, you have 10 minutes to complete it.'), + ('new-account', 'label_enroll', 'en_US', 'Enroll with desktop client'), + ('new-account', 'label_mobile', 'en_US', 'Mobile application'), + ('new-account', 'scan_qr', 'en_US', 'Scan QR code bellow to activate Defguard mobile application.'), + ('new-account', 'mobile_install', 'en_US', 'If you haven''t installed the mobile app, click one of the buttons bellow.'), + ('new-account', 'download_google', 'en_US', 'Download from Google Play'), + ('new-account', 'download_apple', 'en_US', 'Download from Apple Store'), + ('new-device', 'title', 'en_US', 'A new device has been added to your account:'), ('new-device', 'label_device', 'en_US', 'Device name'), ('new-device', 'label_pubkey', 'en_US', 'Public key'), ('mfa-code', 'title', 'en_US', 'Hello,'),