Commit Graph

1771 Commits

Author SHA1 Message Date
Li Xianggang c19d3543c8 feat(url): 支持docreader不上传替换白名单url的图片 2026-05-18 19:39:30 +08:00
Li Xianggang b3dbf86bf5 feat(obs): 支持华为云obs存储 2026-05-18 19:38:23 +08:00
wizardchen 5cee0d0921 feat(frontend): redesign knowledge document tag selector
Replace the default t-dropdown on knowledge document cards with a
custom t-popup-based list that matches the existing card-more popup
styling (rounded corners, blur, dark mode). Selected entries show a
brand-colored check icon.

Also fix the empty placeholder issue: when a document has no tag,
render a dashed "+ category" affordance only for users with edit
permission, instead of an empty tag chip. A "clear category" action
appears at the bottom of the popup only when a tag is already set,
avoiding ambiguity with user-defined tags named the same as the
generic "uncategorized" label.

Adds the knowledgeBase.tagClearAction i18n key in zh-CN, en-US,
ko-KR and ru-RU.
2026-05-18 18:31:49 +08:00
wizardchen bbcedbee63 feat(tests): enhance vector store tests with fake Elasticsearch server and add user pinning methods to knowledge base repository
Introduced a new helper function to create a fake Elasticsearch server for testing, improving the reliability of connection probes in vector store tests. Additionally, added methods for setting and listing user-specific knowledge base pins in the knowledge base repository, aligning with recent user pinning features.

Refs: #1303
2026-05-18 17:57:46 +08:00
wizardchen fc2d858222 fix(session): update AgentQA to conditionally disable title based on request
Modified the AgentQA function to conditionally pass the title disable flag to the executeQA method, enhancing flexibility in session handling. This change improves the behavior of the QA execution based on user requests.
2026-05-18 17:57:46 +08:00
wizardchen 1506c604e1 refactor(knowledgebase): remove TogglePinKnowledgeBase method from KnowledgeBaseRepository interface 2026-05-18 17:28:58 +08:00
wizardchen 4ca5469d1e refactor(tenant-switch): unify tenant switch navigation to always redirect to KB list
Updated the tenant switch logic to consistently redirect users to the platform's knowledge base list after a tenant switch, simplifying the navigation experience. Removed the previous handling of tenant-scoped routes, ensuring a more predictable behavior during tenant transitions. This change enhances user experience by avoiding potential empty states on reload.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 07777bbe4a feat(i18n): add "Shared by me" label to localization files
Enhanced localization files for English, Korean, Russian, and Chinese by adding the "Shared by me" label to improve clarity in shared resource sections. This update ensures a more intuitive user experience when navigating shared content across different languages.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen c65794277b feat(i18n): update localization files for improved clarity and consistency
Enhanced English, Korean, Russian, and Chinese localization files by adding new section labels such as "Built-in," "Created by me," and "Other members" to improve user understanding. Adjusted existing labels for better clarity and consistency across languages. This update ensures a more intuitive user experience in navigating shared and personal resources.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 7528175a74 feat(knowledge-base): implement per-user pinning for knowledge bases
Refactored the pinning mechanism for knowledge bases to be user-specific, allowing any user with read access to pin knowledge bases independently. This change includes the introduction of a new `user_kb_pins` table to manage pin states, replacing the previous tenant-wide pinning system. Updated relevant API endpoints and service logic to support this new functionality, ensuring that the UI reflects the user's personal pin status.

