mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: implement token overrides
This commit is contained in:
parent
a2865523a0
commit
30c9be8366
11 changed files with 159 additions and 41 deletions
|
@ -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()
|
||||||
|
|
|
@ -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))?;
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
src/cli/commands/auth/set_token_override.rs
Normal file
31
src/cli/commands/auth/set_token_override.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
62
src/lib.rs
62
src/lib.rs
|
@ -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
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
14
src/util.rs
14
src/util.rs
|
@ -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> {
|
||||||
|
|
Loading…
Reference in a new issue