romance_core/addon/
security.rs1use 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 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 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 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 super::remove_toml_section(project_root, "security")?;
68
69 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 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 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 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 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 super::add_mod_to_main(project_root, "middleware")?;
135
136 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 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 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 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}