mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-29 08:59:31 +00:00
refactor: remove GATEWAY_USER_TOKENS, fix review feedback
GATEWAY_USER_TOKENS never went to production — replaced entirely by DB-backed user management via /api/admin/users and /api/tokens. Removed: - UserTokenConfig struct and GATEWAY_USER_TOKENS env var parsing - user_tokens field from GatewayConfig - GatewayChannel::new_multi_auth() constructor - Env-var user migration block in main.rs (~90 lines) - multi_tenant auto-detection from GATEWAY_USER_TOKENS (now runtime via db.has_any_users() in app.rs) Review fixes (zmanian): - User ID generation: UUID instead of display-name derivation (#1) - Invitation accept moved to public router (no auth needed) (#3) - libSQL get_invitation_by_hash aligned with postgres: filters status='pending' AND expires_at > now (#4) - UUID parse: returns DatabaseError::Serialization instead of unwrap_or_default (#7) - PostgreSQL SELECT * replaced with explicit column lists (#8) - Sort order aligned (both backends use DESC) (#6) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
@@ -69,6 +69,11 @@ impl MultiAuthState {
|
||||
}
|
||||
|
||||
/// Create a multi-user auth state from a map of tokens to identities.
|
||||
///
|
||||
/// **Test-only** — production multi-user auth is DB-backed via
|
||||
/// `DbAuthenticator`. This constructor is kept public (not `#[cfg(test)]`)
|
||||
/// because integration tests in `tests/` compile the crate as a library
|
||||
/// where `cfg(test)` is not set.
|
||||
pub fn multi(tokens: HashMap<String, UserIdentity>) -> Self {
|
||||
let hashed_tokens: Vec<([u8; 32], UserIdentity)> = tokens
|
||||
.into_iter()
|
||||
@@ -188,7 +193,7 @@ impl DbAuthenticator {
|
||||
/// Combined auth state: tries env-var tokens first, then DB-backed tokens.
|
||||
#[derive(Clone)]
|
||||
pub struct CombinedAuthState {
|
||||
/// In-memory tokens from GATEWAY_USER_TOKENS or GATEWAY_AUTH_TOKEN.
|
||||
/// In-memory tokens from GATEWAY_AUTH_TOKEN.
|
||||
pub env_auth: MultiAuthState,
|
||||
/// DB-backed token authenticator (optional — only when a database is available).
|
||||
pub db_auth: Option<DbAuthenticator>,
|
||||
|
||||
@@ -162,12 +162,7 @@ pub async fn invitations_accept_handler(
|
||||
return Err((StatusCode::GONE, "Invitation has expired".to_string()));
|
||||
}
|
||||
|
||||
// Generate a user id from the display name.
|
||||
let new_user_id = display_name
|
||||
.to_ascii_lowercase()
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join("-");
|
||||
let new_user_id = Uuid::new_v4().to_string();
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let user_record = UserRecord {
|
||||
|
||||
@@ -7,6 +7,7 @@ use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::channels::web::auth::AuthenticatedUser;
|
||||
use crate::channels::web::server::GatewayState;
|
||||
@@ -34,16 +35,7 @@ pub async fn users_create_handler(
|
||||
|
||||
let email = body.get("email").and_then(|v| v.as_str()).map(String::from);
|
||||
|
||||
// Generate user id: prefer email if provided, otherwise derive from display_name.
|
||||
let user_id = if let Some(ref e) = email {
|
||||
e.clone()
|
||||
} else {
|
||||
display_name
|
||||
.to_ascii_lowercase()
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join("-")
|
||||
};
|
||||
let user_id = Uuid::new_v4().to_string();
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let user_record = UserRecord {
|
||||
|
||||
@@ -124,49 +124,6 @@ impl GatewayChannel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a gateway channel with a pre-built multi-user auth state.
|
||||
pub fn new_multi_auth(config: GatewayConfig, auth: MultiAuthState) -> Self {
|
||||
let auth = CombinedAuthState {
|
||||
env_auth: auth,
|
||||
db_auth: None,
|
||||
};
|
||||
let state = Arc::new(GatewayState {
|
||||
msg_tx: tokio::sync::RwLock::new(None),
|
||||
sse: Arc::new(SseManager::new()),
|
||||
workspace: None,
|
||||
workspace_pool: None,
|
||||
session_manager: None,
|
||||
log_broadcaster: None,
|
||||
log_level_handle: None,
|
||||
extension_manager: None,
|
||||
tool_registry: None,
|
||||
store: None,
|
||||
job_manager: None,
|
||||
prompt_queue: None,
|
||||
scheduler: None,
|
||||
default_user_id: config.user_id.clone(),
|
||||
shutdown_tx: tokio::sync::RwLock::new(None),
|
||||
ws_tracker: Some(Arc::new(ws::WsConnectionTracker::new())),
|
||||
llm_provider: None,
|
||||
skill_registry: None,
|
||||
skill_catalog: None,
|
||||
chat_rate_limiter: server::PerUserRateLimiter::new(30, 60),
|
||||
oauth_rate_limiter: server::RateLimiter::new(10, 60),
|
||||
registry_entries: Vec::new(),
|
||||
cost_guard: None,
|
||||
routine_engine: Arc::new(tokio::sync::RwLock::new(None)),
|
||||
startup_time: std::time::Instant::now(),
|
||||
webhook_rate_limiter: server::RateLimiter::new(10, 60),
|
||||
active_config: server::ActiveConfigSnapshot::default(),
|
||||
});
|
||||
|
||||
Self {
|
||||
config,
|
||||
state,
|
||||
auth,
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to rebuild state, copying existing fields and applying a mutation.
|
||||
fn rebuild_state(&mut self, mutate: impl FnOnce(&mut GatewayState)) {
|
||||
let mut new_state = GatewayState {
|
||||
|
||||
@@ -417,6 +417,11 @@ pub async fn start_server(
|
||||
.route(
|
||||
"/api/webhooks/u/{user_id}/{path}",
|
||||
post(crate::channels::web::handlers::webhooks::webhook_trigger_user_scoped_handler),
|
||||
)
|
||||
// Invitation accept (public — validated by invite token, not user auth)
|
||||
.route(
|
||||
"/api/invitations/accept",
|
||||
post(super::handlers::invitations::invitations_accept_handler),
|
||||
);
|
||||
|
||||
// Protected routes (require auth)
|
||||
@@ -545,10 +550,6 @@ pub async fn start_server(
|
||||
get(super::handlers::invitations::invitations_list_handler)
|
||||
.post(super::handlers::invitations::invitations_create_handler),
|
||||
)
|
||||
.route(
|
||||
"/api/invitations/accept",
|
||||
post(super::handlers::invitations::invitations_accept_handler),
|
||||
)
|
||||
// Gateway control plane
|
||||
.route("/api/gateway/status", get(gateway_status_handler))
|
||||
// OpenAI-compatible API
|
||||
|
||||
Reference in New Issue
Block a user