mirror of
https://codeberg.org/Cyborus/forgejo-cli.git
synced 2024-11-10 03:59:31 +01:00
add oauth token support to keys file
This commit is contained in:
parent
98a82d04e7
commit
c47a24ad22
7 changed files with 90 additions and 32 deletions
18
src/auth.rs
18
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(":"))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
72
src/keys.rs
72
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 {
|
||||
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum LoginInfo {
|
||||
Token {
|
||||
name: String,
|
||||
key: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")?
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")?;
|
||||
|
|
12
src/repo.rs
12
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?;
|
||||
|
|
Loading…
Reference in a new issue