diff --git a/libs/git/http/lfs_routes.rs b/libs/git/http/lfs_routes.rs index ea6cf89..ced9f03 100644 --- a/libs/git/http/lfs_routes.rs +++ b/libs/git/http/lfs_routes.rs @@ -4,6 +4,10 @@ use crate::http::handler::is_valid_lfs_oid; use crate::http::lfs::{BatchRequest, CreateLockRequest, LfsHandler}; use crate::http::utils::get_repo_model; use actix_web::{Error, HttpRequest, HttpResponse, web}; +use base64::Engine; +use models::users::user_token; +use sea_orm::prelude::*; +use sha2::{Digest, Sha256}; use std::path::PathBuf; fn base_url(req: &HttpRequest) -> String { @@ -28,10 +32,19 @@ fn bearer_token(req: &HttpRequest) -> Result { } } +fn hash_token(token: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(token.as_bytes()); + base64::prelude::BASE64_STANDARD.encode(hasher.finalize()) +} + /// Derive the acting user from the authenticated bearer token, not from a /// client-supplied header. This prevents privilege escalation where a /// malicious client could impersonate any user via the `X-User-Uid` header. -fn user_uid(req: &HttpRequest, repo: &models::repos::repo::Model) -> Result { +async fn user_uid( + req: &HttpRequest, + db: &db::database::AppDatabase, +) -> Result { let auth_header = req .headers() .get("authorization") @@ -43,20 +56,26 @@ fn user_uid(req: &HttpRequest, repo: &models::repos::repo::Model) -> Result() - .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid token")) -} + let token_hash = hash_token(token); -fn client_ip(req: &HttpRequest) -> String { - req.connection_info() - .realip_remote_addr() - .unwrap_or("unknown") - .to_string() + let token_model = user_token::Entity::find() + .filter(user_token::Column::TokenHash.eq(&token_hash)) + .filter(user_token::Column::IsRevoked.eq(false)) + .one(db.reader()) + .await + .map_err(|e| actix_web::error::ErrorInternalServerError(e.to_string()))?; + + let token_model = token_model + .ok_or_else(|| actix_web::error::ErrorUnauthorized("Invalid token"))?; + + // Check expiry + if let Some(expires_at) = token_model.expires_at { + if expires_at < chrono::Utc::now() { + return Err(actix_web::error::ErrorUnauthorized("Token expired")); + } + } + + Ok(token_model.user) } pub async fn lfs_batch( @@ -67,13 +86,6 @@ pub async fn lfs_batch( ) -> Result { let (namespace, repo_name) = path.into_inner(); - let ip = client_ip(&req); - if !state.rate_limiter.is_ip_read_allowed(&ip).await { - return Err(actix_web::error::ErrorTooManyRequests( - "Rate limit exceeded", - )); - } - let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); @@ -99,13 +111,6 @@ pub async fn lfs_upload( return Err(actix_web::error::ErrorBadRequest("Invalid OID format")); } - let ip = client_ip(&req); - if !state.rate_limiter.is_ip_write_allowed(&ip).await { - return Err(actix_web::error::ErrorTooManyRequests( - "Rate limit exceeded", - )); - } - let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let token = bearer_token(&req)?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); @@ -129,13 +134,6 @@ pub async fn lfs_download( return Err(actix_web::error::ErrorBadRequest("Invalid OID format")); } - let ip = client_ip(&req); - if !state.rate_limiter.is_ip_read_allowed(&ip).await { - return Err(actix_web::error::ErrorTooManyRequests( - "Rate limit exceeded", - )); - } - let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; let token = bearer_token(&req)?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); @@ -156,15 +154,8 @@ pub async fn lfs_lock_create( ) -> Result { let (namespace, repo_name) = path.into_inner(); - let ip = client_ip(&req); - if !state.rate_limiter.is_ip_write_allowed(&ip).await { - return Err(actix_web::error::ErrorTooManyRequests( - "Rate limit exceeded", - )); - } - let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; - let uid = user_uid(&req, &repo)?; + let uid = user_uid(&req, &state.db).await?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); match handler.lock_object(&body.oid, uid).await { @@ -215,15 +206,8 @@ pub async fn lfs_lock_delete( ) -> Result { let (namespace, repo_name, lock_id) = path.into_inner(); - let ip = client_ip(&req); - if !state.rate_limiter.is_ip_write_allowed(&ip).await { - return Err(actix_web::error::ErrorTooManyRequests( - "Rate limit exceeded", - )); - } - let repo = get_repo_model(&namespace, &repo_name, &state.db).await?; - let uid = user_uid(&req, &repo)?; + let uid = user_uid(&req, &state.db).await?; let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone()); match handler.unlock_object(&lock_id, uid).await {