diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f08e917..14f06c5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -65,3 +65,11 @@ updates: groups: compose: patterns: ["*"] + ignore: + # The test and development stacks run the major versions production runs + # (docs/deploy/database/deployment.md): PostgreSQL 17 and Redis 7. Move + # them together with production, never ahead of it. + - dependency-name: postgres + update-types: ["version-update:semver-major"] + - dependency-name: redis + update-types: ["version-update:semver-major"] diff --git a/CHANGELOG.md b/CHANGELOG.md index b030b94..0b3bdee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ as described in the [versioning policy](docs/dev/guides/versioning.md). ## [Unreleased] +### Changed + +- Dependencies: `argon2` 0.6, `jsonwebtoken` 11, `totp-rs` 6, `async-nats` 0.50, + `base64` 0.23 and `sha1` 0.11, plus minor and patch updates. Password hashes + and TOTP secrets stored by earlier releases remain valid, pinned by tests. +- Images: Rust 1.98 for the build, Prometheus 3.14, Alertmanager 0.34, blackbox + exporter 0.28, NATS exporter 0.20, Mailpit 1.31. The test and development + stacks stay on PostgreSQL 17 and Redis 7, the versions production runs. + ## [2.0.0] - 2026-09-16 The release after 1.1.3, and the first under the diff --git a/Cargo.lock b/Cargo.lock index 83842d1..6405627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,16 +25,16 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" +checksum = "7f2b8006a0c83f52b62ba44a97b58bf76fe2f70a329e588f67f89691d93d498f" dependencies = [ "aead", "aes", "cipher", "ctr", + "ctutils", "ghash", - "subtle", ] [[package]] @@ -89,9 +89,9 @@ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arcstr" @@ -101,23 +101,34 @@ checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" [[package]] name = "argon2" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" dependencies = [ "base64ct", "blake2", - "cpufeatures 0.2.17", + "cpufeatures 0.3.0", "password-hash", ] +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-nats" -version = "0.49.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad3cd6df81292728e2a8cb1f1dcb4d7e7a1ab59b80c14fbbcba2baf9d5cf86a" +checksum = "d83a251fa1a4c9d0fe6e816b7acd60549e473e08d14f27a1d992c2675abff05f" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-util", "memchr", @@ -135,7 +146,7 @@ dependencies = [ "serde_json", "serde_nanos", "serde_repr", - "thiserror 2.0.18", + "thiserror 2.0.20", "time", "tokio", "tokio-rustls", @@ -149,13 +160,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -184,7 +195,7 @@ dependencies = [ "async-trait", "axum", "axum-prometheus", - "base64", + "base64 0.23.1", "ciborium", "criterion", "deadpool", @@ -210,17 +221,17 @@ dependencies = [ "reqwest", "serde", "serde_json", - "sha1", + "sha1 0.11.0", "sha2 0.11.0", "sqlx", "tera", "testkit", - "thiserror 2.0.18", + "thiserror 2.0.20", "time", "tokio", "totp-rs", "tower", - "tower-http 0.7.0", + "tower-http 0.7.1", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -241,7 +252,7 @@ dependencies = [ "serde_json", "sqlx", "testkit", - "thiserror 2.0.18", + "thiserror 2.0.20", "tokio", "tower", "uuid", @@ -331,9 +342,9 @@ dependencies = [ [[package]] name = "axum-prometheus" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd44522c72edf6a45dfdd73ea201937bdaa8610cdf473151f694900837ab3879" +checksum = "27e29a21d8a876465c009f1a69883fdfdf428a3ea42719a0c96021076c34cdf0" dependencies = [ "axum", "bytes", @@ -346,7 +357,7 @@ dependencies = [ "pin-project-lite", "tokio", "tower", - "tower-http 0.6.8", + "tower-http 0.7.1", ] [[package]] @@ -367,6 +378,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" @@ -399,11 +416,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" dependencies = [ - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -460,9 +477,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" dependencies = [ "serde", ] @@ -594,9 +611,9 @@ checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "combine" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" dependencies = [ "bytes", "futures-core", @@ -629,9 +646,9 @@ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "core-foundation" @@ -848,20 +865,19 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deadpool" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883466cb8db62725aee5f4a6011e8a5d42912b42632df32aad57fc91127c6e04" +checksum = "3e98a7e119cd347f4201e1159b19831029e203e2d8b790547708e8157b4acf1e" dependencies = [ "deadpool-runtime", - "num_cpus", "tokio", ] [[package]] name = "deadpool-redis" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafa30c49dafe086d10116074e422ad7fc1c3cf554697e744a3ab112599ebd09" +checksum = "243852fff546d8c5aac6ceaac7587f7137915b102c3fbec6256a64ce52452bd2" dependencies = [ "deadpool", "redis", @@ -1017,7 +1033,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6" dependencies = [ - "base64", + "base64 0.22.1", "memchr", ] @@ -1068,6 +1084,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "evmap" version = "11.0.0" @@ -1185,9 +1211,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1200,9 +1226,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1210,15 +1236,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1238,38 +1264,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -1447,12 +1473,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -1508,9 +1528,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -1518,9 +1538,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -1604,7 +1624,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -1887,12 +1907,12 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.4.0" +version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +checksum = "e75fe14a82d81e5f5af639997db37d8b96045938a7ac6ab18cdbe1c7467e05e1" dependencies = [ "aws-lc-rs", - "base64", + "base64 0.22.1", "getrandom 0.2.17", "js-sys", "pem", @@ -1931,12 +1951,12 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da65617f6cb926332d039cb578aad56178da86e128db6a1b09f4c94fa5b3349" +checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae" dependencies = [ "async-trait", - "base64", + "base64 0.23.1", "email-encoding", "email_address", "fastrand", @@ -2084,13 +2104,13 @@ version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108" dependencies = [ - "base64", + "base64 0.22.1", "evmap", "indexmap", "metrics", "metrics-util", "quanta", - "thiserror 2.0.18", + "thiserror 2.0.20", ] [[package]] @@ -2277,16 +2297,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -2315,7 +2325,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.18", + "thiserror 2.0.20", ] [[package]] @@ -2342,7 +2352,7 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "thiserror 2.0.18", + "thiserror 2.0.20", ] [[package]] @@ -2369,7 +2379,7 @@ dependencies = [ "percent-encoding", "portable-atomic", "rand 0.9.4", - "thiserror 2.0.18", + "thiserror 2.0.20", "tokio", "tokio-stream", ] @@ -2433,13 +2443,12 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "getrandom 0.4.2", + "phc", ] [[package]] @@ -2448,7 +2457,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -2467,6 +2476,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", + "getrandom 0.4.2", +] + [[package]] name = "pin-project" version = "1.1.11" @@ -2706,7 +2726,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.18", + "thiserror 2.0.20", "tokio", "tracing", "web-time", @@ -2729,7 +2749,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror 2.0.20", "tinyvec", "tracing", "web-time", @@ -2919,11 +2939,12 @@ dependencies = [ [[package]] name = "redis" -version = "1.0.5" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b36964393906eb775b89b25b05b7b95685b8dd14062f1663a31ff93e75c452e5" +checksum = "2acbc41a996f7652b2ddd9dfd98cc4ff602cfd742ae35382f07f608405ab50ed" dependencies = [ "arcstr", + "async-lock", "bytes", "cfg-if", "combine", @@ -3026,11 +3047,11 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64", + "base64 0.23.1", "bytes", "futures-core", "http", @@ -3311,9 +3332,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -3321,29 +3342,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -3429,6 +3450,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -3512,7 +3544,7 @@ checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.18", + "thiserror 2.0.20", "time", ] @@ -3585,7 +3617,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "crc", "crossbeam-queue", @@ -3608,7 +3640,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.18", + "thiserror 2.0.20", "time", "tokio", "tokio-stream", @@ -3663,7 +3695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "bytes", @@ -3688,12 +3720,12 @@ dependencies = [ "rand 0.8.6", "rsa", "serde", - "sha1", + "sha1 0.10.6", "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.20", "time", "tracing", "uuid", @@ -3707,7 +3739,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "crc", @@ -3733,7 +3765,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.20", "time", "tracing", "uuid", @@ -3759,7 +3791,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.18", + "thiserror 2.0.20", "time", "tracing", "url", @@ -3867,9 +3899,9 @@ dependencies = [ [[package]] name = "tera" -version = "2.0.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ea62bd58771b570262e11ffa274fa9eda9986bd886e6e3a1f7f42afef8024c" +checksum = "61e0cadeeb54426080b4c5395d59769ce8293f542cc25511c167c742b2175809" dependencies = [ "globset", "serde", @@ -3883,7 +3915,7 @@ dependencies = [ "anyhow", "auth-api", "axum", - "base64", + "base64 0.23.1", "ciborium", "deadpool-redis", "dotenvy", @@ -3915,11 +3947,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.20", ] [[package]] @@ -3935,13 +3967,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -3955,9 +3987,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.53" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "num-conv", @@ -3975,9 +4007,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -4020,9 +4052,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -4086,7 +4118,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f591660438b3038dd04d16c938271c79e7e06260ad2ea2885a4861bfb238605d" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "futures-sink", @@ -4103,16 +4135,18 @@ dependencies = [ [[package]] name = "totp-rs" -version = "5.7.2" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e69a15e21b2ff22c415446983978bded3244195f17d59cb113551c1e806f91" +checksum = "49d0f9b7aa5def86d3a8d8ba926fe8c1990924dd715d2b5fc54a4aa3788c11c1" dependencies = [ "base32", "constant_time_eq", - "hmac 0.12.1", - "rand 0.9.4", - "sha1", - "sha2 0.10.9", + "hmac 0.13.0", + "rand 0.10.2", + "serde", + "sha1 0.11.0", + "sha2 0.11.0", + "zeroize", ] [[package]] @@ -4151,9 +4185,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233" +checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" dependencies = [ "bitflags", "bytes", @@ -4407,9 +4441,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.23.4" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -5160,9 +5194,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index fe55397..a681e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,13 @@ exclude = ["fuzz"] [dependencies] aes-gcm = "0.11.0" anyhow = "1.0.102" -argon2 = "0.5.3" -async-nats = "0.49" +argon2 = "0.6.0" +async-nats = "0.50" axum = "0.8.8" axum-prometheus = "0.10" metrics = "0.24" async-trait = "0.1" -base64 = "0.22.1" +base64 = "0.23.1" deadpool = { version = "0.13", default-features = false, features = ["managed", "rt_tokio_1"] } deadpool-redis = { version = "0.23.0", features = ["rt_tokio_1", "script"] } dotenvy = "0.15.7" @@ -28,7 +28,7 @@ ipnetwork = "0.20" # aws_lc_rs backend (already in the tree via rustls): the rust_crypto backend # pulls in the `rsa` crate, which carries the unfixed RUSTSEC-2023-0071 # (Marvin) advisory - RSA is never used here, we only sign/verify ES256. -jsonwebtoken = { version = "10", features = ["aws_lc_rs"] } +jsonwebtoken = { version = "11", features = ["aws_lc_rs"] } lettre = { version = "0.11.19", default-features = false, features = ["tokio1-rustls-tls", "smtp-transport", "builder", "hostname", "pool"] } opentelemetry = { version = "0.32", default-features = false, features = ["trace"] } opentelemetry-http = { version = "0.32", default-features = false } @@ -39,7 +39,7 @@ rand = "0.10.0" rand_core = { version = "0.6", features = ["getrandom"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" -sha1 = "0.10.6" +sha1 = "0.11.0" ciborium = "0.2.2" form_urlencoded = "1.2" hmac = "0.13.0" @@ -49,7 +49,7 @@ tera = { version = "2.0", features = ["glob_fs"] } thiserror = "2.0.18" time = { version = "0.3.47", features = ["serde"] } tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "net", "time", "signal"] } -totp-rs = { version = "5.7.1", features = ["gen_secret"] } +totp-rs = { version = "6.0.0", features = ["gen_secret"] } tracing = "0.1.44" tracing-opentelemetry = { version = "0.33", default-features = false } tracing-subscriber = { version = "0.3.23", features = ["env-filter", "fmt", "json"] } diff --git a/Dockerfile b/Dockerfile index 43b082f..d1d9758 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ # ============================================================================= # Stage 1: Chef - install cargo-chef # ============================================================================= -FROM rust:1.96-slim-bookworm@sha256:e18a79fc84dfcfc3ab5ba72290398a644c135c97eaa881447fddc354ee4701a3 AS chef +FROM rust:1.98-slim-bookworm@sha256:ebd900bae66fd508b466cef82d64a83a5fb34682e4c8b2797a42908bddc95a57 AS chef # hadolint ignore=DL3008 RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/Dockerfile.dev b/Dockerfile.dev index 2f1e8a7..88e7bda 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -4,7 +4,7 @@ # ============================================================================= # Stage 1: Chef - install cargo-chef and sqlx-cli # ============================================================================= -FROM rust:1.96-slim-bookworm@sha256:e18a79fc84dfcfc3ab5ba72290398a644c135c97eaa881447fddc354ee4701a3 AS chef +FROM rust:1.98-slim-bookworm@sha256:ebd900bae66fd508b466cef82d64a83a5fb34682e4c8b2797a42908bddc95a57 AS chef # hadolint ignore=DL3008 RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/benches/core_benches.rs b/benches/core_benches.rs index 49b4607..1c00e3f 100644 --- a/benches/core_benches.rs +++ b/benches/core_benches.rs @@ -7,7 +7,6 @@ use auth_api::{ }; use criterion::{Criterion, SamplingMode, criterion_group, criterion_main}; use time::OffsetDateTime; -use totp_rs::{Algorithm, Secret, TOTP}; use uuid::Uuid; fn jwt_benches(c: &mut Criterion) { @@ -96,10 +95,6 @@ fn totp_benches(c: &mut Criterion) { let encrypted = keyring .encrypt(&secret) .expect("failed to encrypt benchmark secret"); - let secret_bytes = Secret::Encoded(secret.clone()) - .to_bytes() - .expect("valid secret bytes"); - let totp_ctx = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret_bytes).expect("valid totp"); group.bench_function("generate_secret", |b| b.iter(totp::generate_secret)); group.bench_function("build_qr_uri", |b| { @@ -113,7 +108,7 @@ fn totp_benches(c: &mut Criterion) { }); group.bench_function("generate_and_verify_code", |b| { b.iter(|| { - let code = totp_ctx.generate_current().expect("code"); + let code = totp::current_code(&secret).expect("code"); totp::verify_code( black_box(&encrypted), black_box(&code), diff --git a/crates/testkit/Cargo.toml b/crates/testkit/Cargo.toml index 0db1e74..bbc9c3c 100644 --- a/crates/testkit/Cargo.toml +++ b/crates/testkit/Cargo.toml @@ -10,12 +10,12 @@ description = "Shared harness of the auth-api test suites: test app, databases, anyhow = "1.0.102" auth-api = { path = "../.." } axum = "0.8.8" -base64 = "0.22.1" +base64 = "0.23.1" ciborium = "0.2.2" deadpool-redis = "0.23.0" dotenvy = "0.15.7" jsonschema = { version = "0.56.0", default-features = false } -jsonwebtoken = { version = "10", features = ["aws_lc_rs"] } +jsonwebtoken = { version = "11", features = ["aws_lc_rs"] } p256 = { version = "0.13", features = ["ecdsa", "pem"] } rand_core = { version = "0.6", features = ["getrandom"] } lettre = { version = "0.11.19", default-features = false, features = ["builder"] } diff --git a/crates/verifier/Cargo.toml b/crates/verifier/Cargo.toml index 34e7261..a4c29cb 100644 --- a/crates/verifier/Cargo.toml +++ b/crates/verifier/Cargo.toml @@ -14,7 +14,7 @@ axum = ["dep:axum"] [dependencies] axum = { version = "0.8.8", optional = true, default-features = false } -jsonwebtoken = { version = "10", features = ["aws_lc_rs"] } +jsonwebtoken = { version = "11", features = ["aws_lc_rs"] } reqwest = { version = "0.13.2", default-features = false, features = ["json", "rustls", "form"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" diff --git a/deploy/monitoring/docker-compose.monitoring.yml b/deploy/monitoring/docker-compose.monitoring.yml index 2cee873..60b2b65 100644 --- a/deploy/monitoring/docker-compose.monitoring.yml +++ b/deploy/monitoring/docker-compose.monitoring.yml @@ -9,7 +9,7 @@ services: prometheus: - image: prom/prometheus:v3.5.0 + image: prom/prometheus:v3.14.0 restart: unless-stopped command: - --config.file=/etc/prometheus/prometheus.yml @@ -32,7 +32,7 @@ services: limits: { cpus: "1.0", memory: 1G } alertmanager: - image: prom/alertmanager:v0.28.1 + image: prom/alertmanager:v0.34.1 restart: unless-stopped command: - --config.file=/etc/alertmanager/alertmanager.yml @@ -52,7 +52,7 @@ services: limits: { cpus: "0.25", memory: 128M } blackbox: - image: prom/blackbox-exporter:v0.27.0 + image: prom/blackbox-exporter:v0.28.0 restart: unless-stopped command: - --config.file=/etc/blackbox/blackbox.yml diff --git a/docker-compose.api.yml b/docker-compose.api.yml index db6646f..43d1fa9 100644 --- a/docker-compose.api.yml +++ b/docker-compose.api.yml @@ -95,7 +95,7 @@ services: # Domain-event broker. No host port: only the API instances reach it, over the # compose network, and the token is required on top of that isolation. nats: - image: nats:2-alpine@sha256:ad7a43eb7e3337c3c38ce5d784d1461791f95f730f252d2b25eee699752a0ca3 + image: nats:2-alpine@sha256:ac8f88a6494bffc2c2a5289a0ca61cb28a9145c11ba5677cf24265d07f46d8d4 restart: unless-stopped # Limits and JetStream live in nats.conf; the token in a file mounted as a # secret, so it appears neither in the process arguments nor in @@ -136,7 +136,7 @@ services: # NATS metrics for the monitoring host: connections, JetStream storage. nats-exporter: - image: natsio/prometheus-nats-exporter:0.17.3@sha256:26c826662ac8424597cc9bdf89ea5b606eb66e3c11db9b1215c27d2076bbb01b + image: natsio/prometheus-nats-exporter:0.20.2@sha256:c623b608e148e31e1c1c878673a197f1828e58ce90de4f01d22f1baa84c8fee9 restart: unless-stopped command: ["-varz", "-jsz=all", "http://nats:8222"] ports: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 0e2dfc5..c920637 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -57,7 +57,7 @@ services: - cache-net mailpit: - image: axllent/mailpit:v1.21@sha256:81370195cd4a0eab9604d17c2617a7525b0486f9365555253b6c5376c6350f1a + image: axllent/mailpit:v1.31@sha256:98b916bd3c8d61f7633a52d3ea2f58d00620cb01ca57ab59edde68c347a95365 ports: - "127.0.0.1:1025:1025" # SMTP - "127.0.0.1:8025:8025" # Web UI @@ -66,7 +66,7 @@ services: - mail-net nats: - image: nats:2-alpine@sha256:ad7a43eb7e3337c3c38ce5d784d1461791f95f730f252d2b25eee699752a0ca3 + image: nats:2-alpine@sha256:ac8f88a6494bffc2c2a5289a0ca61cb28a9145c11ba5677cf24265d07f46d8d4 # JetStream stores the user events the outbox relay publishes. Overriding # the command drops the image's config file, # so monitoring (probed by the healthcheck) is re-enabled explicitly. diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 403479b..c4df47a 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -32,7 +32,7 @@ services: - test-cache-net mailpit: - image: axllent/mailpit:v1.21@sha256:81370195cd4a0eab9604d17c2617a7525b0486f9365555253b6c5376c6350f1a + image: axllent/mailpit:v1.31@sha256:98b916bd3c8d61f7633a52d3ea2f58d00620cb01ca57ab59edde68c347a95365 ports: - "127.0.0.1:1026:1025" - "127.0.0.1:8026:8025" @@ -45,7 +45,7 @@ services: - test-mail-net nats: - image: nats:2-alpine@sha256:ad7a43eb7e3337c3c38ce5d784d1461791f95f730f252d2b25eee699752a0ca3 + image: nats:2-alpine@sha256:ac8f88a6494bffc2c2a5289a0ca61cb28a9145c11ba5677cf24265d07f46d8d4 # JetStream stores the user events the outbox relay publishes. Overriding # the command drops the image's config file, # so monitoring (probed by the healthcheck) is re-enabled explicitly. diff --git a/docs/deploy/guides/monitoring.md b/docs/deploy/guides/monitoring.md index c6a07b2..d411744 100644 --- a/docs/deploy/guides/monitoring.md +++ b/docs/deploy/guides/monitoring.md @@ -161,7 +161,7 @@ Every infrastructure alert has a scenario in `rules/infrastructure.test.yml`: ```bash docker run --rm --entrypoint promtool -w /m/rules -v "$PWD/deploy/monitoring:/m:ro" \ - prom/prometheus:v3.5.0 test rules infrastructure.test.yml + prom/prometheus:v3.14.0 test rules infrastructure.test.yml ``` `Watchdog` always fires. Point its receiver at a dead man's switch service diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 984049e..2caa61f 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -72,13 +72,13 @@ checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" [[package]] name = "argon2" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +checksum = "134c52ddac6d63c576bef8168db10c83c49c26444ecbc68060fef078925a901c" dependencies = [ "base64ct", "blake2", - "cpufeatures 0.2.17", + "cpufeatures 0.3.1", "password-hash", ] @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "async-nats" -version = "0.49.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad3cd6df81292728e2a8cb1f1dcb4d7e7a1ab59b80c14fbbcba2baf9d5cf86a" +checksum = "d83a251fa1a4c9d0fe6e816b7acd60549e473e08d14f27a1d992c2675abff05f" dependencies = [ "base64 0.22.1", "bytes", @@ -157,29 +157,39 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "auth-api" -version = "0.1.0" +version = "2.0.0" dependencies = [ "aes-gcm", "anyhow", "argon2", "async-nats", + "async-trait", "axum", "axum-prometheus", - "base64 0.22.1", + "base64 0.23.1", + "ciborium", "deadpool", "deadpool-redis", "dotenvy", "email_address", + "form_urlencoded", + "hmac 0.13.0", "ipnetwork", "jsonwebtoken", "lettre", "metrics", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-otlp", + "opentelemetry_sdk", "p256", + "percent-encoding", "rand 0.10.2", "rand_core 0.6.4", "reqwest", "serde", "serde_json", + "sha1 0.11.0", "sha2 0.11.0", "sqlx", "tera", @@ -189,6 +199,7 @@ dependencies = [ "totp-rs", "tower-http 0.7.1", "tracing", + "tracing-opentelemetry", "tracing-subscriber", "utoipa", "uuid", @@ -345,11 +356,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "5b5d4d889834ee8ecfc0f8426ad30faf7cdcb10f741a8e6d7224d95325479f6f" dependencies = [ - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -436,6 +447,33 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.5.2" @@ -490,9 +528,9 @@ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "core-foundation" @@ -573,6 +611,12 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -726,6 +770,7 @@ dependencies = [ "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -1006,6 +1051,17 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "futures-sink" version = "0.3.34" @@ -1026,6 +1082,7 @@ checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1131,6 +1188,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbag" version = "0.1.13" @@ -1190,7 +1258,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", ] [[package]] @@ -1202,6 +1270,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "home" version = "0.5.12" @@ -1475,6 +1552,15 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1553,9 +1639,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "10.4.0" +version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +checksum = "e75fe14a82d81e5f5af639997db37d8b96045938a7ac6ab18cdbe1c7467e05e1" dependencies = [ "aws-lc-rs", "base64 0.22.1", @@ -1906,6 +1992,75 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "opentelemetry" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry-http" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" +dependencies = [ + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "thiserror", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "portable-atomic", + "rand 0.9.5", + "thiserror", + "tokio", + "tokio-stream", +] + [[package]] name = "p256" version = "0.13.2" @@ -1949,13 +2104,12 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "getrandom 0.4.3", + "phc", ] [[package]] @@ -1983,6 +2137,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", + "getrandom 0.4.3", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -2101,6 +2266,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "quanta" version = "0.12.6" @@ -2427,7 +2615,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] @@ -2763,6 +2951,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -3028,7 +3227,7 @@ dependencies = [ "generic-array", "hex", "hkdf", - "hmac", + "hmac 0.12.1", "itoa", "log", "md-5", @@ -3038,7 +3237,7 @@ dependencies = [ "rand 0.8.8", "rsa", "serde", - "sha1", + "sha1 0.10.7", "sha2 0.10.9", "smallvec", "sqlx-core", @@ -3068,7 +3267,7 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac", + "hmac 0.12.1", "home", "ipnetwork", "itoa", @@ -3352,16 +3551,18 @@ dependencies = [ [[package]] name = "totp-rs" -version = "5.7.2" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e69a15e21b2ff22c415446983978bded3244195f17d59cb113551c1e806f91" +checksum = "49d0f9b7aa5def86d3a8d8ba926fe8c1990924dd715d2b5fc54a4aa3788c11c1" dependencies = [ "base32", "constant_time_eq", - "hmac", - "rand 0.9.5", - "sha1", - "sha2 0.10.9", + "hmac 0.13.0", + "rand 0.10.2", + "serde", + "sha1 0.11.0", + "sha2 0.11.0", + "zeroize", ] [[package]] @@ -3471,6 +3672,20 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" +dependencies = [ + "js-sys", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-serde" version = "0.2.0" @@ -3631,6 +3846,7 @@ dependencies = [ "getrandom 0.4.3", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] diff --git a/src/bin/bench_http.rs b/src/bin/bench_http.rs index 08e7047..f0c13c5 100644 --- a/src/bin/bench_http.rs +++ b/src/bin/bench_http.rs @@ -16,7 +16,6 @@ use deadpool_redis::redis::AsyncCommands; use serde::Serialize; use serde_json::{Value, json}; use sqlx::PgPool; -use totp_rs::{Algorithm, Secret, TOTP}; use uuid::Uuid; use auth_api::{ @@ -2412,12 +2411,7 @@ async fn clear_totp_reuse_key(state: &AppState, user_id: Uuid, code: &str) { } fn current_totp_code(base32_secret: &str) -> Result { - let secret_bytes = Secret::Encoded(base32_secret.to_string()) - .to_bytes() - .context("invalid base32 TOTP secret")?; - let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret_bytes) - .context("failed to build TOTP generator")?; - totp.generate_current() + auth_api::utils::totp::current_code(base32_secret) .context("failed to generate current TOTP code") } diff --git a/src/utils/password.rs b/src/utils/password.rs index 21b32e6..ea6ac12 100644 --- a/src/utils/password.rs +++ b/src/utils/password.rs @@ -8,7 +8,7 @@ use std::sync::OnceLock; use argon2::{ Algorithm, Argon2, Params, Version, - password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng}, + password_hash::{PasswordHasher, PasswordVerifier, phc::PasswordHash}, }; use tokio::sync::Semaphore; @@ -21,7 +21,7 @@ pub enum PasswordError { #[error("hashing failed: {0}")] Hash(argon2::password_hash::Error), #[error("hash string is malformed: {0}")] - Parse(argon2::password_hash::Error), + Parse(argon2::password_hash::phc::Error), #[error("verification failed: {0}")] Verify(argon2::password_hash::Error), #[error("password worker task failed: {0}")] @@ -43,11 +43,10 @@ fn argon2_semaphore(cfg: &CryptoConfig) -> &'static Semaphore { SEMAPHORE.get_or_init(|| Semaphore::new(cfg.argon2_max_concurrency.max(1) as usize)) } -/// Hashes a plaintext password using Argon2id with a random salt. -/// The returned string is a self-contained PHC hash (includes params + salt). +/// Hashes a plaintext password using Argon2id with a random 16-byte salt from +/// the operating system. The returned string is a self-contained PHC hash +/// (includes params + salt). pub fn hash(password: &str, cfg: &CryptoConfig) -> Result { - let salt = SaltString::generate(&mut OsRng); - let params = Params::new( cfg.argon2_memory_kib, cfg.argon2_iterations, @@ -59,7 +58,7 @@ pub fn hash(password: &str, cfg: &CryptoConfig) -> Result let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); argon2 - .hash_password(password.as_bytes(), &salt) + .hash_password(password.as_bytes()) .map(|h| h.to_string()) .map_err(PasswordError::Hash) } @@ -71,7 +70,7 @@ pub fn verify(password: &str, hash: &str) -> Result { match Argon2::default().verify_password(password.as_bytes(), &parsed) { Ok(()) => Ok(true), - Err(argon2::password_hash::Error::Password) => Ok(false), + Err(argon2::password_hash::Error::PasswordInvalid) => Ok(false), Err(e) => Err(PasswordError::Verify(e)), } } @@ -220,6 +219,27 @@ mod tests { assert!(verify("hunter2", &h).unwrap()); } + /// Hashes written by argon2 0.5.3, the version every stored password so far + /// was hashed with: an upgrade must never lock anyone out. + #[test] + fn hashes_written_by_the_previous_argon2_release_still_verify() { + for stored in [ + "$argon2id$v=19$m=1024,t=1,p=1$LQRmYeOMiT51hO+QHoiZjA$sgcCK0PEj5O2MB5lT2uHBZBeABzytakC4LLNDPyWiGw", + "$argon2id$v=19$m=65536,t=3,p=4$LQRmYeOMiT51hO+QHoiZjA$PrWpnvu6FOD76HTZWdrRJT9Ubkho34paSp7Sa04Hwhc", + ] { + assert!(verify("correct horse battery staple", stored).unwrap()); + assert!(!verify("correct horse battery stapler", stored).unwrap()); + } + } + + #[test] + fn a_new_hash_is_argon2id_with_the_configured_parameters() { + let h = hash("hunter2", &test_config()).unwrap(); + assert!(h.starts_with("$argon2id$v=19$m=1024,t=1,p=1$"), "{h}"); + // A 16-byte salt, base64 without padding. + assert_eq!(h.split('$').nth(4).unwrap().len(), 22); + } + #[test] fn verify_wrong_password_returns_false() { let cfg = test_config(); diff --git a/src/utils/totp.rs b/src/utils/totp.rs index ff83aca..6eb5bda 100644 --- a/src/utils/totp.rs +++ b/src/utils/totp.rs @@ -5,7 +5,7 @@ //! Verification decrypts the stored secret, rebuilds the TOTP context, and checks //! the submitted code with a 1-step (30s) tolerance window. -use totp_rs::{Algorithm, Secret, TOTP}; +use totp_rs::{Algorithm, Builder, Secret, Totp}; use crate::utils::crypto::{CryptoError, Keyring}; @@ -22,7 +22,7 @@ pub enum TotpError { /// Generates a new TOTP secret and returns its base32 representation. /// The caller is responsible for encrypting it before storage. pub fn generate_secret() -> String { - Secret::generate_secret().to_encoded().to_string() + Secret::generate().to_base32() } /// Returns the otpauth URI to encode into a QR code for authenticator apps. @@ -57,14 +57,35 @@ pub fn verify_code( } let plaintext = keyring.decrypt(encrypted_secret)?; - let secret_bytes = Secret::Encoded(plaintext) - .to_bytes() - .map_err(|_| TotpError::InvalidSecret)?; + Ok(totp(&plaintext, skew)?.check(code, now).is_some()) +} + +/// The code an authenticator app shows at `unix_time` for a base32 secret: for +/// tests, benchmarks and diagnostics. +pub fn code_at(base32_secret: &str, unix_time: u64) -> Result { + Ok(totp(base32_secret, 0)?.generate(unix_time).to_string()) +} - let totp = TOTP::new(Algorithm::SHA1, 6, skew, 30, secret_bytes) - .map_err(|_| TotpError::InvalidSecret)?; +/// The code an authenticator app shows now, by the system clock. +pub fn current_code(base32_secret: &str) -> Result { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_err(|_| TotpError::TimeError)?; + code_at(base32_secret, now.as_secs()) +} - Ok(totp.check(code, now)) +/// SHA-1, 6 digits, 30-second steps: what every authenticator app supports. +/// `build` refuses secrets under 128 bits. +fn totp(base32_secret: &str, skew: u8) -> Result { + let secret = Secret::try_from_base32(base32_secret).map_err(|_| TotpError::InvalidSecret)?; + Builder::new() + .with_algorithm(Algorithm::SHA1) + .with_digits(6) + .with_skew(u16::from(skew)) + .with_step_duration(30) + .with_secret(secret) + .build() + .map_err(|_| TotpError::InvalidSecret) } // Percent-encodes a string for use in a URI (RFC 3986 unreserved chars pass through). @@ -92,7 +113,7 @@ mod tests { fn keyring() -> Keyring { Keyring::new(*KEY, None) } - use totp_rs::{Algorithm, Secret, TOTP}; + use totp_rs::Secret; const KEY: &[u8; 32] = &[7u8; 32]; const NOW: i64 = 1_700_000_000; @@ -102,7 +123,10 @@ mod tests { let secret = generate_secret(); assert!(!secret.is_empty()); // totp-rs must be able to decode it back to bytes - assert!(Secret::Encoded(secret).to_bytes().is_ok()); + assert_eq!( + Secret::try_from_base32(secret).unwrap().as_bytes().len(), + 20 + ); } #[test] @@ -128,10 +152,7 @@ mod tests { /// Code of the step containing `at`. fn code_at(at: i64) -> String { - let bytes = Secret::Encoded(RFC_SECRET.into()).to_bytes().unwrap(); - TOTP::new(Algorithm::SHA1, 6, 0, 30, bytes) - .unwrap() - .generate(at as u64) + super::code_at(RFC_SECRET, at as u64).unwrap() } fn encrypted_rfc_secret() -> String { diff --git a/tests/integration/api/account/overview.rs b/tests/integration/api/account/overview.rs index 277c0bd..fb50ac7 100644 --- a/tests/integration/api/account/overview.rs +++ b/tests/integration/api/account/overview.rs @@ -2,7 +2,6 @@ //! second factors. use serde_json::{Value, json}; -use totp_rs::{Algorithm, Secret, TOTP}; use crate::common::{app::TestApp, fixtures}; @@ -111,13 +110,8 @@ async fn the_two_factor_overview_lists_methods_and_remaining_recovery_codes() { .await .unwrap(); let method_id = setup["method_id"].as_str().unwrap().to_owned(); - let secret = Secret::Encoded(setup["base32_secret"].as_str().unwrap().to_owned()) - .to_bytes() - .unwrap(); - let code = TOTP::new(Algorithm::SHA1, 6, 1, 30, secret) - .unwrap() - .generate_current() - .unwrap(); + let code = + auth_api::utils::totp::current_code(setup["base32_secret"].as_str().unwrap()).unwrap(); let verified: Value = app .post_auth( diff --git a/tests/integration/api/mail.rs b/tests/integration/api/mail.rs index 5e2557d..7f1d6c2 100644 --- a/tests/integration/api/mail.rs +++ b/tests/integration/api/mail.rs @@ -219,8 +219,6 @@ async fn disable_totp_sends_two_factor_disabled_email() { let user = fixtures::authenticated_user(&app, 805).await; - use totp_rs::{Algorithm, Secret, TOTP}; - let setup_res = app .post_auth( "/users/me/two-factor/totp/setup", @@ -233,15 +231,7 @@ async fn disable_totp_sends_two_factor_disabled_email() { let method_id = setup_body["method_id"].as_str().unwrap(); let secret_b32 = setup_body["base32_secret"].as_str().unwrap(); - let totp = TOTP::new( - Algorithm::SHA1, - 6, - 1, - 30, - Secret::Encoded(secret_b32.to_owned()).to_bytes().unwrap(), - ) - .unwrap(); - let code = totp.generate_current().unwrap(); + let code = auth_api::utils::totp::current_code(secret_b32).unwrap(); let verify_res = app .post_auth( @@ -279,8 +269,6 @@ async fn recovery_code_used_sends_notification_email() { let user = fixtures::authenticated_user(&app, 806).await; - use totp_rs::{Algorithm, Secret, TOTP}; - let setup_res = app .post_auth( "/users/me/two-factor/totp/setup", @@ -292,15 +280,7 @@ async fn recovery_code_used_sends_notification_email() { let method_id = setup_body["method_id"].as_str().unwrap(); let secret_b32 = setup_body["base32_secret"].as_str().unwrap(); - let totp = TOTP::new( - Algorithm::SHA1, - 6, - 1, - 30, - Secret::Encoded(secret_b32.to_owned()).to_bytes().unwrap(), - ) - .unwrap(); - let code = totp.generate_current().unwrap(); + let code = auth_api::utils::totp::current_code(secret_b32).unwrap(); let verify_res = app .post_auth( &format!("/users/me/two-factor/totp/{method_id}/verify"), diff --git a/tests/integration/api/two_factor/management.rs b/tests/integration/api/two_factor/management.rs index 79d93d5..f7a85a8 100644 --- a/tests/integration/api/two_factor/management.rs +++ b/tests/integration/api/two_factor/management.rs @@ -16,14 +16,7 @@ use crate::common::{ }; fn totp_code(base32_secret: &str) -> String { - use totp_rs::{Algorithm, Secret, TOTP}; - let bytes = Secret::Encoded(base32_secret.to_owned()) - .to_bytes() - .unwrap(); - TOTP::new(Algorithm::SHA1, 6, 1, 30, bytes) - .unwrap() - .generate_current() - .unwrap() + auth_api::utils::totp::current_code(base32_secret).unwrap() } async fn enable_totp(app: &TestApp, user: &AuthenticatedUser) -> String { diff --git a/tests/integration/api/two_factor/totp.rs b/tests/integration/api/two_factor/totp.rs index 64899f0..a933061 100644 --- a/tests/integration/api/two_factor/totp.rs +++ b/tests/integration/api/two_factor/totp.rs @@ -10,26 +10,17 @@ use serde_json::Value; /// Generate the current valid TOTP code for a base32-encoded secret. fn generate_totp_code(base32_secret: &str) -> String { - use totp_rs::{Algorithm, Secret, TOTP}; - let bytes = Secret::Encoded(base32_secret.to_owned()) - .to_bytes() - .expect("invalid base32 secret from setup response"); - let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, bytes).expect("TOTP construction failed"); - totp.generate_current() - .expect("failed to get current TOTP code") + auth_api::utils::totp::current_code(base32_secret) + .expect("invalid base32 secret from setup response") } /// The code `step_offset` 30-second steps away from now: still accepted within /// the skew, and distinct from the code that confirmed the method, which the /// replay table refuses. fn generate_totp_code_at(base32_secret: &str, step_offset: i64) -> String { - use totp_rs::{Algorithm, Secret, TOTP}; - let bytes = Secret::Encoded(base32_secret.to_owned()) - .to_bytes() - .expect("invalid base32 secret from setup response"); - let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, bytes).expect("TOTP construction failed"); let now = time::OffsetDateTime::now_utc().unix_timestamp() as u64; - totp.generate(now.saturating_add_signed(step_offset * 30)) + auth_api::utils::totp::code_at(base32_secret, now.saturating_add_signed(step_offset * 30)) + .expect("invalid base32 secret from setup response") } /// Set up TOTP for a user: calls setup, then verifies with a real code. diff --git a/tests/security/logs.rs b/tests/security/logs.rs index 6be5e4a..3df3cc9 100644 --- a/tests/security/logs.rs +++ b/tests/security/logs.rs @@ -6,7 +6,6 @@ use serde_json::{Value, json}; use testkit::logs::LogCapture; -use totp_rs::{Algorithm, Secret, TOTP}; use crate::common::{app::TestApp, fixtures}; @@ -112,14 +111,6 @@ async fn account_flows_never_log_their_secrets() { .unwrap(); let secret = setup["base32_secret"].as_str().unwrap().to_owned(); secrets.push(("the TOTP secret", secret.clone())); - let totp = TOTP::new( - Algorithm::SHA1, - 6, - 1, - 30, - Secret::Encoded(secret).to_bytes().unwrap(), - ) - .unwrap(); let verified: Value = app .post_auth( &format!( @@ -127,7 +118,7 @@ async fn account_flows_never_log_their_secrets() { setup["method_id"].as_str().unwrap() ), &access_token, - &json!({ "code": totp.generate_current().unwrap() }), + &json!({ "code": auth_api::utils::totp::current_code(&secret).unwrap() }), ) .await .json() diff --git a/tests/security/regressions/second_factor.rs b/tests/security/regressions/second_factor.rs index 9e99f78..592dccb 100644 --- a/tests/security/regressions/second_factor.rs +++ b/tests/security/regressions/second_factor.rs @@ -16,13 +16,9 @@ use crate::common::{ }; fn totp_code(base32_secret: &str, step_offset: i64) -> String { - use totp_rs::{Algorithm, Secret, TOTP}; - let bytes = Secret::Encoded(base32_secret.to_owned()) - .to_bytes() - .unwrap(); - let totp = TOTP::new(Algorithm::SHA1, 6, 1, 30, bytes).unwrap(); let now = time::OffsetDateTime::now_utc().unix_timestamp() as u64; - totp.generate(now.saturating_add_signed(step_offset * 30)) + auth_api::utils::totp::code_at(base32_secret, now.saturating_add_signed(step_offset * 30)) + .unwrap() } /// A 6-digit code that is not valid anywhere in the accepted window.