use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::harness::Runtime;
use crate::module::Module;
use crate::ports::{Captcha, Port};
use crate::problem::Problem;
use crate::problems::SLUGS;
use crate::surface::{Audience, Surface};
use crate::venture::VentureEnv;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum RoutePolicy {
#[default]
Open,
HumanForm,
Signature,
SignedLink,
ApiKey,
}
impl RoutePolicy {
#[must_use]
pub fn is_guarded(self) -> bool {
self != Self::Open
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureVerification {
Payments,
Hmac {
secret: &'static str,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WriteGuards {
pub captcha_modules: Vec<String>,
pub signature_modules: Vec<String>,
pub payments_signature_modules: Vec<String>,
pub hmac_signature_modules: Vec<(String, &'static str)>,
pub signed_link_modules: Vec<String>,
pub has_public_writes: bool,
pub has_admin_routes: bool,
}
impl WriteGuards {
#[must_use]
pub fn collect(modules: &[Arc<dyn Module>]) -> Self {
let mut guards = Self::default();
for module in modules {
let surface: Surface = module.surface();
let (mut form, mut signature) = Self::surface_flags(&surface);
let mut signed_link = surface
.actions
.iter()
.any(|action| action.policy == RoutePolicy::SignedLink);
if !form && !signature && !signed_link && module.public_writes() {
match module.public_write_policy() {
RoutePolicy::Signature => signature = true,
RoutePolicy::SignedLink => signed_link = true,
RoutePolicy::ApiKey => {}
RoutePolicy::HumanForm | RoutePolicy::Open => form = true,
}
}
if form {
guards.captcha_modules.push(module.name().to_owned());
}
if signature {
guards.signature_modules.push(module.name().to_owned());
match module.signature_verification() {
SignatureVerification::Payments => {
guards
.payments_signature_modules
.push(module.name().to_owned());
}
SignatureVerification::Hmac { secret } => {
guards
.hmac_signature_modules
.push((module.name().to_owned(), secret));
}
}
}
if signed_link {
guards.signed_link_modules.push(module.name().to_owned());
}
if form || signature || signed_link || module.public_writes() {
guards.has_public_writes = true;
}
if Self::declares_admin_routes(&surface) {
guards.has_admin_routes = true;
}
}
guards
}
#[must_use]
pub fn from_surface(module: &str, surface: &Surface) -> Self {
let (form, signature) = Self::surface_flags(surface);
let signed_link = surface
.actions
.iter()
.any(|action| action.policy == RoutePolicy::SignedLink);
Self {
captcha_modules: form.then(|| module.to_owned()).into_iter().collect(),
signature_modules: signature.then(|| module.to_owned()).into_iter().collect(),
payments_signature_modules: signature.then(|| module.to_owned()).into_iter().collect(),
hmac_signature_modules: Vec::new(),
signed_link_modules: signed_link.then(|| module.to_owned()).into_iter().collect(),
has_public_writes: form || signature || signed_link,
has_admin_routes: Self::declares_admin_routes(surface),
}
}
fn surface_flags(surface: &Surface) -> (bool, bool) {
let mut form = false;
let mut signature = false;
for action in &surface.actions {
if action.demands_captcha() {
form = true;
}
if action.policy == RoutePolicy::Signature {
signature = true;
}
}
(form, signature)
}
fn declares_admin_routes(surface: &Surface) -> bool {
surface.actions.iter().any(|action| {
action.audience == Audience::Admin
|| action.path == "/admin"
|| action.path.starts_with("/admin/")
})
}
#[must_use]
pub fn needs_signer(&self) -> bool {
!self.signed_link_modules.is_empty()
}
#[must_use]
pub fn needs_captcha(&self) -> bool {
!self.captcha_modules.is_empty()
}
#[must_use]
pub fn needs_payments(&self) -> bool {
!self.payments_signature_modules.is_empty()
}
#[must_use]
pub fn hmac_signature_secrets(&self) -> Vec<(String, String)> {
self.hmac_signature_modules
.iter()
.map(|(module, suffix)| {
let key = crate::config::ModuleConfig::new(module, &crate::config::EmptyConfig)
.key(suffix);
(module.clone(), key)
})
.collect()
}
#[must_use]
pub fn needs_rate_limiter(&self) -> bool {
self.has_public_writes || self.has_admin_routes
}
}
#[must_use]
pub fn captcha_effective(runtime: Option<&Arc<dyn Runtime>>) -> bool {
runtime.is_some_and(|runtime| runtime.effectively_configured(Port::Captcha))
}
#[must_use]
pub fn payments_effective(runtime: Option<&Arc<dyn Runtime>>) -> bool {
runtime.is_some_and(|runtime| runtime.effectively_configured(Port::Payments))
}
#[must_use]
pub fn webhook_secret_readiness(
env: VentureEnv,
guards: &WriteGuards,
config: &dyn crate::config::Config,
) -> Vec<String> {
if env != VentureEnv::Production {
return Vec::new();
}
guards
.hmac_signature_secrets()
.iter()
.filter(|(_, key)| config.get(key).is_none_or(|value| value.trim().is_empty()))
.map(|(module, key)| {
format!(
"production venture verifies `{module}` webhook signatures with the \
webhook_signature HMAC scheme but `{key}` is not set — its handler refuses \
every delivery until the provider's signing secret is configured"
)
})
.collect()
}
#[must_use]
pub fn stated_reason(reason: Option<&str>) -> Option<&str> {
reason.map(str::trim).filter(|reason| !reason.is_empty())
}
pub const ALLOW_UNPROTECTED_WRITES: &str = "HARNESS_ALLOW_UNPROTECTED_WRITES";
#[must_use]
pub fn unprotected_writes_override(config: &dyn crate::config::Config) -> Option<String> {
config
.get(ALLOW_UNPROTECTED_WRITES)
.and_then(|raw| stated_reason(Some(raw.as_str())).map(str::to_owned))
}
pub const ALLOW_UNLIMITED_PUBLIC_ROUTES: &str = "HARNESS_ALLOW_UNLIMITED_PUBLIC_ROUTES";
#[must_use]
pub fn unlimited_public_routes_override(config: &dyn crate::config::Config) -> Option<String> {
config
.get(ALLOW_UNLIMITED_PUBLIC_ROUTES)
.and_then(|raw| stated_reason(Some(raw.as_str())).map(str::to_owned))
}
#[must_use]
pub fn deployed_env(compiled: VentureEnv, config: &dyn crate::config::Config) -> VentureEnv {
let declared = config
.get("ENV")
.as_deref()
.and_then(VentureEnv::parse)
.unwrap_or_default();
compiled.strictest(declared)
}
#[must_use]
pub fn env_disagreement(compiled: VentureEnv, deployed: VentureEnv) -> Option<String> {
(compiled != deployed).then(|| {
format!(
"this deployment declares ENV={} but the venture was compiled with \
VentureEnv::{compiled:?}: the production readiness gate at build time ran \
against the wrong environment. Treating it as {} — call \
`.env(VentureEnv::{deployed:?})` on the venture so the build refuses what \
the deployment cannot serve (issue #143)",
deployed.as_str(),
deployed.as_str(),
)
})
}
#[must_use]
pub fn signer_effective(runtime: Option<&Arc<dyn Runtime>>) -> bool {
runtime.is_some_and(|runtime| runtime.effectively_configured(Port::Signer))
}
#[must_use]
pub fn rate_limiter_effective(runtime: Option<&Arc<dyn Runtime>>) -> bool {
runtime.is_some_and(|runtime| runtime.effectively_configured(Port::RateLimiter))
}
#[must_use]
pub fn production_readiness(
env: VentureEnv,
guards: &WriteGuards,
runtime: Option<&Arc<dyn Runtime>>,
rate_limiter_ready: bool,
signer_ready: bool,
allow_no_captcha: Option<&str>,
allow_unlimited: Option<&str>,
) -> Vec<String> {
if env != VentureEnv::Production {
return Vec::new();
}
let mut errors = Vec::new();
if guards.needs_captcha()
&& !captcha_effective(runtime)
&& stated_reason(allow_no_captcha).is_none()
{
errors.push(format!(
"production venture has captcha-guarded public writes from [{}] but the Captcha \
port is not effectively configured: provide it on the runtime with a bound \
adapter (Turnstile needs its secret and expected hostname) — `fz doctor \
--allow-no-captcha <reason>` overrides this check for previews only, and \
{ALLOW_UNPROTECTED_WRITES} records the acceptance in a serving deployment",
guards.captcha_modules.join(", ")
));
}
if guards.needs_signer() && !signer_ready {
errors.push(format!(
"production venture has signed-link public writes from [{}] but the Signer port \
is not provided — the magic links, challenges and unsubscribe links those \
routes verify cannot be issued or checked without one (issue #143)",
guards.signed_link_modules.join(", ")
));
}
if guards.needs_payments() && !payments_effective(runtime) {
errors.push(format!(
"production venture has signature-guarded routes from [{}] that verify through \
Payments but the Payments port is not provided — those webhook deliveries cannot \
be verified (see the Inbox dedup ledger and the STRIPE_WEBHOOK_SECRET doctor \
rule; modules verifying with the webhook_signature HMAC scheme instead are \
gated on their own secret key, not this port)",
guards.payments_signature_modules.join(", ")
));
}
if guards.needs_rate_limiter()
&& !rate_limiter_ready
&& stated_reason(allow_unlimited).is_none()
{
errors.push(format!(
"production venture takes public writes or admin routes but the RateLimiter port \
is not resolved: admin bearer routes have no brute-force backstop behind the \
limiter (the fail-closed rule the sidecar forward runs) and every public write \
runs without a budget — resolve the binding so the runtime actually hands over \
a limiter, or set {ALLOW_UNLIMITED_PUBLIC_ROUTES} to a reason to serve \
unlimited (issue #437)"
));
}
errors
}
pub async fn verify_human_form(
captcha: Option<&Arc<dyn Captcha>>,
env: VentureEnv,
accepted_unprotected: bool,
token: Option<&str>,
remote_ip: Option<&str>,
instance: &str,
) -> Result<(), Problem> {
let refused = || Problem::new(&SLUGS.captcha_failed).instance(instance);
match captcha {
Some(captcha) => {
let Some(token) = token else {
return Err(refused());
};
match captcha.verify(token, remote_ip).await {
Ok(verdict) if verdict.ok => Ok(()),
_ => Err(refused()),
}
}
None if env == VentureEnv::Production && !accepted_unprotected => Err(refused()),
None => Ok(()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Migrations;
use crate::ports::{CaptchaError, Verdict};
use crate::surface::Action;
use async_trait::async_trait;
struct StubCaptcha {
ok: bool,
transport_error: bool,
}
#[async_trait]
impl Captcha for StubCaptcha {
async fn verify(
&self,
_token: &str,
_remote_ip: Option<&str>,
) -> Result<Verdict, CaptchaError> {
if self.transport_error {
return Err(CaptchaError::Transport("down".to_owned()));
}
Ok(Verdict {
ok: self.ok,
reason: None,
})
}
}
fn port(ok: bool, transport_error: bool) -> Arc<dyn Captcha> {
Arc::new(StubCaptcha {
ok,
transport_error,
})
}
#[pollster::test]
async fn verified_tokens_pass_in_every_environment() {
for env in [
VentureEnv::Development,
VentureEnv::Staging,
VentureEnv::Production,
] {
verify_human_form(
Some(&port(true, false)),
env,
false,
Some("token"),
Some("203.0.113.7"),
"request-1",
)
.await
.expect("a verified token passes");
}
}
#[pollster::test]
async fn refused_missing_or_unreachable_verifications_fail_closed() {
let good = port(true, false);
let refused = port(false, false);
let unreachable = port(true, true);
for env in [VentureEnv::Development, VentureEnv::Production] {
assert!(
verify_human_form(Some(&refused), env, false, Some("t"), None, "r")
.await
.is_err()
);
assert!(
verify_human_form(Some(&unreachable), env, false, Some("t"), None, "r")
.await
.is_err()
);
assert!(
verify_human_form(Some(&good), env, false, None, None, "r")
.await
.is_err()
);
}
}
#[pollster::test]
async fn absent_port_refuses_production_and_allows_lower_envs() {
assert!(
verify_human_form(None, VentureEnv::Production, false, Some("t"), None, "r")
.await
.is_err()
);
for env in [VentureEnv::Development, VentureEnv::Staging] {
assert!(
verify_human_form(None, env, false, Some("t"), None, "r")
.await
.is_ok()
);
}
}
struct Guarded {
name: &'static str,
surface: Surface,
public_writes: bool,
public_write_policy: RoutePolicy,
verification: SignatureVerification,
}
impl Module for Guarded {
fn name(&self) -> &'static str {
self.name
}
fn version(&self) -> &'static str {
"0.0.0-test"
}
fn requires(&self) -> &'static [Port] {
&[]
}
fn public_writes(&self) -> bool {
self.public_writes
}
fn public_write_policy(&self) -> RoutePolicy {
self.public_write_policy
}
fn signature_verification(&self) -> SignatureVerification {
self.verification
}
fn migrations(&self) -> Migrations {
Migrations::default()
}
fn validate_config(
&self,
_: &dyn crate::config::Config,
) -> Result<(), crate::config::ConfigError> {
Ok(())
}
fn surface(&self) -> Surface {
self.surface.clone()
}
fn router(&self, _: crate::ModuleContext) -> axum::Router {
axum::Router::new()
}
}
fn module(name: &'static str, actions: Vec<Action>, public_writes: bool) -> Arc<dyn Module> {
Arc::new(Guarded {
name,
surface: Surface {
actions,
views: vec![],
},
public_writes,
public_write_policy: RoutePolicy::HumanForm,
verification: SignatureVerification::Payments,
})
}
fn declaring(name: &'static str, policy: RoutePolicy) -> Arc<dyn Module> {
Arc::new(Guarded {
name,
surface: Surface {
actions: vec![],
views: vec![],
},
public_writes: true,
public_write_policy: policy,
verification: SignatureVerification::Payments,
})
}
#[test]
fn human_form_action_requires_captcha() {
let guards = WriteGuards::collect(&[module(
"forms",
vec![Action::post("join", "/").policy(RoutePolicy::HumanForm)],
false,
)]);
assert_eq!(guards.captcha_modules, vec!["forms".to_owned()]);
assert!(!guards.needs_payments());
}
#[test]
fn signature_action_never_requires_captcha() {
let guards = WriteGuards::collect(&[module(
"billing",
vec![Action::post("webhook", "/webhook").policy(RoutePolicy::Signature)],
true, )]);
assert!(guards.captcha_modules.is_empty());
assert_eq!(guards.signature_modules, vec!["billing".to_owned()]);
}
#[test]
fn undeclared_public_writer_falls_back_to_captcha() {
let guards = WriteGuards::collect(&[module("auth", Vec::new(), true)]);
assert_eq!(guards.captcha_modules, vec!["auth".to_owned()]);
}
#[test]
fn legacy_captcha_flag_alias_still_guards() {
let mut action = Action::post("join", "/");
action.captcha = true;
let guards = WriteGuards::collect(&[module("forms", vec![action], false)]);
assert_eq!(guards.captcha_modules, vec!["forms".to_owned()]);
}
#[test]
fn guarded_surface_does_not_need_the_fallback() {
let guards = WriteGuards::collect(&[module(
"waitlist",
vec![
Action::post("join", "/").captcha(),
Action::post("webhook", "/webhook").policy(RoutePolicy::Signature),
],
true,
)]);
assert_eq!(guards.captcha_modules, vec!["waitlist".to_owned()]);
assert_eq!(guards.signature_modules, vec!["waitlist".to_owned()]);
}
#[test]
fn development_venture_needs_nothing() {
let guards = WriteGuards::collect(&[module("forms", Vec::new(), true)]);
for env in [VentureEnv::Development, VentureEnv::Staging] {
assert!(
production_readiness(env, &guards, None, true, false, None, None).is_empty(),
"{env:?} must not gate"
);
}
}
#[test]
fn production_without_runtime_refuses_captcha_writes() {
let guards = WriteGuards::collect(&[module(
"forms",
vec![Action::post("join", "/").captcha()],
false,
)]);
let errors = production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
None,
None,
);
assert_eq!(errors.len(), 1);
assert!(errors[0].contains("forms"));
assert!(errors[0].contains("Captcha"));
assert!(
production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
Some("preview"),
None
)
.is_empty()
);
for nothing in ["", " ", "\t\n"] {
assert_eq!(
production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
Some(nothing),
None
)
.len(),
1,
"an empty reason waived the captcha gate: {nothing:?}"
);
}
}
#[test]
fn production_signature_without_payments_refuses() {
let guards = WriteGuards::collect(&[module(
"billing",
vec![Action::post("webhook", "/webhook").policy(RoutePolicy::Signature)],
false,
)]);
let errors = production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
None,
None,
);
assert_eq!(errors.len(), 1);
assert!(errors[0].contains("billing"));
assert!(errors[0].contains("Payments"));
assert_eq!(
production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
Some("preview"),
None
)
.len(),
1
);
}
use crate::config::MapConfig;
fn hmac_module(name: &'static str) -> Arc<dyn Module> {
Arc::new(Guarded {
name,
surface: Surface {
actions: vec![Action::post("webhook", "/webhook").policy(RoutePolicy::Signature)],
views: vec![],
},
public_writes: false,
public_write_policy: RoutePolicy::HumanForm,
verification: SignatureVerification::Hmac {
secret: "WEBHOOK_SECRET",
},
})
}
#[test]
fn an_hmac_signature_writer_needs_no_payments_and_names_its_secret_key() {
let guards = WriteGuards::collect(&[hmac_module("pos")]);
assert_eq!(guards.signature_modules, ["pos"]);
assert!(!guards.needs_payments());
assert_eq!(
guards.hmac_signature_secrets(),
vec![("pos".to_owned(), "POS_WEBHOOK_SECRET".to_owned())],
"the full key is composed through ModuleConfig's prefix rule"
);
}
#[test]
fn the_webhook_secret_gate_is_production_only() {
let guards = WriteGuards::collect(&[hmac_module("pos")]);
let set = MapConfig::from_pairs([("POS_WEBHOOK_SECRET", "whsec-test-dummy")]);
assert!(
production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
None,
None
)
.is_empty()
);
assert!(webhook_secret_readiness(VentureEnv::Production, &guards, &set).is_empty());
for config in [
MapConfig::default(),
MapConfig::from_pairs([("POS_WEBHOOK_SECRET", " ")]),
] {
let errors = webhook_secret_readiness(VentureEnv::Production, &guards, &config);
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(
errors[0].contains("pos") && errors[0].contains("POS_WEBHOOK_SECRET"),
"{}",
errors[0]
);
}
assert!(
webhook_secret_readiness(VentureEnv::Staging, &guards, &MapConfig::default())
.is_empty()
);
}
#[test]
fn a_mixed_venture_gates_payments_only_for_the_module_that_names_it() {
let surface = Surface {
actions: vec![Action::post("webhook", "/webhook").policy(RoutePolicy::Signature)],
views: vec![],
};
assert!(WriteGuards::from_surface("remote", &surface).needs_payments());
let guards = WriteGuards::collect(&[
hmac_module("pos"),
declaring("billing", RoutePolicy::Signature),
]);
assert!(guards.needs_payments());
let errors = production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
None,
None,
);
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(
errors[0].contains("billing") && !errors[0].contains("pos"),
"{}",
errors[0]
);
}
#[test]
fn the_deployment_environment_is_the_stricter_of_the_two() {
let deployed = MapConfig::from_pairs([("ENV", "production")]);
assert_eq!(
deployed_env(VentureEnv::Development, &deployed),
VentureEnv::Production,
);
let downgrade = MapConfig::from_pairs([("ENV", "development")]);
assert_eq!(
deployed_env(VentureEnv::Production, &downgrade),
VentureEnv::Production,
);
assert_eq!(
deployed_env(VentureEnv::Staging, &MapConfig::default()),
VentureEnv::Staging,
);
assert!(env_disagreement(VentureEnv::Production, VentureEnv::Production).is_none());
let note = env_disagreement(VentureEnv::Development, VentureEnv::Production)
.expect("a compiled-vs-deployed mismatch is reported");
assert!(note.contains("ENV=production"), "{note}");
assert!(!note.contains(" "), "{note}");
assert!(
note.contains("compiled with VentureEnv::Development: the production readiness gate"),
"{note}"
);
}
#[test]
fn a_surface_less_writer_declares_what_actually_guards_it() {
let guards = WriteGuards::collect(&[module("legacy", vec![], true)]);
assert_eq!(guards.captcha_modules, ["legacy"]);
assert!(!guards.needs_signer());
let guards = WriteGuards::collect(&[declaring("passkeys", RoutePolicy::SignedLink)]);
assert!(
guards.captcha_modules.is_empty(),
"a signed-link writer must not demand a captcha it never renders"
);
assert_eq!(guards.signed_link_modules, ["passkeys"]);
assert!(guards.needs_signer());
}
#[test]
fn a_signed_link_writer_still_has_a_production_requirement_of_its_own() {
let guards = WriteGuards::collect(&[declaring("passkeys", RoutePolicy::SignedLink)]);
let errors = production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
None,
None,
);
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(errors[0].contains("signed-link"), "{}", errors[0]);
assert!(errors[0].contains("passkeys"), "{}", errors[0]);
assert!(
production_readiness(VentureEnv::Staging, &guards, None, true, false, None, None)
.is_empty()
);
}
#[test]
fn a_production_venture_needs_a_resolved_signer() {
struct Advertises;
impl Runtime for Advertises {
fn provides(&self) -> Vec<Port> {
vec![Port::Signer]
}
}
let runtime: Arc<dyn Runtime> = Arc::new(Advertises);
let guards = WriteGuards::collect(&[declaring("passkeys", RoutePolicy::SignedLink)]);
let check = |env, signer_ready| {
production_readiness(env, &guards, Some(&runtime), true, signer_ready, None, None)
};
let errors = check(VentureEnv::Production, false);
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(errors[0].contains("Signer"), "{}", errors[0]);
assert!(!errors[0].contains(" "), "{}", errors[0]);
assert!(
errors[0].contains("but the Signer port is not provided"),
"{}",
errors[0]
);
assert!(check(VentureEnv::Production, true).is_empty());
assert!(check(VentureEnv::Development, false).is_empty());
}
#[test]
fn a_declared_policy_on_an_action_still_wins_over_the_fallback() {
let guards = WriteGuards::collect(&[Arc::new(Guarded {
name: "mixed",
surface: Surface {
actions: vec![Action::post("join", "/join").policy(RoutePolicy::HumanForm)],
views: vec![],
},
public_writes: true,
public_write_policy: RoutePolicy::SignedLink,
verification: SignatureVerification::Payments,
})]);
assert_eq!(guards.captcha_modules, ["mixed"]);
assert!(
guards.signed_link_modules.is_empty(),
"the module-level fallback must not override a declared action"
);
}
#[test]
fn admin_and_public_writes_are_recorded_on_the_guards() {
let guards = WriteGuards::collect(&[module(
"exports",
vec![Action::post("export", "/admin/export").audience(Audience::Admin)],
false,
)]);
assert!(guards.has_admin_routes);
assert!(!guards.has_public_writes);
assert!(guards.needs_rate_limiter());
let guards = WriteGuards::collect(&[module(
"landing",
vec![Action::get("console", "/admin/console")],
false,
)]);
assert!(guards.has_admin_routes);
let guards = WriteGuards::collect(&[module("legacy", Vec::new(), true)]);
assert!(guards.has_public_writes);
assert!(!guards.has_admin_routes);
let guards = WriteGuards::collect(&[module(
"billing",
vec![Action::post("webhook", "/webhook").policy(RoutePolicy::Signature)],
false,
)]);
assert!(guards.has_public_writes);
}
#[test]
fn a_production_venture_needs_a_resolved_rate_limiter() {
let guards = WriteGuards::collect(&[module(
"forms",
vec![Action::post("join", "/").captcha()],
false,
)]);
let unlimited = production_readiness(
VentureEnv::Production,
&guards,
None,
false,
false,
Some("preview"),
None,
);
assert_eq!(unlimited.len(), 1, "{unlimited:?}");
assert!(unlimited[0].contains("RateLimiter"), "{}", unlimited[0]);
assert!(
unlimited[0].contains(ALLOW_UNLIMITED_PUBLIC_ROUTES),
"{}",
unlimited[0]
);
assert!(
production_readiness(
VentureEnv::Production,
&guards,
None,
true,
false,
Some("preview"),
None
)
.is_empty()
);
assert!(
production_readiness(
VentureEnv::Production,
&guards,
None,
false,
false,
Some("preview"),
Some("issue #437: between pivots"),
)
.is_empty()
);
for nothing in ["", " "] {
assert_eq!(
production_readiness(
VentureEnv::Production,
&guards,
None,
false,
false,
Some("preview"),
Some(nothing),
)
.len(),
1,
"an empty reason waived the limiter gate: {nothing:?}"
);
}
}
#[test]
fn a_module_with_only_admin_routes_still_needs_the_limiter() {
let guards = WriteGuards::collect(&[module(
"exports",
vec![Action::post("export", "/admin/export").audience(Audience::Admin)],
false,
)]);
assert!(guards.captcha_modules.is_empty());
let errors = production_readiness(
VentureEnv::Production,
&guards,
None,
false,
false,
None,
None,
);
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(errors[0].contains("RateLimiter"), "{}", errors[0]);
assert!(
errors[0].contains(ALLOW_UNLIMITED_PUBLIC_ROUTES),
"{}",
errors[0]
);
}
#[test]
fn an_api_key_policy_serializes_as_api_key_and_guards() {
assert_eq!(
serde_json::to_string(&RoutePolicy::ApiKey).expect("serializes"),
"\"api-key\""
);
let policy: RoutePolicy = serde_json::from_str("\"api-key\"").expect("deserializes");
assert_eq!(policy, RoutePolicy::ApiKey);
assert!(policy.is_guarded());
}
#[test]
fn an_api_key_writer_demands_no_captcha_and_no_port() {
let guards = WriteGuards::collect(&[module(
"devapi",
vec![Action::post("sync", "/sync").policy(RoutePolicy::ApiKey)],
false,
)]);
assert!(guards.captcha_modules.is_empty());
assert!(guards.signature_modules.is_empty());
assert!(guards.signed_link_modules.is_empty());
assert!(!guards.has_public_writes);
assert!(!guards.needs_rate_limiter());
let guards = WriteGuards::collect(&[declaring("devapi", RoutePolicy::ApiKey)]);
assert!(
guards.captcha_modules.is_empty(),
"an api-key writer must not demand a captcha it never renders"
);
assert!(!guards.needs_signer());
assert!(!guards.needs_payments());
}
}