mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-31 00:29:24 +00:00
refactor: collapse GATEWAY_USER_ID into IRONCLAW_OWNER_ID
Remove the separate GATEWAY_USER_ID config. The gateway now uses
IRONCLAW_OWNER_ID (config.owner_id) directly for auth identity,
bootstrap user creation, and workspace scoping.
Previously, with_owner_scope() rebinds the auth identity to owner_id
while keeping default_sender_id as the gateway user_id. This caused
a FK constraint violation when creating users because the auth
identity ("default") didn't match any user in the DB ("nearai").
Changes:
- Remove GATEWAY_USER_ID env var and gateway_user_id from settings
- Remove user_id field from GatewayConfig
- Add owner_id parameter to GatewayChannel::new()
- Remove with_owner_scope() method
- Remove default_sender_id from GatewayState
- Remove sender override logic in chat/approval handlers
- Remove debug endpoint and tracing from prior debugging
- Update all tests and E2E fixtures
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
6cc77fe162
commit
82a9df1ebf
@@ -48,7 +48,6 @@ pub async fn users_create_handler(
|
||||
));
|
||||
}
|
||||
|
||||
tracing::info!("users_create: passed validation, building record");
|
||||
let user_id = Uuid::new_v4().to_string();
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
@@ -68,15 +67,10 @@ pub async fn users_create_handler(
|
||||
metadata: serde_json::json!({}),
|
||||
};
|
||||
|
||||
tracing::info!("users_create: calling create_user for {}", user_id);
|
||||
store
|
||||
.create_user(&user_record)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("users_create: create_user failed: {e}");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
||||
})?;
|
||||
tracing::info!("users_create: create_user succeeded");
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
// Generate a first API token so the new user can authenticate immediately.
|
||||
// Hash the hex-encoded plaintext (what the user sends as Bearer token),
|
||||
@@ -87,15 +81,10 @@ pub async fn users_create_handler(
|
||||
let token_hash = crate::channels::web::auth::hash_token(&plaintext_token);
|
||||
let token_prefix = &plaintext_token[..8];
|
||||
|
||||
tracing::info!("users_create: calling create_api_token");
|
||||
let _token_record = store
|
||||
.create_api_token(&user_id, "initial", &token_hash, token_prefix, None)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("users_create: create_api_token failed: {e}");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
||||
})?;
|
||||
tracing::info!("users_create: complete, returning response");
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"id": user_record.id,
|
||||
|
||||
+3
-17
@@ -73,7 +73,7 @@ impl GatewayChannel {
|
||||
///
|
||||
/// If no auth token is configured, generates a random one and prints it.
|
||||
/// Builds a single-user `MultiAuthState` from the config.
|
||||
pub fn new(config: GatewayConfig) -> Self {
|
||||
pub fn new(config: GatewayConfig, owner_id: String) -> Self {
|
||||
let auth_token = config.auth_token.clone().unwrap_or_else(|| {
|
||||
use rand::RngCore;
|
||||
use rand::rngs::OsRng;
|
||||
@@ -83,7 +83,7 @@ impl GatewayChannel {
|
||||
});
|
||||
|
||||
let auth = CombinedAuthState {
|
||||
env_auth: MultiAuthState::single(auth_token, config.user_id.clone()),
|
||||
env_auth: MultiAuthState::single(auth_token, owner_id.clone()),
|
||||
db_auth: None,
|
||||
};
|
||||
|
||||
@@ -101,8 +101,7 @@ impl GatewayChannel {
|
||||
job_manager: None,
|
||||
prompt_queue: None,
|
||||
scheduler: None,
|
||||
owner_id: config.user_id.clone(),
|
||||
default_sender_id: config.user_id.clone(),
|
||||
owner_id,
|
||||
shutdown_tx: tokio::sync::RwLock::new(None),
|
||||
ws_tracker: Some(Arc::new(ws::WsConnectionTracker::new())),
|
||||
llm_provider: None,
|
||||
@@ -126,18 +125,6 @@ impl GatewayChannel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebind the single-user auth identity to the durable owner scope while
|
||||
/// preserving the configured gateway sender/routing identity.
|
||||
pub fn with_owner_scope(mut self, owner_id: impl Into<String>) -> Self {
|
||||
let owner_id = owner_id.into();
|
||||
let single_user_token = self.auth.env_auth.first_token().map(ToOwned::to_owned);
|
||||
if let Some(token) = single_user_token {
|
||||
self.auth.env_auth = MultiAuthState::single(token, owner_id.clone());
|
||||
}
|
||||
self.rebuild_state(|s| s.owner_id = owner_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
@@ -156,7 +143,6 @@ impl GatewayChannel {
|
||||
prompt_queue: self.state.prompt_queue.clone(),
|
||||
scheduler: self.state.scheduler.clone(),
|
||||
owner_id: self.state.owner_id.clone(),
|
||||
default_sender_id: self.state.default_sender_id.clone(),
|
||||
shutdown_tx: tokio::sync::RwLock::new(None),
|
||||
ws_tracker: self.state.ws_tracker.clone(),
|
||||
llm_provider: self.state.llm_provider.clone(),
|
||||
|
||||
@@ -347,8 +347,6 @@ pub struct GatewayState {
|
||||
pub prompt_queue: Option<PromptQueue>,
|
||||
/// Durable owner scope for persistence and unauthenticated callback flows.
|
||||
pub owner_id: String,
|
||||
/// Default sender/routing identity for gateway-originated messages.
|
||||
pub default_sender_id: String,
|
||||
/// Shutdown signal sender.
|
||||
pub shutdown_tx: tokio::sync::RwLock<Option<oneshot::Sender<()>>>,
|
||||
/// WebSocket connection tracker.
|
||||
@@ -407,42 +405,6 @@ pub async fn start_server(
|
||||
// Public routes (no auth)
|
||||
let public = Router::new()
|
||||
.route("/api/health", get(health_handler))
|
||||
.route("/api/debug/db-write", get({
|
||||
let dbg_state = state.clone();
|
||||
move || async move {
|
||||
tracing::info!("debug/db-write: starting");
|
||||
let store = match dbg_state.store.as_ref() {
|
||||
Some(s) => s,
|
||||
None => return "ERROR: store is None".to_string(),
|
||||
};
|
||||
tracing::info!("debug/db-write: store is Some, attempting create_user");
|
||||
let id = format!("dbg-{}", uuid::Uuid::new_v4());
|
||||
let now = chrono::Utc::now();
|
||||
let user = crate::db::UserRecord {
|
||||
id: id.clone(),
|
||||
email: None,
|
||||
display_name: "debug-test".to_string(),
|
||||
status: "active".to_string(),
|
||||
role: "member".to_string(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
last_login_at: None,
|
||||
created_by: None,
|
||||
metadata: serde_json::json!({}),
|
||||
};
|
||||
match store.create_user(&user).await {
|
||||
Ok(()) => {
|
||||
tracing::info!("debug/db-write: create_user succeeded");
|
||||
let _ = store.delete_user(&id).await;
|
||||
format!("OK: created and deleted user {id}")
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("debug/db-write: create_user failed: {e}");
|
||||
format!("ERROR: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
.route("/oauth/callback", get(oauth_callback_handler))
|
||||
.route(
|
||||
"/oauth/slack/callback",
|
||||
@@ -1413,9 +1375,6 @@ async fn chat_send_handler(
|
||||
}
|
||||
|
||||
let mut msg = IncomingMessage::new("gateway", &user.user_id, &req.content);
|
||||
if state.owner_id != state.default_sender_id && user.user_id == state.owner_id {
|
||||
msg = msg.with_sender_id(&state.default_sender_id);
|
||||
}
|
||||
// Prefer timezone from JSON body, fall back to X-Timezone header
|
||||
let tz = req
|
||||
.timezone
|
||||
@@ -1517,9 +1476,6 @@ async fn chat_approval_handler(
|
||||
})?;
|
||||
|
||||
let mut msg = IncomingMessage::new("gateway", &user.user_id, content);
|
||||
if state.owner_id != state.default_sender_id && user.user_id == state.owner_id {
|
||||
msg = msg.with_sender_id(&state.default_sender_id);
|
||||
}
|
||||
|
||||
if let Some(ref thread_id) = req.thread_id {
|
||||
msg = msg.with_thread(thread_id);
|
||||
@@ -3095,7 +3051,6 @@ mod tests {
|
||||
job_manager: None,
|
||||
prompt_queue: None,
|
||||
owner_id: "test".to_string(),
|
||||
default_sender_id: "test".to_string(),
|
||||
shutdown_tx: tokio::sync::RwLock::new(None),
|
||||
ws_tracker: None,
|
||||
llm_provider: None,
|
||||
|
||||
@@ -77,7 +77,6 @@ impl TestGatewayBuilder {
|
||||
job_manager: None,
|
||||
prompt_queue: None,
|
||||
owner_id: self.user_id.clone(),
|
||||
default_sender_id: self.user_id,
|
||||
shutdown_tx: tokio::sync::RwLock::new(None),
|
||||
ws_tracker: Some(Arc::new(WsConnectionTracker::new())),
|
||||
llm_provider: self.llm_provider,
|
||||
|
||||
@@ -16,7 +16,6 @@ use axum::routing::{delete, get, post};
|
||||
use tower::ServiceExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::channels::web::GatewayChannel;
|
||||
use crate::channels::web::auth::{
|
||||
AuthenticatedUser, MultiAuthState, UserIdentity, auth_middleware,
|
||||
};
|
||||
@@ -24,7 +23,6 @@ use crate::channels::web::server::{
|
||||
ActiveConfigSnapshot, GatewayState, PerUserRateLimiter, PromptQueue, RateLimiter, WorkspacePool,
|
||||
};
|
||||
use crate::channels::web::sse::SseManager;
|
||||
use crate::config::GatewayConfig;
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -69,7 +67,6 @@ fn build_state(
|
||||
job_manager: None,
|
||||
prompt_queue,
|
||||
owner_id: "test".to_string(),
|
||||
default_sender_id: "test".to_string(),
|
||||
shutdown_tx: tokio::sync::RwLock::new(None),
|
||||
ws_tracker: None,
|
||||
llm_provider: None,
|
||||
@@ -88,40 +85,6 @@ fn build_state(
|
||||
})
|
||||
}
|
||||
|
||||
fn gateway_config() -> GatewayConfig {
|
||||
GatewayConfig {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 3000,
|
||||
auth_token: Some("gateway-auth".to_string()),
|
||||
user_id: "gateway-sender".to_string(),
|
||||
workspace_read_scopes: Vec::new(),
|
||||
memory_layers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_owner_scope_updates_gateway_owner_scope_in_multi_user_mode() {
|
||||
let mut gateway = GatewayChannel::new(gateway_config());
|
||||
gateway.auth = two_user_auth().into();
|
||||
let gateway = gateway.with_owner_scope("owner-scope");
|
||||
|
||||
assert_eq!(gateway.state.owner_id, "owner-scope");
|
||||
assert_eq!(gateway.state.default_sender_id, "gateway-sender");
|
||||
|
||||
let alice = gateway
|
||||
.auth
|
||||
.env_auth
|
||||
.authenticate("tok-alice")
|
||||
.expect("alice token should remain valid");
|
||||
let bob = gateway
|
||||
.auth
|
||||
.env_auth
|
||||
.authenticate("tok-bob")
|
||||
.expect("bob token should remain valid");
|
||||
assert_eq!(alice.user_id, "alice");
|
||||
assert_eq!(bob.user_id, "bob");
|
||||
}
|
||||
|
||||
/// Create a libSQL-backed test database in a temporary directory.
|
||||
///
|
||||
/// Returns the database and a `TempDir` guard — the database file is
|
||||
|
||||
@@ -521,7 +521,6 @@ mod tests {
|
||||
prompt_queue: None,
|
||||
scheduler: None,
|
||||
owner_id: "test".to_string(),
|
||||
default_sender_id: "test".to_string(),
|
||||
shutdown_tx: tokio::sync::RwLock::new(None),
|
||||
ws_tracker: Some(Arc::new(WsConnectionTracker::new())),
|
||||
llm_provider: None,
|
||||
|
||||
Reference in New Issue
Block a user