Skip to main content

RFC: Centralized Web Session — Launchpad FE Integration

Document Conventions (do not remove)

Follows the Qontak RFC Template governance shell (metadata table + sections 1–6 + Comment logs) and is agent-execution-ready: §1 Design References, §2 Repo Reading Guide, mermaid diagrams, and §4 Agent Execution Plan + Verification & Rollback Recipe must be complete before §7 flips to yes. The YAML frontmatter is the machine-readable index; the metadata table below is the human record. Both must agree on shared fields.

Scope of THIS RFC: the Launchpad (this repo) FE integration of the centralized web-session system. The Session Manager Golang service, the shared Redis, the mekari-account-web-sdk build, and the SSO Rails changes are owned by the Account & SSO squads and are out of scope here (referenced as upstream contracts only). Launchpad is the FIRST pilot per the PRD rollout (step 4).

Metadata

FieldValueNotes
StatusRFCIDEA / RFC / ABANDON / AGREED
TypefrontendLaunchpad is a Nuxt 4 SPA (ssr: false)
Sub-typenew-featurenew SDK integration + session-event handling
TitleCentralized Web Session — Launchpad FE Integration
OwnerAccount & Launchpad
AuthorsAccount & Launchpad team
ReviewersSSO/Session service reviewer; Launchpad BE reviewerplaceholders pending
ApproversLaunchpad tech lead; infosecinfosec approval required
Submitted2026-06-28ISO-8601
Last updated2026-07-02matches latest edit
Target releaseTBDplaceholder — see Open Questions
Related documentsCentralized Web Session PRDauthoritative driver
DiscussionTBD

Sections at a Glance

§SectionFE hints
1Overviewgoals, scope, Design References, PRD coverage, Decisions index, Per-Story Change Map
2Technical DesignRepo Reading Guide, SDK contract, event handling, current-company sync, diagrams
3High-Availability & SecuritySDK loading hardening, FE observability, perf expectations
4Backwards Compatibility & Rollouttoggle rollout, Agent Execution Plan, Verification & Rollback Recipe
5Concern / Questions / Known LimitationsOpen Questions with severity
6Comment logsreview trail
7Ready for agent executionthe gate

1. Overview

1.0 Problem

Launchpad maintains its own session (OAuth2 bearer tokens in cookies) that is not synchronized with SSO. When a user logs out of SSO, switches accounts, or is idle past the SSO timeout, Launchpad keeps showing the stale account and company. The PRD asks every Mekari product FE to load the shared mekari-account-web-sdk Session SDK (v0.3.0, bundled git dependency — no CDN) that reports authoritative session state (logged_in, logged_out, server_down) so the product can react. An SSO account switch is delivered by the SDK as logged_out — the SDK cannot distinguish a switch from a plain logout and never exposes the incoming user's ssoId, so it carries no dedicated switch_user status (resolved by real SDK mekari-account-web-sdk v0.3.0, session.ts; see R5 and §5). Launchpad is the first pilot.

1.1 Goals (from PRD Success Criteria)

  • Launchpad consults the centralized session via the SDK on page load.
  • Launchpad reacts to SSO logout / idle-timeout (2h) by ending its own session.
  • Launchpad reacts to SSO account switches the same way: the SDK surfaces a switch as logged_out (no dedicated event, R5), so Launchpad runs its normal sign-out flow; any post-re-login current-company re-sync happens through the ordinary SSO re-authentication path, not via an SDK event (R10).
  • Behavior gated behind a centralized_session feature toggle.

1.2 Non-goals

  • Building the Session Manager Golang service, shared Redis, or the mekari-account-web-sdk package (upstream squads).
  • Auto-revoking access/refresh tokens on inactivity timeout (PRD Out of Scope).
  • Multiple-sessions-per-account FE UX (PRD rolls this in parallel, not part of the Launchpad pilot critical path).

1.3 Design References

Launchpad is a backend-of-FE integration, not a net-new visual surface. There is no dedicated "account has changed" notification: the real SDK has no switch_user status (an account switch is delivered as logged_out, R5), so an account switch runs the same sign-out UX as a normal logout — no new toast surface is required for it. The only remaining FE-visible surface is the optional session-loading gate below. Launchpad already renders toasts via @mekari/pixel3 toast.notify (see app/common/store/authStore.ts:117) for any future need, but none is required by this RFC's SDK-driven flows.

