#[cfg(test)]
use std::string::String;
#[cfg(test)]
use std::vec::Vec;
use http::HeaderMap;
use super::{Actor, AuditEvent, Auth, AuthBuilder, AuthEvent, Result, require_admin};
use crate::store::{Session, User};
use crate::types::{Access, AuthError, CloudflareConfig};
impl AuthBuilder {
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "cloudflare")))]
pub fn cloudflare(mut self, config: Option<CloudflareConfig>) -> Self {
self.cloudflare = config;
self
}
}
impl Auth {
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "cloudflare")))]
pub async fn cloudflare(&self) -> Result<Option<CloudflareConfig>> {
let provider = &self.inner.cloudflare;
provider.load(self.store()).await?;
Ok(provider.settings())
}
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "cloudflare")))]
pub async fn set_cloudflare(
&self,
actor: &User,
session: &Session,
config: CloudflareConfig,
) -> Result<CloudflareConfig> {
require_admin(actor)?;
let (before, config) = self.inner.cloudflare.save(self.store(), config).await?;
tracing::info!(user = %actor.id, "Cloudflare Access settings saved");
if before.as_ref() != Some(&config) {
self.audit(AuditEvent::CloudflareChanged {
actor: Actor::of(actor, session),
before,
after: config.clone(),
});
}
Ok(config)
}
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "cloudflare")))]
pub async fn cloudflare_available(&self, headers: &HeaderMap) -> Result<bool> {
Ok(self.cloudflare_user(headers).await?.is_some())
}
#[cfg_attr(watchword_docsrs, doc(cfg(feature = "cloudflare")))]
pub async fn sign_in_cloudflare(&self, headers: &HeaderMap) -> Result<(User, Session)> {
let mut user = self
.cloudflare_user(headers)
.await?
.ok_or(AuthError::Unauthorized)?;
if user.access == Access::Pending {
let activated = self.store().activate(user.id).await?;
user = self
.store()
.user(user.id)
.await?
.ok_or(AuthError::Unauthorized)?;
if activated {
tracing::info!(user = %user.id, "Cloudflare Access activated a pending user");
self.emit(AuthEvent::UserChanged(user.info()));
self.audit(AuditEvent::Activated {
actor: Actor::System,
user: user.info(),
});
}
}
if user.access != Access::Active {
return Err(AuthError::Unauthorized.into());
}
let session = self.create_session(user.id).await?;
Ok((user, session))
}
async fn cloudflare_user(&self, headers: &HeaderMap) -> Result<Option<User>> {
let provider = &self.inner.cloudflare;
provider.load(self.store()).await?;
let Some(email) = provider.email(headers).await else {
return Ok(None);
};
let Some(user) = self.store().user_by_email(&email).await? else {
provider.warn(format!(
"Cloudflare Access sign-in failed: no user has the email {email}"
));
return Ok(None);
};
if user.access == Access::Revoked {
provider.warn(format!(
"Cloudflare Access sign-in failed: the user with the email {email} has had their access revoked"
));
return Ok(None);
}
Ok(Some(user))
}
#[cfg(test)]
pub(crate) fn cloudflare_warnings(&self) -> Vec<String> {
self.inner.cloudflare.warnings()
}
}
#[cfg(test)]
mod tests;