/* factoriauth - An unofficial authentication server for Factorio Copyright (C) 2024 lambda@xiretza.xyz This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ #![warn( clippy::pedantic, clippy::as_conversions, clippy::unwrap_used, // allow case by case, add comment explaining why panic can't happen clippy::expect_used // allow case by case, expect message should be self-explanatory )] #![forbid(unsafe_code)] mod auth; mod config; mod db; mod secrets; mod server; use std::{env, path::PathBuf, sync::Arc}; use auth::{ AuthenticationBackend, ServerPadlockGenerator, UserAuthenticator, UserServerKeyGenerator, }; use clap::{Parser, Subcommand}; use color_eyre::{eyre::Context, Result}; use config::Config; use db::{Database, SqliteDatabase}; use tokio::sync::Mutex; use tracing::{event, instrument, level_filters::LevelFilter, Level}; use tracing_error::ErrorLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; #[instrument] fn init() -> Result<()> { const FILTER_ENV_VAR: &str = EnvFilter::DEFAULT_ENV; color_eyre::install()?; let mut filter_error = None; let filter_layer = EnvFilter::builder() .with_env_var(FILTER_ENV_VAR) .try_from_env() .unwrap_or_else(|e| { // sure would be nice if the error type was useful if env::var_os(FILTER_ENV_VAR).is_some() { filter_error = Some(e); } EnvFilter::builder() .with_default_directive(LevelFilter::INFO.into()) .parse_lossy("") }); let fmt_layer = tracing_subscriber::fmt::layer().with_target(true); tracing_subscriber::registry() .with(filter_layer) .with(fmt_layer) .with(ErrorLayer::default()) .init(); if let Some(e) = filter_error { event!( Level::WARN, error = %e, r#"Tracing filter env variable `{FILTER_ENV_VAR}` contained invalid data, falling back to "info""# ); } Ok(()) } #[instrument] async fn load_config(path: &str) -> Result { event!(Level::DEBUG, "Loading config"); let content = tokio::fs::read_to_string(path).await?; Ok(toml::from_str(&content)?) } #[derive(Debug, Clone, Subcommand)] enum Command { /// Run factoriauth Run, } #[derive(Debug, Clone, Parser)] struct Args { /// Path to the configuration file. #[arg(short, long, default_value = "config.toml")] config: String, #[command(subcommand)] command: Command, } #[tokio::main] #[instrument] async fn main() -> Result<()> { let args = Args::parse(); match args.command { Command::Run => {} } init().context("Failed to initialize tracing")?; let config = load_config(&args.config).await.with_context(|| { if let Ok(path) = PathBuf::from(&args.config).canonicalize() { format!("Failed to load config from {path:?}") } else { format!("Failed to load config from invalid path {}", &args.config) } })?; let database: Arc>> = Arc::new(Mutex::new(Box::new( SqliteDatabase::open(&config.database.connection_string) .await .context("Failed to open database")?, ))); let mut auth_backends = vec![]; for (i, c) in config.auth_backends.into_iter().enumerate() { auth_backends.push( AuthenticationBackend::new(c) .await .with_context(|| format!("Failed to initialize backend {i}"))?, ); } let user_authenticator = Arc::new(UserAuthenticator::new(Arc::clone(&database), auth_backends)); let padlock_generator = Arc::new(ServerPadlockGenerator::new(config.padlock, database)?); let user_server_key_generator = Arc::new(UserServerKeyGenerator::new( Arc::clone(&user_authenticator), Arc::clone(&padlock_generator), )); tokio::spawn(server::run( config.listen, user_authenticator, padlock_generator, user_server_key_generator, )) .await??; Ok(()) }