From 3e6ea9a158788c7c863ca8b2a41d4b729dd6f237 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 9 Aug 2023 13:55:35 +0200 Subject: [PATCH] Add a 404 HTMl fallback --- crates/cli/src/server.rs | 2 ++ crates/handlers/src/lib.rs | 32 ++++++++++++++++++++++---- crates/templates/src/context.rs | 40 ++++++++++++++++++++++++++++++++- crates/templates/src/lib.rs | 6 ++++- frontend/src/templates.css | 7 ++++++ templates/pages/404.html | 35 +++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 templates/pages/404.html diff --git a/crates/cli/src/server.rs b/crates/cli/src/server.rs index 368dacaa..92deb976 100644 --- a/crates/cli/src/server.rs +++ b/crates/cli/src/server.rs @@ -235,6 +235,8 @@ where } } + router = router.fallback(mas_handlers::fallback); + router .layer( InFlightCounterLayer::new("http.server.active_requests").on_request(( diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 6db76112..ea816635 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -34,21 +34,26 @@ use std::{convert::Infallible, sync::Arc, time::Duration}; use axum::{ body::{Bytes, HttpBody}, - extract::{FromRef, FromRequestParts}, + extract::{FromRef, FromRequestParts, OriginalUri, State}, + http::Method, response::{Html, IntoResponse}, routing::{get, on, post, MethodFilter}, Router, }; use headers::HeaderName; -use hyper::header::{ - ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_TYPE, +use hyper::{ + header::{ + ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_TYPE, + }, + StatusCode, Version, }; +use mas_axum_utils::FancyError; use mas_http::CorsLayerExt; use mas_keystore::{Encrypter, Keystore}; use mas_policy::PolicyFactory; use mas_router::{Route, UrlBuilder}; use mas_storage::{BoxClock, BoxRepository, BoxRng}; -use mas_templates::{ErrorContext, Templates}; +use mas_templates::{ErrorContext, NotFoundContext, Templates}; use passwords::PasswordManager; use sqlx::PgPool; use tower::util::AndThenLayer; @@ -368,3 +373,22 @@ where }, )) } + +/// The fallback handler for all routes that don't match anything else. +/// +/// # Errors +/// +/// Returns an error if the template rendering fails. +pub async fn fallback( + State(templates): State, + OriginalUri(uri): OriginalUri, + method: Method, + version: Version, +) -> Result { + let ctx = NotFoundContext::new(&method, version, &uri); + // XXX: this should look at the Accept header and return JSON if requested + + let res = templates.render_not_found(&ctx).await?; + + Ok((StatusCode::NOT_FOUND, Html(res))) +} diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 6e833428..02a9f01b 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -14,7 +14,8 @@ //! Contexts used in templates -use chrono::Utc; +use chrono::{DateTime, Utc}; +use http::{Method, Uri, Version}; use mas_data_model::{ AuthorizationGrant, BrowserSession, Client, CompatSsoLogin, CompatSsoLoginState, UpstreamOAuthLink, UpstreamOAuthProvider, User, UserEmail, UserEmailVerification, @@ -1009,3 +1010,40 @@ impl ErrorContext { self.details.as_deref() } } + +/// Context used by the not found (`404.html`) template +#[derive(Serialize)] +pub struct NotFoundContext { + method: String, + version: String, + uri: String, +} + +impl NotFoundContext { + /// Constructs a context for the not found page + #[must_use] + pub fn new(method: &Method, version: Version, uri: &Uri) -> Self { + Self { + method: method.to_string(), + version: format!("{version:?}"), + uri: uri.to_string(), + } + } +} + +impl TemplateContext for NotFoundContext { + fn sample(_now: DateTime, _rng: &mut impl Rng) -> Vec + where + Self: Sized, + { + vec![ + Self::new(&Method::GET, Version::HTTP_11, &"/".parse().unwrap()), + Self::new(&Method::POST, Version::HTTP_2, &"/foo/bar".parse().unwrap()), + Self::new( + &Method::PUT, + Version::HTTP_10, + &"/foo?bar=baz".parse().unwrap(), + ), + ] + } +} diff --git a/crates/templates/src/lib.rs b/crates/templates/src/lib.rs index 1e70683c..f9dbea55 100644 --- a/crates/templates/src/lib.rs +++ b/crates/templates/src/lib.rs @@ -49,7 +49,7 @@ pub use self::{ context::{ AppContext, CompatSsoContext, ConsentContext, EmailAddContext, EmailVerificationContext, EmailVerificationPageContext, EmptyContext, ErrorContext, FormPostContext, IndexContext, - LoginContext, LoginFormField, PolicyViolationContext, PostAuthContext, + LoginContext, LoginFormField, NotFoundContext, PolicyViolationContext, PostAuthContext, PostAuthContextInner, ReauthContext, ReauthFormField, RegisterContext, RegisterFormField, TemplateContext, UpstreamExistingLinkContext, UpstreamRegister, UpstreamSuggestLink, WithCsrf, WithOptionalSession, WithSession, @@ -222,6 +222,9 @@ pub enum TemplateError { } register_templates! { + /// Render the not found fallback page + pub fn render_not_found(NotFoundContext) { "pages/404.html" } + /// Render the frontend app pub fn render_app(AppContext) { "app.html" } @@ -294,6 +297,7 @@ impl Templates { now: chrono::DateTime, rng: &mut impl Rng, ) -> anyhow::Result<()> { + check::render_not_found(self, now, rng).await?; check::render_app(self, now, rng).await?; check::render_login(self, now, rng).await?; check::render_register(self, now, rng).await?; diff --git a/frontend/src/templates.css b/frontend/src/templates.css index 5094a4e8..43c616b4 100644 --- a/frontend/src/templates.css +++ b/frontend/src/templates.css @@ -13,6 +13,13 @@ * limitations under the License. */ +@import "@fontsource/inter/400.css"; +@import "@fontsource/inter/500.css"; +@import "@fontsource/inter/600.css"; +@import "@fontsource/inter/700.css"; +@import "@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css"; +@import "@vector-im/compound-web/dist/style.css"; + @config "../tailwind.templates.config.cjs"; @tailwind base; diff --git a/templates/pages/404.html b/templates/pages/404.html new file mode 100644 index 00000000..3a2b4872 --- /dev/null +++ b/templates/pages/404.html @@ -0,0 +1,35 @@ +{# +Copyright 2023 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. +#} + +{% extends "base.html" %} + +{% block content %} +
+
+

Page not found

+

The page you were looking for doesn't exist or has been moved

+ Go back to the homepage + +
+ + +
{{ method }} {{ uri }} {{ version }}
+
+{{ version }} 404 Not Found
+
+
+
+{% endblock %}