feat: implement token overrides

This commit is contained in:
daimond113 2024-08-12 22:28:37 +02:00
parent a2865523a0
commit 30c9be8366
No known key found for this signature in database
GPG key ID: 3A8ECE51328B513C
11 changed files with 159 additions and 41 deletions

View file

@ -64,7 +64,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", format!("Bearer {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

@ -1,17 +1,19 @@
use crate::cli::{
auth::{get_token_login, set_token},
config::read_config,
};
use anyhow::Context; use anyhow::Context;
use clap::Args; use clap::Args;
use colored::Colorize; use colored::Colorize;
use serde::Deserialize;
use url::Url;
use pesde::{ use pesde::{
errors::ManifestReadError, errors::ManifestReadError,
source::{pesde::PesdePackageSource, traits::PackageSource}, source::{pesde::PesdePackageSource, traits::PackageSource},
Project, Project,
}; };
use serde::Deserialize;
use url::Url; use crate::cli::{
auth::{get_token_login, set_token},
config::read_config,
};
#[derive(Debug, Args)] #[derive(Debug, Args)]
pub struct LoginCommand { pub struct LoginCommand {
@ -19,6 +21,10 @@ pub struct LoginCommand {
#[arg(short, long)] #[arg(short, long)]
index: Option<String>, index: Option<String>,
/// Whether to not prefix the token with `Bearer `
#[arg(short, long, conflicts_with = "token")]
no_bearer: bool,
/// 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, conflicts_with = "index")]
token: Option<String>, token: Option<String>,
@ -184,7 +190,15 @@ impl LoginCommand {
None => self.authenticate_device_flow(&project, &reqwest)?, None => self.authenticate_device_flow(&project, &reqwest)?,
}; };
println!("logged in as {}", get_token_login(&reqwest, &token)?.bold()); let token = if self.no_bearer {
println!("set token");
token
} else {
let token = format!("Bearer {token}");
println!("logged in as {}", get_token_login(&reqwest, &token)?.bold());
token
};
set_token(Some(&token))?; set_token(Some(&token))?;

View file

@ -3,6 +3,7 @@ use pesde::Project;
mod login; mod login;
mod logout; mod logout;
mod set_token_override;
mod whoami; mod whoami;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
@ -14,6 +15,8 @@ pub enum AuthCommands {
/// 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 AuthCommands {
@ -22,6 +25,7 @@ impl AuthCommands {
AuthCommands::Login(login) => login.run(project, reqwest), AuthCommands::Login(login) => login.run(project, reqwest),
AuthCommands::Logout(logout) => logout.run(), AuthCommands::Logout(logout) => logout.run(),
AuthCommands::WhoAmI(whoami) => whoami.run(reqwest), AuthCommands::WhoAmI(whoami) => whoami.run(reqwest),
AuthCommands::SetTokenOverride(set_token_override) => set_token_override.run(),
} }
} }
} }

View file

@ -0,0 +1,31 @@
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,3 +1,5 @@
use std::collections::BTreeMap;
use anyhow::Context; use anyhow::Context;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -15,8 +17,16 @@ pub struct CliConfig {
deserialize_with = "crate::util::deserialize_gix_url" deserialize_with = "crate::util::deserialize_gix_url"
)] )]
pub scripts_repo: gix::Url, pub scripts_repo: gix::Url,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub token: Option<String>, 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)>,
@ -31,7 +41,9 @@ impl Default for CliConfig {
scripts_repo: "https://github.com/daimond113/pesde-scripts" scripts_repo: "https://github.com/daimond113/pesde-scripts"
.try_into() .try_into()
.unwrap(), .unwrap(),
token: None, token: None,
token_overrides: Default::default(),
last_checked_updates: None, last_checked_updates: None,
} }

View file

@ -1,10 +1,14 @@
use std::{collections::HashSet, fs::create_dir_all, str::FromStr};
use anyhow::Context; use anyhow::Context;
use gix::bstr::BStr;
use pesde::{ use pesde::{
lockfile::DownloadedGraph, names::PackageNames, source::version_id::VersionId, Project, lockfile::DownloadedGraph, names::PackageNames, source::version_id::VersionId, Project,
}; };
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serializer};
use std::{
collections::{BTreeMap, HashSet},
fs::create_dir_all,
str::FromStr,
};
use crate::cli::auth::get_token; use crate::cli::auth::get_token;
@ -168,3 +172,27 @@ impl<V: FromStr<Err = E>, E: Into<anyhow::Error>, N: FromStr<Err = F>, F: Into<a
pub fn parse_gix_url(s: &str) -> Result<gix::Url, gix::url::parse::Error> { 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()
}

View file

