mod flows;
mod jwt;
pub mod login;
pub mod oauth;
pub mod refresh;
mod urlencode;
pub mod wire;
use serde::{Deserialize, Serialize};
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,
})
}
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(())
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OAuthConfig {
pub authorize_url: String,
pub token_url: String,
#[serde(default)]
pub device_url: Option<String>,
pub client_id: String,
#[serde(default)]
pub scope: Option<String>,
#[serde(default)]
pub beta_headers: Vec<(String, String)>,
#[serde(default)]
pub system_preamble: Option<String>,
#[serde(default)]
pub redirect: RedirectSpec,
#[serde(default)]
pub authorize_params: Vec<(String, String)>,
#[serde(default)]
pub account_header: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RedirectSpec {
#[serde(default = "default_host")]
pub host: String,
#[serde(default)]
pub port: Option<u16>,
#[serde(default = "default_path")]
pub path: String,
}
fn default_host() -> String {
"127.0.0.1".to_owned()
}
fn default_path() -> String {
"/callback".to_owned()
}
impl Default for RedirectSpec {
fn default() -> Self {
RedirectSpec {
host: default_host(),
port: None,
path: default_path(),
}
}
}
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,
}
}
pub(crate) fn fetch_cred(store: &dyn CredStore, auth: &AuthCtx) -> Option<Cred> {
store
.get(auth.store_key)
.or_else(|| auth.ambient.and_then(|spec| store.discover(spec)))
}
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) {
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(())
}
}