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

Split the service in multiple crates

This commit is contained in:
Quentin Gliech
2021-09-16 14:43:56 +02:00
parent da91564bf9
commit a44e33931c
83 changed files with 311 additions and 174 deletions

27
crates/cli/Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "mas-cli"
version = "0.1.0"
authors = ["Quentin Gliech <quenting@element.io>"]
edition = "2018"
license = "Apache-2.0"
[dependencies]
tokio = { version = "1.11.0", features = ["full"] }
anyhow = "1.0.44"
clap = "3.0.0-beta.4"
tracing = "0.1.27"
tracing-subscriber = "0.2.22"
dotenv = "0.15.0"
schemars = { version = "0.8.3", features = ["url", "chrono"] }
tower = { version = "0.4.8", features = ["full"] }
tower-http = { version = "0.1.1", features = ["full"] }
hyper = { version = "0.14.12", features = ["full"] }
serde_yaml = "0.8.21"
warp = "0.3.1"
argon2 = { version = "0.3.1", features = ["password-hash"] }
mas-config = { path = "../config" }
mas-core = { path = "../core" }
[dev-dependencies]
indoc = "1.0.3"

75
crates/cli/src/config.rs Normal file
View File

@ -0,0 +1,75 @@
// 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.
use clap::Clap;
use mas_config::{ConfigurationSection, RootConfig};
use schemars::schema_for;
use tracing::info;
use super::RootCommand;
#[derive(Clap, Debug)]
pub(super) struct ConfigCommand {
#[clap(subcommand)]
subcommand: ConfigSubcommand,
}
#[derive(Clap, Debug)]
enum ConfigSubcommand {
/// Dump the current config as YAML
Dump,
/// Print the JSON Schema that validates configuration files
Schema,
/// Check a config file
Check,
/// Generate a new config file
Generate,
}
impl ConfigCommand {
pub async fn run(&self, root: &RootCommand) -> anyhow::Result<()> {
use ConfigSubcommand as SC;
match &self.subcommand {
SC::Dump => {
let config: RootConfig = root.load_config()?;
serde_yaml::to_writer(std::io::stdout(), &config)?;
Ok(())
}
SC::Schema => {
let schema = schema_for!(RootConfig);
serde_yaml::to_writer(std::io::stdout(), &schema)?;
Ok(())
}
SC::Check => {
let _config: RootConfig = root.load_config()?;
info!(path = ?root.config, "Configuration file looks good");
Ok(())
}
SC::Generate => {
let config = RootConfig::load_and_generate().await?;
serde_yaml::to_writer(std::io::stdout(), &config)?;
Ok(())
}
}
}
}

View File

@ -0,0 +1,47 @@
// 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.
use anyhow::Context;
use clap::Clap;
use mas_config::DatabaseConfig;
use mas_core::storage::MIGRATOR;
use super::RootCommand;
#[derive(Clap, Debug)]
pub(super) struct DatabaseCommand {
#[clap(subcommand)]
subcommand: DatabaseSubcommand,
}
#[derive(Clap, Debug)]
enum DatabaseSubcommand {
/// Run database migrations
Migrate,
}
impl DatabaseCommand {
pub async fn run(&self, root: &RootCommand) -> anyhow::Result<()> {
let config: DatabaseConfig = root.load_config()?;
let pool = config.connect().await?;
// Run pending migrations
MIGRATOR
.run(&pool)
.await
.context("could not run migrations")?;
Ok(())
}
}

103
crates/cli/src/main.rs Normal file
View File

