223 lines
7.8 KiB
Rust
223 lines
7.8 KiB
Rust
use crate::error::GitError;
|
|
use crate::http::HttpAppState;
|
|
use crate::http::handler::is_valid_oid;
|
|
use crate::http::lfs::{BatchRequest, CreateLockRequest, LfsHandler};
|
|
use crate::http::utils::get_repo_model;
|
|
use actix_web::{Error, HttpRequest, HttpResponse, web};
|
|
use std::path::PathBuf;
|
|
|
|
fn base_url(req: &HttpRequest) -> String {
|
|
let conn_info = req.connection_info();
|
|
format!("{}://{}", conn_info.scheme(), conn_info.host())
|
|
}
|
|
|
|
fn bearer_token(req: &HttpRequest) -> Result<String, Error> {
|
|
let auth_header = req
|
|
.headers()
|
|
.get("authorization")
|
|
.ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing authorization header"))?
|
|
.to_str()
|
|
.map_err(|_| actix_web::error::ErrorUnauthorized("Invalid authorization header"))?;
|
|
|
|
if let Some(token) = auth_header.strip_prefix("Bearer ") {
|
|
Ok(token.to_string())
|
|
} else {
|
|
Err(actix_web::error::ErrorUnauthorized(
|
|
"Invalid authorization format",
|
|
))
|
|
}
|
|
}
|
|
|
|
fn user_uid(req: &HttpRequest, repo: &models::repos::repo::Model) -> Result<uuid::Uuid, Error> {
|
|
if let Some(hv) = req.headers().get("x-user-uid") {
|
|
if let Ok(s) = hv.to_str() {
|
|
if let Ok(uid) = s.parse::<uuid::Uuid>() {
|
|
return Ok(uid);
|
|
}
|
|
}
|
|
}
|
|
Ok(repo.created_by)
|
|
}
|
|
|
|
fn client_ip(req: &HttpRequest) -> String {
|
|
req.connection_info()
|
|
.realip_remote_addr()
|
|
.unwrap_or("unknown")
|
|
.to_string()
|
|
}
|
|
|
|
pub async fn lfs_batch(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
body: web::Json<BatchRequest>,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
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());
|
|
|
|
let response = handler
|
|
.batch(body.into_inner(), &base_url(&req))
|
|
.await
|
|
.map_err(|e| actix_web::error::ErrorInternalServerError(e.to_string()))?;
|
|
|
|
Ok(HttpResponse::Ok()
|
|
.content_type("application/vnd.git-lfs+json")
|
|
.json(response))
|
|
}
|
|
|
|
pub async fn lfs_upload(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String, String)>,
|
|
payload: web::Payload,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let (namespace, repo_name, oid) = path.into_inner();
|
|
|
|
if !is_valid_oid(&oid) {
|
|
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());
|
|
|
|
match handler.upload_object(&oid, payload, &token).await {
|
|
Ok(response) => Ok(response),
|
|
Err(GitError::InvalidOid(_)) => Err(actix_web::error::ErrorBadRequest("Invalid OID")),
|
|
Err(GitError::AuthFailed(_)) => Err(actix_web::error::ErrorUnauthorized("Unauthorized")),
|
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
|
|
}
|
|
}
|
|
|
|
pub async fn lfs_download(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String, String)>,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let (namespace, repo_name, oid) = path.into_inner();
|
|
|
|
if !is_valid_oid(&oid) {
|
|
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());
|
|
|
|
match handler.download_object(&oid, &token).await {
|
|
Ok(response) => Ok(response),
|
|
Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Object not found")),
|
|
Err(GitError::AuthFailed(_)) => Err(actix_web::error::ErrorUnauthorized("Unauthorized")),
|
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
|
|
}
|
|
}
|
|
|
|
pub async fn lfs_lock_create(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
body: web::Json<CreateLockRequest>,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
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 handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());
|
|
|
|
match handler.lock_object(&body.oid, uid).await {
|
|
Ok(lock) => Ok(HttpResponse::Created().json(lock)),
|
|
Err(GitError::Locked(msg)) => Ok(HttpResponse::Conflict().body(msg)),
|
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
|
|
}
|
|
}
|
|
|
|
pub async fn lfs_lock_list(
|
|
_req: HttpRequest,
|
|
path: web::Path<(String, String)>,
|
|
query: web::Query<std::collections::HashMap<String, String>>,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let (namespace, repo_name) = path.into_inner();
|
|
let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;
|
|
|
|
let maybe_oid = query.get("oid").map(|s| s.as_str());
|
|
let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());
|
|
|
|
match handler.list_locks(maybe_oid).await {
|
|
Ok(list) => Ok(HttpResponse::Ok().json(list)),
|
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
|
|
}
|
|
}
|
|
|
|
pub async fn lfs_lock_get(
|
|
_req: HttpRequest,
|
|
path: web::Path<(String, String, String)>,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let (namespace, repo_name, lock_path) = path.into_inner();
|
|
let repo = get_repo_model(&namespace, &repo_name, &state.db).await?;
|
|
let handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());
|
|
|
|
match handler.get_lock(&lock_path).await {
|
|
Ok(lock) => Ok(HttpResponse::Ok().json(lock)),
|
|
Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Lock not found")),
|
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
|
|
}
|
|
}
|
|
|
|
pub async fn lfs_lock_delete(
|
|
req: HttpRequest,
|
|
path: web::Path<(String, String, String)>,
|
|
state: web::Data<HttpAppState>,
|
|
) -> Result<HttpResponse, Error> {
|
|
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 handler = LfsHandler::new(PathBuf::from(&repo.storage_path), repo, state.db.clone());
|
|
|
|
match handler.unlock_object(&lock_id, uid).await {
|
|
Ok(()) => Ok(HttpResponse::NoContent().finish()),
|
|
Err(GitError::PermissionDenied(_)) => Err(actix_web::error::ErrorForbidden("Not allowed")),
|
|
Err(GitError::NotFound(_)) => Err(actix_web::error::ErrorNotFound("Lock not found")),
|
|
Err(e) => Err(actix_web::error::ErrorInternalServerError(e.to_string())),
|
|
}
|
|
}
|