feat: multi-account user scope isolation#669
Open
kaizhou-lab wants to merge 149 commits into
Open
Conversation
added 30 commits
July 22, 2026 14:12
added 30 commits
July 22, 2026 14:12
…er-scope # Conflicts: # crates/aionui-system/src/client_pref.rs
- migrate legacy cron-source skill rows to system_default_user instead of
global; only builtin skills stay user_id NULL
- stop syncing cron skill dirs into the machine-level catalog and remove
the unauthenticated cron-dir fallback from both skill resolvers; cron
skills resolve only through a repo row owned by the requesting user
- own the skill-row lifecycle in CronService: save_skill upserts an
owner-scoped row named cron-{job_id}, delete_skill/remove_job soft-delete
it, and init() reconciles rows from disk per job owner while cleaning
legacy frontmatter-named duplicates
- make cron insert conversation-owner validation mode-aware: enforced for
existing-mode rows, skipped for new-conversation rows whose stale anchor
is re-validated by the executor before dispatch
- fix stale tests never run at full-suite scope: stream_relay skill
resolver mock now records the user-scoped hook; extension_e2e skill
listing/import tests updated to user-scoped skill storage
Office proxy routes require auth on this branch (preview ports are scoped to the active Core user; unauthenticated access is pinned to 401 by au2_unauthenticated_all_office_endpoints). The four proxy SSRF/root-path tests still asserted 403 without credentials and were tripped by the auth middleware first. They now log in and assert the SSRF port rejection as authenticated users, keeping their original intent.
The WS handshake now resolves the token's user against the users table (active row + session generation), so sign_token seeds an active Core user row before signing instead of minting tokens for nonexistent users. Fixed post-connect races by replacing fixed sleeps before client_count assertions and broadcasts with a bounded wait_for_clients poll.
The layered rule-read fallback migrated legacy-named files only in the user-scoped dir path; the legacy root branch skipped the locale-less retry, so a locale-specific read served the legacy file via the glob last-resort without migrating it to the encoded path. Mirror the scoped fallback in the legacy root branch so the migration side effect applies. Fixes generated_rule_with_requested_locale_falls_back_to_legacy_locale_less_path.
…er-scope # Conflicts: # crates/aionui-cron/src/service.rs
…er-scope # Conflicts: # crates/aionui-team/src/events.rs # crates/aionui-team/src/service.rs # crates/aionui-team/src/session.rs # crates/aionui-team/tests/session_service_integration.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduce per-user data isolation across AionCore so multiple Core users (AionUI local and AionPro external identities) can share one deployment without observing each other's business data, events, or runtime state.
Identity & auth
usersinto a Core User projection:user_type(local/aionpro),external_user_id,status,session_generationsystem_default_usersession_generation; disabling a user or revoking a session invalidates old tokens and tears down WebSocket connections, team/channel runtimes, conversation runtime, and file/office watches for exactly that userData layer
028_user_scope.sql: aggregate roots and independent config roots gainuser_id; pure child tables (messages, artifacts, acp sessions, snapshots, mailbox, team tasks, cron runs) authorize through unbypassable parent JOIN/EXISTS chainssystem_default_user; only explicit builtin/system template rows stay global (user_id IS NULL) — unknown owners never default to globaluser_idand filter in SQL; unscoped variants are marked*_systemand reachable only from scheduler/recovery/startup pathsRuntime boundaries
data.user_id, unscoped events are dropped, and only a 3-entry whitelist broadcasts globallyowner_user_idboundary; session binding requires the channel user and conversation to share one owner (CROSS_ACCOUNT_REFERENCEon mismatch)(user_id, extension_name); install/lifecycle stay machine-levelProduct decisions
Audit & test hardening
A full completion audit against the design acceptance criteria ran across 8 domains (auth, realtime, conversation/cron, config roots, team/runtime-tools, catalog, channel, migration/extension). It surfaced and fixed one real isolation defect (global cron-skill rows) plus ~25 stale tests that had never run at full-suite scope, and one implementation gap in the assistant locale fallback (fixed in the implementation, not the test).
Verification
just pushfull pre-push gate: migration check, fmt, clippy, workspace tests — 7083 passed, 0 failed (two consecutive full runs)user_idrejection, cross-account reference rejection (409/CROSS_ACCOUNT_REFERENCE), unauthenticated 401 / missing-CSRF 403 paths, WS event delivery isolation, revoke teardown per user, migration integrity on fresh and legacy databases