Skip to content

Commit

Permalink
save
Browse files Browse the repository at this point in the history
  • Loading branch information
levkk committed Oct 18, 2024
1 parent 2ba235f commit 2122def
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 104 deletions.
30 changes: 30 additions & 0 deletions docs/docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
# Configuration

Rwf supports file-based and environment-based configuration. The list of configurable options are ever growing, and currently supported features are listed below.

## Enabling configuration

To configure Rwf, place a file called `rwf.toml` into the wording directory of your app. During development, this should be the root directory of your Cargo project. At startup,
Rwf will automatically load configuration settings from that file, as they are needed by the application.

## Available settings

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). The `[database]`
section configures database connection settings, like the database URL, connection pool size, and others.

### `[general]`

| Setting | Description | Example |
|---------|-------------|---------|
| `log_queries` | Toggles logging of all SQL queries executed by the [ORM](../models/). | `log_queries = true` |
| `secret_key` | Secret key, encoded using base64, used for [encryption](../encryption). | `secret_key = "..."` |

#### Secret key

The secret key is a base64-encoded string of randomly generated data. A valid secret key contains 256 bits of entropy and _must_ be generated using a [_secure_](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) random number generator.

### `[database]`

| Setting | Description | Example |
|---------|-------------|---------|
46 changes: 46 additions & 0 deletions docs/docs/controllers/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Authentication

Rwf has multiple authentication and authorization mechanisms. Different kinds of authenitcation require their own kinds of user-supplied credentials. The most commonly used mechanism is [Session](../sessions) authentication, which has built-in methods for easy use in [controllers](../).

## Session authentication

[Session](../sessions) authentication checks that the user-supplied session cookie is valid (not expired). If that's not the case, the request is either rejected with a `403 - Forbidden` or provided an endpoint to re-authenticate, e.g. using a username and password, with a `302 - Found` redirect.

### Enable session authentication

