1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
use std::collections::HashSet;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use crate::protocol::request::HttpMethod;
use crate::protocol::response::{Cookie, SameSite};
use crate::protocol::{response::Response};
use crate::routing::trie::RequestContext;
use crate::security::jwt::{Claims, JwtHandler};
use crate::security::rate_limit::RateLimiter;
use crate::security::session::{Session, SessionStore};
use crate::security::xss::Sanitizer;
use chrono::Local;
use colored::*;
use uuid::Uuid;
pub enum MiddlewareResult {
Next(Option<MiddlewareState>), // State can hold session data, claims, or both
Error(Response), // Stop and return error immediately
}
// A state packer to carry data down the pipe safely
pub struct MiddlewareState {
pub session: Option<Arc<Mutex<Session>>>,
pub claims: Option<Claims>,
pub session_was_stale: bool,
}
pub trait Middleware: Send + Sync {
fn execute(&self, ctx: &mut RequestContext) -> MiddlewareResult;
}
pub trait AfterRequestHook: Send + Sync {
fn call(&self, ctx: &RequestContext, status: u16, duration: std::time::Duration);
}
pub struct AuthMiddleware {
pub store: Arc<SessionStore>,
pub jwt_handler: Option<JwtHandler>,
pub public_paths: Vec<String>, // List of open routes
pub enable_csrf: bool,
pub redirect: Option<String>,
}
impl AuthMiddleware {
/// Pure Stateful Session Architecture (No JWTs)
pub fn new_session(public_paths: Vec<String>, redirect: Option<&str>) -> Self {
let session_store = Arc::new(SessionStore::new());
Self {
store: session_store,
jwt_handler: None,
public_paths,
enable_csrf: true,
redirect: redirect.map(|s| s.to_string()).or(None),
}
}
/// Pure Stateless JWT Architecture (Dummy empty session store bypassed)
pub fn new_jwt(
jwt_handler: JwtHandler,
public_paths: Vec<String>,
redirect: Option<&str>,
) -> Self {
Self {
store: Arc::new(SessionStore::new()),
jwt_handler: Some(jwt_handler),
public_paths,
enable_csrf: false,
redirect: redirect.map(|s| s.to_string()).or(None),
}
}
/// Internal helper to evaluate whether a path matches a whitelisted path rule
fn is_public(&self, incoming_path: &str) -> bool {
let incoming_segments: Vec<&str> =
incoming_path.split('/').filter(|s| !s.is_empty()).collect();
for rule in &self.public_paths {
let rule_segments: Vec<&str> = rule.split('/').filter(|s| !s.is_empty()).collect();
// Handle explicit root match ("/")
if rule == "/" && incoming_path == "/" {
return true;
}
let mut matches = true;
for (i, rule_seg) in rule_segments.iter().enumerate() {
// 1. FIXED: Explicitly look for your actual deep catch-all token "**"
if *rule_seg == "**" || rule_seg.starts_with(":*") {
return true;
}
// If the incoming path is shorter than the rule segment position, it's not a match
if i >= incoming_segments.len() {
matches = false;
break;
}
// 2. Single wildcard matches exactly ONE deep dynamic branch segment
if *rule_seg == "*" {
continue;
}
// Standard exact segment match evaluation
if rule_seg != &incoming_segments[i] {
matches = false;
break;
}
}
// If we checked all rule segments perfectly and length matches, it's a valid match
if matches && incoming_segments.len() == rule_segments.len() {
return true;
}
}
false
}
}
impl Middleware for AuthMiddleware {
fn execute(&self, ctx: &mut RequestContext) -> MiddlewareResult {
// -----------------------------------------------------------------
// STEP 1: IMMEDIATE PUBLIC ROUTE BYPASS
// -----------------------------------------------------------------
if let Some(ref redirect_path) = self.redirect {
// Prevent cascades if the path already matches or ends with the target redirect
if ctx.req.path == *redirect_path || ctx.req.path.ends_with(redirect_path) {
return MiddlewareResult::Next(None);
}
}
let is_public_route = self.is_public(&ctx.req.path);
println!(
"[AUTH MIDDLEWARE] Evaluating route: {} | Public: {}",
ctx.req.path, is_public_route
);
// If it's a public route (like /login or static files), skip CSRF and Auth checks entirely!
if is_public_route {
let mut associated_session = None;
// 1. Try to find an existing session from the browser's cookie jar
if let Some(sid) = ctx.get_signed_cookie("GSESSION_ID") {
if let Ok(store_guard) = self.store.sessions.lock() {
if let Some(session_ptr) = store_guard.get(&sid) {
associated_session = Some(Arc::clone(&session_ptr));
}
}
}
// 2. If no session exists, mint one on-the-fly right here so handlers can write to it!
if associated_session.is_none() && self.jwt_handler.is_none() {
if let Ok(store_guard) = self.store.sessions.lock() {
let new_sid = uuid::Uuid::new_v4().to_string();
let new_session = Arc::new(Mutex::new(Session {
id: new_sid.clone(),
data: std::collections::HashMap::new(),
user_id: None,
last_accessed: std::time::Instant::now(),
}));
store_guard.insert(new_sid.clone(), Arc::clone(&new_session));
let is_production =
crate::core::env::get_env("APP_ENV", "development") == "production";
let session_cookie = Cookie::new("GSESSION_ID", &new_sid)
.set_secure(is_production)
.set_same_site(SameSite::Lax);
ctx.set_signed_cookie(session_cookie);
associated_session = Some(new_session);
}
}
// Sync context state seamlessly
ctx.session = associated_session.clone();
return MiddlewareResult::Next(Some(MiddlewareState {
session: associated_session,
claims: None,
session_was_stale: false,
}));
}
// Absolute Clean Short-Circuit for Explicit Logout Requests
if ctx.req.path == "/logout" {
println!("[AUTH KERNEL] Intercepted explicit /logout pathway trigger.");
// If a session cookie is present, erase its record from the internal memory store
if let Some(session_id) = ctx.get_signed_cookie("GSESSION_ID") {
if let Ok(store_guard) = self.store.sessions.lock() {
store_guard.remove(&session_id);
println!(
"[AUTH KERNEL] Successfully removed session ID {} from memory pool.",
session_id
);
}
}
// Erase the cookie from the browser jar immediately
let mut delete_cookie = Cookie::new("GSESSION_ID", "");
delete_cookie.max_age = 0;
let is_production = crate::core::env::get_env("APP_ENV", "development") == "production";
let delete_cookie = delete_cookie
.set_secure(is_production)
.set_same_site(SameSite::Lax);
ctx.set_signed_cookie(delete_cookie);
// Prevent any further execution and immediately issue a 303 browser redirect
if let Some(ref redirect_path) = self.redirect {
return MiddlewareResult::Error(Response::redirect(303, redirect_path));
}
let res = Response::redirect(303, "/");
return MiddlewareResult::Error(res);
}
// -----------------------------------------------------------------
// STEP 2: RUN PRIVATE ROUTE LIFECYCLE (Sessions & CSRF Guard)
// -----------------------------------------------------------------
let mut active_session = None;
// --- CONDITIONAL SESSION LIEFOCYCLE ---
// Only run session logic if we are NOT in exclusive JWT stateless mode
let running_sessions =
self.jwt_handler.is_none() || ctx.get_signed_cookie("GSESSION_ID").is_some();
let mut cookie_was_stale = false;
if running_sessions {
let session_id: Option<String> = ctx.get_signed_cookie("GSESSION_ID");
let store_guard = match self.store.sessions.lock() {
Ok(guard) => guard,
Err(_) => {
return MiddlewareResult::Error(Response::new(
500,
Sanitizer::trust(
"<h1>500 Internal Server Error: Session Pool Poisoned</h1>",
),
));
}
};
if let Some(ref sid) = session_id {
if let Some(session_ptr) = store_guard.get(sid) {
ctx.session = Some(Arc::clone(&session_ptr));
active_session = Some(Arc::clone(&session_ptr));
} else {
// The browser sent a cookie, but it's dead on the server!
cookie_was_stale = true;
}
}
// Mint a session on-the-fly only if sessions are the primary auth method
if active_session.is_none() && self.jwt_handler.is_none() {
let new_sid = uuid::Uuid::new_v4().to_string();
let new_session = Arc::new(Mutex::new(Session {
id: new_sid.clone(),
data: std::collections::HashMap::new(),
user_id: None,
last_accessed: std::time::Instant::now(),
}));
store_guard.insert(new_sid.clone(), Arc::clone(&new_session));
let is_production =
crate::core::env::get_env("APP_ENV", "development") == "production";
let session_cookie = Cookie::new("GSESSION_ID", &new_sid)
.set_secure(is_production)
.set_same_site(SameSite::Lax);
ctx.set_signed_cookie(session_cookie);
ctx.session = Some(Arc::clone(&new_session));
active_session = Some(new_session);
}
// the browser jar to delete the cookie by setting its Max-Age to 0
if cookie_was_stale {
let mut delete_cookie = Cookie::new("GSESSION_ID", "");
delete_cookie.max_age = 0; // Instructs the browser to evict the key immediately
ctx.set_signed_cookie(delete_cookie);
}
drop(store_guard); // Release lock cleanly
// Only generate a token if the active session doesn't have one yet!
if let Some(ref session_arc) = active_session {
let mut session = session_arc.lock().unwrap();
if !session.data.contains_key("csrf_token") {
let fresh_token = uuid::Uuid::new_v4().to_string();
session.data.insert("csrf_token".to_string(), fresh_token);
println!(
"[CSRF KERNEL] Initialized unique persistent anti-forgery token for session context."
);
}
}
}
// --- : CSRF STATEFUL PROTECTION GUARD (Only runs on protected private routes) ---
if self.enable_csrf && self.jwt_handler.is_none() {
let method = ctx.req.method;
if method == HttpMethod::POST
|| method == HttpMethod::PUT
|| method == HttpMethod::PATCH
|| method == HttpMethod::DELETE
{
let mut csrf_verified = false;
// 1. Try extracting token out of custom AJAX headers first
let mut incoming_token: Option<String> = ctx
.headers
.get("x-csrf-token") // Header keys are typically lowercased by default
.map(|s| s.to_string());
// 2. Fallback to parsing the classic form body if header isn't used
if incoming_token.is_none() {
let form_data = ctx.req.parse_form_body();
if let Some(form_val) = form_data.fields.get("csrf_token") {
incoming_token = Some(form_val.to_string());
}
}
// 3. Cross-reference the discovered input against the global session cache
if let Some(ref session_arc) = active_session {
let session = session_arc.lock().unwrap();
if let Some(session_token) = session.data.get("csrf_token") {
if let Some(ref untrusted) = incoming_token {
println!(
"[CSRF GUARD] Comparing memory token [{}] against incoming challenge token [{}]",
session_token, untrusted
);
if session_token == untrusted {
csrf_verified = true;
}
}
}
}
if !csrf_verified {
println!(
"\x1b[31m[SECURITY ALERT] CSRF Validation Failed for Route: {}\x1b[0m",
ctx.req.path
);
// Use your elegant built-in polymorphic builder helper here!
return MiddlewareResult::Error(Response::forbidden(
&std::collections::HashMap::from([(
"error",
"GritShield: Anti-Forgery Token Validation Rejected.",
)]),
));
}
}
}
// --- PRIVATE ROUTE AUTHENTICATION EVALUATION ---
if let Some(ref session_arc) = active_session {
let session = session_arc.lock().unwrap();
if session.data.get("user_id").is_some() {
drop(session);
return MiddlewareResult::Next(Some(MiddlewareState {
session: active_session.clone(),
claims: None,
session_was_stale: false,
}));
}
}
// ----------------------------------------------------------------------------
// Strategy B: Fallback check against incoming JWT Bearer tokens
if let Some(jwt_handler) = &self.jwt_handler {
if let Some(auth_header) = ctx.headers.get("authorization") {
if auth_header.starts_with("Bearer ") {
let token = &auth_header[7..];
match jwt_handler.verify(token) {
Ok(claims) => {
println!("[AUTH] Verified user token: {}", claims.sub);
ctx.claims = Some(claims.clone());
return MiddlewareResult::Next(Some(MiddlewareState {
session: active_session.clone(), // Cloned safely
claims: Some(claims),
session_was_stale: false,
}));
}
Err(e) => {
println!("[AUTH] Token validation rejected: {}", e);
}
}
}
}
}
if let Some(ref redirect_path) = self.redirect {
return MiddlewareResult::Error(Response::redirect(303, redirect_path));
}
// // --- AUTHENTICATION FAILURE ---
let err_body = Sanitizer::trust("<h1>401 Unauthorized</h1><p>Access Denied.</p>");
MiddlewareResult::Error(Response::new(401, err_body))
}
}
pub struct LoggerMiddleware;
impl Middleware for LoggerMiddleware {
fn execute(&self, ctx: &mut RequestContext) -> MiddlewareResult {
let now = Local::now();
let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
println!(
"[{}] {} request to {}",
timestamp.green(),
format!("{:?}", ctx.req.method).blue(),
ctx.req.path.yellow()
);
MiddlewareResult::Next(None)
}
}
pub struct SessionMiddleware {
pub store: Arc<SessionStore>,
}
impl Middleware for SessionMiddleware {
fn execute(&self, ctx: &mut RequestContext) -> MiddlewareResult {
// pull the cryptographically signed cookie (Tamper-proof!)
let session_id = ctx.get_signed_cookie("session_id");
let store = match self.store.sessions.lock() {
Ok(guard) => guard,
Err(_) => {
return MiddlewareResult::Error(Response::new(
500,
Sanitizer::trust("<h1>500 Internal Server Error: Session Pool Poisoned</h1>"),
));
}
};
// Try to look up an existing valid session in our memory map
if let Some(ref sid) = session_id {
if let Some(session_ptr) = store.get(sid) {
// Attach the pointer copy directly to the active request context
ctx.session = Some(Arc::clone(&session_ptr));
return MiddlewareResult::Next(Some(MiddlewareState {
session: Some(Arc::clone(&session_ptr)),
claims: None,
session_was_stale: false,
}));
}
}
// No session found or it expired? Create one on the fly!
let new_sid = Uuid::new_v4().to_string();
let new_session = Arc::new(Mutex::new(Session {
id: new_sid.clone(),
data: std::collections::HashMap::new(),
user_id: None,
last_accessed: Instant::now(),
}));
// Insert into master framework memory tracking pool
store.insert(new_sid.clone(), Arc::clone(&new_session));
// Drop the secure signed cookie straight back into the browser's CookieJar
let is_production = crate::core::env::get_env("APP_ENV", "development") == "production";
let session_cookie = Cookie::new("session_id", &new_sid)
.set_secure(is_production) // Automatically true on prod, false on localhost HTTP
.set_same_site(SameSite::Lax);
if session_id == None {
ctx.set_signed_cookie(session_cookie);
}
// Bind the freshly minted session into the current request flow
ctx.session = Some(Arc::clone(&new_session));
MiddlewareResult::Next(Some(MiddlewareState {
session: Some(new_session),
claims: None,
session_was_stale: false,
}))
}
}
pub struct RateLimitMiddleware {
pub limiter: RateLimiter,
}
impl Middleware for RateLimitMiddleware {
fn execute(&self, ctx: &mut RequestContext) -> MiddlewareResult {
// SECURELY resolve the true user identity string
let client_ip = ctx.resolve_client_ip();
// logging to see who is making requests
println!(
"[GRITSHIELD RATE-LIMIT] Evaluating limits for bucket identifier: {}",
client_ip
);
if self.limiter.is_allowed(client_ip) {
// Pass execution forward down the routing chain
MiddlewareResult::Next(None)
} else {
// Client hit the ceiling limit! Push back with an explicit HTTP 429 back-off directive
let err_body = Sanitizer::trust(
"<h1>429 Too Many Requests</h1>\
<p>Slow down, friend. Your API bucket limits have been exhausted.</p>",
);
let mut res = Response::new(429, err_body);
// Instruct downstream agents/browsers exactly how long to wait before trying again
res.headers
.push(("Retry-After".to_string(), "60".to_string()));
res.headers.push((
"Content-Type".to_string(),
"text/html; charset=utf-8".to_string(),
));
MiddlewareResult::Error(res)
}
}
}
pub struct IPBlacklistMiddleware {
// Using HashSet for high-performance O(1) lookups
pub blacklisted_ips: HashSet<String>,
}
impl IPBlacklistMiddleware {
/// Constructor helper to instantiate the blacklist layer smoothly
pub fn new(ips: Vec<&str>) -> Self {
let mut set = HashSet::new();
for ip in ips {
set.insert(ip.to_string());
}
Self {
blacklisted_ips: set,
}
}
}
impl Middleware for IPBlacklistMiddleware {
fn execute(&self, ctx: &mut RequestContext) -> MiddlewareResult {
// Leverage your secure IP resolver from earlier
let client_ip = ctx.resolve_client_ip();
// Check the HashSet instantly
if self.blacklisted_ips.contains(&client_ip) {
eprintln!(
"[SECURITY ALERT] Blocked request attempt from blacklisted IP: {}",
client_ip
);
ctx.telemetry
.total_blocked_ips
.fetch_add(1, Ordering::SeqCst);
let err_body = Sanitizer::trust(
"<h1>403 Forbidden</h1>\
<p>Access denied. Your IP address has been blocked.</p>",
);
let mut res = Response::new(403, err_body);
res.headers.push((
"Content-Type".to_string(),
"text/html; charset=utf-8".to_string(),
));
// Drop connection right here! Do not proceed to handlers or next middlewares
MiddlewareResult::Error(res)
} else {
// All clear. Move down the execution pipeline chain smoothly
MiddlewareResult::Next(None)
}
}
}
pub struct MetricsTracker;
impl AfterRequestHook for MetricsTracker {
fn call(&self, ctx: &RequestContext, status: u16, _: std::time::Duration) {
if status >= 500 {
eprintln!(
"🚨 [ALERT] Critical server failure detected on path: {}",
ctx.req.path
);
}
}
}