SurfaceFigma frameImplementing fileNotes
Session-loading gate (blank/spinner while SDK resolves)[REQUIRED: confirm with design whether a loading state is needed]app/middleware/authenticated.global.tsPRD: "Product FE waits for SDK response" when toggle on

1.A PRD Coverage

PRD Section Coverage

PRD sectionCovered inStatus
1. Overview / known issues§1.0, §1.1covered
Success Criteria§1.1, §2.xcovered (FE portion)
Out of Scope§1.2covered
Dependencies (SDK, service, Redis)§2.1 upstream contractscovered (upstream, read-only)
2. Technical Design — Proposal§2.2–§2.6covered (FE portion)
SDK contract / events§2.3covered
FE Product Integration Flows§2.4–§2.6covered
Web Session Flow§2.5n/a — Launchpad uses OAuth2 authz-code flow, not Rails web session
OAuth2 Authorization Code Flow§2.5covered — Launchpad's actual auth model
User Logout From Product§2.6covered
User Switch Account§2.3/§2.4 (surfaces as logged_out; no dedicated event — R5)covered
Database Modeln/a — no DB changes (FE)
3. HA & Security§3covered (FE-relevant subset)
4. Rollout Plan§4covered (Launchpad step 4)
5. Open Questions§5covered
FE Implementation Scope§1.C, §4covered

UI / Consumer Surface Coverage

SurfaceRead endpoint / sourceCoverage
Session gateSDK init resolution§2.4

Removed: an "account changed" toast tied to a switch_user event — the real SDK has no such event (R5); an account switch runs the normal logged_out sign-out flow and introduces no new UI.

Role Coverage

RoleRelevanceCoverage
Any authenticated Launchpad userAll session events apply uniformly§2.4
no per-role session behavior in PRDn/a — session handling is role-agnostic

1.B Decisions Closed (index → §2 ADRs)

#DecisionChosenADR
D1Auth model variant for LaunchpadOAuth2 Authorization Code flow (not web-session)ADR-1
D2Where to load the SDK*.client.ts Nuxt pluginADR-2
D3Toggle mechanismreuse useToggleQontakOne pattern, new centralized_session toggleADR-3
D4Session event → app reaction wiring pointroute middleware + auth storeADR-4
D5SDK distributionbundled git dependencymekari-account-web-sdk v0.3.0 via npm install git+https://<user>:<pass>@bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk#<version>; no CDN alternative exists — resolved (R2, Q1 trace in §5)

1.C Per-Story Change Map

Story (PRD)Layer scopeChangesAcceptance criteriaRFC anchors
Load SDK with currentUser on every pageFE-onlyapp/plugins/mekariSession.client.ts (new), reads authProfile.sso_idplugin instantiates Session({currentUser}); unit test asserts SDK constructed with cookie launchpad.sso_id value§2.2, §4.C chunk 2
Gate behind centralized_sessionFE-onlyuseCentralizedSessionToggle.ts (new)toggle off → no SDK side effects (test)§2.3 ADR-3, §4.C chunk 1
Handle logged_inFE + BEsync current company after session confirmedon logged_in, current company fetched & set; test stubs SDK event§2.4, §2.5, §4.C chunk 4
Handle logged_out (also covers account switches — R5)FE-onlyrevoke tokens + product sign-out (OAuth2 variant)event → resetAuth() + cookies cleared + redirect to SSO sign_out§2.4, §4.C chunk 3
Handle server_downFE-onlyfail-open (PRD 6.10): no destructive action, observability counter onlyserver_down event → log/count only; product session untouched§2.4, §4.C chunk 5
Wire product logout → SSO sign_outFE-onlyalready presentlogout hits SSO_URL/sign_out?client_id=§2.6, SwitchAccountContent.vue:117 (reuse)
Current-company syncFE + BEnew composable + BE endpointcompany set post-session§2.5 — blocked by Q3

2. Technical Design

2.0 Repo Reading Guide (read BEFORE writing)

Repo Map (slice this RFC touches):