Additionally, adjusted the knowledge base listing to sort by user-specific pin status, enhancing the user experience by prioritizing pinned items.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 4aedf5fd62 fix(chat): simplify enableMemoryOverride assignment in sendMsg function
Refactored the assignment of enableMemoryOverride in the sendMsg function to remove explicit type declaration, enhancing code clarity. This change maintains the existing functionality while streamlining the code structure.
2026-05-18 17:28:58 +08:00
wizardchen 5a91487322 feat(memory-management): implement server-side user preference for memory feature
Updated the memory management logic to utilize a server-side per-user preference for enabling memory. The `enable_memory` field is now conditionally set based on user preferences, allowing for better control in both normal and embedded contexts. Adjusted relevant API handlers and request structures to support this change, ensuring backward compatibility with existing clients.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 68ac7fb973 feat(user-preferences): implement user preferences management with backend support
Added functionality for managing user preferences, including the ability to update preferences via a new API endpoint. The preferences are now stored server-side, allowing for synchronization across devices. Updated relevant components to handle user preferences, including the UserMenu and settings views. Enhanced internationalization support for error messages related to preference updates.
2026-05-18 17:28:58 +08:00
wizardchen 3226cb97ff feat(user-menu): add QuickNav entry for members with role-based visibility
Introduced a new QuickNav menu item for 'members' in the UserMenu component, allowing users with the appropriate role to access member-related functionalities. Updated the role alignment for QuickNav entries to ensure consistency with the Settings component. This enhancement improves navigation for users based on their permissions.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen b0b05b49c5 feat(tenant): enhance tenant information update with description field and add global invitation bell component
Updated the tenant information API to allow editing of both name and description fields, improving user experience in managing tenant details. Introduced a new GlobalInvitationBell component to display pending invitations in the UI, ensuring users can easily access their invitations without navigating away from their current context. Additionally, updated internationalization files to support new description-related messages.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen ce67ba0310 refactor(InputField): clean up computed properties and improve agent selection logic
Refactored the Input-field.vue component to enhance the clarity and maintainability of computed properties related to selected items. Improved the logic for handling agent configurations and user-selected files, ensuring a more streamlined approach to displaying knowledge bases and files. Additionally, adjusted the handling of disabled agents to ensure proper fallback selection when necessary.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 8d4c58c5fa feat(creator_name): add creator_name field to agents and knowledge bases for enhanced visibility
Introduced a new optional field, creator_name, in both CustomAgent and KnowledgeBase types to allow the front end to display the creator's name. This enhancement enables better differentiation between resources created by the current user and those created by other members of the same tenant. Updated relevant API handlers to populate this field during list operations, ensuring accurate representation in the UI. Additionally, modified the ResourceOriginBadge component to utilize the creator_name for improved context in resource listings.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 8b681e4a2e refactor(GlobalCommandPalette): streamline template structure for improved readability
Consolidated the template structure in GlobalCommandPalette.vue to enhance code clarity and maintainability. Adjusted the layout of various elements for better alignment without altering existing functionality. This refactor aims to improve the overall readability of the component for future development.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 14f62a6e14 fix(audit_log): prevent deadlock in Stop() when Start() is never called
Updated the AuditLogRetentionRunner to include a 'started' flag, allowing Stop() to return immediately if Start() was never invoked. This change prevents potential deadlocks during graceful shutdown. Additionally, added tests to ensure that Stop() behaves correctly when called before Start() and when the service is nil.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 884ac44283 feat(invitations): implement pending invitations feature with polling and dialog
Added functionality for managing pending invitations, including a polling mechanism to update the invitation count in real-time. Introduced a new dialog component for users to view and respond to their invitations without navigating away from their current context. Updated API endpoints for fetching and managing invitations, and enhanced the user interface with relevant internationalization support.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 3bb0d6d763 feat(i18n): add "leave workspace" functionality in multiple languages
Introduced a new "leave workspace" feature with corresponding titles, descriptions, and buttons in English, Korean, Russian, and Chinese. This enhancement improves user experience and clarity in workspace management, allowing users to easily understand the implications of leaving a workspace.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 165ed89b6d feat(i18n): update chat titles and add leave workspace functionality
Modified chat titles in multiple languages to enhance user engagement. Introduced a new "leave workspace" feature with corresponding titles, descriptions, and buttons in English, Korean, Russian, and Chinese, improving user experience and clarity in workspace management.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 3d30a9d842 feat(organization): implement tenant-based access control for organization visibility
Enhanced the GetOrganization and ListMembers methods to enforce tenant-based access control. Users can now only access organization details if their tenant is a member or if the organization is marked as searchable. This change prevents unauthorized enumeration of organizations and ensures sensitive member information is only accessible to authorized tenants.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 4b4225a79f feat(custom_agent): enhance custom agent service with KB sharing support
Updated the customAgentService to include a new KBShareService interface for handling cross-tenant knowledge base sharing. Implemented logic to group knowledge base IDs by effective tenant, ensuring correct querying of shared KBs. Adjusted the GetSuggestedQuestions method to accommodate these changes, improving the handling of FAQ and document chunks for shared knowledge bases.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen df74fa239b feat(auth): clear tenant-scoped client state on tenant change
Implemented a new function to clear tenant-specific data from localStorage when a user switches tenants. This prevents issues with stale data being used in the new tenant context, which could lead to backend errors. The function is called during the tenant selection process to ensure a clean state for the new tenant.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 3693f977ab style(menu, UserMenu): adjust padding and dimensions for improved layout
Modified padding values in menu.vue and UserMenu.vue to enhance the visual consistency of the components. Reduced padding in various elements and adjusted avatar sizes for a more cohesive design. These changes aim to improve the overall user interface without altering functionality.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 8332a0dab6 feat(favorites): implement user favorites and recents functionality
Introduced a new API client for managing user favorites, allowing users to list, add, and remove starred resources. Enhanced the sidebar to display favorites and recents, improving user navigation. Updated internationalization files to include relevant labels and tooltips for the new features across multiple languages.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen b905f18ebc refactor(sidebar): streamline ListSpaceSidebar template for improved readability
Consolidated and formatted the template structure in ListSpaceSidebar.vue to enhance code clarity and maintainability. Adjusted the layout of various elements for better alignment without altering existing functionality. This refactor aims to improve the overall readability of the component for future development.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen dc0671899e feat(tenant): enhance CreateTenantDialog layout and styling
Updated the CreateTenantDialog component to improve the header layout by adding an icon and title for better visual clarity. Introduced new CSS styles for the header to enhance the overall user experience. Additionally, adjusted font sizes in UserMenu.vue for consistency across the application.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen a31d26153a refactor(menu): improve template structure and readability
Consolidated template elements in menu.vue to enhance code clarity and maintainability. Adjusted formatting for better alignment and readability without altering existing functionality. This refactor aims to streamline the component's structure for future development.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 6c523144bf feat(tenant): update tenant creation dialog and internationalization
Refactored the CreateTenantDialog component to enhance the user experience by improving the layout and adding a subtitle for clarity. Updated the internationalization files to reflect changes in terminology from "tenant" to "workspace" across multiple languages, ensuring consistency in user messaging. This includes adjustments to prompts and labels related to workspace creation.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 7d030a6f6a feat(tenant): implement tenant creation limit and error handling
Added a new configuration option to limit the number of tenants a non-superuser can create via self-service. Introduced a new error type for handling cases where users exceed this limit, returning a 429 status code. Updated the tenant creation and update handlers to enforce this limit and provide appropriate feedback to users. Additionally, refactored the tenant update request structure to ensure only mutable fields are allowed.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 29dc99ae79 feat(tenant): implement self-service tenant creation functionality
Added a new API endpoint for creating tenants, allowing any logged-in user to create their own workspace. The backend automatically assigns the creator as the Owner of the new tenant. A new CreateTenantDialog component was introduced in the frontend, enabling users to input the workspace name and an optional description. Internationalization files were updated to include relevant messages for this feature across multiple languages.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 79d397297d refactor(UserMenu): streamline template structure and improve readability
Refactored the UserMenu.vue component by consolidating template elements and removing unnecessary line breaks. This change enhances the overall readability of the code while maintaining the existing functionality of the user menu.

