hero_agent: replace hardcoded anonymous user with real auth identity #92
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Deferred from #45 (Phase 3: User Identity).
The OSIS storage migration is complete (v0.7.2-dev), but all operations use hardcoded
"anonymous"as the user_id. This means:What needs to happen
"anonymous". Need middleware or helper to extract user identity from the auth token/session (hero_auth provides SSO sessions)routes.rs→agent.handle_message()→osis_store.*()— the plumbing exists (user_sidparameter) but is always"anonymous"list_conversations(),list_memories(),get_stats()already filter byuser_id— just needs real valuesFiles to modify
hero_agent_server/src/routes.rs"anonymous"hero_agent_server/src/main.rsRelated
Signed-off-by: mik-tf
Analysis — complete auth flow mapping
Investigated the full identity flow across hero_auth, hero_os, hero_agent, and hero_proxy.
Current state: two disconnected auth systems
The gap: The WASM shell authenticates via hero_osis identity service, gets a session token, but never sends it (or any identity) on subsequent API calls. hero_agent receives no auth header and hardcodes "anonymous" at 8 locations in routes.rs.
Where "anonymous" is hardcoded (hero_agent_server/src/routes.rs)
Fix options evaluated
Option A — Validate OSIS session token per request:
Option B — JWT via hero_auth (recommended):
Option C — Proxy injects identity header:
Recommended plan (Option B)
Step 1: WASM shell — obtain + store JWT after login
File: hero_os/crates/hero_os_app/src/components/login_screen.rs
Step 2: WASM shell — send JWT on API calls
File: hero_os/crates/hero_os_app/src/ (AI bar, any fetch calls to hero_agent)
Step 3: hero_agent — add JWT extraction middleware
File: hero_agent/crates/hero_agent_server/src/main.rs
Step 4: hero_agent — replace "anonymous" with real identity
File: hero_agent/crates/hero_agent_server/src/routes.rs
Step 5: Test multi-user isolation
Dependencies
Repos touched
Signed-off-by: mik-tf
Implementation Plan — JWT-based User Identity
Problem identified
Two disconnected auth systems exist:
The WASM shell authenticates via hero_osis but never sends identity on API calls. hero_agent has no auth middleware — everything is "anonymous".
Critical design finding
hero_auth's
/sso-loginendpoint currently discards the OSIS session data after validation. It only checks success/failure, then returns the same admin JWT for every user. This defeats multi-user isolation — all users would get the sameClaims.sub.The fix: modify
/sso-loginto extractpublic_keyfrom the validated OSIS session (which is the username in MVP), find-or-create a per-user hero_auth account, and return a JWT with that user's uniqueclient_idasClaims.sub.Architecture
Step 1: Fix hero_auth
/sso-login— per-user JWTsRepo:
hero_authFile:
src/handlers.rs(lines 21-133)authservice.validate_sessionJSON-RPC response to extractpublic_keyfrom the OSIS Sessionpublic_keyto find-or-create a hero_auth user (not always the admin)Claims.sub= that user'sclient_idStep 2: WASM shell — obtain + store JWT after login
Repo:
hero_osFiles:
auth_service.rs,storage.rsauthenticate_local()succeeds and OSIS session token is stored, call hero_auth/sso-loginwith the OSIS tokenhero_os_jwtsave_jwt()/load_jwt()/clear_jwt()helpers tostorage.rs/sso-loginrefresh; if that fails, redirect to loginStep 3: Send JWT on all hero_agent API calls
Repo:
hero_archipelagosFiles:
ai_service.rs,voice.rs,input_area.rs,island.rs,books/mod.rsAll fetch calls to
/hero_agent/*must includeAuthorization: Bearer <jwt>header:ai_service.rs, 1 invoice.rsinput_area.rs,island.rs,books/mod.rsStep 4: hero_agent — JWT extraction middleware
Repo:
hero_agentFiles:
Cargo.toml,main.rs,routes.rsjsonwebtokendependencyAuthorization: Bearer <token>headerHERO_SECRET(HS256, issuer="hero_auth")Claims.sub→ insertUserId(String)into request extensionsUserId("anonymous")(graceful degradation).layer()on the routerStep 5: Replace "anonymous" with extracted user_id
Repo:
hero_agentFile:
routes.rsCreate
fn get_user_id(extensions: &Extensions) -> Stringhelper. Replace all 8 hardcoded locations:stats()—get_stats("anonymous")messages()—get_or_create_conversation("anonymous", ...)messages()— response body"anonymous"memories()—memories.list("anonymous")chat()—user_sidfallbacklist_conversations()—list_conversations("anonymous")create_conversation()—create_conversation("anonymous", ...)voice_chat()—let user_sid = "anonymous"Step 6: Testing
Repos affected
hero_auth/sso-loginper-user JWT (~20 lines)hero_oshero_archipelagosAuthorizationheader to ~10 fetch siteshero_agentNot affected
Key design decisions
/sso-loginonce with stored OSIS token before redirecting to loginmake dist-clean-wasm(WASM shell + archipelago changes)Signed-off-by: mik-tf
Implementation Complete
Changes pushed to
developmentacross 4 repos:hero_auth —
/sso-loginnow extractspublic_keyfrom validated OSIS session, creates per-user accounts ({username}@sso.hero.local), returns user-specific JWT. First SSO user gets admin scope, subsequent users get write.hero_os — After OSIS login, exchanges session token for JWT via
/sso-login, stores in localStorage. Cleared on logout/expired sessions. Addedauth_url()config helper andgloo-netdependency.hero_archipelagos — All fetch calls to
/hero_agent/*now includeAuthorization: Bearer <jwt>header. Covers Rust/WASM (gloo_net) and inline JS for chat, conversations, voice TTS, and transcription.hero_agent — JWT middleware extracts
Claims.subas user identity (HS256, HERO_SECRET, issuer=hero_auth). Falls back to "anonymous" when no valid JWT present. All 8 hardcoded "anonymous" locations replaced.Test results
Signed-off-by: mik-tf