1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-07 17:03:01 +03:00

Start migrating to Axum

Now with the homepage and the static files
This commit is contained in:
Quentin Gliech
2022-03-24 09:01:26 +01:00
parent d177444c83
commit 797257cce7
8 changed files with 182 additions and 106 deletions

75
Cargo.lock generated
View File

@@ -460,6 +460,50 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "axum"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9f346c92c1e9a71d14fe4aaf7c2a5d9932cc4e5e48d8fb6641524416eb79ddd"
dependencies = [
"async-trait",
"axum-core",
"bitflags",
"bytes 1.1.0",
"futures-util",
"http",
"http-body",
"hyper",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-http",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbcda393bef9c87572779cb8ef916f12d77750b27535dd6819fa86591627a51"
dependencies = [
"async-trait",
"bytes 1.1.0",
"futures-util",
"http",
"http-body",
"mime",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.64" version = "0.3.64"
@@ -1820,6 +1864,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "mas-axum-utils"
version = "0.1.0"
dependencies = [
"async-trait",
"axum",
"futures-util",
"headers",
"mas-templates",
"sqlx",
]
[[package]] [[package]]
name = "mas-cli" name = "mas-cli"
version = "0.1.0" version = "0.1.0"
@@ -1928,6 +1984,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
"axum",
"chrono", "chrono",
"crc", "crc",
"data-encoding", "data-encoding",
@@ -1936,6 +1993,7 @@ dependencies = [
"hyper", "hyper",
"indoc", "indoc",
"lettre", "lettre",
"mas-axum-utils",
"mas-config", "mas-config",
"mas-data-model", "mas-data-model",
"mas-email", "mas-email",
@@ -2052,10 +2110,13 @@ dependencies = [
name = "mas-static-files" name = "mas-static-files"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum",
"headers", "headers",
"http",
"mime_guess", "mime_guess",
"rust-embed", "rust-embed",
"warp", "tokio",
"tower",
] ]
[[package]] [[package]]
@@ -2173,6 +2234,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "matchit"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9376a4f0340565ad675d11fc1419227faf5f60cd7ac9cb2e7185a471f30af833"
[[package]] [[package]]
name = "md-5" name = "md-5"
version = "0.9.1" version = "0.9.1"
@@ -3788,6 +3855,12 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
[[package]] [[package]]
name = "synstructure" name = "synstructure"
version = "0.12.6" version = "0.12.6"

View File

@@ -203,20 +203,16 @@ impl Options {
.context("could not watch for templates changes")?; .context("could not watch for templates changes")?;
} }
// Start the server let router =
let root = mas_handlers::root(&pool, &templates, &key_store, &encrypter, &mailer, &config); mas_handlers::router(&pool, &templates, &key_store, &encrypter, &mailer, &config);
// Explicitely the config to properly zeroize secret keys // Explicitely the config to properly zeroize secret keys
drop(config); drop(config);
let warp_service = warp::service(root);
let service = mas_http::ServerLayer::default().layer(warp_service);
info!("Listening on http://{}", listener.local_addr().unwrap()); info!("Listening on http://{}", listener.local_addr().unwrap());
Server::from_tcp(listener)? Server::from_tcp(listener)?
.serve(Shared::new(service)) .serve(router.into_make_service())
.with_graceful_shutdown(shutdown_signal()) .with_graceful_shutdown(shutdown_signal())
.await?; .await?;

View File

@@ -22,6 +22,8 @@ anyhow = "1.0.56"
# Web server # Web server
warp = "0.3.2" warp = "0.3.2"
hyper = { version = "0.14.17", features = ["full"] } hyper = { version = "0.14.17", features = ["full"] }
tower = "0.4.12"
axum = "0.4.8"
# Emails # Emails
lettre = { version = "0.10.0-rc.4", default-features = false, features = ["builder"] } lettre = { version = "0.10.0-rc.4", default-features = false, features = ["builder"] }
@@ -63,7 +65,7 @@ mas-static-files = { path = "../static-files" }
mas-storage = { path = "../storage" } mas-storage = { path = "../storage" }
mas-templates = { path = "../templates" } mas-templates = { path = "../templates" }
mas-warp-utils = { path = "../warp-utils" } mas-warp-utils = { path = "../warp-utils" }
tower = "0.4.12" mas-axum-utils = { path = "../axum-utils" }
[dev-dependencies] [dev-dependencies]
indoc = "1.0.4" indoc = "1.0.4"

View File

@@ -21,12 +21,11 @@
use std::sync::Arc; use std::sync::Arc;
use axum::{extract::Extension, routing::get, Router};
use mas_config::{Encrypter, RootConfig}; use mas_config::{Encrypter, RootConfig};
use mas_email::Mailer; use mas_email::Mailer;
use mas_jose::StaticKeystore; use mas_jose::StaticKeystore;
use mas_static_files::filter as static_files;
use mas_templates::Templates; use mas_templates::Templates;
use mas_warp_utils::filters;
use sqlx::PgPool; use sqlx::PgPool;
use warp::{filters::BoxedFilter, Filter, Reply}; use warp::{filters::BoxedFilter, Filter, Reply};
@@ -55,10 +54,26 @@ pub fn root(
&config.http, &config.http,
&config.csrf, &config.csrf,
); );
let static_files =
static_files(config.http.web_root.clone()).and(filters::trace::name("GET static file"));
let filter = health.or(views).unify().or(static_files).unify().or(oauth2); let filter = health.or(views).unify().or(oauth2);
filter.with(warp::log(module_path!())).boxed() filter.with(warp::log(module_path!())).boxed()
} }
pub fn router<B: Send + 'static>(
pool: &PgPool,
templates: &Templates,
key_store: &Arc<StaticKeystore>,
encrypter: &Encrypter,
mailer: &Mailer,
config: &RootConfig,
) -> Router<B> {
Router::new()
.route("/", get(self::views::index::get))
.fallback(mas_static_files::Assets)
.layer(Extension(pool.clone()))
.layer(Extension(templates.clone()))
.layer(Extension(key_store.clone()))
.layer(Extension(encrypter.clone()))
.layer(Extension(mailer.clone()))
}

