use actix_web::{Error, HttpRequest}; use argon2::{ Argon2, password_hash::{PasswordHash, PasswordVerifier}, }; use db::database::AppDatabase; use model::{ repos::RepoModel, users::{user::UserModel, user_token::UserTokenModel}, }; use crate::{ http::utils::extract_basic_credentials, ssh::authz::SshAuthService, }; pub async fn verify_access_token( db: &AppDatabase, username: &str, access_key: &str, ) -> Result { let user = sqlx::query_as::<_, UserModel>( "SELECT id, username, display_name, avatar_url, website_url, allow_use, can_search, \ last_sign_in_at, created_at, updated_at \ FROM \"user\" \ WHERE username = $1", ) .bind(username) .fetch_optional(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: Vec = sqlx::query_as::<_, UserTokenModel>( "SELECT id, \"user\", name, token_hash, scopes, expires_at, is_revoked, created_at, updated_at \ FROM user_token \ WHERE \"user\" = $1 AND is_revoked = false", ) .bind(user.id) .fetch_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) }) .collect(); 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: &RepoModel, is_write: bool, ) -> Result<(), Error> { if !is_write && repo.visibility == "public" { 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(()) }