Sessions Route + Password Auth (#649)

* Sessions Route + Password Auth

* run prep + fix clippy

* changing passwords + logging in

* register login
This commit is contained in:
Geometrically
2023-07-08 14:29:17 -07:00
committed by GitHub
parent ef9c90a43a
commit 6c0ad7fe1a
39 changed files with 1777 additions and 1206 deletions

View File

@@ -2,6 +2,7 @@ use crate::auth::{get_user_from_headers, is_authorized_version};
use crate::database::models::project_item::QueryProject;
use crate::database::models::version_item::{QueryFile, QueryVersion};
use crate::models::projects::{ProjectId, VersionId};
use crate::queue::session::SessionQueue;
use crate::routes::ApiError;
use crate::{auth::is_authorized, database};
use actix_web::{get, route, web, HttpRequest, HttpResponse};
@@ -67,6 +68,7 @@ pub async fn maven_metadata(
params: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let project_id = params.into_inner().0;
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
@@ -77,7 +79,7 @@ pub async fn maven_metadata(
return Ok(HttpResponse::NotFound().body(""));
};
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -188,6 +190,7 @@ pub async fn version_file(
params: web::Path<(String, String, String)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let (project_id, vnum, file) = params.into_inner();
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
@@ -198,7 +201,7 @@ pub async fn version_file(
return Ok(HttpResponse::NotFound().body(""));
};
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -271,6 +274,7 @@ pub async fn version_file_sha1(
params: web::Path<(String, String, String)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let (project_id, vnum, file) = params.into_inner();
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
@@ -281,7 +285,7 @@ pub async fn version_file_sha1(
return Ok(HttpResponse::NotFound().body(""));
};
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -328,6 +332,7 @@ pub async fn version_file_sha512(
params: web::Path<(String, String, String)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let (project_id, vnum, file) = params.into_inner();
let project_data = database::models::Project::get(&project_id, &**pool, &redis).await?;
@@ -338,7 +343,7 @@ pub async fn version_file_sha512(
return Ok(HttpResponse::NotFound().body(""));
};
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();

View File

@@ -61,11 +61,17 @@ pub enum ApiError {
#[error("Payments Error: {0}")]
Payments(String),
#[error("Discord Error: {0}")]
DiscordError(String),
Discord(String),
#[error("Captcha Error. Try resubmitting the form.")]
Turnstile,
#[error("Error while decoding Base62: {0}")]
Decoding(#[from] crate::models::ids::DecodingError),
#[error("Image Parsing Error: {0}")]
ImageError(#[from] image::ImageError),
ImageParse(#[from] image::ImageError),
#[error("Password Hashing Error: {0}")]
PasswordHashing(#[from] argon2::password_hash::Error),
#[error("Password strength checking error: {0}")]
PasswordStrengthCheck(#[from] zxcvbn::ZxcvbnError),
}
impl actix_web::ResponseError for ApiError {
@@ -85,9 +91,12 @@ impl actix_web::ResponseError for ApiError {
ApiError::Validation(..) => StatusCode::BAD_REQUEST,
ApiError::Analytics(..) => StatusCode::FAILED_DEPENDENCY,
ApiError::Payments(..) => StatusCode::FAILED_DEPENDENCY,
ApiError::DiscordError(..) => StatusCode::FAILED_DEPENDENCY,
ApiError::Discord(..) => StatusCode::FAILED_DEPENDENCY,
ApiError::Turnstile => StatusCode::BAD_REQUEST,
ApiError::Decoding(..) => StatusCode::BAD_REQUEST,
ApiError::ImageError(..) => StatusCode::BAD_REQUEST,
ApiError::ImageParse(..) => StatusCode::BAD_REQUEST,
ApiError::PasswordHashing(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::PasswordStrengthCheck(..) => StatusCode::BAD_REQUEST,
}
}
@@ -108,9 +117,12 @@ impl actix_web::ResponseError for ApiError {
ApiError::Validation(..) => "invalid_input",
ApiError::Analytics(..) => "analytics_error",
ApiError::Payments(..) => "payments_error",
ApiError::DiscordError(..) => "discord_error",
ApiError::Discord(..) => "discord_error",
ApiError::Turnstile => "turnstile_error",
ApiError::Decoding(..) => "decoding_error",
ApiError::ImageError(..) => "invalid_image",
ApiError::ImageParse(..) => "invalid_image",
ApiError::PasswordHashing(..) => "password_hashing_error",
ApiError::PasswordStrengthCheck(..) => "strength_check_error",
},
description: &self.to_string(),
})

View File

@@ -7,6 +7,7 @@ use sqlx::PgPool;
use crate::auth::{filter_authorized_versions, get_user_from_headers, is_authorized};
use crate::database;
use crate::models::projects::VersionType;
use crate::queue::session::SessionQueue;
use super::ApiError;
@@ -20,6 +21,7 @@ pub async fn forge_updates(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
const ERROR: &str = "The specified project does not exist!";
@@ -29,7 +31,7 @@ pub async fn forge_updates(
.await?
.ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();

View File

@@ -11,7 +11,6 @@ use serde::Deserialize;
use serde_json::json;
use sqlx::PgPool;
use std::collections::HashMap;
use std::sync::Arc;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
@@ -36,7 +35,7 @@ pub struct DownloadBody {
pub async fn count_download(
pool: web::Data<PgPool>,
download_body: web::Json<DownloadBody>,
download_queue: web::Data<Arc<DownloadQueue>>,
download_queue: web::Data<DownloadQueue>,
) -> Result<HttpResponse, ApiError> {
let project_id: crate::database::models::ids::ProjectId = download_body.project_id.into();

View File

@@ -20,26 +20,20 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(
actix_web::web::scope("v2")
.configure(admin::config)
.configure(crate::auth::config)
.configure(crate::auth::session::config)
.configure(crate::auth::flows::config)
.configure(moderation::config)
.configure(notifications::config)
.configure(pats::config)
//.configure(pats::config)
.configure(project_creation::config)
// SHOULD CACHE
.configure(projects::config)
.configure(reports::config)
// should cache in future
.configure(statistics::config)
// should cache in future
.configure(tags::config)
// should cache
.configure(teams::config)
.configure(threads::config)
// should cache
.configure(users::config)
// should cache in future
.configure(version_file::config)
// SHOULD CACHE
.configure(versions::config),
);
}

View File

@@ -2,6 +2,7 @@ use super::ApiError;
use crate::auth::check_is_moderator_from_headers;
use crate::database;
use crate::models::projects::ProjectStatus;
use crate::queue::session::SessionQueue;
use actix_web::{get, web, HttpRequest, HttpResponse};
use serde::Deserialize;
use sqlx::PgPool;
@@ -26,8 +27,9 @@ pub async fn get_projects(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
count: web::Query<ResultCount>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
check_is_moderator_from_headers(req.headers(), &**pool, &redis).await?;
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
use futures::stream::TryStreamExt;

View File

@@ -2,6 +2,7 @@ use crate::auth::get_user_from_headers;
use crate::database;
use crate::models::ids::NotificationId;
use crate::models::notifications::Notification;
use crate::queue::session::SessionQueue;
use crate::routes::ApiError;
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
@@ -31,8 +32,9 @@ pub async fn notifications_get(
web::Query(ids): web::Query<NotificationIds>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
use database::models::notification_item::Notification as DBNotification;
use database::models::NotificationId as DBNotificationId;
@@ -62,8 +64,9 @@ pub async fn notification_get(
info: web::Path<(NotificationId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id = info.into_inner().0;
@@ -87,8 +90,9 @@ pub async fn notification_read(
info: web::Path<(NotificationId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id = info.into_inner().0;
@@ -121,8 +125,9 @@ pub async fn notification_delete(
info: web::Path<(NotificationId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id = info.into_inner().0;
@@ -155,8 +160,9 @@ pub async fn notifications_read(
web::Query(ids): web::Query<NotificationIds>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
.into_iter()
@@ -191,8 +197,9 @@ pub async fn notifications_delete(
web::Query(ids): web::Query<NotificationIds>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
.into_iter()

View File

@@ -1,244 +1,249 @@
/*!
Current edition of Ory kratos does not support PAT access of data, so this module is how we allow for PAT authentication.
Just as a summary: Don't implement this flow in your application!
*/
use crate::database;
use crate::database::models::generate_pat_id;
use crate::models::ids::base62_impl::{parse_base62, to_base62};
use crate::auth::get_user_from_headers;
use crate::auth::{generate_pat, PersonalAccessToken};
use crate::models::users::UserId;
use crate::routes::ApiError;
use actix_web::web::{self, Data, Query};
use actix_web::{delete, get, patch, post, HttpRequest, HttpResponse};
use chrono::{Duration, Utc};
use serde::Deserialize;
use sqlx::postgres::PgPool;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(get_pats);
cfg.service(create_pat);
cfg.service(edit_pat);
cfg.service(delete_pat);
}
#[derive(Deserialize)]
pub struct CreatePersonalAccessToken {
pub scope: i64, // todo: should be a vec of enum
pub name: Option<String>,
pub expire_in_days: i64, // resets expiry to expire_in_days days from now
}
#[derive(Deserialize)]
pub struct ModifyPersonalAccessToken {
#[serde(default, with = "::serde_with::rust::double_option")]
pub name: Option<Option<String>>,
pub expire_in_days: Option<i64>, // resets expiry to expire_in_days days from now
}
// GET /pat
// Get all personal access tokens for the given user. Minos/Kratos cookie must be attached for it to work.
// Does not return the actual access token, only the ID + metadata.
#[get("pat")]
pub async fn get_pats(
req: HttpRequest,
pool: Data<PgPool>,
redis: Data<deadpool_redis::Pool>,
) -> Result<HttpResponse, ApiError> {
let user: crate::models::users::User =
get_user_from_headers(req.headers(), &**pool, &redis).await?;
let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
let pats = sqlx::query!(
"
SELECT id, name, user_id, scope, expires_at
FROM pats
WHERE user_id = $1
",
db_user_id.0
)
.fetch_all(&**pool)
.await?;
let pats = pats
.into_iter()
.map(|pat| PersonalAccessToken {
id: to_base62(pat.id as u64),
scope: pat.scope,
name: pat.name,
expires_at: pat.expires_at,
access_token: None,
user_id: UserId(pat.user_id as u64),
})
.collect::<Vec<_>>();
Ok(HttpResponse::Ok().json(pats))
}
// POST /pat
// Create a new personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
// All PAT tokens are base62 encoded, and are prefixed with "modrinth_pat_"
#[post("pat")]
pub async fn create_pat(
req: HttpRequest,
Query(info): Query<CreatePersonalAccessToken>, // callback url
pool: Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
) -> Result<HttpResponse, ApiError> {
let user: crate::models::users::User =
get_user_from_headers(req.headers(), &**pool, &redis).await?;
let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
let mut transaction: sqlx::Transaction<sqlx::Postgres> = pool.begin().await?;
let pat = generate_pat_id(&mut transaction).await?;
let access_token = generate_pat(&mut transaction).await?;
let expiry = Utc::now().naive_utc() + Duration::days(info.expire_in_days);
if info.expire_in_days <= 0 {
return Err(ApiError::InvalidInput(
"'expire_in_days' must be greater than 0".to_string(),
));
}
sqlx::query!(
"
INSERT INTO pats (id, name, access_token, user_id, scope, expires_at)
VALUES ($1, $2, $3, $4, $5, $6)
",
pat.0,
info.name,
access_token,
db_user_id.0,
info.scope,
expiry
)
.execute(&mut *transaction)
.await?;
transaction.commit().await?;
Ok(HttpResponse::Ok().json(PersonalAccessToken {
id: to_base62(pat.0 as u64),
access_token: Some(access_token),
name: info.name,
scope: info.scope,
user_id: user.id,
expires_at: expiry,
}))
}
// PATCH /pat/(id)
// Edit an access token of id "id" for the given user.
// 'None' will mean not edited. Minos/Kratos cookie or PAT must be attached for it to work.
#[patch("pat/{id}")]
pub async fn edit_pat(
req: HttpRequest,
id: web::Path<String>,
Query(info): Query<ModifyPersonalAccessToken>, // callback url
pool: Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
) -> Result<HttpResponse, ApiError> {
let user: crate::models::users::User =
get_user_from_headers(req.headers(), &**pool, &redis).await?;
let pat_id = database::models::PatId(parse_base62(&id)? as i64);
let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
if let Some(expire_in_days) = info.expire_in_days {
if expire_in_days <= 0 {
return Err(ApiError::InvalidInput(
"'expire_in_days' must be greater than 0".to_string(),
));
}
}
// Get the singular PAT and user combination (failing immediately if it doesn't exist)
let mut transaction = pool.begin().await?;
let row = sqlx::query!(
"
SELECT id, name, scope, user_id, expires_at FROM pats
WHERE id = $1 AND user_id = $2
",
pat_id.0,
db_user_id.0 // included for safety
)
.fetch_one(&**pool)
.await?;
let pat = PersonalAccessToken {
id: to_base62(row.id as u64),
access_token: None,
user_id: UserId::from(db_user_id),
name: info.name.unwrap_or(row.name),
scope: row.scope,
expires_at: info
.expire_in_days
.map(|d| Utc::now().naive_utc() + Duration::days(d))
.unwrap_or(row.expires_at),
};
sqlx::query!(
"
UPDATE pats SET
name = $1,
expires_at = $2
WHERE id = $3
",
pat.name,
pat.expires_at,
parse_base62(&pat.id)? as i64
)
.execute(&mut *transaction)
.await?;
transaction.commit().await?;
Ok(HttpResponse::Ok().json(pat))
}
// DELETE /pat
// Delete a personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
#[delete("pat/{id}")]
pub async fn delete_pat(
req: HttpRequest,
id: web::Path<String>,
pool: Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
) -> Result<HttpResponse, ApiError> {
let user: crate::models::users::User =
get_user_from_headers(req.headers(), &**pool, &redis).await?;
let pat_id = database::models::PatId(parse_base62(&id)? as i64);
let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
// Get the singular PAT and user combination (failing immediately if it doesn't exist)
// This is to prevent users from deleting other users' PATs
let pat_id = sqlx::query!(
"
SELECT id FROM pats
WHERE id = $1 AND user_id = $2
",
pat_id.0,
db_user_id.0
)
.fetch_one(&**pool)
.await?
.id;
let mut transaction = pool.begin().await?;
sqlx::query!(
"
DELETE FROM pats
WHERE id = $1
",
pat_id,
)
.execute(&mut *transaction)
.await?;
transaction.commit().await?;
Ok(HttpResponse::Ok().finish())
}
// /*!
// Current edition of Ory kratos does not support PAT access of data, so this module is how we allow for PAT authentication.
//
//
// Just as a summary: Don't implement this flow in your application!
// */
//
// use crate::database;
// use crate::database::models::generate_pat_id;
// use crate::models::ids::base62_impl::{parse_base62, to_base62};
//
// use crate::auth::get_user_from_headers;
// use crate::auth::{generate_pat, PersonalAccessToken};
// use crate::models::users::UserId;
// use crate::routes::ApiError;
//
// use actix_web::web::{self, Data, Query};
// use actix_web::{delete, get, patch, post, HttpRequest, HttpResponse};
// use chrono::{Duration, Utc};
//
// use crate::queue::session::SessionQueue;
// use serde::Deserialize;
// use sqlx::postgres::PgPool;
//
// pub fn config(cfg: &mut web::ServiceConfig) {
// cfg.service(get_pats);
// cfg.service(create_pat);
// cfg.service(edit_pat);
// cfg.service(delete_pat);
// }
//
// #[derive(Deserialize)]
// pub struct CreatePersonalAccessToken {
// pub scope: i64, // todo: should be a vec of enum
// pub name: Option<String>,
// pub expire_in_days: i64, // resets expiry to expire_in_days days from now
// }
//
// #[derive(Deserialize)]
// pub struct ModifyPersonalAccessToken {
// #[serde(default, with = "::serde_with::rust::double_option")]
// pub name: Option<Option<String>>,
// pub expire_in_days: Option<i64>, // resets expiry to expire_in_days days from now
// }
//
// // GET /pat
// // Get all personal access tokens for the given user. Minos/Kratos cookie must be attached for it to work.
// // Does not return the actual access token, only the ID + metadata.
// #[get("pat")]
// pub async fn get_pats(
// req: HttpRequest,
// pool: Data<PgPool>,
// redis: Data<deadpool_redis::Pool>,
// session_queue: web::Data<SessionQueue>,
// ) -> Result<HttpResponse, ApiError> {
// let user: crate::models::users::User =
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
//
// let pats = sqlx::query!(
// "
// SELECT id, name, user_id, scope, expires_at
// FROM pats
// WHERE user_id = $1
// ",
// db_user_id.0
// )
// .fetch_all(&**pool)
// .await?;
//
// let pats = pats
// .into_iter()
// .map(|pat| PersonalAccessToken {
// id: to_base62(pat.id as u64),
// scope: pat.scope,
// name: pat.name,
// expires_at: pat.expires_at,
// access_token: None,
// user_id: UserId(pat.user_id as u64),
// })
// .collect::<Vec<_>>();
//
// Ok(HttpResponse::Ok().json(pats))
// }
//
// // POST /pat
// // Create a new personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
// // All PAT tokens are base62 encoded, and are prefixed with "modrinth_pat_"
// #[post("pat")]
// pub async fn create_pat(
// req: HttpRequest,
// Query(info): Query<CreatePersonalAccessToken>, // callback url
// pool: Data<PgPool>,
// redis: web::Data<deadpool_redis::Pool>,
// session_queue: web::Data<SessionQueue>,
// ) -> Result<HttpResponse, ApiError> {
// let user: crate::models::users::User =
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
//
// let mut transaction: sqlx::Transaction<sqlx::Postgres> = pool.begin().await?;
//
// let pat = generate_pat_id(&mut transaction).await?;
// let access_token = generate_pat(&mut transaction).await?;
// let expiry = Utc::now().naive_utc() + Duration::days(info.expire_in_days);
// if info.expire_in_days <= 0 {
// return Err(ApiError::InvalidInput(
// "'expire_in_days' must be greater than 0".to_string(),
// ));
// }
//
// sqlx::query!(
// "
// INSERT INTO pats (id, name, access_token, user_id, scope, expires_at)
// VALUES ($1, $2, $3, $4, $5, $6)
// ",
// pat.0,
// info.name,
// access_token,
// db_user_id.0,
// info.scope,
// expiry
// )
// .execute(&mut *transaction)
// .await?;
//
// transaction.commit().await?;
//
// Ok(HttpResponse::Ok().json(PersonalAccessToken {
// id: to_base62(pat.0 as u64),
// access_token: Some(access_token),
// name: info.name,
// scope: info.scope,
// user_id: user.id,
// expires_at: expiry,
// }))
// }
//
// // PATCH /pat/(id)
// // Edit an access token of id "id" for the given user.
// // 'None' will mean not edited. Minos/Kratos cookie or PAT must be attached for it to work.
// #[patch("pat/{id}")]
// pub async fn edit_pat(
// req: HttpRequest,
// id: web::Path<String>,
// Query(info): Query<ModifyPersonalAccessToken>, // callback url
// pool: Data<PgPool>,
// redis: web::Data<deadpool_redis::Pool>,
// session_queue: web::Data<SessionQueue>,
// ) -> Result<HttpResponse, ApiError> {
// let user: crate::models::users::User =
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
// let pat_id = database::models::PatId(parse_base62(&id)? as i64);
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
//
// if let Some(expire_in_days) = info.expire_in_days {
// if expire_in_days <= 0 {
// return Err(ApiError::InvalidInput(
// "'expire_in_days' must be greater than 0".to_string(),
// ));
// }
// }
//
// // Get the singular PAT and user combination (failing immediately if it doesn't exist)
// let mut transaction = pool.begin().await?;
// let row = sqlx::query!(
// "
// SELECT id, name, scope, user_id, expires_at FROM pats
// WHERE id = $1 AND user_id = $2
// ",
// pat_id.0,
// db_user_id.0 // included for safety
// )
// .fetch_one(&**pool)
// .await?;
//
// let pat = PersonalAccessToken {
// id: to_base62(row.id as u64),
// access_token: None,
// user_id: UserId::from(db_user_id),
// name: info.name.unwrap_or(row.name),
// scope: row.scope,
// expires_at: info
// .expire_in_days
// .map(|d| Utc::now().naive_utc() + Duration::days(d))
// .unwrap_or(row.expires_at),
// };
//
// sqlx::query!(
// "
// UPDATE pats SET
// name = $1,
// expires_at = $2
// WHERE id = $3
// ",
// pat.name,
// pat.expires_at,
// parse_base62(&pat.id)? as i64
// )
// .execute(&mut *transaction)
// .await?;
// transaction.commit().await?;
//
// Ok(HttpResponse::Ok().json(pat))
// }
//
// // DELETE /pat
// // Delete a personal access token for the given user. Minos/Kratos cookie must be attached for it to work.
// #[delete("pat/{id}")]
// pub async fn delete_pat(
// req: HttpRequest,
// id: web::Path<String>,
// pool: Data<PgPool>,
// redis: web::Data<deadpool_redis::Pool>,
// session_queue: web::Data<SessionQueue>,
// ) -> Result<HttpResponse, ApiError> {
// let user: crate::models::users::User =
// get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
// let pat_id = database::models::PatId(parse_base62(&id)? as i64);
// let db_user_id: database::models::UserId = database::models::UserId::from(user.id);
//
// // Get the singular PAT and user combination (failing immediately if it doesn't exist)
// // This is to prevent users from deleting other users' PATs
// let pat_id = sqlx::query!(
// "
// SELECT id FROM pats
// WHERE id = $1 AND user_id = $2
// ",
// pat_id.0,
// db_user_id.0
// )
// .fetch_one(&**pool)
// .await?
// .id;
//
// let mut transaction = pool.begin().await?;
// sqlx::query!(
// "
// DELETE FROM pats
// WHERE id = $1
// ",
// pat_id,
// )
// .execute(&mut *transaction)
// .await?;
// transaction.commit().await?;
//
// Ok(HttpResponse::Ok().finish())
// }

View File

@@ -10,6 +10,7 @@ use crate::models::projects::{
};
use crate::models::threads::ThreadType;
use crate::models::users::UserId;
use crate::queue::session::SessionQueue;
use crate::search::indexing::IndexingError;
use crate::util::routes::read_from_field;
use crate::util::validate::validation_errors_to_string;
@@ -272,6 +273,7 @@ pub async fn project_create(
client: Data<PgPool>,
redis: Data<deadpool_redis::Pool>,
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: Data<SessionQueue>,
) -> Result<HttpResponse, CreateError> {
let mut transaction = client.begin().await?;
let mut uploaded_files = Vec::new();
@@ -284,6 +286,7 @@ pub async fn project_create(
&mut uploaded_files,
&client,
&redis,
&session_queue,
)
.await;
@@ -331,6 +334,7 @@ Get logged in user
- Add project data to indexing queue
*/
#[allow(clippy::too_many_arguments)]
async fn project_create_inner(
req: HttpRequest,
payload: &mut Multipart,
@@ -339,12 +343,13 @@ async fn project_create_inner(
uploaded_files: &mut Vec<UploadedFile>,
pool: &PgPool,
redis: &deadpool_redis::Pool,
session_queue: &SessionQueue,
) -> Result<HttpResponse, CreateError> {
// The base URL for files uploaded to backblaze
let cdn_url = dotenvy::var("CDN_URL")?;
// The currently logged in user
let current_user = get_user_from_headers(req.headers(), pool, redis).await?;
let current_user = get_user_from_headers(&req, pool, redis, session_queue).await?;
let project_id: ProjectId = models::generate_project_id(transaction).await?.into();

View File

@@ -11,6 +11,7 @@ use crate::models::projects::{
};
use crate::models::teams::Permissions;
use crate::models::threads::MessageBody;
use crate::queue::session::SessionQueue;
use crate::routes::ApiError;
use crate::search::{search_for_project, SearchConfig, SearchError};
use crate::util::routes::read_from_payload;
@@ -115,11 +116,12 @@ pub async fn projects_get(
web::Query(ids): web::Query<ProjectIds>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let ids = serde_json::from_str::<Vec<&str>>(&ids.ids)?;
let projects_data = database::models::Project::get_many(&ids, &**pool, &redis).await?;
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -134,12 +136,13 @@ pub async fn project_get(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0;
let project_data = database::models::Project::get(&string, &**pool, &redis).await?;
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -183,12 +186,13 @@ pub async fn dependency_list(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0;
let result = database::models::Project::get(&string, &**pool, &redis).await?;
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -353,8 +357,9 @@ pub async fn project_edit(
config: web::Data<SearchConfig>,
new_project: web::Json<EditProject>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
new_project
.validate()
@@ -1176,8 +1181,9 @@ pub async fn projects_edit(
pool: web::Data<PgPool>,
bulk_edit_project: web::Json<BulkEditProject>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
bulk_edit_project
.validate()
@@ -1218,7 +1224,7 @@ pub async fn projects_edit(
if !user.role.is_mod() {
if let Some(member) = team_members
.iter()
.find(|x| x.team_id == project.inner.team_id && x.user.id == user.id.into())
.find(|x| x.team_id == project.inner.team_id && x.user_id == user.id.into())
{
if !member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthentication(format!(
@@ -1505,9 +1511,10 @@ pub async fn project_schedule(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
scheduling_data: web::Json<SchedulingData>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
if scheduling_data.time < Utc::now() {
return Err(ApiError::InvalidInput(
@@ -1575,6 +1582,7 @@ pub struct Extension {
}
#[patch("{id}/icon")]
#[allow(clippy::too_many_arguments)]
pub async fn project_icon_edit(
web::Query(ext): web::Query<Extension>,
req: HttpRequest,
@@ -1583,10 +1591,11 @@ pub async fn project_icon_edit(
redis: web::Data<deadpool_redis::Pool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
mut payload: web::Payload,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
let cdn_url = dotenvy::var("CDN_URL")?;
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string = info.into_inner().0;
let project_item = database::models::Project::get(&string, &**pool, &redis)
@@ -1678,8 +1687,9 @@ pub async fn delete_project_icon(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string = info.into_inner().0;
let project_item = database::models::Project::get(&string, &**pool, &redis)
@@ -1763,13 +1773,14 @@ pub async fn add_gallery_item(
redis: web::Data<deadpool_redis::Pool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
mut payload: web::Payload,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
item.validate()
.map_err(|err| ApiError::Validation(validation_errors_to_string(err, None)))?;
let cdn_url = dotenvy::var("CDN_URL")?;
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string = info.into_inner().0;
let project_item = database::models::Project::get(&string, &**pool, &redis)
@@ -1904,8 +1915,9 @@ pub async fn edit_gallery_item(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string = info.into_inner().0;
item.validate()
@@ -2049,8 +2061,9 @@ pub async fn delete_gallery_item(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string = info.into_inner().0;
let project_item = database::models::Project::get(&string, &**pool, &redis)
@@ -2135,8 +2148,9 @@ pub async fn project_delete(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
config: web::Data<SearchConfig>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string = info.into_inner().0;
let project = database::models::Project::get(&string, &**pool, &redis)
@@ -2189,8 +2203,9 @@ pub async fn project_follow(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string = info.into_inner().0;
let result = database::models::Project::get(&string, &**pool, &redis)
@@ -2259,8 +2274,9 @@ pub async fn project_unfollow(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string = info.into_inner().0;
let result = database::models::Project::get(&string, &**pool, &redis)

View File

@@ -3,6 +3,7 @@ use crate::database::models::thread_item::{ThreadBuilder, ThreadMessageBuilder};
use crate::models::ids::{base62_impl::parse_base62, ProjectId, UserId, VersionId};
use crate::models::reports::{ItemType, Report};
use crate::models::threads::{MessageBody, ThreadType};
use crate::queue::session::SessionQueue;
use crate::routes::ApiError;
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
use chrono::Utc;
@@ -34,10 +35,11 @@ pub async fn report_create(
pool: web::Data<PgPool>,
mut body: web::Payload,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let mut transaction = pool.begin().await?;
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let mut bytes = web::BytesMut::new();
while let Some(item) = body.next().await {
@@ -180,8 +182,9 @@ pub async fn reports(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
count: web::Query<ReportsRequestOptions>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
use futures::stream::TryStreamExt;
@@ -245,6 +248,7 @@ pub async fn reports_get(
web::Query(ids): web::Query<ReportIds>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let report_ids: Vec<crate::database::models::ids::ReportId> =
serde_json::from_str::<Vec<crate::models::ids::ReportId>>(&ids.ids)?
@@ -255,7 +259,7 @@ pub async fn reports_get(
let reports_data =
crate::database::models::report_item::Report::get_many(&report_ids, &**pool).await?;
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let all_reports = reports_data
.into_iter()
@@ -272,8 +276,9 @@ pub async fn report_get(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
info: web::Path<(crate::models::reports::ReportId,)>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id = info.into_inner().0.into();
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
@@ -303,9 +308,10 @@ pub async fn report_edit(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
info: web::Path<(crate::models::reports::ReportId,)>,
session_queue: web::Data<SessionQueue>,
edit_report: web::Json<EditReport>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id = info.into_inner().0.into();
let report = crate::database::models::report_item::Report::get(id, &**pool).await?;
@@ -379,8 +385,9 @@ pub async fn report_delete(
pool: web::Data<PgPool>,
info: web::Path<(crate::models::reports::ReportId,)>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
check_is_moderator_from_headers(req.headers(), &**pool, &redis).await?;
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
let mut transaction = pool.begin().await?;
let result = crate::database::models::report_item::Report::remove_full(

View File

@@ -5,6 +5,7 @@ use crate::models::ids::ProjectId;
use crate::models::notifications::NotificationBody;
use crate::models::teams::{Permissions, TeamId};
use crate::models::users::UserId;
use crate::queue::session::SessionQueue;
use crate::routes::ApiError;
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
use rust_decimal::Decimal;
@@ -31,47 +32,53 @@ pub async fn team_members_get_project(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0;
let project_data = crate::database::models::Project::get(&string, &**pool, &redis).await?;
if let Some(project) = project_data {
let current_user = get_user_from_headers(req.headers(), &**pool, &redis)
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
let members_data =
TeamMember::get_from_team_full(project.inner.team_id, &**pool, &redis).await?;
if !is_authorized(&project.inner, &current_user, &pool).await? {
return Ok(HttpResponse::NotFound().body(""));
}
if let Some(user) = &current_user {
let team_member = members_data
.iter()
.find(|x| x.user.id == user.id.into() && x.accepted);
let members_data =
TeamMember::get_from_team_full(project.inner.team_id, &**pool, &redis).await?;
let users = crate::database::models::User::get_many_ids(
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
&**pool,
&redis,
)
.await?;
if team_member.is_some() {
let team_members: Vec<_> = members_data
.into_iter()
.map(|data| crate::models::teams::TeamMember::from(data, false))
.collect();
let user_id = current_user.as_ref().map(|x| x.id.into());
return Ok(HttpResponse::Ok().json(team_members));
}
}
let logged_in = current_user
.and_then(|user| {
members_data
.iter()
.find(|x| x.user_id == user.id.into() && x.accepted)
})
.is_some();
let user_id = current_user.map(|x| x.id.into());
let team_members: Vec<_> = members_data
.into_iter()
.filter(|x| {
x.accepted
logged_in
|| x.accepted
|| user_id
.map(|y: crate::database::models::UserId| y == x.user.id)
.map(|y: crate::database::models::UserId| y == x.user_id)
.unwrap_or(false)
})
.map(|data| crate::models::teams::TeamMember::from(data, true))
.flat_map(|data| {
users.iter().find(|x| x.id == data.user_id).map(|user| {
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
})
})
.collect();
Ok(HttpResponse::Ok().json(team_members))
@@ -86,39 +93,45 @@ pub async fn team_members_get(
info: web::Path<(TeamId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner().0;
let members_data = TeamMember::get_from_team_full(id.into(), &**pool, &redis).await?;
let users = crate::database::models::User::get_many_ids(
&members_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
&**pool,
&redis,
)
.await?;
let current_user = get_user_from_headers(req.headers(), &**pool, &redis)
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
let user_id = current_user.as_ref().map(|x| x.id.into());
if let Some(user) = &current_user {
let team_member = members_data
.iter()
.find(|x| x.user.id == user.id.into() && x.accepted);
let logged_in = current_user
.and_then(|user| {
members_data
.iter()
.find(|x| x.user_id == user.id.into() && x.accepted)
})
.is_some();
if team_member.is_some() {
let team_members: Vec<_> = members_data
.into_iter()
.map(|data| crate::models::teams::TeamMember::from(data, false))
.collect();
return Ok(HttpResponse::Ok().json(team_members));
}
}
let user_id = current_user.map(|x| x.id.into());
let team_members: Vec<_> = members_data
.into_iter()
.filter(|x| {
x.accepted
logged_in
|| x.accepted
|| user_id
.map(|y: crate::database::models::UserId| y == x.user.id)
.map(|y: crate::database::models::UserId| y == x.user_id)
.unwrap_or(false)
})
.map(|data| crate::models::teams::TeamMember::from(data, true))
.flat_map(|data| {
users
.iter()
.find(|x| x.id == data.user_id)
.map(|user| crate::models::teams::TeamMember::from(data, user.clone(), !logged_in))
})
.collect();
Ok(HttpResponse::Ok().json(team_members))
@@ -135,6 +148,7 @@ pub async fn teams_get(
web::Query(ids): web::Query<TeamIds>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
use itertools::Itertools;
@@ -144,8 +158,14 @@ pub async fn teams_get(
.collect::<Vec<crate::database::models::ids::TeamId>>();
let teams_data = TeamMember::get_from_team_full_many(&team_ids, &**pool, &redis).await?;
let users = crate::database::models::User::get_many_ids(
&teams_data.iter().map(|x| x.user_id).collect::<Vec<_>>(),
&**pool,
&redis,
)
.await?;
let current_user = get_user_from_headers(req.headers(), &**pool, &redis)
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -156,28 +176,23 @@ pub async fn teams_get(
for (_, member_data) in &teams_groups {
let members = member_data.collect::<Vec<_>>();
let team_member = if let Some(user) = &current_user {
members
.iter()
.find(|x| x.user.id == user.id.into() && x.accepted)
} else {
None
};
if team_member.is_some() {
let team_members = members
.into_iter()
.map(|data| crate::models::teams::TeamMember::from(data, false));
teams.push(team_members.collect());
continue;
}
let logged_in = current_user
.as_ref()
.and_then(|user| {
members
.iter()
.find(|x| x.user_id == user.id.into() && x.accepted)
})
.is_some();
let team_members = members
.into_iter()
.filter(|x| x.accepted)
.map(|data| crate::models::teams::TeamMember::from(data, true));
.filter(|x| logged_in || x.accepted)
.flat_map(|data| {
users.iter().find(|x| x.id == data.user_id).map(|user| {
crate::models::teams::TeamMember::from(data, user.clone(), !logged_in)
})
});
teams.push(team_members.collect());
}
@@ -191,9 +206,10 @@ pub async fn join_team(
info: web::Path<(TeamId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let team_id = info.into_inner().0.into();
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let member =
TeamMember::get_from_user_id_pending(team_id, current_user.id.into(), &**pool).await?;
@@ -259,12 +275,13 @@ pub async fn add_team_member(
pool: web::Data<PgPool>,
new_member: web::Json<NewTeamMember>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let team_id = info.into_inner().0.into();
let mut transaction = pool.begin().await?;
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let member = TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
.await?
.ok_or_else(|| {
@@ -373,12 +390,13 @@ pub async fn edit_team_member(
pool: web::Data<PgPool>,
edit_member: web::Json<EditTeamMember>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let ids = info.into_inner();
let id = ids.0.into();
let user_id = ids.1.into();
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
.await?
.ok_or_else(|| {
@@ -463,10 +481,11 @@ pub async fn transfer_ownership(
pool: web::Data<PgPool>,
new_owner: web::Json<TransferOwnership>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner().0;
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
if !current_user.role.is_admin() {
let member = TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool)
@@ -535,12 +554,13 @@ pub async fn remove_team_member(
info: web::Path<(TeamId, UserId)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let ids = info.into_inner();
let id = ids.0.into();
let user_id = ids.1.into();
let current_user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let current_user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let member = TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
.await?
.ok_or_else(|| {

View File

@@ -7,6 +7,7 @@ use crate::models::notifications::NotificationBody;
use crate::models::projects::ProjectStatus;
use crate::models::threads::{MessageBody, Thread, ThreadId, ThreadType};
use crate::models::users::User;
use crate::queue::session::SessionQueue;
use crate::routes::ApiError;
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
use futures::TryStreamExt;
@@ -211,12 +212,13 @@ pub async fn thread_get(
info: web::Path<(ThreadId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0.into();
let thread_data = database::models::Thread::get(string, &**pool).await?;
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
if let Some(mut data) = thread_data {
if is_authorized_thread(&data, &user, &pool).await? {
@@ -253,8 +255,9 @@ pub async fn threads_get(
web::Query(ids): web::Query<ThreadIds>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let thread_ids: Vec<database::models::ids::ThreadId> =
serde_json::from_str::<Vec<ThreadId>>(&ids.ids)?
@@ -281,8 +284,9 @@ pub async fn thread_send_message(
pool: web::Data<PgPool>,
new_message: web::Json<NewThreadMessage>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let string: database::models::ThreadId = info.into_inner().0.into();
@@ -370,7 +374,7 @@ pub async fn thread_send_message(
},
}
.insert_many(
members.into_iter().map(|x| x.user.id).collect(),
members.into_iter().map(|x| x.user_id).collect(),
&mut transaction,
)
.await?;
@@ -442,8 +446,9 @@ pub async fn moderation_inbox(
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = check_is_moderator_from_headers(req.headers(), &**pool, &redis).await?;
let user = check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
let ids = sqlx::query!(
"
@@ -469,8 +474,9 @@ pub async fn thread_read(
info: web::Path<(ThreadId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
check_is_moderator_from_headers(req.headers(), &**pool, &redis).await?;
check_is_moderator_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id = info.into_inner().0;
let mut transaction = pool.begin().await?;
@@ -497,8 +503,9 @@ pub async fn message_delete(
info: web::Path<(ThreadMessageId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let result = database::models::ThreadMessage::get(info.into_inner().0.into(), &**pool).await?;

View File

@@ -5,12 +5,17 @@ use crate::models::notifications::Notification;
use crate::models::projects::Project;
use crate::models::users::{Badges, RecipientType, RecipientWallet, Role, UserId};
use crate::queue::payouts::{PayoutAmount, PayoutItem, PayoutsQueue};
use crate::queue::session::SessionQueue;
use crate::routes::ApiError;
use crate::util::routes::read_from_payload;
use crate::util::validate::validation_errors_to_string;
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use chrono::{DateTime, Utc};
use lazy_static::lazy_static;
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha20Rng;
use regex::Regex;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
@@ -44,8 +49,12 @@ pub async fn user_auth_get(
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
Ok(HttpResponse::Ok().json(get_user_from_headers(req.headers(), &**pool, &redis).await?))
Ok(
HttpResponse::Ok()
.json(get_user_from_headers(&req, &**pool, &redis, &session_queue).await?),
)
}
#[derive(Serialize)]
@@ -59,8 +68,9 @@ pub async fn user_data_get(
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let data = sqlx::query!(
"
@@ -128,8 +138,9 @@ pub async fn projects_list(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis)
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -189,6 +200,7 @@ pub struct EditUser {
)]
#[validate]
pub payout_data: Option<Option<EditPayoutData>>,
pub password: Option<(Option<String>, Option<String>)>,
}
#[derive(Serialize, Deserialize, Validate)]
@@ -206,8 +218,9 @@ pub async fn user_edit(
new_user: web::Json<EditUser>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
new_user
.validate()
@@ -387,6 +400,78 @@ pub async fn user_edit(
}
}
if let Some((old_password, new_password)) = &new_user.password {
if let Some(pass) = actual_user.password {
let old_password = old_password.as_ref().ok_or_else(|| {
ApiError::CustomAuthentication(
"You must specify the old password to change your password!"
.to_string(),
)
})?;
let hasher = Argon2::default();
hasher.verify_password(old_password.as_bytes(), &PasswordHash::new(&pass)?)?;
}
let update_password = if let Some(new_password) = new_password {
let score = zxcvbn::zxcvbn(
new_password,
&[
&actual_user.username,
&actual_user.email.unwrap_or_default(),
&actual_user.name.unwrap_or_default(),
],
)?;
if score.score() < 3 {
return Err(ApiError::InvalidInput(
if let Some(feedback) =
score.feedback().clone().and_then(|x| x.warning())
{
format!("Password too weak: {}", feedback)
} else {
"Specified password is too weak! Please improve its strength."
.to_string()
},
));
}
let hasher = Argon2::default();
let salt = SaltString::generate(&mut ChaCha20Rng::from_entropy());
let password_hash = hasher
.hash_password(new_password.as_bytes(), &salt)?
.to_string();
Some(password_hash)
} else {
if !(actual_user.github_id.is_some()
|| actual_user.gitlab_id.is_some()
|| actual_user.microsoft_id.is_some()
|| actual_user.google_id.is_some()
|| actual_user.steam_id.is_some()
|| actual_user.discord_id.is_some())
{
return Err(ApiError::InvalidInput(
"You must have another authentication method added to remove password authentication!".to_string(),
));
}
None
};
sqlx::query!(
"
UPDATE users
SET password = $1
WHERE (id = $2)
",
update_password,
id as crate::database::models::ids::UserId,
)
.execute(&mut *transaction)
.await?;
}
User::clear_caches(&[(id, Some(actual_user.username))], &redis).await?;
transaction.commit().await?;
Ok(HttpResponse::NoContent().body(""))
@@ -406,6 +491,7 @@ pub struct Extension {
}
#[patch("{id}/icon")]
#[allow(clippy::too_many_arguments)]
pub async fn user_icon_edit(
web::Query(ext): web::Query<Extension>,
req: HttpRequest,
@@ -414,10 +500,11 @@ pub async fn user_icon_edit(
redis: web::Data<deadpool_redis::Pool>,
file_host: web::Data<Arc<dyn FileHost + Send + Sync>>,
mut payload: web::Payload,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
if let Some(content_type) = crate::util::ext::get_image_content_type(&ext.ext) {
let cdn_url = dotenvy::var("CDN_URL")?;
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
if let Some(actual_user) = id_option {
@@ -492,8 +579,9 @@ pub async fn user_delete(
pool: web::Data<PgPool>,
removal_type: web::Query<RemovalType>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
if let Some(id) = id_option.map(|x| x.id) {
@@ -531,8 +619,9 @@ pub async fn user_follows(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
if let Some(id) = id_option.map(|x| x.id) {
@@ -578,8 +667,9 @@ pub async fn user_notifications(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
if let Some(id) = id_option.map(|x| x.id) {
@@ -617,8 +707,9 @@ pub async fn user_payouts(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
if let Some(id) = id_option.map(|x| x.id) {
@@ -691,12 +782,13 @@ pub async fn user_payouts_request(
info: web::Path<(String,)>,
pool: web::Data<PgPool>,
data: web::Json<PayoutData>,
payouts_queue: web::Data<Arc<Mutex<PayoutsQueue>>>,
payouts_queue: web::Data<Mutex<PayoutsQueue>>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let mut payouts_queue = payouts_queue.lock().await;
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id_option = User::get(&info.into_inner().0, &**pool, &redis).await?;
if let Some(id) = id_option.map(|x| x.id) {

View File

@@ -13,6 +13,7 @@ use crate::models::projects::{
VersionId, VersionStatus, VersionType,
};
use crate::models::teams::Permissions;
use crate::queue::session::SessionQueue;
use crate::util::routes::read_from_field;
use crate::util::validate::validation_errors_to_string;
use crate::validate::{validate_file, ValidationResult};
@@ -84,6 +85,7 @@ pub async fn version_create(
client: Data<PgPool>,
redis: Data<deadpool_redis::Pool>,
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: Data<SessionQueue>,
) -> Result<HttpResponse, CreateError> {
let mut transaction = client.begin().await?;
let mut uploaded_files = Vec::new();
@@ -96,6 +98,7 @@ pub async fn version_create(
&***file_host,
&mut uploaded_files,
&client,
&session_queue,
)
.await;
@@ -115,6 +118,7 @@ pub async fn version_create(
result
}
#[allow(clippy::too_many_arguments)]
async fn version_create_inner(
req: HttpRequest,
payload: &mut Multipart,
@@ -123,6 +127,7 @@ async fn version_create_inner(
file_host: &dyn FileHost,
uploaded_files: &mut Vec<UploadedFile>,
pool: &PgPool,
session_queue: &SessionQueue,
) -> Result<HttpResponse, CreateError> {
let cdn_url = dotenvy::var("CDN_URL")?;
@@ -132,7 +137,7 @@ async fn version_create_inner(
let all_game_versions = models::categories::GameVersion::list(&mut *transaction).await?;
let all_loaders = models::categories::Loader::list(&mut *transaction).await?;
let user = get_user_from_headers(req.headers(), pool, redis).await?;
let user = get_user_from_headers(&req, pool, redis, session_queue).await?;
let mut error = None;
while let Some(item) = payload.next().await {
@@ -434,8 +439,9 @@ pub async fn upload_file_to_version(
url_data: web::Path<(VersionId,)>,
mut payload: Multipart,
client: Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
redis: Data<deadpool_redis::Pool>,
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, CreateError> {
let mut transaction = client.begin().await?;
let mut uploaded_files = Vec::new();
@@ -451,6 +457,7 @@ pub async fn upload_file_to_version(
&***file_host,
&mut uploaded_files,
version_id,
&session_queue,
)
.await;
@@ -480,13 +487,14 @@ async fn upload_file_to_version_inner(
file_host: &dyn FileHost,
uploaded_files: &mut Vec<UploadedFile>,
version_id: models::VersionId,
session_queue: &SessionQueue,
) -> Result<HttpResponse, CreateError> {
let cdn_url = dotenvy::var("CDN_URL")?;
let mut initial_file_data: Option<InitialFileData> = None;
let mut file_builders: Vec<VersionFileBuilder> = Vec::new();
let user = get_user_from_headers(req.headers(), &**client, &redis).await?;
let user = get_user_from_headers(&req, &**client, &redis, session_queue).await?;
let result = models::Version::get(version_id, &**client, &redis).await?;

View File

@@ -6,6 +6,7 @@ use crate::auth::{
use crate::models::ids::VersionId;
use crate::models::projects::VersionType;
use crate::models::teams::Permissions;
use crate::queue::session::SessionQueue;
use crate::{database, models};
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
use itertools::Itertools;
@@ -48,8 +49,9 @@ pub async fn get_version_from_hash(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
hash_query: web::Query<HashQuery>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -93,8 +95,9 @@ pub async fn download_version(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
hash_query: web::Query<HashQuery>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -135,8 +138,9 @@ pub async fn delete_file(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
hash_query: web::Query<HashQuery>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let hash = info.into_inner().0.to_lowercase();
@@ -230,8 +234,9 @@ pub async fn get_update_from_hash(
redis: web::Data<deadpool_redis::Pool>,
hash_query: web::Query<HashQuery>,
update_data: web::Json<UpdateData>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
let hash = info.into_inner().0.to_lowercase();
@@ -299,8 +304,9 @@ pub async fn get_versions_from_hashes(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
file_data: web::Json<FileHashes>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -339,8 +345,9 @@ pub async fn get_projects_from_hashes(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
file_data: web::Json<FileHashes>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -389,8 +396,9 @@ pub async fn update_files(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
update_data: web::Json<ManyUpdateData>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();

View File

@@ -6,6 +6,7 @@ use crate::database;
use crate::models;
use crate::models::projects::{Dependency, FileType, VersionStatus, VersionType};
use crate::models::teams::Permissions;
use crate::queue::session::SessionQueue;
use crate::util::validate::validation_errors_to_string;
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
use chrono::{DateTime, Utc};
@@ -44,12 +45,13 @@ pub async fn version_list(
web::Query(filters): web::Query<VersionListFilters>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0;
let result = database::models::Project::get(&string, &**pool, &redis).await?;
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -152,12 +154,13 @@ pub async fn version_project_get(
info: web::Path<(String, String)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner();
let version_data =
database::models::Version::get_full_from_id_slug(&id.0, &id.1, &**pool, &redis).await?;
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -181,6 +184,7 @@ pub async fn versions_get(
web::Query(ids): web::Query<VersionIds>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let version_ids = serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
.into_iter()
@@ -188,7 +192,7 @@ pub async fn versions_get(
.collect::<Vec<database::models::VersionId>>();
let versions_data = database::models::Version::get_many(&version_ids, &**pool, &redis).await?;
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -203,11 +207,12 @@ pub async fn version_get(
info: web::Path<(models::ids::VersionId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let id = info.into_inner().0;
let version_data = database::models::Version::get(id.into(), &**pool, &redis).await?;
let user_option = get_user_from_headers(req.headers(), &**pool, &redis)
let user_option = get_user_from_headers(&req, &**pool, &redis, &session_queue)
.await
.ok();
@@ -263,8 +268,9 @@ pub async fn version_edit(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
new_version: web::Json<EditVersion>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
new_version
.validate()
@@ -639,8 +645,9 @@ pub async fn version_schedule(
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
scheduling_data: web::Json<SchedulingData>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
if scheduling_data.time < Utc::now() {
return Err(ApiError::InvalidInput(
@@ -704,8 +711,9 @@ pub async fn version_delete(
info: web::Path<(models::ids::VersionId,)>,
pool: web::Data<PgPool>,
redis: web::Data<deadpool_redis::Pool>,
session_queue: web::Data<SessionQueue>,
) -> Result<HttpResponse, ApiError> {
let user = get_user_from_headers(req.headers(), &**pool, &redis).await?;
let user = get_user_from_headers(&req, &**pool, &redis, &session_queue).await?;
let id = info.into_inner().0;
let version = database::models::Version::get(id.into(), &**pool, &redis)