1pub mod validate;
2
3use serde::{Deserialize, Serialize};
4use utoipa::ToSchema;
5
6use crate::error::{CoreError, InternalError};
7
8use super::boot::AppConfig;
9use super::boot::cache::CacheDriver;
10use super::boot::database::DatabaseDriver;
11use super::validate::Validate;
12
13#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
14pub struct AnzarConfiguration {
15 pub app: App, pub database: Database, #[serde(default)]
18 pub server: Server, #[serde(default)]
20 pub auth: Authentication, pub security: Security, }
23
24impl AnzarConfiguration {
25 pub fn validate(&self) -> Result<(), CoreError> {
26 let mut errors = vec![];
27
28 if let Err(e) = self.auth.validate() {
29 errors.extend(e);
30 }
31 if let Err(e) = self.security.validate() {
32 errors.extend(e);
33 }
34
35 if errors.is_empty() {
36 Ok(())
37 } else {
38 Err(CoreError::Internal(InternalError::InvalidConfig(errors)))
39 }
40 }
41}
42
43impl AnzarConfiguration {
44 pub fn new(app_config: AppConfig) -> Self {
45 Self {
46 app: App {
47 environment: "dev".into(),
48 url: "localhost:3000".to_string(),
49 },
50 database: Database {
51 driver: app_config.database.driver,
52 connection_string: app_config.database.connection_string(),
53 cache: Cache {
54 driver: app_config.cache.driver,
55 url: app_config.cache.url,
56 },
57 },
58 server: Server::default(),
59 auth: Authentication {
60 strategy: app_config.auth,
61 ..Default::default()
62 },
63 security: Security {
64 secret_key: String::default(),
65 rate_limit: RateLimit {
66 enabled: true,
67 ip: RateLimitConfig::ip(),
68 strict: RateLimitConfig {
69 duration_minutes: 60,
70 capacity: 7,
71 },
72 default: RateLimitConfig::defaults(),
73 },
74 headers: vec![],
75 auth: AuthSecurity {
76 max_failed_attempts: 5,
77 lockout_duration: 1800,
78 },
79 },
80 }
81 }
82 pub fn with_appurl(mut self, url: &str) -> Self {
83 self.app.url = url.to_string();
84 self
85 }
86 pub fn with_secret(mut self, key: &str) -> Self {
87 self.security.secret_key = key.to_string();
88 self
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
96pub struct App {
97 pub environment: String,
98 pub url: String,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
105pub struct Database {
106 pub driver: DatabaseDriver,
107 pub connection_string: String,
108 pub cache: Cache,
109}
110impl Database {
111 pub fn name(&self) -> Option<&str> {
112 self.connection_string
113 .rsplit('/')
114 .next()
115 .and_then(|s| s.split('?').next())
116 }
117}
118#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
121pub struct Cache {
122 pub driver: CacheDriver,
123 pub url: String,
124}
125
126#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
130#[serde(default)]
131pub struct Server {
132 pub https: HttpsConfig,
133 pub cors: CorsConfig,
134}
135#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
138#[serde(default)]
139pub struct HttpsConfig {
140 pub enabled: bool,
141 pub port: u16,
142 pub cert_path: Option<String>,
143 pub key_path: Option<String>,
144}
145impl Default for HttpsConfig {
146 fn default() -> Self {
147 Self {
148 enabled: false,
149 port: 3000,
150 cert_path: None,
151 key_path: None,
152 }
153 }
154}
155#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
158#[serde(default)]
159pub struct CorsConfig {
160 pub enabled: bool,
161 pub allowed_origins: Vec<String>,
162 pub allowed_methods: Vec<String>,
163 pub allowed_headers: Vec<String>,
164 pub allow_credentials: bool,
165 pub max_age: u64,
166}
167impl Default for CorsConfig {
168 fn default() -> Self {
169 Self {
170 enabled: true,
171 allowed_origins: vec!["localhost:3000".into()],
172 allowed_methods: vec![
173 "GET".into(),
174 "POST".into(),
175 "PUT".into(),
176 "DELETE".into(),
177 "OPTIONS".into(),
178 ],
179 allowed_headers: vec![
180 "authorization".into(),
181 "content-type".into(),
182 "accept".into(),
183 "accept-language".into(),
184 "Content-Language".into(),
185 ],
186 allow_credentials: true,
187 max_age: 3600,
188 }
189 }
190}
191
192#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
196#[serde(default)]
197pub struct Authentication {
198 pub strategy: AuthStrategy,
199 pub email: EmailConfig,
200 pub password: PasswordConfig,
201 pub rbac: RbacConfig,
202}
203impl Authentication {
204 pub fn jwt(&self) -> Result<&JwtConfig, CoreError> {
205 match &self.strategy {
206 AuthStrategy::Jwt(config) => Ok(config),
207 _ => Err(CoreError::Internal(InternalError::MissingConfiguration(
208 "JWT strategy is required, but auth.strategy was not configured correctly".into(),
209 ))),
210 }
211 }
212 pub fn session(&self) -> Result<&SessionConfig, CoreError> {
213 match &self.strategy {
214 AuthStrategy::Session(config) => Ok(config),
215 _ => Err(CoreError::Internal(InternalError::MissingConfiguration(
216 "Session strategy is required, but auth.strategy was not configured correctly"
217 .into(),
218 ))),
219 }
220 }
221}
222#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
225#[serde(tag = "type")]
226pub enum AuthStrategy {
227 Session(SessionConfig),
228 Jwt(JwtConfig),
229}
230impl Default for AuthStrategy {
231 fn default() -> Self {
232 Self::Session(SessionConfig::default())
233 }
234}
235
236impl std::fmt::Display for AuthStrategy {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 match self {
239 AuthStrategy::Session(_) => write!(f, "Session"),
240 AuthStrategy::Jwt(_) => write!(f, "Jwt"),
241 }
242 }
243}
244#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
247#[serde(default)]
248pub struct JwtConfig {
249 pub algorithm: AlgorithmConfig,
250 pub access_token_expires_in: i64,
251 pub refresh_token_expires_in: i64,
252 pub issuer: String,
253 pub audience: String,
254}
255impl Default for JwtConfig {
256 fn default() -> Self {
257 Self {
258 algorithm: AlgorithmConfig::default(),
259 access_token_expires_in: 900,
260 refresh_token_expires_in: 604800,
261 issuer: String::new(),
262 audience: String::new(),
263 }
264 }
265}
266#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
268pub enum AlgorithmConfig {
269 ES256,
270 ES384,
271 #[default]
272 RS256,
273 RS384,
274 RS512,
275 PS256,
276 PS384,
277 PS512,
278 EdDSA,
279}
280impl AlgorithmConfig {
281 pub fn as_str(&self) -> &'static str {
282 match self {
283 AlgorithmConfig::ES256 => "ES256",
284 AlgorithmConfig::ES384 => "ES384",
285 AlgorithmConfig::RS256 => "RS256",
286 AlgorithmConfig::RS384 => "RS384",
287 AlgorithmConfig::RS512 => "RS512",
288 AlgorithmConfig::PS256 => "PS256",
289 AlgorithmConfig::PS384 => "PS384",
290 AlgorithmConfig::PS512 => "PS512",
291 AlgorithmConfig::EdDSA => "EdDSA",
292 }
293 }
294}
295impl From<AlgorithmConfig> for jsonwebtoken::Algorithm {
296 fn from(value: AlgorithmConfig) -> Self {
297 match value {
298 AlgorithmConfig::ES256 => jsonwebtoken::Algorithm::ES256,
299 AlgorithmConfig::ES384 => jsonwebtoken::Algorithm::ES384,
300 AlgorithmConfig::RS256 => jsonwebtoken::Algorithm::RS256,
301 AlgorithmConfig::RS384 => jsonwebtoken::Algorithm::RS384,
302 AlgorithmConfig::PS256 => jsonwebtoken::Algorithm::PS256,
303 AlgorithmConfig::PS384 => jsonwebtoken::Algorithm::PS384,
304 AlgorithmConfig::PS512 => jsonwebtoken::Algorithm::PS512,
305 AlgorithmConfig::RS512 => jsonwebtoken::Algorithm::RS512,
306 AlgorithmConfig::EdDSA => jsonwebtoken::Algorithm::EdDSA,
307 }
308 }
309}
310#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
313#[serde(default)]
314pub struct SessionConfig {
315 pub name: String,
316 pub max_age: u64,
317 pub secure: bool,
318 pub http_only: bool,
319 pub same_site: SameSiteConfig,
320}
321#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
322pub enum SameSiteConfig {
323 #[default]
324 Strict,
325 Lax,
326 None,
327}
328impl Default for SessionConfig {
329 fn default() -> Self {
330 Self {
331 name: "id".into(),
332 max_age: 3600,
333 secure: true,
334 http_only: true,
335 same_site: SameSiteConfig::default(),
336 }
337 }
338}
339
340#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
343#[serde(default)]
344pub struct EmailConfig {
345 pub verification: EmailVerification,
346}
347#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
349#[serde(default)]
350pub struct EmailVerification {
351 pub required: bool,
352 pub token_expires_in: i64, pub success_redirect: Option<String>,
354 pub error_redirect: Option<String>,
355}
356impl Default for EmailVerification {
357 fn default() -> Self {
358 Self {
359 required: false,
360 token_expires_in: 1800,
361 success_redirect: None,
362 error_redirect: None,
363 }
364 }
365}
366
367#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
370#[serde(default)]
371pub struct PasswordConfig {
372 pub algorithm: HashingAlgorithm,
373 pub requirements: PasswordRequirements,
374 pub reset: PasswordReset,
375}
376#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
378#[serde(tag = "type")]
379pub enum HashingAlgorithm {
380 Argon2 {
381 memory_kib: u32,
382 iterations: u32,
383 parallelism: u32,
384 },
385 Bcrypt {
386 cost: u32,
390 },
391}
392impl Default for HashingAlgorithm {
393 fn default() -> Self {
394 pub const DEFAULT_M_COST: u32 = 19 * 1024; pub const DEFAULT_T_COST: u32 = 2;
396 pub const DEFAULT_P_COST: u32 = 1;
397
398 Self::Argon2 {
399 memory_kib: DEFAULT_M_COST,
400 iterations: DEFAULT_T_COST,
401 parallelism: DEFAULT_P_COST,
402 }
403 }
404}
405
406#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
408#[serde(default)]
409pub struct PasswordRequirements {
410 pub min_length: u16,
411 pub max_length: u16,
412 pub require_uppercase: bool,
413 pub require_number: bool,
414 pub require_special_char: bool,
415}
416impl Default for PasswordRequirements {
417 fn default() -> Self {
418 Self {
419 min_length: 8,
420 max_length: 128,
421 require_uppercase: false,
422 require_number: false,
423 require_special_char: false,
424 }
425 }
426}
427#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
429#[serde(default)]
430pub struct PasswordReset {
431 pub token_expires_in: i64, pub success_redirect: Option<String>,
434 pub error_redirect: Option<String>,
435}
436impl Default for PasswordReset {
437 fn default() -> Self {
438 Self {
439 token_expires_in: 1800,
440 success_redirect: None,
441 error_redirect: None,
442 }
443 }
444}
445
446#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
449#[serde(default)]
450pub struct RbacConfig {
451 pub enabled: bool,
452 pub default_role: String,
453 pub roles: Vec<RoleConfig>,
454}
455
456#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
457pub struct RoleConfig {
458 pub name: String,
459 #[serde(default)]
460 pub inherits: Vec<String>,
461 pub permissions: Vec<String>,
462}
463
464impl Default for RbacConfig {
465 fn default() -> Self {
466 Self {
467 enabled: false,
468 default_role: "user".into(),
469 roles: vec![RoleConfig {
470 name: "user".into(),
471 inherits: vec![],
472 permissions: vec!["*:read".into()],
473 }],
474 }
475 }
476}
477
478#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
482pub struct Security {
483 #[serde(skip_serializing)]
484 pub secret_key: String,
485
486 #[serde(default)]
487 pub auth: AuthSecurity,
488
489 #[serde(default)]
490 pub rate_limit: RateLimit,
491
492 #[serde(default = "default_headers")]
493 pub headers: Vec<(String, String)>,
494}
495
496#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
498#[serde(default)]
499pub struct AuthSecurity {
500 pub max_failed_attempts: u8,
501 pub lockout_duration: i64,
502}
503impl Default for AuthSecurity {
504 fn default() -> Self {
505 Self {
506 max_failed_attempts: 5,
507 lockout_duration: 1800,
508 }
509 }
510}
511
512#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
515pub struct RateLimit {
516 #[serde(default)]
517 pub enabled: bool,
518
519 #[serde(default = "RateLimitConfig::ip")]
520 pub ip: RateLimitConfig,
521
522 #[serde(default = "RateLimitConfig::strict")]
523 pub strict: RateLimitConfig,
524
525 #[serde(default = "RateLimitConfig::defaults")]
526 pub default: RateLimitConfig,
527}
528
529impl Default for RateLimit {
530 fn default() -> Self {
531 Self {
532 enabled: false,
533 ip: RateLimitConfig::ip(),
534 strict: RateLimitConfig::strict(),
535 default: RateLimitConfig::defaults(),
536 }
537 }
538}
539
540#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, ToSchema)]
543pub struct RateLimitConfig {
544 pub capacity: u32,
545 pub duration_minutes: u32,
546}
547impl RateLimitConfig {
548 fn ip() -> RateLimitConfig {
549 RateLimitConfig {
550 duration_minutes: 1,
551 capacity: 100,
552 }
553 }
554 fn strict() -> RateLimitConfig {
555 RateLimitConfig {
556 duration_minutes: 60,
557 capacity: 5,
558 }
559 }
560 fn defaults() -> RateLimitConfig {
561 RateLimitConfig {
562 duration_minutes: 15,
563 capacity: 20,
564 }
565 }
566}
567
568fn default_headers() -> Vec<(String, String)> {
571 vec![
572 ("X-Content-Type-Options".into(), "nosniff".into()),
573 ("X-Frame-Options".into(), "DENY".into()),
574 ("X-XSS-Protection".into(), "0".into()),
575 ("Cache-Control".into(), "no-store".into()),
576 ("Pragma".into(), "no-cache".into()),
577 (
578 "Content-Security-Policy".into(),
579 "default-src 'self'".into(),
580 ),
581 ("Content-Type".into(), "application/json".into()),
582 (
583 "Strict-Transport-Security".into(),
584 "max-age=31536000".into(),
585 ),
586 ]
587}
588
589