Skip to main content

romance_core/addon/
security.rs

1use crate::addon::Addon;
2use anyhow::Result;
3use std::path::Path;
4
5pub struct SecurityAddon;
6
7impl Addon for SecurityAddon {
8    fn name(&self) -> &str {
9        "security"
10    }
11
12    fn check_prerequisites(&self, project_root: &Path) -> Result<()> {
13        super::check_romance_project(project_root)
14    }
15
16    fn is_already_installed(&self, project_root: &Path) -> bool {
17        project_root
18            .join("backend/src/middleware/security_headers.rs")
19            .exists()
20    }
21
22    fn install(&self, project_root: &Path) -> Result<()> {
23        install_security(project_root)
24    }
25
26    fn uninstall(&self, project_root: &Path) -> Result<()> {
27        use colored::Colorize;
28
29        println!("{}", "Uninstalling security middleware...".bold());
30
31        // Delete files
32        if super::remove_file_if_exists(
33            &project_root.join("backend/src/middleware/security_headers.rs"),
34        )? {
35            println!(
36                "  {} backend/src/middleware/security_headers.rs",
37                "delete".red()
38            );
39        }
40        if super::remove_file_if_exists(
41            &project_root.join("backend/src/middleware/rate_limit.rs"),
42        )? {
43            println!("  {} backend/src/middleware/rate_limit.rs", "delete".red());
44        }
45
46        // Remove middleware lines from routes/mod.rs
47        super::remove_line_from_file(
48            &project_root.join("backend/src/routes/mod.rs"),
49            "security_headers",
50        )?;
51        super::remove_line_from_file(
52            &project_root.join("backend/src/routes/mod.rs"),
53            "rate_limit_middleware",
54        )?;
55
56        // Remove lines from middleware/mod.rs
57        super::remove_line_from_file(
58            &project_root.join("backend/src/middleware/mod.rs"),
59            "security_headers",
60        )?;
61        super::remove_line_from_file(
62            &project_root.join("backend/src/middleware/mod.rs"),
63            "rate_limit",
64        )?;
65
66        // Remove [security] section from romance.toml
67        super::remove_toml_section(project_root, "security")?;
68
69        // Clean up middleware module if observability doesn't use it
70        let observability_installed = project_root
71            .join("backend/src/middleware/request_id.rs")
72            .exists();
73        if !observability_installed {
74            super::remove_file_if_exists(&project_root.join("backend/src/middleware/mod.rs"))?;
75            let _ = std::fs::remove_dir(project_root.join("backend/src/middleware"));
76            super::remove_mod_from_main(project_root, "middleware")?;
77        }
78
79        // Regenerate AI context
80        crate::ai_context::regenerate(project_root).ok();
81
82        println!();
83        println!(
84            "{}",
85            "Security middleware uninstalled successfully.".green().bold()
86        );
87
88        Ok(())
89    }
90}
91
92fn install_security(project_root: &Path) -> Result<()> {
93    use crate::template::TemplateEngine;
94    use crate::utils;
95    use colored::Colorize;
96    use tera::Context;
97
98    println!("{}", "Installing security middleware...".bold());
99
100    let engine = TemplateEngine::new()?;
101    let ctx = Context::new();
102
103    // Generate security headers middleware
104    let content = engine.render("addon/security/security_headers.rs.tera", &ctx)?;
105    utils::write_file(
106        &project_root.join("backend/src/middleware/security_headers.rs"),
107        &content,
108    )?;
109    println!(
110        "  {} backend/src/middleware/security_headers.rs",
111        "create".green()
112    );
113
114    // Generate rate limiter
115    let content = engine.render("addon/security/rate_limit.rs.tera", &ctx)?;
116    utils::write_file(
117        &project_root.join("backend/src/middleware/rate_limit.rs"),
118        &content,
119    )?;
120    println!(
121        "  {} backend/src/middleware/rate_limit.rs",
122        "create".green()
123    );
124
125    // Generate middleware mod.rs
126    let content = engine.render("addon/security/middleware_mod.rs.tera", &ctx)?;
127    utils::write_file(
128        &project_root.join("backend/src/middleware/mod.rs"),
129        &content,
130    )?;
131    println!("  {} backend/src/middleware/mod.rs", "create".green());
132
133    // Add mod middleware to main.rs
134    super::add_mod_to_main(project_root, "middleware")?;
135
136    // Inject middleware into routes/mod.rs
137    utils::insert_at_marker(
138        &project_root.join("backend/src/routes/mod.rs"),
139        "// === ROMANCE:MIDDLEWARE ===",
140        "        .layer(axum::middleware::from_fn(crate::middleware::security_headers::security_headers))",
141    )?;
142    utils::insert_at_marker(
143        &project_root.join("backend/src/routes/mod.rs"),
144        "// === ROMANCE:MIDDLEWARE ===",
145        "        .layer(axum::middleware::from_fn(crate::middleware::rate_limit::rate_limit_middleware))",
146    )?;
147
148    // Add dependencies
149    crate::generator::auth::insert_cargo_dependency(
150        &project_root.join("backend/Cargo.toml"),
151        &[
152            ("tower", r#"{ version = "0.5", features = ["limit", "timeout"] }"#),
153            ("governor", r#""0.7""#),
154            ("tower_governor", r#""0.5""#),
155            ("base64", r#""0.22""#),
156        ],
157    )?;
158
159    // Add per-user rate limit env vars (anonymous IP-based + authenticated user-based)
160    super::append_env_var(
161        &project_root.join("backend/.env"),
162        "RATE_LIMIT_ANON_RPM=30",
163    )?;
164    super::append_env_var(
165        &project_root.join("backend/.env"),
166        "RATE_LIMIT_AUTH_RPM=120",
167    )?;
168    super::append_env_var(
169        &project_root.join("backend/.env.example"),
170        "RATE_LIMIT_ANON_RPM=30",
171    )?;
172    super::append_env_var(
173        &project_root.join("backend/.env.example"),
174        "RATE_LIMIT_AUTH_RPM=120",
175    )?;
176
177    // Update romance.toml
178    let config_path = project_root.join("romance.toml");
179    let content = std::fs::read_to_string(&config_path)?;
180    if !content.contains("[security]") {
181        let new_content = format!(
182            "{}\n[security]\nrate_limit_anon_rpm = 30\nrate_limit_auth_rpm = 120\ncors_origins = [\"http://localhost:5173\"]\n",
183            content.trim_end()
184        );
185        std::fs::write(&config_path, new_content)?;
186    }
187
188    println!();
189    println!(
190        "{}",
191        "Security middleware installed successfully!".green().bold()
192    );
193    println!("  Security headers, per-user rate limiting, and CORS configured.");
194    println!("  Anonymous: {} RPM (IP-based), Authenticated: {} RPM (user-based).", 30, 120);
195    println!("  Configure in romance.toml under [security].");
196
197    Ok(())
198}