Cursor: Apply local changes for cloud agent
This commit is contained in:
@@ -24,6 +24,7 @@ futures = { workspace = true }
|
||||
|
||||
# Server utilities
|
||||
dashmap = { workspace = true }
|
||||
governor = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
|
||||
@@ -10,8 +10,12 @@ use crate::error_codes::*;
|
||||
|
||||
pub const SESSION_TTL_SECS: u64 = 24 * 60 * 60; // 24 hours
|
||||
pub const PENDING_LOGIN_TTL_SECS: u64 = 300; // 5 minutes
|
||||
pub const RATE_LIMIT_WINDOW_SECS: u64 = 60;
|
||||
pub const RATE_LIMIT_MAX_ENQUEUES: u32 = 100;
|
||||
/// Maximum enqueues per second per token before GCRA rate limiting kicks in.
|
||||
pub const RATE_LIMIT_MAX_PER_SEC: std::num::NonZeroU32 =
|
||||
std::num::NonZeroU32::new(100).expect("RATE_LIMIT_MAX_PER_SEC must be non-zero");
|
||||
|
||||
/// Keyed GCRA rate limiter backed by DashMap (one bucket per session token).
|
||||
pub type RateLimiter = governor::DefaultKeyedRateLimiter<Vec<u8>>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AuthConfig {
|
||||
@@ -47,11 +51,6 @@ pub struct PendingLogin {
|
||||
pub created_at: u64,
|
||||
}
|
||||
|
||||
pub struct RateEntry {
|
||||
pub count: u32,
|
||||
pub window_start: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthContext {
|
||||
pub token: Vec<u8>,
|
||||
@@ -65,32 +64,14 @@ pub fn current_timestamp() -> u64 {
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
pub fn check_rate_limit(
|
||||
rate_limits: &DashMap<Vec<u8>, RateEntry>,
|
||||
token: &[u8],
|
||||
) -> Result<(), capnp::Error> {
|
||||
let now = current_timestamp();
|
||||
let mut entry = rate_limits.entry(token.to_vec()).or_insert(RateEntry {
|
||||
count: 0,
|
||||
window_start: now,
|
||||
});
|
||||
|
||||
if now - entry.window_start >= RATE_LIMIT_WINDOW_SECS {
|
||||
entry.count = 1;
|
||||
entry.window_start = now;
|
||||
} else {
|
||||
entry.count += 1;
|
||||
if entry.count > RATE_LIMIT_MAX_ENQUEUES {
|
||||
return Err(crate::error_codes::coded_error(
|
||||
E014_RATE_LIMITED,
|
||||
format!(
|
||||
"rate limit exceeded: {} enqueues in {}s window",
|
||||
RATE_LIMIT_MAX_ENQUEUES, RATE_LIMIT_WINDOW_SECS
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
/// Check the GCRA rate limit for a token. Returns an error if the token has exceeded the quota.
|
||||
pub fn check_rate_limit(limiter: &RateLimiter, token: &[u8]) -> Result<(), capnp::Error> {
|
||||
limiter.check_key(&token.to_vec()).map_err(|_| {
|
||||
crate::error_codes::coded_error(
|
||||
E014_RATE_LIMITED,
|
||||
format!("rate limit exceeded: max {} enqueues/s", RATE_LIMIT_MAX_PER_SEC),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_auth(
|
||||
|
||||
@@ -23,7 +23,7 @@ mod sql_store;
|
||||
mod tls;
|
||||
mod storage;
|
||||
|
||||
use auth::{AuthConfig, PendingLogin, RateEntry, SessionInfo};
|
||||
use auth::{AuthConfig, PendingLogin, RateLimiter, SessionInfo, RATE_LIMIT_MAX_PER_SEC};
|
||||
use config::{
|
||||
load_config, merge_config, validate_production_config, DEFAULT_DATA_DIR, DEFAULT_DB_PATH,
|
||||
DEFAULT_LISTEN, DEFAULT_STORE_BACKEND, DEFAULT_TLS_CERT, DEFAULT_TLS_KEY,
|
||||
@@ -215,13 +215,15 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let pending_logins: Arc<DashMap<String, PendingLogin>> = Arc::new(DashMap::new());
|
||||
let sessions: Arc<DashMap<Vec<u8>, SessionInfo>> = Arc::new(DashMap::new());
|
||||
let rate_limits: Arc<DashMap<Vec<u8>, RateEntry>> = Arc::new(DashMap::new());
|
||||
let rate_limiter: Arc<RateLimiter> = Arc::new(governor::RateLimiter::keyed(
|
||||
governor::Quota::per_second(RATE_LIMIT_MAX_PER_SEC),
|
||||
));
|
||||
|
||||
// Background cleanup task (expire sessions, pending logins, rate limits, and stale messages).
|
||||
// Background cleanup task (expire sessions, pending logins, and stale messages).
|
||||
// Governor's DashMapStateStore handles rate-limit cleanup automatically.
|
||||
spawn_cleanup_task(
|
||||
Arc::clone(&sessions),
|
||||
Arc::clone(&pending_logins),
|
||||
Arc::clone(&rate_limits),
|
||||
Arc::clone(&store),
|
||||
);
|
||||
|
||||
@@ -260,7 +262,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let opaque_setup = Arc::clone(&opaque_setup);
|
||||
let pending_logins = Arc::clone(&pending_logins);
|
||||
let sessions = Arc::clone(&sessions);
|
||||
let rate_limits = Arc::clone(&rate_limits);
|
||||
let rate_limiter = Arc::clone(&rate_limiter);
|
||||
let sealed_sender = effective.sealed_sender;
|
||||
|
||||
tokio::task::spawn_local(async move {
|
||||
@@ -272,7 +274,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
opaque_setup,
|
||||
pending_logins,
|
||||
sessions,
|
||||
rate_limits,
|
||||
rate_limiter,
|
||||
sealed_sender,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -84,7 +84,7 @@ impl NodeServiceImpl {
|
||||
));
|
||||
}
|
||||
|
||||
if let Err(e) = check_rate_limit(&self.rate_limits, &auth_ctx.token) {
|
||||
if let Err(e) = check_rate_limit(&self.rate_limiter, &auth_ctx.token) {
|
||||
// Audit: rate limit hit — do not log token or identity.
|
||||
tracing::warn!("rate_limit_hit");
|
||||
metrics::record_rate_limit_hit_total();
|
||||
|
||||
@@ -10,8 +10,7 @@ use tokio::sync::Notify;
|
||||
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
|
||||
|
||||
use crate::auth::{
|
||||
current_timestamp, AuthConfig, PendingLogin, RateEntry, SessionInfo,
|
||||
PENDING_LOGIN_TTL_SECS, RATE_LIMIT_WINDOW_SECS,
|
||||
current_timestamp, AuthConfig, PendingLogin, RateLimiter, SessionInfo, PENDING_LOGIN_TTL_SECS,
|
||||
};
|
||||
use crate::storage::Store;
|
||||
|
||||
@@ -143,7 +142,7 @@ pub struct NodeServiceImpl {
|
||||
pub opaque_setup: Arc<ServerSetup<OpaqueSuite>>,
|
||||
pub pending_logins: Arc<DashMap<String, PendingLogin>>,
|
||||
pub sessions: Arc<DashMap<Vec<u8>, SessionInfo>>,
|
||||
pub rate_limits: Arc<DashMap<Vec<u8>, RateEntry>>,
|
||||
pub rate_limiter: Arc<RateLimiter>,
|
||||
/// When true, enqueue does not require identity-bound session (Sealed Sender).
|
||||
pub sealed_sender: bool,
|
||||
}
|
||||
@@ -156,7 +155,7 @@ impl NodeServiceImpl {
|
||||
opaque_setup: Arc<ServerSetup<OpaqueSuite>>,
|
||||
pending_logins: Arc<DashMap<String, PendingLogin>>,
|
||||
sessions: Arc<DashMap<Vec<u8>, SessionInfo>>,
|
||||
rate_limits: Arc<DashMap<Vec<u8>, RateEntry>>,
|
||||
rate_limiter: Arc<RateLimiter>,
|
||||
sealed_sender: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -166,7 +165,7 @@ impl NodeServiceImpl {
|
||||
opaque_setup,
|
||||
pending_logins,
|
||||
sessions,
|
||||
rate_limits,
|
||||
rate_limiter,
|
||||
sealed_sender,
|
||||
}
|
||||
}
|
||||
@@ -180,7 +179,7 @@ pub async fn handle_node_connection(
|
||||
opaque_setup: Arc<ServerSetup<OpaqueSuite>>,
|
||||
pending_logins: Arc<DashMap<String, PendingLogin>>,
|
||||
sessions: Arc<DashMap<Vec<u8>, SessionInfo>>,
|
||||
rate_limits: Arc<DashMap<Vec<u8>, RateEntry>>,
|
||||
rate_limiter: Arc<RateLimiter>,
|
||||
sealed_sender: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let connection = connecting.await?;
|
||||
@@ -207,7 +206,7 @@ pub async fn handle_node_connection(
|
||||
opaque_setup,
|
||||
pending_logins,
|
||||
sessions,
|
||||
rate_limits,
|
||||
rate_limiter,
|
||||
sealed_sender,
|
||||
));
|
||||
|
||||
@@ -221,7 +220,6 @@ const MESSAGE_TTL_SECS: u64 = 7 * 24 * 60 * 60; // 7 days
|
||||
pub fn spawn_cleanup_task(
|
||||
sessions: Arc<DashMap<Vec<u8>, SessionInfo>>,
|
||||
pending_logins: Arc<DashMap<String, PendingLogin>>,
|
||||
rate_limits: Arc<DashMap<Vec<u8>, RateEntry>>,
|
||||
store: Arc<dyn Store>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
@@ -232,7 +230,7 @@ pub fn spawn_cleanup_task(
|
||||
|
||||
sessions.retain(|_, info| info.expires_at > now);
|
||||
pending_logins.retain(|_, pl| now - pl.created_at < PENDING_LOGIN_TTL_SECS);
|
||||
rate_limits.retain(|_, entry| now - entry.window_start < RATE_LIMIT_WINDOW_SECS * 2);
|
||||
// Rate limit cleanup is handled automatically by governor's DashMapStateStore.
|
||||
|
||||
match store.gc_expired_messages(MESSAGE_TTL_SECS) {
|
||||
Ok(n) if n > 0 => {
|
||||
|
||||
@@ -346,8 +346,12 @@ impl Store for FileBackedStore {
|
||||
channel_id: channel_id.to_vec(),
|
||||
recipient_key: recipient_key.to_vec(),
|
||||
};
|
||||
let seq = *inner.next_seq.entry(key.clone()).or_insert(0);
|
||||
*inner.next_seq.get_mut(&key).unwrap() = seq + 1;
|
||||
let seq = {
|
||||
let entry = inner.next_seq.entry(key.clone()).or_insert(0);
|
||||
let s = *entry;
|
||||
*entry = s + 1;
|
||||
s
|
||||
};
|
||||
inner.map.entry(key).or_default().push_back(SeqEntry { seq, data: payload });
|
||||
self.flush_delivery_map(&self.ds_path, &*inner)?;
|
||||
Ok(seq)
|
||||
|
||||
Reference in New Issue
Block a user