View File

@@ -1,4 +1,4 @@
// Copyright 2021 The Matrix.org Foundation C.I.C. // Copyright 2021, 2022 The Matrix.org Foundation C.I.C.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@@ -12,6 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::str::FromStr;
use axum::{
extract::Extension,
response::{Html, IntoResponse},
};
use mas_axum_utils::{fancy_error, FancyError};
use mas_config::{CsrfConfig, Encrypter, HttpConfig}; use mas_config::{CsrfConfig, Encrypter, HttpConfig};
use mas_data_model::BrowserSession; use mas_data_model::BrowserSession;
use mas_storage::PostgresqlBackend; use mas_storage::PostgresqlBackend;
@@ -25,8 +32,10 @@ use mas_warp_utils::filters::{
with_templates, CsrfToken, with_templates, CsrfToken,
}; };
use sqlx::PgPool; use sqlx::PgPool;
use url::Url;
use warp::{filters::BoxedFilter, reply::html, Filter, Rejection, Reply}; use warp::{filters::BoxedFilter, reply::html, Filter, Rejection, Reply};
/*
pub(super) fn filter( pub(super) fn filter(
pool: &PgPool, pool: &PgPool,
templates: &Templates, templates: &Templates,
@@ -62,3 +71,20 @@ async fn get(
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?; let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
Ok(Box::new(reply)) Ok(Box::new(reply))
} }
*/
pub async fn get(
Extension(templates): Extension<Templates>,
) -> Result<impl IntoResponse, FancyError> {
let ctx = IndexContext::new(
Url::from_str("https://example.com/.well-known/openid-discovery").unwrap(),
)
.maybe_with_session::<PostgresqlBackend>(None)
.with_csrf("csrf_token".to_string());
let content = templates
.render_index(&ctx)
.await
.map_err(fancy_error(templates))?;
Ok(Html(content))
}

View File

