1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +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",
]
[[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]]
name = "backtrace"
version = "0.3.64"
@ -1820,6 +1864,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "mas-axum-utils"
version = "0.1.0"
dependencies = [
"async-trait",
"axum",
"futures-util",
"headers",
"mas-templates",
"sqlx",
]
[[package]]
name = "mas-cli"
version = "0.1.0"
@ -1928,6 +1984,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"argon2",
"axum",
"chrono",
"crc",
"data-encoding",
@ -1936,6 +1993,7 @@ dependencies = [
"hyper",
"indoc",
"lettre",
"mas-axum-utils",
"mas-config",
"mas-data-model",
"mas-email",
@ -2052,10 +2110,13 @@ dependencies = [
name = "mas-static-files"
version = "0.1.0"
dependencies = [
"axum",
"headers",
"http",
"mime_guess",
"rust-embed",
"warp",
"tokio",
"tower",
]
[[package]]
@ -2173,6 +2234,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "matchit"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9376a4f0340565ad675d11fc1419227faf5f60cd7ac9cb2e7185a471f30af833"
[[package]]
name = "md-5"
version = "0.9.1"
@ -3788,6 +3855,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "sync_wrapper"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
[[package]]
name = "synstructure"
version = "0.12.6"

View File

@ -203,20 +203,16 @@ impl Options {
.context("could not watch for templates changes")?;
}
// Start the server
let root = mas_handlers::root(&pool, &templates, &key_store, &encrypter, &mailer, &config);
let router =
mas_handlers::router(&pool, &templates, &key_store, &encrypter, &mailer, &config);
// Explicitely the config to properly zeroize secret keys
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());
Server::from_tcp(listener)?
.serve(Shared::new(service))
.serve(router.into_make_service())
.with_graceful_shutdown(shutdown_signal())
.await?;

View File

@ -22,6 +22,8 @@ anyhow = "1.0.56"
# Web server
warp = "0.3.2"
hyper = { version = "0.14.17", features = ["full"] }
tower = "0.4.12"
axum = "0.4.8"
# Emails
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-templates = { path = "../templates" }
mas-warp-utils = { path = "../warp-utils" }
tower = "0.4.12"
mas-axum-utils = { path = "../axum-utils" }
[dev-dependencies]
indoc = "1.0.4"

View File

@ -21,12 +21,11 @@
use std::sync::Arc;
use axum::{extract::Extension, routing::get, Router};
use mas_config::{Encrypter, RootConfig};
use mas_email::Mailer;
use mas_jose::StaticKeystore;
use mas_static_files::filter as static_files;
use mas_templates::Templates;
use mas_warp_utils::filters;
use sqlx::PgPool;
use warp::{filters::BoxedFilter, Filter, Reply};
@ -55,10 +54,26 @@ pub fn root(
&config.http,
&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()
}
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");
// 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
// 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_data_model::BrowserSession;
use mas_storage::PostgresqlBackend;
@ -25,8 +32,10 @@ use mas_warp_utils::filters::{
with_templates, CsrfToken,
};
use sqlx::PgPool;
use url::Url;
use warp::{filters::BoxedFilter, reply::html, Filter, Rejection, Reply};
/*
pub(super) fn filter(
pool: &PgPool,
templates: &Templates,
@ -62,3 +71,20 @@ async fn get(
let reply = cookie_saver.save_encrypted(&csrf_token, 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 warp::{filters::BoxedFilter, Filter, Reply};
mod account;
mod index;
mod login;
mod logout;
mod reauth;
mod register;
mod shared;
mod verify;
pub mod account;
pub mod index;
pub mod login;
pub mod logout;
pub mod reauth;
pub mod register;
pub mod shared;
pub mod verify;
use self::{
account::filter as account, index::filter as index, login::filter as login,
logout::filter as logout, reauth::filter as reauth, register::filter as register,
verify::filter as verify,
account::filter as account, login::filter as login, logout::filter as logout,
reauth::filter as reauth, register::filter as register, verify::filter as verify,
};
pub(crate) use self::{
login::LoginRequest, reauth::ReauthRequest, register::RegisterRequest, shared::PostAuthAction,
@ -44,7 +43,6 @@ pub(super) fn filter(
http_config: &HttpConfig,
csrf_config: &CsrfConfig,
) -> 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 login = login(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 verify = verify(pool, templates, encrypter, csrf_config);
index
.or(account)
.unify()
account
.or(login)
.unify()
.or(register)

View File

@ -9,7 +9,10 @@ license = "Apache-2.0"
dev = []
[dependencies]
axum = "0.4.8"
headers = "0.3.7"
http = "0.2.6"
mime_guess = "2.0.4"
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)]
#![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"))]
mod builtin {
use std::{fmt::Write, str::FromStr};
// TODO: read the assets live from the filesystem
use headers::{ContentLength, ContentType, ETag, HeaderMapExt};
use rust_embed::RustEmbed;
use warp::{
filters::BoxedFilter, hyper::StatusCode, path::Tail, reply::Response, Filter, Rejection,
Reply,
};
/// Embedded public assets
#[derive(RustEmbed, Clone)]
#[folder = "public/"]
pub struct Assets;
#[derive(RustEmbed)]
#[folder = "public/"]
struct Asset;
#[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));
};
impl Assets {
fn get_response(path: &str) -> Option<Response> {
let asset = Self::get(path)?;
let len = asset.data.len().try_into().unwrap();
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(ContentLength(len));
res.headers_mut()
.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()
Some(res)
}
}
#[cfg(feature = "dev")]
mod builtin {
use std::path::PathBuf;
impl<B> Service<Request<B>> for Assets {
type Response = Response;
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,)> {
let path = PathBuf::from(format!("{}/public", env!("CARGO_MANIFEST_DIR")));
super::filter_for_path(path)
}
}
fn box_reply(reply: impl Reply + 'static) -> Box<dyn Reply> {
Box::new(reply)
}
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()
fn call(&mut self, req: Request<B>) -> Self::Future {
let path = req.uri().path().trim_start_matches('/');
// TODO: support HEAD requests
// TODO: support ETag
// TODO: support range requests
let response =
Self::get_response(path).unwrap_or_else(|| StatusCode::NOT_FOUND.into_response());
ready(Ok(response))
}
}