mod flows;
mod jwt;
pub mod login;
pub mod oauth;
mod oauth_row;
pub mod refresh;
mod urlencode;
pub mod wire;
pub use oauth_row::{OAuthConfig, RedirectSpec};
pub use refresh::OAuth2Auth;
pub(crate) use urlencode::encode_pairs;
pub use wire::query_from_request_line;
#[cfg(test)]
pub(crate) use oauth::{is_expired, parse_token_response, AuthError, Grant, TokenResponse};
#[cfg(test)]
pub(crate) use wire::{build_authorize_url, build_token_exchange_request, parse_callback, Pkce};
use crate::canonical::{CanonicalError, ErrorKind};
use crate::config::provider::{HeaderScheme, HeaderSpec};
use crate::protocol::{ProviderCtx, WireRequest};
use crate::store::{AmbientSpec, Clock, Cred, CredStore, Secret};
use crate::transport::Transport;
pub trait Auth: Send + Sync {
fn apply(
&self,
wire: &mut WireRequest,
ctx: &ProviderCtx,
auth: &AuthCtx,
store: &dyn CredStore,
clock: &dyn Clock,
transport: &dyn Transport,
) -> Result<(), CanonicalError>;
}
pub struct AuthCtx<'a> {
pub store_key: &'a str,
pub inline_key: Option<&'a Secret>,
pub api_header: Option<&'a HeaderSpec>,
pub oauth: Option<&'a OAuthConfig>,
pub ambient: Option<&'a AmbientSpec>,
}
pub(crate) fn require_header<'a>(auth: &AuthCtx<'a>) -> Result<&'a HeaderSpec, CanonicalError> {
auth.api_header.ok_or_else(|| CanonicalError {
kind: ErrorKind::Config,
message: "keyed provider row has no api_header (should be caught at resolve)".to_owned(),
provider_detail: None,
retry_after_seconds: None,
})
}
pub struct NoAuth;
impl Auth for NoAuth {
fn apply(
&self,
_wire: &mut WireRequest,
_ctx: &ProviderCtx,
_auth: &AuthCtx,
_store: &dyn CredStore,
_clock: &dyn Clock,
_transport: &dyn Transport,
) -> Result<(), CanonicalError> {
Ok(())
}
}
pub(crate) fn set_auth_header(wire: &mut WireRequest, spec: &HeaderSpec, secret: &Secret) {
let value = match spec.scheme {
HeaderScheme::Raw => secret.expose().to_owned(),
HeaderScheme::Bearer => format!("Bearer {}", secret.expose()),
};
wire.set_header(&spec.name, &value);
}
pub(crate) fn auth_error(message: &str) -> CanonicalError {
CanonicalError {
kind: ErrorKind::Auth,
message: message.to_owned(),
provider_detail: None,
retry_after_seconds: None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum CredSource {
Owned,
Borrowed,
}
pub(super) struct FetchedCred {
pub(super) cred: Cred,
pub(super) source: CredSource,
}
pub(super) fn fetch_cred(store: &dyn CredStore, auth: &AuthCtx) -> Option<FetchedCred> {
store
.get(auth.store_key)
.map(|cred| FetchedCred {
cred,
source: CredSource::Owned,
})
.or_else(|| {
auth.ambient
.and_then(|spec| store.discover(spec))
.map(|cred| FetchedCred {
cred,
source: CredSource::Borrowed,
})
})
}
fn resolved_secret(store: &dyn CredStore, auth: &AuthCtx) -> Result<Secret, CanonicalError> {
if let Some(inline) = auth.inline_key {
return Ok(inline.clone());
}
match fetch_cred(store, auth).map(|fetched| fetched.cred) {
Some(Cred::ApiKey { key }) => Ok(key),
Some(Cred::Bearer { token }) => Ok(token),
Some(Cred::OAuth2 { .. }) => Err(auth_error(
"stored credential is OAuth2 but this provider is configured for an \
API key / bearer token; reconfigure the row or re-run `bz --login --provider <id>`",
)),
None => Err(auth_error(
"no credential for this provider: set BRAZEN_API_KEY (or the provider \
API-key env var / --api-key) or run `bz --login --provider <id>`",
)),
}
}
pub struct StaticSecretAuth;
impl Auth for StaticSecretAuth {
fn apply(
&self,
wire: &mut WireRequest,
_ctx: &ProviderCtx,
auth: &AuthCtx,
store: &dyn CredStore,
_clock: &dyn Clock,
_transport: &dyn Transport,
) -> Result<(), CanonicalError> {
let secret = resolved_secret(store, auth)?;
set_auth_header(wire, require_header(auth)?, &secret);
Ok(())
}
}