@@ -18,19 +18,18 @@ use mas_templates::Templates;
use sqlx::PgPool; use sqlx::PgPool;
use warp::{filters::BoxedFilter, Filter, Reply}; use warp::{filters::BoxedFilter, Filter, Reply};
mod account; pub mod account;
mod index; pub mod index;
mod login; pub mod login;
mod logout; pub mod logout;
mod reauth; pub mod reauth;
mod register; pub mod register;
mod shared; pub mod shared;
mod verify; pub mod verify;
use self::{ use self::{
account::filter as account, index::filter as index, login::filter as login, account::filter as account, login::filter as login, logout::filter as logout,
logout::filter as logout, reauth::filter as reauth, register::filter as register, reauth::filter as reauth, register::filter as register, verify::filter as verify,
verify::filter as verify,
}; };
pub(crate) use self::{ pub(crate) use self::{
login::LoginRequest, reauth::ReauthRequest, register::RegisterRequest, shared::PostAuthAction, login::LoginRequest, reauth::ReauthRequest, register::RegisterRequest, shared::PostAuthAction,
@@ -44,7 +43,6 @@ pub(super) fn filter(
http_config: &HttpConfig, http_config: &HttpConfig,
csrf_config: &CsrfConfig, csrf_config: &CsrfConfig,
) -> BoxedFilter<(Box<dyn Reply>,)> { ) -> BoxedFilter<(Box<dyn Reply>,)> {
let index = index(pool, templates, encrypter, http_config, csrf_config);
let account = account(pool, templates, mailer, encrypter, http_config, csrf_config); let account = account(pool, templates, mailer, encrypter, http_config, csrf_config);
let login = login(pool, templates, encrypter, csrf_config); let login = login(pool, templates, encrypter, csrf_config);
let register = register(pool, templates, encrypter, csrf_config); let register = register(pool, templates, encrypter, csrf_config);
@@ -52,9 +50,7 @@ pub(super) fn filter(
let reauth = reauth(pool, templates, encrypter, csrf_config); let reauth = reauth(pool, templates, encrypter, csrf_config);
let verify = verify(pool, templates, encrypter, csrf_config); let verify = verify(pool, templates, encrypter, csrf_config);
index account
.or(account)
.unify()
.or(login) .or(login)
.unify() .unify()
.or(register) .or(register)

View File

@@ -9,7 +9,10 @@ license = "Apache-2.0"
dev = [] dev = []
[dependencies] [dependencies]
axum = "0.4.8"
headers = "0.3.7" headers = "0.3.7"
http = "0.2.6"
mime_guess = "2.0.4" mime_guess = "2.0.4"
rust-embed = "6.3.0" rust-embed = "6.3.0"
warp = "0.3.2" tokio = { version = "1.17.0", features = ["fs"] }
tower = "0.4.12"

View File

@@ -18,95 +18,60 @@
#![deny(clippy::all, missing_docs, rustdoc::broken_intra_doc_links)] #![deny(clippy::all, missing_docs, rustdoc::broken_intra_doc_links)]
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
use std::path::PathBuf; use std::{
convert::Infallible,
future::{ready, Ready},
};
use warp::{filters::BoxedFilter, Filter, Reply}; use axum::{
body::{boxed, Full},
response::{IntoResponse, Response},
};
use headers::{ContentLength, ContentType, HeaderMapExt};
use http::{Request, StatusCode};
use rust_embed::RustEmbed;
use tower::Service;
#[cfg(not(feature = "dev"))] // TODO: read the assets live from the filesystem
mod builtin {
use std::{fmt::Write, str::FromStr};
use headers::{ContentLength, ContentType, ETag, HeaderMapExt}; /// Embedded public assets
use rust_embed::RustEmbed; #[derive(RustEmbed, Clone)]
use warp::{ #[folder = "public/"]
filters::BoxedFilter, hyper::StatusCode, path::Tail, reply::Response, Filter, Rejection, pub struct Assets;
Reply,
};
#[derive(RustEmbed)] impl Assets {
#[folder = "public/"] fn get_response(path: &str) -> Option<Response> {
struct Asset; let asset = Self::get(path)?;
#[allow(clippy::unused_async)]
async fn serve_embed(
path: Tail,
if_none_match: Option<String>,
) -> Result<Box<dyn Reply>, Rejection> {
let path = path.as_str();
let asset = Asset::get(path).ok_or_else(warp::reject::not_found)?;
// TODO: this etag calculation is ugly
let etag = {
let mut s = String::with_capacity(32 * 2 + 2);
write!(s, "\"").unwrap();
for b in asset.metadata.sha256_hash() {
write!(s, "{:02x}", b).unwrap();
}
write!(s, "\"").unwrap();
s
};
if Some(&etag) == if_none_match.as_ref() {
return Ok(Box::new(StatusCode::NOT_MODIFIED));
};
let len = asset.data.len().try_into().unwrap(); let len = asset.data.len().try_into().unwrap();
let mime = mime_guess::from_path(path).first_or_octet_stream(); let mime = mime_guess::from_path(path).first_or_octet_stream();
let mut res = Response::new(asset.data.into()); let mut res = Response::new(boxed(Full::from(asset.data)));
res.headers_mut().typed_insert(ContentType::from(mime)); res.headers_mut().typed_insert(ContentType::from(mime));
res.headers_mut().typed_insert(ContentLength(len)); res.headers_mut().typed_insert(ContentLength(len));
res.headers_mut() Some(res)
.typed_insert(ETag::from_str(&etag).unwrap());
Ok(Box::new(res))
}
pub(crate) fn filter() -> BoxedFilter<(impl Reply,)> {
warp::path::tail()
.and(warp::filters::header::optional("If-None-Match"))
.and_then(serve_embed)
.boxed()
} }
} }
#[cfg(feature = "dev")] impl<B> Service<Request<B>> for Assets {
mod builtin { type Response = Response;
use std::path::PathBuf; type Error = Infallible;
type Future = Ready<Result<Self::Response, Self::Error>>;
use warp::{filters::BoxedFilter, Reply}; fn poll_ready(
&mut self,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
pub(crate) fn filter() -> BoxedFilter<(impl Reply,)> { fn call(&mut self, req: Request<B>) -> Self::Future {
let path = PathBuf::from(format!("{}/public", env!("CARGO_MANIFEST_DIR"))); let path = req.uri().path().trim_start_matches('/');
super::filter_for_path(path) // TODO: support HEAD requests
} // TODO: support ETag
} // TODO: support range requests
let response =
fn box_reply(reply: impl Reply + 'static) -> Box<dyn Reply> { Self::get_response(path).unwrap_or_else(|| StatusCode::NOT_FOUND.into_response());
Box::new(reply) ready(Ok(response))
}
fn filter_for_path(path: PathBuf) -> BoxedFilter<(impl Reply,)> {
warp::fs::dir(path).boxed()
}
/// [`warp`] filter that serves static files
#[must_use]
pub fn filter(path: Option<PathBuf>) -> BoxedFilter<(Box<dyn Reply>,)> {
let f = self::builtin::filter();
if let Some(path) = path {
f.or(filter_for_path(path)).map(box_reply).boxed()
} else {
f.map(box_reply).boxed()
} }
} }