extern crate core; use std::error::Error; use std::fs; use std::path::Path; use dotenvy::dotenv; use log::{error, info}; use rocket::{catchers, Request, Response, routes}; use rocket::fairing::{Fairing, Info, Kind}; use rocket::http::Header; use sqlx::migrate::Migrator; use sqlx::postgres::PgPoolOptions; use crate::config::TFConfig; pub mod format; pub mod util; pub mod db; pub mod config; pub mod tokens; pub mod routes; pub mod auth; static MIGRATOR: Migrator = sqlx::migrate!(); pub struct CORS; #[rocket::async_trait] impl Fairing for CORS { fn info(&self) -> Info { Info { name: "Add CORS headers to responses", kind: Kind::Response } } async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { response.set_header(Header::new("Access-Control-Allow-Origin", "*")); response.set_header(Header::new("Access-Control-Allow-Methods", "POST, GET, PATCH, OPTIONS")); response.set_header(Header::new("Access-Control-Allow-Headers", "*")); response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); } } #[rocket::main] async fn main() -> Result<(), Box> { let _ = rocket::build(); info!("[tfapi] loading config"); let _ = dotenv(); if std::env::var("CONFIG_FILE").is_err() && !Path::new("config.toml").exists() { error!("[tfapi] fatal: the environment variable CONFIG_FILE is not set"); error!("[tfapi] help: try creating a .env file that sets it"); error!("[tfapi] help: or, create a file config.toml with your config, as it is loaded automatically"); std::process::exit(1); } let config_file = if Path::new("config.toml").exists() { "config.toml".to_string() } else { std::env::var("CONFIG_FILE").unwrap() }; let config_data = match fs::read_to_string(&config_file) { Ok(d) => d, Err(e) => { error!("[tfapi] fatal: unable to read config from {}", config_file); error!("[tfapi] fatal: {}", e); std::process::exit(1); } }; let config: TFConfig = match toml::from_str(&config_data) { Ok(c) => c, Err(e) => { error!("[tfapi] fatal: unable to parse config from {}", config_file); error!("[tfapi] fatal: {}", e); std::process::exit(1); } }; info!("[tfapi] connecting to database pool"); let pool = match PgPoolOptions::new().max_connections(5).connect(&config.db_url).await { Ok(p) => p, Err(e) => { error!("[tfapi] fatal: unable to connect to database pool"); error!("[tfapi] fatal: {}", e); std::process::exit(1); } }; info!("[tfapi] running database migrations"); MIGRATOR.run(&pool).await?; info!("[tfapi] building rocket config"); let figment = rocket::Config::figment().merge(("port", config.listen_port)); let _ = rocket::custom(figment) .mount("/", routes![ crate::routes::v1::auth::magic_link::magiclink_request, crate::routes::v1::auth::magic_link::options, crate::routes::v1::signup::signup_request, crate::routes::v1::signup::options, crate::routes::v1::auth::verify_magic_link::verify_magic_link, crate::routes::v1::auth::verify_magic_link::options, crate::routes::v1::totp_authenticators::totp_authenticators_request, crate::routes::v1::totp_authenticators::options, crate::routes::v1::verify_totp_authenticator::verify_totp_authenticator_request, crate::routes::v1::verify_totp_authenticator::options, crate::routes::v1::auth::totp::totp_request, crate::routes::v1::auth::totp::options, crate::routes::v1::auth::check_session::check_session, crate::routes::v1::auth::check_session::check_session_auth, crate::routes::v1::auth::check_session::options, crate::routes::v1::auth::check_session::options_auth, crate::routes::v2::whoami::whoami_request, crate::routes::v2::whoami::options ]) .register("/", catchers![ crate::routes::handler_400, crate::routes::handler_401, crate::routes::handler_403, crate::routes::handler_404, crate::routes::handler_422, crate::routes::handler_500, crate::routes::handler_501, crate::routes::handler_502, crate::routes::handler_503, crate::routes::handler_504, crate::routes::handler_505, ]) .attach(CORS) .manage(pool) .manage(config) .launch().await?; Ok(()) }