summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCyborus <cyborus@cyborus.xyz>2024-05-31 21:25:59 +0200
committerCyborus <cyborus@cyborus.xyz>2024-06-04 04:18:27 +0200
commitc47a24ad2276b210f25b5294e210f9e7fa1e3404 (patch)
treeacfcc6bc03ef80542f8c91267a42bbb32cf511cb
parentupdate to `forgejo-api` v0.3.0 (diff)
downloadforgejo-cli-c47a24ad2276b210f25b5294e210f9e7fa1e3404.tar.xz
forgejo-cli-c47a24ad2276b210f25b5294e210f9e7fa1e3404.zip
add oauth token support to keys file
-rw-r--r--src/auth.rs18
-rw-r--r--src/issues.rs4
-rw-r--r--src/keys.rs74
-rw-r--r--src/main.rs6
-rw-r--r--src/prs.rs4
-rw-r--r--src/release.rs4
-rw-r--r--src/repo.rs12
7 files changed, 90 insertions, 32 deletions
diff --git a/src/auth.rs b/src/auth.rs
index 1625414..1511fe8 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -39,8 +39,13 @@ impl AuthCommand {
None => crate::readline("new key: ").await?.trim().to_string(),
};
if keys.hosts.get(&user).is_none() {
- keys.hosts
- .insert(host, crate::keys::LoginInfo::new(user, key));
+ keys.hosts.insert(
+ host,
+ crate::keys::LoginInfo::Token {
+ name: user,
+ token: key,
+ },
+ );
} else {
println!("key for {} already exists", host);
}
@@ -57,3 +62,12 @@ impl AuthCommand {
Ok(())
}
}
+
+pub fn get_client_info_for(url: &url::Url) -> Option<(&'static str, &'static str)> {
+ let host = url.host_str()?;
+ let client_info = match (url.host_str()?, url.path()) {
+ ("codeberg.org", "/") => option_env!("CLIENT_INFO_CODEBERG"),
+ _ => None,
+ };
+ client_info.and_then(|info| info.split_once(":"))
+}
diff --git a/src/issues.rs b/src/issues.rs
index cf1abd7..a8e130d 100644
--- a/src/issues.rs
+++ b/src/issues.rs
@@ -121,10 +121,10 @@ pub enum ViewCommand {
}
impl IssueCommand {
- pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
+ pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
use IssueSubcommand::*;
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref())?;
- let api = keys.get_api(repo.host_url())?;
+ let api = keys.get_api(repo.host_url()).await?;
let repo = repo.name().ok_or_else(|| self.no_repo_error())?;
match self.command {
Create {
diff --git a/src/keys.rs b/src/keys.rs
index 72ab86d..90ac0dd 100644
--- a/src/keys.rs
+++ b/src/keys.rs
@@ -42,7 +42,7 @@ impl KeyInfo {
Ok(())
}
- pub fn get_login(&self, url: &Url) -> eyre::Result<&LoginInfo> {
+ pub fn get_login(&mut self, url: &Url) -> eyre::Result<&mut LoginInfo> {
let host_str = url
.host_str()
.ok_or_else(|| eyre!("remote url does not have host"))?;
@@ -54,32 +54,76 @@ impl KeyInfo {
let login_info = self
.hosts
- .get(&domain)
+ .get_mut(&domain)
.ok_or_else(|| eyre!("not signed in to {domain}"))?;
Ok(login_info)
}
- pub fn get_api(&self, url: &Url) -> eyre::Result<forgejo_api::Forgejo> {
- self.get_login(url)?.api_for(url).map_err(Into::into)
+ pub async fn get_api(&mut self, url: &Url) -> eyre::Result<forgejo_api::Forgejo> {
+ self.get_login(url)?.api_for(url).await.map_err(Into::into)
}
}
-#[derive(serde::Serialize, serde::Deserialize, Clone, Default)]
-pub struct LoginInfo {
- name: String,
- key: String,
+#[derive(serde::Serialize, serde::Deserialize, Clone)]
+#[serde(tag = "type")]
+pub enum LoginInfo {
+ Token {
+ name: String,
+ token: String,
+ },
+ OAuth {
+ name: String,
+ token: String,
+ refresh_token: String,
+ expires_at: time::OffsetDateTime,
+ },
}
impl LoginInfo {
- pub fn new(name: String, key: String) -> Self {
- Self { name, key }
- }
-
pub fn username(&self) -> &str {
- &self.name
+ match self {
+ LoginInfo::Token { name, .. } => name,
+ LoginInfo::OAuth { name, .. } => name,
+ }
}
- pub fn api_for(&self, url: &Url) -> Result<forgejo_api::Forgejo, forgejo_api::ForgejoError> {
- forgejo_api::Forgejo::new(forgejo_api::Auth::Token(&self.key), url.clone())
+ pub async fn api_for(&mut self, url: &Url) -> eyre::Result<forgejo_api::Forgejo> {
+ match self {
+ LoginInfo::Token { token, .. } => {
+ let api = forgejo_api::Forgejo::new(forgejo_api::Auth::Token(token), url.clone())?;
+ Ok(api)
+ }
+ LoginInfo::OAuth {
+ token,
+ refresh_token,
+ expires_at,
+ ..
+ } => {
+ if time::OffsetDateTime::now_utc() >= *expires_at {
+ let api = forgejo_api::Forgejo::new(forgejo_api::Auth::None, url.clone())?;
+ let (client_id, client_secret) = crate::auth::get_client_info_for(url)
+ .ok_or_else(|| {
+ eyre::eyre!("Can't refresh token; no client info for {url}. How did this happen?")
+ })?;
+ let response = api
+ .oauth_get_access_token(forgejo_api::structs::OAuthTokenRequest::Refresh {
+ refresh_token,
+ client_id,
+ client_secret,
+ })
+ .await?;
+ *token = response.access_token;
+ *refresh_token = response.refresh_token;
+ // A minute less, in case any weirdness happens at the exact moment it
+ // expires. Better to refresh slightly too soon than slightly too late.
+ let expires_in = std::time::Duration::from_secs(
+ response.expires_in.saturating_sub(60) as u64,
+ );
+ *expires_at = time::OffsetDateTime::now_utc() + expires_in;
+ }
+ let api = forgejo_api::Forgejo::new(forgejo_api::Auth::Token(token), url.clone())?;
+ Ok(api)
+ }
+ }
}
}
diff --git a/src/main.rs b/src/main.rs
index 92b547c..d24f4c4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -50,9 +50,9 @@ async fn main() -> eyre::Result<()> {
let host_name = args.host.as_deref();
// let remote = repo::RepoInfo::get_current(host_name, remote_name)?;
match args.command {
- Command::Repo(subcommand) => subcommand.run(&keys, host_name).await?,
- Command::Issue(subcommand) => subcommand.run(&keys, host_name).await?,
- Command::Pr(subcommand) => subcommand.run(&keys, host_name).await?,
+ Command::Repo(subcommand) => subcommand.run(&mut keys, host_name).await?,
+ Command::Issue(subcommand) => subcommand.run(&mut keys, host_name).await?,
+ Command::Pr(subcommand) => subcommand.run(&mut keys, host_name).await?,
Command::WhoAmI { remote } => {
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
.wrap_err("could not find host, try specifying with --host")?
diff --git a/src/prs.rs b/src/prs.rs
index bd9852f..535b62e 100644
--- a/src/prs.rs
+++ b/src/prs.rs
@@ -249,10 +249,10 @@ pub enum ViewCommand {
}
impl PrCommand {
- pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
+ pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
use PrSubcommand::*;
let repo = RepoInfo::get_current(host_name, self.repo(), self.remote.as_deref())?;
- let api = keys.get_api(repo.host_url())?;
+ let api = keys.get_api(repo.host_url()).await?;
let repo = repo.name().ok_or_else(|| self.no_repo_error())?;
match self.command {
Create {
diff --git a/src/release.rs b/src/release.rs
index dd9010c..b5a7566 100644
--- a/src/release.rs
+++ b/src/release.rs
@@ -116,10 +116,10 @@ pub enum AssetCommand {
}
impl ReleaseCommand {
- pub async fn run(self, keys: &KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> {
+ pub async fn run(self, keys: &mut KeyInfo, remote_name: Option<&str>) -> eyre::Result<()> {
let repo =
RepoInfo::get_current(remote_name, self.repo.as_deref(), self.remote.as_deref())?;
- let api = keys.get_api(&repo.host_url())?;
+ let api = keys.get_api(&repo.host_url()).await?;
let repo = repo
.name()
.ok_or_eyre("couldn't get repo name, try specifying with --repo")?;
diff --git a/src/repo.rs b/src/repo.rs
index 16bcc0b..7520af8 100644
--- a/src/repo.rs
+++ b/src/repo.rs
@@ -268,7 +268,7 @@ pub enum RepoCommand {
}
impl RepoCommand {
- pub async fn run(self, keys: &crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
+ pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
match self {
RepoCommand::Create {
repo,
@@ -287,7 +287,7 @@ impl RepoCommand {
}
}
let host = RepoInfo::get_current(host_name, None, None)?;
- let api = keys.get_api(host.host_url())?;
+ let api = keys.get_api(host.host_url()).await?;
let repo_spec = CreateRepoOption {
auto_init: Some(false),
default_branch: Some("main".into()),
@@ -343,7 +343,7 @@ impl RepoCommand {
}
RepoCommand::View { name, remote } => {
let repo = RepoInfo::get_current(host_name, name.as_deref(), remote.as_deref())?;
- let api = keys.get_api(&repo.host_url())?;
+ let api = keys.get_api(&repo.host_url()).await?;
let repo = repo
.name()
.ok_or_eyre("couldn't get repo name, please specify")?;
@@ -454,7 +454,7 @@ impl RepoCommand {
}
RepoCommand::Clone { repo, path } => {
let repo = RepoInfo::get_current(host_name, Some(&repo), None)?;
- let api = keys.get_api(&repo.host_url())?;
+ let api = keys.get_api(&repo.host_url()).await?;
let name = repo.name().unwrap();
let repo_data = api.repo_get(name.owner(), name.name()).await?;
@@ -544,14 +544,14 @@ impl RepoCommand {
}
RepoCommand::Star { repo } => {
let repo = RepoInfo::get_current(host_name, Some(&repo), None)?;
- let api = keys.get_api(&repo.host_url())?;
+ let api = keys.get_api(&repo.host_url()).await?;
let name = repo.name().unwrap();
api.user_current_put_star(name.owner(), name.name()).await?;
println!("Starred {}/{}!", name.owner(), name.name());
}
RepoCommand::Unstar { repo } => {
let repo = RepoInfo::get_current(host_name, Some(&repo), None)?;
- let api = keys.get_api(&repo.host_url())?;
+ let api = keys.get_api(&repo.host_url()).await?;
let name = repo.name().unwrap();
api.user_current_delete_star(name.owner(), name.name())
.await?;