feat: improve auth system for registry changes

This commit is contained in:
daimond113 2024-10-14 19:40:02 +02:00
parent 66a885b4e6
commit c7c1daab36
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
15 changed files with 183 additions and 270 deletions

View file

@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Support full version requirements in workspace version field by @daimond113 - Support full version requirements in workspace version field by @daimond113
- Improved authentication system for registry changes by @daimond113
### Fixed ### Fixed
- Correct `pesde.toml` inclusion message in `publish` command by @daimond113 - Correct `pesde.toml` inclusion message in `publish` command by @daimond113

View file

@ -1,23 +1,51 @@
use crate::cli::config::{read_config, write_config}; use crate::cli::config::{read_config, write_config};
use anyhow::Context; use anyhow::Context;
use gix::bstr::BStr;
use keyring::Entry; use keyring::Entry;
use serde::Deserialize; use reqwest::header::AUTHORIZATION;
use serde::{ser::SerializeMap, Deserialize, Serialize};
use std::collections::BTreeMap;
pub fn get_token() -> anyhow::Result<Option<String>> { #[derive(Debug, Clone)]
match std::env::var("PESDE_TOKEN") { pub struct Tokens(pub BTreeMap<gix::Url, String>);
Ok(token) => return Ok(Some(token)),
Err(std::env::VarError::NotPresent) => {} impl Serialize for Tokens {
Err(e) => return Err(e.into()), fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let mut map = serializer.serialize_map(Some(self.0.len()))?;
for (k, v) in &self.0 {
map.serialize_entry(&k.to_bstring().to_string(), v)?;
}
map.end()
} }
}
impl<'de> Deserialize<'de> for Tokens {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
Ok(Tokens(
BTreeMap::<String, String>::deserialize(deserializer)?
.into_iter()
.map(|(k, v)| gix::Url::from_bytes(BStr::new(&k)).map(|k| (k, v)))
.collect::<Result<_, _>>()
.map_err(serde::de::Error::custom)?,
))
}
}
pub fn get_tokens() -> anyhow::Result<Tokens> {
let config = read_config()?; let config = read_config()?;
if let Some(token) = config.token { if !config.tokens.0.is_empty() {
return Ok(Some(token)); return Ok(config.tokens);
} }
match Entry::new("token", env!("CARGO_PKG_NAME")) { match Entry::new("tokens", env!("CARGO_PKG_NAME")) {
Ok(entry) => match entry.get_password() { Ok(entry) => match entry.get_password() {
Ok(token) => return Ok(Some(token)), Ok(token) => return serde_json::from_str(&token).context("failed to parse tokens"),
Err(keyring::Error::PlatformFailure(_) | keyring::Error::NoEntry) => {} Err(keyring::Error::PlatformFailure(_) | keyring::Error::NoEntry) => {}
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}, },
@ -25,32 +53,32 @@ pub fn get_token() -> anyhow::Result<Option<String>> {
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
} }
Ok(None) Ok(Tokens(BTreeMap::new()))
} }
pub fn set_token(token: Option<&str>) -> anyhow::Result<()> { pub fn set_tokens(tokens: Tokens) -> anyhow::Result<()> {
let entry = match Entry::new("token", env!("CARGO_PKG_NAME")) { let entry = Entry::new("tokens", env!("CARGO_PKG_NAME"))?;
Ok(entry) => entry, let json = serde_json::to_string(&tokens).context("failed to serialize tokens")?;
Err(e) => return Err(e.into()),
};
let result = if let Some(token) = token { match entry.set_password(&json) {
entry.set_password(token)
} else {
entry.delete_credential()
};
match result {
Ok(()) => return Ok(()), Ok(()) => return Ok(()),
Err(keyring::Error::PlatformFailure(_) | keyring::Error::NoEntry) => {} Err(keyring::Error::PlatformFailure(_) | keyring::Error::NoEntry) => {}
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
} }
let mut config = read_config()?; let mut config = read_config()?;
config.token = token.map(|s| s.to_string()); config.tokens = tokens;
write_config(&config)?; write_config(&config).map_err(Into::into)
}
Ok(()) pub fn set_token(repo: &gix::Url, token: Option<&str>) -> anyhow::Result<()> {
let mut tokens = get_tokens()?;
if let Some(token) = token {
tokens.0.insert(repo.clone(), token.to_string());
} else {
tokens.0.remove(repo);
}
set_tokens(tokens)
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -64,7 +92,7 @@ pub fn get_token_login(
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
let response = reqwest let response = reqwest
.get("https://api.github.com/user") .get("https://api.github.com/user")
.header("Authorization", access_token) .header(AUTHORIZATION, access_token)
.send() .send()
.context("failed to send user request")? .context("failed to send user request")?
.error_for_status() .error_for_status()

View file

@ -5,24 +5,16 @@ use serde::Deserialize;
use url::Url; use url::Url;
use pesde::{ use pesde::{
errors::ManifestReadError,
source::{pesde::PesdePackageSource, traits::PackageSource}, source::{pesde::PesdePackageSource, traits::PackageSource},
Project, Project,
}; };
use crate::cli::{ use crate::cli::auth::{get_token_login, set_token};
auth::{get_token_login, set_token},
config::read_config,
};
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct LoginCommand { pub struct LoginCommand {
/// The index to use. Defaults to `default`, or the configured default index if current directory doesn't have a manifest
#[arg(short, long)]
index: Option<String>,
/// The token to use for authentication, skipping login /// The token to use for authentication, skipping login
#[arg(short, long, conflicts_with = "index")] #[arg(short, long)]
token: Option<String>, token: Option<String>,
} }
@ -55,41 +47,13 @@ enum AccessTokenResponse {
impl LoginCommand { impl LoginCommand {
pub fn authenticate_device_flow( pub fn authenticate_device_flow(
&self, &self,
index_url: &gix::Url,
project: &Project, project: &Project,
reqwest: &reqwest::blocking::Client, reqwest: &reqwest::blocking::Client,
) -> anyhow::Result<String> { ) -> anyhow::Result<String> {
let manifest = match project.deser_manifest() { println!("logging in into {index_url}");
Ok(manifest) => Some(manifest),
Err(e) => match e {
ManifestReadError::Io(e) if e.kind() == std::io::ErrorKind::NotFound => None,
e => return Err(e.into()),
},
};
let index_url = match self.index.as_deref() { let source = PesdePackageSource::new(index_url.clone());
Some(index) => match index.try_into() {
Ok(url) => Some(url),
Err(_) => None,
},
None => match manifest {
Some(_) => None,
None => Some(read_config()?.default_index),
},
};
let index_url = match index_url {
Some(url) => url,
None => {
let index_name = self.index.as_deref().unwrap_or("default");
match manifest.unwrap().indices.get(index_name) {
Some(index) => index.clone(),
None => anyhow::bail!("Index {index_name} not found"),
}
}
};
let source = PesdePackageSource::new(index_url);
source.refresh(project).context("failed to refresh index")?; source.refresh(project).context("failed to refresh index")?;
let config = source let config = source
@ -182,24 +146,32 @@ impl LoginCommand {
anyhow::bail!("code expired, please re-run the login command"); anyhow::bail!("code expired, please re-run the login command");
} }
pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { pub fn run(
self,
index_url: gix::Url,
project: Project,
reqwest: reqwest::blocking::Client,
) -> anyhow::Result<()> {
let token_given = self.token.is_some(); let token_given = self.token.is_some();
let token = match self.token { let token = match self.token {
Some(token) => token, Some(token) => token,
None => self.authenticate_device_flow(&project, &reqwest)?, None => self.authenticate_device_flow(&index_url, &project, &reqwest)?,
}; };
let token = if token_given { let token = if token_given {
println!("set token"); println!("set token for {index_url}");
token token
} else { } else {
let token = format!("Bearer {token}"); let token = format!("Bearer {token}");
println!("logged in as {}", get_token_login(&reqwest, &token)?.bold()); println!(
"logged in as {} for {index_url}",
get_token_login(&reqwest, &token)?.bold()
);
token token
}; };
set_token(Some(&token))?; set_token(&index_url, Some(&token))?;
Ok(()) Ok(())
} }

View file

@ -5,10 +5,10 @@ use clap::Args;
pub struct LogoutCommand {} pub struct LogoutCommand {}
impl LogoutCommand { impl LogoutCommand {
pub fn run(self) -> anyhow::Result<()> { pub fn run(self, index_url: gix::Url) -> anyhow::Result<()> {
set_token(None)?; set_token(&index_url, None)?;
println!("logged out"); println!("logged out of {index_url}");
Ok(()) Ok(())
} }

View file

@ -1,31 +1,69 @@
use clap::Subcommand; use crate::cli::config::read_config;
use pesde::Project; use clap::{Args, Subcommand};
use pesde::{errors::ManifestReadError, Project};
mod login; mod login;
mod logout; mod logout;
mod set_token_override;
mod whoami; mod whoami;
#[derive(Debug, Args)]
pub struct AuthSubcommand {
/// The index to use. Defaults to `default`, or the configured default index if current directory doesn't have a manifest
#[arg(short, long)]
pub index: Option<String>,
#[clap(subcommand)]
pub command: AuthCommands,
}
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum AuthCommands { pub enum AuthCommands {
/// Logs in into GitHub, and stores the token /// Sets a token for an index. Optionally gets it from GitHub
Login(login::LoginCommand), Login(login::LoginCommand),
/// Removes the stored token /// Removes the stored token
Logout(logout::LogoutCommand), Logout(logout::LogoutCommand),
/// Prints the username of the currently logged-in user /// Prints the username of the currently logged-in user
#[clap(name = "whoami")] #[clap(name = "whoami")]
WhoAmI(whoami::WhoAmICommand), WhoAmI(whoami::WhoAmICommand),
/// Sets a token override for a specific repository
SetTokenOverride(set_token_override::SetTokenOverrideCommand),
} }
impl AuthCommands { impl AuthSubcommand {
pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { pub fn run(self, project: Project, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> {
match self { let manifest = match project.deser_manifest() {
AuthCommands::Login(login) => login.run(project, reqwest), Ok(manifest) => Some(manifest),
AuthCommands::Logout(logout) => logout.run(), Err(e) => match e {
AuthCommands::WhoAmI(whoami) => whoami.run(reqwest), ManifestReadError::Io(e) if e.kind() == std::io::ErrorKind::NotFound => None,
AuthCommands::SetTokenOverride(set_token_override) => set_token_override.run(), e => return Err(e.into()),
},
};
let index_url = match self.index.as_deref() {
Some(index) => match index.try_into() {
Ok(url) => Some(url),
Err(_) => None,
},
None => match manifest {
Some(_) => None,
None => Some(read_config()?.default_index),
},
};
let index_url = match index_url {
Some(url) => url,
None => {
let index_name = self.index.as_deref().unwrap_or("default");
match manifest.unwrap().indices.get(index_name) {
Some(index) => index.clone(),
None => anyhow::bail!("index {index_name} not found"),
}
}
};
match self.command {
AuthCommands::Login(login) => login.run(index_url, project, reqwest),
AuthCommands::Logout(logout) => logout.run(index_url),
AuthCommands::WhoAmI(whoami) => whoami.run(index_url, reqwest),
} }
} }
} }

View file

@ -1,31 +0,0 @@
use crate::cli::config::{read_config, write_config};
use clap::Args;
#[derive(Debug, Args)]
pub struct SetTokenOverrideCommand {
/// The repository to add the token to
#[arg(index = 1, value_parser = crate::cli::parse_gix_url)]
repository: gix::Url,
/// The token to set
#[arg(index = 2)]
token: Option<String>,
}
impl SetTokenOverrideCommand {
pub fn run(self) -> anyhow::Result<()> {
let mut config = read_config()?;
if let Some(token) = self.token {
println!("set token for {}", self.repository);
config.token_overrides.insert(self.repository, token);
} else {
println!("removed token for {}", self.repository);
config.token_overrides.remove(&self.repository);
}
write_config(&config)?;
Ok(())
}
}

View file

@ -1,4 +1,4 @@
use crate::cli::{auth::get_token_login, get_token}; use crate::cli::auth::{get_token_login, get_tokens};
use clap::Args; use clap::Args;
use colored::Colorize; use colored::Colorize;
@ -6,16 +6,24 @@ use colored::Colorize;
pub struct WhoAmICommand {} pub struct WhoAmICommand {}
impl WhoAmICommand { impl WhoAmICommand {
pub fn run(self, reqwest: reqwest::blocking::Client) -> anyhow::Result<()> { pub fn run(
let token = match get_token()? { self,
index_url: gix::Url,
reqwest: reqwest::blocking::Client,
) -> anyhow::Result<()> {
let tokens = get_tokens()?;
let token = match tokens.0.get(&index_url) {
Some(token) => token, Some(token) => token,
None => { None => {
println!("not logged in"); println!("not logged in into {index_url}");
return Ok(()); return Ok(());
} }
}; };
println!("logged in as {}", get_token_login(&reqwest, &token)?.bold()); println!(
"logged in as {} into {index_url}",
get_token_login(&reqwest, token)?.bold()
);
Ok(()) Ok(())
} }

View file

@ -23,8 +23,7 @@ mod update;
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
pub enum Subcommand { pub enum Subcommand {
/// Authentication-related commands /// Authentication-related commands
#[command(subcommand)] Auth(auth::AuthSubcommand),
Auth(auth::AuthCommands),
/// Configuration-related commands /// Configuration-related commands
#[command(subcommand)] #[command(subcommand)]

View file

@ -1,7 +1,7 @@
use anyhow::Context; use anyhow::Context;
use clap::Args; use clap::Args;
use colored::Colorize; use colored::Colorize;
use reqwest::StatusCode; use reqwest::{header::AUTHORIZATION, StatusCode};
use semver::VersionReq; use semver::VersionReq;
use std::{ use std::{
io::{Seek, Write}, io::{Seek, Write},
@ -457,13 +457,11 @@ impl PublishCommand {
.finish() .finish()
.context("failed to get archive bytes")?; .context("failed to get archive bytes")?;
let source = PesdePackageSource::new( let index_url = manifest
manifest .indices
.indices .get(DEFAULT_INDEX_NAME)
.get(DEFAULT_INDEX_NAME) .context("missing default index")?;
.context("missing default index")? let source = PesdePackageSource::new(index_url.clone());
.clone(),
);
source source
.refresh(project) .refresh(project)
.context("failed to refresh source")?; .context("failed to refresh source")?;
@ -501,14 +499,19 @@ impl PublishCommand {
return Ok(()); return Ok(());
} }
let response = reqwest let mut request = reqwest
.post(format!("{}/v0/packages", config.api())) .post(format!("{}/v0/packages", config.api()))
.multipart(reqwest::blocking::multipart::Form::new().part( .multipart(reqwest::blocking::multipart::Form::new().part(
"tarball", "tarball",
reqwest::blocking::multipart::Part::bytes(archive).file_name("package.tar.gz"), reqwest::blocking::multipart::Part::bytes(archive).file_name("package.tar.gz"),
)) ));
.send()
.context("failed to send request")?; if let Some(token) = project.auth_config().tokens().get(index_url) {
log::debug!("using token for {index_url}");
request = request.header(AUTHORIZATION, token);
}
let response = request.send().context("failed to send request")?;
let status = response.status(); let status = response.status();
let text = response.text().context("failed to get response text")?; let text = response.text().context("failed to get response text")?;

View file

@ -1,10 +1,7 @@
use std::collections::BTreeMap; use crate::cli::{auth::Tokens, home_dir};
use anyhow::Context; use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::cli::home_dir;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CliConfig { pub struct CliConfig {
#[serde( #[serde(
@ -18,15 +15,7 @@ pub struct CliConfig {
)] )]
pub scripts_repo: gix::Url, pub scripts_repo: gix::Url,
#[serde(default, skip_serializing_if = "Option::is_none")] pub tokens: Tokens,
pub token: Option<String>,
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
serialize_with = "crate::cli::serialize_string_url_map",
deserialize_with = "crate::cli::deserialize_string_url_map"
)]
pub token_overrides: BTreeMap<gix::Url, String>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub last_checked_updates: Option<(chrono::DateTime<chrono::Utc>, semver::Version)>, pub last_checked_updates: Option<(chrono::DateTime<chrono::Utc>, semver::Version)>,
@ -42,8 +31,7 @@ impl Default for CliConfig {
.try_into() .try_into()
.unwrap(), .unwrap(),
token: None, tokens: Tokens(Default::default()),
token_overrides: Default::default(),
last_checked_updates: None, last_checked_updates: None,
} }

View file

@ -1,6 +1,4 @@
use crate::cli::auth::get_token;
use anyhow::Context; use anyhow::Context;
use gix::bstr::BStr;
use indicatif::MultiProgress; use indicatif::MultiProgress;
use pesde::{ use pesde::{
lockfile::{DependencyGraph, DownloadedGraph, Lockfile}, lockfile::{DependencyGraph, DownloadedGraph, Lockfile},
@ -10,7 +8,6 @@ use pesde::{
Project, Project,
}; };
use relative_path::RelativePathBuf; use relative_path::RelativePathBuf;
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer};
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, HashSet},
fs::create_dir_all, fs::create_dir_all,
@ -188,30 +185,6 @@ pub fn parse_gix_url(s: &str) -> Result<gix::Url, gix::url::parse::Error> {
s.try_into() s.try_into()
} }
pub fn serialize_string_url_map<S: Serializer>(
url: &BTreeMap<gix::Url, String>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(Some(url.len()))?;
for (k, v) in url {
map.serialize_entry(&k.to_bstring().to_string(), v)?;
}
map.end()
}
pub fn deserialize_string_url_map<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<BTreeMap<gix::Url, String>, D::Error> {
BTreeMap::<String, String>::deserialize(deserializer)?
.into_iter()
.map(|(k, v)| {
gix::Url::from_bytes(BStr::new(&k))
.map(|k| (k, v))
.map_err(serde::de::Error::custom)
})
.collect()
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn download_graph( pub fn download_graph(
project: &Project, project: &Project,

View file

@ -44,8 +44,7 @@ pub(crate) const LINK_LIB_NO_FILE_FOUND: &str = "____pesde_no_export_file_found"
/// Struct containing the authentication configuration /// Struct containing the authentication configuration
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct AuthConfig { pub struct AuthConfig {
default_token: Option<String>, tokens: HashMap<gix::Url, String>,
token_overrides: HashMap<gix::Url, String>,
git_credentials: Option<Account>, git_credentials: Option<Account>,
} }
@ -55,18 +54,12 @@ impl AuthConfig {
AuthConfig::default() AuthConfig::default()
} }
/// Sets the default token /// Set the tokens
pub fn with_default_token<S: AsRef<str>>(mut self, token: Option<S>) -> Self { pub fn with_tokens<I: IntoIterator<Item = (gix::Url, S)>, S: AsRef<str>>(
self.default_token = token.map(|s| s.as_ref().to_string());
self
}
/// Set the token overrides
pub fn with_token_overrides<I: IntoIterator<Item = (gix::Url, S)>, S: AsRef<str>>(
mut self, mut self,
tokens: I, tokens: I,
) -> Self { ) -> Self {
self.token_overrides = tokens self.tokens = tokens
.into_iter() .into_iter()
.map(|(url, s)| (url, s.as_ref().to_string())) .map(|(url, s)| (url, s.as_ref().to_string()))
.collect(); .collect();
@ -79,27 +72,15 @@ impl AuthConfig {
self self
} }
/// Get the default token /// Get the tokens
pub fn default_token(&self) -> Option<&str> { pub fn tokens(&self) -> &HashMap<gix::Url, String> {
self.default_token.as_deref() &self.tokens
}
/// Get the token overrides
pub fn token_overrides(&self) -> &HashMap<gix::Url, String> {
&self.token_overrides
} }
/// Get the git credentials /// Get the git credentials
pub fn git_credentials(&self) -> Option<&Account> { pub fn git_credentials(&self) -> Option<&Account> {
self.git_credentials.as_ref() self.git_credentials.as_ref()
} }
pub(crate) fn get_token(&self, url: &gix::Url) -> Option<&str> {
self.token_overrides
.get(url)
.map(|s| s.as_str())
.or(self.default_token.as_deref())
}
} }
/// The main struct of the pesde library, representing a project /// The main struct of the pesde library, representing a project

View file

@ -14,9 +14,7 @@ use std::{
use crate::cli::version::{ use crate::cli::version::{
check_for_updates, current_version, get_or_download_version, max_installed_version, check_for_updates, current_version, get_or_download_version, max_installed_version,
}; };
use crate::cli::{ use crate::cli::{auth::get_tokens, home_dir, repos::update_repo_dependencies, HOME_DIR};
auth::get_token, config::read_config, home_dir, repos::update_repo_dependencies, HOME_DIR,
};
mod cli; mod cli;
pub mod util; pub mod util;
@ -187,14 +185,14 @@ fn run() -> anyhow::Result<()> {
let data_dir = home_dir()?.join("data"); let data_dir = home_dir()?.join("data");
create_dir_all(&data_dir).expect("failed to create data directory"); create_dir_all(&data_dir).expect("failed to create data directory");
let token = get_token()?;
let home_cas_dir = data_dir.join("cas"); let home_cas_dir = data_dir.join("cas");
create_dir_all(&home_cas_dir).expect("failed to create cas directory"); create_dir_all(&home_cas_dir).expect("failed to create cas directory");
let project_root = get_root(&project_root_dir); let project_root = get_root(&project_root_dir);
let cas_dir = if get_root(&home_cas_dir) == project_root { let cas_dir = if get_root(&home_cas_dir) == project_root {
log::debug!("using home cas dir");
home_cas_dir home_cas_dir
} else { } else {
log::debug!("using cas dir in {}", project_root.display());
project_root.join(HOME_DIR).join("cas") project_root.join(HOME_DIR).join("cas")
}; };
@ -203,19 +201,11 @@ fn run() -> anyhow::Result<()> {
project_workspace_dir, project_workspace_dir,
data_dir, data_dir,
cas_dir, cas_dir,
AuthConfig::new() AuthConfig::new().with_tokens(get_tokens()?.0),
.with_default_token(token.clone())
.with_token_overrides(read_config()?.token_overrides),
); );
let reqwest = { let reqwest = {
let mut headers = reqwest::header::HeaderMap::new(); let mut headers = reqwest::header::HeaderMap::new();
if let Some(token) = token {
headers.insert(
reqwest::header::AUTHORIZATION,
token.parse().context("failed to create auth header")?,
);
}
headers.insert( headers.insert(
reqwest::header::ACCEPT, reqwest::header::ACCEPT,

View file

@ -7,7 +7,7 @@ use std::{
use gix::Url; use gix::Url;
use relative_path::RelativePathBuf; use relative_path::RelativePathBuf;
use reqwest::header::{HeaderMap, ACCEPT, AUTHORIZATION}; use reqwest::header::{ACCEPT, AUTHORIZATION};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use pkg_ref::PesdePackageRef; use pkg_ref::PesdePackageRef;
@ -273,29 +273,14 @@ impl PackageSource for PesdePackageSource {
.replace("{PACKAGE_VERSION}", &pkg_ref.version.to_string()) .replace("{PACKAGE_VERSION}", &pkg_ref.version.to_string())
.replace("{PACKAGE_TARGET}", &pkg_ref.target.to_string()); .replace("{PACKAGE_TARGET}", &pkg_ref.target.to_string());
let mut headers = HeaderMap::new(); let mut request = reqwest.get(&url).header(ACCEPT, "application/octet-stream");
headers.insert(
ACCEPT,
"application/octet-stream"
.parse()
.map_err(|e| errors::DownloadError::InvalidHeaderValue("Accept".to_string(), e))?,
);
if let Some(token) = project.auth_config.get_token(&self.repo_url) { if let Some(token) = project.auth_config.tokens().get(&self.repo_url) {
log::debug!("using token for pesde package download"); log::debug!("using token for {}", self.repo_url);
headers.insert( request = request.header(AUTHORIZATION, token);
AUTHORIZATION,
token.parse().map_err(|e| {
errors::DownloadError::InvalidHeaderValue("Authorization".to_string(), e)
})?,
);
} }
let response = reqwest let response = request.send()?.error_for_status()?;
.get(url)
.headers(headers)
.send()?
.error_for_status()?;
let bytes = response.bytes()?; let bytes = response.bytes()?;
let mut decoder = flate2::read::GzDecoder::new(bytes.as_ref()); let mut decoder = flate2::read::GzDecoder::new(bytes.as_ref());
@ -583,9 +568,5 @@ pub mod errors {
/// Error writing index file /// Error writing index file
#[error("error reading index file")] #[error("error reading index file")]
ReadIndex(#[source] std::io::Error), ReadIndex(#[source] std::io::Error),
/// A header value was invalid
#[error("invalid header {0} value")]
InvalidHeaderValue(String, #[source] reqwest::header::InvalidHeaderValue),
} }
} }

View file

@ -5,7 +5,7 @@ use std::{
use gix::Url; use gix::Url;
use relative_path::RelativePathBuf; use relative_path::RelativePathBuf;
use reqwest::header::{HeaderMap, AUTHORIZATION}; use reqwest::header::AUTHORIZATION;
use serde::Deserialize; use serde::Deserialize;
use tempfile::tempdir; use tempfile::tempdir;
@ -178,33 +178,19 @@ impl PackageSource for WallyPackageSource {
pkg_ref.version pkg_ref.version
); );
let mut headers = HeaderMap::new(); let mut request = reqwest.get(&url).header(
headers.insert(
"Wally-Version", "Wally-Version",
std::env::var("PESDE_WALLY_VERSION") std::env::var("PESDE_WALLY_VERSION")
.as_deref() .as_deref()
.unwrap_or("0.3.2") .unwrap_or("0.3.2"),
.parse()
.map_err(|e| {
errors::DownloadError::InvalidHeaderValue("Wally-Version".to_string(), e)
})?,
); );
if let Some(token) = project.auth_config.get_token(&self.repo_url) { if let Some(token) = project.auth_config.tokens().get(&self.repo_url) {
log::debug!("using token for wally package download"); log::debug!("using token for {}", self.repo_url);
headers.insert( request = request.header(AUTHORIZATION, token);
AUTHORIZATION,
token.parse().map_err(|e| {
errors::DownloadError::InvalidHeaderValue("Authorization".to_string(), e)
})?,
);
} }
let response = reqwest let response = request.send()?.error_for_status()?;
.get(url)
.headers(headers)
.send()?
.error_for_status()?;
let bytes = response.bytes()?; let bytes = response.bytes()?;
let mut archive = zip::ZipArchive::new(std::io::Cursor::new(bytes))?; let mut archive = zip::ZipArchive::new(std::io::Cursor::new(bytes))?;
@ -355,9 +341,5 @@ pub mod errors {
/// Error writing index file /// Error writing index file
#[error("error writing index file")] #[error("error writing index file")]
WriteIndex(#[source] std::io::Error), WriteIndex(#[source] std::io::Error),
/// A header value was invalid
#[error("invalid header {0} value")]
InvalidHeaderValue(String, #[source] reqwest::header::InvalidHeaderValue),
} }
} }