use crate::http::utils::extract_basic_credentials; use crate::ssh::authz::SshAuthService; use actix_web::{Error, HttpRequest}; use argon2::Argon2; use argon2::password_hash::{PasswordHash, PasswordVerifier}; use db::database::AppDatabase; use models::repos::repo; use models::users::{user, user_token}; use sea_orm::sqlx::types::chrono; use sea_orm::*; pub async fn verify_access_token( db: &AppDatabase, username: &str, access_key: &str, ) -> Result { let user = user::Entity::find() .filter(user::Column::Username.eq(username)) .one(db.reader()) .await .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid username or access key"))? .ok_or_else(|| actix_web::error::ErrorUnauthorized("Invalid username or access key"))?; let tokens = user_token::Entity::find() .filter(user_token::Column::User.eq(user.uid)) .filter(user_token::Column::IsRevoked.eq(false)) .all(db.reader()) .await .map_err(|_| actix_web::error::ErrorUnauthorized("Invalid username or access key"))? .into_iter() .filter(|token| { token .expires_at .map(|expires_at| expires_at >= chrono::Utc::now()) .unwrap_or(true) }); for token in tokens { let Ok(hash) = PasswordHash::new(&token.token_hash) else { tracing::warn!(token_id = token.id, "invalid stored access key hash"); continue; }; if Argon2::default() .verify_password(access_key.as_bytes(), &hash) .is_ok() { return Ok(user); } } Err(actix_web::error::ErrorUnauthorized( "Invalid username or access key", )) } pub async fn authorize_repo_access( req: &HttpRequest, db: &AppDatabase, repo: &repo::Model, is_write: bool, ) -> Result<(), Error> { if !is_write && !repo.is_private { return Ok(()); } let (username, access_key) = extract_basic_credentials(req)?; let user = verify_access_token(db, &username, &access_key).await?; let authz = SshAuthService::new(db.clone()); let can_access = authz.check_repo_permission(&user, repo, is_write).await; if !can_access { return Err(actix_web::error::ErrorForbidden( "No permission for repository", )); } Ok(()) }