better CSRF handling in forms

This commit is contained in:
Quentin Gliech
2021-07-01 10:12:02 +02:00
parent cca18d3d79
commit 4422b63dfd
5 changed files with 33 additions and 22 deletions

View File

@@ -3,6 +3,7 @@ use std::convert::TryInto;
use async_trait::async_trait;
use csrf::CsrfProtection;
use data_encoding::BASE64;
use serde::Deserialize;
use tide::{http::Cookie, Middleware};
use time::Duration;
@@ -43,23 +44,34 @@ impl Middleware<State> for HasCsrf {
}
}
pub fn verify_csrf(request: &tide::Request<State>, token: &str) -> tide::Result<()> {
// Verify CSRF from body
let state = request.state();
let protection = state.csrf_protection();
/// A CSRF-protected form
#[derive(Deserialize)]
pub struct CsrfForm<T> {
csrf: String,
let cookie = request
.cookie("csrf")
.ok_or_else(|| anyhow::anyhow!("missing csrf cookie"))?; // TODO: proper error
let cookie = BASE64.decode(cookie.value().as_bytes())?;
let cookie = protection.parse_cookie(&cookie)?;
#[serde(flatten)]
inner: T,
}
let token = BASE64.decode(token.as_bytes())?;
let token = protection.parse_token(&token)?;
impl<T> CsrfForm<T> {
pub fn verify_csrf(self, request: &tide::Request<State>) -> tide::Result<T> {
// Verify CSRF from body
let state = request.state();
let protection = state.csrf_protection();
if protection.verify_token_pair(&token, &cookie) {
Ok(())
} else {
Err(tide::Error::from_str(400, "failed CSRF validation"))
let cookie = request
.cookie("csrf")
.ok_or_else(|| anyhow::anyhow!("missing csrf cookie"))?; // TODO: proper error
let cookie = BASE64.decode(cookie.value().as_bytes())?;
let cookie = protection.parse_cookie(&cookie)?;
let token = BASE64.decode(self.csrf.as_bytes())?;
let token = protection.parse_token(&token)?;
if protection.verify_token_pair(&token, &cookie) {
Ok(self.inner)
} else {
Err(tide::Error::from_str(400, "failed CSRF validation"))
}
}
}

View File

@@ -1,7 +1,7 @@
use async_trait::async_trait;
use serde::Deserialize;
use thiserror::Error;
use tide::{sessions::SessionMiddleware, Middleware, Redirect, Server};
use tide::{Middleware, Redirect, Server};
use url::Url;
use crate::{

View File

@@ -1,12 +1,12 @@
use serde::Deserialize;
use tide::{Redirect, Request, Response};
use crate::csrf::CsrfForm;
use crate::state::State;
use crate::templates::common_context;
#[derive(Deserialize)]
struct LoginForm {
csrf: String,
username: String,
password: String,
}
@@ -36,8 +36,8 @@ pub async fn login(req: Request<State>) -> tide::Result {
}
pub async fn login_post(mut req: Request<State>) -> tide::Result {
let form: LoginForm = req.body_form().await?;
crate::csrf::verify_csrf(&req, &form.csrf)?;
let form: CsrfForm<LoginForm> = req.body_form().await?;
let form = form.verify_csrf(&req)?;
let state = req.state();
let user = state

View File

@@ -14,8 +14,7 @@ use self::state::State;
async fn main() -> tide::Result<()> {
// Setup logging & tracing
let fmt_layer = tracing_subscriber::fmt::layer();
let filter_layer =
EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("debug"))?;
let filter_layer = EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("info"))?;
let subscriber = Registry::default().with(filter_layer).with(fmt_layer);
subscriber.try_init()?;

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use async_trait::async_trait;
use csrf::{AesGcmCsrfProtection, CsrfProtection};
use csrf::AesGcmCsrfProtection;
use tera::Tera;
use tide::{
sessions::{MemoryStore, SessionMiddleware, SessionStore},