You've already forked authentication-service
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:
75
Cargo.lock
generated
75
Cargo.lock
generated
@ -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"
|
||||
|
@ -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?;
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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()))
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user