autter-core 2.0.2

Autter authentication service for Shrimpcamp
Documentation
use oiseau::config::{Configuration, DatabaseConfig};
use pathbufd::PathBufD;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Config {
    /// The name of the site. Shown in the UI.
    #[serde(default = "default_name")]
    pub name: String,
    /// Database configuration.
    #[serde(default = "default_database")]
    pub database: DatabaseConfig,
    /// Service hosts config.
    #[serde(default)]
    pub service_hosts: ServiceHostsConfig,
    /// The public URL of this service.
    #[serde(default)]
    pub host: String,
    /// Usernames which cannot be used by any user.
    #[serde(default = "default_banned_usernames")]
    pub banned_usernames: Vec<String>,
    /// Security settings.
    #[serde(default)]
    pub security: SecurityConfig,
    /// Directories config.
    #[serde(default)]
    pub dirs: DirectoriesConfig,
    /// Stripe payments config.
    pub stripe: StripeConfig,
    #[serde(default)]
    pub turnstile: TurnstileConfig,
}

fn default_banned_usernames() -> Vec<String> {
    vec!["admin".to_string(), "settings".to_string()]
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct TurnstileConfig {
    pub site_key: String,
    pub secret_key: String,
}

impl Default for TurnstileConfig {
    fn default() -> Self {
        Self {
            site_key: "1x00000000000000000000AA".to_string(), // always passing, visible
            secret_key: "1x0000000000000000000000000000000AA".to_string(), // always passing
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct SecurityConfig {
    #[serde(default)]
    pub accepting_purchases: bool,
    #[serde(default)]
    pub registration_enabled: bool,
    /// Real IP header (for reverse proxy).
    #[serde(default = "default_real_ip_header")]
    pub real_ip_header: String,
    /// The hostnames of approved login redirect destinations.
    #[serde(default)]
    pub approved_login_redirects: Vec<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ServiceHostsConfig {
    #[serde(default = "default_shrimpcamp")]
    pub shrimpcamp: String,
    #[serde(default = "default_tetratto")]
    pub tetratto: String,
    #[serde(default = "default_buckets")]
    pub buckets: String,
    #[serde(default = "default_askall")]
    pub askall: String,
    #[serde(default = "default_issuestack")]
    pub issuestack: String,
    #[serde(default = "default_fluffle")]
    pub fluffle: String,
    #[serde(default = "default_overkit")]
    pub overkit: String,
    #[serde(default = "default_juicespace")]
    pub juicespace: String,
}

fn default_shrimpcamp() -> String {
    "https://about.shrimpcamp.com".to_string()
}

fn default_tetratto() -> String {
    "https://tetratto.com".to_string()
}

fn default_askall() -> String {
    "https://askall.cc".to_string()
}

fn default_issuestack() -> String {
    "https://stack.shrimpcamp.com".to_string()
}

fn default_fluffle() -> String {
    "https://fluffle.cc".to_string()
}

fn default_buckets() -> String {
    "http://localhost:8020".to_string()
}

fn default_overkit() -> String {
    "http://localhost:8026".to_string()
}

fn default_juicespace() -> String {
    "https://juicespace.org".to_string()
}

impl Default for ServiceHostsConfig {
    fn default() -> Self {
        Self {
            shrimpcamp: default_shrimpcamp(),
            tetratto: default_tetratto(),
            buckets: default_buckets(),
            askall: default_askall(),
            issuestack: default_issuestack(),
            fluffle: default_fluffle(),
            overkit: default_overkit(),
            juicespace: default_juicespace(),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DirectoriesConfig {
    #[serde(default = "default_media")]
    pub media: String,
}

fn default_media() -> String {
    "media".to_string()
}

impl Default for DirectoriesConfig {
    fn default() -> Self {
        Self {
            media: default_media(),
        }
    }
}

fn default_name() -> String {
    "Autter".to_string()
}

fn default_real_ip_header() -> String {
    "CF-Connecting-IP".to_string()
}

fn default_database() -> DatabaseConfig {
    DatabaseConfig::default()
}

/// Configuration for Stripe integration.
///
/// User IDs are sent to Stripe through the payment link.
/// <https://docs.stripe.com/payment-links/url-parameters#streamline-reconciliation-with-a-url-parameter>
///
/// # Testing
///
/// - Run `stripe login` using the Stripe CLI
/// - Run `stripe listen --forward-to localhost:4118/api/v1/service_hooks/stripe`
/// - Use testing card numbers: <https://docs.stripe.com/testing?testing-method=card-numbers#visa>
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct StripeConfig {
    /// Your Stripe API secret.
    pub secret: String,
    /// To apply benefits to user accounts, you should then go into the Stripe developer
    /// "workbench" and create a new webhook. The webhook needs the scopes:
    /// `invoice.payment_succeeded`, `customer.subscription.deleted`, `checkout.session.completed`, `charge.succeeded`.
    ///
    /// The webhook's destination address should be `{your server origin}/api/v1/service_hooks/stripe`.
    ///
    /// The signing secret can be found on the right after you have created the webhook.
    pub webhook_signing_secret: String,
    /// The URL of your customer billing portal.
    ///
    /// <https://docs.stripe.com/no-code/customer-portal>
    pub billing_portal_url: String,
    /// The text representation of prices. (like `$4 USD`)
    pub price_texts: StripePriceTexts,
    /// Product IDs from the Stripe dashboard.
    ///
    /// These are checked when we receive a webhook to ensure we provide the correct product.
    pub product_ids: StripeProductIds,
    /// The IDs of individual prices for products which require us to generate sessions ourselves.
    pub price_ids: StripePriceIds,
}

#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct StripePriceTexts {
    pub organization: String,
    pub user_reg: String,
    pub seedling: String,
    pub user_verification: String,
}

#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct StripeProductIds {
    pub organization: String,
    pub user_reg: String,
    pub seedling: String,
    pub user_verification: String,
}

#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct StripePriceIds {
    pub organization: String,
    pub user_reg: String,
    pub seedling: String,
    pub user_verification: String,
}

impl Configuration for Config {
    fn db_config(&self) -> DatabaseConfig {
        self.database.to_owned()
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            name: default_name(),
            database: default_database(),
            service_hosts: ServiceHostsConfig::default(),
            host: String::new(),
            banned_usernames: default_banned_usernames(),
            security: SecurityConfig::default(),
            dirs: DirectoriesConfig::default(),
            stripe: StripeConfig::default(),
            turnstile: TurnstileConfig::default(),
        }
    }
}

impl Config {
    /// Read the configuration file.
    pub fn read() -> Self {
        toml::from_str(
            &match std::fs::read_to_string(PathBufD::current().join("app.toml")) {
                Ok(x) => x,
                Err(_) => {
                    let x = Config::default();

                    std::fs::write(
                        PathBufD::current().join("app.toml"),
                        toml::to_string_pretty(&x).expect("failed to serialize config"),
                    )
                    .expect("failed to write config");

                    return x;
                }
            },
        )
        .expect("failed to deserialize config")
    }
}