You've already forked authentication-service
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:
74
Cargo.lock
generated
74
Cargo.lock
generated
@ -1355,22 +1355,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "mas-cli"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
|
|
||||||
dependencies = [
|
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]]
|
[[package]]
|
||||||
name = "matches"
|
name = "mas-config"
|
||||||
version = "0.1.9"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
dependencies = [
|
||||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
"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]]
|
[[package]]
|
||||||
name = "matrix-authentication-service"
|
name = "mas-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1379,11 +1412,9 @@ dependencies = [
|
|||||||
"bincode",
|
"bincode",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
|
||||||
"cookie",
|
"cookie",
|
||||||
"crc",
|
"crc",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"dotenv",
|
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
"figment",
|
"figment",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1393,6 +1424,7 @@ dependencies = [
|
|||||||
"itertools",
|
"itertools",
|
||||||
"jwt-compact",
|
"jwt-compact",
|
||||||
"k256",
|
"k256",
|
||||||
|
"mas-config",
|
||||||
"mime",
|
"mime",
|
||||||
"oauth2-types",
|
"oauth2-types",
|
||||||
"password-hash",
|
"password-hash",
|
||||||
@ -1411,14 +1443,26 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tower",
|
|
||||||
"tower-http",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
|
||||||
"url",
|
"url",
|
||||||
"warp",
|
"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]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = [
|
members = ["crates/*"]
|
||||||
"oauth2-types",
|
|
||||||
"matrix-authentication-service",
|
|
||||||
]
|
|
||||||
|
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
27
crates/cli/Cargo.toml
Normal file
27
crates/cli/Cargo.toml
Normal 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"
|
@ -13,11 +13,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
|
use mas_config::{ConfigurationSection, RootConfig};
|
||||||
use schemars::schema_for;
|
use schemars::schema_for;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use super::RootCommand;
|
use super::RootCommand;
|
||||||
use crate::config::{ConfigurationSection, RootConfig};
|
|
||||||
|
|
||||||
#[derive(Clap, Debug)]
|
#[derive(Clap, Debug)]
|
||||||
pub(super) struct ConfigCommand {
|
pub(super) struct ConfigCommand {
|
@ -14,9 +14,10 @@
|
|||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
|
use mas_config::DatabaseConfig;
|
||||||
|
use mas_core::storage::MIGRATOR;
|
||||||
|
|
||||||
use super::RootCommand;
|
use super::RootCommand;
|
||||||
use crate::{config::DatabaseConfig, storage::MIGRATOR};
|
|
||||||
|
|
||||||
#[derive(Clap, Debug)]
|
#[derive(Clap, Debug)]
|
||||||
pub(super) struct DatabaseCommand {
|
pub(super) struct DatabaseCommand {
|
@ -12,18 +12,22 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
// 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)]
|
#![allow(clippy::suspicious_else_formatting)]
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
|
use mas_config::ConfigurationSection;
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
config::ConfigCommand, database::DatabaseCommand, manage::ManageCommand, server::ServerCommand,
|
config::ConfigCommand, database::DatabaseCommand, manage::ManageCommand, server::ServerCommand,
|
||||||
};
|
};
|
||||||
use crate::config::ConfigurationSection;
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod database;
|
mod database;
|
||||||
@ -46,7 +50,7 @@ enum Subcommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clap, Debug)]
|
#[derive(Clap, Debug)]
|
||||||
pub struct RootCommand {
|
struct RootCommand {
|
||||||
/// Path to the configuration file
|
/// Path to the configuration file
|
||||||
#[clap(short, long, global = true, default_value = "config.yaml")]
|
#[clap(short, long, global = true, default_value = "config.yaml")]
|
||||||
config: PathBuf,
|
config: PathBuf,
|
||||||
@ -56,7 +60,7 @@ pub struct RootCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RootCommand {
|
impl RootCommand {
|
||||||
pub async fn run(&self) -> anyhow::Result<()> {
|
async fn run(&self) -> anyhow::Result<()> {
|
||||||
use Subcommand as S;
|
use Subcommand as S;
|
||||||
match &self.subcommand {
|
match &self.subcommand {
|
||||||
Some(S::Config(c)) => c.run(self).await,
|
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")
|
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
|
||||||
|
}
|
@ -14,10 +14,11 @@
|
|||||||
|
|
||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
|
use mas_config::DatabaseConfig;
|
||||||
|
use mas_core::storage::register_user;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use super::RootCommand;
|
use super::RootCommand;
|
||||||
use crate::{config::DatabaseConfig, storage::register_user};
|
|
||||||
|
|
||||||
#[derive(Clap, Debug)]
|
#[derive(Clap, Debug)]
|
||||||
pub(super) struct ManageCommand {
|
pub(super) struct ManageCommand {
|
@ -20,6 +20,11 @@ use std::{
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
use hyper::{header, Server};
|
use hyper::{header, Server};
|
||||||
|
use mas_config::RootConfig;
|
||||||
|
use mas_core::{
|
||||||
|
tasks::{self, TaskQueue},
|
||||||
|
templates::Templates,
|
||||||
|
};
|
||||||
use tower::{make::Shared, ServiceBuilder};
|
use tower::{make::Shared, ServiceBuilder};
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
compression::CompressionLayer,
|
compression::CompressionLayer,
|
||||||
@ -29,11 +34,6 @@ use tower_http::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::RootCommand;
|
use super::RootCommand;
|
||||||
use crate::{
|
|
||||||
config::RootConfig,
|
|
||||||
tasks::{self, TaskQueue},
|
|
||||||
templates::Templates,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clap, Debug, Default)]
|
#[derive(Clap, Debug, Default)]
|
||||||
pub(super) struct ServerCommand;
|
pub(super) struct ServerCommand;
|
||||||
@ -52,7 +52,7 @@ impl ServerCommand {
|
|||||||
let templates = Templates::load().context("could not load templates")?;
|
let templates = Templates::load().context("could not load templates")?;
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
let root = crate::handlers::root(&pool, &templates, &config);
|
let root = mas_core::handlers::root(&pool, &templates, &config);
|
||||||
|
|
||||||
let queue = TaskQueue::default();
|
let queue = TaskQueue::default();
|
||||||
queue.recuring(Duration::from_secs(15), tasks::cleanup_expired(&pool));
|
queue.recuring(Duration::from_secs(15), tasks::cleanup_expired(&pool));
|
38
crates/config/Cargo.toml
Normal file
38
crates/config/Cargo.toml
Normal 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"]
|
@ -42,4 +42,8 @@ impl ConfigurationSection<'_> for CookiesConfig {
|
|||||||
secret: rand::random(),
|
secret: rand::random(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test() -> Self {
|
||||||
|
Self { secret: [0xEA; 32] }
|
||||||
|
}
|
||||||
}
|
}
|
@ -52,6 +52,10 @@ impl ConfigurationSection<'_> for CsrfConfig {
|
|||||||
async fn generate() -> anyhow::Result<Self> {
|
async fn generate() -> anyhow::Result<Self> {
|
||||||
Ok(Self::default())
|
Ok(Self::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
@ -19,12 +19,11 @@ use async_trait::async_trait;
|
|||||||
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
|
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::{serde_as, skip_serializing_none};
|
use serde_with::{serde_as, skip_serializing_none};
|
||||||
use sqlx::{
|
use sqlx::postgres::{PgConnectOptions, PgPool, PgPoolOptions};
|
||||||
postgres::{PgConnectOptions, PgPool, PgPoolOptions},
|
|
||||||
ConnectOptions,
|
|
||||||
};
|
|
||||||
use tracing::log::LevelFilter;
|
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
// use sqlx::ConnectOptions
|
||||||
|
// use tracing::log::LevelFilter;
|
||||||
use super::ConfigurationSection;
|
use super::ConfigurationSection;
|
||||||
|
|
||||||
fn default_uri() -> String {
|
fn default_uri() -> String {
|
||||||
@ -102,15 +101,16 @@ pub struct DatabaseConfig {
|
|||||||
impl DatabaseConfig {
|
impl DatabaseConfig {
|
||||||
#[tracing::instrument(err)]
|
#[tracing::instrument(err)]
|
||||||
pub async fn connect(&self) -> anyhow::Result<PgPool> {
|
pub async fn connect(&self) -> anyhow::Result<PgPool> {
|
||||||
let mut options = self
|
let options = self
|
||||||
.uri
|
.uri
|
||||||
.parse::<PgConnectOptions>()
|
.parse::<PgConnectOptions>()
|
||||||
.context("invalid database URL")?
|
.context("invalid database URL")?
|
||||||
.application_name("matrix-authentication-service");
|
.application_name("matrix-authentication-service");
|
||||||
|
|
||||||
options
|
// FIXME
|
||||||
.log_statements(LevelFilter::Debug)
|
// options
|
||||||
.log_slow_statements(LevelFilter::Warn, Duration::from_millis(100));
|
// .log_statements(LevelFilter::Debug)
|
||||||
|
// .log_slow_statements(LevelFilter::Warn, Duration::from_millis(100));
|
||||||
|
|
||||||
PgPoolOptions::new()
|
PgPoolOptions::new()
|
||||||
.max_connections(self.max_connections)
|
.max_connections(self.max_connections)
|
||||||
@ -133,6 +133,10 @@ impl ConfigurationSection<'_> for DatabaseConfig {
|
|||||||
async fn generate() -> anyhow::Result<Self> {
|
async fn generate() -> anyhow::Result<Self> {
|
||||||
Ok(Self::default())
|
Ok(Self::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
@ -45,4 +45,8 @@ impl ConfigurationSection<'_> for HttpConfig {
|
|||||||
async fn generate() -> anyhow::Result<Self> {
|
async fn generate() -> anyhow::Result<Self> {
|
||||||
Ok(Self::default())
|
Ok(Self::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
}
|
}
|
@ -63,4 +63,14 @@ impl ConfigurationSection<'_> for RootConfig {
|
|||||||
csrf: CsrfConfig::generate().await?,
|
csrf: CsrfConfig::generate().await?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test() -> Self {
|
||||||
|
Self {
|
||||||
|
oauth2: OAuth2Config::test(),
|
||||||
|
http: HttpConfig::test(),
|
||||||
|
database: DatabaseConfig::test(),
|
||||||
|
cookies: CookiesConfig::test(),
|
||||||
|
csrf: CsrfConfig::test(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -341,9 +341,49 @@ impl OAuth2Config {
|
|||||||
.join(".well-known/openid-configuration")
|
.join(".well-known/openid-configuration")
|
||||||
.expect("could not build discovery url")
|
.expect("could not build discovery url")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[async_trait]
|
||||||
pub fn test() -> Self {
|
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#"
|
let rsa_key = Key::from_rsa_pem(indoc::indoc! {r#"
|
||||||
-----BEGIN PRIVATE KEY-----
|
-----BEGIN PRIVATE KEY-----
|
||||||
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAymS2RkeIZo7pUeEN
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use figment::Jail;
|
use figment::Jail;
|
@ -66,4 +66,7 @@ pub trait ConfigurationSection<'a>: Sized + Deserialize<'a> + Serialize {
|
|||||||
.merge(Yaml::file(path))
|
.merge(Yaml::file(path))
|
||||||
.extract_inner(Self::path())
|
.extract_inner(Self::path())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate config used in unit tests
|
||||||
|
fn test() -> Self;
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "matrix-authentication-service"
|
name = "mas-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Quentin Gliech <quenting@element.io>"]
|
authors = ["Quentin Gliech <quenting@element.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@ -14,7 +14,6 @@ futures-util = "0.3.17"
|
|||||||
|
|
||||||
# Logging and tracing
|
# Logging and tracing
|
||||||
tracing = "0.1.27"
|
tracing = "0.1.27"
|
||||||
tracing-subscriber = "0.2.22"
|
|
||||||
|
|
||||||
# Error management
|
# Error management
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
@ -22,8 +21,6 @@ anyhow = "1.0.44"
|
|||||||
|
|
||||||
# Web server
|
# Web server
|
||||||
warp = "0.3.1"
|
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"] }
|
hyper = { version = "0.14.12", features = ["full"] }
|
||||||
|
|
||||||
# Template engine
|
# Template engine
|
||||||
@ -40,10 +37,8 @@ serde_json = "1.0.68"
|
|||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
|
|
||||||
# Argument & config parsing
|
# Argument & config parsing
|
||||||
clap = "3.0.0-beta.4"
|
|
||||||
figment = { version = "0.10.6", features = ["env", "yaml", "test"] }
|
figment = { version = "0.10.6", features = ["env", "yaml", "test"] }
|
||||||
schemars = { version = "0.8.3", features = ["url", "chrono"] }
|
schemars = { version = "0.8.3", features = ["url", "chrono"] }
|
||||||
dotenv = "0.15.0"
|
|
||||||
|
|
||||||
# Password hashing
|
# Password hashing
|
||||||
argon2 = { version = "0.3.1", features = ["password-hash"] }
|
argon2 = { version = "0.3.1", features = ["password-hash"] }
|
||||||
@ -70,6 +65,7 @@ headers = "0.3.4"
|
|||||||
cookie = "0.15.1"
|
cookie = "0.15.1"
|
||||||
|
|
||||||
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
||||||
|
mas-config = { path = "../config" }
|
||||||
|
|
||||||
[dependencies.jwt-compact]
|
[dependencies.jwt-compact]
|
||||||
# Waiting on the next release because of the bump of the `rsa` dependency
|
# Waiting on the next release because of the bump of the `rsa` dependency
|
@ -93,7 +93,7 @@ pub struct ErroredForm<FieldType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ErroredForm<T> {
|
impl<T> ErroredForm<T> {
|
||||||
pub fn new() -> Self {
|
#[must_use] pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
form: Vec::new(),
|
form: Vec::new(),
|
||||||
fields: Vec::new(),
|
fields: Vec::new(),
|
@ -28,11 +28,13 @@ pub enum ClientAuthentication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ClientAuthentication {
|
impl ClientAuthentication {
|
||||||
|
#[must_use]
|
||||||
pub fn public(&self) -> bool {
|
pub fn public(&self) -> bool {
|
||||||
matches!(self, &Self::None)
|
matches!(self, &Self::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn with_client_auth<T: DeserializeOwned + Send + 'static>(
|
pub fn with_client_auth<T: DeserializeOwned + Send + 'static>(
|
||||||
oauth2_config: &OAuth2Config,
|
oauth2_config: &OAuth2Config,
|
||||||
) -> impl Filter<Extract = (ClientAuthentication, OAuth2ClientConfig, T), Error = Rejection>
|
) -> impl Filter<Extract = (ClientAuthentication, OAuth2ClientConfig, T), Error = Rejection>
|
||||||
@ -132,6 +134,8 @@ struct ClientAuthForm<T> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use mas_config::ConfigurationSection;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn oauth2_config() -> OAuth2Config {
|
fn oauth2_config() -> OAuth2Config {
|
@ -69,7 +69,7 @@ impl EncryptedCookie {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maybe_encrypted<T>(
|
#[must_use] pub fn maybe_encrypted<T>(
|
||||||
options: &CookiesConfig,
|
options: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (Option<T>,), Error = Infallible> + Clone + Send + Sync + 'static
|
) -> impl Filter<Extract = (Option<T>,), Error = Infallible> + Clone + Send + Sync + 'static
|
||||||
where
|
where
|
||||||
@ -83,7 +83,7 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encrypted<T>(
|
#[must_use] pub fn encrypted<T>(
|
||||||
options: &CookiesConfig,
|
options: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone + Send + Sync + 'static
|
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone + Send + Sync + 'static
|
||||||
where
|
where
|
||||||
@ -97,7 +97,7 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_cookie_saver(
|
#[must_use] pub fn with_cookie_saver(
|
||||||
options: &CookiesConfig,
|
options: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (EncryptedCookieSaver,), Error = Infallible> + Clone + Send + Sync + 'static
|
) -> impl Filter<Extract = (EncryptedCookieSaver,), Error = Infallible> + Clone + Send + Sync + 'static
|
||||||
{
|
{
|
@ -66,7 +66,7 @@ impl CsrfToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value to include in HTML forms
|
/// 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[..])
|
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,
|
cookies_config: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (CsrfToken,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (CsrfToken,), Error = Rejection> + Clone + Send + Sync + 'static {
|
||||||
super::cookies::encrypted(cookies_config).and_then(move |token: CsrfToken| async move {
|
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,
|
cookies_config: &CookiesConfig,
|
||||||
csrf_config: &CsrfConfig,
|
csrf_config: &CsrfConfig,
|
||||||
) -> impl Filter<Extract = (CsrfToken,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> 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,
|
cookies_config: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone + Send + Sync + 'static
|
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone + Send + Sync + 'static
|
||||||
where
|
where
|
@ -33,14 +33,14 @@ use crate::{
|
|||||||
templates::Templates,
|
templates::Templates,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn with_templates(
|
#[must_use] pub fn with_templates(
|
||||||
templates: &Templates,
|
templates: &Templates,
|
||||||
) -> impl Filter<Extract = (Templates,), Error = Infallible> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (Templates,), Error = Infallible> + Clone + Send + Sync + 'static {
|
||||||
let templates = templates.clone();
|
let templates = templates.clone();
|
||||||
warp::any().map(move || templates.clone())
|
warp::any().map(move || templates.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_keys(
|
#[must_use] pub fn with_keys(
|
||||||
oauth2_config: &OAuth2Config,
|
oauth2_config: &OAuth2Config,
|
||||||
) -> impl Filter<Extract = (KeySet,), Error = Infallible> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (KeySet,), Error = Infallible> + Clone + Send + Sync + 'static {
|
||||||
let keyset = oauth2_config.keys.clone();
|
let keyset = oauth2_config.keys.clone();
|
@ -32,7 +32,7 @@ pub struct SessionCookie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SessionCookie {
|
impl SessionCookie {
|
||||||
pub fn from_session_info(info: &SessionInfo) -> Self {
|
#[must_use] pub fn from_session_info(info: &SessionInfo) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: info.key(),
|
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,
|
pool: &PgPool,
|
||||||
cookies_config: &CookiesConfig,
|
cookies_config: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (Option<SessionInfo>,), Error = Rejection> + Clone + Send + Sync + 'static
|
) -> 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,
|
pool: &PgPool,
|
||||||
cookies_config: &CookiesConfig,
|
cookies_config: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (SessionInfo,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (SessionInfo,), Error = Rejection> + Clone + Send + Sync + 'static {
|
@ -25,7 +25,7 @@ mod views;
|
|||||||
|
|
||||||
use self::{health::filter as health, oauth2::filter as oauth2, views::filter as 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,
|
pool: &PgPool,
|
||||||
templates: &Templates,
|
templates: &Templates,
|
||||||
config: &RootConfig,
|
config: &RootConfig,
|
31
crates/core/src/lib.rs
Normal file
31
crates/core/src/lib.rs
Normal 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;
|
@ -67,7 +67,7 @@ pub struct OAuth2AccessTokenLookup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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))
|
self.created_at + Duration::seconds(i64::from(self.expires_after))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
self.max_age
|
||||||
.map(|d| Duration::seconds(i64::from(d)))
|
.map(|d| Duration::seconds(i64::from(d)))
|
||||||
.map(|d| self.created_at - d)
|
.map(|d| self.created_at - d)
|
@ -44,7 +44,7 @@ pub struct SessionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SessionInfo {
|
impl SessionInfo {
|
||||||
pub fn key(&self) -> i64 {
|
#[must_use] pub fn key(&self) -> i64 {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
|
|
@ -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())
|
CleanupExpired(pool.clone())
|
||||||
}
|
}
|
@ -212,7 +212,7 @@ pub struct IndexContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IndexContext {
|
impl IndexContext {
|
||||||
pub fn new(discovery_url: Url) -> Self {
|
#[must_use] pub fn new(discovery_url: Url) -> Self {
|
||||||
Self { discovery_url }
|
Self { discovery_url }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,7 +230,7 @@ pub struct LoginContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 }
|
Self { form }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,22 +267,22 @@ pub struct ErrorContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorContext {
|
impl ErrorContext {
|
||||||
pub fn new() -> Self {
|
#[must_use] pub fn new() -> Self {
|
||||||
Self::default()
|
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.code = Some(code);
|
||||||
self
|
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.description = Some(description);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[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.details = Some(details);
|
||||||
self
|
self
|
||||||
}
|
}
|
@ -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
|
|
||||||
}
|
|
Reference in New Issue
Block a user