PR: #726: feat(channels): session-lifetime config + remote logout UI [Story-2878] by @ankitmaheshwari-com
View PR →

Files changed

55 file(s), +4265 / -3 lines

Path Role + -
messages/en.jsonConfig+20-0
messages/hi.jsonConfig+20-0
openapi/app-api.jsonConfig+589-0
openapi/permission-map.jsonConfig+39-0
src/app/[locale]/(workspace)/settings/account/security/actions.test.tsApp route+250-0
src/app/[locale]/(workspace)/settings/account/security/actions.tsApp route+154-0
src/app/[locale]/(workspace)/settings/account/security/page.tsxApp route+40-0
src/components/settings/security/account-security-page.stories.tsxComponent+36-0
src/components/settings/security/account-security-page.test.tsxComponent+171-0
src/components/settings/security/account-security-page.tsxComponent+125-0
src/components/settings/security/remote-logout-dialog.stories.tsxComponent+63-0
src/components/settings/security/remote-logout-dialog.tsxComponent+73-0
src/components/settings/settings-sidebar.tsxComponent+15-1
src/gen/client/appIssueEnrollmentCode.tsGenerated contract+67-0
src/gen/client/appListFirmBindings.tsGenerated contract+54-0
src/gen/client/appListMyBindings.tsGenerated contract+54-0
src/gen/client/appRevokeFirmBinding.tsGenerated contract+63-0
src/gen/client/appUnbindBinding.tsGenerated contract+63-0
src/gen/client/index.tsGenerated contract+5-0
src/gen/hooks/index.tsGenerated contract+28-0
src/gen/hooks/useAppIssueEnrollmentCode.tsGenerated contract+144-0
src/gen/hooks/useAppListFirmBindings.tsGenerated contract+114-0
src/gen/hooks/useAppListFirmBindingsSuspense.tsGenerated contract+113-0
src/gen/hooks/useAppListMyBindings.tsGenerated contract+114-0
src/gen/hooks/useAppListMyBindingsSuspense.tsGenerated contract+113-0
src/gen/hooks/useAppRevokeFirmBinding.tsGenerated contract+146-0
src/gen/hooks/useAppUnbindBinding.tsGenerated contract+146-0
src/gen/index.tsGenerated contract+146-0
src/gen/schemas/channelBindingListResponse.jsonGenerated contract+76-0
src/gen/schemas/channelBindingView.jsonGenerated contract+62-0
src/gen/schemas/enrollmentCodeView.jsonGenerated contract+25-0
src/gen/schemas/issueEnrollmentCodeRequest.jsonGenerated contract+16-0
src/gen/types/AppIssueEnrollmentCode.tsGenerated contract+61-0
src/gen/types/AppListFirmBindings.tsGenerated contract+54-0
src/gen/types/AppListMyBindings.tsGenerated contract+54-0
src/gen/types/AppRevokeFirmBinding.tsGenerated contract+69-0
src/gen/types/AppUnbindBinding.tsGenerated contract+69-0
src/gen/types/ChannelBindingListResponse.tsGenerated contract+19-0
src/gen/types/ChannelBindingView.tsGenerated contract+50-0
src/gen/types/EnrollmentCodeView.tsGenerated contract+27-0
src/gen/types/IssueEnrollmentCodeRequest.tsGenerated contract+19-0
src/gen/types/index.tsGenerated contract+59-0
src/gen/zod/appIssueEnrollmentCodeSchema.tsGenerated contract+81-0
src/gen/zod/appListFirmBindingsSchema.tsGenerated contract+73-0
src/gen/zod/appListMyBindingsSchema.tsGenerated contract+73-0
src/gen/zod/appRevokeFirmBindingSchema.tsGenerated contract+88-0
src/gen/zod/appUnbindBindingSchema.tsGenerated contract+88-0
src/gen/zod/channelBindingListResponseSchema.tsGenerated contract+28-0
src/gen/zod/channelBindingViewSchema.tsGenerated contract+42-0
src/gen/zod/enrollmentCodeViewSchema.tsGenerated contract+27-0
src/gen/zod/index.tsGenerated contract+54-0
src/gen/zod/issueEnrollmentCodeRequestSchema.tsGenerated contract+27-0
src/lib/channels/gateway.tsLibrary+44-0
src/lib/surface-registry/page-card/catalog.tsLibrary+3-0
src/lib/surface-registry/route-registry.tsLibrary+12-2

Story-2878: Session-lifetime config + remote logout entry point (RFD-AGT-013 T2c-b)

A lost or stolen phone must be killable from any web session immediately. RFD-AGT-013 (D18/D19, CC-25/CC-45) makes remote logout the stolen-phone kill switch: one action ends every PRAMAAN session on every device and disconnects the counsel's conversational channels, so a thief holding an enrolled WhatsApp number gets nothing — the channel refuses the number and every outstanding document link dies with the sessions. This story ships the app-side entry point under Settings → Account → Sign-in & devices.

Settings → Account → Sign-in & devices → "Log out everywhere" → destructive confirm → bindings revoked, every Clerk session revoked → channel refuses the counsel until re-enrollment with a one-time code.

What the action does, in order

StepCallWhy this order / scope
1. Revoke channel bindings GET /app/channels/bindings then DELETE /app/channels/bindings/{id} per active binding (functions T2a-2 contract, merged) FIRST, because the gateway call needs the caller's still-live Clerk bearer. Each revoke kills the binding's derived sticky context and its pending fetch links (functions _revoke_binding, CC-25). The channel then refuses the number via the functions-side zero-amp shield (Story-2850).
2. Revoke every Clerk session clerkClient.sessions.getSessionList({ userId, status: "active" }) (paged) → revokeSession per session, including this one Every wrapper/document open requires an active app session (D18), so this is the door slam (CC-45). Paging is defensive — a partial list would silently leave devices alive.
3. Client sign-out Clerk signOut({ redirectUrl: /{locale}/sign-in }) The current session is already dead server-side; this clears local Clerk state and lands on the localized sign-in page.

Kill-switch failure semantics

Where the UI reflects the channel consequence

What is deliberately NOT in this story

Files

Test evidence

pnpm vitest run "src/app/[locale]/(workspace)/settings/account/security/actions.test.ts" \
  src/components/settings/security/account-security-page.test.tsx

CC-25 live check (human UAT)

The LIVE half of the ticket's test ("logout → channel refused") needs a real enrolled number against staging: enroll a WhatsApp number (T2c-a flow), run remote logout from Settings → Account → Sign-in & devices, then message Mira from that number — the zero-amp shield's static refusal replies (or silence within the window), enforced functions-side. The app-side half (this story) is covered by the tests above.