Skip to main content

max / makenotwork

Bump the tower/axum middleware cluster (server, kberg) tower-http 0.6 -> 0.7, tower_governor 0.6 -> 0.8, governor 0.8 -> 0.10.4, axum-extra 0.10 -> 0.12.6, as one batch so the layer types stay compatible. kberg's tower-http moves in the same commit; it was held back deliberately to stay in step with the server. tower-sessions 0.14 -> 0.15 is NOT here and stays blocked upstream: tower-sessions-sqlx-store 0.15.0 is still the newest release and still builds against tower-sessions-core 0.14, so PostgresStore would not implement the 0.15 SessionStore trait. Rechecked against crates.io today. Same crate blocks the sqlx 0.9 task. Only code change: tower_governor made GovernorLayer's fields private, so 37 `GovernorLayer { config }` literals across 12 route modules became GovernorLayer::new(config). 36 were a mechanical rewrite; the last one wraps a multi-argument call and was done by hand. A mechanical rewrite at that scale can compile and still be wrong, so the behavior was checked rather than assumed: the rate-limiting suite confirms burst still trips, Retry-After is still emitted, and per-IP limits stay independent. axum-extra crossing two majors puts the cookie layer at risk too, so the session, login, and CSRF workflows were run as a group (45 tests). Full suite: 1889 lib + 1190 integration green, clippy clean at --all-targets --all-features. kberg's lock now carries tower-http twice; the 0.6.11 copy is reqwest 0.13.4's own dependency, not ours to move. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-22 19:40 UTC
Commit: 3935637feb94064b0399005e0ba15033fd0610e8
Parent: 99cc2d1
16 files changed, +140 insertions, -135 deletions
M server/Cargo.lock +75 -19
@@ -1214,15 +1214,16 @@ dependencies = [
1214 1214
1215 1215 [[package]]
1216 1216 name = "axum-extra"
1217 - version = "0.10.3"
1217 + version = "0.12.6"
1218 1218 source = "registry+https://github.com/rust-lang/crates.io-index"
1219 - checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96"
1219 + checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970"
1220 1220 dependencies = [
1221 1221 "axum",
1222 1222 "axum-core",
1223 1223 "bytes",
1224 1224 "cookie",
1225 1225 "form_urlencoded",
1226 + "futures-core",
1226 1227 "futures-util",
1227 1228 "headers",
1228 1229 "http 1.4.2",
@@ -1230,7 +1231,6 @@ dependencies = [
1230 1231 "http-body-util",
1231 1232 "mime",
1232 1233 "pin-project-lite",
1233 - "rustversion",
1234 1234 "serde_core",
1235 1235 "serde_html_form",
1236 1236 "serde_path_to_error",
@@ -3233,9 +3233,9 @@ dependencies = [
3233 3233
3234 3234 [[package]]
3235 3235 name = "governor"
3236 - version = "0.8.1"
3236 + version = "0.10.4"
3237 3237 source = "registry+https://github.com/rust-lang/crates.io-index"
3238 - checksum = "be93b4ec2e4710b04d9264c0c7350cdd62a8c20e5e4ac732552ebb8f0debe8eb"
3238 + checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
3239 3239 dependencies = [
3240 3240 "cfg-if",
3241 3241 "dashmap",
@@ -3243,7 +3243,7 @@ dependencies = [
3243 3243 "futures-timer",
3244 3244 "futures-util",
3245 3245 "getrandom 0.3.4",
3246 - "no-std-compat",
3246 + "hashbrown 0.16.1",
3247 3247 "nonzero_ext",
3248 3248 "parking_lot",
3249 3249 "portable-atomic",
@@ -3559,6 +3559,19 @@ dependencies = [
3559 3559 ]
3560 3560
3561 3561 [[package]]
3562 + name = "hyper-timeout"
3563 + version = "0.5.2"
3564 + source = "registry+https://github.com/rust-lang/crates.io-index"
3565 + checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
3566 + dependencies = [
3567 + "hyper",
3568 + "hyper-util",
3569 + "pin-project-lite",
3570 + "tokio",
3571 + "tower-service",
3572 + ]
3573 +
3574 + [[package]]
3562 3575 name = "hyper-tls"
3563 3576 version = "0.6.0"
3564 3577 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4368,7 +4381,7 @@ dependencies = [
4368 4381 "toml 1.1.3+spec-1.1.0",
4369 4382 "totp-rs",
4370 4383 "tower",
4371 - "tower-http",
4384 + "tower-http 0.7.0",
4372 4385 "tower-sessions",
4373 4386 "tower-sessions-sqlx-store",
4374 4387 "tower_governor",
@@ -4648,12 +4661,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4648 4661 checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
4649 4662
4650 4663 [[package]]
4651 - name = "no-std-compat"
4652 - version = "0.4.1"
4653 - source = "registry+https://github.com/rust-lang/crates.io-index"
4654 - checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
4655 -
4656 - [[package]]
4657 4664 name = "nom"
4658 4665 version = "7.1.3"
4659 4666 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5967,7 +5974,7 @@ dependencies = [
5967 5974 "tokio",
5968 5975 "tokio-rustls",
5969 5976 "tower",
5970 - "tower-http",
5977 + "tower-http 0.6.11",
5971 5978 "tower-service",
5972 5979 "url",
5973 5980 "wasm-bindgen",
@@ -6011,7 +6018,7 @@ dependencies = [
6011 6018 "tokio",
6012 6019 "tokio-rustls",
6013 6020 "tower",
6014 - "tower-http",
6021 + "tower-http 0.6.11",
6015 6022 "tower-service",
6016 6023 "url",
6017 6024 "wasm-bindgen",
@@ -7556,6 +7563,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
7556 7563 checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
7557 7564
7558 7565 [[package]]
7566 + name = "tonic"
7567 + version = "0.14.6"
7568 + source = "registry+https://github.com/rust-lang/crates.io-index"
7569 + checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
7570 + dependencies = [
7571 + "async-trait",
7572 + "axum",
7573 + "base64 0.22.1",
7574 + "bytes",
7575 + "h2",
7576 + "http 1.4.2",
7577 + "http-body 1.0.1",
7578 + "http-body-util",
7579 + "hyper",
7580 + "hyper-timeout",
7581 + "hyper-util",
7582 + "percent-encoding",
7583 + "pin-project",
7584 + "socket2",
7585 + "sync_wrapper",
7586 + "tokio",
7587 + "tokio-stream",
7588 + "tower",
7589 + "tower-layer",
7590 + "tower-service",
7591 + "tracing",
7592 + ]
7593 +
7594 + [[package]]
7559 7595 name = "totp-rs"
7560 7596 version = "5.7.2"
7561 7597 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7579,9 +7615,12 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
7579 7615 dependencies = [
7580 7616 "futures-core",
7581 7617 "futures-util",
7618 + "indexmap",
7582 7619 "pin-project-lite",
7620 + "slab",
7583 7621 "sync_wrapper",
7584 7622 "tokio",
7623 + "tokio-util",
7585 7624 "tower-layer",
7586 7625 "tower-service",
7587 7626 "tracing",
@@ -7611,6 +7650,24 @@ checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
7611 7650 dependencies = [
7612 7651 "bitflags 2.13.0",
7613 7652 "bytes",
7653 + "futures-util",
7654 + "http 1.4.2",
7655 + "http-body 1.0.1",
7656 + "pin-project-lite",
7657 + "tower",
7658 + "tower-layer",
7659 + "tower-service",
7660 + "url",
7661 + ]
7662 +
7663 + [[package]]
7664 + name = "tower-http"
7665 + version = "0.7.0"
7666 + source = "registry+https://github.com/rust-lang/crates.io-index"
7667 + checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233"
7668 + dependencies = [
7669 + "bitflags 2.13.0",
7670 + "bytes",
7614 7671 "futures-core",
7615 7672 "futures-util",
7616 7673 "http 1.4.2",
@@ -7624,11 +7681,9 @@ dependencies = [
7624 7681 "pin-project-lite",
7625 7682 "tokio",
7626 7683 "tokio-util",
7627 - "tower",
7628 7684 "tower-layer",
7629 7685 "tower-service",
7630 7686 "tracing",
7631 - "url",
7632 7687 "uuid",
7633 7688 ]
7634 7689
@@ -7711,9 +7766,9 @@ dependencies = [
7711 7766
7712 7767 [[package]]
7713 7768 name = "tower_governor"
7714 - version = "0.6.0"
7769 + version = "0.8.0"
7715 7770 source = "registry+https://github.com/rust-lang/crates.io-index"
7716 - checksum = "57a2ccff6830fa835371af7541e561a90e4c07b84f72991ebac4b3cb6790dc0d"
7771 + checksum = "44de9b94d849d3c46e06a883d72d408c2de6403367b39df2b1c9d9e7b6736fe6"
7717 7772 dependencies = [
7718 7773 "axum",
7719 7774 "forwarded-header-value",
@@ -7721,6 +7776,7 @@ dependencies = [
7721 7776 "http 1.4.2",
7722 7777 "pin-project",
7723 7778 "thiserror 2.0.18",
7779 + "tonic",
7724 7780 "tower",
7725 7781 "tracing",
7726 7782 ]
@@ -16,7 +16,7 @@ async-trait = "0.1"
16 16
17 17 # Web framework
18 18 axum = { version = "0.8.8", features = ["macros"] }
19 - axum-extra = { version = "0.10.3", features = ["cookie", "form", "query", "typed-header"] }
19 + axum-extra = { version = "0.12.6", features = ["cookie", "form", "query", "typed-header"] }
20 20
21 21 # OpenAPI spec generation
22 22 utoipa = { version = "5", features = ["axum_extras", "chrono", "uuid"] }
@@ -29,7 +29,7 @@ tokio = { version = "1.50.0", features = ["macros", "rt-multi-thread", "net", "s
29 29 tokio-stream = { version = "0.1", features = ["sync"] }
30 30 tokio-util = { version = "0.7", features = ["io"] }
31 31 tower = "0.5.3"
32 - tower-http = { version = "0.6.8", features = ["trace", "fs", "limit", "request-id", "propagate-header", "set-header"] }
32 + tower-http = { version = "0.7.0", features = ["trace", "fs", "limit", "request-id", "propagate-header", "set-header"] }
33 33 tracing = "0.1.44"
34 34 tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] }
35 35
@@ -53,8 +53,8 @@ tower-sessions-sqlx-store = { version = "0.15.0", features = ["postgres"] }
53 53 dashmap = "6"
54 54
55 55 # Rate Limiting
56 - tower_governor = "0.6.0"
57 - governor = "0.8.1"
56 + tower_governor = "0.8.0"
57 + governor = "0.10.4"
58 58
59 59 # JWT (SyncKit)
60 60 jsonwebtoken = "9.3.1"
@@ -548,9 +548,7 @@ pub fn api_routes() -> CsrfRouter<AppState> {
548 548 "/api/email-signup",
549 549 post_csrf_skip("pre-auth landing signup, no session", email_signup),
550 550 )
551 - .route_layer(GovernorLayer {
552 - config: write_rate_limit,
553 - });
551 + .route_layer(GovernorLayer::new(write_rate_limit));
554 552
555 553 // Password/code-verifying TOTP mutations — strict auth-strength rate limit
556 554 // (matching login), not the looser API-write limit, so confirm-code and
@@ -566,9 +564,7 @@ pub fn api_routes() -> CsrfRouter<AppState> {
566 564 "/api/users/me/totp/backup-codes",
567 565 post_csrf(totp::regenerate_backup_codes),
568 566 )
569 - .route_layer(GovernorLayer {
570 - config: totp_sensitive_rate_limit,
571 - });
567 + .route_layer(GovernorLayer::new(totp_sensitive_rate_limit));
572 568
573 569 // Export routes — stricter rate limit
574 570 let export_routes = CsrfRouter::new()
@@ -589,9 +585,7 @@ pub fn api_routes() -> CsrfRouter<AppState> {
589 585 )
590 586 .route("/api/export/content", post_csrf(exports::export_content))
591 587 .route("/api/export/contacts", post_csrf(exports::export_contacts))
592 - .route_layer(GovernorLayer {
593 - config: export_rate_limit,
594 - });
588 + .route_layer(GovernorLayer::new(export_rate_limit));
595 589
596 590 let key_rate_limit = crate::helpers::rate_limiter_ms(
597 591 constants::LICENSE_KEY_RATE_LIMIT_MS,
@@ -639,9 +633,7 @@ pub fn api_routes() -> CsrfRouter<AppState> {
639 633 "/api/v1/license/deactivate",
640 634 post_csrf_skip(LICENSE_BEARER_SKIP, license_keys::license_deactivate),
641 635 )
642 - .route_layer(GovernorLayer {
643 - config: key_rate_limit,
644 - });
636 + .route_layer(GovernorLayer::new(key_rate_limit));
645 637
646 638 let read_rate_limit = crate::helpers::rate_limiter_ms(
647 639 constants::API_READ_RATE_LIMIT_MS,
@@ -706,9 +698,7 @@ pub fn api_routes() -> CsrfRouter<AppState> {
706 698 "/api/v1/items/{id}/license.txt",
707 699 get(license_keys::license_text),
708 700 )
709 - .route_layer(GovernorLayer {
710 - config: read_rate_limit,
711 - });
701 + .route_layer(GovernorLayer::new(read_rate_limit));
712 702
713 703 let validate_rate_limit = crate::helpers::rate_limiter_per_sec(
714 704 constants::VALIDATE_RATE_LIMIT_PER_SEC,
@@ -728,9 +718,7 @@ pub fn api_routes() -> CsrfRouter<AppState> {
728 718 "/api/validate/blog-slug",
729 719 post_csrf(validate::validate_blog_slug),
730 720 )
731 - .route_layer(GovernorLayer {
732 - config: validate_rate_limit,
733 - });
721 + .route_layer(GovernorLayer::new(validate_rate_limit));
734 722
735 723 // Import route needs a higher body limit (base64-encoded CSV up to 10 MB
736 724 // ≈ 14 MB encoded). The global 1 MB RequestBodyLimitLayer would reject it,
@@ -738,12 +726,10 @@ pub fn api_routes() -> CsrfRouter<AppState> {
738 726 let import_routes = CsrfRouter::new()
739 727 .route("/api/users/me/import", post_csrf(imports::start_import))
740 728 .layer(axum::extract::DefaultBodyLimit::max(15 * 1024 * 1024))
741 - .route_layer(GovernorLayer {
742 - config: crate::helpers::rate_limiter_ms(
743 - constants::API_WRITE_RATE_LIMIT_MS,
744 - constants::API_WRITE_RATE_LIMIT_BURST,
745 - ),
746 - });
729 + .route_layer(GovernorLayer::new(crate::helpers::rate_limiter_ms(
730 + constants::API_WRITE_RATE_LIMIT_MS,
731 + constants::API_WRITE_RATE_LIMIT_BURST,
732 + )));
747 733
748 734 // Guest checkout routes — public, no auth, CORS-enabled, stricter rate limit
749 735 let guest_checkout_rate_limit = crate::helpers::rate_limiter_per_sec(
@@ -767,9 +753,7 @@ pub fn api_routes() -> CsrfRouter<AppState> {
767 753 "/api/purchases/claim",
768 754 post_csrf(guest_checkout::claim_purchase),
769 755 )
770 - .route_layer(GovernorLayer {
771 - config: guest_checkout_rate_limit,
772 - });
756 + .route_layer(GovernorLayer::new(guest_checkout_rate_limit));
773 757
774 758 // Guest download route — separate, more lenient per-IP rate limit. Unauth'd
775 759 // and token-gated; the throttle is defense-in-depth against anonymous flooding
@@ -780,9 +764,7 @@ pub fn api_routes() -> CsrfRouter<AppState> {
780 764 );
781 765 let download_routes = CsrfRouter::new()
782 766 .route_get("/download/{token}", get(guest_checkout::guest_download))
783 - .route_layer(GovernorLayer {
784 - config: download_rate_limit,
785 - });
767 + .route_layer(GovernorLayer::new(download_rate_limit));
786 768
787 769 write_routes
788 770 .merge(totp_sensitive_routes)
@@ -53,17 +53,17 @@ pub fn auth_routes() -> CsrfRouter<AppState> {
53 53 .route("/login", with_csrf_manual(
54 54 "POST validates via validate_token_consuming (defense-in-depth on top of SameSite=Lax)",
55 55 get(crate::routes::pages::public::landing::login_page)
56 - .post(login_handler.layer(GovernorLayer { config: auth_rate_limit.clone() })),
56 + .post(login_handler.layer(GovernorLayer::new(auth_rate_limit.clone()))),
57 57 ))
58 58 .route("/auth/passkey/start", with_csrf_skip(
59 59 "pre-auth WebAuthn challenge",
60 60 post(passkey_auth_start)
61 - .layer(GovernorLayer { config: auth_rate_limit.clone() }),
61 + .layer(GovernorLayer::new(auth_rate_limit.clone())),
62 62 ))
63 63 .route("/auth/passkey/finish", with_csrf_skip(
64 64 "pre-auth WebAuthn assertion",
65 65 post(passkey_auth_finish)
66 - .layer(GovernorLayer { config: auth_rate_limit }),
66 + .layer(GovernorLayer::new(auth_rate_limit)),
67 67 ))
68 68 // Routes without auth rate limiting
69 69 .route("/logout", post_csrf(logout_handler))
@@ -71,9 +71,7 @@ pub fn auth_routes() -> CsrfRouter<AppState> {
71 71 // Username validation with its own rate limit
72 72 .route(
73 73 "/api/validate/username",
74 - with_csrf(post(validate_username).layer(GovernorLayer {
75 - config: validate_rate_limit,
76 - })),
74 + with_csrf(post(validate_username).layer(GovernorLayer::new(validate_rate_limit))),
77 75 )
78 76 }
79 77
@@ -546,9 +546,7 @@ pub fn build_routes() -> CsrfRouter<AppState> {
546 546 "/api/sync/builds/apps/{app_id}/builds/{build_id}/cancel",
547 547 post_csrf_skip(SYNC_SKIP, cancel_build),
548 548 )
549 - .route_layer(GovernorLayer {
550 - config: write_rate_limit,
551 - });
549 + .route_layer(GovernorLayer::new(write_rate_limit));
552 550
553 551 let trigger_rate_limit = crate::helpers::rate_limiter_per_sec(
554 552 constants::BUILD_TRIGGER_RATE_LIMIT_PER_SEC,
@@ -560,9 +558,7 @@ pub fn build_routes() -> CsrfRouter<AppState> {
560 558 "/api/internal/builds/trigger",
561 559 post_csrf_skip("internal CI hook: HMAC bearer auth", hook_trigger),
562 560 )
563 - .route_layer(GovernorLayer {
564 - config: trigger_rate_limit,
565 - });
561 + .route_layer(GovernorLayer::new(trigger_rate_limit));
566 562
567 563 mgmt_routes.merge(internal_routes)
568 564 }
@@ -74,9 +74,7 @@ pub fn git_routes() -> Router<AppState> {
74 74 post(raw::smart_http_receive_pack)
75 75 .layer(DefaultBodyLimit::max(constants::GIT_RECEIVE_PACK_MAX_BYTES)),
76 76 )
77 - .route_layer(GovernorLayer {
78 - config: browse_rate_limit,
79 - })
77 + .route_layer(GovernorLayer::new(browse_rate_limit))
80 78 }
81 79
82 80 // ============================================================================
@@ -1078,18 +1078,14 @@ pub fn oauth_routes() -> CsrfRouter<AppState> {
1078 1078 let authorize_routes = CsrfRouter::new()
1079 1079 .route_get("/oauth/authorize", get(authorize_get))
1080 1080 .route("/oauth/authorize", post_csrf_manual("OAuth authorize validates the consent form _csrf in-handler via validate_token_consuming", authorize_post))
1081 - .route_layer(GovernorLayer {
1082 - config: authorize_rate_limit,
1083 - });
1081 + .route_layer(GovernorLayer::new(authorize_rate_limit));
1084 1082
1085 1083 let token_routes = CsrfRouter::new()
1086 1084 .route(
1087 1085 "/oauth/token",
1088 1086 post_csrf_skip("pre-auth OAuth token exchange", token_exchange),
1089 1087 )
1090 - .route_layer(GovernorLayer {
1091 - config: token_rate_limit,
1092 - });
1088 + .route_layer(GovernorLayer::new(token_rate_limit));
1093 1089
1094 1090 // userinfo is DB-amplifying (user + creator-tier lookup) and discovery is a
1095 1091 // public read; govern both so every public OAuth route carries a rate limit
@@ -1104,9 +1100,7 @@ pub fn oauth_routes() -> CsrfRouter<AppState> {
1104 1100 "/.well-known/oauth-authorization-server",
1105 1101 get(discovery_metadata),
1106 1102 )
1107 - .route_layer(GovernorLayer {
1108 - config: read_rate_limit,
1109 - });
1103 + .route_layer(GovernorLayer::new(read_rate_limit));
1110 1104
1111 1105 authorize_routes.merge(token_routes).merge(read_routes)
1112 1106 }
@@ -611,9 +611,7 @@ pub fn ota_routes() -> CsrfRouter<AppState> {
611 611 "/api/v1/sync/ota/apps/{app_id}/releases/{release_id}/artifacts/confirm",
612 612 post_csrf_skip(OTA_SKIP, confirm_artifact),
613 613 )
614 - .route_layer(GovernorLayer {
615 - config: write_rate_limit,
616 - });
614 + .route_layer(GovernorLayer::new(write_rate_limit));
617 615
618 616 let read_rate_limit = crate::helpers::rate_limiter_ms(
619 617 constants::OTA_READ_RATE_LIMIT_MS,
@@ -637,9 +635,7 @@ pub fn ota_routes() -> CsrfRouter<AppState> {
637 635 "/api/v1/sync/ota/{slug}/download/{release_id}/{target}/{arch}",
638 636 get(artifact_download),
639 637 )
640 - .route_layer(GovernorLayer {
641 - config: read_rate_limit,
642 - });
638 + .route_layer(GovernorLayer::new(read_rate_limit));
643 639
644 640 mgmt_routes.merge(public_routes)
645 641 }
@@ -153,9 +153,7 @@ pub fn dashboard_routes() -> CsrfRouter<AppState> {
153 153 "/dashboard/item/{id}/analytics",
154 154 get(main::dashboard_item_analytics),
155 155 )
156 - .route_layer(GovernorLayer {
157 - config: read_rate_limit,
158 - });
156 + .route_layer(GovernorLayer::new(read_rate_limit));
159 157
160 158 CsrfRouter::new()
161 159 .merge(wizards::wizard_routes())
@@ -67,7 +67,5 @@ pub fn email_action_routes() -> CsrfRouter<AppState> {
67 67 get(account::unsubscribe_page).post(account::unsubscribe_handler),
68 68 ),
69 69 )
70 - .layer(GovernorLayer {
71 - config: auth_rate_limit,
72 - })
70 + .layer(GovernorLayer::new(auth_rate_limit))
73 71 }
@@ -80,15 +80,11 @@ pub fn public_routes() -> CsrfRouter<AppState> {
80 80 // any real uptime monitor while capping a flood.
81 81 .route_get(
82 82 "/health",
83 - get(health::health).layer(GovernorLayer {
84 - config: search_rate_limit.clone(),
85 - }),
83 + get(health::health).layer(GovernorLayer::new(search_rate_limit.clone())),
86 84 )
87 85 .route_get(
88 86 "/api/health",
89 - get(health::health_json).layer(GovernorLayer {
90 - config: search_rate_limit.clone(),
91 - }),
87 + get(health::health_json).layer(GovernorLayer::new(search_rate_limit.clone())),
92 88 )
93 89 .route_get("/robots.txt", get(sitemap::robots_txt))
94 90 .route_get("/sitemap.xml", get(sitemap::sitemap_xml))
@@ -102,9 +98,7 @@ pub fn public_routes() -> CsrfRouter<AppState> {
102 98 "join-wizard step 1: pre-auth signup",
103 99 join_wizard::step_account_create,
104 100 )
105 - .layer(GovernorLayer {
106 - config: join_rate_limit,
107 - }),
101 + .layer(GovernorLayer::new(join_rate_limit)),
108 102 )
109 103 .route(
110 104 "/join/step/{step}",
@@ -116,27 +110,20 @@ pub fn public_routes() -> CsrfRouter<AppState> {
116 110 .route_get("/discover", get(discover::discover))
117 111 .route_get(
118 112 "/discover/results",
119 - get(discover::discover_results).layer(GovernorLayer {
120 - config: search_rate_limit.clone(),
121 - }),
113 + get(discover::discover_results).layer(GovernorLayer::new(search_rate_limit.clone())),
122 114 )
123 115 .route_get(
124 116 "/discover/suggestions",
125 - get(discover::search_suggestions_handler).layer(GovernorLayer {
126 - config: search_rate_limit.clone(),
127 - }),
117 + get(discover::search_suggestions_handler)
118 + .layer(GovernorLayer::new(search_rate_limit.clone())),
128 119 )
129 120 .route_get(
130 121 "/discover/tags",
131 - get(discover::tag_tree).layer(GovernorLayer {
132 - config: search_rate_limit.clone(),
133 - }),
122 + get(discover::tag_tree).layer(GovernorLayer::new(search_rate_limit.clone())),
134 123 )
135 124 .route_get(
136 125 "/discover/tag-suggest",
137 - get(discover::tag_suggestions_handler).layer(GovernorLayer {
138 - config: search_rate_limit,
139 - }),
126 + get(discover::tag_suggestions_handler).layer(GovernorLayer::new(search_rate_limit)),
140 127 )
141 128 .route_get("/feed", get(feed::feed_page))
142 129 .route_get("/u/{username}", get(content::user_page))
@@ -176,9 +163,7 @@ pub fn public_routes() -> CsrfRouter<AppState> {
176 163 "2FA verification: pre-promotion to full auth, no session yet",
177 164 two_factor::verify_two_factor,
178 165 )
179 - .layer(GovernorLayer {
180 - config: twofa_rate_limit,
181 - }),
166 + .layer(GovernorLayer::new(twofa_rate_limit)),
182 167 )
183 168 }
184 169
@@ -39,9 +39,7 @@ pub fn sandbox_routes() -> CsrfRouter<AppState> {
39 39 .route_get("/sandbox", get(sandbox_page))
40 40 .route(
41 41 "/sandbox",
42 - post_csrf(create_sandbox).layer(GovernorLayer {
43 - config: sandbox_rate_limit,
44 - }),
42 + post_csrf(create_sandbox).layer(GovernorLayer::new(sandbox_rate_limit)),
45 43 )
46 44 }
47 45
@@ -181,9 +181,7 @@ pub fn storage_routes() -> CsrfRouter<AppState> {
181 181 .route_get("/api/media", get(media::media_list))
182 182 .route_get("/api/media/folders", get(media::media_folders))
183 183 .route("/api/media/{id}", delete_csrf(media::media_delete))
184 - .route_layer(GovernorLayer {
185 - config: upload_rate_limit,
186 - });
184 + .route_layer(GovernorLayer::new(upload_rate_limit));
187 185
188 186 let stream_rate_limit = crate::helpers::rate_limiter_ms(
189 187 constants::STREAM_RATE_LIMIT_MS,
@@ -196,9 +194,7 @@ pub fn storage_routes() -> CsrfRouter<AppState> {
196 194 "/api/versions/{version_id}/download",
197 195 get(downloads::version_download),
198 196 )
199 - .route_layer(GovernorLayer {
200 - config: stream_rate_limit,
201 - });
197 + .route_layer(GovernorLayer::new(stream_rate_limit));
202 198
203 199 upload_routes.merge(stream_routes)
204 200 }
@@ -668,9 +668,7 @@ pub fn synckit_routes(synckit_jwt_secret: Option<std::sync::Arc<String>>) -> Csr
668 668 "/api/v1/sync/app/pricing",
669 669 post_csrf_skip(SYNCKIT_API_KEY_SKIP, sync::get_app_pricing),
670 670 )
671 - .route_layer(GovernorLayer {
672 - config: auth_rate_limit,
673 - });
671 + .route_layer(GovernorLayer::new(auth_rate_limit));
674 672
675 673 let sync_ip_rate_limit = crate::helpers::rate_limiter_ms(
676 674 constants::SYNCKIT_SYNC_RATE_LIMIT_MS,
@@ -871,14 +869,10 @@ pub fn synckit_routes(synckit_jwt_secret: Option<std::sync::Arc<String>>) -> Csr
871 869 )
872 870 // Per-app rate limit (inner layer runs first): prevents one developer's
873 871 // app from starving other apps. Extracts app ID from JWT payload.
874 - .route_layer(GovernorLayer {
875 - config: sync_app_rate_limit,
876 - })
872 + .route_layer(GovernorLayer::new(sync_app_rate_limit))
877 873 // Per-IP rate limit (outer layer): prevents a single client from
878 874 // overwhelming the endpoint regardless of which app they claim.
879 - .route_layer(GovernorLayer {
880 - config: sync_ip_rate_limit,
881 - });
875 + .route_layer(GovernorLayer::new(sync_ip_rate_limit));
882 876
883 877 // App management endpoints use session auth (no extra rate limit beyond global)
884 878 let app_routes = CsrfRouter::new()
@@ -681,7 +681,7 @@ dependencies = [
681 681 "tokio",
682 682 "tokio-stream",
683 683 "tower",
684 - "tower-http",
684 + "tower-http 0.7.0",
685 685 "tracing",
686 686 "tracing-subscriber",
687 687 "uuid",
@@ -959,7 +959,7 @@ dependencies = [
959 959 "tokio",
960 960 "tokio-rustls",
961 961 "tower",
962 - "tower-http",
962 + "tower-http 0.6.11",
963 963 "tower-service",
964 964 "url",
965 965 "wasm-bindgen",
@@ -1459,11 +1459,27 @@ dependencies = [
1459 1459 "tower",
1460 1460 "tower-layer",
1461 1461 "tower-service",
1462 - "tracing",
1463 1462 "url",
1464 1463 ]
1465 1464
1466 1465 [[package]]
1466 + name = "tower-http"
1467 + version = "0.7.0"
1468 + source = "registry+https://github.com/rust-lang/crates.io-index"
1469 + checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233"
1470 + dependencies = [
1471 + "bitflags",
1472 + "bytes",
1473 + "http",
1474 + "http-body",
1475 + "percent-encoding",
1476 + "pin-project-lite",
1477 + "tower-layer",
1478 + "tower-service",
1479 + "tracing",
1480 + ]
1481 +
1482 + [[package]]
1467 1483 name = "tower-layer"
1468 1484 version = "0.3.3"
1469 1485 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -23,7 +23,7 @@ uuid = { version = "1", features = ["v4", "serde"] }
23 23
24 24 axum = { version = "0.8", optional = true }
25 25 tower = { version = "0.5", optional = true }
26 - tower-http = { version = "0.6", optional = true, features = ["trace"] }
26 + tower-http = { version = "0.7", optional = true, features = ["trace"] }
27 27
28 28 reqwest = { version = "0.13", optional = true, features = ["json"] }
29 29