Improve environment variable handling and reading (#5389)
* wip: better env var reading * move most env vars to env.rs * migrate more env vars * more migration * more migrations * More migration * 🦀 dotenvy is gone (almost) * 🦀 dotenvy is gone 🦀 * Fix mural source account env var handling * Remove defaults from admin key vars * dummy commit to update github pr * fix ci
This commit is contained in:
@@ -8,6 +8,8 @@ SITE_URL=http://localhost:3000
|
||||
# This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH
|
||||
CDN_URL=file:///tmp/modrinth
|
||||
LABRINTH_ADMIN_KEY=feedbeef
|
||||
LABRINTH_MEDAL_KEY=
|
||||
LABRINTH_EXTERNAL_NOTIFICATION_KEY=beeffeed
|
||||
RATE_LIMIT_IGNORE_KEY=feedbeef
|
||||
|
||||
DATABASE_URL=postgresql://labrinth:labrinth@labrinth-postgres/labrinth
|
||||
@@ -152,6 +154,6 @@ ARCHON_URL=none
|
||||
MURALPAY_API_URL=https://api.muralpay.com
|
||||
MURALPAY_API_KEY=none
|
||||
MURALPAY_TRANSFER_API_KEY=none
|
||||
MURALPAY_SOURCE_ACCOUNT_ID=none
|
||||
MURALPAY_SOURCE_ACCOUNT_ID=00000000-0000-0000-0000-000000000000
|
||||
|
||||
DEFAULT_AFFILIATE_REVENUE_SPLIT=0.1
|
||||
|
||||
@@ -8,6 +8,7 @@ SITE_URL=http://localhost:3000
|
||||
# This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH
|
||||
CDN_URL=file:///tmp/modrinth
|
||||
LABRINTH_ADMIN_KEY=feedbeef
|
||||
LABRINTH_MEDAL_KEY=
|
||||
LABRINTH_EXTERNAL_NOTIFICATION_KEY=beeffeed
|
||||
RATE_LIMIT_IGNORE_KEY=feedbeef
|
||||
|
||||
@@ -163,6 +164,6 @@ ARCHON_URL=none
|
||||
MURALPAY_API_URL=https://api-staging.muralpay.com
|
||||
MURALPAY_API_KEY=none
|
||||
MURALPAY_TRANSFER_API_KEY=none
|
||||
MURALPAY_SOURCE_ACCOUNT_ID=none
|
||||
MURALPAY_SOURCE_ACCOUNT_ID=00000000-0000-0000-0000-000000000000
|
||||
|
||||
DEFAULT_AFFILIATE_REVENUE_SPLIT=0.1
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::AuthProvider;
|
||||
use crate::auth::AuthenticationError;
|
||||
use crate::database::models::{DBUser, user_item};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::users::User;
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -146,7 +147,7 @@ where
|
||||
user_item::DBUser::get_id(session.user_id, executor, redis)
|
||||
.await?;
|
||||
|
||||
let rate_limit_ignore = dotenvy::var("RATE_LIMIT_IGNORE_KEY")?;
|
||||
let rate_limit_ignore = &ENV.RATE_LIMIT_IGNORE_KEY;
|
||||
if req
|
||||
.headers()
|
||||
.get("x-ratelimit-key")
|
||||
|
||||
@@ -5,9 +5,10 @@ mod fetch;
|
||||
|
||||
pub use fetch::*;
|
||||
|
||||
use crate::env::ENV;
|
||||
|
||||
pub async fn init_client() -> clickhouse::error::Result<clickhouse::Client> {
|
||||
init_client_with_database(&dotenvy::var("CLICKHOUSE_DATABASE").unwrap())
|
||||
.await
|
||||
init_client_with_database(&ENV.CLICKHOUSE_DATABASE).await
|
||||
}
|
||||
|
||||
pub async fn init_client_with_database(
|
||||
@@ -24,9 +25,9 @@ pub async fn init_client_with_database(
|
||||
.build(https_connector);
|
||||
|
||||
clickhouse::Client::with_http_client(hyper_client)
|
||||
.with_url(dotenvy::var("CLICKHOUSE_URL").unwrap())
|
||||
.with_user(dotenvy::var("CLICKHOUSE_USER").unwrap())
|
||||
.with_password(dotenvy::var("CLICKHOUSE_PASSWORD").unwrap())
|
||||
.with_url(&ENV.CLICKHOUSE_URL)
|
||||
.with_user(&ENV.CLICKHOUSE_USER)
|
||||
.with_password(&ENV.CLICKHOUSE_PASSWORD)
|
||||
.with_validation(false)
|
||||
};
|
||||
|
||||
@@ -35,8 +36,7 @@ pub async fn init_client_with_database(
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
let clickhouse_replicated =
|
||||
dotenvy::var("CLICKHOUSE_REPLICATED").unwrap() == "true";
|
||||
let clickhouse_replicated = ENV.CLICKHOUSE_REPLICATED;
|
||||
let cluster_line = if clickhouse_replicated {
|
||||
"ON cluster '{cluster}'"
|
||||
} else {
|
||||
|
||||
@@ -13,6 +13,8 @@ pub type PgTransaction<'c> = sqlx_tracing::Transaction<'c, Postgres>;
|
||||
pub use sqlx_tracing::Acquire;
|
||||
pub use sqlx_tracing::Executor;
|
||||
|
||||
use crate::env::ENV;
|
||||
|
||||
// pub type PgPool = sqlx::PgPool;
|
||||
// pub type PgTransaction<'c> = sqlx::Transaction<'c, Postgres>;
|
||||
// pub use sqlx::Acquire;
|
||||
@@ -50,57 +52,27 @@ impl DerefMut for ReadOnlyPgPool {
|
||||
|
||||
pub async fn connect_all() -> Result<(PgPool, ReadOnlyPgPool), sqlx::Error> {
|
||||
info!("Initializing database connection");
|
||||
let database_url =
|
||||
dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` not in .env");
|
||||
let database_url = &ENV.DATABASE_URL;
|
||||
|
||||
let acquire_timeout =
|
||||
dotenvy::var("DATABASE_ACQUIRE_TIMEOUT_MS")
|
||||
.ok()
|
||||
.map_or_else(
|
||||
|| Duration::from_millis(30000),
|
||||
|x| {
|
||||
Duration::from_millis(x.parse::<u64>().expect(
|
||||
"DATABASE_ACQUIRE_TIMEOUT_MS must be a valid u64",
|
||||
))
|
||||
},
|
||||
);
|
||||
Duration::from_millis(ENV.DATABASE_ACQUIRE_TIMEOUT_MS);
|
||||
|
||||
let pool = PgPoolOptions::new()
|
||||
.acquire_timeout(acquire_timeout)
|
||||
.min_connections(
|
||||
dotenvy::var("DATABASE_MIN_CONNECTIONS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(0),
|
||||
)
|
||||
.max_connections(
|
||||
dotenvy::var("DATABASE_MAX_CONNECTIONS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(16),
|
||||
)
|
||||
.min_connections(ENV.DATABASE_MIN_CONNECTIONS)
|
||||
.max_connections(ENV.DATABASE_MAX_CONNECTIONS)
|
||||
.max_lifetime(Some(Duration::from_secs(60 * 60)))
|
||||
.connect(&database_url)
|
||||
.connect(database_url)
|
||||
.await?;
|
||||
let pool = PgPool::from(pool);
|
||||
|
||||
if let Ok(url) = dotenvy::var("READONLY_DATABASE_URL") {
|
||||
if !ENV.READONLY_DATABASE_URL.is_empty() {
|
||||
let ro_pool = PgPoolOptions::new()
|
||||
.acquire_timeout(acquire_timeout)
|
||||
.min_connections(
|
||||
dotenvy::var("READONLY_DATABASE_MIN_CONNECTIONS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(0),
|
||||
)
|
||||
.max_connections(
|
||||
dotenvy::var("READONLY_DATABASE_MAX_CONNECTIONS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(1),
|
||||
)
|
||||
.min_connections(ENV.READONLY_DATABASE_MIN_CONNECTIONS)
|
||||
.max_connections(ENV.READONLY_DATABASE_MAX_CONNECTIONS)
|
||||
.max_lifetime(Some(Duration::from_secs(60 * 60)))
|
||||
.connect(&url)
|
||||
.connect(&ENV.READONLY_DATABASE_URL)
|
||||
.await?;
|
||||
let ro_pool = PgPool::from(ro_pool);
|
||||
|
||||
@@ -112,8 +84,7 @@ pub async fn connect_all() -> Result<(PgPool, ReadOnlyPgPool), sqlx::Error> {
|
||||
}
|
||||
|
||||
pub async fn check_for_migrations() -> eyre::Result<()> {
|
||||
let uri =
|
||||
dotenvy::var("DATABASE_URL").wrap_err("`DATABASE_URL` not in .env")?;
|
||||
let uri = &ENV.DATABASE_URL;
|
||||
let uri = uri.as_str();
|
||||
if !Postgres::database_exists(uri)
|
||||
.await
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::env::ENV;
|
||||
|
||||
use super::models::DatabaseError;
|
||||
use ariadne::ids::base62_impl::{parse_base62, to_base62};
|
||||
use chrono::{TimeZone, Utc};
|
||||
@@ -42,44 +44,26 @@ impl RedisPool {
|
||||
// testing pool uses a hashmap to mimic redis behaviour for very small data sizes (ie: tests)
|
||||
// PANICS: production pool will panic if redis url is not set
|
||||
pub fn new(meta_namespace: impl Into<Arc<str>>) -> Self {
|
||||
let wait_timeout =
|
||||
dotenvy::var("REDIS_WAIT_TIMEOUT_MS").ok().map_or_else(
|
||||
|| Duration::from_millis(15000),
|
||||
|x| {
|
||||
Duration::from_millis(
|
||||
x.parse::<u64>().expect(
|
||||
"REDIS_WAIT_TIMEOUT_MS must be a valid u64",
|
||||
),
|
||||
)
|
||||
},
|
||||
);
|
||||
let wait_timeout = Duration::from_millis(ENV.REDIS_WAIT_TIMEOUT_MS);
|
||||
|
||||
let url = dotenvy::var("REDIS_URL").expect("Redis URL not set");
|
||||
let url = &ENV.REDIS_URL;
|
||||
let pool = Config::from_url(url.clone())
|
||||
.builder()
|
||||
.expect("Error building Redis pool")
|
||||
.max_size(
|
||||
dotenvy::var("REDIS_MAX_CONNECTIONS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse().ok())
|
||||
.unwrap_or(10000),
|
||||
)
|
||||
.max_size(ENV.REDIS_MAX_CONNECTIONS as usize)
|
||||
.wait_timeout(Some(wait_timeout))
|
||||
.runtime(Runtime::Tokio1)
|
||||
.build()
|
||||
.expect("Redis connection failed");
|
||||
|
||||
let pool = RedisPool {
|
||||
url,
|
||||
url: url.clone(),
|
||||
pool,
|
||||
cache_list: Arc::new(DashMap::with_capacity(2048)),
|
||||
meta_namespace: meta_namespace.into(),
|
||||
};
|
||||
|
||||
let redis_min_connections = dotenvy::var("REDIS_MIN_CONNECTIONS")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<usize>().ok())
|
||||
.unwrap_or(0);
|
||||
let redis_min_connections = ENV.REDIS_MIN_CONNECTIONS;
|
||||
let spawn_min_connections = (0..redis_min_connections)
|
||||
.map(|_| {
|
||||
let pool = pool.clone();
|
||||
|
||||
280
apps/labrinth/src/env.rs
Normal file
280
apps/labrinth/src/env.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
use std::{any::type_name, convert::Infallible, str::FromStr, sync::LazyLock};
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use eyre::{Context, eyre};
|
||||
use rust_decimal::Decimal;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
macro_rules! vars {
|
||||
(
|
||||
$(
|
||||
$field:ident: $ty:ty $(= $default:expr)?;
|
||||
)*
|
||||
) => {
|
||||
#[derive(Debug)]
|
||||
#[allow(
|
||||
non_snake_case,
|
||||
reason = "environment variables are UPPER_SNAKE_CASE",
|
||||
)]
|
||||
pub struct EnvVars {
|
||||
$(
|
||||
pub $field: $ty,
|
||||
)*
|
||||
}
|
||||
|
||||
impl EnvVars {
|
||||
pub fn from_env() -> eyre::Result<Self> {
|
||||
let mut err = eyre!("failed to read environment variables");
|
||||
|
||||
$(
|
||||
#[expect(
|
||||
non_snake_case,
|
||||
reason = "environment variables are UPPER_SNAKE_CASE",
|
||||
)]
|
||||
#[allow(
|
||||
unused_assignments,
|
||||
unused_mut,
|
||||
reason = "`default` is not used if there is no default",
|
||||
)]
|
||||
let $field: Option<$ty> = {
|
||||
let mut default = None::<$ty>;
|
||||
$( default = Some({ $default }.into()); )?
|
||||
|
||||
match parse_value::<$ty>(stringify!($field), default) {
|
||||
Ok(value) => Some(value),
|
||||
Err(source) => {
|
||||
err = err.wrap_err(eyre!("{source:#}"));
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
)*
|
||||
|
||||
Ok(EnvVars {
|
||||
$(
|
||||
$field: match $field {
|
||||
Some(value) => value,
|
||||
None => return Err(err),
|
||||
},
|
||||
)*
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub static ENV: LazyLock<EnvVars> = LazyLock::new(|| {
|
||||
EnvVars::from_env().unwrap_or_else(|err| panic!("{err:?}"))
|
||||
});
|
||||
|
||||
fn parse_value<T>(key: &str, default: Option<T>) -> eyre::Result<T>
|
||||
where
|
||||
T: FromStr,
|
||||
T::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
match (dotenvy::var(key), default) {
|
||||
(Ok(value), _) => value.parse::<T>().wrap_err_with(|| {
|
||||
eyre!("`{key}` is not a valid `{}`", type_name::<T>())
|
||||
}),
|
||||
(Err(_), Some(default)) => Ok(default),
|
||||
(Err(_), None) => Err(eyre!("`{key}` missing")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() -> eyre::Result<()> {
|
||||
EnvVars::from_env()?;
|
||||
LazyLock::force(&ENV);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Deref, DerefMut,
|
||||
)]
|
||||
pub struct Json<T: DeserializeOwned>(pub T);
|
||||
|
||||
impl<T: DeserializeOwned> FromStr for Json<T> {
|
||||
type Err = serde_json::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_str(s).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deref, DerefMut,
|
||||
)]
|
||||
pub struct StringCsv(pub Vec<String>);
|
||||
|
||||
impl FromStr for StringCsv {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let v = s
|
||||
.split(',')
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Self(v))
|
||||
}
|
||||
}
|
||||
|
||||
vars! {
|
||||
SENTRY_ENVIRONMENT: String;
|
||||
SENTRY_TRACES_SAMPLE_RATE: f32;
|
||||
SITE_URL: String;
|
||||
CDN_URL: String;
|
||||
LABRINTH_ADMIN_KEY: String;
|
||||
LABRINTH_MEDAL_KEY: String;
|
||||
LABRINTH_EXTERNAL_NOTIFICATION_KEY: String;
|
||||
RATE_LIMIT_IGNORE_KEY: String;
|
||||
DATABASE_URL: String;
|
||||
MEILISEARCH_READ_ADDR: String;
|
||||
MEILISEARCH_WRITE_ADDRS: StringCsv;
|
||||
MEILISEARCH_KEY: String;
|
||||
REDIS_URL: String;
|
||||
BIND_ADDR: String;
|
||||
SELF_ADDR: String;
|
||||
|
||||
LOCAL_INDEX_INTERVAL: u64;
|
||||
VERSION_INDEX_INTERVAL: u64;
|
||||
|
||||
WHITELISTED_MODPACK_DOMAINS: Json<Vec<String>>;
|
||||
ALLOWED_CALLBACK_URLS: Json<Vec<String>>;
|
||||
ANALYTICS_ALLOWED_ORIGINS: Json<Vec<String>>;
|
||||
|
||||
// storage
|
||||
STORAGE_BACKEND: crate::file_hosting::FileHostKind;
|
||||
|
||||
// s3
|
||||
S3_PUBLIC_BUCKET_NAME: String = "";
|
||||
S3_PUBLIC_USES_PATH_STYLE_BUCKET: bool = false;
|
||||
S3_PUBLIC_REGION: String = "";
|
||||
S3_PUBLIC_URL: String = "";
|
||||
S3_PUBLIC_ACCESS_TOKEN: String = "";
|
||||
S3_PUBLIC_SECRET: String = "";
|
||||
|
||||
S3_PRIVATE_BUCKET_NAME: String = "";
|
||||
S3_PRIVATE_USES_PATH_STYLE_BUCKET: bool = false;
|
||||
S3_PRIVATE_REGION: String = "";
|
||||
S3_PRIVATE_URL: String = "";
|
||||
S3_PRIVATE_ACCESS_TOKEN: String = "";
|
||||
S3_PRIVATE_SECRET: String = "";
|
||||
|
||||
// local
|
||||
MOCK_FILE_PATH: String = "";
|
||||
|
||||
GITHUB_CLIENT_ID: String;
|
||||
GITHUB_CLIENT_SECRET: String;
|
||||
GITLAB_CLIENT_ID: String;
|
||||
GITLAB_CLIENT_SECRET: String;
|
||||
DISCORD_CLIENT_ID: String;
|
||||
DISCORD_CLIENT_SECRET: String;
|
||||
MICROSOFT_CLIENT_ID: String;
|
||||
MICROSOFT_CLIENT_SECRET: String;
|
||||
GOOGLE_CLIENT_ID: String;
|
||||
GOOGLE_CLIENT_SECRET: String;
|
||||
STEAM_API_KEY: String;
|
||||
|
||||
TREMENDOUS_API_URL: String;
|
||||
TREMENDOUS_API_KEY: String;
|
||||
TREMENDOUS_PRIVATE_KEY: String;
|
||||
|
||||
PAYPAL_API_URL: String;
|
||||
PAYPAL_WEBHOOK_ID: String;
|
||||
PAYPAL_CLIENT_ID: String;
|
||||
PAYPAL_CLIENT_SECRET: String;
|
||||
PAYPAL_NVP_USERNAME: String;
|
||||
PAYPAL_NVP_PASSWORD: String;
|
||||
PAYPAL_NVP_SIGNATURE: String;
|
||||
|
||||
PAYPAL_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||
BREX_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||
TREMENDOUS_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||
MURAL_BALANCE_ALERT_THRESHOLD: u64 = 0u64;
|
||||
|
||||
HCAPTCHA_SECRET: String;
|
||||
|
||||
SMTP_USERNAME: String;
|
||||
SMTP_PASSWORD: String;
|
||||
SMTP_HOST: String;
|
||||
SMTP_PORT: u16;
|
||||
SMTP_TLS: String;
|
||||
SMTP_FROM_NAME: String;
|
||||
SMTP_FROM_ADDRESS: String;
|
||||
|
||||
SITE_VERIFY_EMAIL_PATH: String;
|
||||
SITE_RESET_PASSWORD_PATH: String;
|
||||
SITE_BILLING_PATH: String;
|
||||
|
||||
SENDY_URL: String;
|
||||
SENDY_LIST_ID: String;
|
||||
SENDY_API_KEY: String;
|
||||
|
||||
CLICKHOUSE_REPLICATED: bool;
|
||||
CLICKHOUSE_URL: String;
|
||||
CLICKHOUSE_USER: String;
|
||||
CLICKHOUSE_PASSWORD: String;
|
||||
CLICKHOUSE_DATABASE: String;
|
||||
|
||||
FLAME_ANVIL_URL: String;
|
||||
|
||||
GOTENBERG_URL: String;
|
||||
GOTENBERG_CALLBACK_BASE: String;
|
||||
GOTENBERG_TIMEOUT: u64;
|
||||
|
||||
STRIPE_API_KEY: String;
|
||||
STRIPE_WEBHOOK_SECRET: String;
|
||||
|
||||
ADITUDE_API_KEY: String;
|
||||
|
||||
PYRO_API_KEY: String;
|
||||
|
||||
BREX_API_URL: String;
|
||||
BREX_API_KEY: String;
|
||||
|
||||
DELPHI_URL: String;
|
||||
|
||||
AVALARA_1099_API_URL: String;
|
||||
AVALARA_1099_API_KEY: String;
|
||||
AVALARA_1099_API_TEAM_ID: String;
|
||||
AVALARA_1099_COMPANY_ID: String;
|
||||
|
||||
ANROK_API_URL: String;
|
||||
ANROK_API_KEY: String;
|
||||
|
||||
COMPLIANCE_PAYOUT_THRESHOLD: String;
|
||||
|
||||
PAYOUT_ALERT_SLACK_WEBHOOK: String;
|
||||
CLOUDFLARE_INTEGRATION: bool = false;
|
||||
|
||||
ARCHON_URL: String;
|
||||
|
||||
MURALPAY_API_URL: String;
|
||||
MURALPAY_API_KEY: String;
|
||||
MURALPAY_TRANSFER_API_KEY: String;
|
||||
MURALPAY_SOURCE_ACCOUNT_ID: muralpay::AccountId = muralpay::AccountId(uuid::Uuid::nil());
|
||||
|
||||
DEFAULT_AFFILIATE_REVENUE_SPLIT: Decimal;
|
||||
|
||||
DATABASE_ACQUIRE_TIMEOUT_MS: u64 = 30000u64;
|
||||
DATABASE_MIN_CONNECTIONS: u32 = 0u32;
|
||||
DATABASE_MAX_CONNECTIONS: u32 = 16u32;
|
||||
READONLY_DATABASE_URL: String = "";
|
||||
READONLY_DATABASE_MIN_CONNECTIONS: u32 = 0u32;
|
||||
READONLY_DATABASE_MAX_CONNECTIONS: u32 = 1u32;
|
||||
|
||||
REDIS_WAIT_TIMEOUT_MS: u64 = 15000u64;
|
||||
REDIS_MAX_CONNECTIONS: u32 = 10000u32;
|
||||
REDIS_MIN_CONNECTIONS: usize = 0usize;
|
||||
|
||||
SEARCH_OPERATION_TIMEOUT: u64 = 300000u64;
|
||||
|
||||
SMTP_REPLY_TO_NAME: String = "";
|
||||
SMTP_REPLY_TO_ADDRESS: String = "";
|
||||
|
||||
PUBLIC_DISCORD_WEBHOOK: String = "";
|
||||
MODERATION_SLACK_WEBHOOK: String = "";
|
||||
DELPHI_SLACK_WEBHOOK: String = "";
|
||||
|
||||
TREMENDOUS_CAMPAIGN_ID: String = "";
|
||||
}
|
||||
@@ -9,6 +9,8 @@ use hex::ToHex;
|
||||
use sha2::Digest;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::env::ENV;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MockHost(());
|
||||
|
||||
@@ -54,8 +56,7 @@ impl FileHost for MockHost {
|
||||
file_name: &str,
|
||||
_expiry_secs: u32,
|
||||
) -> Result<String, FileHostingError> {
|
||||
let cdn_url = dotenvy::var("CDN_URL").unwrap();
|
||||
Ok(format!("{cdn_url}/private/{file_name}"))
|
||||
Ok(format!("{}/private/{file_name}", ENV.CDN_URL))
|
||||
}
|
||||
|
||||
async fn delete_file(
|
||||
@@ -77,7 +78,7 @@ fn get_file_path(
|
||||
file_name: &str,
|
||||
file_publicity: FileHostPublicity,
|
||||
) -> PathBuf {
|
||||
let mut path = PathBuf::from(dotenvy::var("MOCK_FILE_PATH").unwrap());
|
||||
let mut path = PathBuf::from(ENV.MOCK_FILE_PATH.clone());
|
||||
|
||||
if matches!(file_publicity, FileHostPublicity::Private) {
|
||||
path.push("private");
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -63,3 +65,25 @@ pub trait FileHost {
|
||||
file_publicity: FileHostPublicity,
|
||||
) -> Result<DeleteFileData, FileHostingError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum FileHostKind {
|
||||
S3,
|
||||
Local,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("invalid file host kind")]
|
||||
pub struct InvalidFileHostKind;
|
||||
|
||||
impl FromStr for FileHostKind {
|
||||
type Err = InvalidFileHostKind;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"s3" => Self::S3,
|
||||
"local" => Self::Local,
|
||||
_ => return Err(InvalidFileHostKind),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ use util::gotenberg::GotenbergClient;
|
||||
|
||||
use crate::background_task::update_versions;
|
||||
use crate::database::{PgPool, ReadOnlyPgPool};
|
||||
use crate::env::ENV;
|
||||
use crate::queue::billing::{index_billing, index_subscriptions};
|
||||
use crate::queue::moderation::AutomatedModerationQueue;
|
||||
use crate::util::anrok;
|
||||
use crate::util::archon::ArchonClient;
|
||||
use crate::util::env::{parse_strings_from_var, parse_var};
|
||||
use crate::util::ratelimit::{AsyncRateLimiter, GCRAParameters};
|
||||
use sync::friends::handle_pubsub;
|
||||
|
||||
@@ -28,6 +28,7 @@ pub mod auth;
|
||||
pub mod background_task;
|
||||
pub mod clickhouse;
|
||||
pub mod database;
|
||||
pub mod env;
|
||||
pub mod file_hosting;
|
||||
pub mod models;
|
||||
pub mod queue;
|
||||
@@ -83,10 +84,7 @@ pub fn app_setup(
|
||||
gotenberg_client: GotenbergClient,
|
||||
enable_background_tasks: bool,
|
||||
) -> LabrinthConfig {
|
||||
info!(
|
||||
"Starting labrinth on {}",
|
||||
dotenvy::var("BIND_ADDR").unwrap()
|
||||
);
|
||||
info!("Starting labrinth on {}", &ENV.BIND_ADDR);
|
||||
|
||||
let automated_moderation_queue =
|
||||
web::Data::new(AutomatedModerationQueue::default());
|
||||
@@ -112,9 +110,8 @@ pub fn app_setup(
|
||||
if enable_background_tasks {
|
||||
// The interval in seconds at which the local database is indexed
|
||||
// for searching. Defaults to 1 hour if unset.
|
||||
let local_index_interval = Duration::from_secs(
|
||||
parse_var("LOCAL_INDEX_INTERVAL").unwrap_or(3600),
|
||||
);
|
||||
let local_index_interval =
|
||||
Duration::from_secs(ENV.LOCAL_INDEX_INTERVAL);
|
||||
let pool_ref = pool.clone();
|
||||
let search_config_ref = search_config.clone();
|
||||
let redis_pool_ref = redis_pool.clone();
|
||||
@@ -142,9 +139,8 @@ pub fn app_setup(
|
||||
}
|
||||
});
|
||||
|
||||
let version_index_interval = Duration::from_secs(
|
||||
parse_var("VERSION_INDEX_INTERVAL").unwrap_or(1800),
|
||||
);
|
||||
let version_index_interval =
|
||||
Duration::from_secs(ENV.VERSION_INDEX_INTERVAL);
|
||||
let pool_ref = pool.clone();
|
||||
let redis_pool_ref = redis_pool.clone();
|
||||
scheduler.run(version_index_interval, move || {
|
||||
@@ -349,188 +345,3 @@ pub fn utoipa_app_config(
|
||||
.configure(routes::v3::utoipa_config)
|
||||
.configure(routes::internal::utoipa_config);
|
||||
}
|
||||
|
||||
// This is so that env vars not used immediately don't panic at runtime
|
||||
pub fn check_env_vars() -> bool {
|
||||
let mut failed = false;
|
||||
|
||||
fn check_var<T: std::str::FromStr>(var: &str) -> bool {
|
||||
let check = parse_var::<T>(var).is_none();
|
||||
if check {
|
||||
warn!(
|
||||
"Variable `{}` missing in dotenv or not of type `{}`",
|
||||
var,
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
}
|
||||
check
|
||||
}
|
||||
|
||||
failed |= check_var::<String>("SENTRY_ENVIRONMENT");
|
||||
failed |= check_var::<String>("SENTRY_TRACES_SAMPLE_RATE");
|
||||
failed |= check_var::<String>("SITE_URL");
|
||||
failed |= check_var::<String>("CDN_URL");
|
||||
failed |= check_var::<String>("LABRINTH_ADMIN_KEY");
|
||||
failed |= check_var::<String>("LABRINTH_EXTERNAL_NOTIFICATION_KEY");
|
||||
failed |= check_var::<String>("RATE_LIMIT_IGNORE_KEY");
|
||||
failed |= check_var::<String>("DATABASE_URL");
|
||||
failed |= check_var::<String>("MEILISEARCH_READ_ADDR");
|
||||
failed |= check_var::<String>("MEILISEARCH_WRITE_ADDRS");
|
||||
failed |= check_var::<String>("MEILISEARCH_KEY");
|
||||
failed |= check_var::<String>("REDIS_URL");
|
||||
failed |= check_var::<String>("BIND_ADDR");
|
||||
failed |= check_var::<String>("SELF_ADDR");
|
||||
|
||||
failed |= check_var::<String>("STORAGE_BACKEND");
|
||||
|
||||
let storage_backend = dotenvy::var("STORAGE_BACKEND").ok();
|
||||
match storage_backend.as_deref() {
|
||||
Some("s3") => {
|
||||
let mut check_var_set = |var_prefix| {
|
||||
failed |= check_var::<String>(&format!(
|
||||
"S3_{var_prefix}_BUCKET_NAME"
|
||||
));
|
||||
failed |= check_var::<bool>(&format!(
|
||||
"S3_{var_prefix}_USES_PATH_STYLE_BUCKET"
|
||||
));
|
||||
failed |=
|
||||
check_var::<String>(&format!("S3_{var_prefix}_REGION"));
|
||||
failed |= check_var::<String>(&format!("S3_{var_prefix}_URL"));
|
||||
failed |= check_var::<String>(&format!(
|
||||
"S3_{var_prefix}_ACCESS_TOKEN"
|
||||
));
|
||||
failed |=
|
||||
check_var::<String>(&format!("S3_{var_prefix}_SECRET"));
|
||||
};
|
||||
|
||||
check_var_set("PUBLIC");
|
||||
check_var_set("PRIVATE");
|
||||
}
|
||||
Some("local") => {
|
||||
failed |= check_var::<String>("MOCK_FILE_PATH");
|
||||
}
|
||||
Some(backend) => {
|
||||
warn!(
|
||||
"Variable `STORAGE_BACKEND` contains an invalid value: {backend}. Expected \"s3\" or \"local\"."
|
||||
);
|
||||
failed |= true;
|
||||
}
|
||||
_ => {
|
||||
warn!("Variable `STORAGE_BACKEND` is not set!");
|
||||
failed |= true;
|
||||
}
|
||||
}
|
||||
|
||||
failed |= check_var::<usize>("LOCAL_INDEX_INTERVAL");
|
||||
failed |= check_var::<usize>("VERSION_INDEX_INTERVAL");
|
||||
|
||||
if parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS").is_none() {
|
||||
warn!(
|
||||
"Variable `WHITELISTED_MODPACK_DOMAINS` missing in dotenv or not a json array of strings"
|
||||
);
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
if parse_strings_from_var("ALLOWED_CALLBACK_URLS").is_none() {
|
||||
warn!(
|
||||
"Variable `ALLOWED_CALLBACK_URLS` missing in dotenv or not a json array of strings"
|
||||
);
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
failed |= check_var::<String>("GITHUB_CLIENT_ID");
|
||||
failed |= check_var::<String>("GITHUB_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("GITLAB_CLIENT_ID");
|
||||
failed |= check_var::<String>("GITLAB_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("DISCORD_CLIENT_ID");
|
||||
failed |= check_var::<String>("DISCORD_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("MICROSOFT_CLIENT_ID");
|
||||
failed |= check_var::<String>("MICROSOFT_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("GOOGLE_CLIENT_ID");
|
||||
failed |= check_var::<String>("GOOGLE_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("STEAM_API_KEY");
|
||||
|
||||
failed |= check_var::<String>("TREMENDOUS_API_URL");
|
||||
failed |= check_var::<String>("TREMENDOUS_API_KEY");
|
||||
failed |= check_var::<String>("TREMENDOUS_PRIVATE_KEY");
|
||||
|
||||
failed |= check_var::<String>("PAYPAL_API_URL");
|
||||
failed |= check_var::<String>("PAYPAL_WEBHOOK_ID");
|
||||
failed |= check_var::<String>("PAYPAL_CLIENT_ID");
|
||||
failed |= check_var::<String>("PAYPAL_CLIENT_SECRET");
|
||||
failed |= check_var::<String>("PAYPAL_NVP_USERNAME");
|
||||
failed |= check_var::<String>("PAYPAL_NVP_PASSWORD");
|
||||
failed |= check_var::<String>("PAYPAL_NVP_SIGNATURE");
|
||||
|
||||
failed |= check_var::<String>("HCAPTCHA_SECRET");
|
||||
|
||||
failed |= check_var::<String>("SMTP_USERNAME");
|
||||
failed |= check_var::<String>("SMTP_PASSWORD");
|
||||
failed |= check_var::<String>("SMTP_HOST");
|
||||
failed |= check_var::<u16>("SMTP_PORT");
|
||||
failed |= check_var::<String>("SMTP_TLS");
|
||||
failed |= check_var::<String>("SMTP_FROM_NAME");
|
||||
failed |= check_var::<String>("SMTP_FROM_ADDRESS");
|
||||
|
||||
failed |= check_var::<String>("SITE_VERIFY_EMAIL_PATH");
|
||||
failed |= check_var::<String>("SITE_RESET_PASSWORD_PATH");
|
||||
failed |= check_var::<String>("SITE_BILLING_PATH");
|
||||
|
||||
failed |= check_var::<String>("SENDY_URL");
|
||||
failed |= check_var::<String>("SENDY_LIST_ID");
|
||||
failed |= check_var::<String>("SENDY_API_KEY");
|
||||
|
||||
if parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS").is_none() {
|
||||
warn!(
|
||||
"Variable `ANALYTICS_ALLOWED_ORIGINS` missing in dotenv or not a json array of strings"
|
||||
);
|
||||
failed |= true;
|
||||
}
|
||||
|
||||
failed |= check_var::<bool>("CLICKHOUSE_REPLICATED");
|
||||
failed |= check_var::<String>("CLICKHOUSE_URL");
|
||||
failed |= check_var::<String>("CLICKHOUSE_USER");
|
||||
failed |= check_var::<String>("CLICKHOUSE_PASSWORD");
|
||||
failed |= check_var::<String>("CLICKHOUSE_DATABASE");
|
||||
|
||||
failed |= check_var::<String>("FLAME_ANVIL_URL");
|
||||
|
||||
failed |= check_var::<String>("GOTENBERG_URL");
|
||||
failed |= check_var::<String>("GOTENBERG_CALLBACK_BASE");
|
||||
failed |= check_var::<String>("GOTENBERG_TIMEOUT");
|
||||
|
||||
failed |= check_var::<String>("STRIPE_API_KEY");
|
||||
failed |= check_var::<String>("STRIPE_WEBHOOK_SECRET");
|
||||
|
||||
failed |= check_var::<String>("ADITUDE_API_KEY");
|
||||
|
||||
failed |= check_var::<String>("PYRO_API_KEY");
|
||||
|
||||
failed |= check_var::<String>("BREX_API_URL");
|
||||
failed |= check_var::<String>("BREX_API_KEY");
|
||||
|
||||
failed |= check_var::<String>("DELPHI_URL");
|
||||
|
||||
failed |= check_var::<String>("AVALARA_1099_API_URL");
|
||||
failed |= check_var::<String>("AVALARA_1099_API_KEY");
|
||||
failed |= check_var::<String>("AVALARA_1099_API_TEAM_ID");
|
||||
failed |= check_var::<String>("AVALARA_1099_COMPANY_ID");
|
||||
|
||||
failed |= check_var::<String>("ANROK_API_URL");
|
||||
failed |= check_var::<String>("ANROK_API_KEY");
|
||||
|
||||
failed |= check_var::<String>("COMPLIANCE_PAYOUT_THRESHOLD");
|
||||
|
||||
failed |= check_var::<String>("PAYOUT_ALERT_SLACK_WEBHOOK");
|
||||
|
||||
failed |= check_var::<String>("ARCHON_URL");
|
||||
|
||||
failed |= check_var::<String>("MURALPAY_API_URL");
|
||||
failed |= check_var::<String>("MURALPAY_API_KEY");
|
||||
failed |= check_var::<String>("MURALPAY_TRANSFER_API_KEY");
|
||||
failed |= check_var::<String>("MURALPAY_SOURCE_ACCOUNT_ID");
|
||||
|
||||
failed |= check_var::<String>("DEFAULT_AFFILIATE_REVENUE_SPLIT");
|
||||
|
||||
failed
|
||||
}
|
||||
|
||||
@@ -4,21 +4,21 @@ use actix_web::{App, HttpServer};
|
||||
use actix_web_prom::PrometheusMetricsBuilder;
|
||||
use clap::Parser;
|
||||
|
||||
use labrinth::app_config;
|
||||
use labrinth::background_task::BackgroundTask;
|
||||
use labrinth::database::redis::RedisPool;
|
||||
use labrinth::file_hosting::{S3BucketConfig, S3Host};
|
||||
use labrinth::env::ENV;
|
||||
use labrinth::file_hosting::{FileHostKind, S3BucketConfig, S3Host};
|
||||
use labrinth::queue::email::EmailQueue;
|
||||
use labrinth::search;
|
||||
use labrinth::util::anrok;
|
||||
use labrinth::util::env::parse_var;
|
||||
use labrinth::util::gotenberg::GotenbergClient;
|
||||
use labrinth::util::ratelimit::rate_limit_middleware;
|
||||
use labrinth::utoipa_app_config;
|
||||
use labrinth::{check_env_vars, clickhouse, database, file_hosting};
|
||||
use labrinth::{app_config, env};
|
||||
use labrinth::{clickhouse, database, file_hosting};
|
||||
use std::ffi::CStr;
|
||||
use std::sync::Arc;
|
||||
use tracing::{Instrument, error, info, info_span};
|
||||
use tracing::{Instrument, info, info_span};
|
||||
use tracing_actix_web::TracingLogger;
|
||||
use utoipa::OpenApi;
|
||||
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
|
||||
@@ -58,11 +58,7 @@ fn main() -> std::io::Result<()> {
|
||||
color_eyre::install().expect("failed to install `color-eyre`");
|
||||
dotenvy::dotenv().ok();
|
||||
modrinth_util::log::init().expect("failed to initialize logging");
|
||||
|
||||
if check_env_vars() {
|
||||
error!("Some environment variables are missing!");
|
||||
std::process::exit(1);
|
||||
}
|
||||
env::init().expect("failed to initialize environment variables");
|
||||
|
||||
// Sentry must be set up before the async runtime is started
|
||||
// <https://docs.sentry.io/platforms/rust/guides/actix-web/>
|
||||
@@ -70,11 +66,8 @@ fn main() -> std::io::Result<()> {
|
||||
// Has no effect if not set.
|
||||
let sentry = sentry::init(sentry::ClientOptions {
|
||||
release: sentry::release_name!(),
|
||||
traces_sample_rate: dotenvy::var("SENTRY_TRACES_SAMPLE_RATE")
|
||||
.unwrap()
|
||||
.parse()
|
||||
.expect("failed to parse `SENTRY_TRACES_SAMPLE_RATE` as number"),
|
||||
environment: Some(dotenvy::var("SENTRY_ENVIRONMENT").unwrap().into()),
|
||||
traces_sample_rate: ENV.SENTRY_TRACES_SAMPLE_RATE,
|
||||
environment: Some((&ENV.SENTRY_ENVIRONMENT).into()),
|
||||
..Default::default()
|
||||
});
|
||||
if sentry.is_enabled() {
|
||||
@@ -99,10 +92,7 @@ async fn app() -> std::io::Result<()> {
|
||||
.unwrap();
|
||||
|
||||
if args.run_background_task.is_none() {
|
||||
info!(
|
||||
"Starting labrinth on {}",
|
||||
dotenvy::var("BIND_ADDR").unwrap()
|
||||
);
|
||||
info!("Starting labrinth on {}", &ENV.BIND_ADDR);
|
||||
|
||||
if !args.no_migrations {
|
||||
database::check_for_migrations()
|
||||
@@ -119,40 +109,44 @@ async fn app() -> std::io::Result<()> {
|
||||
// Redis connector
|
||||
let redis_pool = RedisPool::new("");
|
||||
|
||||
let storage_backend =
|
||||
dotenvy::var("STORAGE_BACKEND").unwrap_or_else(|_| "local".to_string());
|
||||
|
||||
let storage_backend = ENV.STORAGE_BACKEND;
|
||||
let file_host: Arc<dyn file_hosting::FileHost + Send + Sync> =
|
||||
match storage_backend.as_str() {
|
||||
"s3" => {
|
||||
let config_from_env = |bucket_type| S3BucketConfig {
|
||||
name: parse_var(&format!("S3_{bucket_type}_BUCKET_NAME"))
|
||||
.unwrap(),
|
||||
uses_path_style: parse_var(&format!(
|
||||
"S3_{bucket_type}_USES_PATH_STYLE_BUCKET"
|
||||
))
|
||||
.unwrap(),
|
||||
region: parse_var(&format!("S3_{bucket_type}_REGION"))
|
||||
.unwrap(),
|
||||
url: parse_var(&format!("S3_{bucket_type}_URL")).unwrap(),
|
||||
access_token: parse_var(&format!(
|
||||
"S3_{bucket_type}_ACCESS_TOKEN"
|
||||
))
|
||||
.unwrap(),
|
||||
secret: parse_var(&format!("S3_{bucket_type}_SECRET"))
|
||||
.unwrap(),
|
||||
match storage_backend {
|
||||
FileHostKind::S3 => {
|
||||
let not_empty = |v: &str| -> String {
|
||||
assert!(!v.is_empty(), "S3 env var is empty");
|
||||
v.to_string()
|
||||
};
|
||||
|
||||
Arc::new(
|
||||
S3Host::new(
|
||||
config_from_env("PUBLIC"),
|
||||
config_from_env("PRIVATE"),
|
||||
S3BucketConfig {
|
||||
name: not_empty(&ENV.S3_PUBLIC_BUCKET_NAME),
|
||||
uses_path_style: ENV
|
||||
.S3_PUBLIC_USES_PATH_STYLE_BUCKET,
|
||||
region: not_empty(&ENV.S3_PUBLIC_REGION),
|
||||
url: not_empty(&ENV.S3_PUBLIC_URL),
|
||||
access_token: not_empty(
|
||||
&ENV.S3_PUBLIC_ACCESS_TOKEN,
|
||||
),
|
||||
secret: not_empty(&ENV.S3_PUBLIC_SECRET),
|
||||
},
|
||||
S3BucketConfig {
|
||||
name: not_empty(&ENV.S3_PRIVATE_BUCKET_NAME),
|
||||
uses_path_style: ENV
|
||||
.S3_PRIVATE_USES_PATH_STYLE_BUCKET,
|
||||
region: not_empty(&ENV.S3_PRIVATE_REGION),
|
||||
url: not_empty(&ENV.S3_PRIVATE_URL),
|
||||
access_token: not_empty(
|
||||
&ENV.S3_PRIVATE_ACCESS_TOKEN,
|
||||
),
|
||||
secret: not_empty(&ENV.S3_PRIVATE_SECRET),
|
||||
},
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
"local" => Arc::new(file_hosting::MockHost::new()),
|
||||
_ => panic!("Invalid storage backend specified. Aborting startup!"),
|
||||
FileHostKind::Local => Arc::new(file_hosting::MockHost::new()),
|
||||
};
|
||||
|
||||
info!("Initializing clickhouse connection");
|
||||
@@ -160,8 +154,7 @@ async fn app() -> std::io::Result<()> {
|
||||
|
||||
let search_config = search::SearchConfig::new(None);
|
||||
|
||||
let stripe_client =
|
||||
stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
|
||||
let stripe_client = stripe::Client::new(ENV.STRIPE_API_KEY.clone());
|
||||
|
||||
let anrok_client = anrok::Client::from_env().unwrap();
|
||||
let email_queue =
|
||||
@@ -272,7 +265,7 @@ async fn app() -> std::io::Result<()> {
|
||||
.into_app()
|
||||
.configure(|cfg| app_config(cfg, labrinth_config.clone()))
|
||||
})
|
||||
.bind(dotenvy::var("BIND_ADDR").unwrap())?
|
||||
.bind(&ENV.BIND_ADDR)?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use crate::{
|
||||
models::v2::projects::LegacySideType, util::env::parse_strings_from_var,
|
||||
};
|
||||
use crate::{env::ENV, models::v2::projects::LegacySideType};
|
||||
use path_util::SafeRelativeUtf8UnixPathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
@@ -44,9 +42,7 @@ fn validate_download_url(
|
||||
return Err(validator::ValidationError::new("invalid URL"));
|
||||
}
|
||||
|
||||
let domains = parse_strings_from_var("WHITELISTED_MODPACK_DOMAINS")
|
||||
.unwrap_or_default();
|
||||
if !domains.contains(
|
||||
if !ENV.WHITELISTED_MODPACK_DOMAINS.contains(
|
||||
&url.domain()
|
||||
.ok_or_else(|| validator::ValidationError::new("invalid URL"))?
|
||||
.to_string(),
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::database::models::{
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::{PgPool, PgTransaction};
|
||||
use crate::env::ENV;
|
||||
use crate::models::billing::{
|
||||
ChargeStatus, ChargeType, PaymentPlatform, Price, PriceDuration,
|
||||
ProductMetadata, SubscriptionMetadata, SubscriptionStatus,
|
||||
@@ -913,10 +914,10 @@ async fn unprovision_subscriptions(
|
||||
let res = reqwest::Client::new()
|
||||
.post(format!(
|
||||
"{}/modrinth/v0/servers/{}/suspend",
|
||||
dotenvy::var("ARCHON_URL")?,
|
||||
ENV.ARCHON_URL,
|
||||
server_id
|
||||
))
|
||||
.header("X-Master-Key", dotenvy::var("PYRO_API_KEY")?)
|
||||
.header("X-Master-Key", &ENV.PYRO_API_KEY)
|
||||
.json(&serde_json::json!({
|
||||
"reason": if charge.status == ChargeStatus::Cancelled || charge.status == ChargeStatus::Expiring {
|
||||
"cancelled"
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::database::models::notifications_template_item::NotificationTemplate;
|
||||
use crate::database::models::user_item::DBUser;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::{PgPool, PgTransaction};
|
||||
use crate::env::ENV;
|
||||
use crate::models::notifications::{NotificationBody, NotificationType};
|
||||
use crate::models::v3::notifications::{
|
||||
NotificationChannel, NotificationDeliveryStatus,
|
||||
@@ -36,16 +37,16 @@ impl Mailer {
|
||||
) -> Result<Arc<AsyncSmtpTransport<Tokio1Executor>>, MailError> {
|
||||
let maybe_transport = match self {
|
||||
Mailer::Uninitialized => {
|
||||
let username = dotenvy::var("SMTP_USERNAME")?;
|
||||
let password = dotenvy::var("SMTP_PASSWORD")?;
|
||||
let host = dotenvy::var("SMTP_HOST")?;
|
||||
let port =
|
||||
dotenvy::var("SMTP_PORT")?.parse::<u16>().unwrap_or(465);
|
||||
let username = &ENV.SMTP_USERNAME;
|
||||
let password = &ENV.SMTP_PASSWORD;
|
||||
let host = &ENV.SMTP_HOST;
|
||||
let port = ENV.SMTP_PORT;
|
||||
|
||||
let creds = (!username.is_empty())
|
||||
.then(|| Credentials::new(username, password));
|
||||
let creds = (!username.is_empty()).then(|| {
|
||||
Credentials::new(username.clone(), password.clone())
|
||||
});
|
||||
|
||||
let tls_setting = match dotenvy::var("SMTP_TLS")?.as_str() {
|
||||
let tls_setting = match ENV.SMTP_TLS.as_str() {
|
||||
"none" => Tls::None,
|
||||
"opportunistic_start_tls" => Tls::Opportunistic(
|
||||
TlsParameters::new(host.to_string())?,
|
||||
@@ -65,7 +66,7 @@ impl Mailer {
|
||||
};
|
||||
|
||||
let mut mailer =
|
||||
AsyncSmtpTransport::<Tokio1Executor>::relay(&host)?
|
||||
AsyncSmtpTransport::<Tokio1Executor>::relay(host)?
|
||||
.port(port)
|
||||
.tls(tls_setting);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::database::models::{
|
||||
DBOrganization, DBProject, DBUser, DatabaseError,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::models::v3::notifications::NotificationBody;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::error::Context;
|
||||
@@ -96,10 +97,18 @@ pub struct MailingIdentity {
|
||||
impl MailingIdentity {
|
||||
pub fn from_env() -> dotenvy::Result<Self> {
|
||||
Ok(Self {
|
||||
from_name: dotenvy::var("SMTP_FROM_NAME")?,
|
||||
from_address: dotenvy::var("SMTP_FROM_ADDRESS")?,
|
||||
reply_name: dotenvy::var("SMTP_REPLY_TO_NAME").ok(),
|
||||
reply_address: dotenvy::var("SMTP_REPLY_TO_ADDRESS").ok(),
|
||||
from_name: ENV.SMTP_FROM_NAME.clone(),
|
||||
from_address: ENV.SMTP_FROM_ADDRESS.clone(),
|
||||
reply_name: if ENV.SMTP_REPLY_TO_NAME.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(ENV.SMTP_REPLY_TO_NAME.clone())
|
||||
},
|
||||
reply_address: if ENV.SMTP_REPLY_TO_ADDRESS.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(ENV.SMTP_REPLY_TO_ADDRESS.clone())
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -558,9 +567,7 @@ async fn collect_template_variables(
|
||||
NotificationBody::ResetPassword { flow } => {
|
||||
let url = format!(
|
||||
"{}/{}?flow={}",
|
||||
dotenvy::var("SITE_URL")?,
|
||||
dotenvy::var("SITE_RESET_PASSWORD_PATH")?,
|
||||
flow
|
||||
ENV.SITE_URL, ENV.SITE_RESET_PASSWORD_PATH, flow
|
||||
);
|
||||
|
||||
map.insert(RESETPASSWORD_URL, url);
|
||||
@@ -571,9 +578,7 @@ async fn collect_template_variables(
|
||||
NotificationBody::VerifyEmail { flow } => {
|
||||
let url = format!(
|
||||
"{}/{}?flow={}",
|
||||
dotenvy::var("SITE_URL")?,
|
||||
dotenvy::var("SITE_VERIFY_EMAIL_PATH")?,
|
||||
flow
|
||||
ENV.SITE_URL, ENV.SITE_VERIFY_EMAIL_PATH, flow
|
||||
);
|
||||
|
||||
map.insert(VERIFYEMAIL_URL, url);
|
||||
@@ -603,11 +608,7 @@ async fn collect_template_variables(
|
||||
}
|
||||
|
||||
NotificationBody::PaymentFailed { amount, service } => {
|
||||
let url = format!(
|
||||
"{}/{}",
|
||||
dotenvy::var("SITE_URL")?,
|
||||
dotenvy::var("SITE_BILLING_PATH")?,
|
||||
);
|
||||
let url = format!("{}/{}", ENV.SITE_URL, ENV.SITE_BILLING_PATH,);
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(PAYMENTFAILED_AMOUNT, amount.clone());
|
||||
@@ -748,8 +749,7 @@ async fn dynamic_email_body(
|
||||
key: &str,
|
||||
) -> Result<String, ApiError> {
|
||||
get_or_set_cached_dynamic_html(redis, key, || async {
|
||||
let site_url = dotenvy::var("SITE_URL")
|
||||
.wrap_internal_err("SITE_URL is not set")?;
|
||||
let site_url = &ENV.SITE_URL;
|
||||
let site_url = site_url.trim_end_matches('/');
|
||||
|
||||
let url = format!("{site_url}/_internal/templates/email/dynamic");
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::database::PgPool;
|
||||
use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::thread_item::ThreadMessageBuilder;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::models::ids::ProjectId;
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::pack::{PackFile, PackFileHash, PackFormat};
|
||||
@@ -454,7 +455,7 @@ impl AutomatedModerationQueue {
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let res = client
|
||||
.post(format!("{}/v1/fingerprints", dotenvy::var("FLAME_ANVIL_URL")?))
|
||||
.post(format!("{}/v1/fingerprints", ENV.FLAME_ANVIL_URL))
|
||||
.json(&serde_json::json!({
|
||||
"fingerprints": hashes.iter().filter_map(|x| x.3).collect::<Vec<u32>>()
|
||||
}))
|
||||
@@ -553,11 +554,11 @@ impl AutomatedModerationQueue {
|
||||
continue;
|
||||
}
|
||||
|
||||
let flame_projects = if flame_files.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let res = client
|
||||
.post(format!("{}v1/mods", dotenvy::var("FLAME_ANVIL_URL")?))
|
||||
let flame_projects = if flame_files.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let res = client
|
||||
.post(format!("{}v1/mods", ENV.FLAME_ANVIL_URL))
|
||||
.json(&serde_json::json!({
|
||||
"modIds": flame_files.iter().map(|x| x.1).collect::<Vec<_>>()
|
||||
}))
|
||||
@@ -664,16 +665,16 @@ impl AutomatedModerationQueue {
|
||||
.insert_many(members.into_iter().map(|x| x.user_id).collect(), &mut transaction, &redis)
|
||||
.await?;
|
||||
|
||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_SLACK_WEBHOOK") {
|
||||
if !ENV.MODERATION_SLACK_WEBHOOK.is_empty() {
|
||||
crate::util::webhook::send_slack_project_webhook(
|
||||
project.inner.id.into(),
|
||||
&pool,
|
||||
&redis,
|
||||
webhook_url,
|
||||
&ENV.MODERATION_SLACK_WEBHOOK,
|
||||
Some(
|
||||
format!(
|
||||
"*<{}/user/AutoMod|AutoMod>* changed project status from *{}* to *Rejected*",
|
||||
dotenvy::var("SITE_URL")?,
|
||||
ENV.SITE_URL,
|
||||
&project.inner.status.as_friendly_str(),
|
||||
)
|
||||
.to_string(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::database::PgPool;
|
||||
use crate::env::ENV;
|
||||
use chrono::{Datelike, Duration, TimeZone, Utc};
|
||||
use eyre::{Context, Result, eyre};
|
||||
use rust_decimal::{Decimal, dec};
|
||||
@@ -62,11 +63,7 @@ pub async fn process_affiliate_payouts(postgres: &PgPool) -> Result<()> {
|
||||
.await
|
||||
.wrap_err("failed to fetch charges awaiting affiliate payout")?;
|
||||
|
||||
let default_affiliate_revenue_split =
|
||||
dotenvy::var("DEFAULT_AFFILIATE_REVENUE_SPLIT")
|
||||
.wrap_err("no env var `DEFAULT_AFFILIATE_REVENUE_SPLIT`")?
|
||||
.parse::<Decimal>()
|
||||
.wrap_err("`DEFAULT_AFFILIATE_REVENUE_SPLIT` is not a decimal")?;
|
||||
let default_affiliate_revenue_split = ENV.DEFAULT_AFFILIATE_REVENUE_SPLIT;
|
||||
|
||||
let (
|
||||
mut insert_usap_charges,
|
||||
|
||||
@@ -8,6 +8,7 @@ use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
database::models::payout_item::DBPayout,
|
||||
env::ENV,
|
||||
models::payouts::{
|
||||
PayoutMethod, PayoutMethodFee, PayoutMethodType, PayoutStatus,
|
||||
TremendousCurrency, TremendousDetails, TremendousForexResponse,
|
||||
@@ -210,7 +211,7 @@ pub(super) async fn execute(
|
||||
"products": [
|
||||
method_id,
|
||||
],
|
||||
"campaign_id": dotenvy::var("TREMENDOUS_CAMPAIGN_ID")?,
|
||||
"campaign_id": ENV.TREMENDOUS_CAMPAIGN_ID.as_str(),
|
||||
}]
|
||||
});
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::payouts_values_notifications;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::{PgPool, PgTransaction};
|
||||
use crate::env::ENV;
|
||||
use crate::models::payouts::{
|
||||
PayoutDecimal, PayoutInterval, PayoutMethod, PayoutMethodType,
|
||||
TremendousForexResponse,
|
||||
};
|
||||
use crate::models::projects::MonetizationStatus;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::env::env_var;
|
||||
use crate::util::error::Context;
|
||||
use crate::util::webhook::{
|
||||
PayoutSourceAlertType, send_slack_payout_source_alert_webhook,
|
||||
@@ -76,21 +76,18 @@ impl Default for PayoutsQueue {
|
||||
}
|
||||
|
||||
pub fn create_muralpay_client() -> Result<muralpay::Client> {
|
||||
let api_url = env_var("MURALPAY_API_URL")?;
|
||||
let api_key = env_var("MURALPAY_API_KEY")?;
|
||||
let transfer_api_key = env_var("MURALPAY_TRANSFER_API_KEY")?;
|
||||
Ok(muralpay::Client::new(api_url, api_key, transfer_api_key))
|
||||
Ok(muralpay::Client::new(
|
||||
&ENV.MURALPAY_API_URL,
|
||||
ENV.MURALPAY_API_KEY.as_str(),
|
||||
ENV.MURALPAY_TRANSFER_API_KEY.as_str(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn create_muralpay() -> Result<MuralPayConfig> {
|
||||
let client = create_muralpay_client()?;
|
||||
let source_account_id = env_var("MURALPAY_SOURCE_ACCOUNT_ID")?
|
||||
.parse::<muralpay::AccountId>()
|
||||
.wrap_err("failed to parse source account ID")?;
|
||||
|
||||
Ok(MuralPayConfig {
|
||||
client,
|
||||
source_account_id,
|
||||
source_account_id: ENV.MURALPAY_SOURCE_ACCOUNT_ID,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -185,11 +182,8 @@ impl PayoutsQueue {
|
||||
let mut creds = self.credential.write().await;
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let combined_key = format!(
|
||||
"{}:{}",
|
||||
dotenvy::var("PAYPAL_CLIENT_ID")?,
|
||||
dotenvy::var("PAYPAL_CLIENT_SECRET")?
|
||||
);
|
||||
let combined_key =
|
||||
format!("{}:{}", ENV.PAYPAL_CLIENT_ID, ENV.PAYPAL_CLIENT_SECRET);
|
||||
let formatted_key = format!(
|
||||
"Basic {}",
|
||||
base64::engine::general_purpose::STANDARD.encode(combined_key)
|
||||
@@ -206,7 +200,7 @@ impl PayoutsQueue {
|
||||
}
|
||||
|
||||
let credential: PaypalCredential = client
|
||||
.post(format!("{}oauth2/token", dotenvy::var("PAYPAL_API_URL")?))
|
||||
.post(format!("{}oauth2/token", ENV.PAYPAL_API_URL))
|
||||
.header("Accept", "application/json")
|
||||
.header("Accept-Language", "en_US")
|
||||
.header("Authorization", formatted_key)
|
||||
@@ -274,7 +268,7 @@ impl PayoutsQueue {
|
||||
if no_api_prefix.unwrap_or(false) {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("{}{path}", dotenvy::var("PAYPAL_API_URL")?)
|
||||
format!("{}{path}", ENV.PAYPAL_API_URL)
|
||||
},
|
||||
)
|
||||
.header(
|
||||
@@ -355,13 +349,10 @@ impl PayoutsQueue {
|
||||
) -> Result<X, ApiError> {
|
||||
let client = reqwest::Client::new();
|
||||
let mut request = client
|
||||
.request(
|
||||
method,
|
||||
format!("{}{path}", dotenvy::var("TREMENDOUS_API_URL")?),
|
||||
)
|
||||
.request(method, format!("{}{path}", ENV.TREMENDOUS_API_URL))
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("Bearer {}", dotenvy::var("TREMENDOUS_API_KEY")?),
|
||||
format!("Bearer {}", ENV.TREMENDOUS_API_KEY),
|
||||
);
|
||||
|
||||
if let Some(body) = body {
|
||||
@@ -511,8 +502,8 @@ impl PayoutsQueue {
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let res = client
|
||||
.get(format!("{}accounts/cash", dotenvy::var("BREX_API_URL")?))
|
||||
.bearer_auth(&dotenvy::var("BREX_API_KEY")?)
|
||||
.get(format!("{}accounts/cash", ENV.BREX_API_URL))
|
||||
.bearer_auth(&ENV.BREX_API_KEY)
|
||||
.send()
|
||||
.await?
|
||||
.json::<BrexResponse>()
|
||||
@@ -538,16 +529,16 @@ impl PayoutsQueue {
|
||||
|
||||
pub async fn get_paypal_balance() -> Result<Option<AccountBalance>, ApiError>
|
||||
{
|
||||
let api_username = dotenvy::var("PAYPAL_NVP_USERNAME")?;
|
||||
let api_password = dotenvy::var("PAYPAL_NVP_PASSWORD")?;
|
||||
let api_signature = dotenvy::var("PAYPAL_NVP_SIGNATURE")?;
|
||||
let api_username = &ENV.PAYPAL_NVP_USERNAME;
|
||||
let api_password = &ENV.PAYPAL_NVP_PASSWORD;
|
||||
let api_signature = &ENV.PAYPAL_NVP_SIGNATURE;
|
||||
|
||||
let mut params = HashMap::new();
|
||||
params.insert("METHOD", "GetBalance");
|
||||
params.insert("VERSION", "204");
|
||||
params.insert("USER", &api_username);
|
||||
params.insert("PWD", &api_password);
|
||||
params.insert("SIGNATURE", &api_signature);
|
||||
params.insert("USER", api_username);
|
||||
params.insert("PWD", api_password);
|
||||
params.insert("SIGNATURE", api_signature);
|
||||
params.insert("RETURNALLCURRENCIES", "1");
|
||||
|
||||
let endpoint = "https://api-3t.paypal.com/nvp";
|
||||
@@ -870,7 +861,7 @@ pub async fn make_aditude_request(
|
||||
) -> Result<Vec<AditudePoints>, ApiError> {
|
||||
let request = reqwest::Client::new()
|
||||
.post("https://cloud.aditude.io/api/public/insights/metrics")
|
||||
.bearer_auth(&dotenvy::var("ADITUDE_API_KEY")?)
|
||||
.bearer_auth(&ENV.ADITUDE_API_KEY)
|
||||
.json(&serde_json::json!({
|
||||
"metrics": metrics,
|
||||
"range": range,
|
||||
@@ -1326,25 +1317,25 @@ pub async fn insert_bank_balances_and_webhook(
|
||||
if inserted {
|
||||
check_balance_with_webhook(
|
||||
"paypal",
|
||||
"PAYPAL_BALANCE_ALERT_THRESHOLD",
|
||||
ENV.PAYPAL_BALANCE_ALERT_THRESHOLD,
|
||||
paypal_result,
|
||||
)
|
||||
.await?;
|
||||
check_balance_with_webhook(
|
||||
"brex",
|
||||
"BREX_BALANCE_ALERT_THRESHOLD",
|
||||
ENV.BREX_BALANCE_ALERT_THRESHOLD,
|
||||
brex_result,
|
||||
)
|
||||
.await?;
|
||||
check_balance_with_webhook(
|
||||
"tremendous",
|
||||
"TREMENDOUS_BALANCE_ALERT_THRESHOLD",
|
||||
ENV.TREMENDOUS_BALANCE_ALERT_THRESHOLD,
|
||||
tremendous_result,
|
||||
)
|
||||
.await?;
|
||||
check_balance_with_webhook(
|
||||
"mural",
|
||||
"MURAL_BALANCE_ALERT_THRESHOLD",
|
||||
ENV.MURAL_BALANCE_ALERT_THRESHOLD,
|
||||
mural_result,
|
||||
)
|
||||
.await?;
|
||||
@@ -1357,14 +1348,11 @@ pub async fn insert_bank_balances_and_webhook(
|
||||
|
||||
async fn check_balance_with_webhook(
|
||||
source: &str,
|
||||
threshold_env_var_name: &str,
|
||||
threshold: u64,
|
||||
result: Result<Option<AccountBalance>, ApiError>,
|
||||
) -> Result<Option<AccountBalance>, ApiError> {
|
||||
let maybe_threshold = dotenvy::var(threshold_env_var_name)
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok())
|
||||
.filter(|x| *x != 0);
|
||||
let payout_alert_webhook = dotenvy::var("PAYOUT_ALERT_SLACK_WEBHOOK")?;
|
||||
let maybe_threshold = if threshold > 0 { Some(threshold) } else { None };
|
||||
let payout_alert_webhook = &ENV.PAYOUT_ALERT_SLACK_WEBHOOK;
|
||||
|
||||
match &result {
|
||||
Ok(Some(account_balance)) => {
|
||||
@@ -1379,7 +1367,7 @@ async fn check_balance_with_webhook(
|
||||
threshold,
|
||||
current_balance: available,
|
||||
},
|
||||
&payout_alert_webhook,
|
||||
payout_alert_webhook,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -1394,7 +1382,7 @@ async fn check_balance_with_webhook(
|
||||
source: source.to_owned(),
|
||||
display_error: error.to_string(),
|
||||
},
|
||||
&payout_alert_webhook,
|
||||
payout_alert_webhook,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::PgPool;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::models::analytics::{PageView, Playtime};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::queue::analytics::AnalyticsQueue;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::date::get_current_tenths_of_ms;
|
||||
use crate::util::env::parse_strings_from_var;
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use actix_web::{post, web};
|
||||
use serde::Deserialize;
|
||||
@@ -73,11 +73,10 @@ pub async fn page_view_ingest(
|
||||
})?;
|
||||
let url_origin = url.origin().ascii_serialization();
|
||||
|
||||
let is_valid_url_origin =
|
||||
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS")
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.any(|origin| origin == "*" || url_origin == *origin);
|
||||
let is_valid_url_origin = ENV
|
||||
.ANALYTICS_ALLOWED_ORIGINS
|
||||
.iter()
|
||||
.any(|origin| origin == "*" || url_origin == *origin);
|
||||
|
||||
if !is_valid_url_origin {
|
||||
return Err(ApiError::InvalidInput(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{collections::HashMap, net::Ipv4Addr, sync::Arc};
|
||||
|
||||
use crate::database::PgPool;
|
||||
use crate::env::ENV;
|
||||
use crate::{
|
||||
auth::get_user_from_headers,
|
||||
database::{
|
||||
@@ -13,10 +14,7 @@ use crate::{
|
||||
},
|
||||
queue::{analytics::AnalyticsQueue, session::AuthQueue},
|
||||
routes::analytics::FILTERED_HEADERS,
|
||||
util::{
|
||||
date::get_current_tenths_of_ms, env::parse_strings_from_var,
|
||||
error::Context,
|
||||
},
|
||||
util::{date::get_current_tenths_of_ms, error::Context},
|
||||
};
|
||||
use actix_web::{HttpRequest, delete, get, patch, post, put, web};
|
||||
use ariadne::ids::UserId;
|
||||
@@ -70,11 +68,10 @@ async fn ingest_click(
|
||||
})?;
|
||||
let url_origin = url.origin().ascii_serialization();
|
||||
|
||||
let is_valid_url_origin =
|
||||
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS")
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.any(|origin| origin == "*" || url_origin == *origin);
|
||||
let is_valid_url_origin = ENV
|
||||
.ANALYTICS_ALLOWED_ORIGINS
|
||||
.iter()
|
||||
.any(|origin| origin == "*" || url_origin == *origin);
|
||||
|
||||
if !is_valid_url_origin {
|
||||
return Err(ApiError::InvalidInput(
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::database::models::{
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::{PgPool, PgTransaction};
|
||||
use crate::env::ENV;
|
||||
use crate::models::billing::{
|
||||
Charge, ChargeStatus, ChargeType, PaymentPlatform, Price, PriceDuration,
|
||||
Product, ProductMetadata, ProductPrice, SubscriptionMetadata,
|
||||
@@ -1437,7 +1438,7 @@ pub async fn active_servers(
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<ActiveServersQuery>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let master_key = dotenvy::var("PYRO_API_KEY")?;
|
||||
let master_key = &ENV.PYRO_API_KEY;
|
||||
|
||||
if req
|
||||
.head()
|
||||
@@ -1626,7 +1627,7 @@ pub async fn stripe_webhook(
|
||||
if let Ok(event) = Webhook::construct_event(
|
||||
&payload,
|
||||
stripe_signature,
|
||||
&dotenvy::var("STRIPE_WEBHOOK_SECRET")?,
|
||||
&ENV.STRIPE_WEBHOOK_SECRET,
|
||||
) {
|
||||
struct PaymentIntentMetadata {
|
||||
pub user_item: crate::database::models::user_item::DBUser,
|
||||
@@ -2036,23 +2037,23 @@ pub async fn stripe_webhook(
|
||||
client
|
||||
.post(format!(
|
||||
"{}/modrinth/v0/servers/{}/unsuspend",
|
||||
dotenvy::var("ARCHON_URL")?,
|
||||
ENV.ARCHON_URL,
|
||||
id
|
||||
))
|
||||
.header("X-Master-Key", dotenvy::var("PYRO_API_KEY")?)
|
||||
.header("X-Master-Key", &ENV.PYRO_API_KEY)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
|
||||
client
|
||||
.post(format!(
|
||||
"{}/modrinth/v0/servers/{}/reallocate",
|
||||
dotenvy::var("ARCHON_URL")?,
|
||||
id
|
||||
))
|
||||
"{}/modrinth/v0/servers/{}/reallocate",
|
||||
ENV.ARCHON_URL,
|
||||
id
|
||||
))
|
||||
.header(
|
||||
"X-Master-Key",
|
||||
dotenvy::var("PYRO_API_KEY")?,
|
||||
&ENV.PYRO_API_KEY,
|
||||
)
|
||||
.json(&body)
|
||||
.send()
|
||||
@@ -2114,9 +2115,9 @@ pub async fn stripe_webhook(
|
||||
let res = client
|
||||
.post(format!(
|
||||
"{}/modrinth/v0/servers/create",
|
||||
dotenvy::var("ARCHON_URL")?,
|
||||
ENV.ARCHON_URL,
|
||||
))
|
||||
.header("X-Master-Key", dotenvy::var("PYRO_API_KEY")?)
|
||||
.header("X-Master-Key", &ENV.PYRO_API_KEY)
|
||||
.json(&serde_json::json!({
|
||||
"user_id": to_base62(metadata.user_item.id.0 as u64),
|
||||
"name": server_name,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{collections::HashMap, fmt::Write, sync::LazyLock, time::Instant};
|
||||
|
||||
use crate::database::PgPool;
|
||||
use crate::env::ENV;
|
||||
use actix_web::{HttpRequest, HttpResponse, get, post, web};
|
||||
use chrono::{DateTime, Utc};
|
||||
use eyre::eyre;
|
||||
@@ -89,7 +90,7 @@ impl DelphiReport {
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), ApiError> {
|
||||
let webhook_url = dotenvy::var("DELPHI_SLACK_WEBHOOK")?;
|
||||
let webhook_url = ENV.DELPHI_SLACK_WEBHOOK.clone();
|
||||
|
||||
let mut message_header =
|
||||
format!("⚠️ Suspicious traces found at {}", self.url);
|
||||
@@ -115,7 +116,7 @@ impl DelphiReport {
|
||||
self.project_id,
|
||||
pool,
|
||||
redis,
|
||||
webhook_url,
|
||||
&webhook_url,
|
||||
Some(message_header),
|
||||
)
|
||||
.await
|
||||
@@ -317,7 +318,7 @@ pub async fn run(
|
||||
);
|
||||
|
||||
DELPHI_CLIENT
|
||||
.post(dotenvy::var("DELPHI_URL")?)
|
||||
.post(&ENV.DELPHI_URL)
|
||||
.json(&serde_json::json!({
|
||||
"url": file_data.url,
|
||||
"project_id": ProjectId(file_data.project_id.0 as u64),
|
||||
@@ -407,7 +408,7 @@ async fn issue_type_schema(
|
||||
&cache_entry
|
||||
.insert((
|
||||
DELPHI_CLIENT
|
||||
.get(format!("{}/schema", dotenvy::var("DELPHI_URL")?))
|
||||
.get(format!("{}/schema", ENV.DELPHI_URL))
|
||||
.send()
|
||||
.await
|
||||
.and_then(|res| res.error_for_status())
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::database::models::flow_item::DBFlow;
|
||||
use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::{DBUser, DBUserId};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||
use crate::models::notifications::NotificationBody;
|
||||
use crate::models::pats::Scopes;
|
||||
@@ -17,7 +18,6 @@ use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::routes::internal::session::issue_session;
|
||||
use crate::util::captcha::check_hcaptcha;
|
||||
use crate::util::env::parse_strings_from_var;
|
||||
use crate::util::error::Context;
|
||||
use crate::util::ext::get_image_ext;
|
||||
use crate::util::img::upload_image_optimized;
|
||||
@@ -257,41 +257,41 @@ impl AuthProvider {
|
||||
&self,
|
||||
state: String,
|
||||
) -> Result<String, AuthenticationError> {
|
||||
let self_addr = dotenvy::var("SELF_ADDR")?;
|
||||
let self_addr = &ENV.SELF_ADDR;
|
||||
let raw_redirect_uri = format!("{self_addr}/v2/auth/callback");
|
||||
let redirect_uri = urlencoding::encode(&raw_redirect_uri);
|
||||
|
||||
Ok(match self {
|
||||
AuthProvider::GitHub => {
|
||||
let client_id = dotenvy::var("GITHUB_CLIENT_ID")?;
|
||||
let client_id = &ENV.GITHUB_CLIENT_ID;
|
||||
|
||||
format!(
|
||||
"https://github.com/login/oauth/authorize?client_id={client_id}&prompt=select_account&state={state}&scope=read%3Auser%20user%3Aemail&redirect_uri={redirect_uri}",
|
||||
)
|
||||
}
|
||||
AuthProvider::Discord => {
|
||||
let client_id = dotenvy::var("DISCORD_CLIENT_ID")?;
|
||||
let client_id = &ENV.DISCORD_CLIENT_ID;
|
||||
|
||||
format!(
|
||||
"https://discord.com/api/oauth2/authorize?client_id={client_id}&state={state}&response_type=code&scope=identify%20email&redirect_uri={redirect_uri}"
|
||||
)
|
||||
}
|
||||
AuthProvider::Microsoft => {
|
||||
let client_id = dotenvy::var("MICROSOFT_CLIENT_ID")?;
|
||||
let client_id = &ENV.MICROSOFT_CLIENT_ID;
|
||||
|
||||
format!(
|
||||
"https://login.live.com/oauth20_authorize.srf?client_id={client_id}&response_type=code&scope=user.read&state={state}&prompt=select_account&redirect_uri={redirect_uri}"
|
||||
)
|
||||
}
|
||||
AuthProvider::GitLab => {
|
||||
let client_id = dotenvy::var("GITLAB_CLIENT_ID")?;
|
||||
let client_id = &ENV.GITLAB_CLIENT_ID;
|
||||
|
||||
format!(
|
||||
"https://gitlab.com/oauth/authorize?client_id={client_id}&state={state}&scope=read_user+profile+email&response_type=code&redirect_uri={redirect_uri}",
|
||||
)
|
||||
}
|
||||
AuthProvider::Google => {
|
||||
let client_id = dotenvy::var("GOOGLE_CLIENT_ID")?;
|
||||
let client_id = &ENV.GOOGLE_CLIENT_ID;
|
||||
|
||||
format!(
|
||||
"https://accounts.google.com/o/oauth2/v2/auth?client_id={}&state={}&scope={}&response_type=code&redirect_uri={}",
|
||||
@@ -317,8 +317,8 @@ impl AuthProvider {
|
||||
)
|
||||
}
|
||||
AuthProvider::PayPal => {
|
||||
let api_url = dotenvy::var("PAYPAL_API_URL")?;
|
||||
let client_id = dotenvy::var("PAYPAL_CLIENT_ID")?;
|
||||
let api_url = &ENV.PAYPAL_API_URL;
|
||||
let client_id = &ENV.PAYPAL_CLIENT_ID;
|
||||
|
||||
let auth_url = if api_url.contains("sandbox") {
|
||||
"sandbox.paypal.com"
|
||||
@@ -340,8 +340,7 @@ impl AuthProvider {
|
||||
&self,
|
||||
query: HashMap<String, String>,
|
||||
) -> Result<String, AuthenticationError> {
|
||||
let redirect_uri =
|
||||
format!("{}/v2/auth/callback", dotenvy::var("SELF_ADDR")?);
|
||||
let redirect_uri = format!("{}/v2/auth/callback", &ENV.SELF_ADDR);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AccessToken {
|
||||
@@ -353,8 +352,8 @@ impl AuthProvider {
|
||||
let code = query
|
||||
.get("code")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let client_id = dotenvy::var("GITHUB_CLIENT_ID")?;
|
||||
let client_secret = dotenvy::var("GITHUB_CLIENT_SECRET")?;
|
||||
let client_id = ENV.GITHUB_CLIENT_ID.as_str();
|
||||
let client_secret = ENV.GITHUB_CLIENT_SECRET.as_str();
|
||||
|
||||
let url = format!(
|
||||
"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret={client_secret}&code={code}&redirect_uri={redirect_uri}"
|
||||
@@ -374,12 +373,12 @@ impl AuthProvider {
|
||||
let code = query
|
||||
.get("code")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let client_id = dotenvy::var("DISCORD_CLIENT_ID")?;
|
||||
let client_secret = dotenvy::var("DISCORD_CLIENT_SECRET")?;
|
||||
let client_id = ENV.DISCORD_CLIENT_ID.as_str();
|
||||
let client_secret = ENV.DISCORD_CLIENT_SECRET.as_str();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("client_id", &*client_id);
|
||||
map.insert("client_secret", &*client_secret);
|
||||
map.insert("client_id", client_id);
|
||||
map.insert("client_secret", client_secret);
|
||||
map.insert("code", code);
|
||||
map.insert("grant_type", "authorization_code");
|
||||
map.insert("redirect_uri", &redirect_uri);
|
||||
@@ -399,12 +398,12 @@ impl AuthProvider {
|
||||
let code = query
|
||||
.get("code")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let client_id = dotenvy::var("MICROSOFT_CLIENT_ID")?;
|
||||
let client_secret = dotenvy::var("MICROSOFT_CLIENT_SECRET")?;
|
||||
let client_id = ENV.MICROSOFT_CLIENT_ID.as_str();
|
||||
let client_secret = ENV.MICROSOFT_CLIENT_SECRET.as_str();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("client_id", &*client_id);
|
||||
map.insert("client_secret", &*client_secret);
|
||||
map.insert("client_id", client_id);
|
||||
map.insert("client_secret", client_secret);
|
||||
map.insert("code", code);
|
||||
map.insert("grant_type", "authorization_code");
|
||||
map.insert("redirect_uri", &redirect_uri);
|
||||
@@ -424,12 +423,12 @@ impl AuthProvider {
|
||||
let code = query
|
||||
.get("code")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let client_id = dotenvy::var("GITLAB_CLIENT_ID")?;
|
||||
let client_secret = dotenvy::var("GITLAB_CLIENT_SECRET")?;
|
||||
let client_id = ENV.GITLAB_CLIENT_ID.as_str();
|
||||
let client_secret = ENV.GITLAB_CLIENT_SECRET.as_str();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("client_id", &*client_id);
|
||||
map.insert("client_secret", &*client_secret);
|
||||
map.insert("client_id", client_id);
|
||||
map.insert("client_secret", client_secret);
|
||||
map.insert("code", code);
|
||||
map.insert("grant_type", "authorization_code");
|
||||
map.insert("redirect_uri", &redirect_uri);
|
||||
@@ -449,12 +448,12 @@ impl AuthProvider {
|
||||
let code = query
|
||||
.get("code")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let client_id = dotenvy::var("GOOGLE_CLIENT_ID")?;
|
||||
let client_secret = dotenvy::var("GOOGLE_CLIENT_SECRET")?;
|
||||
let client_id = ENV.GOOGLE_CLIENT_ID.as_str();
|
||||
let client_secret = ENV.GOOGLE_CLIENT_SECRET.as_str();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("client_id", &*client_id);
|
||||
map.insert("client_secret", &*client_secret);
|
||||
map.insert("client_id", client_id);
|
||||
map.insert("client_secret", client_secret);
|
||||
map.insert("code", code);
|
||||
map.insert("grant_type", "authorization_code");
|
||||
map.insert("redirect_uri", &redirect_uri);
|
||||
@@ -529,9 +528,9 @@ impl AuthProvider {
|
||||
let code = query
|
||||
.get("code")
|
||||
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
||||
let api_url = dotenvy::var("PAYPAL_API_URL")?;
|
||||
let client_id = dotenvy::var("PAYPAL_CLIENT_ID")?;
|
||||
let client_secret = dotenvy::var("PAYPAL_CLIENT_SECRET")?;
|
||||
let api_url = ENV.PAYPAL_API_URL.as_str();
|
||||
let client_id = ENV.PAYPAL_CLIENT_ID.as_str();
|
||||
let client_secret = ENV.PAYPAL_CLIENT_SECRET.as_str();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("code", code.as_str());
|
||||
@@ -580,9 +579,7 @@ impl AuthProvider {
|
||||
.get("x-oauth-client-id")
|
||||
.and_then(|x| x.to_str().ok());
|
||||
|
||||
if client_id
|
||||
!= Some(&*dotenvy::var("GITHUB_CLIENT_ID").unwrap())
|
||||
{
|
||||
if client_id != Some(ENV.GITHUB_CLIENT_ID.as_str()) {
|
||||
return Err(AuthenticationError::InvalidClientId);
|
||||
}
|
||||
}
|
||||
@@ -732,7 +729,7 @@ impl AuthProvider {
|
||||
}
|
||||
}
|
||||
AuthProvider::Steam => {
|
||||
let api_key = dotenvy::var("STEAM_API_KEY")?;
|
||||
let api_key = &ENV.STEAM_API_KEY;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SteamResponse {
|
||||
@@ -797,7 +794,7 @@ impl AuthProvider {
|
||||
pub country: String,
|
||||
}
|
||||
|
||||
let api_url = dotenvy::var("PAYPAL_API_URL")?;
|
||||
let api_url = &ENV.PAYPAL_API_URL;
|
||||
|
||||
let paypal_user: PayPalUser = reqwest::Client::new()
|
||||
.get(format!(
|
||||
@@ -1100,10 +1097,11 @@ pub async fn init(
|
||||
let url =
|
||||
url::Url::parse(&info.url).map_err(|_| AuthenticationError::Url)?;
|
||||
|
||||
let allowed_callback_urls =
|
||||
parse_strings_from_var("ALLOWED_CALLBACK_URLS").unwrap_or_default();
|
||||
let domain = url.host_str().ok_or(AuthenticationError::Url)?;
|
||||
if !allowed_callback_urls.iter().any(|x| domain.ends_with(x))
|
||||
if !ENV
|
||||
.ALLOWED_CALLBACK_URLS
|
||||
.iter()
|
||||
.any(|x| domain.ends_with(x))
|
||||
&& domain != "modrinth.com"
|
||||
{
|
||||
return Err(AuthenticationError::Url);
|
||||
@@ -1396,9 +1394,9 @@ pub async fn delete_auth_provider(
|
||||
pub async fn check_sendy_subscription(
|
||||
email: &str,
|
||||
) -> Result<bool, AuthenticationError> {
|
||||
let url = dotenvy::var("SENDY_URL")?;
|
||||
let id = dotenvy::var("SENDY_LIST_ID")?;
|
||||
let api_key = dotenvy::var("SENDY_API_KEY")?;
|
||||
let url = &ENV.SENDY_URL;
|
||||
let id = &ENV.SENDY_LIST_ID;
|
||||
let api_key = &ENV.SENDY_API_KEY;
|
||||
|
||||
if url.is_empty() || url == "none" {
|
||||
tracing::info!(
|
||||
@@ -1408,9 +1406,9 @@ pub async fn check_sendy_subscription(
|
||||
}
|
||||
|
||||
let mut form = HashMap::new();
|
||||
form.insert("api_key", &*api_key);
|
||||
form.insert("api_key", api_key.as_str());
|
||||
form.insert("email", email);
|
||||
form.insert("list_id", &*id);
|
||||
form.insert("list_id", id.as_str());
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let response = client
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
|
||||
use crate::env::ENV;
|
||||
use actix_web::{get, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -28,7 +29,8 @@ static GLOBALS: LazyLock<Globals> = LazyLock::new(|| Globals {
|
||||
tax_compliance_thresholds: [(2025, 600), (2026, 2000)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
captcha_enabled: dotenvy::var("HCAPTCHA_SECRET").is_ok_and(|x| x != "none"),
|
||||
captcha_enabled: !ENV.HCAPTCHA_SECRET.is_empty()
|
||||
&& ENV.HCAPTCHA_SECRET != "none",
|
||||
});
|
||||
|
||||
/// Gets configured global non-secret variables for this backend instance.
|
||||
|
||||
@@ -4,11 +4,11 @@ use crate::database::models::session_item::DBSession;
|
||||
use crate::database::models::session_item::SessionBuilder;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::{PgPool, PgTransaction};
|
||||
use crate::env::ENV;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::sessions::Session;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::env::parse_var;
|
||||
use actix_web::http::header::AUTHORIZATION;
|
||||
use actix_web::web::{Data, ServiceConfig, scope};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
@@ -41,7 +41,7 @@ pub async fn get_session_metadata(
|
||||
req: &HttpRequest,
|
||||
) -> Result<SessionMetadata, AuthenticationError> {
|
||||
let conn_info = req.connection_info().clone();
|
||||
let ip_addr = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
||||
let ip_addr = if ENV.CLOUDFLARE_INTEGRATION {
|
||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||
header.to_str().ok()
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::database::models::DelphiReportIssueDetailsId;
|
||||
use crate::env::ENV;
|
||||
use crate::file_hosting::FileHostingError;
|
||||
use crate::routes::analytics::{page_view_ingest, playtime_ingest};
|
||||
use crate::util::cors::default_cors;
|
||||
use crate::util::env::parse_strings_from_var;
|
||||
use actix_cors::Cors;
|
||||
use actix_files::Files;
|
||||
use actix_web::http::StatusCode;
|
||||
@@ -40,10 +40,7 @@ pub fn root_config(cfg: &mut web::ServiceConfig) {
|
||||
.wrap(
|
||||
Cors::default()
|
||||
.allowed_origin_fn(|origin, _req_head| {
|
||||
let allowed_origins =
|
||||
parse_strings_from_var("ANALYTICS_ALLOWED_ORIGINS")
|
||||
.unwrap_or_default();
|
||||
|
||||
let allowed_origins = &ENV.ANALYTICS_ALLOWED_ORIGINS;
|
||||
allowed_origins.contains(&"*".to_string())
|
||||
|| allowed_origins.contains(
|
||||
&origin
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::database::PgPool;
|
||||
use crate::env::ENV;
|
||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -94,11 +95,7 @@ pub async fn forge_updates(
|
||||
}
|
||||
|
||||
let mut response = ForgeUpdates {
|
||||
homepage: format!(
|
||||
"{}/mod/{}",
|
||||
dotenvy::var("SITE_URL").unwrap_or_default(),
|
||||
id
|
||||
),
|
||||
homepage: format!("{}/mod/{}", ENV.SITE_URL, id),
|
||||
promos: HashMap::new(),
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::database::PgPool;
|
||||
use crate::database::models::DBUserId;
|
||||
use crate::database::models::{generate_payout_id, users_compliance};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::models::ids::PayoutId;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::payouts::{PayoutMethodType, PayoutStatus, Withdrawal};
|
||||
@@ -212,7 +213,7 @@ pub async fn paypal_webhook(
|
||||
\"webhook_id\": \"{}\",
|
||||
\"webhook_event\": {body}
|
||||
}}",
|
||||
dotenvy::var("PAYPAL_WEBHOOK_ID")?
|
||||
ENV.PAYPAL_WEBHOOK_ID,
|
||||
)),
|
||||
None,
|
||||
)
|
||||
@@ -322,7 +323,7 @@ pub async fn tremendous_webhook(
|
||||
})?;
|
||||
|
||||
let mut mac: Hmac<Sha256> = Hmac::new_from_slice(
|
||||
dotenvy::var("TREMENDOUS_PRIVATE_KEY")?.as_bytes(),
|
||||
ENV.TREMENDOUS_PRIVATE_KEY.as_bytes(),
|
||||
)
|
||||
.map_err(|_| ApiError::Payments("error initializing HMAC".to_string()))?;
|
||||
mac.update(body.as_bytes());
|
||||
@@ -1114,9 +1115,7 @@ async fn update_compliance_status(
|
||||
}
|
||||
|
||||
fn tax_compliance_payout_threshold() -> Option<Decimal> {
|
||||
dotenvy::var("COMPLIANCE_PAYOUT_THRESHOLD")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
ENV.COMPLIANCE_PAYOUT_THRESHOLD.parse().ok()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::database::models::{
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::{self, models as db_models};
|
||||
use crate::database::{PgPool, PgTransaction};
|
||||
use crate::env::ENV;
|
||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||
use crate::models;
|
||||
use crate::models::ids::{ProjectId, VersionId};
|
||||
@@ -427,13 +428,13 @@ pub async fn project_edit(
|
||||
|
||||
if status.is_searchable()
|
||||
&& !project_item.inner.webhook_sent
|
||||
&& let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK")
|
||||
&& !ENV.PUBLIC_DISCORD_WEBHOOK.is_empty()
|
||||
{
|
||||
crate::util::webhook::send_discord_webhook(
|
||||
project_item.inner.id.into(),
|
||||
&pool,
|
||||
&redis,
|
||||
webhook_url,
|
||||
&ENV.PUBLIC_DISCORD_WEBHOOK,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
@@ -451,18 +452,16 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if user.role.is_mod()
|
||||
&& let Ok(webhook_url) = dotenvy::var("MODERATION_SLACK_WEBHOOK")
|
||||
{
|
||||
if user.role.is_mod() && !ENV.MODERATION_SLACK_WEBHOOK.is_empty() {
|
||||
crate::util::webhook::send_slack_project_webhook(
|
||||
project_item.inner.id.into(),
|
||||
&pool,
|
||||
&redis,
|
||||
webhook_url,
|
||||
&ENV.MODERATION_SLACK_WEBHOOK,
|
||||
Some(
|
||||
format!(
|
||||
"*<{}/user/{}|{}>* changed project status from *{}* to *{}*",
|
||||
dotenvy::var("SITE_URL")?,
|
||||
ENV.SITE_URL,
|
||||
user.username,
|
||||
user.username,
|
||||
&project_item.inner.status.as_friendly_str(),
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::database::models::image_item;
|
||||
use crate::database::models::notification_item::NotificationBuilder;
|
||||
use crate::database::models::thread_item::ThreadMessageBuilder;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||
use crate::models::ids::{ThreadId, ThreadMessageId};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
@@ -631,9 +632,8 @@ pub async fn message_delete(
|
||||
let images =
|
||||
database::DBImage::get_many_contexted(context, &mut transaction)
|
||||
.await?;
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
for image in images {
|
||||
let name = image.url.split(&format!("{cdn_url}/")).nth(1);
|
||||
let name = image.url.split(&format!("{}/", ENV.CDN_URL)).nth(1);
|
||||
if let Some(icon_path) = name {
|
||||
file_host
|
||||
.delete_file(
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::database::models::version_item::{
|
||||
};
|
||||
use crate::database::models::{self, DBOrganization, image_item};
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||
use crate::models::ids::{ImageId, ProjectId, VersionId};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
@@ -974,7 +975,7 @@ pub async fn upload_file(
|
||||
|
||||
version_files.push(VersionFileBuilder {
|
||||
filename: file_name.to_string(),
|
||||
url: format!("{}/{file_path_encode}", dotenvy::var("CDN_URL")?),
|
||||
url: format!("{}/{file_path_encode}", ENV.CDN_URL),
|
||||
hashes: vec![
|
||||
models::version_item::HashBuilder {
|
||||
algorithm: "sha1".to_string(),
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::time::Duration;
|
||||
|
||||
use crate::database::PgPool;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::search::{SearchConfig, UploadSearchProject};
|
||||
use crate::util::error::Context;
|
||||
use ariadne::ids::base62_impl::to_base62;
|
||||
@@ -43,12 +44,7 @@ pub enum IndexingError {
|
||||
const MEILISEARCH_CHUNK_SIZE: usize = 50000; // 10_000_000
|
||||
|
||||
fn search_operation_timeout() -> std::time::Duration {
|
||||
let default_ms = 5 * 60 * 1000; // 5 minutes
|
||||
let ms = dotenvy::var("SEARCH_OPERATION_TIMEOUT")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.unwrap_or(default_ms);
|
||||
std::time::Duration::from_millis(ms)
|
||||
std::time::Duration::from_millis(ENV.SEARCH_OPERATION_TIMEOUT)
|
||||
}
|
||||
|
||||
pub async fn remove_documents(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::env::ENV;
|
||||
use crate::models::projects::SearchRequest;
|
||||
use crate::{models::error::ApiError, search::indexing::IndexingError};
|
||||
use actix_web::HttpResponse;
|
||||
@@ -134,26 +135,11 @@ impl SearchConfig {
|
||||
// Panics if the environment variables are not set,
|
||||
// but these are already checked for on startup.
|
||||
pub fn new(meta_namespace: Option<String>) -> Self {
|
||||
let address_many = dotenvy::var("MEILISEARCH_WRITE_ADDRS")
|
||||
.expect("MEILISEARCH_WRITE_ADDRS not set");
|
||||
|
||||
let read_lb_address = dotenvy::var("MEILISEARCH_READ_ADDR")
|
||||
.expect("MEILISEARCH_READ_ADDR not set");
|
||||
|
||||
let addresses = address_many
|
||||
.split(',')
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let key =
|
||||
dotenvy::var("MEILISEARCH_KEY").expect("MEILISEARCH_KEY not set");
|
||||
|
||||
Self {
|
||||
addresses,
|
||||
key,
|
||||
addresses: ENV.MEILISEARCH_WRITE_ADDRS.0.clone(),
|
||||
key: ENV.MEILISEARCH_KEY.clone(),
|
||||
meta_namespace: meta_namespace.unwrap_or_default(),
|
||||
read_lb_address,
|
||||
read_lb_address: ENV.MEILISEARCH_READ_ADDR.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::{
|
||||
environment::LocalService,
|
||||
};
|
||||
use crate::LabrinthConfig;
|
||||
use crate::env::ENV;
|
||||
use actix_web::{App, dev::ServiceResponse, test};
|
||||
use async_trait::async_trait;
|
||||
use std::rc::Rc;
|
||||
@@ -46,10 +47,7 @@ impl Api for ApiV2 {
|
||||
async fn reset_search_index(&self) -> ServiceResponse {
|
||||
let req = actix_web::test::TestRequest::post()
|
||||
.uri("/v2/admin/_force_reindex")
|
||||
.append_header((
|
||||
"Modrinth-Admin",
|
||||
dotenvy::var("LABRINTH_ADMIN_KEY").unwrap(),
|
||||
))
|
||||
.append_header(("Modrinth-Admin", ENV.LABRINTH_ADMIN_KEY.clone()))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::{
|
||||
environment::LocalService,
|
||||
};
|
||||
use crate::LabrinthConfig;
|
||||
use crate::env::ENV;
|
||||
use actix_web::{App, dev::ServiceResponse, test};
|
||||
use async_trait::async_trait;
|
||||
use std::rc::Rc;
|
||||
@@ -51,10 +52,7 @@ impl Api for ApiV3 {
|
||||
async fn reset_search_index(&self) -> ServiceResponse {
|
||||
let req = actix_web::test::TestRequest::post()
|
||||
.uri("/_internal/admin/_force_reindex")
|
||||
.append_header((
|
||||
"Modrinth-Admin",
|
||||
dotenvy::var("LABRINTH_ADMIN_KEY").unwrap(),
|
||||
))
|
||||
.append_header(("Modrinth-Admin", ENV.LABRINTH_ADMIN_KEY.clone()))
|
||||
.to_request();
|
||||
self.call(req).await
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::database::PgPool;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::{MIGRATOR, ReadOnlyPgPool};
|
||||
use crate::env::ENV;
|
||||
use crate::search;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use std::time::Duration;
|
||||
@@ -57,15 +58,14 @@ impl TemporaryDatabase {
|
||||
let temp_database_name = generate_random_name("labrinth_tests_db_");
|
||||
println!("Creating temporary database: {}", &temp_database_name);
|
||||
|
||||
let database_url =
|
||||
dotenvy::var("DATABASE_URL").expect("No database URL");
|
||||
let database_url = &ENV.DATABASE_URL;
|
||||
|
||||
// Create the temporary (and template database, if needed)
|
||||
Self::create_temporary(&database_url, &temp_database_name).await;
|
||||
Self::create_temporary(database_url, &temp_database_name).await;
|
||||
|
||||
// Pool to the temporary database
|
||||
let mut temporary_url =
|
||||
Url::parse(&database_url).expect("Invalid database URL");
|
||||
Url::parse(database_url).expect("Invalid database URL");
|
||||
|
||||
temporary_url.set_path(&format!("/{}", &temp_database_name));
|
||||
let temp_db_url = temporary_url.to_string();
|
||||
@@ -139,10 +139,8 @@ impl TemporaryDatabase {
|
||||
}
|
||||
|
||||
// Switch to template
|
||||
let url =
|
||||
dotenvy::var("DATABASE_URL").expect("No database URL");
|
||||
let mut template_url =
|
||||
Url::parse(&url).expect("Invalid database URL");
|
||||
let mut template_url = Url::parse(&ENV.DATABASE_URL)
|
||||
.expect("Invalid database URL");
|
||||
template_url.set_path(&format!("/{TEMPLATE_DATABASE_NAME}"));
|
||||
|
||||
let pool = sqlx::PgPool::connect(template_url.as_str())
|
||||
@@ -234,11 +232,10 @@ impl TemporaryDatabase {
|
||||
// If a temporary db is created, it must be cleaned up with cleanup.
|
||||
// This means that dbs will only 'remain' if a test fails (for examination of the db), and will be cleaned up otherwise.
|
||||
pub async fn cleanup(mut self) {
|
||||
let database_url =
|
||||
dotenvy::var("DATABASE_URL").expect("No database URL");
|
||||
let database_url = &ENV.DATABASE_URL;
|
||||
self.pool.close().await;
|
||||
|
||||
self.pool = sqlx::PgPool::connect(&database_url)
|
||||
self.pool = sqlx::PgPool::connect(database_url)
|
||||
.await
|
||||
.map(PgPool::from)
|
||||
.expect("Connection to main database failed");
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::env::ENV;
|
||||
use crate::queue::email::EmailQueue;
|
||||
use crate::util::anrok;
|
||||
use crate::util::gotenberg::GotenbergClient;
|
||||
use crate::{LabrinthConfig, file_hosting};
|
||||
use crate::{check_env_vars, clickhouse};
|
||||
use crate::{clickhouse, env};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod api_common;
|
||||
@@ -22,12 +23,8 @@ pub mod search;
|
||||
// If making a test, you should probably use environment::TestEnvironment::build() (which calls this)
|
||||
pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig {
|
||||
println!("Setting up labrinth config");
|
||||
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
if check_env_vars() {
|
||||
println!("Some environment variables are missing!");
|
||||
}
|
||||
env::init().expect("failed to initialize environment variables");
|
||||
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
|
||||
@@ -39,8 +36,7 @@ pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig {
|
||||
Arc::new(file_hosting::MockHost::new());
|
||||
let mut clickhouse = clickhouse::init_client().await.unwrap();
|
||||
|
||||
let stripe_client =
|
||||
stripe::Client::new(dotenvy::var("STRIPE_API_KEY").unwrap());
|
||||
let stripe_client = stripe::Client::new(ENV.STRIPE_API_KEY.clone());
|
||||
|
||||
let anrok_client = anrok::Client::from_env().unwrap();
|
||||
let email_queue =
|
||||
|
||||
@@ -7,6 +7,9 @@ use serde_with::{DisplayFromStr, serde_as};
|
||||
use thiserror::Error;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::env::ENV;
|
||||
use crate::routes::ApiError;
|
||||
|
||||
pub fn transaction_id_stripe_pi(pi: &stripe::PaymentIntentId) -> String {
|
||||
format!("stripe:charge:{pi}")
|
||||
}
|
||||
@@ -154,19 +157,14 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn from_env() -> Result<Self, dotenvy::Error> {
|
||||
let api_key = dotenvy::var("ANROK_API_KEY")?;
|
||||
let api_url = dotenvy::var("ANROK_API_URL")?
|
||||
.trim_start_matches('/')
|
||||
.to_owned();
|
||||
|
||||
pub fn from_env() -> Result<Self, ApiError> {
|
||||
Ok(Self {
|
||||
client: reqwest::Client::builder()
|
||||
.user_agent("Modrinth")
|
||||
.build()
|
||||
.expect("AnrokClient to build"),
|
||||
api_key,
|
||||
api_url,
|
||||
api_key: ENV.ANROK_API_KEY.clone(),
|
||||
api_url: ENV.ANROK_API_URL.trim_start_matches('/').to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use reqwest::header::HeaderName;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::env::ENV;
|
||||
use crate::routes::ApiError;
|
||||
|
||||
const X_MASTER_KEY: HeaderName = HeaderName::from_static("x-master-key");
|
||||
@@ -42,13 +43,12 @@ impl ArchonClient {
|
||||
pub fn from_env() -> Result<Self, ApiError> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let base_url =
|
||||
dotenvy::var("ARCHON_URL")?.trim_end_matches('/').to_owned();
|
||||
let base_url = ENV.ARCHON_URL.trim_end_matches('/').to_owned();
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
base_url,
|
||||
pyro_api_key: dotenvy::var("PYRO_API_KEY")?,
|
||||
pyro_api_key: ENV.PYRO_API_KEY.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::database::models::{DBUserId, users_compliance::FormType};
|
||||
use crate::env::ENV;
|
||||
use crate::routes::ApiError;
|
||||
use ariadne::ids::base62_impl::to_base62;
|
||||
use chrono::Datelike;
|
||||
@@ -131,10 +132,10 @@ fn team_request(
|
||||
method: reqwest::Method,
|
||||
route: &str,
|
||||
) -> Result<(reqwest::RequestBuilder, String), ApiError> {
|
||||
let key = dotenvy::var("AVALARA_1099_API_KEY")?;
|
||||
let url = dotenvy::var("AVALARA_1099_API_URL")?;
|
||||
let team = dotenvy::var("AVALARA_1099_API_TEAM_ID")?;
|
||||
let company = dotenvy::var("AVALARA_1099_COMPANY_ID")?;
|
||||
let key = &ENV.AVALARA_1099_API_KEY;
|
||||
let url = &ENV.AVALARA_1099_API_URL;
|
||||
let team = &ENV.AVALARA_1099_API_TEAM_ID;
|
||||
let company = &ENV.AVALARA_1099_COMPANY_ID;
|
||||
|
||||
let url = url.trim_end_matches('/');
|
||||
|
||||
@@ -144,8 +145,8 @@ fn team_request(
|
||||
client
|
||||
.request(method, format!("{url}/v1/{team}{route}"))
|
||||
.header(reqwest::header::USER_AGENT, "Modrinth")
|
||||
.bearer_auth(&key),
|
||||
company,
|
||||
.bearer_auth(key),
|
||||
company.to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::env::ENV;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::env::parse_var;
|
||||
use actix_web::HttpRequest;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -8,7 +8,7 @@ pub async fn check_hcaptcha(
|
||||
req: &HttpRequest,
|
||||
challenge: &str,
|
||||
) -> Result<bool, ApiError> {
|
||||
let secret = dotenvy::var("HCAPTCHA_SECRET")?;
|
||||
let secret = &ENV.HCAPTCHA_SECRET;
|
||||
|
||||
if secret.is_empty() || secret == "none" {
|
||||
tracing::info!("hCaptcha secret not set, skipping check");
|
||||
@@ -16,7 +16,7 @@ pub async fn check_hcaptcha(
|
||||
}
|
||||
|
||||
let conn_info = req.connection_info().clone();
|
||||
let ip_addr = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
||||
let ip_addr = if ENV.CLOUDFLARE_INTEGRATION {
|
||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||
header.to_str().ok()
|
||||
} else {
|
||||
@@ -38,7 +38,7 @@ pub async fn check_hcaptcha(
|
||||
let mut form = HashMap::new();
|
||||
|
||||
form.insert("response", challenge);
|
||||
form.insert("secret", &*secret);
|
||||
form.insert("secret", secret);
|
||||
form.insert("remoteip", ip_addr);
|
||||
|
||||
let val: Response = client
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use eyre::{Context, eyre};
|
||||
|
||||
pub fn env_var(key: &str) -> eyre::Result<String> {
|
||||
dotenvy::var(key)
|
||||
.wrap_err_with(|| eyre!("missing environment variable `{key}`"))
|
||||
}
|
||||
|
||||
pub fn parse_var<T: FromStr>(var: &str) -> Option<T> {
|
||||
dotenvy::var(var).ok().and_then(|i| i.parse().ok())
|
||||
}
|
||||
pub fn parse_strings_from_var(var: &'static str) -> Option<Vec<String>> {
|
||||
dotenvy::var(var)
|
||||
.ok()
|
||||
.and_then(|s| serde_json::from_str::<Vec<String>>(&s).ok())
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::models::ids::PayoutId;
|
||||
use crate::routes::ApiError;
|
||||
use crate::routes::internal::gotenberg::{GotenbergDocument, GotenbergError};
|
||||
use crate::util::env::env_var;
|
||||
use crate::util::error::Context;
|
||||
use actix_web::http::header::HeaderName;
|
||||
use chrono::{DateTime, Datelike, Utc};
|
||||
@@ -71,15 +71,14 @@ impl GotenbergClient {
|
||||
.build()
|
||||
.wrap_err("failed to build reqwest client")?;
|
||||
|
||||
let gotenberg_url = env_var("GOTENBERG_URL")?;
|
||||
let site_url = env_var("SITE_URL")?;
|
||||
let callback_base = env_var("GOTENBERG_CALLBACK_BASE")?;
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
gotenberg_url: gotenberg_url.trim_end_matches('/').to_owned(),
|
||||
site_url: site_url.trim_end_matches('/').to_owned(),
|
||||
callback_base: callback_base.trim_end_matches('/').to_owned(),
|
||||
gotenberg_url: ENV.GOTENBERG_URL.trim_end_matches('/').to_owned(),
|
||||
site_url: ENV.SITE_URL.trim_end_matches('/').to_owned(),
|
||||
callback_base: ENV
|
||||
.GOTENBERG_CALLBACK_BASE
|
||||
.trim_end_matches('/')
|
||||
.to_owned(),
|
||||
redis,
|
||||
})
|
||||
}
|
||||
@@ -189,12 +188,7 @@ impl GotenbergClient {
|
||||
|
||||
self.generate_payment_statement(statement).await?;
|
||||
|
||||
let timeout_ms = env_var("GOTENBERG_TIMEOUT")
|
||||
.map_err(ApiError::Internal)?
|
||||
.parse::<u64>()
|
||||
.wrap_internal_err(
|
||||
"`GOTENBERG_TIMEOUT` is not a valid number of milliseconds",
|
||||
)?;
|
||||
let timeout_ms = ENV.GOTENBERG_TIMEOUT;
|
||||
|
||||
let [_key, document] = tokio::time::timeout(
|
||||
Duration::from_millis(timeout_ms),
|
||||
|
||||
@@ -1,47 +1,33 @@
|
||||
use actix_web::guard::GuardContext;
|
||||
use actix_web::http::header::X_FORWARDED_FOR;
|
||||
|
||||
use crate::env::ENV;
|
||||
|
||||
pub const ADMIN_KEY_HEADER: &str = "Modrinth-Admin";
|
||||
pub const MEDAL_KEY_HEADER: &str = "X-Medal-Access-Key";
|
||||
pub const EXTERNAL_NOTIFICATION_KEY_HEADER: &str = "External-Notification-Key";
|
||||
|
||||
pub fn admin_key_guard(ctx: &GuardContext) -> bool {
|
||||
let admin_key = std::env::var("LABRINTH_ADMIN_KEY").expect(
|
||||
"No admin key provided, this should have been caught by check_env_vars",
|
||||
);
|
||||
ctx.head()
|
||||
.headers()
|
||||
.get(ADMIN_KEY_HEADER)
|
||||
.is_some_and(|it| it.as_bytes() == admin_key.as_bytes())
|
||||
.is_some_and(|it| it.as_bytes() == ENV.LABRINTH_ADMIN_KEY.as_bytes())
|
||||
}
|
||||
|
||||
pub fn medal_key_guard(ctx: &GuardContext) -> bool {
|
||||
let maybe_medal_key = dotenvy::var("LABRINTH_MEDAL_KEY").ok();
|
||||
|
||||
match maybe_medal_key {
|
||||
None => false,
|
||||
Some(medal_key) => ctx
|
||||
.head()
|
||||
.headers()
|
||||
.get(MEDAL_KEY_HEADER)
|
||||
.is_some_and(|it| it.as_bytes() == medal_key.as_bytes()),
|
||||
}
|
||||
ctx.head()
|
||||
.headers()
|
||||
.get(MEDAL_KEY_HEADER)
|
||||
.is_some_and(|it| it.as_bytes() == ENV.LABRINTH_MEDAL_KEY.as_bytes())
|
||||
}
|
||||
|
||||
pub fn external_notification_key_guard(ctx: &GuardContext) -> bool {
|
||||
let maybe_external_notification_key =
|
||||
dotenvy::var("LABRINTH_EXTERNAL_NOTIFICATION_KEY").ok();
|
||||
|
||||
match maybe_external_notification_key {
|
||||
None => false,
|
||||
Some(external_notification_key) => ctx
|
||||
.head()
|
||||
.headers()
|
||||
.get(EXTERNAL_NOTIFICATION_KEY_HEADER)
|
||||
.is_some_and(|it| {
|
||||
it.as_bytes() == external_notification_key.as_bytes()
|
||||
}),
|
||||
}
|
||||
ctx.head()
|
||||
.headers()
|
||||
.get(EXTERNAL_NOTIFICATION_KEY_HEADER)
|
||||
.is_some_and(|it| {
|
||||
it.as_bytes() == ENV.LABRINTH_EXTERNAL_NOTIFICATION_KEY.as_bytes()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn internal_network_guard(ctx: &GuardContext) -> bool {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::database::models::image_item;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::database::{self, PgTransaction};
|
||||
use crate::env::ENV;
|
||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||
use crate::models::images::ImageContext;
|
||||
use crate::routes::ApiError;
|
||||
@@ -59,7 +60,7 @@ pub async fn upload_image_optimized(
|
||||
))
|
||||
})?;
|
||||
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
let cdn_url = &ENV.CDN_URL;
|
||||
|
||||
let hash = sha1::Sha1::digest(&bytes).encode_hex::<String>();
|
||||
let (processed_image, processed_image_ext) = process_image(
|
||||
@@ -175,7 +176,7 @@ pub async fn delete_old_images(
|
||||
publicity: FileHostPublicity,
|
||||
file_host: &dyn FileHost,
|
||||
) -> Result<(), ApiError> {
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
let cdn_url = &ENV.CDN_URL;
|
||||
let cdn_url_start = format!("{cdn_url}/");
|
||||
if let Some(image_url) = image_url {
|
||||
let name = image_url.split(&cdn_url_start).nth(1);
|
||||
|
||||
@@ -6,7 +6,6 @@ pub mod bitflag;
|
||||
pub mod captcha;
|
||||
pub mod cors;
|
||||
pub mod date;
|
||||
pub mod env;
|
||||
pub mod error;
|
||||
pub mod ext;
|
||||
pub mod gotenberg;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::env::parse_var;
|
||||
use crate::{database::redis::RedisPool, env::ENV};
|
||||
use actix_web::{
|
||||
Error, ResponseError,
|
||||
body::{EitherBody, MessageBody},
|
||||
@@ -134,14 +133,13 @@ pub async fn rate_limit_middleware(
|
||||
.clone();
|
||||
|
||||
if let Some(key) = req.headers().get("x-ratelimit-key")
|
||||
&& key.to_str().ok()
|
||||
== dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok().as_deref()
|
||||
&& key.to_str().ok() == Some(&ENV.RATE_LIMIT_IGNORE_KEY)
|
||||
{
|
||||
return Ok(next.call(req).await?.map_into_left_body());
|
||||
}
|
||||
|
||||
let conn_info = req.connection_info().clone();
|
||||
let ip = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
||||
let ip = if ENV.CLOUDFLARE_INTEGRATION {
|
||||
if let Some(header) = req.headers().get("CF-Connecting-IP") {
|
||||
header.to_str().ok()
|
||||
} else {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::database::PgPool;
|
||||
use crate::database::models::legacy_loader_fields::MinecraftGameVersion;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::ProjectId;
|
||||
use crate::routes::ApiError;
|
||||
use crate::{database::PgPool, env::ENV};
|
||||
use ariadne::ids::base62_impl::to_base62;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
@@ -69,7 +69,7 @@ async fn get_webhook_metadata(
|
||||
name: organization.name,
|
||||
url: format!(
|
||||
"{}/organization/{}",
|
||||
dotenvy::var("SITE_URL").unwrap_or_default(),
|
||||
ENV.SITE_URL,
|
||||
to_base62(organization.id.0 as u64)
|
||||
),
|
||||
icon_url: organization.icon_url,
|
||||
@@ -95,7 +95,7 @@ async fn get_webhook_metadata(
|
||||
owner = Some(WebhookAuthor {
|
||||
url: format!(
|
||||
"{}/user/{}",
|
||||
dotenvy::var("SITE_URL").unwrap_or_default(),
|
||||
ENV.SITE_URL,
|
||||
to_base62(user.id.0 as u64)
|
||||
),
|
||||
name: user.username,
|
||||
@@ -142,7 +142,7 @@ async fn get_webhook_metadata(
|
||||
Ok(Some(WebhookMetadata {
|
||||
project_url: format!(
|
||||
"{}/{}/{}",
|
||||
dotenvy::var("SITE_URL").unwrap_or_default(),
|
||||
ENV.SITE_URL,
|
||||
project_type,
|
||||
to_base62(project.inner.id.0 as u64)
|
||||
),
|
||||
@@ -251,7 +251,7 @@ pub async fn send_slack_project_webhook(
|
||||
project_id: ProjectId,
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
webhook_url: String,
|
||||
webhook_url: &str,
|
||||
message: Option<String>,
|
||||
) -> Result<(), ApiError> {
|
||||
let metadata = get_webhook_metadata(project_id, pool, redis).await?;
|
||||
@@ -350,7 +350,7 @@ pub async fn send_slack_project_webhook(
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
client
|
||||
.post(&webhook_url)
|
||||
.post(webhook_url)
|
||||
.json(&serde_json::json!({
|
||||
"blocks": blocks,
|
||||
}))
|
||||
@@ -422,7 +422,7 @@ pub async fn send_discord_webhook(
|
||||
project_id: ProjectId,
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
webhook_url: String,
|
||||
webhook_url: &str,
|
||||
message: Option<String>,
|
||||
) -> Result<(), ApiError> {
|
||||
let metadata = get_webhook_metadata(project_id, pool, redis).await?;
|
||||
@@ -482,7 +482,7 @@ pub async fn send_discord_webhook(
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
client
|
||||
.post(&webhook_url)
|
||||
.post(webhook_url)
|
||||
.json(&DiscordWebhook {
|
||||
avatar_url: Some(
|
||||
"https://cdn.modrinth.com/Modrinth_Dark_Logo.png"
|
||||
|
||||
Reference in New Issue
Block a user