1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Frontend/static files building & serving

This commit is contained in:
Quentin Gliech
2021-12-09 22:09:39 +01:00
parent c53318eca0
commit d8df34db4c
16 changed files with 4284 additions and 7 deletions

View File

@ -1,3 +1,6 @@
target/ target/
crates/*/target crates/*/target
crates/*/node_modules
.git/ .git/
Dockerfile
.dockerignore

45
Cargo.lock generated
View File

@ -1539,6 +1539,7 @@ dependencies = [
"k256", "k256",
"mas-config", "mas-config",
"mas-data-model", "mas-data-model",
"mas-static-files",
"mas-templates", "mas-templates",
"mime", "mime",
"oauth2-types", "oauth2-types",
@ -1573,6 +1574,16 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "mas-static-files"
version = "0.1.0"
dependencies = [
"headers",
"mime_guess",
"rust-embed",
"warp",
]
[[package]] [[package]]
name = "mas-templates" name = "mas-templates"
version = "0.1.0" version = "0.1.0"
@ -2528,6 +2539,40 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rust-embed"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40377bff8cceee81e28ddb73ac97f5c2856ce5522f0b260b763f434cdfae602"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e763e24ba2bf0c72bc6be883f967f794a019fafd1b86ba1daff9c91a7edd30"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad22c7226e4829104deab21df575e995bfbc4adfad13a595e387477f238c1aec"
dependencies = [
"sha2",
"walkdir",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"

View File

@ -7,12 +7,27 @@
# there is a small script that translates those platforms to LLVM triples, # there is a small script that translates those platforms to LLVM triples,
# respectively x86-64-unknown-linux-gnu and aarch64-unknown-linux-gnu # respectively x86-64-unknown-linux-gnu and aarch64-unknown-linux-gnu
ARG RUSTC_VERSION=1.56.1 # The Debian version and version name must be in sync
ARG DEBIAN_VERSION=11
ARG DEBIAN_VERSION_NAME=bullseye
ARG RUSTC_VERSION=1.57.0
ARG NODEJS_VERSION=16
## Build stage that builds the static files/frontend ##
FROM --platform=${BUILDPLATFORM} docker.io/library/node:${NODEJS_VERSION}-${DEBIAN_VERSION_NAME}-slim AS static-files
WORKDIR /app/crates/static-files
COPY ./crates/static-files/package.json ./crates/static-files/package-lock.json /app/crates/static-files/
RUN npm ci
COPY . /app/
RUN npm run build
# Change the timestamp of built files for better caching
RUN find public -type f -exec touch -t 197001010000.00 {} +
## Base image with cargo-chef and the right cross-compilation toolchain ## ## Base image with cargo-chef and the right cross-compilation toolchain ##
# cargo-chef helps with caching dependencies between builds # cargo-chef helps with caching dependencies between builds
# The image Debian base name (bullseye) must be in sync with the runtime variant (debian11) # The image Debian base name (bullseye) must be in sync with the runtime variant (debian11)
FROM --platform=${BUILDPLATFORM} docker.io/library/rust:${RUSTC_VERSION}-slim-bullseye AS chef FROM --platform=${BUILDPLATFORM} docker.io/library/rust:${RUSTC_VERSION}-slim-${DEBIAN_VERSION_NAME} AS chef
# Install x86_64 and aarch64 cross-compiling stack # Install x86_64 and aarch64 cross-compiling stack
RUN apt update && apt install -y --no-install-recommends \ RUN apt update && apt install -y --no-install-recommends \
@ -60,6 +75,7 @@ RUN cargo chef cook \
# Build the rest # Build the rest
COPY . . COPY . .
COPY --from=static-files /app/crates/static-files/public /app/crates/static-files/public
RUN cargo build \ RUN cargo build \
--release \ --release \
--bin mas-cli \ --bin mas-cli \
@ -68,8 +84,12 @@ RUN cargo build \
# Move the binary to avoid having to guess its name in the next stage # Move the binary to avoid having to guess its name in the next stage
RUN mv target/$(/docker-arch-to-rust-target.sh "${TARGETPLATFORM}")/release/mas-cli /mas-cli RUN mv target/$(/docker-arch-to-rust-target.sh "${TARGETPLATFORM}")/release/mas-cli /mas-cli
## Runtime stage ## ## Runtime stage, debug variant ##
# The image Debian base name (bullseye) must be in sync with the runtime variant (debian11) FROM --platform=${TARGETPLATFORM} gcr.io/distroless/cc-debian${DEBIAN_VERSION}:debug-nonroot AS debug
FROM --platform=${TARGETPLATFORM} gcr.io/distroless/cc-debian11:nonroot COPY --from=builder /mas-cli /mas-cli
ENTRYPOINT ["/mas-cli"]
## Runtime stage ##
FROM --platform=${TARGETPLATFORM} gcr.io/distroless/cc-debian${DEBIAN_VERSION}:nonroot
COPY --from=builder /mas-cli /mas-cli COPY --from=builder /mas-cli /mas-cli
ENTRYPOINT ["/mas-cli"] ENTRYPOINT ["/mas-cli"]

View File

@ -8,7 +8,15 @@ See the [Documentation](https://matrix-org.github.io/matrix-authentication-servi
## Running ## Running
- [Install Rust and Cargo](https://www.rust-lang.org/learn/get-started) - [Install Rust and Cargo](https://www.rust-lang.org/learn/get-started)
- [Install Node.js and npm](https://nodejs.org/)
- Clone this repository - Clone this repository
- Generate the frontend:
```sh
cd crates/static-files
npm ci
npm run build
cd ../..
```
- Generate the sample config via `cargo run -- config generate > config.yaml` - Generate the sample config via `cargo run -- config generate > config.yaml`
- Run the database migrations via `cargo run -- database migrate` - Run the database migrations via `cargo run -- database migrate`
- Run the server via `cargo run -- server -c config.yaml` - Run the server via `cargo run -- server -c config.yaml`

View File

@ -41,7 +41,7 @@ indoc = "1.0.3"
[features] [features]
default = ["otlp", "jaeger", "zipkin"] default = ["otlp", "jaeger", "zipkin"]
dev = ["mas-templates/dev"] dev = ["mas-templates/dev", "mas-core/dev"]
# Enable OpenTelemetry OTLP exporter. Requires "protoc" # Enable OpenTelemetry OTLP exporter. Requires "protoc"
otlp = ["opentelemetry-otlp"] otlp = ["opentelemetry-otlp"]
# Enable OpenTelemetry Jaeger exporter and propagator. # Enable OpenTelemetry Jaeger exporter and propagator.

View File

@ -12,6 +12,8 @@
// 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::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -26,12 +28,16 @@ fn default_http_address() -> String {
pub struct HttpConfig { pub struct HttpConfig {
#[serde(default = "default_http_address")] #[serde(default = "default_http_address")]
pub address: String, pub address: String,
#[serde(default)]
pub web_root: Option<PathBuf>,
} }
impl Default for HttpConfig { impl Default for HttpConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
address: default_http_address(), address: default_http_address(),
web_root: None,
} }
} }
} }

View File

@ -5,6 +5,9 @@ authors = ["Quentin Gliech <quenting@element.io>"]
edition = "2018" edition = "2018"
license = "Apache-2.0" license = "Apache-2.0"
[features]
dev = ["mas-static-files/dev", "mas-templates/dev"]
[dependencies] [dependencies]
# Async runtime # Async runtime
tokio = { version = "1.14.0", features = ["full"] } tokio = { version = "1.14.0", features = ["full"] }
@ -63,6 +66,7 @@ oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
mas-config = { path = "../config" } mas-config = { path = "../config" }
mas-data-model = { path = "../data-model" } mas-data-model = { path = "../data-model" }
mas-templates = { path = "../templates" } mas-templates = { path = "../templates" }
mas-static-files = { path = "../static-files" }
[dev-dependencies] [dev-dependencies]
indoc = "1.0.3" indoc = "1.0.3"

View File

@ -15,6 +15,7 @@
#![allow(clippy::unused_async)] // Some warp filters need that #![allow(clippy::unused_async)] // Some warp filters need that
use mas_config::RootConfig; use mas_config::RootConfig;
use mas_static_files::filter as static_files;
use mas_templates::Templates; use mas_templates::Templates;
use sqlx::PgPool; use sqlx::PgPool;
use warp::{filters::BoxedFilter, Filter, Reply}; use warp::{filters::BoxedFilter, Filter, Reply};
@ -31,7 +32,8 @@ pub fn root(
templates: &Templates, templates: &Templates,
config: &RootConfig, config: &RootConfig,
) -> BoxedFilter<(impl Reply,)> { ) -> BoxedFilter<(impl Reply,)> {
health(pool) static_files(config.http.web_root.clone())
.or(health(pool))
.or(oauth2(pool, templates, &config.oauth2, &config.cookies)) .or(oauth2(pool, templates, &config.oauth2, &config.cookies))
.or(views( .or(views(
pool, pool,

2
crates/static-files/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/node_modules/
/public/tailwind.css

View File

@ -0,0 +1,15 @@
[package]
name = "mas-static-files"
version = "0.1.0"
authors = ["Quentin Gliech <quenting@element.io>"]
edition = "2018"
license = "Apache-2.0"
[features]
dev = []
[dependencies]
headers = "0.3.5"
mime_guess = "2.0.3"
rust-embed = "6.3.0"
warp = "0.3.2"

3996
crates/static-files/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
{
"name": "static-files",
"private": true,
"scripts": {
"build": "tailwindcss --postcss -o public/tailwind.css",
"start": "tailwindcss --postcss -o public/tailwind.css --watch"
},
"license": "Apache-2.0",
"devDependencies": {
"autoprefixer": "^10.4.0",
"cssnano": "^5.0.12",
"postcss": "^8.4.4",
"tailwindcss": "^2.2.19"
}
}

View File

@ -0,0 +1,21 @@
// Copyright 2021 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.
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
cssnano: {},
}
}

View File

@ -0,0 +1,113 @@
// Copyright 2021 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.
#![forbid(unsafe_code)]
#![deny(clippy::all)]
#![deny(rustdoc::broken_intra_doc_links)]
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::unused_async)]
use std::path::PathBuf;
use warp::{filters::BoxedFilter, Filter, Reply};
#[cfg(not(feature = "dev"))]
mod builtin {
use std::{convert::TryInto, fmt::Write, str::FromStr};
use headers::{ContentLength, ContentType, ETag, HeaderMapExt};
use rust_embed::RustEmbed;
use warp::{
filters::BoxedFilter, hyper::StatusCode, path::Tail, reply::Response, Filter, Rejection,
Reply,
};
#[derive(RustEmbed)]
#[folder = "public/"]
struct Asset;
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 mime = mime_guess::from_path(path).first_or_octet_stream();
let mut res = Response::new(asset.data.into());
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()
}
}
#[cfg(feature = "dev")]
mod builtin {
use std::path::PathBuf;
use warp::{filters::BoxedFilter, Reply};
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()
}
#[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()
}
}

View File

@ -0,0 +1,26 @@
// Copyright 2021 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.
module.exports = {
mode: "jit",
purge: ["../templates/src/res/*.html"],
darkMode: false,
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}

View File

@ -21,6 +21,7 @@ limitations under the License.
<title>{% block title %}matrix-authentication-service{% endblock title %}</title> <title>{% block title %}matrix-authentication-service{% endblock title %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<link rel="stylesheet" href="/tailwind.css">
</head> </head>
<body> <body>
<nav class="navbar is-dark" role="navigation" aria-label="main navigation"> <nav class="navbar is-dark" role="navigation" aria-label="main navigation">