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-sdkbuild, 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
| Field | Value | Notes |
|---|---|---|
| Status | RFC | IDEA / RFC / ABANDON / AGREED |
| Type | frontend | Launchpad is a Nuxt 4 SPA (ssr: false) |
| Sub-type | new-feature | new SDK integration + session-event handling |
| Title | Centralized Web Session — Launchpad FE Integration | |
| Owner | Account & Launchpad | |
| Authors | Account & Launchpad team | |
| Reviewers | SSO/Session service reviewer; Launchpad BE reviewer | placeholders pending |
| Approvers | Launchpad tech lead; infosec | infosec approval required |
| Submitted | 2026-06-28 | ISO-8601 |
| Last updated | 2026-07-02 | matches latest edit |
| Target release | TBD | placeholder — see Open Questions |
| Related documents | Centralized Web Session PRD | authoritative driver |
| Discussion | TBD |
Sections at a Glance
| § | Section | FE hints |
|---|---|---|
| 1 | Overview | goals, scope, Design References, PRD coverage, Decisions index, Per-Story Change Map |
| 2 | Technical Design | Repo Reading Guide, SDK contract, event handling, current-company sync, diagrams |
| 3 | High-Availability & Security | SDK loading hardening, FE observability, perf expectations |
| 4 | Backwards Compatibility & Rollout | toggle rollout, Agent Execution Plan, Verification & Rollback Recipe |
| 5 | Concern / Questions / Known Limitations | Open Questions with severity |
| 6 | Comment logs | review trail |
| 7 | Ready for agent execution | the 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_sessionfeature toggle.
1.2 Non-goals
- Building the Session Manager Golang service, shared Redis, or the
mekari-account-web-sdkpackage (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.
| Surface | Figma frame | Implementing file | Notes |
|---|---|---|---|
| Session-loading gate (blank/spinner while SDK resolves) | [REQUIRED: confirm with design whether a loading state is needed] | app/middleware/authenticated.global.ts | PRD: "Product FE waits for SDK response" when toggle on |
1.A PRD Coverage
PRD Section Coverage
| PRD section | Covered in | Status |
|---|---|---|
| 1. Overview / known issues | §1.0, §1.1 | covered |
| Success Criteria | §1.1, §2.x | covered (FE portion) |
| Out of Scope | §1.2 | covered |
| Dependencies (SDK, service, Redis) | §2.1 upstream contracts | covered (upstream, read-only) |
| 2. Technical Design — Proposal | §2.2–§2.6 | covered (FE portion) |
| SDK contract / events | §2.3 | covered |
| FE Product Integration Flows | §2.4–§2.6 | covered |
| Web Session Flow | §2.5 | n/a — Launchpad uses OAuth2 authz-code flow, not Rails web session |
| OAuth2 Authorization Code Flow | §2.5 | covered — Launchpad's actual auth model |
| User Logout From Product | §2.6 | covered |
| User Switch Account | §2.3/§2.4 (surfaces as logged_out; no dedicated event — R5) | covered |
| Database Model | — | n/a — no DB changes (FE) |
| 3. HA & Security | §3 | covered (FE-relevant subset) |
| 4. Rollout Plan | §4 | covered (Launchpad step 4) |
| 5. Open Questions | §5 | covered |
| FE Implementation Scope | §1.C, §4 | covered |
UI / Consumer Surface Coverage
| Surface | Read endpoint / source | Coverage |
|---|---|---|
| Session gate | SDK init resolution | §2.4 |
Removed: an "account changed" toast tied to a
switch_userevent — the real SDK has no such event (R5); an account switch runs the normallogged_outsign-out flow and introduces no new UI.
Role Coverage
| Role | Relevance | Coverage |
|---|---|---|
| Any authenticated Launchpad user | All session events apply uniformly | §2.4 |
| no per-role session behavior in PRD | — | n/a — session handling is role-agnostic |
1.B Decisions Closed (index → §2 ADRs)
| # | Decision | Chosen | ADR |
|---|---|---|---|
| D1 | Auth model variant for Launchpad | OAuth2 Authorization Code flow (not web-session) | ADR-1 |
| D2 | Where to load the SDK | *.client.ts Nuxt plugin | ADR-2 |
| D3 | Toggle mechanism | reuse useToggleQontakOne pattern, new centralized_session toggle | ADR-3 |
| D4 | Session event → app reaction wiring point | route middleware + auth store | ADR-4 |
| D5 | SDK distribution | bundled git dependency — mekari-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 scope | Changes | Acceptance criteria | RFC anchors |
|---|---|---|---|---|
Load SDK with currentUser on every page | FE-only | app/plugins/mekariSession.client.ts (new), reads authProfile.sso_id | plugin instantiates Session({currentUser}); unit test asserts SDK constructed with cookie launchpad.sso_id value | §2.2, §4.C chunk 2 |
Gate behind centralized_session | FE-only | useCentralizedSessionToggle.ts (new) | toggle off → no SDK side effects (test) | §2.3 ADR-3, §4.C chunk 1 |
Handle logged_in | FE + BE | sync current company after session confirmed | on 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-only | revoke tokens + product sign-out (OAuth2 variant) | event → resetAuth() + cookies cleared + redirect to SSO sign_out | §2.4, §4.C chunk 3 |
Handle server_down | FE-only | fail-open (PRD 6.10): no destructive action, observability counter only | server_down event → log/count only; product session untouched | §2.4, §4.C chunk 5 |
Wire product logout → SSO sign_out | FE-only | already present | logout hits SSO_URL/sign_out?client_id= | §2.6, SwitchAccountContent.vue:117 (reuse) |
| Current-company sync | FE + BE | new composable + BE endpoint | company 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
| File | What to learn |
|---|---|
app/middleware/authenticated.global.ts | the single auth gate; existing token-refresh + unified-logout flow; where to await SDK |
app/common/store/authStore.ts | fetchAuthLaunchpad, setProfileAuth (sets LAUNCHPAD_SSO_ID), resetAuth; authProfile.sso_id is the user SSO ID |
app/common/store/ssoCallbackStore.ts | setTokenLaunchpad/clearTokenLaunchpad, OAuth2 fetchOauthSSO (authorization_code) |
app/common/composables/useAuthCookies.ts | cookie 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.ts | central $fetch wrapper, 401 refresh singleton, handleAuthenticationFailure |
app/common/composables/useToggleQontakOne.ts | existing feature-toggle pattern to mirror for centralized_session |
app/features/sso-callback/composable/ssoCallback.ts | handleRedirection; SSO authz URL construction (SSO_URL/auth?client_id=...&response_type=code) |
app/layouts/components/SwitchAccountContent.vue | sign-out wiring → SSO_URL/sign_out?client_id= (line 117) — already matches PRD §2.6 |
app/app.vue / app/layouts/default.vue | mount points; $auth provided by plugins/auth.ts |
Patterns to Follow
| Concern | Reference file | Pattern |
|---|---|---|
| State management | app/common/store/authStore.ts | Pinia setup store, refs + actions |
| Feature toggle | app/common/composables/useToggleQontakOne.ts:11 | module-singleton TOGGLE_SOURCE switch |
| API call | app/common/composables/useClient.ts:43 | useClient(url, opts) → {data,error}; read body at data.value.data |
| Auth cookies | app/common/composables/useAuthCookies.ts:9 | useCookie with prod domain from config |
| Error normalize | app/common/composables/useErrorHandler.ts | route API errors through useErrorHandler (per AGENTS.md) |
| User notification | app/common/store/authStore.ts:117 | toast.notify({position,variant,title}) |
| SSO re-auth redirect | app/middleware/authenticated.global.ts:79 | SSO_URL/auth/?client_id=&response_type=code&scope=sso:profile&redirect_uri=BASE_URL/sso-callback |
| Logout redirect | app/layouts/components/SwitchAccountContent.vue:117 | clear cookies → SSO_URL/sign_out?client_id= |
| Composable test | app/common/composables/useAuthCookies.spec.ts | Vitest + happy-dom, mock #app |
Reading Order for the Agent
app/middleware/authenticated.global.tsapp/common/store/authStore.tsapp/common/store/ssoCallbackStore.tsapp/common/composables/useAuthCookies.tsapp/common/composables/useClient.tsapp/common/composables/useToggleQontakOne.tsapp/features/sso-callback/composable/ssoCallback.tsapp/layouts/components/SwitchAccountContent.vuenuxt.config.ts(runtimeConfig:SSO_URL,SSO_UNIFIED_CLIENT_ID,CHATPANEL_URL,BASE_URL,COOKIE_DOMAIN,apiBaseUrl)app/common/composables/useAuthCookies.spec.ts(test conventions)
2.0.1 Source Verification
| Item | Evidence |
|---|---|
| Auth gate is route middleware | app/middleware/authenticated.global.ts:5 defineNuxtRouteMiddleware; refresh at :44, unified logout at :90 |
| User SSO ID source | authStore.ts:139 LAUNCHPAD_SSO_ID.value = payload?.sso_id; cookie launchpad.sso_id at useAuthCookies.ts:30 |
| OAuth2 authz-code is Launchpad's model | ssoCallbackStore.ts:53 grantType: "authorization_code"; redirect builder authenticated.global.ts:80 |
| Token cookies | useAuthCookies.ts:5-7 global_sso_token/global_sso_refresh_token/global_sso_valid_until |
| Sign-out already hits SSO | SwitchAccountContent.vue:117 window.location.href = ${ssoBaseUrl}/sign_out?client_id=${ssoClientId} |
| Toggle pattern | useToggleQontakOne.ts:11 const TOGGLE_SOURCE = "always-on" |
| Central fetch wrapper | useClient.ts:9; 401 refresh :51; handleAuthenticationFailure :146 |
| Test stack | package.json: "test": "vitest --dom --pool=forks"; example spec useAuthCookies.spec.ts |
_mekari_account NOT a Launchpad cookie | grep 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 installed | package.json dependencies — absent; install via the git dependency in R1 — resolved (Q1 trace in §5) |
| current_company sync absent | grep current_company across **/*.{ts,vue} → no matches → new, see Q3 |
centralized_session toggle absent | grep centralized_session → no matches → new |
2.1 Upstream contracts (read-only, owned by SSO/Session squads)
| Contract | Shape | Owner |
|---|---|---|
SDK package mekari-account-web-sdk v0.3.0 | new Session({ currentUser, sessionUrl?, interval?, checkTimeout?, includeSessionId? }), session.on("event", (data) => {}), session.off("event", cb), session.destroy() — no .refresh() | Account/SSO |
| Session Manager | iframe → https://sm.mekari.com/current (the SDK's sessionUrl default) | Account/SSO |
| Events | logged_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 fallback | localStorage 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 company | GET 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) | Meaning | Launchpad reaction (OAuth2 authz-code) | Reuses |
|---|---|---|---|
logged_in | session exists, same currentUser | sync current company; allow interaction | useClient, current-company composable |
logged_out | no 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_down | the SDK's internal msli-based fallback (2h) could not confirm the session within checkTimeout | fail-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
| Entity | Surfaced to | Source of truth | Notes |
|---|---|---|---|
| Session status | route middleware + auth store | SDK data.status via "event" | drives gate/redirect |
currentUser | SDK constructor input | launchpad.sso_id cookie | set at authStore.ts:139; passed once at construction (SDK is a singleton — R11) |
msliintentionally does not appear in this table: it is an SDK-internal implementation detail (localStorage keymsli) 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 inglobal_sso_*cookies. - Rationale: matches existing code; no Rails session in this SPA.
- Consequences:
logged_out(including account switches, which the SDK reports aslogged_out— R5) must revoke tokens, not destroy a Rails session.server_downmust fail-open — no token revocation (R7). Current-company uses the/meendpoint 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.vueuseHeadscript; (b)*.client.tsNuxt 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 callingsession.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_sessiontoggle; Launchpad's only toggle (useToggleQontakOne.ts:11) is hardcodedalways-on. - Options: (a) hardcoded const; (b) runtimeConfig env var; (c) backend per-company endpoint.
- Decision: mirror
useToggleQontakOnestructure with aTOGGLE_SOURCEswitch; 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
useToggleQontakOnebackend 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:81 → apiBaseUrl/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)
| Method | Path | Status | Notes |
|---|---|---|---|
| GET | /users/me/current_company (via Kong or BE proxy) | new-with-justification | host/owner unresolved — Q3 |
| POST | /seamless/refresh_sso_token | reused | useClient.ts:120 |
| GET | /users/me | reused | authStore.ts:81 |
| redirect | SSO_URL/sign_out?client_id= | reused | SwitchAccountContent.vue:117 |
| redirect | SSO_URL/auth?...response_type=code | reused | authenticated.global.ts:80 |
APIs — Inbound (→ Launchpad) — n/a — FE has no inbound HTTP; SDK delivers events via postMessage
Role × Endpoint Authorization — n/a — session handling is role-agnostic; all authenticated users follow the same flow
Branch & Skip Catalog
| Branch | Trigger | Owner |
|---|---|---|
| Toggle off → skip SDK | centralized_session false | FE |
server_down fail-open — no sign-out | any server_down status | FE (R7) |
| Excluded pages (login/sso-callback) → no gate | authenticated.global.ts:11 | FE |
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.comviaframe-src/child-src(NOTscript-src— there is no script to load; the SDK is bundled at build time, R2).frame-ancestorson the Session Manager side applies to thesm.mekari.comorigin, notaccount.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 thesm.mekari.comiframe — Q6. - A08 / A07 — SDK does not validate
event.origin(new upstream finding, R9): the real SDK'swindowmessagelistener checks onlyevent.data.source === "mekari-account-web-sdk"; it does not validateevent.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 aslogged_out— R5) must clearglobal_sso_*cookies via existingclearTokenLaunchpad/resetAuth; never log tokens (AGENTS.md).server_downmust 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
| Step | Command | Source |
|---|---|---|
| Lint | pnpm run lint | package.json "lint" |
| Typecheck | pnpm run type-check | package.json "type-check" |
| Test | pnpm run test | package.json "test": "vitest --dom --pool=forks" |
| Build | pnpm run build | package.json "build": "nuxt build" |
Install caveat (AGENTS.md):
qontak-unified-componentis agit+sshBitbucket dep;pnpm installneeds 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_idset,new Session({currentUser})is called with that id (mock SDK), andsession.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-sdkgit 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 toSSO_URL/sign_out?client_id=. ReusessoCallbackStore. 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; extenduseCentralizedSession.ts. - Accept:
data.status === "logged_in"→ company fetched viauseClient, set on store. (The SDK's internalmslibookkeeping 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'smslifallback 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_downcounter near zero; no spike in unexpected sign-outs; SDK iframe loads on Launchpad (network panel showssm.mekari.com/current).[REQUIRED: dashboard/metric name]. - Rollback: set
centralized_sessiontoggle 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)
| Stage | Audience | Go/No-go evidence |
|---|---|---|
| Internal | Account & Launchpad team | events fire correctly in staging |
| Canary | toggle on for pilot company set | no abnormal sign-out rate; fallback counter low |
| GA | all Launchpad users | canary signals stable; infosec sign-off on SDK hardening |
5. Concern / Questions / Known Limitations
- Q1 [RESOLVED] SDK distribution:
mekari-account-web-sdkv0.3.0, a bundled git dependency (npm install git+https://...#<version>); there is no CDN, no<script>tag, nosdk.js. Resolved by real SDKmekari-account-web-sdkv0.3.0,README.md(R1/R2/R12). Unblocks chunk 2. - Q2 [RESOLVED]
server_downfallback: the earlier_mekari_accountpredicate was wrong — the real SDK owns itsmslifallback (2h expiry) internally (set onlogged_in, cleared onlogged_out); Launchpad never evaluates a predicate. Onserver_down, Launchpad fails open (PRD 6.10) — no destructive action. Resolved by real SDKmekari-account-web-sdkv0.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.comdirectly (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_sessiontoggle 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'ssessionUrldefault) — notaccount.mekari.com/sm/current, not/sessionmanager/current. Resolved by real SDKmekari-account-web-sdkv0.3.0,README.md(R8/R12). - Q6 [important] Launchpad CSP must whitelist
sm.mekari.comviaframe-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
messagelistener validates onlyevent.data.source, neverevent.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
| Date | Author | Comment |
|---|---|---|
| 2026-06-28 | rfc-starter | Initial draft from PRD; 4 critical FE/cross-layer blockers raised. |
| 2026-07-02 | rfc-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.