@ -0,0 +1,103 @@
// 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)]
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::suspicious_else_formatting)]
use std::path::PathBuf;
use anyhow::Context;
use clap::Clap;
use mas_config::ConfigurationSection;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
use self::{
config::ConfigCommand, database::DatabaseCommand, manage::ManageCommand, server::ServerCommand,
};
mod config;
mod database;
mod manage;
mod server;
#[derive(Clap, Debug)]
enum Subcommand {
/// Configuration-related commands
Config(ConfigCommand),
/// Manage the database
Database(DatabaseCommand),
/// Runs the web server
Server(ServerCommand),
/// Manage the instance
Manage(ManageCommand),
}
#[derive(Clap, Debug)]
struct RootCommand {
/// Path to the configuration file
#[clap(short, long, global = true, default_value = "config.yaml")]
config: PathBuf,
#[clap(subcommand)]
subcommand: Option<Subcommand>,
}
impl RootCommand {
async fn run(&self) -> anyhow::Result<()> {
use Subcommand as S;
match &self.subcommand {
Some(S::Config(c)) => c.run(self).await,
Some(S::Database(c)) => c.run(self).await,
Some(S::Server(c)) => c.run(self).await,
Some(S::Manage(c)) => c.run(self).await,
None => ServerCommand::default().run(self).await,
}
}
fn load_config<'de, T: ConfigurationSection<'de>>(&self) -> anyhow::Result<T> {
T::load_from_file(&self.config).context("could not load configuration")
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Load environment variables from .env files
if let Err(e) = dotenv::dotenv() {
// Display the error if it is something other than the .env file not existing
if !e.not_found() {
return Err(e).context("could not load .env file");
}
}
// Setup logging & tracing
let fmt_layer = tracing_subscriber::fmt::layer().with_writer(std::io::stderr);
let filter_layer = EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("info"))?;
let subscriber = Registry::default().with(filter_layer).with(fmt_layer);
subscriber
.try_init()
.context("could not initialize logging")?;
// Parse the CLI arguments
let opts = RootCommand::parse();
// And run the command
opts.run().await
}

59
crates/cli/src/manage.rs Normal file
View File

@ -0,0 +1,59 @@
// 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.
use argon2::Argon2;
use clap::Clap;
use mas_config::DatabaseConfig;
use mas_core::storage::register_user;
use tracing::{info, warn};
use super::RootCommand;
#[derive(Clap, Debug)]
pub(super) struct ManageCommand {
#[clap(subcommand)]
subcommand: ManageSubcommand,
}
#[derive(Clap, Debug)]
enum ManageSubcommand {
/// Register a new user
Register { username: String, password: String },
/// List active users
Users,
}
impl ManageCommand {
pub async fn run(&self, root: &RootCommand) -> anyhow::Result<()> {
use ManageSubcommand as SC;
match &self.subcommand {
SC::Register { username, password } => {
let config: DatabaseConfig = root.load_config()?;
let pool = config.connect().await?;
let hasher = Argon2::default();
let user = register_user(&pool, hasher, username, password).await?;
info!(?user, "User registered");
Ok(())
}
SC::Users => {
warn!("Not implemented yet");
Ok(())
}
}
}
}

93
crates/cli/src/server.rs Normal file
View File

@ -0,0 +1,93 @@
// 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.
use std::{
net::{SocketAddr, TcpListener},
time::Duration,
};
use anyhow::Context;
use clap::Clap;
use hyper::{header, Server};
use mas_config::RootConfig;
use mas_core::{
tasks::{self, TaskQueue},
templates::Templates,
};
use tower::{make::Shared, ServiceBuilder};
use tower_http::{
compression::CompressionLayer,
sensitive_headers::SetSensitiveHeadersLayer,
trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer},
LatencyUnit,
};
use super::RootCommand;
#[derive(Clap, Debug, Default)]
pub(super) struct ServerCommand;
impl ServerCommand {
pub async fn run(&self, root: &RootCommand) -> anyhow::Result<()> {
let config: RootConfig = root.load_config()?;
let addr: SocketAddr = config.http.address.parse()?;
let listener = TcpListener::bind(addr)?;
// Connect to the database
let pool = config.database.connect().await?;
// Load and compile the templates
let templates = Templates::load().context("could not load templates")?;
// Start the server
let root = mas_core::handlers::root(&pool, &templates, &config);
let queue = TaskQueue::default();
queue.recuring(Duration::from_secs(15), tasks::cleanup_expired(&pool));
queue.start();
let warp_service = warp::service(root);
let service = ServiceBuilder::new()
// Add high level tracing/logging to all requests
.layer(
TraceLayer::new_for_http()
.make_span_with(DefaultMakeSpan::new().include_headers(true))
.on_response(
DefaultOnResponse::new()
.include_headers(true)
.latency_unit(LatencyUnit::Micros),
),
)
// Set a timeout
.timeout(Duration::from_secs(10))
// Compress responses
.layer(CompressionLayer::new())
// Mark the `Authorization` and `Cookie` headers as sensitive so it doesn't show in logs
.layer(SetSensitiveHeadersLayer::new(vec![
header::AUTHORIZATION,
header::COOKIE,
]))
.service(warp_service);
tracing::info!("Listening on http://{}", listener.local_addr().unwrap());
Server::from_tcp(listener)?
.serve(Shared::new(service))
.await?;
Ok(())
}
}