No functional changes were made; the focus was solely on code clarity.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen aae0d6ccf4 fix(tenant): correct inline editing input attributes in TenantInfo.vue
Updated the inline editing input in TenantInfo.vue to ensure proper event handling by consolidating the input attributes. This change enhances the user experience during tenant name editing by maintaining consistent functionality for saving and canceling edits.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen b2411542eb feat(tenant): add tenant name update functionality and enhance UI
Implemented a new API endpoint for updating tenant names, allowing owners to edit tenant information directly within the UI. The frontend now supports inline editing with success and error notifications. Additionally, internationalization files have been updated to include relevant messages for the new functionality across English, Korean, Russian, and Chinese languages, ensuring a consistent user experience.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 57fde55583 feat(i18n): enhance tenant management messages across multiple languages
Updated internationalization files for English, Korean, Russian, and Chinese to include new messages related to editing tenant names and updating tenant information. This includes prompts for user actions such as saving or canceling changes, as well as success and failure notifications. The changes ensure a consistent user experience across all supported languages regarding tenant management functionalities.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen dc8d033b4d feat(organization): transition membership model to tenant-level and enhance invite functionality
Updated the organization membership model to operate at the tenant level, aligning with Plan 3. This change introduces new interfaces and modifies existing ones to accommodate tenant-centric operations, including the `InviteMemberRequest` and `TenantInviteCandidate`. The search functionality for inviting members has been updated to reflect this shift, allowing for tenant-based searches instead of user-based. Additionally, internationalization files have been updated to ensure consistent messaging across languages regarding tenant membership and roles.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 9c88af3370 feat(i18n): add RBAC messages for tenant admin requirements
Enhanced internationalization support by adding RBAC-related messages across multiple language files. These messages inform users about the necessary tenant admin role for actions such as creating, joining, and managing shared spaces. Updated English, Korean, Russian, and Chinese translations to ensure consistent user experience regarding role-based access control.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen 3fbc271e7c feat(migration): add index on created_at for audit_logs
Introduced an index on the created_at column of the audit_logs table to optimize the daily retention sweep, improving performance by reducing the need for full table scans during deletions. This change enhances the efficiency of the retention process, particularly for tenants with high RBAC traffic. The corresponding down migration includes the removal of this index.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen ff1f03ce0d feat(rbac): daily retention sweep for audit_logs (default 90 days)
The audit_logs table grows monotonically until something purges it.
PR 6 explicitly listed retention as out of scope for v1; this picks
that up.

