mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-13 11:50: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
|
# AUTHENTICATION CONFIGURATION
|
||||||
# Set the variables of the authentication you want to use in order to enable it
|
# 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
|
# Single Token
|
||||||
ACCESS_TOKEN= # a single token that is used to authenticate all publish requests
|
ACCESS_TOKEN= # a single token that is used to authenticate all publish requests
|
||||||
|
|
||||||
# Read/Write Tokens
|
# Read/Write Tokens
|
||||||
|
# READ_NEEDS_AUTH isn't required for this
|
||||||
|
|
||||||
READ_ACCESS_TOKEN= # a token that is used to authenticate read requests
|
READ_ACCESS_TOKEN= # a token that is used to authenticate read requests
|
||||||
WRITE_ACCESS_TOKEN= # a token that is used to authenticate write requests
|
WRITE_ACCESS_TOKEN= # a token that is used to authenticate write requests
|
||||||
|
|
||||||
# GitHub
|
# GitHub
|
||||||
GITHUB_AUTH= # set to any value to enable GitHub authentication
|
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
|
# 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 crate::auth::{get_token_from_req, AuthImpl, UserId};
|
||||||
use actix_web::{dev::ServiceRequest, Error as ActixError};
|
use actix_web::{dev::ServiceRequest, Error as ActixError};
|
||||||
use serde::Deserialize;
|
use reqwest::StatusCode;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GitHubAuth {
|
pub struct GitHubAuth {
|
||||||
pub reqwest_client: reqwest::Client,
|
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 {
|
impl AuthImpl for GitHubAuth {
|
||||||
async fn for_write_request(&self, req: &ServiceRequest) -> Result<Option<UserId>, ActixError> {
|
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,
|
Some(token) => token,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = match self
|
let response = match self
|
||||||
.reqwest_client
|
.reqwest_client
|
||||||
.get("https://api.github.com/user")
|
.post(format!(
|
||||||
.header(reqwest::header::AUTHORIZATION, token)
|
"https://api.github.com/applications/{}/token",
|
||||||
|
self.client_id
|
||||||
|
))
|
||||||
|
.basic_auth(&self.client_id, Some(&self.client_secret))
|
||||||
|
.json(&TokenRequestBody {
|
||||||
|
access_token: token,
|
||||||
|
})
|
||||||
.send()
|
.send()
|
||||||
.await
|
.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) => {
|
Err(e) => {
|
||||||
log::error!("failed to get user: {e}");
|
log::error!("failed to get user: {e}");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
@ -31,7 +53,7 @@ impl AuthImpl for GitHubAuth {
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = match response.json::<UserResponse>().await {
|
let user_id = match response.json::<UserResponse>().await {
|
||||||
Ok(user) => user.id,
|
Ok(resp) => resp.user.id,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("failed to get user: {e}");
|
log::error!("failed to get user: {e}");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
@ -49,6 +71,11 @@ impl Display for GitHubAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct UserResponse {
|
struct User {
|
||||||
id: u64,
|
id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct UserResponse {
|
||||||
|
user: User,
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ use actix_web::{
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
web, HttpMessage, HttpResponse,
|
web, HttpMessage, HttpResponse,
|
||||||
};
|
};
|
||||||
|
use pesde::{source::pesde::PesdePackageSource, Project};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ pub trait AuthImpl: Display {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_needs_auth(&self) -> bool {
|
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())
|
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") {
|
if let Ok(token) = benv!("ACCESS_TOKEN") {
|
||||||
Auth::Token(token::TokenAuth {
|
Auth::Token(token::TokenAuth {
|
||||||
token: *Sha256::digest(token.as_bytes()).as_ref(),
|
token: *Sha256::digest(token.as_bytes()).as_ref(),
|
||||||
})
|
})
|
||||||
} else if benv!("GITHUB_AUTH").is_ok() {
|
} else if benv!("GITHUB_AUTH").is_ok() {
|
||||||
|
let config = index.config(project).expect("failed to get index config");
|
||||||
|
|
||||||
Auth::GitHub(github::GitHubAuth {
|
Auth::GitHub(github::GitHubAuth {
|
||||||
reqwest_client: make_reqwest(),
|
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)) =
|
} else if let Ok((r, w)) =
|
||||||
benv!("READ_ACCESS_TOKEN").and_then(|r| benv!("WRITE_ACCESS_TOKEN").map(|w| (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
|
let token = match req
|
||||||
.headers()
|
.headers()
|
||||||
.get(AUTHORIZATION)
|
.get(AUTHORIZATION)
|
||||||
|
@ -172,13 +179,7 @@ pub fn get_token_from_req(req: &ServiceRequest, bearer: bool) -> Option<String>
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let token = if bearer {
|
let token = if token.to_lowercase().starts_with("bearer ") {
|
||||||
if token.to_lowercase().starts_with("bearer ") {
|
|
||||||
token.to_string()
|
|
||||||
} else {
|
|
||||||
format!("Bearer {token}")
|
|
||||||
}
|
|
||||||
} else if token.to_lowercase().starts_with("bearer ") {
|
|
||||||
token[7..].to_string()
|
token[7..].to_string()
|
||||||
} else {
|
} else {
|
||||||
token.to_string()
|
token.to_string()
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub struct RwTokenAuth {
|
||||||
|
|
||||||
impl AuthImpl for RwTokenAuth {
|
impl AuthImpl for RwTokenAuth {
|
||||||
async fn for_write_request(&self, req: &ServiceRequest) -> Result<Option<UserId>, ActixError> {
|
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,
|
Some(token) => token,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@ impl AuthImpl for RwTokenAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn for_read_request(&self, req: &ServiceRequest) -> Result<Option<UserId>, ActixError> {
|
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,
|
Some(token) => token,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub struct TokenAuth {
|
||||||
|
|
||||||
impl AuthImpl for TokenAuth {
|
impl AuthImpl for TokenAuth {
|
||||||
async fn for_write_request(&self, req: &ServiceRequest) -> Result<Option<UserId>, ActixError> {
|
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,
|
Some(token) => token,
|
||||||
None => return Ok(None),
|
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 (search_reader, search_writer) = make_search(&project, &source);
|
||||||
|
|
||||||
let app_data = web::Data::new(AppState {
|
let app_data = web::Data::new(AppState {
|
||||||
source: Mutex::new(source),
|
|
||||||
project,
|
|
||||||
storage: {
|
storage: {
|
||||||
let storage = get_storage_from_env();
|
let storage = get_storage_from_env();
|
||||||
info!("storage: {storage}");
|
info!("storage: {storage}");
|
||||||
storage
|
storage
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
let auth = get_auth_from_env();
|
let auth = get_auth_from_env(&source, &project);
|
||||||
info!("auth: {auth}");
|
info!("auth: {auth}");
|
||||||
auth
|
auth
|
||||||
},
|
},
|
||||||
|
source: Mutex::new(source),
|
||||||
|
project,
|
||||||
|
|
||||||
search_reader,
|
search_reader,
|
||||||
search_writer: Mutex::new(search_writer),
|
search_writer: Mutex::new(search_writer),
|
||||||
|
|
Loading…
Reference in a new issue