use crate::addon::Addon;
use anyhow::Result;
use std::path::Path;
pub struct SecurityAddon;
impl Addon for SecurityAddon {
fn name(&self) -> &str {
"security"
}
fn check_prerequisites(&self, project_root: &Path) -> Result<()> {
super::check_romance_project(project_root)
}
fn is_already_installed(&self, project_root: &Path) -> bool {
project_root
.join("backend/src/middleware/security_headers.rs")
.exists()
}
fn install(&self, project_root: &Path) -> Result<()> {
install_security(project_root)
}
fn uninstall(&self, project_root: &Path) -> Result<()> {
use colored::Colorize;
println!("{}", "Uninstalling security middleware...".bold());
if super::remove_file_if_exists(
&project_root.join("backend/src/middleware/security_headers.rs"),
)? {
println!(
" {} backend/src/middleware/security_headers.rs",
"delete".red()
);
}
if super::remove_file_if_exists(
&project_root.join("backend/src/middleware/rate_limit.rs"),
)? {
println!(" {} backend/src/middleware/rate_limit.rs", "delete".red());
}
super::remove_line_from_file(
&project_root.join("backend/src/routes/mod.rs"),
"security_headers",
)?;
super::remove_line_from_file(
&project_root.join("backend/src/routes/mod.rs"),
"rate_limit_middleware",
)?;
super::remove_line_from_file(
&project_root.join("backend/src/middleware/mod.rs"),
"security_headers",
)?;
super::remove_line_from_file(
&project_root.join("backend/src/middleware/mod.rs"),
"rate_limit",
)?;
super::remove_toml_section(project_root, "security")?;
let observability_installed = project_root
.join("backend/src/middleware/request_id.rs")
.exists();
if !observability_installed {
super::remove_file_if_exists(&project_root.join("backend/src/middleware/mod.rs"))?;
let _ = std::fs::remove_dir(project_root.join("backend/src/middleware"));
super::remove_mod_from_main(project_root, "middleware")?;
}
crate::ai_context::regenerate(project_root).ok();
println!();
println!(
"{}",
"Security middleware uninstalled successfully.".green().bold()
);
Ok(())
}
}
fn install_security(project_root: &Path) -> Result<()> {
use crate::template::TemplateEngine;
use crate::utils;
use colored::Colorize;
use tera::Context;
println!("{}", "Installing security middleware...".bold());
let engine = TemplateEngine::new()?;
let ctx = Context::new();
let content = engine.render("addon/security/security_headers.rs.tera", &ctx)?;
utils::write_file(
&project_root.join("backend/src/middleware/security_headers.rs"),
&content,
)?;
println!(
" {} backend/src/middleware/security_headers.rs",
"create".green()
);
let content = engine.render("addon/security/rate_limit.rs.tera", &ctx)?;
utils::write_file(
&project_root.join("backend/src/middleware/rate_limit.rs"),
&content,
)?;
println!(
" {} backend/src/middleware/rate_limit.rs",
"create".green()
);
let content = engine.render("addon/security/middleware_mod.rs.tera", &ctx)?;
utils::write_file(
&project_root.join("backend/src/middleware/mod.rs"),
&content,
)?;
println!(" {} backend/src/middleware/mod.rs", "create".green());
super::add_mod_to_main(project_root, "middleware")?;
utils::insert_at_marker(
&project_root.join("backend/src/routes/mod.rs"),
"// === ROMANCE:MIDDLEWARE ===",
" .layer(axum::middleware::from_fn(crate::middleware::security_headers::security_headers))",
)?;
utils::insert_at_marker(
&project_root.join("backend/src/routes/mod.rs"),
"// === ROMANCE:MIDDLEWARE ===",
" .layer(axum::middleware::from_fn(crate::middleware::rate_limit::rate_limit_middleware))",
)?;
crate::generator::auth::insert_cargo_dependency(
&project_root.join("backend/Cargo.toml"),
&[
("tower", r#"{ version = "0.5", features = ["limit", "timeout"] }"#),
("governor", r#""0.7""#),
("tower_governor", r#""0.5""#),
("base64", r#""0.22""#),
],
)?;
super::append_env_var(
&project_root.join("backend/.env"),
"RATE_LIMIT_ANON_RPM=30",
)?;
super::append_env_var(
&project_root.join("backend/.env"),
"RATE_LIMIT_AUTH_RPM=120",
)?;
super::append_env_var(
&project_root.join("backend/.env.example"),
"RATE_LIMIT_ANON_RPM=30",
)?;
super::append_env_var(
&project_root.join("backend/.env.example"),
"RATE_LIMIT_AUTH_RPM=120",
)?;
let config_path = project_root.join("romance.toml");
let content = std::fs::read_to_string(&config_path)?;
if !content.contains("[security]") {
let new_content = format!(
"{}\n[security]\nrate_limit_anon_rpm = 30\nrate_limit_auth_rpm = 120\ncors_origins = [\"http://localhost:5173\"]\n",
content.trim_end()
);
std::fs::write(&config_path, new_content)?;
}
println!();
println!(
"{}",
"Security middleware installed successfully!".green().bold()
);
println!(" Security headers, per-user rate limiting, and CORS configured.");
println!(" Anonymous: {} RPM (IP-based), Authenticated: {} RPM (user-based).", 30, 120);
println!(" Configure in romance.toml under [security].");
Ok(())
}