add oauth token support to keys file

This commit is contained in:
Cyborus 2024-05-31 15:25:59 -04:00
parent 98a82d04e7
commit c47a24ad22
No known key found for this signature in database
7 changed files with 90 additions and 32 deletions

View file

@ -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(":"))
}

View file

@ -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 {

View file

@ -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)
}
}
}
}

View file

@ -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")?

View file

@ -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 {

View file

@ -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")?;

View file

@ -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?;