flowchart LR
subgraph launchpad["FE: qontak-launchpad-fe (this repo)"]
plugin["plugins/mekariSession.client.ts (new)"]
toggle["composables/useCentralizedSessionToggle.ts (new)"]
sess["composables/useCentralizedSession.ts (new)"]
company["composables/useCurrentCompany.ts (new)"]
mw["middleware/authenticated.global.ts (modified)"]
authStore["store/authStore.ts (modified)"]
ssoStore["store/ssoCallbackStore.ts (read/reuse)"]
cookies["composables/useAuthCookies.ts (read/reuse)"]
client["composables/useClient.ts (read/reuse)"]
logout["SwitchAccountContent.vue / TheSwitchAccount.vue (reuse)"]
end
subgraph upstream["Upstream (READ-ONLY contracts)"]
sdk["mekari-account-web-sdk Session SDK (bundled git dep)"]
sm["sm.mekari.com/current (Session Manager)"]
sso["SSO auth / sign_out / current_company"]
end
plugin --> sdk
plugin --> toggle
plugin --> sess
sdk -. postMessage events .-> sess
sess --> authStore
sess --> company
sess --> mw
mw --> ssoStore
authStore --> cookies
company --> client
client --> sso
sdk -. iframe .-> sm
logout --> sso

Existing Code Anchors

FileWhat to learn
app/middleware/authenticated.global.tsthe single auth gate; existing token-refresh + unified-logout flow; where to await SDK
app/common/store/authStore.tsfetchAuthLaunchpad, setProfileAuth (sets LAUNCHPAD_SSO_ID), resetAuth; authProfile.sso_id is the user SSO ID
app/common/store/ssoCallbackStore.tssetTokenLaunchpad/clearTokenLaunchpad, OAuth2 fetchOauthSSO (authorization_code)
app/common/composables/useAuthCookies.tscookie names global_sso_token, global_sso_refresh_token, global_sso_valid_until, launchpad.sso_id; domain from config.public.COOKIE_DOMAIN
app/common/composables/useClient.tscentral $fetch wrapper, 401 refresh singleton, handleAuthenticationFailure
app/common/composables/useToggleQontakOne.tsexisting feature-toggle pattern to mirror for centralized_session
app/features/sso-callback/composable/ssoCallback.tshandleRedirection; SSO authz URL construction (SSO_URL/auth?client_id=...&response_type=code)
app/layouts/components/SwitchAccountContent.vuesign-out wiring → SSO_URL/sign_out?client_id= (line 117) — already matches PRD §2.6
app/app.vue / app/layouts/default.vuemount points; $auth provided by plugins/auth.ts

Patterns to Follow

ConcernReference filePattern
State managementapp/common/store/authStore.tsPinia setup store, refs + actions
Feature toggleapp/common/composables/useToggleQontakOne.ts:11module-singleton TOGGLE_SOURCE switch
API callapp/common/composables/useClient.ts:43useClient(url, opts){data,error}; read body at data.value.data
Auth cookiesapp/common/composables/useAuthCookies.ts:9useCookie with prod domain from config
Error normalizeapp/common/composables/useErrorHandler.tsroute API errors through useErrorHandler (per AGENTS.md)
User notificationapp/common/store/authStore.ts:117toast.notify({position,variant,title})
SSO re-auth redirectapp/middleware/authenticated.global.ts:79SSO_URL/auth/?client_id=&response_type=code&scope=sso:profile&redirect_uri=BASE_URL/sso-callback
Logout redirectapp/layouts/components/SwitchAccountContent.vue:117clear cookies → SSO_URL/sign_out?client_id=
Composable testapp/common/composables/useAuthCookies.spec.tsVitest + happy-dom, mock #app

Reading Order for the Agent

  1. app/middleware/authenticated.global.ts
  2. app/common/store/authStore.ts
  3. app/common/store/ssoCallbackStore.ts
  4. app/common/composables/useAuthCookies.ts
  5. app/common/composables/useClient.ts
  6. app/common/composables/useToggleQontakOne.ts
  7. app/features/sso-callback/composable/ssoCallback.ts
  8. app/layouts/components/SwitchAccountContent.vue
  9. nuxt.config.ts (runtimeConfig: SSO_URL, SSO_UNIFIED_CLIENT_ID, CHATPANEL_URL, BASE_URL, COOKIE_DOMAIN, apiBaseUrl)
  10. app/common/composables/useAuthCookies.spec.ts (test conventions)

2.0.1 Source Verification