Adds an `audit.retention_days` config (default 90, 0 disables) and a
small background goroutine `AuditLogRetentionRunner` that fires once
~10 minutes after boot and then every 24h, calling
`AuditLogService.Purge` which DELETEs rows older than the cutoff in a
single statement. The dependency surface is intentionally minimal —
no robfig/cron, no asynq, no migration — because retention has no
wall-clock alignment requirement.

Wiring is the same shape as the existing data source scheduler:
container `Provide(NewAuditLogRetentionRunner)`, an `Invoke` that
calls `Start`, and a `ResourceCleaner.RegisterWithName` for graceful
shutdown.

Defaults preserve operator intent:
  - `audit:` section omitted from YAML  -> retention_days=90.
  - explicit `audit.retention_days: 0` in YAML  -> purge disabled.
  - `WEKNORA_AUDIT_RETENTION_DAYS=N` env override (incl. N=0).
  - `retention_days < 0` is rejected by ValidateConfig.

Tests cover the service Purge contract (no-op when disabled, cutoff
math, error propagation) and the runner lifecycle (Start no-op when
disabled, idempotent Start/Stop, ticker cadence, runOnce swallows
errors). 10 new tests, all green.

docs/rbac.md updated with the new YAML / env / behaviour.

Refs: #1303
2026-05-18 17:28:58 +08:00
wizardchen cf6c28fe7e fix(rbac-ui): identify org owner row by tenant_id, not user_id
Plan 3 (#1303) lifted Org membership from per-user to per-tenant, and
40fdb978 pinned the org's owner tenant in DB as organizations.owner_tenant_id.
The OrganizationSettingsModal members list, however, still decided
"which row is the owner" by comparing member.user_id against
orgInfo.owner_id. After Plan 3 the members list is keyed on tenant_id,
so the user-id check is structurally wrong: the representative user of
the owner tenant happens to be the owner today, but if that user is
moved to another tenant — or a different user of the owner tenant
becomes the representative — the modal would either lose the "(owner)"
tag, or re-tag a non-owner row, and would offer the role-select /
remove-button affordances on what is in fact the owner tenant row.

This change:

  - Adds OwnerTenantID to OrganizationResponse and populates it in
    toOrgResponse from the persisted column. IsOwner now compares the
    caller's current tenant against owner_tenant_id (with a user-id
    fallback for legacy rows where owner_tenant_id == 0).

  - Adds owner_tenant_id to the frontend Organization type and a small
    isOwnerMember(member) helper in OrganizationSettingsModal, which
    tenant-matches by default and falls back to user-id only on
    pre-000046 rows. The role <select>, role tag suffix, and the
    delete button on member rows now all use the helper.