To enable session authentication, it needs to be configured on the controller by implementing the [`auth`](https://docs.rs/rwf/latest/rwf/controller/trait.Controller.html#method.auth) method:

```rust
use rwf::prelude::*;

/// A controller that requires authentication.
struct Private {
auth: AuthHandler,
}

impl Default for Private {
fn default() -> Self {
Private {
// Redirect unauthenitcated requests to the `/login` route.
auth: AuthHandler::new(
SessionAuth::redirect("/login"),
),
}
}
}

#[async_trait]
impl Controller for Private {
/// Enable authentication on this controller.
fn auth(&self) -> &AuthHandler {
&self.auth
}

/* ... */
}
```

## Basic authentication

HTTP Basic is a form of authentication using a global username and password. It's not particularly secure, but it's good enough to protect an endpoint quickly against random visitors. Enabling basic authentication is as simple
as setting a [`AuthHandler`](https://docs.rs/rwf/latest/rwf/controller/auth/struct.AuthHandler.html) with [`BasicAuth`](https://docs.rs/rwf/latest/rwf/controller/auth/struct.BasicAuth.html) on your [controller](../). See [examples/auth](https://github.com/levkk/rwf/tree/main/examples/auth) for examples on how to do this.
44 changes: 44 additions & 0 deletions docs/docs/controllers/sessions.md
Original file line number Diff line number Diff line change
@@ -1 +1,45 @@
# Sessions

A session is an [encrypted](../../encryption) [cookie](../cookies) managed by Rwf. It contains a unique identifier for each browser using your web app. All standard-compliant browsers talking to Rwf-powered apps will have a Rwf session set, and should send it back on each request.

## Check for valid session

All [controllers](../) can check for the presence of a valid session:

```rust
let session = request.session();

let valid = session
.map(|session| !session.expired())
.unwrap_or(false);
```

Unless the session cookie is set and has been encrypted using the correct algorithm and secret key, calling [`session`](https://docs.rs/rwf/latest/rwf/http/request/struct.Request.html#method.session) will return `None`.

#### Expired sessions
If the session is expired, it's advisable not to trust its point of origin. While the contents are guaranteed to be accurate, the browser sending the data has not been validated in several weeks (4 weeks, by default).

The session can be used to privately store custom user-specific data. This allows your web apps to persist sensitive data on the client without using `localStorage` and JavaScript encryption.

### Session authentication

Rwf can ensure all requests have valid and current (not expired) sessions. To enable this feature, enable the [`SessionAuth`](https://docs.rs/rwf/latest/rwf/controller/auth/struct.SessionAuth.html) [authentication](../authentication) on your controllers.

## Store data in session

Rwf sessions allow you to store arbitrary JSON-encoded data. Since browsers place limits on cookie sizes, this data should be relatively small. To store some data in the session, you can set it on the [response](../response):

```rust
let session = Session::new(
serde_json::json!({
"data": "secret_value"
})
);

let response = Response::new()
.set_session(session);
```

## Renew sessions

Sessions are automatically renewed on each request. Expired sessions are renewed as well, unless session [authentication](../authentication) is enabled.
102 changes: 0 additions & 102 deletions docs/docs/getting-started.md

This file was deleted.

104 changes: 103 additions & 1 deletion docs/docs/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,105 @@
# Introduction

[Rwf](https://github.com/levkk/rwf) is a framework for building web applications in the Rust programming language.
Rust Web Framework (Rwf for short) is set of libraries and tools to build web applications using the Rust programming langauge. It aims to be comprehensive, by providing all features
for you to build modern, fast, and secure web apps, out of the box.

Rwf has very few dependencies, and is easy to install and use within new or existing Rust applications.

## Install Rust

If you haven't already, install the Rust compiler and tools from [rust-lang.org](https://rust-lang.org). Rwf doesn't use any nightly or experimental features,
so the stable version of the compiler will work.

## Create a project

Rwf can be used inside any Rust binary or library project. If you don't have a project already, you can create one with Cargo:

```bash
cargo init --bin rwf-web-app
```

## Install Rwf

Rwf has two packages:

* `rwf` which is the Rust crate[^1] used to build web apps
* `rwf-cli` which is a binary application that helps manage Rust projects built with Rwf

To install them, run the following while inside the root directory of your Cargo-created project:

```
cargo add rwf
cargo install rwf-cli
```

[^1]: A "crate" is a Rust package used as a dependency in other packages. It's analogous to "package" in JavaScript or Python.

## Building the app

With the packages installed, you're ready to launch your first web app in Rust. Rwf is built using the [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (Model-view-controller) design pattern,
so to get started, let's create a simple controller that will serve the index page (`/`) of your app:

```rust
use rwf::prelude::*;

#[derive(Default)]
struct Index;

#[async_trait]
impl Controller for Index {
async fn handle(&self, request: &Request) -> Result<Response, Error> {
Ok(Response::new().html("<h1>My first Rwf app!</h1>"))
}
}
```

`rwf::prelude::*` includes the vast majority of types, structs, traits and functions you'll be using when building controllers with Rwf.
Adding this declaration in your source files will make handling imports easier, but it's not required.

Rwf controllers are defined as Rust structs which implement the [`Controller`](../controllers/) trait. The trait is asynchronous, hence the `#[async_trait]` macro[^2],
and has only one method you need to implement: `async fn handle`. This method
accepts a [`Request`](../controllers/request), and must return a [`Response`](../controllers/response).

In this example, we are returning HTTP `200 - OK` with the body `<h1>My first Rwf app</h1>`. This is not strictly valid HTML,
but it'll work in all browsers for our demo purposes.

[^2]: The Rust language support for async traits is still incomplete. The `async_trait` crate helps with writing async traits in an ergonomic way.

## Launching the server

Once you have at least one controller, you can add it to the Rwf HTTP server and launch it on the address and port of your choosing:

```rust
use rwf::http::{self, Server};

#[tokio::main]
async fn main() -> Result<(), http::Error> {
// Configure then logger (stderr with colors by default)
Logger::init();

Server::new(vec![
route!("/" => Index),
])
.launch("0.0.0.0:8000")
.await
}
```

Rwf uses the `log` crate for logging. `Logger::init()` automatically configures it for your app using `env_logger`, but if you prefer, you can configure logging yourself
using the crate of your choosing.

Launching the server can be done with Cargo:

```
cargo run
```

Once the server is running, you can visit the index page by pointing your browser to [http://localhost:8000](http://localhost:8000).

Full code for this is available in GitHub in [examples/quick-start](https://github.com/levkk/rwf/tree/main/examples/quick-start).

## Learn more

- [Controllers](../controllers/)
- [Models](../models/)
- [Views](../views/)
20 changes: 19 additions & 1 deletion rwf/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub enum Error {

#[error("config is already loaded")]
ConfigLoaded,

#[error("config not found")]
NoConfig,
}

/// Global configuration.
Expand Down Expand Up @@ -172,7 +175,22 @@ impl Default for Config {
impl Config {
pub fn load() -> Result<Config, Error> {
let mut config = Config::default();
let config_file = ConfigFile::load("Rum.toml")?;
let mut config_file = None;

for name in ["rwf.toml", "Rum.toml", "Rwf.toml"] {
let path = PathBuf::from(name);
if path.exists() {
config_file = Some(ConfigFile::load("Rum.toml")?);
break;
}

return Err(Error::NoConfig);
}

let config_file = match config_file {
Some(config_file) => config_file,
None => return Err(Error::NoConfig),
};

let secret_key = config_file.general.secret_key()?;

Expand Down
1 change: 1 addition & 0 deletions rwf/src/http/cookies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl Cookies {

pub fn get_session(&self) -> Result<Option<Session>, Error> {
let cookie = self.get_private("rwf_session")?;

if let Some(cookie) = cookie {
Ok(serde_json::from_str(cookie.value())?)
} else {
Expand Down

0 comments on commit 2122def

Please sign in to comment.