mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
* Backend routes for choosing username in OAuth flow * fix up oauth flow routes * improve URL-related OAuth code * Use user-provided callback addr instead of SELF_ADDR * Revert "Use user-provided callback addr instead of SELF_ADDR" This reverts commit7ea0635d86. * fix flow * fix: backend response for create oauth account * feat: new auth flow (#5840) * update auth with new designs * refactor: auth.js to auth.ts * refactor: componentize auth pages * fix: auth pages height * feat: initial implementation of new sign-in oauth * fix create account flow * fix checkbox * remove hard coded username * implement create user validation endpoint and add more specific error responses * feat: implement under 13 DOB guard and email/password validation route * fix: TOCTOU issue * refactor: pnpm prepr * fix: make sure staging uses staging * fix: hcaptcha styles * fix: copy * remove: auth/welcome page as its no longer used * refactor: bring root page card styles into individual components and use tailwind * fix: account settings modals to use new modal and fix lots of bad styles * refactor: pnpm prepr * feat: implement last signed in indicator * fix: append number when generated name from email is taken * refactor: pnpm prepr * fix: last sign in badge color * fix: qa issues * refactor: pnpm prepr * fix: hover effect on native date picker * chore: temp staging undo * Revert "chore: temp staging undo" This reverts commitcad6bd4f92. * feat: handle app create account * fix: last signed in style * fix: add initOnMounted for SSR race * refactor: use typescript * refactor: pnpm prepr * refactor: use typescript for reset-password * refactor: convert verify-email to use typescript * refactor: convert authorize.vue to use typescript * fix: authorize.vue error states * feat: small style updates * feat: implement date picker component * feat: improve UX and styles for range select * refactor: pnpm prepr * fix: range select border styles * feat: implement date picker component in create account * feat: implement preserve date for date picker * update rust toolchain * increase recursion limit * fix: date picker can be null * fix: calculate age based on user's timezone * fix: number input icons color * fix: date picker icons * feat: improve styles * fix: add width on date * fix: hover color bad on number input * fix lints * feat: add default date open view * fmt * fix: account.vue * fix: remove default date to open 13 years ago * fix: edit copy on info banner * fix: cannot hover over project card tooltip items (#6071) fix: cannot hover over project cards * feat: improve add dependency flow (#6075) * fix: shadow on nav * feat: improve add dependency flow * feat: update suggested dependency style * feat: update dependency rows to use version number and update styles * feat: implement combobox select searched text on focus * feat: add Tabs.vue * feat: update nav tabs to use tabs * feat: improve project search dropdown * fix: dependency search not clearing inbound query * fix: combobox no options open state bug * feat: improve dependency project and version search * fix: open modrinth project links in the app (#6072) * pin tanstack versions + set pnpm min age to 7 days * squash commits * fix: 2 factor auth enter code screen styles * update copy * update copy * improve reset password * feat: update sign in screen * fix: unused import * Merge branch 'main' into boris/dev-908-backend-changes * Revert "Merge branch 'main' into boris/dev-908-backend-changes" This reverts commitb9b03796e3. * fix: add stroke * feat: add passkey support (#6375) * feat: add passkey backend * feat: passkey frontend * invalidate sessions on compromised passkey * chore: run sqlx prepare * fix: make passkey button use both collumns to prevent empty space * fix: correctly verify max passkeys in finish route * fix: use structs for response * fix: add rp name default * style: use web::Json * fmt * feat: improve manage passkeys UI * fix copy * pnpm prepr --------- Co-authored-by: tdgao <mr.trumgao@gmail.com> Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: Michael H. <michael@iptables.sh> Co-authored-by: Calum H. (IMB11) <contact@cal.engineer> Co-authored-by: Calum H. <calum@modrinth.com> Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com> Co-authored-by: DeDiamondPro <67508414+DeDiamondPro@users.noreply.github.com>
317 lines
10 KiB
Rust
317 lines
10 KiB
Rust
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<()> {
|
|
dotenvy::dotenv().ok();
|
|
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 = "development";
|
|
SENTRY_TRACES_SAMPLE_RATE: f32 = 0.1f32;
|
|
SITE_URL: String = "http://localhost:3000";
|
|
CDN_URL: String = "file:///tmp/modrinth";
|
|
LABRINTH_ADMIN_KEY: String = "";
|
|
LABRINTH_MEDAL_KEY: String = "";
|
|
LABRINTH_EXTERNAL_NOTIFICATION_KEY: String = "";
|
|
RATE_LIMIT_IGNORE_KEY: String = "";
|
|
DATABASE_URL: String = "postgresql://labrinth:labrinth@localhost/labrinth";
|
|
REDIS_URL: String = "redis://localhost";
|
|
KAFKA_BOOTSTRAP_SERVERS: StringCsv = StringCsv(vec!["localhost:19092".into()]);
|
|
KAFKA_CLIENT_ID: String = "labrinth";
|
|
BIND_ADDR: String = "";
|
|
SELF_ADDR: String = "";
|
|
|
|
LOCAL_INDEX_INTERVAL: u64 = 3600u64;
|
|
VERSION_INDEX_INTERVAL: u64 = 1800u64;
|
|
|
|
WHITELISTED_MODPACK_DOMAINS: Json<Vec<String>> = Json(vec![
|
|
"cdn.modrinth.com".into(),
|
|
"github.com".into(),
|
|
"raw.githubusercontent.com".into(),
|
|
]);
|
|
ALLOWED_CALLBACK_URLS: Json<Vec<String>> = Json(vec![
|
|
"localhost".into(),
|
|
".modrinth.com".into(),
|
|
"127.0.0.1".into(),
|
|
"[::1]".into(),
|
|
]);
|
|
ANALYTICS_ALLOWED_ORIGINS: Json<Vec<String>> = Json(vec![
|
|
"http://127.0.0.1:3000".into(),
|
|
"http://localhost:3000".into(),
|
|
"https://modrinth.com".into(),
|
|
"https://www.modrinth.com".into(),
|
|
"*".into(),
|
|
]);
|
|
|
|
// search
|
|
SEARCH_BACKEND: crate::search::SearchBackendKind = crate::search::SearchBackendKind::Typesense;
|
|
SEARCH_INDEX_CHUNK_SIZE: i64 = 5000i64;
|
|
TYPESENSE_URL: String = "http://localhost:8108";
|
|
TYPESENSE_API_KEY: String = "modrinth";
|
|
TYPESENSE_INDEX_PREFIX: String = "labrinth";
|
|
|
|
// storage
|
|
STORAGE_BACKEND: crate::file_hosting::FileHostKind = crate::file_hosting::FileHostKind::Local;
|
|
FILE_SCAN_CONCURRENCY: i64 = 8i64;
|
|
|
|
// 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 = "/tmp/modrinth";
|
|
|
|
GITHUB_CLIENT_ID: String = "none";
|
|
GITHUB_CLIENT_SECRET: String = "none";
|
|
GITLAB_CLIENT_ID: String = "none";
|
|
GITLAB_CLIENT_SECRET: String = "none";
|
|
DISCORD_CLIENT_ID: String = "none";
|
|
DISCORD_CLIENT_SECRET: String = "none";
|
|
DISCORD_COMMUNITY_BOT_HANDOFF_URL: String = "http://localhost:3000/modrinth/handoff";
|
|
DISCORD_COMMUNITY_LINK_SECRET: String = "";
|
|
MICROSOFT_CLIENT_ID: String = "none";
|
|
MICROSOFT_CLIENT_SECRET: String = "none";
|
|
GOOGLE_CLIENT_ID: String = "none";
|
|
GOOGLE_CLIENT_SECRET: String = "none";
|
|
STEAM_API_KEY: String = "none";
|
|
|
|
TREMENDOUS_API_URL: String = "https://testflight.tremendous.com/api/v2/";
|
|
TREMENDOUS_API_KEY: String = "none";
|
|
TREMENDOUS_PRIVATE_KEY: String = "none";
|
|
|
|
PAYPAL_API_URL: String = "https://api-m.sandbox.paypal.com/v1/";
|
|
PAYPAL_WEBHOOK_ID: String = "none";
|
|
PAYPAL_CLIENT_ID: String = "none";
|
|
PAYPAL_CLIENT_SECRET: String = "none";
|
|
PAYPAL_NVP_USERNAME: String = "none";
|
|
PAYPAL_NVP_PASSWORD: String = "none";
|
|
PAYPAL_NVP_SIGNATURE: String = "none";
|
|
|
|
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 = "none";
|
|
|
|
SMTP_USERNAME: String = "";
|
|
SMTP_PASSWORD: String = "";
|
|
SMTP_HOST: String = "localhost";
|
|
SMTP_PORT: u16 = 1025u16;
|
|
SMTP_TLS: String = "none";
|
|
SMTP_FROM_NAME: String = "Modrinth";
|
|
SMTP_FROM_ADDRESS: String = "no-reply@mail.modrinth.com";
|
|
|
|
SITE_VERIFY_EMAIL_PATH: String = "auth/verify-email";
|
|
SITE_RESET_PASSWORD_PATH: String = "auth/reset-password";
|
|
SITE_BILLING_PATH: String = "none";
|
|
|
|
SENDY_URL: String = "none";
|
|
SENDY_LIST_ID: String = "none";
|
|
SENDY_API_KEY: String = "none";
|
|
|
|
CLICKHOUSE_REPLICATED: bool = false;
|
|
CLICKHOUSE_URL: String = "http://localhost:8123";
|
|
CLICKHOUSE_USER: String = "default";
|
|
CLICKHOUSE_PASSWORD: String = "default";
|
|
CLICKHOUSE_DATABASE: String = "staging_ariadne";
|
|
|
|
FLAME_ANVIL_URL: String = "none";
|
|
|
|
GOTENBERG_URL: String = "http://localhost:13000";
|
|
GOTENBERG_CALLBACK_BASE: String = "http://host.docker.internal:8000/_internal/gotenberg";
|
|
GOTENBERG_TIMEOUT: u64 = 30000u64;
|
|
|
|
STRIPE_API_KEY: String = "none";
|
|
STRIPE_WEBHOOK_SECRET: String = "none";
|
|
|
|
ADITUDE_API_KEY: String = "none";
|
|
|
|
PYRO_API_KEY: String = "none";
|
|
|
|
BREX_API_URL: String = "https://platform.brexapis.com/v2/";
|
|
BREX_API_KEY: String = "none";
|
|
|
|
DELPHI_URL: String = "";
|
|
|
|
AVALARA_1099_API_URL: String = "https://www.track1099.com/api";
|
|
AVALARA_1099_API_KEY: String = "none";
|
|
AVALARA_1099_API_TEAM_ID: String = "none";
|
|
AVALARA_1099_COMPANY_ID: String = "207337084";
|
|
|
|
ANROK_API_URL: String = "";
|
|
ANROK_API_KEY: String = "";
|
|
|
|
PAYOUT_ALERT_SLACK_WEBHOOK: String = "none";
|
|
CLOUDFLARE_INTEGRATION: bool = false;
|
|
|
|
ARCHON_URL: String = "";
|
|
|
|
MURALPAY_API_URL: String = "https://api-staging.muralpay.com";
|
|
MURALPAY_API_KEY: String = "none";
|
|
MURALPAY_TRANSFER_API_KEY: String = "none";
|
|
MURALPAY_SOURCE_ACCOUNT_ID: muralpay::AccountId = muralpay::AccountId(uuid::Uuid::nil());
|
|
|
|
DEFAULT_AFFILIATE_REVENUE_SPLIT: Decimal = Decimal::new(1, 1);
|
|
|
|
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 = "none";
|
|
TILTIFY_CLIENT_ID: String = "";
|
|
TILTIFY_CLIENT_SECRET: String = "";
|
|
TILTIFY_PRIDE_26_CAMPAIGN_ID: String = "";
|
|
TILTIFY_WEBHOOK_SIGNING_KEY: String = "";
|
|
|
|
// server pinging
|
|
SERVER_PING_MAX_CONCURRENT: usize = 16usize;
|
|
SERVER_PING_RETRIES: usize = 3usize;
|
|
SERVER_PING_MIN_INTERVAL_SEC: u64 = 30u64 * 60;
|
|
SERVER_PING_TIMEOUT_MS: u64 = 3u64 * 1000;
|
|
SERVER_PING_MAX_FAIL_COUNT: u64 = 3u64;
|
|
|
|
WEBAUTHN_RP_NAME: String = "Modrinth";
|
|
}
|