Skip to content

Commit 410a7ff

Browse files
authored
Merge pull request #39 from Tuntii/feature/framework-ergonomics-update
Feature/framework ergonomics update
2 parents 87f05a2 + ec236ee commit 410a7ff

File tree

7 files changed

+356
-26
lines changed

7 files changed

+356
-26
lines changed

Cargo.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ members = [
2121
]
2222

2323
[workspace.package]
24-
version = "0.1.12"
24+
version = "0.1.13"
2525
edition = "2021"
2626
authors = ["RustAPI Contributors"]
2727
license = "MIT OR Apache-2.0"
@@ -100,14 +100,14 @@ indicatif = "0.17"
100100
console = "0.15"
101101

102102
# Internal crates
103-
rustapi-core = { path = "crates/rustapi-core", version = "0.1.12", default-features = false }
104-
rustapi-macros = { path = "crates/rustapi-macros", version = "0.1.12" }
105-
rustapi-validate = { path = "crates/rustapi-validate", version = "0.1.12" }
106-
rustapi-openapi = { path = "crates/rustapi-openapi", version = "0.1.12", default-features = false }
107-
rustapi-extras = { path = "crates/rustapi-extras", version = "0.1.12" }
108-
rustapi-toon = { path = "crates/rustapi-toon", version = "0.1.12" }
109-
rustapi-ws = { path = "crates/rustapi-ws", version = "0.1.12" }
110-
rustapi-view = { path = "crates/rustapi-view", version = "0.1.12" }
111-
rustapi-testing = { path = "crates/rustapi-testing", version = "0.1.12" }
112-
rustapi-jobs = { path = "crates/rustapi-jobs", version = "0.1.12" }
103+
rustapi-core = { path = "crates/rustapi-core", version = "0.1.13", default-features = false }
104+
rustapi-macros = { path = "crates/rustapi-macros", version = "0.1.13" }
105+
rustapi-validate = { path = "crates/rustapi-validate", version = "0.1.13" }
106+
rustapi-openapi = { path = "crates/rustapi-openapi", version = "0.1.13", default-features = false }
107+
rustapi-extras = { path = "crates/rustapi-extras", version = "0.1.13" }
108+
rustapi-toon = { path = "crates/rustapi-toon", version = "0.1.13" }
109+
rustapi-ws = { path = "crates/rustapi-ws", version = "0.1.13" }
110+
rustapi-view = { path = "crates/rustapi-view", version = "0.1.13" }
111+
rustapi-testing = { path = "crates/rustapi-testing", version = "0.1.13" }
112+
rustapi-jobs = { path = "crates/rustapi-jobs", version = "0.1.13" }
113113

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ rustapi-rs = { version = "0.1.9", features = ["jwt", "cors", "toon", "ws", "view
204204
| `jwt` | JWT authentication with `AuthUser<T>` extractor |
205205
| `cors` | CORS middleware with builder pattern |
206206
| `rate-limit` | IP-based rate limiting |
207+
| `csrf` | CSRF protection with Double-Submit Cookie pattern |
207208
| `toon` | LLM-optimized TOON format responses |
208209
| `ws` | WebSocket support with broadcast |
209210
| `view` | Template engine (Tera) for SSR |

crates/rustapi-extras/src/lib.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,33 @@
1010
//! - `jwt` - JWT authentication middleware and `AuthUser<T>` extractor
1111
//! - `cors` - CORS middleware with builder pattern configuration
1212
//! - `rate-limit` - IP-based rate limiting middleware
13+
//! - `csrf` - CSRF protection using Double-Submit Cookie pattern
1314
//! - `config` - Configuration management with `.env` file support
1415
//! - `cookies` - Cookie parsing extractor
1516
//! - `sqlx` - SQLx database error conversion to ApiError
1617
//! - `insight` - Traffic insight middleware for analytics and debugging
1718
//! - `extras` - Meta feature enabling jwt, cors, and rate-limit
1819
//! - `full` - All features enabled
1920
//!
21+
//! ## CSRF Protection Example
22+
//!
23+
//! ```rust,ignore
24+
//! use rustapi_core::RustApi;
25+
//! use rustapi_extras::csrf::{CsrfConfig, CsrfLayer, CsrfToken};
26+
//!
27+
//! let config = CsrfConfig::new()
28+
//! .cookie_name("csrf_token")
29+
//! .header_name("X-CSRF-Token");
30+
//!
31+
//! let app = RustApi::new()
32+
//! .layer(CsrfLayer::new(config));
33+
//! ```
34+
//!
2035
//! ## Example
2136
//!
2237
//! ```toml
2338
//! [dependencies]
24-
//! rustapi-extras = { version = "0.1", features = ["jwt", "cors", "insight"] }
39+
//! rustapi-extras = { version = "0.1", features = ["jwt", "cors", "csrf"] }
2540
//! ```
2641
2742
#![warn(missing_docs)]

docs/cookbook/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
- [Part IV: Recipes](recipes/README.md)
2929
- [Creating Resources](recipes/crud_resource.md)
3030
- [JWT Authentication](recipes/jwt_auth.md)
31+
- [CSRF Protection](recipes/csrf_protection.md)
3132
- [Database Integration](recipes/db_integration.md)
3233
- [File Uploads](recipes/file_uploads.md)
3334
- [Custom Middleware](recipes/custom_middleware.md)
3435
- [Real-time Chat](recipes/websockets.md)
3536
- [Production Tuning](recipes/high_performance.md)
3637

38+

docs/cookbook/src/crates/rustapi_extras.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This crate is a collection of production-ready middleware. Everything is behind
1111
|---------|-----------|
1212
| `jwt` | `JwtLayer`, `AuthUser` extractor |
1313
| `cors` | `CorsLayer` |
14+
| `csrf` | `CsrfLayer`, `CsrfToken` extractor |
1415
| `audit` | `AuditStore`, `AuditLogger` |
1516
| `rate-limit` | `RateLimitLayer` |
1617

@@ -25,6 +26,46 @@ let app = RustApi::new()
2526
.route("/", get(handler));
2627
```
2728

29+
## CSRF Protection
30+
31+
Cross-Site Request Forgery protection using the Double-Submit Cookie pattern.
32+
33+
```rust
34+
use rustapi_extras::csrf::{CsrfConfig, CsrfLayer, CsrfToken};
35+
36+
// Configure CSRF middleware
37+
let csrf_config = CsrfConfig::new()
38+
.cookie_name("csrf_token")
39+
.header_name("X-CSRF-Token")
40+
.cookie_secure(true); // HTTPS only
41+
42+
let app = RustApi::new()
43+
.layer(CsrfLayer::new(csrf_config))
44+
.route("/form", get(show_form))
45+
.route("/submit", post(handle_submit));
46+
```
47+
48+
### Extracting the Token
49+
50+
Use the `CsrfToken` extractor to access the token in handlers:
51+
52+
```rust
53+
#[rustapi_rs::get("/form")]
54+
async fn show_form(token: CsrfToken) -> Html<String> {
55+
Html(format!(r#"
56+
<input type="hidden" name="_csrf" value="{}" />
57+
"#, token.as_str()))
58+
}
59+
```
60+
61+
### How It Works
62+
63+
1. **Safe methods** (`GET`, `HEAD`) generate and set the token cookie
64+
2. **Unsafe methods** (`POST`, `PUT`, `DELETE`) require the token in the `X-CSRF-Token` header
65+
3. If header doesn't match cookie → `403 Forbidden`
66+
67+
See [CSRF Protection Recipe](../recipes/csrf_protection.md) for a complete guide.
68+
2869
## Audit Logging
2970

3071
For enterprise compliance (GDPR/SOC2), the `audit` feature provides a structured way to record sensitive actions.
@@ -40,3 +81,4 @@ async fn delete_user(
4081
);
4182
}
4283
```
84+

0 commit comments

Comments
 (0)