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

74
Cargo.lock generated
View File

@ -1355,22 +1355,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matchers"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
name = "mas-cli"
version = "0.1.0"
dependencies = [
"regex-automata",
"anyhow",
"argon2",
"clap",
"dotenv",
"hyper",
"indoc",
"mas-config",
"mas-core",
"schemars",
"serde_yaml",
"tokio",
"tower",
"tower-http",
"tracing",
"tracing-subscriber",
"warp",
]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
name = "mas-config"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"elliptic-curve",
"figment",
"indoc",
"jwt-compact",
"k256",
"pkcs8",
"rand 0.8.4",
"rsa",
"schemars",
"serde",
"serde_json",
"serde_with",
"sqlx",
"thiserror",
"tokio",
"tracing",
"url",
]
[[package]]
name = "matrix-authentication-service"
name = "mas-core"
version = "0.1.0"
dependencies = [
"anyhow",
@ -1379,11 +1412,9 @@ dependencies = [
"bincode",
"chacha20poly1305",
"chrono",
"clap",
"cookie",
"crc",
"data-encoding",
"dotenv",
"elliptic-curve",
"figment",
"futures-util",
@ -1393,6 +1424,7 @@ dependencies = [
"itertools",
"jwt-compact",
"k256",
"mas-config",
"mime",
"oauth2-types",
"password-hash",
@ -1411,14 +1443,26 @@ dependencies = [
"thiserror",
"tokio",
"tokio-stream",
"tower",
"tower-http",
"tracing",
"tracing-subscriber",
"url",
"warp",
]
[[package]]
name = "matchers"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
dependencies = [
"regex-automata",
]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "md-5"
version = "0.9.1"

View File

@ -1,8 +1,5 @@
[workspace]
members = [
"oauth2-types",
"matrix-authentication-service",
]
members = ["crates/*"]
resolver = "2"

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"

View File

@ -13,11 +13,11 @@
// limitations under the License.
use clap::Clap;
use mas_config::{ConfigurationSection, RootConfig};
use schemars::schema_for;
use tracing::info;
use super::RootCommand;
use crate::config::{ConfigurationSection, RootConfig};
#[derive(Clap, Debug)]
pub(super) struct ConfigCommand {

View File

@ -14,9 +14,10 @@
use anyhow::Context;
use clap::Clap;
use mas_config::DatabaseConfig;
use mas_core::storage::MIGRATOR;
use super::RootCommand;
use crate::{config::DatabaseConfig, storage::MIGRATOR};
#[derive(Clap, Debug)]
pub(super) struct DatabaseCommand {

View File

@ -12,18 +12,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Clippy seems confused by clap.rs derive macros
#![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,
};
use crate::config::ConfigurationSection;
mod config;
mod database;
@ -46,7 +50,7 @@ enum Subcommand {
}
#[derive(Clap, Debug)]
pub struct RootCommand {
struct RootCommand {
/// Path to the configuration file
#[clap(short, long, global = true, default_value = "config.yaml")]
config: PathBuf,
@ -56,7 +60,7 @@ pub struct RootCommand {
}
impl RootCommand {
pub async fn run(&self) -> anyhow::Result<()> {
async fn run(&self) -> anyhow::Result<()> {
use Subcommand as S;
match &self.subcommand {
Some(S::Config(c)) => c.run(self).await,
@ -71,3 +75,29 @@ impl RootCommand {
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
}

View File

@ -14,10 +14,11 @@
use argon2::Argon2;
use clap::Clap;
use mas_config::DatabaseConfig;
use mas_core::storage::register_user;
use tracing::{info, warn};
use super::RootCommand;
use crate::{config::DatabaseConfig, storage::register_user};
#[derive(Clap, Debug)]
pub(super) struct ManageCommand {

View File

@ -20,6 +20,11 @@ use std::{
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,
@ -29,11 +34,6 @@ use tower_http::{
};
use super::RootCommand;
use crate::{
config::RootConfig,
tasks::{self, TaskQueue},
templates::Templates,
};
#[derive(Clap, Debug, Default)]
pub(super) struct ServerCommand;
@ -52,7 +52,7 @@ impl ServerCommand {
let templates = Templates::load().context("could not load templates")?;
// Start the server
let root = crate::handlers::root(&pool, &templates, &config);
let root = mas_core::handlers::root(&pool, &templates, &config);
let queue = TaskQueue::default();
queue.recuring(Duration::from_secs(15), tasks::cleanup_expired(&pool));

38
crates/config/Cargo.toml Normal file
View File

@ -0,0 +1,38 @@
[package]
name = "mas-config"
version = "0.1.0"
authors = ["Quentin Gliech <quenting@element.io>"]
edition = "2018"
license = "Apache-2.0"
[dependencies]
tokio = { version = "1.11.0", features = [] }
tracing = "0.1.27"
async-trait = "0.1.51"
thiserror = "1.0.29"
anyhow = "1.0.44"
schemars = { version = "0.8.3", features = ["url", "chrono"] }
figment = { version = "0.10.6", features = ["env", "yaml", "test"] }
chrono = { version = "0.4.19", features = ["serde"] }
url = { version = "2.2.2", features = ["serde"] }
serde = { version = "1.0.130", features = ["derive"] }
serde_with = { version = "1.10.0", features = ["hex", "chrono"] }
serde_json = "1.0.68"
sqlx = { version = "0.5.7", features = ["runtime-tokio-rustls", "postgres"] }
rand = "0.8.4"
rsa = "0.5.0"
k256 = "0.9.6"
pkcs8 = { version = "0.7.6", features = ["pem"] }
elliptic-curve = { version = "0.10.6", features = ["pem"] }
indoc = "1.0.3"
[dependencies.jwt-compact]
# Waiting on the next release because of the bump of the `rsa` dependency
git = "https://github.com/slowli/jwt-compact.git"
rev = "7a6dee6824c1d4e7c7f81019c9a968e5c9e44923"
features = ["rsa", "k256"]

View File

@ -42,4 +42,8 @@ impl ConfigurationSection<'_> for CookiesConfig {
secret: rand::random(),
})
}
fn test() -> Self {
Self { secret: [0xEA; 32] }
}
}

View File

@ -52,6 +52,10 @@ impl ConfigurationSection<'_> for CsrfConfig {
async fn generate() -> anyhow::Result<Self> {
Ok(Self::default())
}
fn test() -> Self {
Self::default()
}
}
#[cfg(test)]

View File

@ -19,12 +19,11 @@ use async_trait::async_trait;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none};
use sqlx::{
postgres::{PgConnectOptions, PgPool, PgPoolOptions},
ConnectOptions,
};
use tracing::log::LevelFilter;
use sqlx::postgres::{PgConnectOptions, PgPool, PgPoolOptions};
// FIXME
// use sqlx::ConnectOptions
// use tracing::log::LevelFilter;
use super::ConfigurationSection;
fn default_uri() -> String {
@ -102,15 +101,16 @@ pub struct DatabaseConfig {
impl DatabaseConfig {
#[tracing::instrument(err)]
pub async fn connect(&self) -> anyhow::Result<PgPool> {
let mut options = self
let options = self
.uri
.parse::<PgConnectOptions>()
.context("invalid database URL")?
.application_name("matrix-authentication-service");
options
.log_statements(LevelFilter::Debug)
.log_slow_statements(LevelFilter::Warn, Duration::from_millis(100));
// FIXME
// options
// .log_statements(LevelFilter::Debug)
// .log_slow_statements(LevelFilter::Warn, Duration::from_millis(100));
PgPoolOptions::new()
.max_connections(self.max_connections)
@ -133,6 +133,10 @@ impl ConfigurationSection<'_> for DatabaseConfig {
async fn generate() -> anyhow::Result<Self> {
Ok(Self::default())
}
fn test() -> Self {
Self::default()
}
}
#[cfg(test)]

View File

@ -45,4 +45,8 @@ impl ConfigurationSection<'_> for HttpConfig {
async fn generate() -> anyhow::Result<Self> {
Ok(Self::default())
}
fn test() -> Self {
Self::default()
}
}

View File

@ -63,4 +63,14 @@ impl ConfigurationSection<'_> for RootConfig {
csrf: CsrfConfig::generate().await?,
})
}
fn test() -> Self {
Self {
oauth2: OAuth2Config::test(),
http: HttpConfig::test(),
database: DatabaseConfig::test(),
cookies: CookiesConfig::test(),
csrf: CsrfConfig::test(),
}
}
}

View File

@ -341,9 +341,49 @@ impl OAuth2Config {
.join(".well-known/openid-configuration")
.expect("could not build discovery url")
}
}
#[cfg(test)]
pub fn test() -> Self {
#[async_trait]
impl ConfigurationSection<'_> for OAuth2Config {
fn path() -> &'static str {
"oauth2"
}
#[tracing::instrument]
async fn generate() -> anyhow::Result<Self> {
info!("Generating keys...");
let span = tracing::info_span!("rsa");
let rsa_key = task::spawn_blocking(move || {
let _entered = span.enter();
let mut rng = rand::thread_rng();
let ret =
RsaPrivateKey::new(&mut rng, 2048).context("could not generate RSA private key");
info!("Done generating RSA key");
ret
})
.await
.context("could not join blocking task")??;
let span = tracing::info_span!("ecdsa");
let ecdsa_key = task::spawn_blocking(move || {
let _entered = span.enter();
let rng = rand::thread_rng();
let ret = k256::SecretKey::random(rng);
info!("Done generating ECDSA key");
ret
})
.await
.context("could not join blocking task")?;
Ok(Self {
issuer: default_oauth2_issuer(),
clients: Vec::new(),
keys: KeySet(vec![Key::from_rsa(rsa_key), Key::from_ecdsa(ecdsa_key)]),
})
}
fn test() -> Self {
let rsa_key = Key::from_rsa_pem(indoc::indoc! {r#"
-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAymS2RkeIZo7pUeEN
@ -374,47 +414,6 @@ impl OAuth2Config {
}
}
#[async_trait]
impl ConfigurationSection<'_> for OAuth2Config {
fn path() -> &'static str {
"oauth2"
}
#[tracing::instrument]
async fn generate() -> anyhow::Result<Self> {
info!("Generating keys...");
let span = tracing::info_span!("rsa");
let rsa_key = task::spawn_blocking(move || {
let _entered = span.enter();
let mut rng = rand::thread_rng();
let ret =
RsaPrivateKey::new(&mut rng, 2048).context("could not generate RSA private key");
info!("Done generating RSA key");
ret
});
let span = tracing::info_span!("ecdsa");
let ecdsa_key = task::spawn_blocking(move || {
let _entered = span.enter();
let rng = rand::thread_rng();
let ret = k256::SecretKey::random(rng);
info!("Done generating ECDSA key");
ret
});
let (ecdsa_key, rsa_key) = tokio::join!(ecdsa_key, rsa_key);
let rsa_key = rsa_key.context("could not join blocking task")??;
let ecdsa_key = ecdsa_key.context("could not join blocking task")?;
Ok(Self {
issuer: default_oauth2_issuer(),
clients: Vec::new(),
keys: KeySet(vec![Key::from_rsa(rsa_key), Key::from_ecdsa(ecdsa_key)]),
})
}
}
#[cfg(test)]
mod tests {
use figment::Jail;

View File

@ -66,4 +66,7 @@ pub trait ConfigurationSection<'a>: Sized + Deserialize<'a> + Serialize {
.merge(Yaml::file(path))
.extract_inner(Self::path())
}
/// Generate config used in unit tests
fn test() -> Self;
}

View File

@ -1,5 +1,5 @@
[package]
name = "matrix-authentication-service"
name = "mas-core"
version = "0.1.0"
authors = ["Quentin Gliech <quenting@element.io>"]
edition = "2018"
@ -14,7 +14,6 @@ futures-util = "0.3.17"
# Logging and tracing
tracing = "0.1.27"
tracing-subscriber = "0.2.22"
# Error management
thiserror = "1.0.29"
@ -22,8 +21,6 @@ anyhow = "1.0.44"
# Web server
warp = "0.3.1"
tower = { version = "0.4.8", features = ["full"] }
tower-http = { version = "0.1.1", features = ["full"] }
hyper = { version = "0.14.12", features = ["full"] }
# Template engine
@ -40,10 +37,8 @@ serde_json = "1.0.68"
serde_urlencoded = "0.7.0"
# Argument & config parsing
clap = "3.0.0-beta.4"
figment = { version = "0.10.6", features = ["env", "yaml", "test"] }
schemars = { version = "0.8.3", features = ["url", "chrono"] }
dotenv = "0.15.0"
# Password hashing
argon2 = { version = "0.3.1", features = ["password-hash"] }
@ -70,6 +65,7 @@ headers = "0.3.4"
cookie = "0.15.1"
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
mas-config = { path = "../config" }
[dependencies.jwt-compact]
# Waiting on the next release because of the bump of the `rsa` dependency

View File

@ -93,7 +93,7 @@ pub struct ErroredForm<FieldType> {
}
impl<T> ErroredForm<T> {
pub fn new() -> Self {
#[must_use] pub fn new() -> Self {
Self {
form: Vec::new(),
fields: Vec::new(),

View File

@ -28,11 +28,13 @@ pub enum ClientAuthentication {
}
impl ClientAuthentication {
#[must_use]
pub fn public(&self) -> bool {
matches!(self, &Self::None)
}
}
#[must_use]
pub fn with_client_auth<T: DeserializeOwned + Send + 'static>(
oauth2_config: &OAuth2Config,
) -> impl Filter<Extract = (ClientAuthentication, OAuth2ClientConfig, T), Error = Rejection>
@ -132,6 +134,8 @@ struct ClientAuthForm<T> {
#[cfg(test)]
mod tests {
use mas_config::ConfigurationSection;
use super::*;
fn oauth2_config() -> OAuth2Config {

View File

@ -69,7 +69,7 @@ impl EncryptedCookie {
}
}
pub fn maybe_encrypted<T>(
#[must_use] pub fn maybe_encrypted<T>(
options: &CookiesConfig,
) -> impl Filter<Extract = (Option<T>,), Error = Infallible> + Clone + Send + Sync + 'static
where
@ -83,7 +83,7 @@ where
})
}
pub fn encrypted<T>(
#[must_use] pub fn encrypted<T>(
options: &CookiesConfig,
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone + Send + Sync + 'static
where
@ -97,7 +97,7 @@ where
})
}
pub fn with_cookie_saver(
#[must_use] pub fn with_cookie_saver(
options: &CookiesConfig,
) -> impl Filter<Extract = (EncryptedCookieSaver,), Error = Infallible> + Clone + Send + Sync + 'static
{

View File

@ -66,7 +66,7 @@ impl CsrfToken {
}
/// Get the value to include in HTML forms
pub fn form_value(&self) -> String {
#[must_use] pub fn form_value(&self) -> String {
BASE64URL_NOPAD.encode(&self.token[..])
}
@ -112,7 +112,7 @@ impl<T> CsrfForm<T> {
}
}
pub fn csrf_token(
#[must_use] pub fn csrf_token(
cookies_config: &CookiesConfig,
) -> impl Filter<Extract = (CsrfToken,), Error = Rejection> + Clone + Send + Sync + 'static {
super::cookies::encrypted(cookies_config).and_then(move |token: CsrfToken| async move {
@ -121,7 +121,7 @@ pub fn csrf_token(
})
}
pub fn updated_csrf_token(
#[must_use] pub fn updated_csrf_token(
cookies_config: &CookiesConfig,
csrf_config: &CsrfConfig,
) -> impl Filter<Extract = (CsrfToken,), Error = Rejection> + Clone + Send + Sync + 'static {
@ -144,7 +144,7 @@ pub fn updated_csrf_token(
)
}
pub fn protected_form<T>(
#[must_use] pub fn protected_form<T>(
cookies_config: &CookiesConfig,
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone + Send + Sync + 'static
where

View File

@ -33,14 +33,14 @@ use crate::{
templates::Templates,
};
pub fn with_templates(
#[must_use] pub fn with_templates(
templates: &Templates,
) -> impl Filter<Extract = (Templates,), Error = Infallible> + Clone + Send + Sync + 'static {
let templates = templates.clone();
warp::any().map(move || templates.clone())
}
pub fn with_keys(
#[must_use] pub fn with_keys(
oauth2_config: &OAuth2Config,
) -> impl Filter<Extract = (KeySet,), Error = Infallible> + Clone + Send + Sync + 'static {
let keyset = oauth2_config.keys.clone();

View File

@ -32,7 +32,7 @@ pub struct SessionCookie {
}
impl SessionCookie {
pub fn from_session_info(info: &SessionInfo) -> Self {
#[must_use] pub fn from_session_info(info: &SessionInfo) -> Self {
Self {
current: info.key(),
}
@ -52,7 +52,7 @@ impl EncryptableCookieValue for SessionCookie {
}
}
pub fn with_optional_session(
#[must_use] pub fn with_optional_session(
pool: &PgPool,
cookies_config: &CookiesConfig,
) -> impl Filter<Extract = (Option<SessionInfo>,), Error = Rejection> + Clone + Send + Sync + 'static
@ -71,7 +71,7 @@ pub fn with_optional_session(
)
}
pub fn with_session(
#[must_use] pub fn with_session(
pool: &PgPool,
cookies_config: &CookiesConfig,
) -> impl Filter<Extract = (SessionInfo,), Error = Rejection> + Clone + Send + Sync + 'static {

View File

@ -25,7 +25,7 @@ mod views;
use self::{health::filter as health, oauth2::filter as oauth2, views::filter as views};
pub fn root(
#[must_use] pub fn root(
pool: &PgPool,
templates: &Templates,
config: &RootConfig,

31
crates/core/src/lib.rs Normal file
View File

@ -0,0 +1,31 @@
// 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::missing_panics_doc)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::implicit_hasher)]
pub(crate) use mas_config as config;
pub mod errors;
pub mod filters;
pub mod handlers;
pub mod storage;
pub mod tasks;
pub mod templates;
pub mod tokens;

View File

@ -67,7 +67,7 @@ pub struct OAuth2AccessTokenLookup {
}
impl OAuth2AccessTokenLookup {
pub fn exp(&self) -> DateTime<Utc> {
#[must_use] pub fn exp(&self) -> DateTime<Utc> {
self.created_at + Duration::seconds(i64::from(self.expires_after))
}
}

View File

@ -103,7 +103,7 @@ impl OAuth2Session {
}
}
pub fn max_auth_time(&self) -> Option<DateTime<Utc>> {
#[must_use] pub fn max_auth_time(&self) -> Option<DateTime<Utc>> {
self.max_age
.map(|d| Duration::seconds(i64::from(d)))
.map(|d| self.created_at - d)

View File

@ -44,7 +44,7 @@ pub struct SessionInfo {
}
impl SessionInfo {
pub fn key(&self) -> i64 {
#[must_use] pub fn key(&self) -> i64 {
self.id
}

View File

@ -38,6 +38,6 @@ impl Task for CleanupExpired {
}
}
pub fn cleanup_expired(pool: &Pool<Postgres>) -> impl Task + Clone {
#[must_use] pub fn cleanup_expired(pool: &Pool<Postgres>) -> impl Task + Clone {
CleanupExpired(pool.clone())
}

View File

@ -212,7 +212,7 @@ pub struct IndexContext {
}
impl IndexContext {
pub fn new(discovery_url: Url) -> Self {
#[must_use] pub fn new(discovery_url: Url) -> Self {
Self { discovery_url }
}
}
@ -230,7 +230,7 @@ pub struct LoginContext {
}
impl LoginContext {
pub fn with_form_error(form: ErroredForm<LoginFormField>) -> Self {
#[must_use] pub fn with_form_error(form: ErroredForm<LoginFormField>) -> Self {
Self { form }
}
}
@ -267,22 +267,22 @@ pub struct ErrorContext {
}
impl ErrorContext {
pub fn new() -> Self {
#[must_use] pub fn new() -> Self {
Self::default()
}
pub fn with_code(mut self, code: &'static str) -> Self {
#[must_use] pub fn with_code(mut self, code: &'static str) -> Self {
self.code = Some(code);
self
}
pub fn with_description(mut self, description: String) -> Self {
#[must_use] pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self
}
#[allow(dead_code)]
pub fn with_details(mut self, details: String) -> Self {
#[must_use] pub fn with_details(mut self, details: String) -> Self {
self.details = Some(details);
self
}

View File

@ -1,60 +0,0 @@
// 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)]
use anyhow::Context;
use clap::Clap;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
mod cli;
mod config;
mod errors;
mod filters;
mod handlers;
mod storage;
mod tasks;
mod templates;
mod tokens;
use self::cli::RootCommand;
#[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
}