fix: harden identity, live access, drafts and setup (review only)
Source-reviewed hardening changes and regression tests. UNBUILT / UNTESTED: not a deployable release. Go formatting, compilation, automated regression tests, full frontend validation and staging acceptance remain required. See docs/hardening/VALIDATION.md. Main remains the original clean snapshot; no upstream Git history imported.
This commit is contained in:
109
docs/hardening/CUSTOMER_IDENTITY_UPGRADE.md
Normal file
109
docs/hardening/CUSTOMER_IDENTITY_UPGRADE.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Customer identity and live-access upgrade
|
||||
|
||||
These are operator instructions for a reviewed staging rollout, not commands
|
||||
executed by this change. Back up data and verify the exact schema/engine before
|
||||
any production upgrade. Existing customer and conversation records are retained.
|
||||
|
||||
## Additive schema and issuer boundaries
|
||||
|
||||
`CustomerIdentity` adds:
|
||||
|
||||
- `identity_scope`: indexed varchar(80), default empty for legacy rows.
|
||||
- `scoped_key`: nullable varchar(64) with a unique index. Legacy rows remain NULL.
|
||||
|
||||
The normal GORM startup migration adds these definitions. No destructive index
|
||||
replacement, automatic legacy merge, or data deletion is supplied. Test this
|
||||
additive change on a backup with the deployment's actual SQLite/MySQL version.
|
||||
|
||||
A public identity scope is `v1:<jwt|hmac|guest>:SHA256(trimmed channel code)`.
|
||||
The server derives it; clients cannot select an issuer or inject this scope.
|
||||
The unique claim key is SHA-256 of the JSON array containing scope, external
|
||||
source and trimmed external ID. Use the implementation's
|
||||
`ScopedCustomerIdentityKey` helper rather than a hand-built delimiter format.
|
||||
New public registrations insert the claim in the same customer transaction.
|
||||
A competing claim rolls back rather than creating two authorized customers.
|
||||
|
||||
WebSocket routing uses canonical customer ID and channel record ID. Incoming
|
||||
subscriptions and outgoing recipients are checked against current ownership.
|
||||
Raw external IDs are not cross-channel or cross-trust routing keys.
|
||||
|
||||
## Existing sessions and history
|
||||
|
||||
Old customer-session JWTs without the new scope are rejected. The updated client
|
||||
must exchange a fresh token. Until legacy mappings are explicitly reconciled,
|
||||
a newly authenticated scoped identity can receive a separate customer record,
|
||||
not the old account's history. The original records remain available to
|
||||
appropriately authorized operators.
|
||||
|
||||
Before enabling traffic:
|
||||
|
||||
1. Back up data; stop exchanges/writes for the affected scope during reconciliation.
|
||||
2. Run the read-only `scripts/audit-customer-identity-scopes.sql` reports against an
|
||||
approved staging copy after the additive columns exist. Keep report output
|
||||
private: it contains customer identifiers.
|
||||
3. Establish channel and issuer ownership using trustworthy host-side evidence.
|
||||
Legacy `user` rows alone cannot distinguish JWT from HMAC provenance.
|
||||
4. For a proven unique mapping, review a transaction setting both its scope and
|
||||
computed claim key while retaining its customer ID and conversation history.
|
||||
Check all associated conversation channels before making a change.
|
||||
5. Stop on duplicate, inactive, multi-channel or mixed-trust mappings. Never take
|
||||
the first row or merge customers solely because external IDs match. If a new
|
||||
scoped record already exists, reconcile both histories explicitly.
|
||||
6. Reissue customer tokens and verify authorized history, isolation, and rollback
|
||||
behavior on the staging copy before resuming traffic.
|
||||
|
||||
A channel reassigned to a different identity issuer needs a new channel code.
|
||||
Ordinary signing-key rotation intentionally retains the same issuer scope.
|
||||
Use a distinct host JWT signing secret for each independently trusted channel.
|
||||
The existing host JWT contract validates the channel's secret and expiry, but
|
||||
does not require a channel audience claim. Reusing a signing key across unrelated
|
||||
channels lets the holder assert an identity to either channel; scoped storage
|
||||
is not a defense against a shared issuer key. HMAC proofs bind the channel code
|
||||
inside the signed payload. No mandatory JWT audience rollout is introduced here.
|
||||
Legacy duplicate repair remains an operator-reviewed migration, not an automatic
|
||||
startup side effect. The nullable unique claim allows dirty legacy rows to
|
||||
remain isolated without blocking new correctly scoped registrations.
|
||||
|
||||
## Anonymous and authenticated-only channels
|
||||
|
||||
Unsigned exchange issues a new random server-owned anonymous identity. Supplying
|
||||
an old guest external ID is not proof of ownership. Normal resume and refresh
|
||||
use the signed customer-session token. Near-expiry, still-valid credentials are
|
||||
renewed through the rate-limited `POST /api/customer/session_refresh` endpoint,
|
||||
which authenticates the existing session and never creates an anonymous identity.
|
||||
A refresh failure is not silently replaced with a fresh guest. Losing or expiring
|
||||
that credential does not authorize reclaiming history by an external ID.
|
||||
|
||||
An optional boolean `requireSignedIdentity` in a web channel's `configJson`
|
||||
rejects unsigned exchanges and existing guest sessions server-side. Default
|
||||
false retains anonymous channels. The SDK's `requireUserToken` is not a substitute
|
||||
for this policy.
|
||||
|
||||
Use the existing authenticated channel GET and
|
||||
`POST /api/dashboard/channel/update` flow with `channel.update` permission,
|
||||
preserving all unrelated configuration and required fields. No dedicated new UI
|
||||
toggle is provided. Older editors which omit this property preserve a configured
|
||||
true value; an explicit JSON false is needed to disable it. Null and nonboolean
|
||||
values are rejected. This source change does not configure a live channel.
|
||||
|
||||
## Staff and customer live-session revocation
|
||||
|
||||
Staff sockets bind their original login-session ID and absolute expiry, without
|
||||
serializing that internal metadata into API responses. Fresh session, account,
|
||||
role and permission state is checked for incoming operations, event fanout,
|
||||
actual queued writes and idle checks. Scope changes terminate the connection;
|
||||
reconnection must obtain fresh authority. Idle checks run every five seconds;
|
||||
data operations revalidate immediately rather than using a five-second cache.
|
||||
|
||||
Notification sockets cannot subscribe to conversation topics. Dashboard
|
||||
conversation sockets require conversation-view permission. Customer connections
|
||||
recheck token expiry, channel policy, customer state and exact identity mapping.
|
||||
|
||||
This implementation deliberately makes database reads at authorization
|
||||
boundaries. Measure the cost under realistic fanout; any later caching must have
|
||||
complete revocation invalidation rather than silently reintroducing stale access.
|
||||
|
||||
Channel configuration updates and token-secret resets compare the previously read
|
||||
configuration bytes and update timestamp before writing. A stale writer receives
|
||||
a conflict instead of silently overwriting a newly enabled signed-identity policy.
|
||||
Reload the channel and reapply the intended change after reviewing that conflict.
|
||||
63
docs/hardening/README.md
Normal file
63
docs/hardening/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Security and reliability hardening
|
||||
|
||||
This independent fork starts from the current source tree of
|
||||
[MonoVerseNS/agent-desk-ng](https://github.com/MonoVerseNS/agent-desk-ng), revision
|
||||
`b06954b0a495389cbb5194e0d388342eabe60636`. It does not import the old Git history.
|
||||
The upstream Apache-2.0 license and notices remain applicable. This document
|
||||
records changes made by this fork; it is not a claim of production certification.
|
||||
|
||||
## Release gate
|
||||
|
||||
The hardening changes must be reviewed and validated before deployment. Source
|
||||
inspection, a compiler check, a passing isolated test, and live acceptance are
|
||||
separate kinds of evidence. See `VALIDATION.md` for the actual checks performed.
|
||||
No production service, database, credentials, or external deployment is changed
|
||||
by committing this source code.
|
||||
|
||||
## Changes being integrated
|
||||
|
||||
- Separate guest, signed-user, issuer and channel identities. Use canonical
|
||||
customer/channel WebSocket topics and recheck recipients instead of treating
|
||||
a topic subscription as authorization.
|
||||
- Apply conversation-view permission to staff WebSockets; keep notifications
|
||||
separate. Revalidate session expiry, revocation, account state and permissions
|
||||
on long-lived connections. Disabled roles must no longer grant permissions.
|
||||
- Keep drafts and asynchronous responses bound to their identity and dialogue.
|
||||
Preserve drafts on failed sends, protect newly typed revisions, and prevent
|
||||
duplicate concurrent sends. Profile refresh and delayed API errors must not
|
||||
restore or expire the wrong signed-in account.
|
||||
- Turn setup repair into an explicit preview/apply operation. Never resurrect
|
||||
deleted configuration or silently introduce continuous working hours. Apply
|
||||
reviewed writes transactionally and dispatch only after commit.
|
||||
- Make empty setup, initial retry and standalone chat dismissal truthful and
|
||||
recoverable; remove bearer-token-bearing WebSocket URLs from browser logs.
|
||||
|
||||
## Upgrade precautions
|
||||
|
||||
Customer identity scoping is an additive schema and token-contract change, not
|
||||
only a frontend patch. Existing unscoped customer tokens will be rejected.
|
||||
Legacy identities and conversations must be inventoried and reconciled with
|
||||
trusted issuer/channel provenance before expecting their old history in new
|
||||
customer sessions. No automatic customer merge or destructive cleanup is
|
||||
appropriate. See `CUSTOMER_IDENTITY_UPGRADE.md` before any deployment.
|
||||
|
||||
New anonymous sessions are server-issued. Knowing an old anonymous external ID
|
||||
is not sufficient to reclaim its history. Preserve the signed customer-session
|
||||
credential for normal resume; loss or expiry can require a new anonymous session.
|
||||
|
||||
The setup quick-fix API now requires preview and explicit apply. Older callers
|
||||
must not treat a successful preview response as proof that configuration changed.
|
||||
See [the setup contract](SETUP_PREVIEW.md) before integrating custom callers.
|
||||
|
||||
## Verification priorities
|
||||
|
||||
1. Guest/user/channel collisions, unauthorized staff topics, revoked/expired
|
||||
sockets, disabled roles and concurrent identity registration.
|
||||
2. Wrong-recipient drafts, failed and duplicate sends, delayed responses after
|
||||
switching conversations or accounts, and logout during profile refresh.
|
||||
3. Preview/apply tampering, permission denial, rollback, deleted configuration,
|
||||
off-hours schedules, empty-channel simulation and dispatch failure reporting.
|
||||
4. Desktop and narrow-screen interaction checks in Russian, English and Chinese.
|
||||
|
||||
Use temporary, isolated data for checks. Do not run migration/repair commands
|
||||
against a live database merely to validate the code.
|
||||
56
docs/hardening/SETUP_PREVIEW.md
Normal file
56
docs/hardening/SETUP_PREVIEW.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Reviewed setup repairs
|
||||
|
||||
The existing authenticated `POST /api/dashboard/setup/quick_fix` endpoint now uses a two-phase contract. It does not write configuration unless the caller explicitly applies a current preview.
|
||||
|
||||
## Contract
|
||||
|
||||
1. Request a preview with `action` and, for an existing agent/channel/team, its `entity_id`. Omit `apply` or set it to `false`.
|
||||
2. Review `changes`. Each entry names the entity, operation, and exact operational fields before and after. New linked entities are identified by their proposed names until IDs are assigned. Audit timestamps and acting-user metadata are maintained automatically.
|
||||
3. Submit the same action and target with `apply: true` and the returned `preview_token`.
|
||||
|
||||
The response includes:
|
||||
|
||||
- `applied`: whether an explicit apply request completed. An empty `applied_actions` array means no configuration writes were needed.
|
||||
- `changes`: the reviewed plan; an empty array is a valid no-op, not a claim that setup is complete.
|
||||
- `preview_token`: a fingerprint of the acting user, action, target, proposed changes, and current setup configuration.
|
||||
- `applied_actions`: committed operation/entity identifiers, empty on preview.
|
||||
- `warning_codes`: manual follow-up requirements or post-commit failures.
|
||||
- `readiness`: the refreshed diagnostic, omitted if refreshing failed.
|
||||
|
||||
Old callers that omit `apply` receive a read-only preview. Callers must not interpret any HTTP success as a successful mutation. There is deliberately no compatibility path that silently applies an unreviewed repair.
|
||||
|
||||
## Authorization and freshness
|
||||
|
||||
The handler authenticates every request. Setup checks the principal's resolved permission list, matching the normal authorization service. An `admin` or `super_admin` role name alone does not bypass missing permissions. Every planned create or field update also checks its specific permission.
|
||||
|
||||
Apply rebuilds the plan inside one transaction and verifies the fingerprint before writing. A stale preview is rejected and must be reviewed again. Transactional reads use separate GORM statements, preserving row locks without leaking a prior query's table or filters. Updates also compare the target's previous update timestamp. The fingerprint is a freshness/intent check, not an authentication credential or signed authorization grant.
|
||||
|
||||
## Deliberately narrow behavior
|
||||
|
||||
- Broad setup only creates missing defaults. It never rewrites existing configuration, moves existing operators to another team, expands an agent's team pool, or rebinds existing channel routes.
|
||||
- Deleted records are not update candidates and are never restored. Disabled records and zero-rollout settings are preserved. A targeted “enable my profile” action can enable the authenticated user's non-deleted profile only when the preview explicitly shows the change and the required permissions are present.
|
||||
- A targeted handoff change affects only the named eligible agent. Missing teams, route choices, disabled configurations, and rollout changes must be chosen in their existing editors.
|
||||
- Quick fixes never create or modify shifts. Missing schedules and off-hours schedules are distinct diagnostics. Working hours must be entered explicitly in the existing schedule editor.
|
||||
- A newly created default agent is unpublished. Setup does not publish agents. A default channel can be created only after an eligible agent has a published revision, matching the normal channel editor's publication guard. Readiness reports unpublished agents as incomplete.
|
||||
|
||||
Therefore a broad repair is not a promise of a production-ready routing path. Publishing, deliberate routing choices, and explicit schedules may still be required.
|
||||
|
||||
## Transactions and dispatch
|
||||
|
||||
All configuration writes in an apply operation share one transaction. A failed write rolls back the entire repair. Pending dispatch begins only after the configuration transaction commits.
|
||||
|
||||
A dispatch failure produces `dispatch_failed`; it does not undo committed changes or return an error implying that the configuration write should be retried. A diagnostic refresh failure similarly produces `readiness_refresh_failed`. The UI displays these warnings and requires a fresh preview for a subsequent repair.
|
||||
|
||||
The pending-queue query now reports database read errors and applies its existing batch limit in SQL. Its existing newest-first priority and periodic retry semantics are unchanged. This repair does not redesign queue fairness.
|
||||
|
||||
## Simulation
|
||||
|
||||
The simulator is read-only and sends no customer message. `candidate_operators` is always an array, including missing-channel responses; the client also normalizes legacy null values.
|
||||
|
||||
`bypass_ai: true` is explicitly rejected because the service does not implement AI bypass. AI-only paths remain blocked. AI-first/unknown service modes and partial channel rollout are reported as unverified, never as successful operator delivery. Only an eligible, published, full-channel-rollout, human-only route can produce a positive current-eligibility result. That result is a point-in-time diagnostic, not a delivery guarantee or an end-to-end AI execution test.
|
||||
|
||||
## Validation status
|
||||
|
||||
Focused regression tests cover preview-only behavior, explicit apply, stale previews, atomic rollback, deleted/disabled/zero-rollout preservation, named-target scope, off-hours versus missing schedules, role-name permission bypass, status-only permissions, empty arrays, simulation limitations, publication requirements, and post-commit failures.
|
||||
|
||||
At implementation handoff these tests have been written but not executed. Go compilation, formatting, dependency installation, application execution, database integration, and browser verification were not performed in this source-review environment. TypeScript source parsing, locale-key parity, and whitespace/diff checks have been performed. Those source checks are not substitutes for the deferred tests.
|
||||
47
docs/hardening/VALIDATION.md
Normal file
47
docs/hardening/VALIDATION.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Validation status: review branch, not a release
|
||||
|
||||
This source change is published for review. Do not deploy it as a verified
|
||||
security release until the checks below have been completed.
|
||||
|
||||
## Completed source checks
|
||||
|
||||
- Independent source review of customer/issuer isolation, staff permissions,
|
||||
long-lived authorization, channel policy concurrency and session refresh.
|
||||
- Cross-review of editor draft isolation and customer chat lifecycle boundaries.
|
||||
- Setup preview/apply permission, transaction and target review.
|
||||
- Syntax parsing of changed TypeScript/TSX and JavaScript regression sources.
|
||||
- JSON/YAML parsing, matching new locale keys, and Git whitespace checks.
|
||||
|
||||
These checks are not a replacement for compilation or executing the tests.
|
||||
|
||||
## Not yet validated
|
||||
|
||||
- Go formatting and compilation: the Go toolchain was unavailable during this
|
||||
source-review pass. Run gofmt on all changed Go files before merging.
|
||||
- Automated regression tests: authored but not executed.
|
||||
- Full frontend typecheck, lint and build with declared dependencies: not run.
|
||||
A limited attempt using unrelated available dependencies was incomplete due
|
||||
to missing project packages; it is not a successful project typecheck.
|
||||
- Race detector, actual MySQL CAS/unique-index behavior, and staging additive
|
||||
schema upgrade/reconciliation: not run.
|
||||
- Application/browser acceptance on desktop and narrow screens: not run.
|
||||
- Production migration, service startup and deployment: not performed.
|
||||
|
||||
## Checks required before merge or release
|
||||
|
||||
Use the repository's declared Go and frontend package versions, isolated temporary
|
||||
data, and mocked/loopback external services. Inspect test initialization first;
|
||||
never point regression tests at a live database or real accounts.
|
||||
|
||||
1. Format all changed Go files and compile the relevant packages.
|
||||
2. Run openidentity and backend security/setup tests, including concurrent identity
|
||||
registration, the actual WebSocket pumps, channel CAS and transaction rollback.
|
||||
Then run the package suites and race checks where supported.
|
||||
3. Run the frontend typecheck, lint and build with installed locked dependencies.
|
||||
4. Run the focused Node tests for customer sessions/store/realtime, message drafts,
|
||||
account isolation, setup preview and dismissal navigation.
|
||||
5. Validate SQLite and MySQL schema upgrades and uniqueness/CAS behavior on backups.
|
||||
6. Exercise the modified UI in Russian, English and Chinese, including failures,
|
||||
account/conversation switches, pending sends and expired sessions.
|
||||
|
||||
Do not treat an unexecuted regression test as proof that a defect is fixed.
|
||||
Reference in New Issue
Block a user