Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ members = [
]

[workspace.package]
version = "0.1.12"
version = "0.1.13"
edition = "2021"
authors = ["RustAPI Contributors"]
license = "MIT OR Apache-2.0"
Expand Down Expand Up @@ -100,14 +100,14 @@ indicatif = "0.17"
console = "0.15"

# Internal crates
rustapi-core = { path = "crates/rustapi-core", version = "0.1.12", default-features = false }
rustapi-macros = { path = "crates/rustapi-macros", version = "0.1.12" }
rustapi-validate = { path = "crates/rustapi-validate", version = "0.1.12" }
rustapi-openapi = { path = "crates/rustapi-openapi", version = "0.1.12", default-features = false }
rustapi-extras = { path = "crates/rustapi-extras", version = "0.1.12" }
rustapi-toon = { path = "crates/rustapi-toon", version = "0.1.12" }
rustapi-ws = { path = "crates/rustapi-ws", version = "0.1.12" }
rustapi-view = { path = "crates/rustapi-view", version = "0.1.12" }
rustapi-testing = { path = "crates/rustapi-testing", version = "0.1.12" }
rustapi-jobs = { path = "crates/rustapi-jobs", version = "0.1.12" }
rustapi-core = { path = "crates/rustapi-core", version = "0.1.13", default-features = false }
rustapi-macros = { path = "crates/rustapi-macros", version = "0.1.13" }
rustapi-validate = { path = "crates/rustapi-validate", version = "0.1.13" }
rustapi-openapi = { path = "crates/rustapi-openapi", version = "0.1.13", default-features = false }
rustapi-extras = { path = "crates/rustapi-extras", version = "0.1.13" }
rustapi-toon = { path = "crates/rustapi-toon", version = "0.1.13" }
rustapi-ws = { path = "crates/rustapi-ws", version = "0.1.13" }
rustapi-view = { path = "crates/rustapi-view", version = "0.1.13" }
rustapi-testing = { path = "crates/rustapi-testing", version = "0.1.13" }
rustapi-jobs = { path = "crates/rustapi-jobs", version = "0.1.13" }

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ rustapi-rs = { version = "0.1.9", features = ["jwt", "cors", "toon", "ws", "view
| `jwt` | JWT authentication with `AuthUser<T>` extractor |
| `cors` | CORS middleware with builder pattern |
| `rate-limit` | IP-based rate limiting |
| `csrf` | CSRF protection with Double-Submit Cookie pattern |
| `toon` | LLM-optimized TOON format responses |
| `ws` | WebSocket support with broadcast |
| `view` | Template engine (Tera) for SSR |
Expand Down
17 changes: 16 additions & 1 deletion crates/rustapi-extras/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,33 @@
//! - `jwt` - JWT authentication middleware and `AuthUser<T>` extractor
//! - `cors` - CORS middleware with builder pattern configuration
//! - `rate-limit` - IP-based rate limiting middleware
//! - `csrf` - CSRF protection using Double-Submit Cookie pattern
//! - `config` - Configuration management with `.env` file support
//! - `cookies` - Cookie parsing extractor
//! - `sqlx` - SQLx database error conversion to ApiError
//! - `insight` - Traffic insight middleware for analytics and debugging
//! - `extras` - Meta feature enabling jwt, cors, and rate-limit
//! - `full` - All features enabled
//!
//! ## CSRF Protection Example
//!
//! ```rust,ignore
//! use rustapi_core::RustApi;
//! use rustapi_extras::csrf::{CsrfConfig, CsrfLayer, CsrfToken};
//!
//! let config = CsrfConfig::new()
//! .cookie_name("csrf_token")
//! .header_name("X-CSRF-Token");
//!
//! let app = RustApi::new()
//! .layer(CsrfLayer::new(config));
//! ```
//!
//! ## Example
//!
//! ```toml
//! [dependencies]
//! rustapi-extras = { version = "0.1", features = ["jwt", "cors", "insight"] }
//! rustapi-extras = { version = "0.1", features = ["jwt", "cors", "csrf"] }
//! ```

#![warn(missing_docs)]
Expand Down
2 changes: 2 additions & 0 deletions docs/cookbook/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
- [Part IV: Recipes](recipes/README.md)
- [Creating Resources](recipes/crud_resource.md)
- [JWT Authentication](recipes/jwt_auth.md)
- [CSRF Protection](recipes/csrf_protection.md)
- [Database Integration](recipes/db_integration.md)
- [File Uploads](recipes/file_uploads.md)
- [Custom Middleware](recipes/custom_middleware.md)
- [Real-time Chat](recipes/websockets.md)
- [Production Tuning](recipes/high_performance.md)


42 changes: 42 additions & 0 deletions docs/cookbook/src/crates/rustapi_extras.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This crate is a collection of production-ready middleware. Everything is behind
|---------|-----------|
| `jwt` | `JwtLayer`, `AuthUser` extractor |
| `cors` | `CorsLayer` |
| `csrf` | `CsrfLayer`, `CsrfToken` extractor |
| `audit` | `AuditStore`, `AuditLogger` |
| `rate-limit` | `RateLimitLayer` |

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

## CSRF Protection

Cross-Site Request Forgery protection using the Double-Submit Cookie pattern.

```rust
use rustapi_extras::csrf::{CsrfConfig, CsrfLayer, CsrfToken};

// Configure CSRF middleware
let csrf_config = CsrfConfig::new()
.cookie_name("csrf_token")
.header_name("X-CSRF-Token")
.cookie_secure(true); // HTTPS only
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method name is incorrect. The actual method is secure(), not cookie_secure(). According to the implementation in config.rs, the builder method is named secure().

Suggested change
.cookie_secure(true); // HTTPS only
.secure(true); // HTTPS only

Copilot uses AI. Check for mistakes.

let app = RustApi::new()
.layer(CsrfLayer::new(csrf_config))
.route("/form", get(show_form))
.route("/submit", post(handle_submit));
```

### Extracting the Token

Use the `CsrfToken` extractor to access the token in handlers:

```rust
#[rustapi_rs::get("/form")]
async fn show_form(token: CsrfToken) -> Html<String> {
Html(format!(r#"
<input type="hidden" name="_csrf" value="{}" />
"#, token.as_str()))
}
```

### How It Works

1. **Safe methods** (`GET`, `HEAD`) generate and set the token cookie
2. **Unsafe methods** (`POST`, `PUT`, `DELETE`) require the token in the `X-CSRF-Token` header
3. If header doesn't match cookie → `403 Forbidden`

See [CSRF Protection Recipe](../recipes/csrf_protection.md) for a complete guide.

## Audit Logging

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

Loading
Loading