diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 8cb9f5fc..5e3639e0 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -11,7 +11,7 @@ Rwf will automatically load configuration settings from that file, as they are n The configuration file is using the [TOML language](https://toml.io/). If you're not familiar with TOML, it's pretty simple and expressive language commonly used in the world of Rust programming. -Rwf configuration file is split into multiple sections. The `[general]` section controls various options such as logging settings, and which secret key to use for [encryption](encryption.md). The `[database]` +Rwf configuration file is split into multiple sections. The `[general]` section controls various options such as logging settings, and which secret key to use for [encryption](security/encryption.md). The `[database]` section configures database connection settings, like the database URL, connection pool size, and others. ### `[general]` @@ -19,8 +19,9 @@ section configures database connection settings, like the database URL, connecti | Setting | Description | Default | |---------|-------------|---------| | `log_queries` | Toggles logging of all SQL queries executed by the [ORM](models/index.md). | `false` | -| `secret_key` | Secret key, encoded using base64, used for [encryption](encryption.md). | Randomly generated | +| `secret_key` | Secret key, encoded using base64, used for [encryption](security/encryption.md). | Randomly generated | | `cache_templates` | Toggle caching of [dynamic templates](views/templates/index.md). | `false` in debug, `true` in release | +| `csrf_protection` | Validate the [CSRF](security/CSRF.md) token is present on requests that mutate your application (POST, PUT, PATCH). | `true` | #### Secret key diff --git a/docs/docs/controllers/cookies.md b/docs/docs/controllers/cookies.md index 35c3ae41..639ef2c5 100644 --- a/docs/docs/controllers/cookies.md +++ b/docs/docs/controllers/cookies.md @@ -61,7 +61,7 @@ response .add_private(cookie)?; ``` -Cookies are [encrypted](../encryption.md) with AES-128, using the security key set in the [configuration](../configuration.md). +Cookies are [encrypted](../security/encryption.md) with AES-128, using the security key set in the [configuration](../configuration.md). ### Read private cookies diff --git a/docs/docs/controllers/sessions.md b/docs/docs/controllers/sessions.md index 2efe8ce4..f13c0dc7 100644 --- a/docs/docs/controllers/sessions.md +++ b/docs/docs/controllers/sessions.md @@ -1,6 +1,6 @@ # Sessions -A session is an [encrypted](../encryption.md) [cookie](cookies.md) managed by Rwf. It contains a unique identifier for each browser using your web app. All standard-compliant browsers connecting to Rwf-powered apps will have a Rwf session set automatically, and should send it back on each request. +A session is an [encrypted](../security/encryption.md) [cookie](cookies.md) managed by Rwf. It contains a unique identifier for each browser using your web app. All standard-compliant browsers connecting to Rwf-powered apps will have a Rwf session set automatically, and should send it back on each request. ## Session types diff --git a/docs/docs/security/CSRF.md b/docs/docs/security/CSRF.md new file mode 100644 index 00000000..30d4c82e --- /dev/null +++ b/docs/docs/security/CSRF.md @@ -0,0 +1,95 @@ +# CSRF protection + +Cross-site request forgery[^1] (or CSRF) is a type of attack which uses your website's forms to trick the user into submitting data to your application from somewhere else. Rwf comes with [middleware](../controllers/middleware.md) to protect your application against such attacks. + +## Enable CSRF protection + +CSRF protection is enabled by default. When users make `POST`, `PUT`, and `PATCH` requests to your app, Rwf will check for the presence of a CSRF token. If the token is not there, or has expired, the request will be blocked and `HTTP 400 - Bad Request` response will be returned. + +## Passing the token + +The CSRF token can be passed using one of two methods: + +- `X-CSRF-Token` HTTP header +- `` inside a form + +If you're submitting a form, you can add the `rwf_csrf_token` input automatically: + +```html +
+ <%= rwf_token() %> +
+``` + +If you're making AJAX requests (using `fetch`, for example), you can pass the token via the header. If you're using Stimulus (which comes standard with Rwf), you can pass the token via a data attribute to the Stimulus controller: + +=== "HTML" + ```html +
+ +
+ ``` +=== "JavaScript" + ```javascript + import { Controller } from "hotwired/stimulus" + + export default class LoginController extends Controller { + + // Send request with CSRF token included. + sendRequest() { + const csrfToken = this.element.dataset.csrfToken; + + fetch("/login", { + headers: { + "X-CSRF-Token": csrfToken, + } + }) + } + + } + ``` + +## Disable CSRF protection + +If you want to disable CSRF protection, you can do so globally by toggling the `csrf_protection` [configuration option](../configuration.md) to `false`, or on the controller level by implementing the `fn skip_csrf(&self)` method: + +```rust +use rwf::prelude::*; + +#[derive(Default)] +struct IndexController; + +impl Controller for IndexController { + /// Disable CSRF protection for this controller. + fn skip_csrf(&self) -> bool { + true + } + + /* ... */ +} +``` + +### REST + +If you're using JavaScript frameworks like React or Vue for your frontend, it's common to disable CSRF protection on your [REST](../controllers/REST/index.md) controllers. To do so, you can add the `#[skip_csrf]` attribute to your `ModelController`, for example: + +```rust +#[derive(macros::ModelController)] +#[skip_csrf] +struct Users; +``` + +You can always disable CSRF globally via [configuration](#disable-csrf-protection) and enable it only on the controllers that serve HTML forms. + +## Token validity + +The CSRF token is valid for the same duration as Rwf [sessions](../controllers/sessions.md). By default, this is set to 4 weeks. A new token is generated every time your users load a page which contains a token generated with the built-in template functions. + +## WSGI / Rack controllers + +Rwf CSRF protection is disabled for [Python](../migrating-from-python.md) and [Rails](../migrating-from-rails.md) applications. It's expected that Django/Flask/Rails applications will use their own CSRF protection middleware. + +[^1]: [https://en.wikipedia.org/wiki/Cross-site_request_forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) diff --git a/docs/docs/encryption.md b/docs/docs/security/encryption.md similarity index 81% rename from docs/docs/encryption.md rename to docs/docs/security/encryption.md index 3f1b1232..7163d0a6 100644 --- a/docs/docs/encryption.md +++ b/docs/docs/security/encryption.md @@ -1,39 +1,39 @@ -# Encryption - -Rwf uses [AES-128](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) for encrypting user [sessions](controllers/sessions.md) and private [cookies](controllers/cookies.md). The same functionality is available through the [`rwf::crypto`](https://docs.rs/rwf/latest/rwf/crypto/index.html) module to encrypt and decrypt arbitrary data. - -## Encrypt data - -To encrypt data using AES-128 and the application secret key, you can use the [`encrypt`](https://docs.rs/rwf/latest/rwf/crypto/fn.encrypt.html) function, for example: - -```rust -use rwf::crypto::encrypt; - -let data = serde_json::json!({ - "user": "test", - "password": "hunter2" -}); - -// JSON is converted into a byte array. -let data = serde_json::to_vec(&data).unwrap(); - -// Data is encrypted with AES. -let encrypted = encrypt(&data).unwrap(); -``` - -Any kind of data can be encrypted, as long as it's serializable to an array of bytes. Serialization can typically be achieved by using [`serde`](https://docs.rs/serde/latest/serde/). - -Encryption produces a base64-encoded UTF-8 string. You can save this string in the database or send it via an insecure medium like email. - -## Decrypt data - -To decrypt the data, you can call the [`decrypt`](https://docs.rs/rwf/latest/rwf/crypto/fn.decrypt.html) function on the string produced by the `encrypt` function. The decryption algorithm will automatically convert the base64-encoded string to bytes and decrypt those bytes using the secret key, for example: - -```rust -use rwf::crypto::decrypt; - -let decrypted = decrypt(&encrypted).unwrap(); -let json = serde_json::from_slice(&decrypted).unwrap(); - -assert_eq!(json["user"], "test"); -``` +# Encryption + +Rwf uses [AES-128](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) for encrypting user [sessions](../controllers/sessions.md) and private [cookies](../controllers/cookies.md). The same functionality is available through the [`rwf::crypto`](https://docs.rs/rwf/latest/rwf/crypto/index.html) module to encrypt and decrypt arbitrary data. + +## Encrypt data + +To encrypt data using AES-128 and the application secret key, you can use the [`encrypt`](https://docs.rs/rwf/latest/rwf/crypto/fn.encrypt.html) function, for example: + +```rust +use rwf::crypto::encrypt; + +let data = serde_json::json!({ + "user": "test", + "password": "hunter2" +}); + +// JSON is converted into a byte array. +let data = serde_json::to_vec(&data).unwrap(); + +// Data is encrypted with AES. +let encrypted = encrypt(&data).unwrap(); +``` + +Any kind of data can be encrypted, as long as it's serializable to an array of bytes. Serialization can typically be achieved by using [`serde`](https://docs.rs/serde/latest/serde/). + +Encryption produces a base64-encoded UTF-8 string. You can save this string in the database or send it via an insecure medium like email. + +## Decrypt data + +To decrypt the data, you can call the [`decrypt`](https://docs.rs/rwf/latest/rwf/crypto/fn.decrypt.html) function on the string produced by the `encrypt` function. The decryption algorithm will automatically convert the base64-encoded string to bytes and decrypt those bytes using the secret key, for example: + +```rust +use rwf::crypto::decrypt; + +let decrypted = decrypt(&encrypted).unwrap(); +let json = serde_json::from_slice(&decrypted).unwrap(); + +assert_eq!(json["user"], "test"); +```