WIP: migrate to warp, part 2
This commit is contained in:
137
Cargo.lock
generated
137
Cargo.lock
generated
@@ -57,12 +57,48 @@ dependencies = [
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-session"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07da4ce523b4e2ebaaf330746761df23a465b951a83d84bbce4233dabedae630"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-lock",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"bincode",
|
||||
"blake3",
|
||||
"chrono",
|
||||
"hmac 0.11.0",
|
||||
"log",
|
||||
"rand 0.8.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.50"
|
||||
@@ -165,6 +201,21 @@ dependencies = [
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if 0.1.10",
|
||||
"constant_time_eq",
|
||||
"crypto-mac 0.8.0",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
@@ -250,6 +301,12 @@ version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@@ -262,7 +319,7 @@ version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea8756167ea0aca10e066cdbe7813bd71d2f24e69b0bc7b50509590cef2ce0b9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"zeroize",
|
||||
@@ -352,6 +409,12 @@ version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.15.1"
|
||||
@@ -386,7 +449,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
@@ -396,7 +459,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
@@ -406,7 +469,7 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
@@ -430,6 +493,16 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.0"
|
||||
@@ -545,6 +618,12 @@ version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
@@ -708,7 +787,7 @@ version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
@@ -719,7 +798,7 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
]
|
||||
@@ -844,6 +923,16 @@ dependencies = [
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
||||
dependencies = [
|
||||
"crypto-mac 0.11.1",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.4"
|
||||
@@ -983,7 +1072,7 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1024,7 +1113,7 @@ checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
@@ -1056,7 +1145,7 @@ version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1094,6 +1183,7 @@ dependencies = [
|
||||
"cookie",
|
||||
"data-encoding",
|
||||
"figment",
|
||||
"headers",
|
||||
"mime",
|
||||
"oauth2-types",
|
||||
"password-hash",
|
||||
@@ -1110,6 +1200,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"warp",
|
||||
"warp-sessions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1291,7 +1382,7 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
@@ -1917,7 +2008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
@@ -1936,7 +2027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
@@ -2044,7 +2135,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hashlink",
|
||||
"hex",
|
||||
"hmac",
|
||||
"hmac 0.10.1",
|
||||
"itoa",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -2234,7 +2325,7 @@ version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"rand 0.8.4",
|
||||
"redox_syscall",
|
||||
@@ -2467,7 +2558,7 @@ version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
@@ -2796,6 +2887,20 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "warp-sessions"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c193bbfd203c4fa0b1ce64dce5d3ce9eb78c03f39723e080771e160fd416c145"
|
||||
dependencies = [
|
||||
"async-session",
|
||||
"async-trait",
|
||||
"http",
|
||||
"serde",
|
||||
"tokio",
|
||||
"warp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
@@ -2814,7 +2919,7 @@ version = "0.2.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
|
||||
@@ -50,3 +50,5 @@ cookie = "0.15.1"
|
||||
chacha20poly1305 = { version = "0.8.1", features = ["std"] }
|
||||
|
||||
oauth2-types = { path = "../oauth2-types" }
|
||||
headers = "0.3.4"
|
||||
warp-sessions = "1.0.15"
|
||||
|
||||
@@ -16,6 +16,9 @@ use chrono::Duration;
|
||||
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
use warp::filters::BoxedFilter;
|
||||
|
||||
use crate::filters::{csrf::extract_or_generate, CsrfToken};
|
||||
|
||||
use super::ConfigurationSection;
|
||||
|
||||
@@ -40,10 +43,10 @@ fn ttl_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
pub struct CsrfConfig {
|
||||
#[schemars(schema_with = "key_schema")]
|
||||
#[serde_as(as = "serde_with::hex::Hex")]
|
||||
key: [u8; 32],
|
||||
pub key: [u8; 32],
|
||||
|
||||
#[serde(default = "default_cookie_name")]
|
||||
cookie_name: String,
|
||||
pub cookie_name: String,
|
||||
|
||||
#[schemars(schema_with = "ttl_schema")]
|
||||
#[serde(default = "default_ttl")]
|
||||
@@ -51,14 +54,14 @@ pub struct CsrfConfig {
|
||||
ttl: Duration,
|
||||
}
|
||||
|
||||
// impl CsrfConfig {
|
||||
// pub fn into_middleware<State: Clone + Send + Sync + 'static>(self) -> impl Middleware<State> {
|
||||
// let ttl = self.ttl;
|
||||
// let cookie_name = self.cookie_name.clone();
|
||||
// let protection = self.key;
|
||||
// CsrfMiddleware::new(protection, cookie_name, ttl)
|
||||
// }
|
||||
// }
|
||||
impl CsrfConfig {
|
||||
pub fn into_extract_filter(self) -> BoxedFilter<(CsrfToken,)> {
|
||||
let ttl = self.ttl;
|
||||
// TODO: we should probably not leak here
|
||||
let cookie_name = Box::leak(Box::new(self.cookie_name));
|
||||
extract_or_generate(self.key, cookie_name, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigurationSection<'_> for CsrfConfig {
|
||||
fn path() -> &'static str {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::middlewares::CsrfToken;
|
||||
use crate::filters::CsrfToken;
|
||||
|
||||
/// A CSRF-protected form
|
||||
#[derive(Deserialize)]
|
||||
@@ -26,16 +26,9 @@ pub struct CsrfForm<T> {
|
||||
}
|
||||
|
||||
impl<T> CsrfForm<T> {
|
||||
pub fn verify_csrf<State>(self, request: &tide::Request<State>) -> tide::Result<T>
|
||||
where
|
||||
State: Clone + Send + Sync + 'static,
|
||||
{
|
||||
pub fn verify_csrf(self, token: &CsrfToken) -> anyhow::Result<T> {
|
||||
// Verify CSRF from request
|
||||
let csrf_token: &CsrfToken = request
|
||||
.ext()
|
||||
.ok_or_else(|| anyhow::anyhow!("missing csrf cookie"))?; // TODO: proper error
|
||||
|
||||
csrf_token.verify_form_value(&self.csrf)?;
|
||||
token.verify_form_value(&self.csrf)?;
|
||||
Ok(self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,12 @@ use chacha20poly1305::{
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use cookie::{Cookie, CookieBuilder, SameSite};
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use headers::{Header, HeaderMapExt, HeaderValue, SetCookie};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, TimestampSeconds};
|
||||
use warp::filters::BoxedFilter;
|
||||
use warp::{filters::BoxedFilter, Filter, Rejection, Reply};
|
||||
|
||||
use crate::errors::WrapError;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -100,17 +103,16 @@ impl UnencryptedToken {
|
||||
name: &'n str,
|
||||
key: &[u8; 32],
|
||||
) -> anyhow::Result<CookieBuilder<'c>> {
|
||||
let value = self.encrypt(key)?.to_cookie_value()?;
|
||||
// Converting expiration time from `chrono` to `time` via native `SystemTime`
|
||||
let expires: SystemTime = self.expiration.into();
|
||||
Ok(Cookie::build(name, value)
|
||||
.expires(expires)
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict))
|
||||
Ok(self
|
||||
.encrypt(key)?
|
||||
.to_cookie_builder(name)?
|
||||
.expires(Some(expires.into())))
|
||||
}
|
||||
|
||||
fn from_cookie(cookie: &Cookie, key: &[u8; 32]) -> anyhow::Result<Self> {
|
||||
let encrypted = EncryptedToken::from_cookie_value(cookie.value())?;
|
||||
fn from_cookie_value(value: &str, key: &[u8; 32]) -> anyhow::Result<Self> {
|
||||
let encrypted = EncryptedToken::from_cookie_value(value)?;
|
||||
let token = encrypted.decrypt(key)?;
|
||||
Ok(token)
|
||||
}
|
||||
@@ -147,75 +149,82 @@ impl EncryptedToken {
|
||||
let content = bincode::deserialize(&raw)?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Middleware {
|
||||
key: [u8; 32],
|
||||
ttl: Duration,
|
||||
cookie_name: String,
|
||||
}
|
||||
|
||||
impl Middleware {
|
||||
/// Create a new CSRF protection middleware from a key, cookie name and TTL
|
||||
pub fn new(key: [u8; 32], cookie_name: String, ttl: Duration) -> Self {
|
||||
Self {
|
||||
key,
|
||||
ttl,
|
||||
cookie_name,
|
||||
}
|
||||
fn to_cookie_builder<'c, 'n: 'c>(&self, name: &'n str) -> anyhow::Result<CookieBuilder<'c>> {
|
||||
let value = self.to_cookie_value()?;
|
||||
Ok(Cookie::build(name, value)
|
||||
.http_only(true)
|
||||
.same_site(SameSite::Strict))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_or_generate(
|
||||
key: [u8; 32],
|
||||
cookie_name: String,
|
||||
cookie_name: &'static str,
|
||||
ttl: Duration,
|
||||
) -> BoxedFilter<(UnencryptedToken,)> {
|
||||
warp::cookie::optional(cookie_name)
|
||||
warp::any()
|
||||
.map(move || (key, ttl))
|
||||
.untuple_one()
|
||||
.and(warp::cookie::optional(cookie_name))
|
||||
.and_then(|key, ttl, maybe_cookie: Option<String>| async move {
|
||||
// Explicitely specify the "Error" type here to have the `?` operation working
|
||||
Ok::<_, Rejection>(
|
||||
maybe_cookie
|
||||
// Try decrypting the cookie
|
||||
.map(|cookie| UnencryptedToken::from_cookie_value(&cookie, &key))
|
||||
// If there was an error decrypting it, bail out here
|
||||
.transpose()
|
||||
.wrap_error()?
|
||||
// Verify its TTL (but do not hard-error if it expired)
|
||||
.and_then(|token| token.verify_expiration().ok())
|
||||
.map_or_else(
|
||||
// Generate a new token if no valid one were found
|
||||
|| UnencryptedToken::generate(ttl),
|
||||
// Else, refresh the expiration of the token
|
||||
|token| token.refresh(ttl),
|
||||
),
|
||||
)
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
/*
|
||||
pub struct WithTypedHeader<R, H> {
|
||||
reply: R,
|
||||
header: H,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<State> tide::Middleware<State> for Middleware
|
||||
impl<R, H> Reply for WithTypedHeader<R, H>
|
||||
where
|
||||
State: Clone + Send + Sync + 'static,
|
||||
R: Reply,
|
||||
H: Header + Send,
|
||||
{
|
||||
async fn handle(
|
||||
&self,
|
||||
mut request: tide::Request<State>,
|
||||
next: tide::Next<'_, State>,
|
||||
) -> tide::Result {
|
||||
let csrf_token = request
|
||||
// Get the CSRF cookie
|
||||
.cookie(&self.cookie_name)
|
||||
// Try decrypting it
|
||||
.map(|cookie| UnencryptedToken::from_cookie(&cookie, &self.key))
|
||||
// If there was an error decrypting it, bail out here
|
||||
.transpose()?
|
||||
// Verify it's TTL (but do not hard-error if it expired)
|
||||
.and_then(|token| token.verify_expiration().ok())
|
||||
.map_or_else(
|
||||
// Generate a new token if no valid one were found
|
||||
|| UnencryptedToken::generate(self.ttl),
|
||||
// Else, refresh the expiration of the cookie
|
||||
|token| token.refresh(self.ttl),
|
||||
);
|
||||
|
||||
// Build the cookie before calling the next stage since the owned csrf_token has
|
||||
// to be passed as a request extension
|
||||
let cookie = csrf_token
|
||||
.to_cookie_builder(&self.cookie_name, &self.key)?
|
||||
.finish()
|
||||
.into_owned();
|
||||
|
||||
request.set_ext(csrf_token);
|
||||
|
||||
let mut response = next.run(request).await;
|
||||
response.insert_cookie(cookie);
|
||||
|
||||
Ok(response)
|
||||
fn into_response(self) -> warp::reply::Response {
|
||||
let mut res = self.reply.into_response();
|
||||
res.headers_mut().typed_insert(self.header);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_csrf<R: Reply, F>(
|
||||
key: [u8; 32],
|
||||
cookie_name: &'static str,
|
||||
) -> impl Fn(F) -> BoxedFilter<(WithTypedHeader<R, SetCookie>,)>
|
||||
where
|
||||
F: Filter<Extract = (UnencryptedToken, R), Error = Rejection> + Clone + Send + Sync + 'static,
|
||||
{
|
||||
move |f: F| {
|
||||
f.and_then(move |token: UnencryptedToken, reply: R| async move {
|
||||
let cookie = token
|
||||
.to_cookie_builder(cookie_name, &key)
|
||||
.wrap_error()?
|
||||
.finish()
|
||||
.to_string();
|
||||
let header =
|
||||
SetCookie::decode(&mut [HeaderValue::from_str(&cookie).wrap_error()?].iter())
|
||||
.wrap_error()?;
|
||||
Ok::<_, Rejection>(WithTypedHeader { reply, header })
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -12,5 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod csrf;
|
||||
pub mod csrf;
|
||||
// mod errors;
|
||||
|
||||
pub use csrf::UnencryptedToken as CsrfToken;
|
||||
|
||||
@@ -12,32 +12,41 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{convert::Infallible, sync::Arc};
|
||||
|
||||
use sqlx::PgPool;
|
||||
use tera::Tera;
|
||||
use warp::{filters::BoxedFilter, Filter};
|
||||
use warp::{filters::BoxedFilter, wrap_fn, Filter, Rejection, Reply};
|
||||
|
||||
use crate::config::RootConfig;
|
||||
use crate::{config::RootConfig, filters::csrf::with_csrf};
|
||||
|
||||
mod health;
|
||||
mod oauth2;
|
||||
mod views;
|
||||
|
||||
async fn display_error(err: Rejection) -> Result<impl Reply, Infallible> {
|
||||
let ret = format!("{:?}", err);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub fn root(
|
||||
pool: PgPool,
|
||||
templates: Tera,
|
||||
config: &RootConfig,
|
||||
) -> BoxedFilter<(impl warp::Reply,)> {
|
||||
let templates = Arc::new(templates);
|
||||
let with_pool = move || pool.clone();
|
||||
let with_templates = move || templates.clone();
|
||||
let with_csrf_token = config.csrf.clone().into_extract_filter();
|
||||
let with_pool = warp::any().map(move || pool.clone());
|
||||
let with_templates = warp::any().map(move || templates.clone());
|
||||
|
||||
// TODO: this is ugly and leaks
|
||||
let csrf_cookie_name = Box::leak(Box::new(config.csrf.cookie_name.clone()));
|
||||
|
||||
let cors = warp::cors().allow_any_origin();
|
||||
|
||||
let health = warp::path("health")
|
||||
.and(warp::get())
|
||||
.map(with_pool)
|
||||
.and(with_pool.clone())
|
||||
.and_then(self::health::get)
|
||||
.boxed();
|
||||
|
||||
@@ -48,10 +57,23 @@ pub fn root(
|
||||
|
||||
let index = warp::path::end()
|
||||
.and(warp::get())
|
||||
.map(with_templates)
|
||||
.and_then(self::views::index::get);
|
||||
.and(with_templates.clone())
|
||||
.and(with_csrf_token.clone())
|
||||
.and(with_pool.clone())
|
||||
.and_then(self::views::index::get)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(config.csrf.key, csrf_cookie_name)));
|
||||
|
||||
health.or(index).or(metadata).boxed()
|
||||
let login = warp::path("login")
|
||||
.and(warp::get())
|
||||
.and(with_templates)
|
||||
.and(with_csrf_token)
|
||||
.and(with_pool)
|
||||
.and_then(self::views::login::get)
|
||||
.untuple_one()
|
||||
.with(wrap_fn(with_csrf(config.csrf.key, csrf_cookie_name)));
|
||||
|
||||
health.or(index).or(login).or(metadata).boxed()
|
||||
|
||||
// app.at("/").nest({
|
||||
// let mut views = tide::with_state(state.clone());
|
||||
|
||||
@@ -14,13 +14,28 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tera::{Context, Tera};
|
||||
use sqlx::PgPool;
|
||||
use tera::Tera;
|
||||
use warp::{reply::with_header, Rejection, Reply};
|
||||
|
||||
use crate::errors::WrapError;
|
||||
use crate::{errors::WrapError, filters::CsrfToken, templates::CommonContext};
|
||||
|
||||
pub async fn get(
|
||||
templates: Arc<Tera>,
|
||||
csrf_token: CsrfToken,
|
||||
db: PgPool,
|
||||
) -> Result<(CsrfToken, impl Reply), Rejection> {
|
||||
let ctx = CommonContext::default()
|
||||
.with_csrf_token(&csrf_token)
|
||||
.with_session(&db)
|
||||
.await
|
||||
.wrap_error()?
|
||||
.finish()
|
||||
.wrap_error()?;
|
||||
|
||||
pub async fn get(templates: Arc<Tera>) -> Result<impl Reply, Rejection> {
|
||||
let ctx = Context::new();
|
||||
let content = templates.render("index.html", &ctx).wrap_error()?;
|
||||
Ok(with_header(content, "Content-Type", "text/html"))
|
||||
Ok((
|
||||
csrf_token,
|
||||
with_header(content, "Content-Type", "text/html"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -12,10 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::Deserialize;
|
||||
use tide::{Redirect, Request, Response};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{csrf::CsrfForm, state::State, templates::common_context};
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
use tera::Tera;
|
||||
use warp::{reply::with_header, Rejection, Reply};
|
||||
|
||||
use crate::{errors::WrapError, filters::CsrfToken, templates::CommonContext};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginForm {
|
||||
@@ -23,19 +27,28 @@ struct LoginForm {
|
||||
password: String,
|
||||
}
|
||||
|
||||
pub async fn get(req: Request<State>) -> tide::Result {
|
||||
let state = req.state();
|
||||
let ctx = common_context(&req).await?;
|
||||
pub async fn get(
|
||||
templates: Arc<Tera>,
|
||||
csrf_token: CsrfToken,
|
||||
db: PgPool,
|
||||
) -> Result<(CsrfToken, impl Reply), Rejection> {
|
||||
let ctx = CommonContext::default()
|
||||
.with_csrf_token(&csrf_token)
|
||||
.with_session(&db)
|
||||
.await
|
||||
.wrap_error()?
|
||||
.finish()
|
||||
.wrap_error()?;
|
||||
|
||||
// TODO: check if there is an existing session
|
||||
let content = state.templates().render("login.html", &ctx)?;
|
||||
let body = Response::builder(200)
|
||||
.body(content)
|
||||
.content_type("text/html")
|
||||
.into();
|
||||
Ok(body)
|
||||
let content = templates.render("login.html", &ctx).wrap_error()?;
|
||||
Ok((
|
||||
csrf_token,
|
||||
with_header(content, "Content-Type", "text/html"),
|
||||
))
|
||||
}
|
||||
|
||||
/*
|
||||
pub async fn post(mut req: Request<State>) -> tide::Result {
|
||||
let form: CsrfForm<LoginForm> = req.body_form().await?;
|
||||
let form = form.verify_csrf(&req)?;
|
||||
@@ -51,3 +64,4 @@ pub async fn post(mut req: Request<State>) -> tide::Result {
|
||||
|
||||
Ok(Redirect::new("/").into())
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
pub(super) mod index;
|
||||
// pub(super) mod login;
|
||||
pub(super) mod login;
|
||||
// pub(super) mod logout;
|
||||
// pub(super) mod reauth;
|
||||
|
||||
@@ -23,8 +23,8 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte
|
||||
|
||||
mod cli;
|
||||
mod config;
|
||||
mod csrf;
|
||||
mod errors;
|
||||
// mod csrf;
|
||||
mod filters;
|
||||
mod handlers;
|
||||
mod state;
|
||||
|
||||
@@ -22,7 +22,7 @@ mod user;
|
||||
|
||||
pub use self::{
|
||||
client::{Client, ClientLookupError, InvalidRedirectUriError},
|
||||
user::User,
|
||||
user::{lookup_session, SessionInfo, User},
|
||||
};
|
||||
|
||||
pub static MIGRATOR: Migrator = sqlx::migrate!();
|
||||
|
||||
@@ -12,15 +12,56 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use tera::Tera;
|
||||
use anyhow::Context as _;
|
||||
use serde::Serialize;
|
||||
use sqlx::{Executor, Postgres};
|
||||
use tera::{Context, Tera};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
filters::CsrfToken,
|
||||
storage::{lookup_session, SessionInfo},
|
||||
};
|
||||
|
||||
pub fn load() -> Result<Tera, tera::Error> {
|
||||
let path = format!("{}/templates/**/*.{{html,txt}}", env!("CARGO_MANIFEST_DIR"));
|
||||
info!(%path, "Loading templates");
|
||||
Tera::new(&path)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
pub struct CommonContext {
|
||||
csrf_token: Option<String>,
|
||||
session: Option<SessionInfo>,
|
||||
}
|
||||
|
||||
impl CommonContext {
|
||||
pub fn with_csrf_token(self, token: &CsrfToken) -> Self {
|
||||
Self {
|
||||
csrf_token: Some(token.form_value()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn with_session<'e>(
|
||||
self,
|
||||
_executor: impl Executor<'e, Database = Postgres>,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(self)
|
||||
/*
|
||||
let session = lookup_session(executor, 1).await?;
|
||||
Ok(Self {
|
||||
session: Some(session),
|
||||
..self
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn finish(self) -> anyhow::Result<Context> {
|
||||
Context::from_serialize(&self).context("could not serialize common context for templates")
|
||||
}
|
||||
}
|
||||
|
||||
// pub async fn common_context(req: &Request<State>) -> Result<Context, anyhow::Error> {
|
||||
// let state = req.state();
|
||||
// let session = req.session();
|
||||
|
||||
Reference in New Issue
Block a user