Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix CSRF protection #43

Merged
merged 6 commits into from
Nov 25, 2024
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
2 changes: 1 addition & 1 deletion Cargo.lock

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

4 changes: 2 additions & 2 deletions docs/docs/controllers/pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ if request.get() {

To avoid doing this and cluttering your codebase, Rwf comes with the [`PageController`](https://docs.rs/rwf/latest/rwf/controller/trait.PageController.html). This controller trait implements the `GET`/`POST` split automatically and routes requests to two separate methods: `async fn get` and `async fn post`.

Let's use the example of a login page and implement the `PageController` for it:
Let's use the example of a login page built using the `PageController`:

```rust
use rwf::prelude::*;
Expand All @@ -29,7 +29,7 @@ struct Login;
impl PageController for Login {
// Handle GET and show the login form.
async fn get(&self, request: &Request) -> Result<Response, Error> {
render!("templates/login.html")
render!(request, "templates/login.html")
}

// Handle POST, receive form data, check information, and
Expand Down
12 changes: 7 additions & 5 deletions docs/docs/views/templates/templates-in-controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,25 @@ Since it's very common to render templates inside controllers, Rwf has the `rend
#[async_trait]
impl Controller for Index {
async fn handle(&self, request: &Request) -> Result<Response, Error> {
render!("templates/index.html", "title" => "Home page")
render!(request, "templates/index.html", "title" => "Home page")
}
}
```

The `render!` macro takes the template path as the first argument, and optionally, a mapping of variable names and values as subsequent arguments. It returns a [`Response`](../../controllers/response.md) automatically.
The `render!` macro takes the request as the first argument, the template path, and optionally a mapping of variable names and values. It returns a [`Response`](../../controllers/response.md) automatically.

If the template doesn't have any variables, you can use `render!` with just the template name:
If the template doesn't have any variables, you can omit them:

```rust
render!("templates/index.html")
render!(request, "templates/index.html")
```

Passing the request into the macro ensures that secure [CSRF](../../security/CSRF.md) protection tokens are generated automatically.

### Response code

By default, the `render!` macro returns the rendered template with HTTP code `200 OK`. If you want to return a different code, pass it as the last argument to the macro:

```rust
render!("templates/index.html", "title" => "Home page", 201)
render!(request, "templates/index.html", "title" => "Home page", 201)
```
2 changes: 1 addition & 1 deletion docs/docs/views/templates/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async fn main() {
You can override default variables in each template, by specifying the variable value when rendering the template:

```rust
render!("templates/index.html", "global_var" => "Another value")
render!(request, "templates/index.html", "global_var" => "Another value")
```


Expand Down
6 changes: 3 additions & 3 deletions examples/files/src/controllers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ pub struct Upload;
#[async_trait]
impl PageController for Upload {
/// Upload page.
async fn get(&self, _req: &Request) -> Result<Response, Error> {
render!("templates/upload.html")
async fn get(&self, req: &Request) -> Result<Response, Error> {
render!(req, "templates/upload.html")
}

/// Handle upload file.
Expand All @@ -16,7 +16,7 @@ impl PageController for Upload {
let comment = form_data.get_required::<String>("comment")?;

if let Some(file) = form_data.file("file") {
render!("templates/ok.html",
render!(req, "templates/ok.html",
"name" => file.name(),
"size" => file.body().len() as i64,
"content_type" => file.content_type(),
Expand Down
17 changes: 12 additions & 5 deletions examples/turbo/src/controllers/chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ impl Default for ChatController {
}

impl ChatController {
fn chat_message(user: &User, message: &ChatMessage, mine: bool) -> Result<TurboStream, Error> {
fn chat_message(
request: &Request,
user: &User,
message: &ChatMessage,
mine: bool,
) -> Result<TurboStream, Error> {
Ok(turbo_stream!(
request,
"templates/chat_message.html",
"messages",
"message" => UserMessage {
Expand Down Expand Up @@ -73,7 +79,7 @@ impl PageController for ChatController {
})
.collect::<Vec<_>>();

render!("templates/chat.html",
render!(request, "templates/chat.html",
"title" => "rwf + Turbo = chat",
"messages" => messages,
"user" => user
Expand All @@ -99,16 +105,17 @@ impl PageController for ChatController {
// Broadcast the message to everyone else.
{
let broadcast = Comms::broadcast(&user);
let message = Self::chat_message(&user, &message, false)?.render();
let message = Self::chat_message(request, &user, &message, false)?.render();

broadcast.send(message)?;
broadcast.send(TypingState { typing: false }.render(&user)?)?;
broadcast.send(TypingState { typing: false }.render(request, &user)?)?;
}

// Display the message for the user.
let chat_message = Self::chat_message(&user, &message, true)?;
let chat_message = Self::chat_message(request, &user, &message, true)?;

let form = turbo_stream!(
request,
"templates/chat_form.html",
"form",
"user" => user,
Expand Down
5 changes: 3 additions & 2 deletions examples/turbo/src/controllers/chat/typing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl Controller for TypingController {

if let Some(user) = user {
let broadcast = Comms::broadcast(&user);
broadcast.send(state.render(&user)?)?;
broadcast.send(state.render(request, &user)?)?;

Ok(serde_json::json!({
"status": "success",
Expand All @@ -33,8 +33,9 @@ pub struct TypingState {
}

impl TypingState {
pub fn render(&self, user: &User) -> Result<TurboStream, Error> {
pub fn render(&self, request: &Request, user: &User) -> Result<TurboStream, Error> {
let stream = turbo_stream!(
request,
"templates/typing.html",
"typing-indicators"
"user" => user.clone()
Expand Down
4 changes: 2 additions & 2 deletions examples/turbo/src/controllers/signup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ impl Default for SignupController {
#[async_trait]
impl PageController for SignupController {
/// Respond to GET request.
async fn get(&self, _request: &Request) -> Result<Response, Error> {
render!("templates/signup.html", "title" => "Signup")
async fn get(&self, request: &Request) -> Result<Response, Error> {
render!(request, "templates/signup.html", "title" => "Signup")
}

/// Respond to POST request.
Expand Down
10 changes: 6 additions & 4 deletions rwf-admin/src/controllers/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ pub struct ModelsController;

#[async_trait]
impl Controller for ModelsController {
async fn handle(&self, _request: &Request) -> Result<Response, Error> {
async fn handle(&self, request: &Request) -> Result<Response, Error> {
let tables = Table::load().await?;
render!("templates/rwf_admin/models.html",
render!(request,
"templates/rwf_admin/models.html",
"title" => "Models | Rust Web Framework",
"models" => tables
)
Expand Down Expand Up @@ -85,7 +86,8 @@ impl PageController for ModelController {
data.push(row.values()?);
}

render!("templates/rwf_admin/model.html",
render!(request,
"templates/rwf_admin/model.html",
"title" => format!("{} | Rust Web Framework", model),
"table_name" => model,
"columns" => columns,
Expand Down Expand Up @@ -114,7 +116,7 @@ impl PageController for NewModelController {
.filter(|c| !c.skip())
.collect::<Vec<_>>();

render!("templates/rwf_admin/model_new.html",
render!(request, "templates/rwf_admin/model_new.html",
"title" => format!("New record | {} | Rust Web Framework", model),
"table_name" => model,
"columns" => columns,
Expand Down
4 changes: 2 additions & 2 deletions rwf-admin/src/controllers/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct Requests;

#[async_trait]
impl Controller for Requests {
async fn handle(&self, _request: &Request) -> Result<Response, Error> {
async fn handle(&self, request: &Request) -> Result<Response, Error> {
let requests = {
let mut conn = Pool::connection().await?;
RequestByCode::count(60).fetch_all(&mut conn).await?
Expand All @@ -20,7 +20,7 @@ impl Controller for Requests {
let requests = serde_json::to_string(&requests)?;
let duration = serde_json::to_string(&duration)?;

render!("templates/rwf_admin/requests.html",
render!(request, "templates/rwf_admin/requests.html",
"title" => "Requests | Rust Web Framework",
"requests" => requests,
"duration" => duration,
Expand Down
2 changes: 1 addition & 1 deletion rwf-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rwf-macros"
version = "0.1.11"
version = "0.1.12"
edition = "2021"
license = "MIT"
description = "Macros for the Rust Web Framework"
Expand Down
2 changes: 1 addition & 1 deletion rwf-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ pub fn context(input: TokenStream) -> TokenStream {
/// ### Example
///
/// ```ignore
/// render!("templates/index.html", "title" => "Home page")
/// render!(request, "templates/index.html", "title" => "Home page")
/// ```
#[proc_macro]
pub fn render(input: TokenStream) -> TokenStream {
Expand Down
40 changes: 32 additions & 8 deletions rwf-macros/src/render.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::prelude::*;

struct RenderInput {
request: Expr,
_comma_0: Token![,],
template_name: LitStr,
_comma: Option<Token![,]>,
_comma_1: Option<Token![,]>,
context: Vec<ContextInput>,
code: Option<LitInt>,
}

struct TurboStreamInput {
request: Expr,
_comma_0: Token![,],
template_name: LitStr,
_comma_1: Token![,],
id: Expr,
Expand All @@ -18,8 +22,10 @@ struct TurboStreamInput {
impl TurboStreamInput {
fn render_input(&self) -> RenderInput {
RenderInput {
request: self.request.clone(),
_comma_0: self._comma_0.clone(),
template_name: self.template_name.clone(),
_comma: self._comma_2.clone(),
_comma_1: self._comma_2.clone(),
context: self.context.clone(),
code: None,
}
Expand All @@ -28,6 +34,8 @@ impl TurboStreamInput {

impl Parse for TurboStreamInput {
fn parse(input: ParseStream) -> Result<Self> {
let request: Expr = input.parse()?;
let _comma_0: Token![,] = input.parse()?;
let template_name: LitStr = input.parse()?;
let _comma_1: Token![,] = input.parse()?;
let id: Expr = input.parse()?;
Expand All @@ -43,6 +51,8 @@ impl Parse for TurboStreamInput {
}

Ok(TurboStreamInput {
request,
_comma_0,
template_name,
_comma_1,
id,
Expand All @@ -61,11 +71,15 @@ struct ContextInput {
}

struct Context {
// request: Expr,
// _comma_0: Token![,],
values: Vec<ContextInput>,
}

impl Parse for Context {
fn parse(input: ParseStream) -> Result<Self> {
// let request: Expr = input.parse()?;
// let _comma_0: Token![,] = input.parse()?;
let mut values = vec![];
loop {
let context: Result<ContextInput> = input.parse();
Expand All @@ -77,7 +91,11 @@ impl Parse for Context {
}
}

Ok(Context { values })
Ok(Context {
// request,
// _comma_0,
values,
})
}
}

Expand All @@ -94,11 +112,13 @@ impl Parse for ContextInput {

impl Parse for RenderInput {
fn parse(input: ParseStream) -> Result<Self> {
let request: Expr = input.parse()?;
let _comma_0: Token![,] = input.parse()?;
let template_name: LitStr = input.parse()?;
let _comma: Option<Token![,]> = input.parse()?;
let _comma_1: Option<Token![,]> = input.parse()?;
let mut code = None;

let context = if _comma.is_some() {
let context = if _comma_1.is_some() {
let mut result = vec![];
loop {
if input.peek(LitInt) {
Expand All @@ -121,22 +141,26 @@ impl Parse for RenderInput {
};

Ok(RenderInput {
request,
_comma_0,
template_name,
_comma,
_comma_1,
context,
code,
})
}
}

fn render_call(input: &RenderInput) -> proc_macro2::TokenStream {
let request = &input.request;
let render_call = if input.context.is_empty() {
vec![quote! {
let html = template.render_default()?;
let context = rwf::view::template::Context::from_request(#request)?;
let html = template.render(&context)?;
}]
} else {
let mut values = vec![quote! {
let mut context = rwf::view::template::Context::new();
let mut context = rwf::view::template::Context::from_request(#request)?;
}];

for value in &input.context {
Expand Down
2 changes: 1 addition & 1 deletion rwf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ parking_lot = "0.12"
once_cell = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
rwf-macros = { path = "../rwf-macros", version = "0.1.11" }
rwf-macros = { path = "../rwf-macros", version = "0.1.12" }
colored = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
Loading
Loading