95 lines
2.7 KiB
Rust
95 lines
2.7 KiB
Rust
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<UserModel, Error> {
|
|
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<UserTokenModel> = 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(())
|
|
}
|