Compare commits

...
Author SHA1 Message Date
aecsocket 6bf0d74330 Start reworking how we handle Labrinth state 2025-11-17 23:31:08 +00:00
24 changed files with 311 additions and 311 deletions
Generated
+2
View File
@@ -4523,6 +4523,7 @@ dependencies = [
"const_format",
"dashmap",
"deadpool-redis",
"derive_more 2.0.1",
"dotenv-build",
"dotenvy",
"either",
@@ -4558,6 +4559,7 @@ dependencies = [
"rust_iso3166",
"rustls 0.23.32",
"rusty-money",
"secrecy",
"sentry",
"sentry-actix",
"serde",
+2
View File
@@ -16,10 +16,12 @@ actix-http = { workspace = true, optional = true }
actix-multipart = { workspace = true }
actix-rt = { workspace = true }
actix-web = { workspace = true }
derive_more = { workspace = true, features = ["debug"] }
actix-web-prom = { workspace = true, features = ["process"] }
actix-ws = { workspace = true }
arc-swap = { workspace = true }
argon2 = { workspace = true }
secrecy = { workspace = true }
ariadne = { workspace = true }
async-stripe = { workspace = true, features = [
"billing",
+4 -5
View File
@@ -8,7 +8,10 @@ pub use checks::{
filter_visible_projects,
};
use serde::{Deserialize, Serialize};
pub use validate::{check_is_moderator_from_headers, get_user_from_headers};
pub use validate::{
check_is_moderator_from_headers, get_user_from_headers,
get_user_from_headers_v2,
};
use crate::file_hosting::FileHostingError;
use crate::models::error::ApiError;
@@ -20,8 +23,6 @@ use thiserror::Error;
pub enum AuthenticationError {
#[error(transparent)]
Internal(#[from] eyre::Report),
#[error("Environment Error")]
Env(#[from] dotenvy::Error),
#[error("An unknown database error occurred: {0}")]
Sqlx(#[from] sqlx::Error),
#[error("Database Error: {0}")]
@@ -58,7 +59,6 @@ impl actix_web::ResponseError for AuthenticationError {
AuthenticationError::Internal(..) => {
StatusCode::INTERNAL_SERVER_ERROR
}
AuthenticationError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
AuthenticationError::Sqlx(..) => StatusCode::INTERNAL_SERVER_ERROR,
AuthenticationError::Database(..) => {
StatusCode::INTERNAL_SERVER_ERROR
@@ -93,7 +93,6 @@ impl AuthenticationError {
pub fn error_name(&self) -> &'static str {
match self {
AuthenticationError::Internal(..) => "internal_error",
AuthenticationError::Env(..) => "environment_error",
AuthenticationError::Sqlx(..) => "database_error",
AuthenticationError::Database(..) => "database_error",
AuthenticationError::SerDe(..) => "invalid_input",
+18 -41
View File
@@ -1,6 +1,6 @@
use std::fmt::Write;
use crate::auth::get_user_from_headers;
use crate::auth::get_user_from_headers_v2;
use crate::auth::oauth::uris::{OAuthRedirectUris, ValidatedRedirectUri};
use crate::auth::validate::extract_authorization_header;
use crate::database::models::flow_item::DBFlow;
@@ -12,10 +12,9 @@ use crate::database::models::{
generate_oauth_client_authorization_id,
};
use crate::database::redis::RedisPool;
use crate::models;
use crate::models::ids::OAuthClientId;
use crate::models::pats::Scopes;
use crate::queue::session::AuthQueue;
use crate::{App, models};
use actix_web::http::header::{CACHE_CONTROL, LOCATION, PRAGMA};
use actix_web::web::{Data, Query, ServiceConfig};
use actix_web::{HttpRequest, HttpResponse, get, post, web};
@@ -59,21 +58,16 @@ pub struct OAuthClientAccessRequest {
#[get("authorize")]
pub async fn init_oauth(
app: Data<App>,
req: HttpRequest,
Query(oauth_info): Query<OAuthInit>,
pool: Data<PgPool>,
redis: Data<RedisPool>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, OAuthError> {
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::USER_AUTH_WRITE,
)
.await?
.1;
let user =
get_user_from_headers_v2(&app, &req, &**pool, Scopes::USER_AUTH_WRITE)
.await?
.1;
let client_id = oauth_info.client_id.into();
let client = DBOAuthClient::get(client_id, &**pool).await?;
@@ -172,33 +166,22 @@ pub struct RespondToOAuthClientScopes {
#[post("accept")]
pub async fn accept_client_scopes(
app: Data<App>,
req: HttpRequest,
accept_body: web::Json<RespondToOAuthClientScopes>,
pool: Data<PgPool>,
redis: Data<RedisPool>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, OAuthError> {
accept_or_reject_client_scopes(
true,
req,
accept_body,
pool,
redis,
session_queue,
)
.await
accept_or_reject_client_scopes(true, app, req, accept_body, pool).await
}
#[post("reject")]
pub async fn reject_client_scopes(
app: Data<App>,
req: HttpRequest,
body: web::Json<RespondToOAuthClientScopes>,
pool: Data<PgPool>,
redis: Data<RedisPool>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, OAuthError> {
accept_or_reject_client_scopes(false, req, body, pool, redis, session_queue)
.await
accept_or_reject_client_scopes(false, app, req, body, pool).await
}
#[derive(Serialize, Deserialize)]
@@ -314,26 +297,20 @@ pub async fn request_token(
pub async fn accept_or_reject_client_scopes(
accept: bool,
app: Data<App>,
req: HttpRequest,
body: web::Json<RespondToOAuthClientScopes>,
pool: Data<PgPool>,
redis: Data<RedisPool>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, OAuthError> {
let current_user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::SESSION_ACCESS,
)
.await?
.1;
let current_user =
get_user_from_headers_v2(&app, &req, &**pool, Scopes::SESSION_ACCESS)
.await?
.1;
let flow = DBFlow::take_if(
&body.flow,
|f| matches!(f, DBFlow::InitOAuthAppApproval { .. }),
&redis,
&app.env.redis,
)
.await?;
if let Some(DBFlow::InitOAuthAppApproval {
@@ -379,7 +356,7 @@ pub async fn accept_or_reject_client_scopes(
scopes,
redirect_uris,
state,
&redis,
&app.env.redis,
)
.await
} else {
+60 -55
View File
@@ -1,4 +1,5 @@
use super::AuthProvider;
use crate::App;
use crate::auth::AuthenticationError;
use crate::database::models::{DBUser, user_item};
use crate::database::redis::RedisPool;
@@ -11,10 +12,9 @@ use actix_web::http::header::{AUTHORIZATION, HeaderValue};
use chrono::Utc;
pub async fn get_maybe_user_from_headers<'a, E>(
app: &App,
req: &HttpRequest,
executor: E,
redis: &RedisPool,
session_queue: &AuthQueue,
required_scopes: Scopes,
) -> Result<Option<(Scopes, User)>, AuthenticationError>
where
@@ -25,14 +25,8 @@ where
}
// Fetch DB user record and minos user from headers
let Some((scopes, db_user)) = get_user_record_from_bearer_token(
req,
None,
executor,
redis,
session_queue,
)
.await?
let Some((scopes, db_user)) =
get_user_record_from_bearer_token(app, req, None, executor).await?
else {
return Ok(None);
};
@@ -45,24 +39,18 @@ where
}
pub async fn get_full_user_from_headers<'a, E>(
app: &App,
req: &HttpRequest,
executor: E,
redis: &RedisPool,
session_queue: &AuthQueue,
required_scopes: Scopes,
) -> Result<(Scopes, DBUser), AuthenticationError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let (scopes, db_user) = get_user_record_from_bearer_token(
req,
None,
executor,
redis,
session_queue,
)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
let (scopes, db_user) =
get_user_record_from_bearer_token(app, req, None, executor)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
if !scopes.contains(required_scopes) {
return Err(AuthenticationError::InvalidCredentials);
@@ -74,31 +62,42 @@ where
pub async fn get_user_from_headers<'a, E>(
req: &HttpRequest,
executor: E,
redis: &RedisPool,
session_queue: &AuthQueue,
_redis: &RedisPool,
_session_queue: &AuthQueue,
required_scopes: Scopes,
) -> Result<(Scopes, User), AuthenticationError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let (scopes, db_user) = get_full_user_from_headers(
get_user_from_headers_v2(
crate::APP.get().unwrap(),
req,
executor,
redis,
session_queue,
required_scopes,
)
.await?;
.await
}
pub async fn get_user_from_headers_v2<'a, E>(
app: &App,
req: &HttpRequest,
executor: E,
required_scopes: Scopes,
) -> Result<(Scopes, User), AuthenticationError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let (scopes, db_user) =
get_full_user_from_headers(app, req, executor, required_scopes).await?;
Ok((scopes, User::from_full(db_user)))
}
pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
app: &App,
req: &HttpRequest,
token: Option<&str>,
executor: E,
redis: &RedisPool,
session_queue: &AuthQueue,
) -> Result<Option<(Scopes, user_item::DBUser)>, AuthenticationError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
@@ -113,7 +112,9 @@ where
Some(("mrp", _)) => {
let pat =
crate::database::models::pat_item::DBPersonalAccessToken::get(
token, executor, redis,
token,
executor,
&app.env.redis,
)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
@@ -122,17 +123,23 @@ where
return Err(AuthenticationError::InvalidCredentials);
}
let user =
user_item::DBUser::get_id(pat.user_id, executor, redis).await?;
let user = user_item::DBUser::get_id(
pat.user_id,
executor,
&app.env.redis,
)
.await?;
session_queue.add_pat(pat.id).await;
app.state.auth_queue.add_pat(pat.id).await;
user.map(|x| (pat.scopes, x))
}
Some(("mra", _)) => {
let session =
crate::database::models::session_item::DBSession::get(
token, executor, redis,
token,
executor,
&app.env.redis,
)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
@@ -141,19 +148,21 @@ where
return Err(AuthenticationError::InvalidCredentials);
}
let user =
user_item::DBUser::get_id(session.user_id, executor, redis)
.await?;
let user = user_item::DBUser::get_id(
session.user_id,
executor,
&app.env.redis,
)
.await?;
let rate_limit_ignore = dotenvy::var("RATE_LIMIT_IGNORE_KEY")?;
if req
.headers()
.get("x-ratelimit-key")
.and_then(|x| x.to_str().ok())
.is_none_or(|x| x != rate_limit_ignore)
.is_none_or(|x| x != &app.env.rate_limit_ignore_key)
{
let metadata = get_session_metadata(req).await?;
session_queue.add_session(session.id, metadata).await;
app.state.auth_queue.add_session(session.id, metadata).await;
}
user.map(|x| (Scopes::all(), x))
@@ -174,23 +183,26 @@ where
let user = user_item::DBUser::get_id(
access_token.user_id,
executor,
redis,
&app.env.redis,
)
.await?;
session_queue.add_oauth_access_token(access_token.id).await;
app.state
.auth_queue
.add_oauth_access_token(access_token.id)
.await;
user.map(|u| (access_token.scopes, u))
}
Some(("github" | "gho" | "ghp", _)) => {
let user = AuthProvider::GitHub.get_user(token).await?;
let user = AuthProvider::GitHub.get_user(&app.env, token).await?;
let id =
AuthProvider::GitHub.get_user_id(&user.id, executor).await?;
let user = user_item::DBUser::get_id(
id.ok_or_else(|| AuthenticationError::InvalidCredentials)?,
executor,
redis,
&app.env.redis,
)
.await?;
@@ -219,24 +231,17 @@ pub fn extract_authorization_header(
}
pub async fn check_is_moderator_from_headers<'a, 'b, E>(
app: &App,
req: &HttpRequest,
executor: E,
redis: &RedisPool,
session_queue: &AuthQueue,
required_scopes: Scopes,
) -> Result<User, AuthenticationError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let user = get_user_from_headers(
req,
executor,
redis,
session_queue,
required_scopes,
)
.await?
.1;
let user = get_user_from_headers_v2(app, req, executor, required_scopes)
.await?
.1;
if user.role.is_mod() {
Ok(user)
@@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut};
use std::time::Duration;
use tracing::info;
#[derive(Clone)]
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct ReadOnlyPgPool(PgPool);
+1 -1
View File
@@ -20,7 +20,7 @@ use std::time::Duration;
const DEFAULT_EXPIRY: i64 = 60 * 60 * 12; // 12 hours
const ACTUAL_EXPIRY: i64 = 60 * 30; // 30 minutes
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct RedisPool {
pub url: String,
pub pool: deadpool_redis::Pool,
+59 -4
View File
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use actix_web::web;
@@ -8,7 +8,8 @@ use queue::{
analytics::AnalyticsQueue, email::EmailQueue, payouts::PayoutsQueue,
session::AuthQueue, socket::ActiveSockets,
};
use sqlx::Postgres;
use secrecy::SecretString;
use sqlx::{PgPool, Postgres};
use tracing::{info, warn};
extern crate clickhouse as clickhouse_crate;
@@ -50,6 +51,7 @@ pub struct Pepper {
#[derive(Clone)]
pub struct LabrinthConfig {
pub app: App,
pub pool: sqlx::Pool<Postgres>,
pub ro_pool: ReadOnlyPgPool,
pub redis_pool: RedisPool,
@@ -72,8 +74,52 @@ pub struct LabrinthConfig {
pub gotenberg_client: GotenbergClient,
}
#[derive(Debug, Clone)]
pub struct App {
pub env: AppEnv,
pub state: AppState,
}
#[derive(derive_more::Debug, Clone)]
pub struct AppEnv {
pub postgres: PgPool,
pub postgres_ro: PgPool,
pub redis: RedisPool,
#[debug(skip)]
pub clickhouse: ::clickhouse::Client,
// env vars
pub self_addr: String,
pub rate_limit_ignore_key: String,
pub github_client_id: String,
pub github_client_secret: SecretString,
pub discord_client_id: String,
pub discord_client_secret: SecretString,
pub microsoft_client_id: String,
pub microsoft_client_secret: SecretString,
pub gitlab_client_id: String,
pub gitlab_client_secret: SecretString,
pub google_client_id: String,
pub google_client_secret: SecretString,
pub steam_api_key: SecretString,
pub paypal_api_url: String,
pub paypal_client_id: String,
pub paypal_client_secret: SecretString,
pub sendy_url: String,
pub sendy_list_id: String,
pub sendy_api_key: SecretString,
}
#[derive(Debug, Clone)]
pub struct AppState {
// TODO: fully own the `AuthQueue`
pub auth_queue: web::Data<AuthQueue>,
}
pub static APP: OnceLock<App> = OnceLock::new();
#[allow(clippy::too_many_arguments)]
pub fn app_setup(
env: AppEnv,
pool: sqlx::Pool<Postgres>,
ro_pool: ReadOnlyPgPool,
redis_pool: RedisPool,
@@ -92,6 +138,16 @@ pub fn app_setup(
dotenvy::var("BIND_ADDR").unwrap()
);
let session_queue = web::Data::new(AuthQueue::new());
let state = AppState {
auth_queue: session_queue.clone(),
};
let app = App { env, state };
if APP.set(app.clone()).is_err() {
warn!("Setting `ENV` multiple times");
}
let automated_moderation_queue =
web::Data::new(AutomatedModerationQueue::default());
@@ -207,8 +263,6 @@ pub fn app_setup(
});
}
let session_queue = web::Data::new(AuthQueue::new());
let pool_ref = pool.clone();
let redis_ref = redis_pool.clone();
let session_queue_ref = session_queue.clone();
@@ -270,6 +324,7 @@ pub fn app_setup(
}
LabrinthConfig {
app,
pool,
ro_pool,
redis_pool,
+10 -1
View File
@@ -4,7 +4,6 @@ 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};
@@ -15,7 +14,9 @@ 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::{AppEnv, app_config};
use labrinth::{check_env_vars, clickhouse, database, file_hosting};
use modrinth_util::env_var;
use std::ffi::CStr;
use std::sync::Arc;
use tracing::{Instrument, error, info, info_span};
@@ -198,6 +199,14 @@ async fn main() -> std::io::Result<()> {
.expect("Failed to register jemalloc metrics");
let labrinth_config = labrinth::app_setup(
AppEnv {
self_addr: env_var("SELF_ADDR").unwrap(),
rate_limit_ignore_key: env_var("RATE_LIMIT_IGNORE_KEY").unwrap(),
postgres: pool.clone(),
postgres_ro: ro_pool.clone(),
redis: redis_pool.clone(),
clickhouse: clickhouse.clone(),
},
pool.clone(),
ro_pool.clone(),
redis_pool.clone(),
+1 -1
View File
@@ -9,7 +9,6 @@ use crate::models::payouts::{
use crate::models::projects::MonetizationStatus;
use crate::queue::payouts::mural::MuralPayoutRequest;
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,
@@ -20,6 +19,7 @@ use chrono::{DateTime, Datelike, Duration, NaiveTime, TimeZone, Utc};
use dashmap::DashMap;
use eyre::{Result, eyre};
use futures::TryStreamExt;
use modrinth_util::env_var;
use muralpay::MuralPay;
use reqwest::Method;
use rust_decimal::prelude::ToPrimitive;
+1
View File
@@ -11,6 +11,7 @@ use sqlx::PgPool;
use std::collections::{HashMap, HashSet};
use tokio::sync::Mutex;
#[derive(Debug)]
pub struct AuthQueue {
session_queue: Mutex<HashMap<DBSessionId, SessionMetadata>>,
pat_queue: Mutex<HashSet<DBPatId>>,
+6 -11
View File
@@ -1,4 +1,5 @@
use crate::auth::get_user_from_headers;
use crate::App;
use crate::auth::{get_user_from_headers, get_user_from_headers_v2};
use crate::database::redis::RedisPool;
use crate::models::analytics::{PageView, Playtime};
use crate::models::pats::Scopes;
@@ -49,22 +50,16 @@ pub struct UrlInput {
#[post("view")]
pub async fn page_view_ingest(
req: HttpRequest,
app: web::Data<App>,
maxmind: web::Data<MaxMind>,
analytics_queue: web::Data<Arc<AnalyticsQueue>>,
session_queue: web::Data<AuthQueue>,
url_input: web::Json<UrlInput>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::empty(),
)
.await
.ok();
let user = get_user_from_headers_v2(&app, &req, &**pool, Scopes::empty())
.await
.ok();
let conn_info = req.connection_info().peer_addr().map(|x| x.to_string());
let url = Url::parse(&url_input.url).map_err(|_| {
+6 -13
View File
@@ -1,3 +1,4 @@
use crate::App;
use crate::auth::validate::get_user_record_from_bearer_token;
use crate::database::models::thread_item::ThreadMessageBuilder;
use crate::database::redis::RedisPool;
@@ -7,7 +8,6 @@ use crate::models::pats::Scopes;
use crate::models::threads::MessageBody;
use crate::queue::analytics::AnalyticsQueue;
use crate::queue::moderation::AUTOMOD_ID;
use crate::queue::session::AuthQueue;
use crate::routes::ApiError;
use crate::search::SearchConfig;
use crate::util::date::get_current_tenths_of_ms;
@@ -46,11 +46,10 @@ pub struct DownloadBody {
#[allow(clippy::too_many_arguments)]
pub async fn count_download(
req: HttpRequest,
app: web::Data<App>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
maxmind: web::Data<MaxMind>,
analytics_queue: web::Data<Arc<AnalyticsQueue>>,
session_queue: web::Data<AuthQueue>,
download_body: web::Json<DownloadBody>,
) -> Result<HttpResponse, ApiError> {
let token = download_body
@@ -59,16 +58,10 @@ pub async fn count_download(
.find(|x| x.0.to_lowercase() == "authorization")
.map(|x| &**x.1);
let user = get_user_record_from_bearer_token(
&req,
token,
&**pool,
&redis,
&session_queue,
)
.await
.ok()
.flatten();
let user = get_user_record_from_bearer_token(&app, &req, token, &**pool)
.await
.ok()
.flatten();
let project_id: crate::database::models::ids::DBProjectId =
download_body.project_id.into();
+85 -82
View File
@@ -20,6 +20,7 @@ use crate::util::error::Context;
use crate::util::ext::get_image_ext;
use crate::util::img::upload_image_optimized;
use crate::util::validate::validation_errors_to_string;
use crate::{App, AppEnv};
use actix_web::web::{Data, Query, ServiceConfig, scope};
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
use argon2::password_hash::SaltString;
@@ -33,6 +34,7 @@ use lettre::message::Mailbox;
use rand_chacha::ChaCha20Rng;
use rand_chacha::rand_core::SeedableRng;
use reqwest::header::AUTHORIZATION;
use secrecy::ExposeSecret;
use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPool;
use std::collections::HashMap;
@@ -254,43 +256,44 @@ impl TempUser {
impl AuthProvider {
pub fn get_redirect_url(
&self,
app: &App,
state: String,
) -> Result<String, AuthenticationError> {
let self_addr = dotenvy::var("SELF_ADDR")?;
let self_addr = &app.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 = &app.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 = &app.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 = &app.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 = &app.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 = &app.env.google_client_id;
format!(
"https://accounts.google.com/o/oauth2/v2/auth?client_id={}&state={}&scope={}&response_type=code&redirect_uri={}",
@@ -316,8 +319,8 @@ impl AuthProvider {
)
}
AuthProvider::PayPal => {
let api_url = dotenvy::var("PAYPAL_API_URL")?;
let client_id = dotenvy::var("PAYPAL_CLIENT_ID")?;
let api_url = &app.env.paypal_api_url;
let client_id = &app.env.paypal_client_id;
let auth_url = if api_url.contains("sandbox") {
"sandbox.paypal.com"
@@ -337,10 +340,10 @@ impl AuthProvider {
pub async fn get_token(
&self,
app: &App,
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", &app.env.self_addr);
#[derive(Deserialize)]
struct AccessToken {
@@ -352,8 +355,9 @@ 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 = &app.env.github_client_id;
let client_secret =
&app.env.github_client_secret.expose_secret();
let url = format!(
"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret={client_secret}&code={code}&redirect_uri={redirect_uri}"
@@ -373,11 +377,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 = &app.env.discord_client_id;
let client_secret =
app.env.discord_client_secret.expose_secret();
let mut map = HashMap::new();
map.insert("client_id", &*client_id);
map.insert("client_id", client_id.as_str());
map.insert("client_secret", &*client_secret);
map.insert("code", code);
map.insert("grant_type", "authorization_code");
@@ -398,11 +403,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 = &app.env.microsoft_client_id;
let client_secret =
app.env.microsoft_client_secret.expose_secret();
let mut map = HashMap::new();
map.insert("client_id", &*client_id);
map.insert("client_id", client_id.as_str());
map.insert("client_secret", &*client_secret);
map.insert("code", code);
map.insert("grant_type", "authorization_code");
@@ -423,11 +429,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 = &app.env.gitlab_client_id;
let client_secret =
app.env.gitlab_client_secret.expose_secret();
let mut map = HashMap::new();
map.insert("client_id", &*client_id);
map.insert("client_id", client_id.as_str());
map.insert("client_secret", &*client_secret);
map.insert("code", code);
map.insert("grant_type", "authorization_code");
@@ -448,12 +455,13 @@ 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 = &app.env.google_client_id;
let client_secret =
app.env.google_client_secret.expose_secret();
let mut map = HashMap::new();
map.insert("client_id", &*client_id);
map.insert("client_secret", &*client_secret);
map.insert("client_id", client_id.as_str());
map.insert("client_secret", client_secret);
map.insert("code", code);
map.insert("grant_type", "authorization_code");
map.insert("redirect_uri", &redirect_uri);
@@ -528,9 +536,10 @@ 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 = &app.env.paypal_api_url;
let client_id = &app.env.paypal_client_id;
let client_secret =
&app.env.paypal_client_secret.expose_secret();
let mut map = HashMap::new();
map.insert("code", code.as_str());
@@ -562,6 +571,7 @@ impl AuthProvider {
pub async fn get_user(
&self,
env: &AppEnv,
token: &str,
) -> Result<TempUser, AuthenticationError> {
let res = match self {
@@ -579,9 +589,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) {
return Err(AuthenticationError::InvalidClientId);
}
}
@@ -731,7 +739,7 @@ impl AuthProvider {
}
}
AuthProvider::Steam => {
let api_key = dotenvy::var("STEAM_API_KEY")?;
let api_key = env.steam_api_key.expose_secret();
#[derive(Deserialize)]
struct SteamResponse {
@@ -796,7 +804,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!(
@@ -1063,11 +1071,10 @@ pub struct Authorization {
// http://localhost:8000/auth/init?url=https://modrinth.com
#[get("init")]
pub async fn init(
app: Data<App>,
req: HttpRequest,
Query(info): Query<AuthorizationInit>, // callback url
client: Data<PgPool>,
redis: Data<RedisPool>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, AuthenticationError> {
// If a user is logging into an OAuth method while already logged in,
// this may be present.
@@ -1076,11 +1083,10 @@ pub async fn init(
// logged in.
let existing_user_id = if let Some(auth_token) = &info.auth_token {
get_user_record_from_bearer_token(
&app,
&req,
Some(auth_token),
&**client,
&redis,
&session_queue,
)
.await
.ok()
@@ -1110,11 +1116,10 @@ pub async fn init(
let user_id = if let Some(token) = info.token {
let (_, user) = get_user_record_from_bearer_token(
&app,
&req,
Some(&token),
&**client,
&redis,
&session_queue,
)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
@@ -1130,10 +1135,10 @@ pub async fn init(
provider: info.provider,
existing_user_id,
}
.insert(Duration::minutes(30), &redis)
.insert(Duration::minutes(30), &app.env.redis)
.await?;
let url = info.provider.get_redirect_url(state)?;
let url = info.provider.get_redirect_url(&app, state)?;
Ok(HttpResponse::TemporaryRedirect()
.append_header(("Location", &*url))
.json(serde_json::json!({ "url": url })))
@@ -1142,6 +1147,7 @@ pub async fn init(
#[get("callback")]
pub async fn auth_callback(
req: HttpRequest,
app: Data<App>,
Query(query): Query<HashMap<String, String>>,
client: Data<PgPool>,
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
@@ -1177,11 +1183,11 @@ pub async fn auth_callback(
.wrap_err("failed to remove flow")?;
let token = provider
.get_token(query)
.get_token(&app, query)
.await
.wrap_err("failed to get token from provider")?;
let oauth_user = provider
.get_user(&token)
.get_user(&app.env, &token)
.await
.wrap_err("failed to get user from provider")?;
@@ -1393,11 +1399,12 @@ pub async fn delete_auth_provider(
}
pub async fn check_sendy_subscription(
app: &App,
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 = &app.env.sendy_url;
let id = &app.env.sendy_list_id;
let api_key = app.env.sendy_api_key.expose_secret();
if url.is_empty() || url == "none" {
tracing::info!(
@@ -1934,21 +1941,15 @@ pub struct Remove2FA {
#[delete("2fa")]
pub async fn remove_2fa(
app: Data<App>,
req: HttpRequest,
pool: Data<PgPool>,
redis: Data<RedisPool>,
login: web::Json<Remove2FA>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let (scopes, user) = get_user_record_from_bearer_token(
&req,
None,
&**pool,
&redis,
&session_queue,
)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
let (scopes, user) =
get_user_record_from_bearer_token(&app, &req, None, &**pool)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
if !scopes.contains(Scopes::USER_AUTH_WRITE) {
return Err(ApiError::Authentication(
@@ -1967,7 +1968,7 @@ pub async fn remove_2fa(
})?,
true,
user.id,
&redis,
&app.env.redis,
&pool,
&mut transaction,
)
@@ -2002,12 +2003,15 @@ pub async fn remove_2fa(
NotificationBuilder {
body: NotificationBody::TwoFactorRemoved,
}
.insert(user.id, &mut transaction, &redis)
.insert(user.id, &mut transaction, &app.env.redis)
.await?;
transaction.commit().await?;
crate::database::models::DBUser::clear_caches(&[(user.id, None)], &redis)
.await?;
crate::database::models::DBUser::clear_caches(
&[(user.id, None)],
&app.env.redis,
)
.await?;
Ok(HttpResponse::NoContent().finish())
}
@@ -2116,18 +2120,18 @@ pub struct ChangePassword {
#[patch("password")]
pub async fn change_password(
app: Data<App>,
req: HttpRequest,
pool: Data<PgPool>,
redis: Data<RedisPool>,
change_password: web::Json<ChangePassword>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let user = if let Some(flow) = &change_password.flow {
let flow = DBFlow::get(flow, &redis).await?;
let flow = DBFlow::get(flow, &app.env.redis).await?;
if let Some(DBFlow::ForgotPassword { user_id }) = flow {
let user = crate::database::models::DBUser::get_id(
user_id, &**pool, &redis,
user_id,
&app.env.postgres,
&app.env.redis,
)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
@@ -2146,11 +2150,10 @@ pub async fn change_password(
user
} else {
let (scopes, user) = get_user_record_from_bearer_token(
&app,
&req,
None,
&**pool,
&redis,
&session_queue,
&app.env.postgres,
)
.await?
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
@@ -2178,7 +2181,7 @@ pub async fn change_password(
user
};
let mut transaction = pool.begin().await?;
let mut transaction = app.env.postgres.begin().await?;
let update_password = if let Some(new_password) =
&change_password.new_password
@@ -2236,26 +2239,29 @@ pub async fn change_password(
.await?;
if let Some(flow) = &change_password.flow {
DBFlow::remove(flow, &redis).await?;
DBFlow::remove(flow, &app.env.redis).await?;
}
if update_password.is_some() {
NotificationBuilder {
body: NotificationBody::PasswordChanged,
}
.insert(user.id, &mut transaction, &redis)
.insert(user.id, &mut transaction, &app.env.redis)
.await?;
} else {
NotificationBuilder {
body: NotificationBody::PasswordRemoved,
}
.insert(user.id, &mut transaction, &redis)
.insert(user.id, &mut transaction, &app.env.redis)
.await?;
}
transaction.commit().await?;
crate::database::models::DBUser::clear_caches(&[(user.id, None)], &redis)
.await?;
crate::database::models::DBUser::clear_caches(
&[(user.id, None)],
&app.env.redis,
)
.await?;
Ok(HttpResponse::Ok().finish())
}
@@ -2532,16 +2538,13 @@ pub async fn subscribe_newsletter(
#[get("email/subscribe")]
pub async fn get_newsletter_subscription_status(
app: Data<App>,
req: HttpRequest,
pool: Data<PgPool>,
redis: Data<RedisPool>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_full_user_from_headers(
&app,
&req,
&**pool,
&redis,
&session_queue,
&app.env.postgres,
Scopes::USER_READ,
)
.await?
@@ -2549,7 +2552,7 @@ pub async fn get_newsletter_subscription_status(
let is_subscribed = user.is_subscribed_to_newsletter
|| if let Some(email) = user.email {
check_sendy_subscription(&email).await?
check_sendy_subscription(&app, &email).await?
} else {
false
};
+12 -32
View File
@@ -1,12 +1,11 @@
use super::ApiError;
use crate::database;
use crate::database::models::{DBOrganization, DBTeamId, DBTeamMember, DBUser};
use crate::database::redis::RedisPool;
use crate::models::ids::{OrganizationId, TeamId};
use crate::models::projects::{Project, ProjectStatus};
use crate::queue::moderation::{ApprovalType, IdentifiedFile, MissingMetadata};
use crate::queue::session::AuthQueue;
use crate::util::error::Context;
use crate::{App, database};
use crate::{auth::check_is_moderator_from_headers, models::pats::Scopes};
use actix_web::{HttpRequest, get, post, web};
use ariadne::ids::{UserId, random_base62};
@@ -76,30 +75,24 @@ pub enum Ownership {
)]
#[get("/projects")]
async fn get_projects(
app: web::Data<App>,
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
request_opts: web::Query<ProjectsRequestOptions>,
session_queue: web::Data<AuthQueue>,
) -> Result<web::Json<Vec<FetchedProject>>, ApiError> {
get_projects_internal(req, pool, redis, request_opts, session_queue).await
get_projects_internal(app, req, pool, redis, request_opts).await
}
pub async fn get_projects_internal(
app: web::Data<App>,
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
request_opts: web::Query<ProjectsRequestOptions>,
session_queue: web::Data<AuthQueue>,
) -> Result<web::Json<Vec<FetchedProject>>, ApiError> {
check_is_moderator_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
check_is_moderator_from_headers(&app, &req, &**pool, Scopes::PROJECT_READ)
.await?;
use futures::stream::TryStreamExt;
@@ -207,19 +200,13 @@ pub async fn get_projects_internal(
#[get("/project/{id}")]
async fn get_project_meta(
req: HttpRequest,
app: web::Data<App>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
info: web::Path<(String,)>,
) -> Result<web::Json<MissingMetadata>, ApiError> {
check_is_moderator_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
check_is_moderator_from_headers(&app, &req, &**pool, Scopes::PROJECT_READ)
.await?;
let project_id = info.into_inner().0;
let project =
@@ -361,19 +348,12 @@ pub enum Judgement {
#[post("/project")]
async fn set_project_meta(
req: HttpRequest,
app: web::Data<App>,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
judgements: web::Json<HashMap<String, Judgement>>,
) -> Result<(), ApiError> {
check_is_moderator_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
check_is_moderator_from_headers(&app, &req, &**pool, Scopes::PROJECT_READ)
.await?;
let mut transaction = pool.begin().await?;
@@ -27,6 +27,7 @@ pub fn config(cfg: &mut ServiceConfig) {
);
}
#[derive(Debug)]
pub struct SessionMetadata {
pub city: Option<String>,
pub country: Option<String>,
@@ -1,10 +1,10 @@
use crate::App;
use crate::auth::AuthenticationError;
use crate::auth::validate::get_user_record_from_bearer_token;
use crate::database::models::friend_item::DBFriend;
use crate::database::redis::RedisPool;
use crate::models::pats::Scopes;
use crate::models::users::User;
use crate::queue::session::AuthQueue;
use crate::queue::socket::{
ActiveSocket, ActiveSockets, SocketId, TunnelSocketType,
};
@@ -45,19 +45,18 @@ struct LauncherHeartbeatInit {
#[get("launcher_socket")]
pub async fn ws_init(
req: HttpRequest,
app: Data<App>,
pool: Data<PgPool>,
web::Query(auth): web::Query<LauncherHeartbeatInit>,
body: Payload,
db: Data<ActiveSockets>,
redis: Data<RedisPool>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let (scopes, db_user) = get_user_record_from_bearer_token(
&app,
&req,
Some(&auth.code),
&**pool,
&redis,
&session_queue,
)
.await?
.ok_or_else(|| {
+3 -3
View File
@@ -1,7 +1,7 @@
use super::ApiError;
use crate::App;
use crate::models::projects::Project;
use crate::models::v2::projects::LegacyProject;
use crate::queue::session::AuthQueue;
use crate::routes::internal;
use crate::{database::redis::RedisPool, routes::v2_reroute};
use actix_web::{HttpRequest, HttpResponse, get, web};
@@ -24,13 +24,14 @@ fn default_count() -> u16 {
#[get("projects")]
pub async fn get_projects(
app: web::Data<App>,
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
count: web::Query<ResultCount>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let response = internal::moderation::get_projects_internal(
app,
req,
pool.clone(),
redis.clone(),
@@ -38,7 +39,6 @@ pub async fn get_projects(
count: count.count,
offset: 0,
}),
session_queue,
)
.await
.map(|resp| HttpResponse::Ok().json(resp))
+3 -2
View File
@@ -1,3 +1,4 @@
use crate::App;
use crate::database::redis::RedisPool;
use crate::models::reports::Report;
use crate::models::v2::reports::LegacyReport;
@@ -181,13 +182,13 @@ pub async fn report_edit(
#[delete("report/{id}")]
pub async fn report_delete(
req: HttpRequest,
app: web::Data<App>,
pool: web::Data<PgPool>,
info: web::Path<(crate::models::ids::ReportId,)>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
// Returns NoContent, so no need to convert
v3::reports::report_delete(req, pool, info, redis, session_queue)
v3::reports::report_delete(req, app, pool, info, redis)
.await
.or_else(v2_reroute::flatten_404_error)
}
+19 -25
View File
@@ -1,3 +1,4 @@
use crate::App;
use crate::auth::validate::get_user_record_from_bearer_token;
use crate::auth::{AuthenticationError, get_user_from_headers};
use crate::database::models::payout_item::DBPayout;
@@ -439,25 +440,21 @@ pub struct WithdrawalFees {
#[utoipa::path]
#[post("/fees")]
pub async fn calculate_fees(
app: web::Data<App>,
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
body: web::Json<Withdrawal>,
session_queue: web::Data<AuthQueue>,
payouts_queue: web::Data<PayoutsQueue>,
) -> Result<web::Json<WithdrawalFees>, ApiError> {
// even though we don't use the user, we ensure they're logged in to make API calls
let (_, _user) = get_user_record_from_bearer_token(
&req,
None,
&**pool,
&redis,
&session_queue,
)
.await?
.ok_or_else(|| {
ApiError::Authentication(AuthenticationError::InvalidCredentials)
})?;
let (_, _user) =
get_user_record_from_bearer_token(&app, &req, None, &**pool)
.await?
.ok_or_else(|| {
ApiError::Authentication(
AuthenticationError::InvalidCredentials,
)
})?;
let fees = payouts_queue
.calculate_fees(&body.method, &body.method_id, body.amount)
@@ -472,25 +469,22 @@ pub async fn calculate_fees(
#[utoipa::path]
#[post("")]
pub async fn create_payout(
app: web::Data<App>,
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
body: web::Json<Withdrawal>,
session_queue: web::Data<AuthQueue>,
payouts_queue: web::Data<PayoutsQueue>,
gotenberg: web::Data<GotenbergClient>,
) -> Result<(), ApiError> {
let (scopes, user) = get_user_record_from_bearer_token(
&req,
None,
&**pool,
&redis,
&session_queue,
)
.await?
.ok_or_else(|| {
ApiError::Authentication(AuthenticationError::InvalidCredentials)
})?;
let (scopes, user) =
get_user_record_from_bearer_token(&app, &req, None, &**pool)
.await?
.ok_or_else(|| {
ApiError::Authentication(
AuthenticationError::InvalidCredentials,
)
})?;
if !scopes.contains(Scopes::PAYOUTS_WRITE) {
return Err(ApiError::Authentication(
+4 -10
View File
@@ -1,5 +1,4 @@
use crate::auth::{check_is_moderator_from_headers, get_user_from_headers};
use crate::database;
use crate::database::models::image_item;
use crate::database::models::notification_item::NotificationBuilder;
use crate::database::models::thread_item::{
@@ -17,6 +16,7 @@ use crate::queue::session::AuthQueue;
use crate::routes::ApiError;
use crate::util::img;
use crate::util::routes::read_typed_from_payload;
use crate::{App, database};
use actix_web::{HttpRequest, HttpResponse, web};
use ariadne::ids::UserId;
use ariadne::ids::base62_impl::parse_base62;
@@ -513,19 +513,13 @@ pub async fn report_edit(
pub async fn report_delete(
req: HttpRequest,
app: web::Data<App>,
pool: web::Data<PgPool>,
info: web::Path<(crate::models::ids::ReportId,)>,
redis: web::Data<RedisPool>,
session_queue: web::Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
check_is_moderator_from_headers(
&req,
&**pool,
&redis,
&session_queue,
Scopes::REPORT_DELETE,
)
.await?;
check_is_moderator_from_headers(&app, &req, &**pool, Scopes::REPORT_DELETE)
.await?;
let mut transaction = pool.begin().await?;
@@ -1,3 +1,4 @@
use crate::App;
use crate::auth::get_user_from_headers;
use crate::auth::validate::get_maybe_user_from_headers;
use crate::database::models::shared_instance_item::{
@@ -150,19 +151,18 @@ pub async fn shared_instance_list(
}
pub async fn shared_instance_get(
app: Data<App>,
req: HttpRequest,
pool: Data<PgPool>,
redis: Data<RedisPool>,
info: web::Path<(SharedInstanceId,)>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner().0.into();
let user = get_maybe_user_from_headers(
&app,
&req,
&**pool,
&redis,
&session_queue,
Scopes::SHARED_INSTANCE_READ,
)
.await?
@@ -357,19 +357,18 @@ pub async fn shared_instance_delete(
}
pub async fn shared_instance_version_list(
app: Data<App>,
req: HttpRequest,
pool: Data<PgPool>,
redis: Data<RedisPool>,
info: web::Path<(SharedInstanceId,)>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner().0.into();
let user = get_maybe_user_from_headers(
&app,
&req,
&**pool,
&redis,
&session_queue,
Scopes::SHARED_INSTANCE_READ,
)
.await?
@@ -403,19 +402,18 @@ pub async fn shared_instance_version_list(
}
pub async fn shared_instance_version_get(
app: Data<App>,
req: HttpRequest,
pool: Data<PgPool>,
redis: Data<RedisPool>,
info: web::Path<(SharedInstanceVersionId,)>,
session_queue: Data<AuthQueue>,
) -> Result<HttpResponse, ApiError> {
let version_id = info.into_inner().0.into();
let user = get_maybe_user_from_headers(
&app,
&req,
&**pool,
&redis,
&session_queue,
Scopes::SHARED_INSTANCE_READ,
)
.await?
@@ -562,20 +560,19 @@ async fn delete_instance_version(
}
pub async fn shared_instance_version_download(
app: Data<App>,
req: HttpRequest,
pool: Data<PgPool>,
redis: Data<RedisPool>,
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
info: web::Path<(SharedInstanceVersionId,)>,
session_queue: Data<AuthQueue>,
) -> Result<Redirect, ApiError> {
let version_id = info.into_inner().0.into();
let user = get_maybe_user_from_headers(
&app,
&req,
&**pool,
&redis,
&session_queue,
Scopes::SHARED_INSTANCE_VERSION_READ,
)
.await?
-7
View File
@@ -1,12 +1,5 @@
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())
}
+1 -1
View File
@@ -2,10 +2,10 @@ use crate::database::redis::RedisPool;
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};
use modrinth_util::env_var;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::time::Duration;