@ -7,7 +7,11 @@
compile_error!("at least one of the features `roblox`, `lune`, or `luau` must be enabled"); compile_error!("at least one of the features `roblox`, `lune`, or `luau` must be enabled");
use crate::lockfile::Lockfile; use crate::lockfile::Lockfile;
use std::path::{Path, PathBuf}; use gix::sec::identity::Account;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
/// Downloading packages /// Downloading packages
pub mod download; pub mod download;
@ -43,8 +47,9 @@ 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 {
github_token: Option<String>, default_token: Option<String>,
git_credentials: Option<gix::sec::identity::Account>, token_overrides: HashMap<gix::Url, String>,
git_credentials: Option<Account>,
} }
impl AuthConfig { impl AuthConfig {
@ -53,30 +58,51 @@ impl AuthConfig {
AuthConfig::default() AuthConfig::default()
} }
/// Access the GitHub token /// Sets the default token
pub fn github_token(&self) -> Option<&str> { pub fn with_default_token<S: AsRef<str>>(mut self, token: Option<S>) -> Self {
self.github_token.as_deref() self.default_token = token.map(|s| s.as_ref().to_string());
self
} }
/// Access the git credentials /// Set the token overrides
pub fn git_credentials(&self) -> Option<&gix::sec::identity::Account> { pub fn with_token_overrides<I: IntoIterator<Item = (gix::Url, S)>, S: AsRef<str>>(
self.git_credentials.as_ref() mut self,
} tokens: I,
) -> Self {
/// Set the GitHub token self.token_overrides = tokens
pub fn with_github_token<S: AsRef<str>>(mut self, token: Option<S>) -> Self { .into_iter()
self.github_token = token.map(|s| s.as_ref().to_string()); .map(|(url, s)| (url, s.as_ref().to_string()))
.collect();
self self
} }
/// Set the git credentials /// Set the git credentials
pub fn with_git_credentials( pub fn with_git_credentials(mut self, git_credentials: Option<Account>) -> Self {
mut self,
git_credentials: Option<gix::sec::identity::Account>,
) -> Self {
self.git_credentials = git_credentials; self.git_credentials = git_credentials;
self self
} }
/// Get the default token
pub fn default_token(&self) -> Option<&str> {
self.default_token.as_deref()
}
/// Get the token overrides
pub fn token_overrides(&self) -> &HashMap<gix::Url, String> {
&self.token_overrides
}
/// Get the git credentials
pub fn git_credentials(&self) -> Option<&Account> {
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

@ -10,6 +10,7 @@ use pesde::{AuthConfig, Project, MANIFEST_FILE_NAME};
use crate::cli::{ use crate::cli::{
auth::get_token, auth::get_token,
config::read_config,
home_dir, home_dir,
scripts::update_scripts_folder, scripts::update_scripts_folder,
version::{check_for_updates, current_version, get_or_download_version, max_installed_version}, version::{check_for_updates, current_version, get_or_download_version, max_installed_version},
@ -140,7 +141,9 @@ fn run() -> anyhow::Result<()> {
project_root_dir, project_root_dir,
data_dir, data_dir,
cas_dir, cas_dir,
AuthConfig::new().with_github_token(token.as_ref()), AuthConfig::new()
.with_default_token(token.clone())
.with_token_overrides(read_config()?.token_overrides),
); );
let reqwest = { let reqwest = {

View file

@ -277,9 +277,9 @@ impl PackageSource for PesdePackageSource {
let mut response = reqwest.get(url).header(ACCEPT, "application/octet-stream"); let mut response = reqwest.get(url).header(ACCEPT, "application/octet-stream");
if let Some(token) = &project.auth_config.github_token { if let Some(token) = project.auth_config.get_token(&self.repo_url) {
log::debug!("using token for pesde package download"); log::debug!("using token for pesde package download");
response = response.header("Authorization", format!("Bearer {token}")); response = response.header("Authorization", token);
} }
let response = response.send()?.error_for_status()?; let response = response.send()?.error_for_status()?;

View file

@ -184,9 +184,9 @@ impl PackageSource for WallyPackageSource {
.unwrap_or("0.3.2"), .unwrap_or("0.3.2"),
); );
if let Some(token) = &project.auth_config.github_token { if let Some(token) = project.auth_config.get_token(&self.repo_url) {
log::debug!("using token for wally package download"); log::debug!("using token for wally package download");
response = response.header("Authorization", format!("Bearer {token}")); response = response.header("Authorization", token);
} }
let response = response.send()?.error_for_status()?; let response = response.send()?.error_for_status()?;

View file

@ -30,6 +30,13 @@ pub fn serialize_gix_url<S: Serializer>(url: &gix::Url, serializer: S) -> Result
serializer.serialize_str(&url.to_bstring().to_string()) serializer.serialize_str(&url.to_bstring().to_string())
} }
pub fn deserialize_gix_url<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<gix::Url, D::Error> {
let s = String::deserialize(deserializer)?;
gix::Url::from_bytes(BStr::new(&s)).map_err(serde::de::Error::custom)
}
pub fn serialize_gix_url_map<S: Serializer>( pub fn serialize_gix_url_map<S: Serializer>(
url: &BTreeMap<String, gix::Url>, url: &BTreeMap<String, gix::Url>,
serializer: S, serializer: S,
@ -41,13 +48,6 @@ pub fn serialize_gix_url_map<S: Serializer>(
map.end() map.end()
} }
pub fn deserialize_gix_url<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<gix::Url, D::Error> {
let s = String::deserialize(deserializer)?;
gix::Url::from_bytes(BStr::new(&s)).map_err(serde::de::Error::custom)
}
pub fn deserialize_gix_url_map<'de, D: Deserializer<'de>>( pub fn deserialize_gix_url_map<'de, D: Deserializer<'de>>(
deserializer: D, deserializer: D,
) -> Result<BTreeMap<String, gix::Url>, D::Error> { ) -> Result<BTreeMap<String, gix::Url>, D::Error> {