feat(kb): serve portal KB feed only via the portal-authed route #118

Merged
vas2000-work merged 3 commits from feat/portal-kb-feed-auth into main 2026-06-05 23:10:30 +02:00
Owner

Follow-up: "portal KB feed needs portal-JWT auth."

Finding

Two routes served portal-visible articles:

  • GET /api/v1/portal/kb (list_kb) - already behind PortalAuthMiddleware + RequirePortalAuth, scoped to the authenticated contact tenant_id/company_id. This is what the mokosh-apps portal page actually calls.
  • GET /kb/articles/portal - mislabeled "portal" but ran under agent auth (RequireKnowledgeBase), scoped to the agent tenant, NOT portal-JWT protected, and consumed by nothing (verified across server + mokosh-apps).

Change

Remove the redundant, unauthed-for-portal /kb/articles/portal route and its now-dead KbService::list_portal_articles. The portal-visible feed is served exclusively by the portal-authenticated /api/v1/portal/kb, scoped from JWT claims (published + public/client-specific for the contact company). No mokosh-apps change needed (client already uses the surviving path).

Test (tests/knowledge_base.rs, #[sqlx::test], through the real router): no/garbage token -> 401; valid portal token -> only that tenant+company published portal-visible articles (excludes drafts, other-company client_specific, other-tenant public).

Confirm

Removed rather than retrofitted the agent-tree route (a RequirePortalAuth extractor there would always 401, since PortalAuthState is only populated under the portal tree). If a staff-facing preview of portal-visible articles is wanted, reintroduce under agent auth with a non-portal name. Needs a gate run.

Follow-up: "portal KB feed needs portal-JWT auth." ## Finding Two routes served portal-visible articles: - `GET /api/v1/portal/kb` (`list_kb`) - already behind PortalAuthMiddleware + RequirePortalAuth, scoped to the authenticated contact tenant_id/company_id. This is what the mokosh-apps portal page actually calls. - `GET /kb/articles/portal` - mislabeled "portal" but ran under agent auth (RequireKnowledgeBase), scoped to the agent tenant, NOT portal-JWT protected, and consumed by nothing (verified across server + mokosh-apps). ## Change Remove the redundant, unauthed-for-portal `/kb/articles/portal` route and its now-dead `KbService::list_portal_articles`. The portal-visible feed is served exclusively by the portal-authenticated `/api/v1/portal/kb`, scoped from JWT claims (published + public/client-specific for the contact company). No mokosh-apps change needed (client already uses the surviving path). Test (`tests/knowledge_base.rs`, #[sqlx::test], through the real router): no/garbage token -> 401; valid portal token -> only that tenant+company published portal-visible articles (excludes drafts, other-company client_specific, other-tenant public). ## Confirm Removed rather than retrofitted the agent-tree route (a RequirePortalAuth extractor there would always 401, since PortalAuthState is only populated under the portal tree). If a staff-facing preview of portal-visible articles is wanted, reintroduce under agent auth with a non-portal name. Needs a gate run.
feat(kb): serve portal KB feed only via portal-authed route
Some checks failed
Check / fmt + clippy + compile + tests (pull_request) Failing after 13s
E2E (staging) / Playwright against staging (pull_request) Successful in 38s
Build OCI container / Build and push mokosh-api image (push) Successful in 3m13s
de3ebff5a4
The agent-tree route GET /kb/articles/portal was labeled "portal" but ran under RequireKnowledgeBase (agent) auth and scoped to the agent's own tenant, so it was not protected by portal-JWT auth and could not be reached by portal contact tokens. No client consumed it: the mokosh-apps portal page fetches GET /api/v1/portal/kb, which already lives behind PortalAuthMiddleware + RequirePortalAuth and scopes results to the authenticated contact's tenant and company via KbService::list_portal_articles_for_company.

Remove the redundant, mislabeled agent-tree route and its now-unused list_portal_articles service method so the portal-visible feed is served exclusively by the portal-authenticated GET /api/v1/portal/kb. Visibility and tenant/company scoping are derived from the portal JWT claims (CurrentContact.tenant_id / company_id), never from client input.

Add an end-to-end integration test: an anon or bad-token request to GET /api/v1/portal/kb is rejected with 401, and a valid portal token returns only that contact's tenant + company published portal-visible articles (public plus client_specific scoped to the contact's company), excluding drafts, other companies' articles, and other tenants' articles. Keep the existing service-level scoping pin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
style: apply cargo fmt
All checks were successful
E2E (staging) / Playwright against staging (pull_request) Successful in 45s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m23s
a1747d4f46
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merge branch 'main' into feat/portal-kb-feed-auth
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
E2E (staging) / Playwright against staging (pull_request) Successful in 1m19s
Check / fmt + clippy + compile + tests (pull_request) Successful in 4m30s
Build OCI container / Build and push mokosh-api image (push) Successful in 11m45s
bb0d8d2dcc
vas2000-work deleted branch feat/portal-kb-feed-auth 2026-06-05 23:10:30 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!118
No description provided.