use crate::errors::Result;
use crate::errors::SigstoreError;
use crate::oauth::openidflow::{OpenIDAuthorize, RedirectListener};
use openidconnect::core::CoreIdToken;
pub const DEFAULT_CLIENT_ID: &str = "sigstore";
pub const DEFAULT_CLIENT_SECRET: &str = "";
pub const DEFAULT_ISSUER: &str = "https://oauth2.sigstore.dev/auth";
pub const DEFAULT_REDIRECT_PORT: u32 = 8080;
#[derive(Default)]
pub struct OauthTokenProvider {
client_id: Option<String>,
client_secret: Option<String>,
issuer: Option<String>,
redirect_port: Option<u32>,
}
impl OauthTokenProvider {
pub fn with_client_id(self, client_id: &str) -> Self {
Self {
client_id: Some(client_id.to_string()),
client_secret: self.client_secret,
issuer: self.issuer,
redirect_port: self.redirect_port,
}
}
pub fn with_client_secret(self, client_secret: &str) -> Self {
Self {
client_id: self.client_id,
client_secret: Some(client_secret.to_string()),
issuer: self.issuer,
redirect_port: self.redirect_port,
}
}
pub fn with_issuer(self, issuer: &str) -> Self {
Self {
client_id: self.client_id,
client_secret: self.client_secret,
issuer: Some(issuer.to_string()),
redirect_port: self.redirect_port,
}
}
pub fn with_redirect_port(self, port: u32) -> Self {
Self {
client_id: self.client_id,
client_secret: self.client_secret,
issuer: self.issuer,
redirect_port: Some(port),
}
}
fn redirect_url(&self) -> String {
format!(
"http://localhost:{}",
self.redirect_port.unwrap_or(DEFAULT_REDIRECT_PORT)
)
}
pub async fn get_token(&self) -> Result<(CoreIdToken, String)> {
let oidc_url = OpenIDAuthorize::new(
self.client_id
.as_ref()
.unwrap_or(&DEFAULT_CLIENT_ID.to_string()),
self.client_secret
.as_ref()
.unwrap_or(&DEFAULT_CLIENT_SECRET.to_string()),
self.issuer.as_ref().unwrap_or(&DEFAULT_ISSUER.to_string()),
&self.redirect_url(),
)
.auth_url_async()
.await;
match oidc_url.as_ref() {
Ok(url) => {
webbrowser::open(url.0.as_ref())?;
println!(
"Open this URL in a browser if it does not automatically open for you:\n{}\n",
url.0,
);
}
Err(e) => println!("{e}"),
}
let oidc_url = oidc_url?;
let result = RedirectListener::new(
&format!(
"127.0.0.1:{}",
self.redirect_port.unwrap_or(DEFAULT_REDIRECT_PORT)
),
oidc_url.1.clone(), oidc_url.2.clone(), oidc_url.3, )
.redirect_listener_async()
.await;
if let Ok((_, id_token)) = result {
let verifier = oidc_url.1.id_token_verifier();
let nonce = &oidc_url.2;
let claims = id_token.claims(&verifier, nonce);
if let Ok(claims) = claims
&& let Some(email) = claims.email()
{
let email = &**email;
return Ok((id_token.clone(), email.clone()));
}
}
Err(SigstoreError::NoIDToken)
}
}