mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
fix(registry): prevent token usage from unauthorized apps
This commit is contained in:
parent
756b5c8257
commit
66a885b4e6
6 changed files with 57 additions and 24 deletions
|
@ -9,15 +9,20 @@ COMMITTER_GIT_EMAIL= # email of the committer used for index updates
|
|||
# AUTHENTICATION CONFIGURATION
|
||||
# Set the variables of the authentication you want to use in order to enable it
|
||||
|
||||
READ_NEEDS_AUTH= # set to any value to require authentication for read requests
|
||||
|
||||
# Single Token
|
||||
ACCESS_TOKEN= # a single token that is used to authenticate all publish requests
|
||||
|
||||
# Read/Write Tokens
|
||||
# READ_NEEDS_AUTH isn't required for this
|
||||
|
||||
READ_ACCESS_TOKEN= # a token that is used to authenticate read requests
|
||||
WRITE_ACCESS_TOKEN= # a token that is used to authenticate write requests
|
||||
|
||||
# GitHub
|
||||
GITHUB_AUTH= # set to any value to enable GitHub authentication
|
||||
GITHUB_CLIENT_SECRET= # client secret of the GitHub OAuth app configured in the index's `config.toml`
|
||||
|
||||
# If none of the above is set, no authentication is required, even for write requests
|
||||
|
||||
|
|
|
@ -1,29 +1,51 @@
|
|||
use crate::auth::{get_token_from_req, AuthImpl, UserId};
|
||||
use actix_web::{dev::ServiceRequest, Error as ActixError};
|
||||
use serde::Deserialize;
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GitHubAuth {
|
||||
pub reqwest_client: reqwest::Client,
|
||||
pub client_id: String,
|
||||
pub client_secret: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct TokenRequestBody {
|
||||
access_token: String,
|
||||
}
|
||||
|
||||
impl AuthImpl for GitHubAuth {
|
||||
async fn for_write_request(&self, req: &ServiceRequest) -> Result<Option<UserId>, ActixError> {
|
||||
let token = match get_token_from_req(req, true) {
|
||||
let token = match get_token_from_req(req) {
|
||||
Some(token) => token,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let response = match self
|
||||
.reqwest_client
|
||||
.get("https://api.github.com/user")
|
||||
.header(reqwest::header::AUTHORIZATION, token)
|
||||
.post(format!(
|
||||
"https://api.github.com/applications/{}/token",
|
||||
self.client_id
|
||||
))
|
||||
.basic_auth(&self.client_id, Some(&self.client_secret))
|
||||
.json(&TokenRequestBody {
|
||||
access_token: token,
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.and_then(|res| res.error_for_status())
|
||||
{
|
||||
Ok(response) => response,
|
||||
Ok(response) => match response.error_for_status_ref() {
|
||||
Ok(_) => response,
|
||||
Err(e) if e.status().is_some_and(|s| s == StatusCode::UNAUTHORIZED) => {
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("failed to get user: {e}");
|
||||
return Ok(None);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("failed to get user: {e}");
|
||||
return Ok(None);
|
||||
|
@ -31,7 +53,7 @@ impl AuthImpl for GitHubAuth {
|
|||
};
|
||||
|
||||
let user_id = match response.json::<UserResponse>().await {
|
||||
Ok(user) => user.id,
|
||||
Ok(resp) => resp.user.id,
|
||||
Err(e) => {
|
||||
log::error!("failed to get user: {e}");
|
||||
return Ok(None);
|
||||
|
@ -49,6 +71,11 @@ impl Display for GitHubAuth {
|
|||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UserResponse {
|
||||
struct User {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UserResponse {
|
||||
user: User,
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use actix_web::{
|
|||
middleware::Next,
|
||||
web, HttpMessage, HttpResponse,
|
||||
};
|
||||
use pesde::{source::pesde::PesdePackageSource, Project};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fmt::Display;
|
||||
|
||||
|
@ -55,7 +56,7 @@ pub trait AuthImpl: Display {
|
|||
}
|
||||
|
||||
fn read_needs_auth(&self) -> bool {
|
||||
false
|
||||
benv!("READ_NEEDS_AUTH").is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,14 +142,20 @@ pub async fn read_mw(
|
|||
next.call(req).await.map(|res| res.map_into_left_body())
|
||||
}
|
||||
|
||||
pub fn get_auth_from_env() -> Auth {
|
||||
pub fn get_auth_from_env(index: &PesdePackageSource, project: &Project) -> Auth {
|
||||
if let Ok(token) = benv!("ACCESS_TOKEN") {
|
||||
Auth::Token(token::TokenAuth {
|
||||
token: *Sha256::digest(token.as_bytes()).as_ref(),
|
||||
})
|
||||
} else if benv!("GITHUB_AUTH").is_ok() {
|
||||
let config = index.config(project).expect("failed to get index config");
|
||||
|
||||
Auth::GitHub(github::GitHubAuth {
|
||||
reqwest_client: make_reqwest(),
|
||||
client_id: config
|
||||
.github_oauth_client_id
|
||||
.expect("index isn't configured for GitHub"),
|
||||
client_secret: benv!(required "GITHUB_CLIENT_SECRET"),
|
||||
})
|
||||
} else if let Ok((r, w)) =
|
||||
benv!("READ_ACCESS_TOKEN").and_then(|r| benv!("WRITE_ACCESS_TOKEN").map(|w| (r, w)))
|
||||
|
@ -162,7 +169,7 @@ pub fn get_auth_from_env() -> Auth {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_token_from_req(req: &ServiceRequest, bearer: bool) -> Option<String> {
|
||||
pub fn get_token_from_req(req: &ServiceRequest) -> Option<String> {
|
||||
let token = match req
|
||||
.headers()
|
||||
.get(AUTHORIZATION)
|
||||
|
@ -172,13 +179,7 @@ pub fn get_token_from_req(req: &ServiceRequest, bearer: bool) -> Option<String>
|
|||
None => return None,
|
||||
};
|
||||
|
||||
let token = if bearer {
|
||||
if token.to_lowercase().starts_with("bearer ") {
|
||||
token.to_string()
|
||||
} else {
|
||||
format!("Bearer {token}")
|
||||
}
|
||||
} else if token.to_lowercase().starts_with("bearer ") {
|
||||
let token = if token.to_lowercase().starts_with("bearer ") {
|
||||
token[7..].to_string()
|
||||
} else {
|
||||
token.to_string()
|
||||
|
|
|
@ -12,7 +12,7 @@ pub struct RwTokenAuth {
|
|||
|
||||
impl AuthImpl for RwTokenAuth {
|
||||
async fn for_write_request(&self, req: &ServiceRequest) -> Result<Option<UserId>, ActixError> {
|
||||
let token = match get_token_from_req(req, false) {
|
||||
let token = match get_token_from_req(req) {
|
||||
Some(token) => token,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ impl AuthImpl for RwTokenAuth {
|
|||
}
|
||||
|
||||
async fn for_read_request(&self, req: &ServiceRequest) -> Result<Option<UserId>, ActixError> {
|
||||
let token = match get_token_from_req(req, false) {
|
||||
let token = match get_token_from_req(req) {
|
||||
Some(token) => token,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ pub struct TokenAuth {
|
|||
|
||||
impl AuthImpl for TokenAuth {
|
||||
async fn for_write_request(&self, req: &ServiceRequest) -> Result<Option<UserId>, ActixError> {
|
||||
let token = match get_token_from_req(req, false) {
|
||||
let token = match get_token_from_req(req) {
|
||||
Some(token) => token,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
|
|
@ -104,18 +104,18 @@ async fn run(with_sentry: bool) -> std::io::Result<()> {
|
|||
let (search_reader, search_writer) = make_search(&project, &source);
|
||||
|
||||
let app_data = web::Data::new(AppState {
|
||||
source: Mutex::new(source),
|
||||
project,
|
||||
storage: {
|
||||
let storage = get_storage_from_env();
|
||||
info!("storage: {storage}");
|
||||
storage
|
||||
},
|
||||
auth: {
|
||||
let auth = get_auth_from_env();
|
||||
let auth = get_auth_from_env(&source, &project);
|
||||
info!("auth: {auth}");
|
||||
auth
|
||||
},
|
||||
source: Mutex::new(source),
|
||||
project,
|
||||
|
||||
search_reader,
|
||||
search_writer: Mutex::new(search_writer),
|
||||
|
|
Loading…
Reference in a new issue