├── .gitignore ├── AxumCSRF.code-workspace ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── example ├── middleware │ ├── Cargo.toml │ ├── src │ │ └── main.rs │ └── templates │ │ └── template.html └── minimal │ ├── Cargo.toml │ ├── src │ └── main.rs │ └── templates │ └── template.html └── src ├── config.rs ├── cookies.rs ├── error.rs ├── layer.rs ├── lib.rs ├── service.rs └── token.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | -------------------------------------------------------------------------------- /AxumCSRF.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | 7 | ## 0.11.0 (1. Janurary, 2025) 8 | ### Changed 9 | - (Breaking) Updated to Axum 0.8.1" 10 | 11 | ## 0.10.2 (21. November, 2024) 12 | ### Changed 13 | - Skip expires attribute on cookie if lifespan is 0 @adayoung 14 | - Updated ThisError to 2.0.3 15 | 16 | ## 0.10.1 (28. October, 2024) 17 | ### Added 18 | - Tags to Docs for features @joshka 19 | 20 | ## 0.10.0 (11. September, 2024) 21 | ### Changed 22 | - (Breaking) Fixed with_cookie_domain optional string intake. 23 | 24 | ## 0.9.0 (27. November, 2023) 25 | ### Changed 26 | - (Breaking) Updated axum_core to support axum 0.7.0 27 | 28 | ## 0.8.0 (13. November, 2023) 29 | ### Changed 30 | - Removed Argon2 inplace of Hmac to help prevent Dos attacks. 31 | - Updated Cookie to 0.18.0 32 | 33 | ### Added 34 | - Option to enable auto appending __Host- to the cookie name to prevent subdomain attacks. 35 | 36 | ## 0.7.2 (13. July, 2023) 37 | ### Added 38 | - docs.rs cargo entries so layer options will appear in documents. 39 | 40 | ## 0.7.1 (13. July, 2023) 41 | ### Added 42 | - Layer Feature to allow getting CsrfTokens using a service. 43 | - Example for middleware usage. 44 | 45 | ## 0.7.0 (12. July, 2023) 46 | ### Changed 47 | - Replaced Bcrypt with Argon2. 48 | - authenticity_token now returns an error instead of unwrapping. 49 | - Added Error type to give slightly better error return messages. 50 | 51 | ## 0.6.1 (30. March, 2023) 52 | ### Changed 53 | - Updated Base64 and cookie. 54 | 55 | ## 0.6.0 (26. November, 2022) 56 | ### Changed 57 | - (Breaking) Removed Service 58 | - (Breaking) Removed Layer 59 | - Updated to Axum 0.6 60 | 61 | ## 0.5.0 (20. July, 2022) 62 | ### Changed 63 | - Removed key from layer added to config instead. 64 | 65 | ### Removed 66 | - Removed layer builder. Please change to use Config method instead. 67 | 68 | ### Added 69 | - Several new configurations to better config the cookie. 70 | - Added key switch to enable Private or Public cookies. Public is only for debugging! 71 | - Starting this Changelog at 0.5. 72 | 73 | ### Fixed 74 | - cookie not being saved due to cookie domain not getting set and SameSite set to Strict. Only affected linux? 75 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aead" 22 | version = "0.5.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 25 | dependencies = [ 26 | "crypto-common", 27 | "generic-array", 28 | ] 29 | 30 | [[package]] 31 | name = "aes" 32 | version = "0.8.4" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 35 | dependencies = [ 36 | "cfg-if", 37 | "cipher", 38 | "cpufeatures", 39 | ] 40 | 41 | [[package]] 42 | name = "aes-gcm" 43 | version = "0.10.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" 46 | dependencies = [ 47 | "aead", 48 | "aes", 49 | "cipher", 50 | "ctr", 51 | "ghash", 52 | "subtle", 53 | ] 54 | 55 | [[package]] 56 | name = "askama" 57 | version = "0.12.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" 60 | dependencies = [ 61 | "askama_derive", 62 | "askama_escape", 63 | "humansize", 64 | "num-traits", 65 | "percent-encoding", 66 | ] 67 | 68 | [[package]] 69 | name = "askama_axum" 70 | version = "0.4.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163" 73 | dependencies = [ 74 | "askama", 75 | "axum-core 0.4.5", 76 | "http", 77 | ] 78 | 79 | [[package]] 80 | name = "askama_derive" 81 | version = "0.12.5" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" 84 | dependencies = [ 85 | "askama_parser", 86 | "basic-toml", 87 | "mime", 88 | "mime_guess", 89 | "proc-macro2", 90 | "quote", 91 | "serde", 92 | "syn", 93 | ] 94 | 95 | [[package]] 96 | name = "askama_escape" 97 | version = "0.10.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" 100 | 101 | [[package]] 102 | name = "askama_parser" 103 | version = "0.2.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" 106 | dependencies = [ 107 | "nom", 108 | ] 109 | 110 | [[package]] 111 | name = "async-trait" 112 | version = "0.1.83" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 115 | dependencies = [ 116 | "proc-macro2", 117 | "quote", 118 | "syn", 119 | ] 120 | 121 | [[package]] 122 | name = "autocfg" 123 | version = "1.4.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 126 | 127 | [[package]] 128 | name = "axum" 129 | version = "0.8.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" 132 | dependencies = [ 133 | "axum-core 0.5.0", 134 | "bytes", 135 | "form_urlencoded", 136 | "futures-util", 137 | "http", 138 | "http-body", 139 | "http-body-util", 140 | "hyper", 141 | "hyper-util", 142 | "itoa", 143 | "matchit", 144 | "memchr", 145 | "mime", 146 | "percent-encoding", 147 | "pin-project-lite", 148 | "rustversion", 149 | "serde", 150 | "serde_json", 151 | "serde_path_to_error", 152 | "serde_urlencoded", 153 | "sync_wrapper", 154 | "tokio", 155 | "tower", 156 | "tower-layer", 157 | "tower-service", 158 | "tracing", 159 | ] 160 | 161 | [[package]] 162 | name = "axum-core" 163 | version = "0.4.5" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" 166 | dependencies = [ 167 | "async-trait", 168 | "bytes", 169 | "futures-util", 170 | "http", 171 | "http-body", 172 | "http-body-util", 173 | "mime", 174 | "pin-project-lite", 175 | "rustversion", 176 | "sync_wrapper", 177 | "tower-layer", 178 | "tower-service", 179 | ] 180 | 181 | [[package]] 182 | name = "axum-core" 183 | version = "0.5.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" 186 | dependencies = [ 187 | "bytes", 188 | "futures-util", 189 | "http", 190 | "http-body", 191 | "http-body-util", 192 | "mime", 193 | "pin-project-lite", 194 | "rustversion", 195 | "sync_wrapper", 196 | "tower-layer", 197 | "tower-service", 198 | "tracing", 199 | ] 200 | 201 | [[package]] 202 | name = "axum_csrf" 203 | version = "0.11.0" 204 | dependencies = [ 205 | "async-trait", 206 | "axum-core 0.5.0", 207 | "base64ct", 208 | "cookie", 209 | "hmac", 210 | "http", 211 | "rand", 212 | "sha2", 213 | "thiserror", 214 | "time", 215 | "tower-layer", 216 | "tower-service", 217 | ] 218 | 219 | [[package]] 220 | name = "backtrace" 221 | version = "0.3.74" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 224 | dependencies = [ 225 | "addr2line", 226 | "cfg-if", 227 | "libc", 228 | "miniz_oxide", 229 | "object", 230 | "rustc-demangle", 231 | "windows-targets", 232 | ] 233 | 234 | [[package]] 235 | name = "base64" 236 | version = "0.22.1" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 239 | 240 | [[package]] 241 | name = "base64ct" 242 | version = "1.6.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 245 | 246 | [[package]] 247 | name = "basic-toml" 248 | version = "0.1.9" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" 251 | dependencies = [ 252 | "serde", 253 | ] 254 | 255 | [[package]] 256 | name = "bitflags" 257 | version = "2.6.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 260 | 261 | [[package]] 262 | name = "block-buffer" 263 | version = "0.10.4" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 266 | dependencies = [ 267 | "generic-array", 268 | ] 269 | 270 | [[package]] 271 | name = "byteorder" 272 | version = "1.5.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 275 | 276 | [[package]] 277 | name = "bytes" 278 | version = "1.8.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 281 | 282 | [[package]] 283 | name = "cfg-if" 284 | version = "1.0.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 287 | 288 | [[package]] 289 | name = "cipher" 290 | version = "0.4.4" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 293 | dependencies = [ 294 | "crypto-common", 295 | "inout", 296 | ] 297 | 298 | [[package]] 299 | name = "cookie" 300 | version = "0.18.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" 303 | dependencies = [ 304 | "aes-gcm", 305 | "base64", 306 | "hmac", 307 | "percent-encoding", 308 | "rand", 309 | "sha2", 310 | "subtle", 311 | "time", 312 | "version_check", 313 | ] 314 | 315 | [[package]] 316 | name = "cpufeatures" 317 | version = "0.2.14" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 320 | dependencies = [ 321 | "libc", 322 | ] 323 | 324 | [[package]] 325 | name = "crypto-common" 326 | version = "0.1.6" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 329 | dependencies = [ 330 | "generic-array", 331 | "rand_core", 332 | "typenum", 333 | ] 334 | 335 | [[package]] 336 | name = "ctr" 337 | version = "0.9.2" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" 340 | dependencies = [ 341 | "cipher", 342 | ] 343 | 344 | [[package]] 345 | name = "deranged" 346 | version = "0.3.11" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 349 | dependencies = [ 350 | "powerfmt", 351 | ] 352 | 353 | [[package]] 354 | name = "digest" 355 | version = "0.10.7" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 358 | dependencies = [ 359 | "block-buffer", 360 | "crypto-common", 361 | "subtle", 362 | ] 363 | 364 | [[package]] 365 | name = "fnv" 366 | version = "1.0.7" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 369 | 370 | [[package]] 371 | name = "form_urlencoded" 372 | version = "1.2.1" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 375 | dependencies = [ 376 | "percent-encoding", 377 | ] 378 | 379 | [[package]] 380 | name = "futures-channel" 381 | version = "0.3.31" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 384 | dependencies = [ 385 | "futures-core", 386 | ] 387 | 388 | [[package]] 389 | name = "futures-core" 390 | version = "0.3.31" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 393 | 394 | [[package]] 395 | name = "futures-task" 396 | version = "0.3.31" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 399 | 400 | [[package]] 401 | name = "futures-util" 402 | version = "0.3.31" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 405 | dependencies = [ 406 | "futures-core", 407 | "futures-task", 408 | "pin-project-lite", 409 | "pin-utils", 410 | ] 411 | 412 | [[package]] 413 | name = "generic-array" 414 | version = "0.14.7" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 417 | dependencies = [ 418 | "typenum", 419 | "version_check", 420 | ] 421 | 422 | [[package]] 423 | name = "getrandom" 424 | version = "0.2.15" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 427 | dependencies = [ 428 | "cfg-if", 429 | "libc", 430 | "wasi", 431 | ] 432 | 433 | [[package]] 434 | name = "ghash" 435 | version = "0.5.1" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" 438 | dependencies = [ 439 | "opaque-debug", 440 | "polyval", 441 | ] 442 | 443 | [[package]] 444 | name = "gimli" 445 | version = "0.31.1" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 448 | 449 | [[package]] 450 | name = "hermit-abi" 451 | version = "0.3.9" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 454 | 455 | [[package]] 456 | name = "hmac" 457 | version = "0.12.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 460 | dependencies = [ 461 | "digest", 462 | ] 463 | 464 | [[package]] 465 | name = "http" 466 | version = "1.2.0" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 469 | dependencies = [ 470 | "bytes", 471 | "fnv", 472 | "itoa", 473 | ] 474 | 475 | [[package]] 476 | name = "http-body" 477 | version = "1.0.1" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 480 | dependencies = [ 481 | "bytes", 482 | "http", 483 | ] 484 | 485 | [[package]] 486 | name = "http-body-util" 487 | version = "0.1.2" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 490 | dependencies = [ 491 | "bytes", 492 | "futures-util", 493 | "http", 494 | "http-body", 495 | "pin-project-lite", 496 | ] 497 | 498 | [[package]] 499 | name = "httparse" 500 | version = "1.9.5" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 503 | 504 | [[package]] 505 | name = "httpdate" 506 | version = "1.0.3" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 509 | 510 | [[package]] 511 | name = "humansize" 512 | version = "2.1.3" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" 515 | dependencies = [ 516 | "libm", 517 | ] 518 | 519 | [[package]] 520 | name = "hyper" 521 | version = "1.5.2" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" 524 | dependencies = [ 525 | "bytes", 526 | "futures-channel", 527 | "futures-util", 528 | "http", 529 | "http-body", 530 | "httparse", 531 | "httpdate", 532 | "itoa", 533 | "pin-project-lite", 534 | "smallvec", 535 | "tokio", 536 | ] 537 | 538 | [[package]] 539 | name = "hyper-util" 540 | version = "0.1.9" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" 543 | dependencies = [ 544 | "bytes", 545 | "futures-util", 546 | "http", 547 | "http-body", 548 | "hyper", 549 | "pin-project-lite", 550 | "tokio", 551 | "tower-service", 552 | ] 553 | 554 | [[package]] 555 | name = "inout" 556 | version = "0.1.3" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 559 | dependencies = [ 560 | "generic-array", 561 | ] 562 | 563 | [[package]] 564 | name = "itoa" 565 | version = "1.0.11" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 568 | 569 | [[package]] 570 | name = "lazy_static" 571 | version = "1.5.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 574 | 575 | [[package]] 576 | name = "libc" 577 | version = "0.2.161" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 580 | 581 | [[package]] 582 | name = "libm" 583 | version = "0.2.10" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "a00419de735aac21d53b0de5ce2c03bd3627277cf471300f27ebc89f7d828047" 586 | 587 | [[package]] 588 | name = "lock_api" 589 | version = "0.4.12" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 592 | dependencies = [ 593 | "autocfg", 594 | "scopeguard", 595 | ] 596 | 597 | [[package]] 598 | name = "log" 599 | version = "0.4.22" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 602 | 603 | [[package]] 604 | name = "matchit" 605 | version = "0.8.4" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 608 | 609 | [[package]] 610 | name = "memchr" 611 | version = "2.7.4" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 614 | 615 | [[package]] 616 | name = "middleware" 617 | version = "0.1.0" 618 | dependencies = [ 619 | "askama", 620 | "askama_axum", 621 | "axum", 622 | "axum_csrf", 623 | "http-body-util", 624 | "hyper", 625 | "serde", 626 | "serde_urlencoded", 627 | "tokio", 628 | "tracing", 629 | "tracing-subscriber", 630 | ] 631 | 632 | [[package]] 633 | name = "mime" 634 | version = "0.3.17" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 637 | 638 | [[package]] 639 | name = "mime_guess" 640 | version = "2.0.5" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 643 | dependencies = [ 644 | "mime", 645 | "unicase", 646 | ] 647 | 648 | [[package]] 649 | name = "minimal-lexical" 650 | version = "0.2.1" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 653 | 654 | [[package]] 655 | name = "miniz_oxide" 656 | version = "0.8.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 659 | dependencies = [ 660 | "adler2", 661 | ] 662 | 663 | [[package]] 664 | name = "mio" 665 | version = "1.0.2" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 668 | dependencies = [ 669 | "hermit-abi", 670 | "libc", 671 | "wasi", 672 | "windows-sys", 673 | ] 674 | 675 | [[package]] 676 | name = "nom" 677 | version = "7.1.3" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 680 | dependencies = [ 681 | "memchr", 682 | "minimal-lexical", 683 | ] 684 | 685 | [[package]] 686 | name = "nu-ansi-term" 687 | version = "0.46.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 690 | dependencies = [ 691 | "overload", 692 | "winapi", 693 | ] 694 | 695 | [[package]] 696 | name = "num-conv" 697 | version = "0.1.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 700 | 701 | [[package]] 702 | name = "num-traits" 703 | version = "0.2.19" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 706 | dependencies = [ 707 | "autocfg", 708 | ] 709 | 710 | [[package]] 711 | name = "object" 712 | version = "0.36.5" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 715 | dependencies = [ 716 | "memchr", 717 | ] 718 | 719 | [[package]] 720 | name = "once_cell" 721 | version = "1.20.2" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 724 | 725 | [[package]] 726 | name = "opaque-debug" 727 | version = "0.3.1" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 730 | 731 | [[package]] 732 | name = "overload" 733 | version = "0.1.1" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 736 | 737 | [[package]] 738 | name = "parking_lot" 739 | version = "0.12.3" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 742 | dependencies = [ 743 | "lock_api", 744 | "parking_lot_core", 745 | ] 746 | 747 | [[package]] 748 | name = "parking_lot_core" 749 | version = "0.9.10" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 752 | dependencies = [ 753 | "cfg-if", 754 | "libc", 755 | "redox_syscall", 756 | "smallvec", 757 | "windows-targets", 758 | ] 759 | 760 | [[package]] 761 | name = "percent-encoding" 762 | version = "2.3.1" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 765 | 766 | [[package]] 767 | name = "pin-project-lite" 768 | version = "0.2.15" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 771 | 772 | [[package]] 773 | name = "pin-utils" 774 | version = "0.1.0" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 777 | 778 | [[package]] 779 | name = "polyval" 780 | version = "0.6.2" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" 783 | dependencies = [ 784 | "cfg-if", 785 | "cpufeatures", 786 | "opaque-debug", 787 | "universal-hash", 788 | ] 789 | 790 | [[package]] 791 | name = "powerfmt" 792 | version = "0.2.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 795 | 796 | [[package]] 797 | name = "ppv-lite86" 798 | version = "0.2.20" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 801 | dependencies = [ 802 | "zerocopy", 803 | ] 804 | 805 | [[package]] 806 | name = "proc-macro2" 807 | version = "1.0.91" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" 810 | dependencies = [ 811 | "unicode-ident", 812 | ] 813 | 814 | [[package]] 815 | name = "quote" 816 | version = "1.0.37" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 819 | dependencies = [ 820 | "proc-macro2", 821 | ] 822 | 823 | [[package]] 824 | name = "rand" 825 | version = "0.8.5" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 828 | dependencies = [ 829 | "libc", 830 | "rand_chacha", 831 | "rand_core", 832 | ] 833 | 834 | [[package]] 835 | name = "rand_chacha" 836 | version = "0.3.1" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 839 | dependencies = [ 840 | "ppv-lite86", 841 | "rand_core", 842 | ] 843 | 844 | [[package]] 845 | name = "rand_core" 846 | version = "0.6.4" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 849 | dependencies = [ 850 | "getrandom", 851 | ] 852 | 853 | [[package]] 854 | name = "redox_syscall" 855 | version = "0.5.7" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 858 | dependencies = [ 859 | "bitflags", 860 | ] 861 | 862 | [[package]] 863 | name = "rustc-demangle" 864 | version = "0.1.24" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 867 | 868 | [[package]] 869 | name = "rustversion" 870 | version = "1.0.18" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 873 | 874 | [[package]] 875 | name = "ryu" 876 | version = "1.0.18" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 879 | 880 | [[package]] 881 | name = "scopeguard" 882 | version = "1.2.0" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 885 | 886 | [[package]] 887 | name = "serde" 888 | version = "1.0.213" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" 891 | dependencies = [ 892 | "serde_derive", 893 | ] 894 | 895 | [[package]] 896 | name = "serde_derive" 897 | version = "1.0.213" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" 900 | dependencies = [ 901 | "proc-macro2", 902 | "quote", 903 | "syn", 904 | ] 905 | 906 | [[package]] 907 | name = "serde_json" 908 | version = "1.0.132" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 911 | dependencies = [ 912 | "itoa", 913 | "memchr", 914 | "ryu", 915 | "serde", 916 | ] 917 | 918 | [[package]] 919 | name = "serde_path_to_error" 920 | version = "0.1.16" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" 923 | dependencies = [ 924 | "itoa", 925 | "serde", 926 | ] 927 | 928 | [[package]] 929 | name = "serde_urlencoded" 930 | version = "0.7.1" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 933 | dependencies = [ 934 | "form_urlencoded", 935 | "itoa", 936 | "ryu", 937 | "serde", 938 | ] 939 | 940 | [[package]] 941 | name = "sha2" 942 | version = "0.10.8" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 945 | dependencies = [ 946 | "cfg-if", 947 | "cpufeatures", 948 | "digest", 949 | ] 950 | 951 | [[package]] 952 | name = "sharded-slab" 953 | version = "0.1.7" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 956 | dependencies = [ 957 | "lazy_static", 958 | ] 959 | 960 | [[package]] 961 | name = "signal-hook-registry" 962 | version = "1.4.2" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 965 | dependencies = [ 966 | "libc", 967 | ] 968 | 969 | [[package]] 970 | name = "smallvec" 971 | version = "1.13.2" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 974 | 975 | [[package]] 976 | name = "socket2" 977 | version = "0.5.7" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 980 | dependencies = [ 981 | "libc", 982 | "windows-sys", 983 | ] 984 | 985 | [[package]] 986 | name = "subtle" 987 | version = "2.6.1" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 990 | 991 | [[package]] 992 | name = "syn" 993 | version = "2.0.89" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 996 | dependencies = [ 997 | "proc-macro2", 998 | "quote", 999 | "unicode-ident", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "sync_wrapper" 1004 | version = "1.0.1" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 1007 | 1008 | [[package]] 1009 | name = "test" 1010 | version = "0.1.0" 1011 | dependencies = [ 1012 | "axum", 1013 | "axum_csrf", 1014 | "serde", 1015 | "tokio", 1016 | "tracing", 1017 | "tracing-subscriber", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "thiserror" 1022 | version = "2.0.9" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" 1025 | dependencies = [ 1026 | "thiserror-impl", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "thiserror-impl" 1031 | version = "2.0.9" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" 1034 | dependencies = [ 1035 | "proc-macro2", 1036 | "quote", 1037 | "syn", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "thread_local" 1042 | version = "1.1.8" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1045 | dependencies = [ 1046 | "cfg-if", 1047 | "once_cell", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "time" 1052 | version = "0.3.37" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" 1055 | dependencies = [ 1056 | "deranged", 1057 | "itoa", 1058 | "num-conv", 1059 | "powerfmt", 1060 | "serde", 1061 | "time-core", 1062 | "time-macros", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "time-core" 1067 | version = "0.1.2" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1070 | 1071 | [[package]] 1072 | name = "time-macros" 1073 | version = "0.2.19" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" 1076 | dependencies = [ 1077 | "num-conv", 1078 | "time-core", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "tokio" 1083 | version = "1.41.0" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" 1086 | dependencies = [ 1087 | "backtrace", 1088 | "bytes", 1089 | "libc", 1090 | "mio", 1091 | "parking_lot", 1092 | "pin-project-lite", 1093 | "signal-hook-registry", 1094 | "socket2", 1095 | "tokio-macros", 1096 | "windows-sys", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "tokio-macros" 1101 | version = "2.4.0" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1104 | dependencies = [ 1105 | "proc-macro2", 1106 | "quote", 1107 | "syn", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "tower" 1112 | version = "0.5.2" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1115 | dependencies = [ 1116 | "futures-core", 1117 | "futures-util", 1118 | "pin-project-lite", 1119 | "sync_wrapper", 1120 | "tokio", 1121 | "tower-layer", 1122 | "tower-service", 1123 | "tracing", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "tower-layer" 1128 | version = "0.3.3" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1131 | 1132 | [[package]] 1133 | name = "tower-service" 1134 | version = "0.3.3" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1137 | 1138 | [[package]] 1139 | name = "tracing" 1140 | version = "0.1.40" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1143 | dependencies = [ 1144 | "log", 1145 | "pin-project-lite", 1146 | "tracing-attributes", 1147 | "tracing-core", 1148 | ] 1149 | 1150 | [[package]] 1151 | name = "tracing-attributes" 1152 | version = "0.1.27" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1155 | dependencies = [ 1156 | "proc-macro2", 1157 | "quote", 1158 | "syn", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "tracing-core" 1163 | version = "0.1.32" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1166 | dependencies = [ 1167 | "once_cell", 1168 | "valuable", 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "tracing-log" 1173 | version = "0.2.0" 1174 | source = "registry+https://github.com/rust-lang/crates.io-index" 1175 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1176 | dependencies = [ 1177 | "log", 1178 | "once_cell", 1179 | "tracing-core", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "tracing-subscriber" 1184 | version = "0.3.18" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 1187 | dependencies = [ 1188 | "nu-ansi-term", 1189 | "sharded-slab", 1190 | "smallvec", 1191 | "thread_local", 1192 | "tracing-core", 1193 | "tracing-log", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "typenum" 1198 | version = "1.17.0" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1201 | 1202 | [[package]] 1203 | name = "unicase" 1204 | version = "2.8.0" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" 1207 | 1208 | [[package]] 1209 | name = "unicode-ident" 1210 | version = "1.0.13" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1213 | 1214 | [[package]] 1215 | name = "universal-hash" 1216 | version = "0.5.1" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 1219 | dependencies = [ 1220 | "crypto-common", 1221 | "subtle", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "valuable" 1226 | version = "0.1.0" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1229 | 1230 | [[package]] 1231 | name = "version_check" 1232 | version = "0.9.5" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1235 | 1236 | [[package]] 1237 | name = "wasi" 1238 | version = "0.11.0+wasi-snapshot-preview1" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1241 | 1242 | [[package]] 1243 | name = "winapi" 1244 | version = "0.3.9" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1247 | dependencies = [ 1248 | "winapi-i686-pc-windows-gnu", 1249 | "winapi-x86_64-pc-windows-gnu", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "winapi-i686-pc-windows-gnu" 1254 | version = "0.4.0" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1257 | 1258 | [[package]] 1259 | name = "winapi-x86_64-pc-windows-gnu" 1260 | version = "0.4.0" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1263 | 1264 | [[package]] 1265 | name = "windows-sys" 1266 | version = "0.52.0" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1269 | dependencies = [ 1270 | "windows-targets", 1271 | ] 1272 | 1273 | [[package]] 1274 | name = "windows-targets" 1275 | version = "0.52.6" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1278 | dependencies = [ 1279 | "windows_aarch64_gnullvm", 1280 | "windows_aarch64_msvc", 1281 | "windows_i686_gnu", 1282 | "windows_i686_gnullvm", 1283 | "windows_i686_msvc", 1284 | "windows_x86_64_gnu", 1285 | "windows_x86_64_gnullvm", 1286 | "windows_x86_64_msvc", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "windows_aarch64_gnullvm" 1291 | version = "0.52.6" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1294 | 1295 | [[package]] 1296 | name = "windows_aarch64_msvc" 1297 | version = "0.52.6" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1300 | 1301 | [[package]] 1302 | name = "windows_i686_gnu" 1303 | version = "0.52.6" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1306 | 1307 | [[package]] 1308 | name = "windows_i686_gnullvm" 1309 | version = "0.52.6" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1312 | 1313 | [[package]] 1314 | name = "windows_i686_msvc" 1315 | version = "0.52.6" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1318 | 1319 | [[package]] 1320 | name = "windows_x86_64_gnu" 1321 | version = "0.52.6" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1324 | 1325 | [[package]] 1326 | name = "windows_x86_64_gnullvm" 1327 | version = "0.52.6" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1330 | 1331 | [[package]] 1332 | name = "windows_x86_64_msvc" 1333 | version = "0.52.6" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1336 | 1337 | [[package]] 1338 | name = "zerocopy" 1339 | version = "0.7.35" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1342 | dependencies = [ 1343 | "byteorder", 1344 | "zerocopy-derive", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "zerocopy-derive" 1349 | version = "0.7.35" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1352 | dependencies = [ 1353 | "proc-macro2", 1354 | "quote", 1355 | "syn", 1356 | ] 1357 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [".", "example/minimal", "example/middleware"] 3 | 4 | [package] 5 | name = "axum_csrf" 6 | version = "0.11.0" 7 | authors = ["Andrew Wheeler "] 8 | description = "Library to Provide a CSRF (Cross-Site Request Forgery) protection layer." 9 | edition = "2021" 10 | license = "MIT" 11 | readme = "README.md" 12 | documentation = "https://docs.rs/axum_csrf" 13 | keywords = ["Axum", "CSRF", "Cookies"] 14 | repository = "https://github.com/AscendingCreations/AxumCSRF" 15 | 16 | [features] 17 | default = [] 18 | layer = ["dep:tower-layer", "dep:tower-service"] 19 | 20 | [dependencies] 21 | axum-core = "0.5.0" 22 | http = "1.2.0" 23 | async-trait = "0.1.83" 24 | rand = "0.8.5" 25 | time = { version = "0.3.37", default-features = false, features = ["std"] } 26 | cookie = { version = "0.18.1", features = [ 27 | "percent-encode", 28 | "signed", 29 | "private", 30 | ] } 31 | thiserror = "2.0.9" 32 | tower-layer = { version = "0.3.3", optional = true } 33 | tower-service = { version = "0.3.3", optional = true } 34 | hmac = "0.12.1" 35 | sha2 = "0.10.8" 36 | base64ct = { version = "1.6.0", features = ["alloc"] } 37 | 38 | [package.metadata.docs.rs] 39 | features = ["layer"] 40 | rustdoc-args = ["--document-private-items", "--cfg", "docsrs"] 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ascending Creations 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Axum_CSRF 3 |