ItemEvidence
Auth gate is route middlewareapp/middleware/authenticated.global.ts:5 defineNuxtRouteMiddleware; refresh at :44, unified logout at :90
User SSO ID sourceauthStore.ts:139 LAUNCHPAD_SSO_ID.value = payload?.sso_id; cookie launchpad.sso_id at useAuthCookies.ts:30
OAuth2 authz-code is Launchpad's modelssoCallbackStore.ts:53 grantType: "authorization_code"; redirect builder authenticated.global.ts:80
Token cookiesuseAuthCookies.ts:5-7 global_sso_token/global_sso_refresh_token/global_sso_valid_until
Sign-out already hits SSOSwitchAccountContent.vue:117 window.location.href = ${ssoBaseUrl}/sign_out?client_id=${ssoClientId}
Toggle patternuseToggleQontakOne.ts:11 const TOGGLE_SOURCE = "always-on"
Central fetch wrapperuseClient.ts:9; 401 refresh :51; handleAuthenticationFailure :146
Test stackpackage.json: "test": "vitest --dom --pool=forks"; example spec useAuthCookies.spec.ts
_mekari_account NOT a Launchpad cookiegrep of useAuthCookies.ts — only global_sso_* + launchpad.sso_id; no _mekari_account. Moot: the real SDK owns its msli fallback internally and never surfaces _mekari_account to consumers — resolved by real SDK mekari-account-web-sdk v0.3.0 (R7/R12)
mekari-account-web-sdk NOT installedpackage.json dependencies — absent; install via the git dependency in R1 — resolved (Q1 trace in §5)
current_company sync absentgrep current_company across **/*.{ts,vue} → no matches → new, see Q3
centralized_session toggle absentgrep centralized_session → no matches → new

2.1 Upstream contracts (read-only, owned by SSO/Session squads)

ContractShapeOwner
SDK package mekari-account-web-sdk v0.3.0new Session({ currentUser, sessionUrl?, interval?, checkTimeout?, includeSessionId? }), session.on("event", (data) => {}), session.off("event", cb), session.destroy() — no .refresh()Account/SSO
Session Manageriframe → https://sm.mekari.com/current (the SDK's sessionUrl default)Account/SSO
Eventslogged_in, logged_out, server_down — no switch_user; an account switch surfaces as logged_out and the incoming ssoId is never exposed (R5)Account/SSO
msli fallbacklocalStorage key msli (2h expiry), owned internally by the SDK — set on logged_in, cleared on logged_out; consumers must NOT read or write it (R7)Account/SSO
Current companyGET https://api.mekari.com/v1.1/users/me/current_company (OAuth2 me variant)SSO

Removed vs. the earlier draft: the "SDK CDN" row (account.mekari.com/sm/sdk.js — no such CDN exists, the SDK is a bundled git dependency, R2) and the "Shared cookie _mekari_account" row (never surfaced to consumers by the real SDK; the premise that Launchpad could read it was wrong, R7).

2.2 Proposal (Launchpad FE)

A *.client.ts Nuxt plugin loads the bundled mekari-account-web-sdk package, reads the current user_sso_id (launchpad.sso_id cookie / authProfile.sso_id), instantiates new Session({ currentUser, interval: 5 * 60 * 1000 }) (periodic re-check per PRD constraint 6.9 — the SDK's only periodic mechanism reloads the hidden iframe on a fixed timer, not activity-driven, R6), and subscribes once via session.on("event", (data) => { switch (data.status) { ... } }). The three possible statuses (logged_in, logged_out, server_down) are forwarded into a useCentralizedSession composable that maps each status to a Launchpad reaction using existing store actions and redirect builders. All behavior is gated by a new centralized_session toggle mirroring useToggleQontakOne. The SDK is a singleton — a second new Session() call anywhere in the app returns the first instance and ignores new options; teardown (e.g. on toggle flip or unmount) must call session.destroy(), not just off() (R11).

2.3 SDK contract & event mapping (OAuth2 variant)

Status (data.status from the single session.on("event", (data) => ...) callback)MeaningLaunchpad reaction (OAuth2 authz-code)Reuses
logged_insession exists, same currentUsersync current company; allow interactionuseClient, current-company composable
logged_outno session — explicit logout, 2h idle timeout, or an SSO account switch (the SDK cannot distinguish a switch from a logout and never exposes the incoming ssoId — R5)revoke tokens → product sign-out (normal flow); any post-re-login company re-sync happens after the user re-authenticates through the ordinary SSO flow, not via this event (R10)ssoStore.clearTokenLaunchpad(), authStore.resetAuth(), SSO_URL/sign_out
server_downthe SDK's internal msli-based fallback (2h) could not confirm the session within checkTimeoutfail-open (PRD 6.10): take NO destructive action, do not force sign-out; log/observe only. The SDK owns msli internally — Launchpad must not read or write it (R7)observability counter only

State Surface Contract

EntitySurfaced toSource of truthNotes
Session statusroute middleware + auth storeSDK data.status via "event"drives gate/redirect
currentUserSDK constructor inputlaunchpad.sso_id cookieset at authStore.ts:139; passed once at construction (SDK is a singleton — R11)

msli intentionally does not appear in this table: it is an SDK-internal implementation detail (localStorage key msli) that Launchpad must never read or write (R7).

2.3.1 ADR-1 — Auth model variant: OAuth2 Authorization Code

  • Context: PRD defines two product variants (Rails web-session vs OAuth2 authz-code). Launchpad authenticates via SSO authorization-code exchange.
  • Options: (a) web-session flow with client_credentials + Rails cookie; (b) OAuth2 authz-code flow with bearer tokens.
  • Decision: (b). Launchpad already exchanges authorization_code (ssoCallbackStore.ts:53) and stores bearer tokens in global_sso_* cookies.
  • Rationale: matches existing code; no Rails session in this SPA.
  • Consequences: logged_out (including account switches, which the SDK reports as logged_out — R5) must revoke tokens, not destroy a Rails session. server_down must fail-open — no token revocation (R7). Current-company uses the /me endpoint variant.
  • Reversibility: high — variant is isolated to the event handlers.

2.3.2 ADR-2 — SDK load location: client plugin

  • Context: SPA (ssr:false); SDK injects an iframe and needs DOM.
  • Options: (a) app.vue useHead script; (b) *.client.ts Nuxt plugin; (c) per-page import.
  • Decision: (b) app/plugins/mekariSession.client.ts.
  • Rationale: plugins already wire global singletons (plugins/auth.ts); client-only suffix guarantees browser context; single init point.
  • Consequences: SDK runs on every authenticated page; init must be idempotent and toggle-gated. Because the SDK is a singleton (a 2nd new Session() call returns the 1st instance and ignores new options), re-init on toggle flip or user change requires calling session.destroy() first — off() alone leaves the iframe and window listener attached (R11).
  • Reversibility: high — delete plugin / flip toggle.

2.3.3 ADR-3 — Toggle mechanism

  • Context: PRD requires a centralized_session toggle; Launchpad's only toggle (useToggleQontakOne.ts:11) is hardcoded always-on.
  • Options: (a) hardcoded const; (b) runtimeConfig env var; (c) backend per-company endpoint.
  • Decision: mirror useToggleQontakOne structure with a TOGGLE_SOURCE switch; default source unresolved — Q4 (env var preferred for the pilot).
  • Rationale: consistency with the existing pattern; isolates the rollout lever.
  • Consequences: if per-company control is required at step 5, source flips to backend (same as the documented useToggleQontakOne backend path).
  • Reversibility: high.

2.3.4 ADR-4 — Event wiring point

  • Context: events must end/redirect sessions; the auth gate is route middleware.
  • Options: (a) handle entirely in plugin; (b) plugin emits → composable → middleware + store.
  • Decision: (b). Plugin forwards events to useCentralizedSession; the middleware awaits the initial resolution (PRD: "Product FE waits for SDK response" when toggle on), store actions perform token/company changes.
  • Rationale: reuses the existing single gate (authenticated.global.ts) and avoids duplicate redirect logic.
  • Consequences: middleware gains a toggle-gated await; must not block when toggle off.
  • Reversibility: medium — touches the shared middleware.

ADR minimum-coverage: Storage n/a — no DB (FE); Sync/async — SDK event + company fetch are async, awaited in middleware; Caching — Session Manager disables caching (max 5s, upstream); the SDK's internal msli cache is not FE-visible and must not be touched by Launchpad (R7); Third-party — SDK is a bundled git dependency delivering events via iframe/postMessage, not a runtime script load (ADR-2, R2); Consistency — eventual, driven by the SDK's fixed-interval iframe re-check (interval option, not activity-driven — R6); Multi-tenancy — company sync per currentUser; Reuse/new — reuses sign-out + re-auth builders, new composables only.

2.4 Sequence diagrams

Happy path — logged_in

sequenceDiagram
participant U as User
participant FE as Launchpad FE
participant MW as authenticated.global.ts
participant SDK as mekari-account-web-sdk
participant IF as iframe (sm.mekari.com/current)
participant SM as Session Manager + Redis
participant API as SSO current_company
U->>FE: open page
FE->>MW: route enter
MW->>SDK: (toggle on) await session
SDK->>IF: open iframe
IF->>SM: GET /current
SM->>SM: validate Redis, update last_request_at
SM-->>IF: render session status (no-cache ≤5s)
IF-->>SDK: postMessage(status)
SDK-->>MW: session.on("event", {status: "logged_in"})
MW->>FE: allow nav
FE->>API: GET /users/me/current_company (Q3)
API-->>FE: current company
FE-->>U: render

Failure path — server_down (timing: SDK checkTimeout exhausted)

sequenceDiagram
participant FE as Launchpad FE
participant SDK as mekari-account-web-sdk
participant SM as Session Manager
SDK->>SM: GET /current (iframe, checkTimeout window)
SM--xSDK: no response within checkTimeout
Note over SDK: SDK's own internal msli check (2h) — not FE-readable, R7
alt internal msli < 2h
SDK-->>FE: session.on("event", {status: "logged_in"})
else msli stale/absent
SDK-->>FE: session.on("event", {status: "server_down"})
Note over FE: fail-open (PRD 6.10) — no destructive action, log/observe only (R7)
end

Account switch behavior (no dedicated event — R5)

An SSO account switch is delivered by the SDK as the same logged_out status used for an explicit logout or idle timeout; the SDK does not expose the incoming user's ssoId, so Launchpad cannot special-case it. Launchpad simply runs its normal sign-out flow (see the logged_out diagram below). Any post-re-login current-company re-sync happens naturally once the user re-authenticates through the ordinary SSO flow and the logged_in status fires again — it is a separate BE/SSO concern, not driven by a switch-specific SDK event (R10).

logged_out / product logout (also covers account switches — R5)

sequenceDiagram
participant U as User
participant FE as Launchpad FE
participant SDK as mekari-account-web-sdk
participant SSO as SSO sign_out
SDK-->>FE: session.on("event", {status: "logged_out"})
Note over SDK,FE: same status fires for explicit logout, 2h idle timeout, or an SSO account switch (SDK cannot distinguish — R5)
FE->>FE: clearTokenLaunchpad() + resetAuth() (cookies cleared)
FE->>SSO: window.location SSO_URL/sign_out?client_id=
SSO-->>U: empty-session redirect to sign in

2.5 Current-company sync

Per PRD OAuth2 variant, after a session is confirmed Launchpad must fetch the current company. Launchpad's existing profile call goes through Kong (authStore.ts:81apiBaseUrl/users/me). The current-company endpoint (/users/me/current_company) is not present in the repo and has no verified FE-reachable host. New composable useCurrentCompany.ts will call it via useClient, but the endpoint owner/host is an Open Question (Q3) — do not hardcode api.mekari.com from the FE without confirming CORS/Kong routing.

Endpoint tag: new-with-justification — no existing Launchpad contract returns current company; reuse of /users/me is insufficient because the PRD requires a post-switch re-sync against SSO's authoritative current company.

APIs — Outbound (FE → BE)

MethodPathStatusNotes
GET/users/me/current_company (via Kong or BE proxy)new-with-justificationhost/owner unresolved — Q3
POST/seamless/refresh_sso_tokenreuseduseClient.ts:120
GET/users/mereusedauthStore.ts:81
redirectSSO_URL/sign_out?client_id=reusedSwitchAccountContent.vue:117
redirectSSO_URL/auth?...response_type=codereusedauthenticated.global.ts:80

APIs — Inbound (→ Launchpad)n/a — FE has no inbound HTTP; SDK delivers events via postMessage

Role × Endpoint Authorizationn/a — session handling is role-agnostic; all authenticated users follow the same flow

Branch & Skip Catalog

BranchTriggerOwner
Toggle off → skip SDKcentralized_session falseFE
server_down fail-open — no sign-outany server_down statusFE (R7)
Excluded pages (login/sso-callback) → no gateauthenticated.global.ts:11FE

2.6 Database Model

n/a — no database changes (frontend RFC).


3. High-Availability & Security

3.1 Performance (FE-relevant)

Service-side RPS/latency (6k/s, 50ms avg, p95 < 100ms) are owned by the Session Manager service. FE obligation: SDK init must be non-blocking when the toggle is off, and the awaited resolution when on must have a hard client-side timeout so a slow /sm/current cannot hang Launchpad navigation indefinitely (falls through to server_down per ADR-4).

3.2 Security (OWASP-aligned)

  • A05 / A07 — CSP for the SDK iframe (resolved host — R8): whitelist sm.mekari.com via frame-src/child-src (NOT script-src — there is no script to load; the SDK is bundled at build time, R2). frame-ancestors on the Session Manager side applies to the sm.mekari.com origin, not account.mekari.com. Adding the Launchpad origin to that allowlist is the step-5 rollout gate. FE-side: confirm the Launchpad CSP header does not block the sm.mekari.com iframe — Q6.
  • A08 / A07 — SDK does not validate event.origin (new upstream finding, R9): the real SDK's window message listener checks only event.data.source === "mekari-account-web-sdk"; it does not validate event.origin. Launchpad (or any consumer) cannot mitigate this — the SDK owns the listener and exposes no override hook. This is an upstream infosec finding for the SDK owners (Account & Launchpad / Account & SSO), not something fixable from the Launchpad FE. Tracked as an open security item, not a confirm-question — see Q7 in §5.
  • A02 — token handling: logged_out (including account switches, which surface as logged_out — R5) must clear global_sso_* cookies via existing clearTokenLaunchpad/resetAuth; never log tokens (AGENTS.md). server_down must fail-open — no token revocation (R7).

3.3 Observability

Reuse console.error/console.warn for unexpected SDK states (AGENTS.md; no console.log in committed code). Route any API errors through useErrorHandler. Add an FE counter/log line whenever server_down fires (fail-open, R7 — no destructive action taken) so the pilot can measure SDK reachability. [REQUIRED: confirm FE telemetry sink — Mixpanel via plugins/mixpanel.ts or log only].


4. Backwards Compatibility & Rollout Plan

4.A Compatibility

Toggle-gated; when centralized_session is off, behavior is byte-for-byte the current flow (SDK never instantiated, middleware await skipped). Logout already hits SSO sign_out (SwitchAccountContent.vue:117), so no regression there.

4.B Pre-merge verification commands

StepCommandSource
Lintpnpm run lintpackage.json "lint"
Typecheckpnpm run type-checkpackage.json "type-check"
Testpnpm run testpackage.json "test": "vitest --dom --pool=forks"
Buildpnpm run buildpackage.json "build": "nuxt build"

Install caveat (AGENTS.md): qontak-unified-component is a git+ssh Bitbucket dep; pnpm install needs SSH access. Do not loop on install failures.

4.C Agent Execution Plan

Chunk 1 — centralized_session toggle

  • Files: app/common/composables/useCentralizedSessionToggle.ts (new), app/common/composables/useCentralizedSessionToggle.spec.ts (new).
  • Command: pnpm run test -- app/common/composables/useCentralizedSessionToggle.spec.ts
  • Accept: toggle off → composable returns false; on → true. Test red first.
  • Blocked by: Q4 (source).

Chunk 2 — SDK loader plugin

  • Files: app/plugins/mekariSession.client.ts (new), spec.
  • Accept: when toggle on and launchpad.sso_id set, new Session({currentUser}) is called with that id (mock SDK), and session.on("event", handler) is registered exactly once; when toggle off, SDK is not loaded.
  • Blocked by: none — Q1 (distribution) and Q5 (iframe URL) are resolved (mekari-account-web-sdk git dependency; sm.mekari.com/current — R12).

Chunk 3 — logged_out handler

  • Files: app/common/composables/useCentralizedSession.ts (new) + spec.
  • Accept: data.status === "logged_out"clearTokenLaunchpad() + resetAuth() called + redirect to SSO_URL/sign_out?client_id=. Reuse ssoCallbackStore. This same branch handles account switches (R5) — no separate case is needed.

Chunk 4 — logged_in + current-company sync

  • Files: app/common/composables/useCurrentCompany.ts (new) + spec; extend useCentralizedSession.ts.
  • Accept: data.status === "logged_in" → company fetched via useClient, set on store. (The SDK's internal msli bookkeeping is not touched by Launchpad — R7.) Blocked by: Q3 (endpoint host).

Chunk 5 — server_down fallback

  • Files: extend useCentralizedSession.ts + spec.
  • Accept: data.status === "server_down" → fail-open (PRD 6.10): no destructive action, log/observe only. Not blocked — Q2 is resolved (R7/R12); there is no FE-readable predicate to build because the SDK's msli fallback is internal to the SDK.

Chunk 6 — middleware integration

  • Files: app/middleware/authenticated.global.ts (modify).
  • Accept: toggle on → middleware awaits initial SDK resolution with a timeout; toggle off → unchanged path (existing specs still green).

Order respects dependencies: toggle → loader → handlers → middleware.

4.D Verification & Rollback Recipe

  • Pre-merge: run §4.B in order; all green.
  • Post-deploy signals: server_down counter near zero; no spike in unexpected sign-outs; SDK iframe loads on Launchpad (network panel shows sm.mekari.com/current). [REQUIRED: dashboard/metric name].
  • Rollback: set centralized_session toggle off (one lever — ADR-3) → behavior reverts to current flow; if toggle is env-sourced, revert the env value and redeploy; revert the SDK-loader PR if the plugin itself misbehaves.

4.E Rollout (Launchpad = PRD step 4 pilot)

StageAudienceGo/No-go evidence
InternalAccount & Launchpad teamevents fire correctly in staging
Canarytoggle on for pilot company setno abnormal sign-out rate; fallback counter low
GAall Launchpad userscanary signals stable; infosec sign-off on SDK hardening

5. Concern / Questions / Known Limitations

  • Q1 [RESOLVED] SDK distribution: mekari-account-web-sdk v0.3.0, a bundled git dependency (npm install git+https://...#<version>); there is no CDN, no <script> tag, no sdk.js. Resolved by real SDK mekari-account-web-sdk v0.3.0, README.md (R1/R2/R12). Unblocks chunk 2.
  • Q2 [RESOLVED] server_down fallback: the earlier _mekari_account predicate was wrong — the real SDK owns its msli fallback (2h expiry) internally (set on logged_in, cleared on logged_out); Launchpad never evaluates a predicate. On server_down, Launchpad fails open (PRD 6.10) — no destructive action. Resolved by real SDK mekari-account-web-sdk v0.3.0, session.ts (R7/R12). Unblocks chunk 5.
  • Q3 [critical, still open] Current-company endpoint host/owner: is there a Launchpad BE proxy, or does FE call api.mekari.com directly (CORS/Kong)? This is a genuine cross-layer BE dependency, unaffected by the SDK correction (R10). Blocks chunk 4. Owner: Launchpad BE.
  • Q4 [important, still open] centralized_session toggle source for the pilot (env var vs backend per-company). Owner: AL.
  • Q5 [RESOLVED] Canonical Session Manager path: https://sm.mekari.com/current (the SDK's sessionUrl default) — not account.mekari.com/sm/current, not /sessionmanager/current. Resolved by real SDK mekari-account-web-sdk v0.3.0, README.md (R8/R12).
  • Q6 [important] Launchpad CSP must whitelist sm.mekari.com via frame-src/child-src (R8); confirm no existing Launchpad CSP header blocks it. Owner: infosec.
  • Q7 [important — upstream security finding, not a confirm-question, R9] The real SDK's message listener validates only event.data.source, never event.origin. Launchpad cannot mitigate this from the FE. Record as an open infosec finding to the SDK owners (Account & Launchpad / Account & SSO), OWASP A08/A07. Owner: infosec + SDK owners.
  • [nice-to-have] Whether a visible loading state is needed while the SDK resolves. Owner: design.

6. Comment logs

DateAuthorComment
2026-06-28rfc-starterInitial draft from PRD; 4 critical FE/cross-layer blockers raised.
2026-07-02rfc-corrector (latest review)Reconciled against real SDK mekari-account-web-sdk v0.3.0 (latest review): corrected package/import, currentUser, single-arg on("event"), removed switch_user/session.refresh()/consumer-msli, fixed iframe host to sm.mekari.com, recorded the event.origin security finding, reclassified resolved OQs.

7. Ready for agent execution

Ready for agent execution: no

Blocking gates (must resolve before yes):

  • Q3 current-company endpoint host/owner (cross-layer dependency; genuine, unaffected by the SDK correction — R10).

Resolved by the latest review SDK reconciliation (no longer blocking): Q1 (SDK distribution), Q2 (server_down fallback), Q5 (iframe path).

Non-blocking but required for full coverage: Q4 (toggle source), Q6 (CSP whitelist for sm.mekari.com), Q7 (upstream event.origin security finding — infosec to track with SDK owners). Optional: design loading state.

Optional second pass: hand to rfc-reviewer for scoring once Q3 closes.