The "is-me" CSS class and the me-tag in the member name still match
on user_id — that one really is a per-user marker ("this row's user is
me, the signed-in user").
2026-05-18 17:28:58 +08:00
wizardchen 7e20b9a57e fix(rbac): gate remaining organization mutating routes with Admin+
Plan 3 (#1303) fixed several Org mutating routes (POST /organizations,
/join, /join-request, /join-by-id, /:id/leave, /:id/request-upgrade)
to Admin+ in the caller's tenant, and e1deb810 followed up by gating
DELETE /:id/members/:tenant_id symmetric with /:id/leave.

The remaining Org-admin surfaces still relied solely on a
service-layer "is the caller's tenant admin in this org?" check, with
no route-layer floor. Add g.Admin() to:

  - PUT    /organizations/:id                          (update org)
  - DELETE /organizations/:id                          (delete org)
  - POST   /organizations/:id/invite-code              (rotate code)
  - GET    /organizations/:id/search-users             (member picker)
  - POST   /organizations/:id/invite                   (direct invite)
  - PUT    /organizations/:id/members/:tenant_id       (change role)
  - GET    /organizations/:id/join-requests            (review queue)
  - PUT    /organizations/:id/join-requests/:request_id/review

Each of these takes an action that is visible to the whole org or to
another tenant; per the RBAC matrix in router/rbac.go they belong on
Admin+. The service-layer check stays as the authority on org-level
membership; the route guard prevents a tenant Viewer/Contributor from
ever reaching the service in the first place.

Behaviour is unchanged when cfg.Tenant.EnableRBAC is false: RequireRole
continues to log the would-be rejection and let the request through.
2026-05-18 17:28:58 +08:00
wizardchen 8efe1b4187 refactor(rbac): address kb-access guard review findings
Follow-up to the route-level KB-access guard introduced earlier in
this PR. Five reviewer findings, all fixed in middleware + tests
without changing handler call sites:

  - Honour `?agent_id` in the agent-share fallback. The in-handler
    resolution in knowledgebase.go branches on a specific agent's
    KBSelectionMode (all / selected / none); the guard was only ever
    calling TenantCanAccessKBViaSomeSharedAgent, which let some
    requests pass at the guard layer that the handler would then
    reject — and rewrote the request's tenant context to the source
    tenant before the rejection happened. The guard now mirrors the
    handler: a specific agent_id pins the resolution to that agent
    (no fallback to any-agent), an empty agent_id keeps the existing
    any-agent behaviour. The knowledge.go / knowledgebase.go handler
    helpers stay in place as defence in depth until they're folded
    into the guard in a follow-up.

  - Distinguish not-found vs. transient on the knowledge / chunk
    resolvers. A DB hiccup used to surface as a 404, both confusing
    clients and hiding the underlying failure from monitoring. The
    resolvers now return apperror 404 only for the known not-found
    sentinels (apprepo.ErrKnowledgeBaseNotFound, ErrResourceNotFound)
    and let other errors propagate; the guard maps those to a fresh
    NewServiceUnavailableError (503).

  - Stop 500-ing on legacy chunks with empty knowledge_base_id. The
    chunk is effectively unresolvable to a KB, so the client gets the
    same 404 they'd get for a missing chunk instead of a 500 that
    pollutes alerting. The warn log preserves the operator signal.

  - Honour cfg.Tenant.EnableRBAC. The guard now mirrors RequireRole /
    RequireOwnershipOrRole: with enforcement off, 401/403 paths log
    the would-be rejection and pass through. 404 still fires either
    way (a missing resource is not an authorisation event). Keeps the
    rollout window safe — operators can flip enforcement on globally
    without code changes elsewhere.

  - Document the c.Keys vs. c.Request.Context() split. The guard
    intentionally rewrites only the request context (handlers that
    still run their own share resolution, currently knowledge.go /
    knowledgebase.go, would otherwise see kb.TenantID == c.Keys
    tenant and misclassify shared-Viewer access as Admin). The
    package-level comment now spells this out so the migration of the
    remaining handlers in a follow-up doesn't accidentally regress
    `my_permission` rendering.

Tests:

  - Existing four cases keep working; runGuard signature gained a
    guardOpts knob.
  - Eight new cases cover the agent-share resolution paths (any-agent
    viewer, Editor required → must reject, specific agent in modes
    all / selected-match / selected-miss / none, cross-tenant
    mismatch) plus the EnableRBAC=false fail-open + 404-still-fires
    pair.
2026-05-18 17:28:58 +08:00
wizardchen ef01f99e69 refactor(rbac): extend KB-access guard to chunk / knowledge / knowledgebase routes
Follow-up to the FAQ + Tag refactor in this same PR. Three additional
handler areas now run through the route-level g.KBAccess* guard:

  - chunk.go  : full refactor — helper effectiveCtxForKnowledge and
                validateAndGetChunk's access check are gone; the
                handler's chunk-fetch + ownership-mismatch check stay
                as defence-in-depth (a same-tenant attacker can't
                pass mismatched knowledge_id + chunk_id).
  - knowledge.go and knowledgebase.go : routes now run the guard
                BEFORE the handler. The in-handler
                validateKnowledgeBaseAccess /
                resolveKnowledgeAndValidateKBAccess helpers stay for
                now because they branch on the agent_id query param
                (specific-agent check vs. any-agent fallback) — that
                handler-specific branch can collapse into the guard
                in a follow-up. The duplicated DB roundtrip is
                negligible compared to the handler's main work.

New middleware exports:

  - KBIDFromChunkIDParam   : walks chunk_id → KnowledgeBaseID via the
                             chunk's denormalised column.
  - g.KBAccessReadFromChunkIDParam / g.KBAccessWriteFromChunkIDParam
                           : convenience methods on rbacGuards.

Route changes are purely additive (existing role / ownership guards
stay in place; the new KB-access guard runs after them and rewrites
the request's tenant context to the source tenant for shared KBs).
No new dependencies on the kbShareService / agentShareService at the
handler level for chunk.go — those move to the route layer.

Tests pass; behaviour unchanged.
2026-05-18 17:28:58 +08:00
wizardchen 1109cb7bff refactor(rbac): KB-access guard at the route layer (no helper in handlers)
Five handler files used to carry near-identical 30-line helpers
(effectiveCtxForKB / validateAndGetKnowledgeBase) that did the same
own-or-shared resolution before every KB-scoped operation:

  1. KB belongs to caller's tenant     -> grant own access
  2. Org-shared KB                      -> grant min(share, role) cap
  3. Shared agent carries the KB        -> grant Viewer (read-only)

A bug found in any one of them had to be fixed in five places, and
several already drifted apart (different log levels, slightly
different error messages, one path missing the share-cap check).

Move the resolution to a route-level gin.HandlerFunc:
middleware.RequireKBAccess, exposed on rbacGuards as
g.KBAccessRead("id") / g.KBAccessWrite("id"). The guard runs after
the role/ownership checks, then on success:

  - stashes the resolved (KB + effective tenant id + permission)
    on c.Keys under middleware.KBAccessContextKey, and
  - rewrites c.Request.Context() to carry the effective tenant id

so handlers downstream just read tenant the way they always did
(types.MustTenantIDFromContext) without a per-handler helper.

Refactor FAQ and Tag handlers to drop their helper methods; their
service constructors lose the kbShareService / agentShareService
dependencies (now lifted to the route layer). Routes:

  faq.GET("/entries",            g.Viewer(),         g.KBAccessRead("id"),  ...)
  faq.POST("/entries",           g.OwnedKBOrAdmin(), g.KBAccessWrite("id"), ...)
  kbTags.GET("",                 g.Viewer(),         g.KBAccessRead("id"),  ...)
  kbTags.POST("",                g.OwnedKBOrAdmin(), g.KBAccessWrite("id"), ...)

The middleware also exports KBIDFromKnowledgeIDParam so chunk routes
(URL :knowledge_id) can adopt the same pattern in a follow-up PR
without reshaping the helper.

Five new tests cover own-KB, not-found, shared-with-sufficient-perm,
shared-but-below-min, and missing-tenant-context paths. Handler
tests still pass (no behaviour change).
2026-05-18 17:28:58 +08:00
wizardchen a16fe83034 fix(rbac): pin organization owner tenant in DB instead of deriving at runtime
Plan 3's isOwnerTenant resolved "is this tenant the owner tenant?"
by looking up the owner user's CURRENT tenant via userRepo. That
breaks silently the moment the owner user is moved to another
tenant: the formerly-protected owning tenant becomes removable
(can be kicked from OTM, role-downgraded), and the new tenant the
owner moved to is treated as untouchable even though it isn't even
in OTM.

Promote owner-tenant to a first-class column on `organizations`:

- New migration 000046 adds owner_tenant_id BIGINT NOT NULL with
  index. Backfill: (1) owner user's current tenant when known,
  (2) earliest Admin tenant in OTM as fallback for orphans, then
  (3) RAISE EXCEPTION on anything still NULL — refuse to silently
  ship a bogus value. SQLite init schema mirrors the column.

- CreateOrganization now sets OwnerTenantID at create time so the
  pinned value is correct for all new orgs.

- isOwnerTenant becomes a pure equality check on the persisted
  column — no userRepo lookup, no error tuple. Fail-closed on a
  zero column value (legacy data not yet backfilled or unit tests
  bypassing the migration) so the worst case is "membership table
  frozen for that org" rather than "owner tenant is removable",
  matching the prior fail-closed-on-error semantics.

- RemoveTenantMember / UpdateTenantMemberRole simplify to a single
  bool check.
2026-05-18 17:28:58 +08:00
wizardchen 7720db39fa fix(rbac): dedup pending join/upgrade requests per (org, tenant, type)
Plan 3 of #1303 changed the join-request lookup key from per-user
to per-tenant (GetPendingRequestByTenantAndType), but didn't touch
existing data or add a structural constraint. Result: any (org,
tenant) that had multiple pending rows under the old per-user model
now has ambiguous "which one wins" semantics — the LIMIT 1 query
returns one row, the others stay pending and resurface in admin
lists after the first is approved/rejected.

Two fixes:

1. Migration 000045 up.sql now collapses pre-existing pending rows
   to one per (org, tenant, type), keeping the earliest by
   created_at. The losers are flagged status='rejected' with a
   '[Plan 3] superseded' review_message so the audit trail shows
   it was a system cleanup, not a human reject. down.sql drops
   the new index but leaves the data mutation in place — we don't
   have enough information to safely re-promote the discarded rows.

2. Add a partial unique index on
   (organization_id, tenant_id, request_type) WHERE status='pending'
   to both the PG migration and the SQLite init schema. This makes
   the invariant structural so SubmitJoinRequest /
   RequestRoleUpgrade cannot regress.

Approved/rejected history is intentionally unconstrained so the
audit trail remains complete.
2026-05-18 17:28:58 +08:00
wizardchen f6684f226b fix(rbac): allow source-tenant Admin+ to manage their cross-tenant shares
Plan 3 of #1303 lifts share ownership to the tenant level, but
RemoveShare / UpdateSharePermission still gated on the original sharer
user. If that user left the tenant or moved on, the source tenant lost
the ability to revoke its own share — only the target org's admin
could clean up, which is governance theatre rather than ownership.

Extend the authz envelope on KB-share and agent-share mutations:

  (1) original sharer (unchanged);
  (2) source-tenant Admin+ — new, encodes "ownership lives in the
      tenant" by reading callerTenantRole from ctx;
  (3) target-org admin (unchanged).

Factored out as kbShareService.callerCanManageShare so Update and
Remove cannot drift apart.
2026-05-18 17:28:58 +08:00
wizardchen eda6b1011a fix(rbac): close org-tenant gate gaps from Plan 3 review
Three follow-ups from #1350 review:

1. DELETE /organizations/:id/members/:tenant_id was un-gated, so a
   tenant Viewer could remove their own tenant from an org via the
   members endpoint and bypass the Admin+ requirement that POST
   /:id/leave already enforces. Gate it with g.Admin() — symmetric
   with /leave.

2. isOwnerTenant() returned (false, err) when the owner user lookup
   failed, and the two call sites only checked the bool. A deleted /
   unreadable owner user would silently fall through and let admins
   remove or re-role the would-be owner tenant. Make the helper
   fail-closed: any lookup error now returns (true, err), and both
   call sites refuse the destructive op on error.

3. POST /:id/request-upgrade was un-gated. Approval of the request
   changes the whole tenant's org role, so it is in the same trust
   tier as join/leave/create. Add g.Admin().
2026-05-18 17:28:58 +08:00