4 |
5 | Library to provide a CSRF (Cross-Site Request Forgery) protection layer to Axum-based web applications. Axum 0.8 is currently supported. 6 |
7 |
8 |
9 | crates.io 10 | docs.rs 11 | Minimum Rust Version 12 |
13 | 14 | # Help 15 | 16 | If you need help with this library please join our [Discord Group](https://discord.gg/gVXNDwpS3Z) 17 | 18 | ## Install 19 | ```toml 20 | # Cargo.toml 21 | [dependencies] 22 | axum_csrf = "0.11.0" 23 | ``` 24 | 25 | #### Cargo Feature Flags 26 | `default`: [] 27 | 28 | `layer`: Disables the state and enables a service layer. Useful for middleware interactions. 29 | 30 | # Example 31 | 32 | Add it to axum via shared state: 33 | ```rust 34 | use askama::Template; 35 | use axum::{Form, response::IntoResponse, routing::get, Router}; 36 | use axum_csrf::{CsrfConfig, CsrfToken}; 37 | use serde::{Deserialize, Serialize}; 38 | use std::net::SocketAddr; 39 | 40 | #[derive(Template, Deserialize, Serialize)] 41 | #[template(path = "template.html")] 42 | struct Keys { 43 | authenticity_token: String, 44 | // Your attributes... 45 | } 46 | 47 | #[tokio::main] 48 | async fn main() { 49 | // initialize tracing 50 | tracing_subscriber::fmt::init(); 51 | let config = CsrfConfig::default(); 52 | 53 | // build our application with a route 54 | let app = Router::new() 55 | // `GET /` goes to `root` and Post Goes to check key 56 | .route("/", get(root).post(check_key)) 57 | .with_state(config); 58 | 59 | // run our app with hyper 60 | // `axum::Server` is a re-export of `hyper::Server` 61 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 62 | tracing::debug!("listening on {}", addr); 63 | axum::Server::bind(&addr) 64 | .serve(app.into_make_service()) 65 | .await 66 | .unwrap(); 67 | } 68 | 69 | // root creates the CSRF Token and sends it into the page for return. 70 | async fn root(token: CsrfToken) -> impl IntoResponse { 71 | let keys = Keys { 72 | //this Token is a hashed Token. it is returned and the original token is hashed for comparison. 73 | authenticity_token: token.authenticity_token().unwrap(), 74 | }; 75 | 76 | // We must return the token so that into_response will run and add it to our response cookies. 77 | (token, keys).into_response() 78 | } 79 | 80 | async fn check_key(token: CsrfToken, Form(payload): Form) -> &'static str { 81 | // Verfiy the Hash and return the String message. 82 | if token.verify(&payload.authenticity_token).is_err() { 83 | "Token is invalid" 84 | } else { 85 | "Token is Valid lets do stuff!" 86 | } 87 | } 88 | ``` 89 | 90 | The Template File 91 | ```html 92 | 93 | 94 | 95 | 96 | Example 97 | 98 | 99 | 100 |
101 | 102 | 103 |
104 | 105 | 106 | ``` 107 | 108 | Or use the "layer" feature if you dont want to use state: 109 | ```rust 110 | use askama::Template; 111 | use axum::{Form, response::IntoResponse, routing::get, Router}; 112 | use axum_csrf::{CsrfConfig, CsrfLayer, CsrfToken }; 113 | use serde::{Deserialize, Serialize}; 114 | use std::net::SocketAddr; 115 | 116 | #[derive(Template, Deserialize, Serialize)] 117 | #[template(path = "template.html")] 118 | struct Keys { 119 | authenticity_token: String, 120 | // Your attributes... 121 | } 122 | 123 | #[tokio::main] 124 | async fn main() { 125 | // initialize tracing 126 | tracing_subscriber::fmt::init(); 127 | let config = CsrfConfig::default(); 128 | 129 | // build our application with a route 130 | let app = Router::new() 131 | // `GET /` goes to `root` and Post Goes to check key 132 | .route("/", get(root).post(check_key)) 133 | .layer(CsrfLayer::new(config)); 134 | 135 | // run our app with hyper 136 | // `axum::Server` is a re-export of `hyper::Server` 137 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 138 | tracing::debug!("listening on {}", addr); 139 | axum::Server::bind(&addr) 140 | .serve(app.into_make_service()) 141 | .await 142 | .unwrap(); 143 | } 144 | 145 | // root creates the CSRF Token and sends it into the page for return. 146 | async fn root(token: CsrfToken) -> impl IntoResponse { 147 | let keys = Keys { 148 | //this Token is a hashed Token. it is returned and the original token is hashed for comparison. 149 | authenticity_token: token.authenticity_token().unwrap(), 150 | }; 151 | 152 | // We must return the token so that into_response will run and add it to our response cookies. 153 | (token, keys).into_response() 154 | } 155 | 156 | async fn check_key(token: CsrfToken, Form(payload): Form) -> &'static str { 157 | // Verfiy the Hash and return the String message. 158 | if token.verify(&payload.authenticity_token).is_err() { 159 | "Token is invalid" 160 | } else { 161 | "Token is Valid lets do stuff!" 162 | } 163 | } 164 | ``` 165 | 166 | If you already have an encryption key for private cookies, build the CSRF configuration a different way: 167 | ```rust 168 | let cookie_key = cookie::Key::generate(); 169 | let config = CsrfConfig::default().with_key(Some(cookie_key)); 170 | 171 | let app = Router::new().with_state(config) 172 | ``` 173 | 174 | # Prevent Post Replay Attacks with CSRF. 175 | 176 | If you want to Prevent Post Replay Attacks then you should use a Session Storage method. 177 | you store the hash in the server side session store as well as send it with the form. 178 | when they post the data you would check the hash of the form first and then against the internal session data 2nd. 179 | After the 2nd hash is valid you would then remove the hash from the session. 180 | This prevents replay attacks and ensure no data was manipulated. 181 | If you need a Session database I would suggest using [`axum_session`](https://crates.io/crates/axum_session) 182 | 183 | Changes using `axum_session`. 184 | ```rust 185 | async fn greet(token: CsrfToken, session: Session) -> impl IntoResponse { 186 | let authenticity_token = token.authenticity_token(); 187 | session.set("authenticity_token", authenticity_token.clone()).await; 188 | 189 | let keys = Keys { 190 | authenticity_token, 191 | } 192 | 193 | //we must return the token so that into_response will run and add it to our response cookies. 194 | (token, keys).into_response() 195 | } 196 | ``` 197 | 198 | Validate the CSRF Key and Validate for Post Replay attacks 199 | ```rust 200 | async fn check_key(token: CsrfToken, session: Session, Form(payload): Form,) -> &'static str { 201 | let authenticity_token: String = session.get("authenticity_token").await.unwrap_or_default(); 202 | 203 | if let Err(_) = token.verify(&payload.authenticity_token) { 204 | "Token is invalid" 205 | } else if let Err(_) = token.verify(&authenticity_token) { 206 | "Modification of both Cookie/token OR a replay attack occured" 207 | } else { 208 | // we remove it to only allow one post per generated token. 209 | session.remove("authenticity_token").await; 210 | "Token is Valid lets do stuff!" 211 | } 212 | } 213 | ``` 214 | -------------------------------------------------------------------------------- /example/middleware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | axum = "0.8.1" 8 | serde = { version = "1.0.210", features = ["derive"] } 9 | tokio = { version = "1.40.0", features = ["full"] } 10 | askama = "0.12.1" 11 | askama_axum = "0.4.0" 12 | tracing = "0.1.40" 13 | tracing-subscriber = "0.3.18" 14 | serde_urlencoded = "0.7.1" 15 | hyper = "1.5.2" 16 | http-body-util = "0.1.2" 17 | 18 | [dependencies.axum_csrf] 19 | path = "../.." 20 | features = ["layer"] -------------------------------------------------------------------------------- /example/middleware/src/main.rs: -------------------------------------------------------------------------------- 1 | use askama::Template; 2 | use axum::{ 3 | body::Body, 4 | extract::Request, 5 | http::{Method, StatusCode}, 6 | middleware::Next, 7 | response::{IntoResponse, Response}, 8 | routing::{get, post}, 9 | Form, Router, 10 | }; 11 | use axum_csrf::{CsrfConfig, CsrfLayer, CsrfToken, Key}; 12 | use http_body_util::BodyExt; 13 | use serde::{Deserialize, Serialize}; 14 | use tokio::net::TcpListener; 15 | 16 | #[derive(Template, Deserialize, Serialize)] 17 | #[template(path = "template.html")] 18 | pub struct Keys { 19 | authenticity_token: String, 20 | // Your attributes... 21 | } 22 | 23 | #[tokio::main] 24 | async fn main() { 25 | // initialize tracing 26 | tracing_subscriber::fmt::init(); 27 | let cookie_key = Key::generate(); 28 | let config = CsrfConfig::default() 29 | .with_key(Some(cookie_key)) 30 | .with_cookie_domain(Some("127.0.0.1")); 31 | 32 | // build our application with a route 33 | let app = Router::new() 34 | .route("/", post(check_key)) 35 | .layer(axum::middleware::from_fn(auth_middleware)) 36 | // `GET /` goes to `root` and Post Goes to check key 37 | .route("/", get(root)) 38 | .layer(CsrfLayer::new(config)); 39 | 40 | // run our app with hyper 41 | // `axum::Server` is a re-export of `hyper::Server` 42 | let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap(); 43 | axum::serve(listener, app).await.unwrap(); 44 | } 45 | 46 | // basic handler that responds with a static string 47 | async fn root(token: CsrfToken) -> impl IntoResponse { 48 | let keys = Keys { 49 | authenticity_token: token.authenticity_token().unwrap(), 50 | }; 51 | 52 | // We must return the token so that into_response will run and add it to our response cookies. 53 | (token, keys).into_response() 54 | } 55 | 56 | /// Can only be done with the feature layer enabled 57 | pub async fn auth_middleware( 58 | token: CsrfToken, 59 | method: Method, 60 | mut request: Request, 61 | next: Next, 62 | ) -> Result { 63 | if method == Method::POST { 64 | let (parts, body) = request.into_parts(); 65 | 66 | let bytes = body 67 | .collect() 68 | .await 69 | .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? 70 | .to_bytes() 71 | .to_vec(); 72 | 73 | let value = serde_urlencoded::from_bytes(&bytes) 74 | .map_err(|_| -> StatusCode { StatusCode::INTERNAL_SERVER_ERROR })?; 75 | let payload: Form = Form(value); 76 | if token.verify(&payload.authenticity_token).is_err() { 77 | return Err(StatusCode::UNAUTHORIZED); 78 | } 79 | 80 | request = Request::from_parts(parts, Body::from(bytes)); 81 | } 82 | 83 | Ok(next.run(request).await) 84 | } 85 | 86 | async fn check_key() -> &'static str { 87 | "Token is Valid lets do stuff!" 88 | } 89 | -------------------------------------------------------------------------------- /example/middleware/templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Minimal 7 | 8 | 9 | 10 |
11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /example/minimal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | axum = "0.8.1" 8 | serde = { version = "1.0.210", features = ["derive"] } 9 | tokio = { version = "1.40.0", features = ["full"] } 10 | tracing = "0.1.40" 11 | tracing-subscriber = "0.3.18" 12 | 13 | [dependencies.axum_csrf] 14 | #version = "0.8.0" 15 | path = "../.." -------------------------------------------------------------------------------- /example/minimal/src/main.rs: -------------------------------------------------------------------------------- 1 | use axum::{body::Body, response::IntoResponse, routing::get, Form, Router}; 2 | use axum_csrf::{CsrfConfig, CsrfToken, Key}; 3 | use serde::{Deserialize, Serialize}; 4 | use tokio::net::TcpListener; 5 | 6 | #[derive(Deserialize, Serialize)] 7 | struct Keys { 8 | authenticity_token: String, 9 | // Your attributes... 10 | } 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | // initialize tracing 15 | tracing_subscriber::fmt::init(); 16 | let cookie_key = Key::generate(); 17 | let config = CsrfConfig::default().with_key(Some(cookie_key)); 18 | 19 | // build our application with a route 20 | let app = Router::new() 21 | // `GET /` goes to `root` and Post Goes to check key 22 | .route("/", get(root).post(check_key)) 23 | .with_state(config); 24 | 25 | // run our app with hyper 26 | // `axum::Server` is a re-export of `hyper::Server` 27 | let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap(); 28 | axum::serve(listener, app).await.unwrap(); 29 | } 30 | 31 | // basic handler that responds with a static string 32 | async fn root(token: CsrfToken) -> impl IntoResponse { 33 | let key = Body::new( 34 | r#" 35 | 36 | 37 | 38 | 39 | 40 | Minimal 41 | 42 | 43 | 44 |
45 | 46 | 47 |
48 | 49 | "# 50 | .replace( 51 | "{{ authenticity_token }}", 52 | &token.authenticity_token().unwrap(), 53 | ), 54 | ); 55 | 56 | // We must return the token so that into_response will run and add it to our response cookies. 57 | (token, key) 58 | } 59 | 60 | async fn check_key(token: CsrfToken, Form(payload): Form) -> &'static str { 61 | if token.verify(&payload.authenticity_token).is_err() { 62 | "Token is invalid" 63 | } else { 64 | "Token is Valid lets do stuff!" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/minimal/templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Minimal 7 | 8 | 9 | 10 |
11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | pub use cookie::{Key, SameSite}; 2 | use rand::{distributions::Alphanumeric, thread_rng, Rng}; 3 | use std::borrow::Cow; 4 | use time::Duration; 5 | 6 | ///This is the CSRF Config it is used to manage how we set the Restricted Cookie. 7 | #[derive(Clone)] 8 | pub struct CsrfConfig { 9 | /// CSRF Cookie lifespan 10 | pub(crate) lifespan: Duration, 11 | /// CSRF cookie name 12 | pub(crate) cookie_name: String, 13 | /// CSRF Token character length 14 | pub(crate) cookie_len: usize, 15 | /// Session cookie domain 16 | pub(crate) cookie_domain: Option>, 17 | /// Session cookie http only flag 18 | pub(crate) cookie_http_only: bool, 19 | /// Session cookie http only flag 20 | pub(crate) cookie_path: Cow<'static, str>, 21 | /// Resticts how Cookies are sent cross-site. Default is `SameSite::None` 22 | /// Only works if domain is also set. 23 | pub(crate) cookie_same_site: SameSite, 24 | /// Session cookie secure flag 25 | pub(crate) cookie_secure: bool, 26 | ///Encyption Key used to encypt cookies for confidentiality, integrity, and authenticity. 27 | pub(crate) key: Option, 28 | ///Hashing Salt. 29 | pub(crate) salt: Cow<'static, str>, 30 | /// This is used to append __Host- to the front of all Cookie names to prevent sub domain usage. 31 | /// It is disabled by default. 32 | pub(crate) prefix_with_host: bool, 33 | } 34 | 35 | impl std::fmt::Debug for CsrfConfig { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | f.debug_struct("CsrfConfig") 38 | .field("lifespan", &self.lifespan) 39 | .field("cookie_name", &self.cookie_name) 40 | .field("cookie_len", &self.cookie_len) 41 | .field("cookie_domain", &self.cookie_domain) 42 | .field("cookie_http_only", &self.cookie_http_only) 43 | .field("cookie_path", &self.cookie_path) 44 | .field("cookie_same_site", &self.cookie_same_site) 45 | .field("cookie_secure", &self.cookie_secure) 46 | .field("key", &"key hidden") 47 | .field("salt", &"salt hidden") 48 | .field("prefix_with_host", &self.prefix_with_host) 49 | .finish() 50 | } 51 | } 52 | 53 | impl CsrfConfig { 54 | /// Creates [`Default`] configuration of [`CsrfConfig`]. 55 | /// This is equivalent to the [`CsrfConfig::default()`]. 56 | #[must_use] 57 | pub fn new() -> Self { 58 | Default::default() 59 | } 60 | 61 | /// Set's the csrf's cookie's domain name. 62 | /// 63 | /// # Examples 64 | /// ```rust 65 | /// use axum_csrf::CsrfConfig; 66 | /// 67 | /// let config = CsrfConfig::default().with_cookie_domain(Some("www.helpme.com")); 68 | /// ``` 69 | /// 70 | #[must_use] 71 | pub fn with_cookie_domain(mut self, name: Option) -> Self 72 | where 73 | T: Into>, 74 | { 75 | self.cookie_domain = name.map(|v| v.into()); 76 | self 77 | } 78 | 79 | /// Set's the csrf's lifetime (expiration time). 80 | /// 81 | /// # Examples 82 | /// ```rust 83 | /// use axum_csrf::CsrfConfig; 84 | /// use chrono::Duration; 85 | /// 86 | /// let config = CsrfConfig::default().with_lifetime(Duration::days(32)); 87 | /// ``` 88 | /// 89 | #[must_use] 90 | pub fn with_lifetime(mut self, time: Duration) -> Self { 91 | self.lifespan = time; 92 | self 93 | } 94 | 95 | /// Set's the csrf's cookie's name. 96 | /// 97 | /// # Examples 98 | /// ```rust 99 | /// use axum_csrf::CsrfConfig; 100 | /// 101 | /// let config = CsrfConfig::default().with_cookie_name("my_cookie"); 102 | /// ``` 103 | /// 104 | #[must_use] 105 | pub fn with_cookie_name(mut self, name: &str) -> Self { 106 | self.cookie_name = name.into(); 107 | self 108 | } 109 | 110 | /// Set's the csrf's cookie's path. 111 | /// 112 | /// This is used to deturmine when the cookie takes effect within the website path. 113 | /// Leave as default ("/") for cookie to be used site wide. 114 | /// 115 | /// # Examples 116 | /// ```rust 117 | /// use axum_csrf::CsrfConfig; 118 | /// 119 | /// let config = CsrfConfig::default().with_cookie_path("/"); 120 | /// ``` 121 | /// 122 | #[must_use] 123 | pub fn with_cookie_path(mut self, path: impl Into>) -> Self { 124 | self.cookie_path = path.into(); 125 | self 126 | } 127 | 128 | /// Set's the csrf's cookie's Same Site Setting for Cross-Site restrictions. 129 | /// 130 | /// Only works if Domain is also set to restrict it to that domain only. 131 | /// 132 | /// # Examples 133 | /// ```rust 134 | /// use axum_csrf::CsrfConfig; 135 | /// use cookie::SameSite; 136 | /// 137 | /// let config = CsrfConfig::default().with_cookie_same_site(SameSite::Strict); 138 | /// ``` 139 | /// 140 | #[must_use] 141 | pub fn with_cookie_same_site(mut self, same_site: SameSite) -> Self { 142 | self.cookie_same_site = same_site; 143 | self 144 | } 145 | 146 | /// Set's the csrf's cookie's to http only. 147 | /// 148 | /// # Examples 149 | /// ```rust 150 | /// use axum_csrf::CsrfConfig; 151 | /// 152 | /// let config = CsrfConfig::default().with_http_only(false); 153 | /// ``` 154 | /// 155 | #[must_use] 156 | pub fn with_http_only(mut self, is_set: bool) -> Self { 157 | self.cookie_http_only = is_set; 158 | self 159 | } 160 | 161 | /// Set's the csrf's secure flag for if it gets sent over https. 162 | /// 163 | /// # Examples 164 | /// ```rust 165 | /// use axum_csrf::CsrfConfig; 166 | /// 167 | /// let config = CsrfConfig::default().with_secure(true); 168 | /// ``` 169 | /// 170 | #[must_use] 171 | pub fn with_secure(mut self, is_set: bool) -> Self { 172 | self.cookie_secure = is_set; 173 | self 174 | } 175 | 176 | /// Set's the csrf's token length. 177 | /// 178 | /// # Examples 179 | /// ```rust 180 | /// use axum_csrf::CsrfConfig; 181 | /// 182 | /// let config = CsrfConfig::default().with_cookie_len(16); 183 | /// ``` 184 | /// 185 | #[must_use] 186 | pub fn with_cookie_len(mut self, length: usize) -> Self { 187 | self.cookie_len = length; 188 | self 189 | } 190 | 191 | /// Set's the csrf's cookie encyption key enabling private cookies. 192 | /// 193 | /// When Set it will enforce Private cookies across all Sessions. 194 | /// If you use Key::generate() it will make a new key each server reboot. 195 | /// To prevent this make and save a key to a config file for long term usage. 196 | /// For Extra Security Regenerate the key every so many months to a year. 197 | /// 198 | /// # Examples 199 | /// ```rust 200 | /// use axum_csrf::{Key, CsrfConfig}; 201 | /// 202 | /// let config = CsrfConfig::default().with_key(Key::generate()); 203 | /// ``` 204 | /// 205 | #[must_use] 206 | pub fn with_key(mut self, key: Option) -> Self { 207 | self.key = key; 208 | self 209 | } 210 | 211 | /// Set's the csrf's cookie's salt. 212 | /// 213 | /// This is used to hash the CSRF key for the html insertion. 214 | /// 215 | /// # Examples 216 | /// ```rust 217 | /// use axum_csrf::CsrfConfig; 218 | /// 219 | /// let config = CsrfConfig::default().with_salt("somesalthere"); 220 | /// ``` 221 | /// 222 | #[must_use] 223 | pub fn with_salt(mut self, salt: impl Into>) -> Self { 224 | self.salt = salt.into(); 225 | self 226 | } 227 | 228 | /// Set's the CSRF's prefix_with_host to either true: __Host- gets prefixed to the cookie names false: __Host- does not get prepended. 229 | /// 230 | /// __Host- prefix: Cookies with names starting with __Host- must be set with the secure flag, must be from a secure page (HTTPS), 231 | /// must not have a domain specified (and therefore, are not sent to subdomains), and the path must be /. 232 | /// 233 | /// # Examples 234 | /// ```rust 235 | /// use axum_csrf::CsrfConfig; 236 | /// 237 | /// let config = CsrfConfig::default().with_prefix_with_host(true); 238 | /// ``` 239 | /// 240 | #[must_use] 241 | pub fn with_prefix_with_host(mut self, enable: bool) -> Self { 242 | self.prefix_with_host = enable; 243 | self 244 | } 245 | } 246 | 247 | impl Default for CsrfConfig { 248 | fn default() -> Self { 249 | Self { 250 | // Set to 6hour for default in Database Session stores. 251 | lifespan: Duration::hours(6), 252 | cookie_name: "Csrf_Token".into(), 253 | cookie_path: "/".into(), 254 | cookie_http_only: true, 255 | cookie_secure: false, 256 | cookie_domain: None, 257 | cookie_same_site: SameSite::Lax, 258 | cookie_len: 32, 259 | //We do this by default since we always want this to be secure. 260 | key: Some(Key::generate()), 261 | salt: thread_rng() 262 | .sample_iter(&Alphanumeric) 263 | .take(32) 264 | .map(char::from) 265 | .collect::() 266 | .into(), 267 | prefix_with_host: false, 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/cookies.rs: -------------------------------------------------------------------------------- 1 | use crate::CsrfConfig; 2 | use cookie::{Cookie, CookieJar, Key}; 3 | use http::{ 4 | self, 5 | header::{COOKIE, SET_COOKIE}, 6 | HeaderMap, 7 | }; 8 | use rand::{distributions::Alphanumeric, thread_rng, Rng}; 9 | 10 | pub(crate) trait CookiesExt { 11 | fn get_cookie(&self, name: &str, key: &Option) -> Option>; 12 | fn add_cookie(&mut self, cookie: Cookie<'static>, key: &Option); 13 | } 14 | 15 | impl CookiesExt for CookieJar { 16 | fn get_cookie(&self, name: &str, key: &Option) -> Option> { 17 | if let Some(key) = key { 18 | self.private(key).get(name) 19 | } else { 20 | self.get(name).cloned() 21 | } 22 | } 23 | 24 | fn add_cookie(&mut self, cookie: Cookie<'static>, key: &Option) { 25 | if let Some(key) = key { 26 | self.private_mut(key).add(cookie) 27 | } else { 28 | self.add(cookie) 29 | } 30 | } 31 | } 32 | 33 | pub(crate) fn get_cookies(headers: &mut HeaderMap) -> CookieJar { 34 | let mut jar = CookieJar::new(); 35 | 36 | let cookie_iter = headers 37 | .get_all(COOKIE) 38 | .into_iter() 39 | .filter_map(|value| value.to_str().ok()) 40 | .flat_map(|value| value.split(';')) 41 | .filter_map(|cookie| Cookie::parse_encoded(cookie.to_owned()).ok()); 42 | 43 | for cookie in cookie_iter { 44 | jar.add_original(cookie); 45 | } 46 | 47 | jar 48 | } 49 | 50 | pub(crate) fn set_cookies(jar: CookieJar, headers: &mut HeaderMap) { 51 | for cookie in jar.delta() { 52 | if let Ok(header_value) = cookie.encoded().to_string().parse() { 53 | headers.append(SET_COOKIE, header_value); 54 | } 55 | } 56 | } 57 | 58 | pub(crate) fn get_token(config: &CsrfConfig, headers: &mut HeaderMap) -> String { 59 | let cookie_jar = get_cookies(headers); 60 | let mut prefixed = String::with_capacity(config.cookie_name.len() + "__Host-".len()); 61 | 62 | if config.prefix_with_host { 63 | prefixed.push_str("__Host-"); 64 | prefixed.push_str(&config.cookie_name); 65 | } else { 66 | prefixed.push_str(&config.cookie_name); 67 | } 68 | 69 | //We check if the Cookie Exists as a signed Cookie or not. If so we use the value of the cookie. 70 | //If not we create a new one. 71 | if let Some(cookie) = cookie_jar.get_cookie(&prefixed, &config.key) { 72 | cookie.value().to_owned() 73 | } else { 74 | thread_rng() 75 | .sample_iter(&Alphanumeric) 76 | .take(config.cookie_len) 77 | .map(char::from) 78 | .collect() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum CsrfError { 5 | #[error("Token could not be hashed.")] 6 | PasswordHash, 7 | #[error("Verification Failed.")] 8 | Verify, 9 | #[error("Could not Encode Salt.")] 10 | Salt, 11 | #[error("Could not Hash Token.")] 12 | Token, 13 | } 14 | -------------------------------------------------------------------------------- /src/layer.rs: -------------------------------------------------------------------------------- 1 | use crate::{AxumCsrfService, CsrfConfig}; 2 | use tower_layer::Layer; 3 | 4 | /// CSRF layer struct used to pass key and CsrfConfig around. 5 | #[derive(Clone)] 6 | pub struct CsrfLayer { 7 | pub(crate) config: CsrfConfig, 8 | } 9 | 10 | impl CsrfLayer { 11 | /// Creates the CSRF Protection Layer. 12 | pub fn new(config: CsrfConfig) -> Self { 13 | Self { config } 14 | } 15 | } 16 | 17 | impl Layer for CsrfLayer { 18 | type Service = AxumCsrfService; 19 | 20 | fn layer(&self, inner: S) -> Self::Service { 21 | AxumCsrfService { 22 | config: self.config.clone(), 23 | inner, 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 3 | mod config; 4 | mod error; 5 | mod token; 6 | 7 | pub(crate) mod cookies; 8 | 9 | #[cfg(feature = "layer")] 10 | mod layer; 11 | #[cfg(feature = "layer")] 12 | mod service; 13 | 14 | #[cfg(feature = "layer")] 15 | pub use layer::CsrfLayer; 16 | #[cfg(feature = "layer")] 17 | pub(crate) use service::AxumCsrfService; 18 | 19 | pub use config::{CsrfConfig, Key, SameSite}; 20 | pub use error::CsrfError; 21 | pub use token::CsrfToken; 22 | -------------------------------------------------------------------------------- /src/service.rs: -------------------------------------------------------------------------------- 1 | use crate::{cookies::*, CsrfConfig, CsrfToken}; 2 | use http::Request; 3 | use std::task::{Context, Poll}; 4 | use tower_service::Service; 5 | 6 | #[derive(Clone)] 7 | pub struct AxumCsrfService { 8 | pub(crate) config: CsrfConfig, 9 | pub(crate) inner: S, 10 | } 11 | 12 | impl Service> for AxumCsrfService 13 | where 14 | S: Service>, 15 | { 16 | type Response = S::Response; 17 | type Error = S::Error; 18 | type Future = S::Future; 19 | 20 | #[inline] 21 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 22 | self.inner.poll_ready(cx) 23 | } 24 | 25 | fn call(&mut self, mut req: Request) -> Self::Future { 26 | let config = self.config.clone(); 27 | let token = get_token(&config, req.headers_mut()); 28 | 29 | req.extensions_mut().insert(CsrfToken { token, config }); 30 | self.inner.call(req) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use crate::{cookies::*, CsrfConfig, CsrfError}; 2 | #[cfg(not(feature = "layer"))] 3 | use axum_core::extract::FromRef; 4 | use axum_core::{ 5 | extract::FromRequestParts, 6 | response::{IntoResponse, IntoResponseParts, Response, ResponseParts}, 7 | }; 8 | use cookie::{Cookie, CookieJar, Expiration}; 9 | use http::{self, request::Parts}; 10 | use std::convert::Infallible; 11 | 12 | use base64ct::{Base64, Encoding}; 13 | use hmac::{Hmac, Mac}; 14 | use sha2::Sha256; 15 | 16 | /// This is the Token that is generated when a user is routed to a page. 17 | /// If a Cookie exists then it will be used as the Token. 18 | /// Otherwise a new one is made. 19 | #[derive(Clone)] 20 | pub struct CsrfToken { 21 | pub(crate) token: String, 22 | pub(crate) config: CsrfConfig, 23 | } 24 | 25 | /// this auto pulls a Cookies nd Generates the CsrfToken from the extensions 26 | #[cfg(not(feature = "layer"))] 27 | impl FromRequestParts for CsrfToken 28 | where 29 | S: Send + Sync, 30 | CsrfConfig: FromRef, 31 | { 32 | type Rejection = (http::StatusCode, &'static str); 33 | 34 | async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { 35 | let config = CsrfConfig::from_ref(state); 36 | let token = get_token(&config, &mut parts.headers); 37 | 38 | Ok(CsrfToken { token, config }) 39 | } 40 | } 41 | 42 | #[cfg(feature = "layer")] 43 | impl FromRequestParts for CsrfToken 44 | where 45 | S: Send + Sync, 46 | { 47 | type Rejection = (http::StatusCode, &'static str); 48 | 49 | async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { 50 | let token = parts.extensions.get::().cloned().ok_or(( 51 | http::StatusCode::INTERNAL_SERVER_ERROR, 52 | "Can't extract CsrfConfig. Is `CSRFLayer` enabled?", 53 | ))?; 54 | 55 | Ok(token) 56 | } 57 | } 58 | 59 | impl IntoResponseParts for CsrfToken { 60 | type Error = Infallible; 61 | 62 | fn into_response_parts(self, mut res: ResponseParts) -> Result { 63 | let mut jar = CookieJar::new(); 64 | let lifespan = time::OffsetDateTime::now_utc() + self.config.lifespan; 65 | 66 | let mut cookie_builder = Cookie::build(( 67 | if self.config.prefix_with_host { 68 | let mut prefixed = "__Host-".to_owned(); 69 | prefixed.push_str(&self.config.cookie_name); 70 | prefixed 71 | } else { 72 | self.config.cookie_name.clone() 73 | }, 74 | self.token.clone(), 75 | )) 76 | .path(self.config.cookie_path.clone()) 77 | .secure(self.config.cookie_secure) 78 | .http_only(self.config.cookie_http_only) 79 | .same_site(self.config.cookie_same_site); 80 | 81 | if self.config.lifespan > time::Duration::seconds(0) { 82 | cookie_builder = cookie_builder.expires(Expiration::DateTime(lifespan)); 83 | } 84 | 85 | if let Some(domain) = &self.config.cookie_domain { 86 | cookie_builder = cookie_builder.domain(domain.clone()); 87 | } 88 | 89 | jar.add_cookie(cookie_builder.build(), &self.config.key); 90 | 91 | set_cookies(jar, res.headers_mut()); 92 | Ok(res) 93 | } 94 | } 95 | 96 | impl IntoResponse for CsrfToken { 97 | fn into_response(self) -> Response { 98 | (self, ()).into_response() 99 | } 100 | } 101 | 102 | impl CsrfToken { 103 | ///Used to get the hashed Token to place within the form. 104 | pub fn authenticity_token(&self) -> Result { 105 | let mut mac = Hmac::::new_from_slice(self.config.salt.as_bytes()) 106 | .map_err(|_| CsrfError::Salt)?; 107 | mac.update(self.token.as_bytes()); 108 | 109 | let result = mac.finalize(); 110 | let bytes = result.into_bytes(); 111 | Ok(Base64::encode_string(&bytes)) 112 | } 113 | 114 | ///Verifies that the form returned Token and the cookie tokens match. 115 | pub fn verify(&self, form_authenticity_token: &str) -> Result<(), crate::CsrfError> { 116 | let mut mac = Hmac::::new_from_slice(self.config.salt.as_bytes()) 117 | .map_err(|_| CsrfError::Salt)?; 118 | mac.update(self.token.as_bytes()); 119 | 120 | mac.verify_slice( 121 | &Base64::decode_vec(form_authenticity_token).map_err(|_| CsrfError::PasswordHash)?, 122 | ) 123 | .map_err(|_| CsrfError::Verify)?; 124 | Ok(()) 125 | } 126 | } 127 | --------------------------------------------------------------------------------