1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2026-01-03 17:02:28 +03:00

better CSRF form handling

This commit is contained in:
Quentin Gliech
2021-07-31 14:47:54 +02:00
parent 9b6eae17ea
commit f079229abd
6 changed files with 47 additions and 79 deletions

View File

@@ -1,34 +0,0 @@
// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use serde::Deserialize;
use crate::filters::CsrfToken;
/// A CSRF-protected form
#[derive(Deserialize)]
pub struct CsrfForm<T> {
csrf: String,
#[serde(flatten)]
inner: T,
}
impl<T> CsrfForm<T> {
pub fn verify_csrf(self, token: &CsrfToken) -> anyhow::Result<T> {
// Verify CSRF from request
token.verify_form_value(&self.csrf)?;
Ok(self.inner)
}
}

View File

@@ -18,7 +18,7 @@
use chrono::{DateTime, Duration, Utc};
use data_encoding::BASE64URL_NOPAD;
use headers::SetCookie;
use serde::{Deserialize, Serialize};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_with::{serde_as, TimestampSeconds};
use warp::{filters::BoxedFilter, Filter, Rejection, Reply};
@@ -78,6 +78,23 @@ impl CsrfToken {
}
}
/// A CSRF-protected form
#[derive(Deserialize)]
struct CsrfForm<T> {
csrf: String,
#[serde(flatten)]
inner: T,
}
impl<T> CsrfForm<T> {
fn verify_csrf(self, token: &CsrfToken) -> anyhow::Result<T> {
// Verify CSRF from request
token.verify_form_value(&self.csrf)?;
Ok(self.inner)
}
}
pub fn csrf_token(cookies_config: &CookiesConfig) -> BoxedFilter<(CsrfToken,)> {
super::cookies::encrypted("csrf", cookies_config)
.and_then(move |token: CsrfToken| async move {
@@ -118,3 +135,18 @@ where
{
save_encrypted("csrf", cookies_config)
}
pub fn protected_form<T>(cookies_config: &CookiesConfig) -> BoxedFilter<(T,)>
where
T: DeserializeOwned + Send + 'static,
{
csrf_token(cookies_config)
.and(warp::body::form())
.and_then(
|csrf_token: CsrfToken, protected_form: CsrfForm<T>| async move {
let form = protected_form.verify_csrf(&csrf_token).wrap_error()?;
Ok::<_, Rejection>(form)
},
)
.boxed()
}

View File

@@ -20,10 +20,9 @@ use warp::{
use crate::{
config::{CookiesConfig, CsrfConfig},
csrf::CsrfForm,
errors::WrapError,
filters::{
csrf::{csrf_token, save_csrf_token, updated_csrf_token},
csrf::{protected_form, save_csrf_token, updated_csrf_token},
session::{save_session, with_optional_session},
with_pool, with_templates, CsrfToken,
},
@@ -52,9 +51,8 @@ pub(super) fn filter(
.with(wrap_fn(save_csrf_token(cookies_config)));
let post = warp::post()
.and(csrf_token(cookies_config))
.and(with_pool(pool))
.and(warp::body::form())
.and(protected_form(cookies_config))
.and_then(post)
.untuple_one()
.with(wrap_fn(save_session(cookies_config)));
@@ -81,13 +79,7 @@ async fn get(
))
}
async fn post(
csrf_token: CsrfToken,
db: PgPool,
form: CsrfForm<LoginForm>,
) -> Result<(SessionInfo, impl Reply), Rejection> {
let form = form.verify_csrf(&csrf_token).wrap_error()?;
async fn post(db: PgPool, form: LoginForm) -> Result<(SessionInfo, impl Reply), Rejection> {
let session_info = login(&db, &form.username, &form.password)
.await
.wrap_error()?;

View File

@@ -13,40 +13,26 @@
// limitations under the License.
use sqlx::PgPool;
use warp::{filters::BoxedFilter, hyper::Uri, wrap_fn, Filter, Rejection, Reply};
use warp::{filters::BoxedFilter, hyper::Uri, Filter, Rejection, Reply};
use crate::{
config::CookiesConfig,
csrf::CsrfForm,
errors::WrapError,
filters::{
csrf::{csrf_token, save_csrf_token},
session::with_session,
with_pool, CsrfToken,
},
filters::{csrf::protected_form, session::with_session, with_pool},
storage::SessionInfo,
};
pub(super) fn filter(pool: &PgPool, cookies_config: &CookiesConfig) -> BoxedFilter<(impl Reply,)> {
warp::post()
.and(warp::path("logout"))
.and(csrf_token(cookies_config))
.and(with_session(pool, cookies_config))
.and(with_pool(pool))
.and(warp::body::form())
.and(protected_form(cookies_config))
.and_then(post)
.untuple_one()
.with(wrap_fn(save_csrf_token(cookies_config)))
.boxed()
}
async fn post(
token: CsrfToken,
session: SessionInfo,
pool: PgPool,
form: CsrfForm<()>,
) -> Result<(CsrfToken, impl Reply), Rejection> {
form.verify_csrf(&token).wrap_error()?;
async fn post(session: SessionInfo, pool: PgPool, _form: ()) -> Result<impl Reply, Rejection> {
session.end(&pool).await.wrap_error()?;
Ok::<_, Rejection>((token, warp::redirect(Uri::from_static("/login"))))
Ok::<_, Rejection>(warp::redirect(Uri::from_static("/login")))
}

View File

@@ -20,10 +20,9 @@ use warp::{
use crate::{
config::{CookiesConfig, CsrfConfig},
csrf::CsrfForm,
errors::WrapError,
filters::{
csrf::{csrf_token, save_csrf_token, updated_csrf_token},
csrf::{protected_form, save_csrf_token, updated_csrf_token},
session::with_session,
with_pool, with_templates, CsrfToken,
},
@@ -51,13 +50,10 @@ pub(super) fn filter(
.with(wrap_fn(save_csrf_token(cookies_config)));
let post = warp::post()
.and(csrf_token(cookies_config))
.and(with_session(pool, cookies_config))
.and(with_pool(pool))
.and(warp::body::form())
.and_then(post)
.untuple_one()
.with(wrap_fn(save_csrf_token(cookies_config)));
.and(protected_form(cookies_config))
.and_then(post);
warp::path("reauth").and(get.or(post)).boxed()
}
@@ -81,14 +77,11 @@ async fn get(
}
async fn post(
csrf_token: CsrfToken,
session: SessionInfo,
pool: PgPool,
form: CsrfForm<ReauthForm>,
) -> Result<(CsrfToken, impl Reply), Rejection> {
let form = form.verify_csrf(&csrf_token).wrap_error()?;
form: ReauthForm,
) -> Result<impl Reply, Rejection> {
let _session = session.reauth(&pool, &form.password).await.wrap_error()?;
Ok((csrf_token, warp::redirect(Uri::from_static("/"))))
Ok(warp::redirect(Uri::from_static("/")))
}

View File

@@ -24,7 +24,6 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte
mod cli;
mod config;
mod csrf;
mod errors;
mod filters;
mod handlers;