Skip to main content

RFC: Centralized Web Session — Hub FE Integration with mekari-session SDK

Document Conventions (do not remove)

Governance follows the Qontak RFC Template (metadata table + sections 1–6 + Comment logs). This RFC is agent-execution-ready: §1 references, §2 Repo Reading Guide, mermaid diagrams, and §4 Agent Execution Plan + Verification & Rollback Recipe must be complete before §7 flips to yes.

Scope of THIS RFC = Hub frontend integration only (the local/hub repo). The cross-product SDK (@mekari/sdk), the Golang Session Manager service, the dedicated Redis, and the SSO/Kong changes are owned by Account & Launchpad and are treated here as upstream dependencies — not in scope to build.


Metadata

FieldValueNotes
StatusRFCOne of IDEA / RFC / ABANDON / AGREED
Type / Sub-typefrontend / enhancementHub consumes a new shared SDK
TitleCentralized Web Session — Hub FE Integration
OwnerHub (Qontak)Consuming squad
AuthorsSyafrizal Muhammad
ReviewersHub Tech Lead · Account & LaunchpadA&L owns SDK + Session Manager
Approver(s)Hub EM · Infosec [REQUIRED]Infosec mandatory (iframe / postMessage / CSP)
Submitted2026-06-27ISO-8601
Last updated2026-06-27ISO-8601
Target release2026-Q3After Launchpad pilot (rollout step 4)
Related docsSource RFC (Confluence)See frontmatter
Discussion[REQUIRED]Slack thread

Sections at a Glance

§SectionHub-FE hints
1OverviewProblem, scope, PRD coverage, design references, decision index
2Technical DesignRepo Reading Guide, SDK wiring, event handling, sequences, ER/state
3High-Availability & Securityiframe/postMessage/CSP/referrer, observability
4Backwards Compatibility & Rolloutfeature toggle, agent execution plan, verify/rollback
5Concerns / Questions / LimitationsOpen questions with severity
6Comment logsReview trail
7Ready for agent executionGate marker

1. Overview

1.1 Problem

Hub maintains its own session (bearer token in qontak._token.hub cookie, see schemes/hubAuthScheme.js:152) independently of the SSO session. When a user logs out on SSO, or switches account on SSO, or is idle on SSO for >2h, Hub does not notice — it stays logged in to the stale account/company. The source RFC documents two concrete bugs (stale session after SSO logout; wrong company shown after account switch). Hub already redirects to SSO for login (middleware/login.js:22) and to ${SSO_ACCOUNT_URL}/sign_out on logout (components/layouts/main/SwitchAccount.vue:409), but there is no continuous session-status check while the user sits inside Hub.

This RFC integrates the new mekari-session SDK into Hub so Hub reacts to SSO session state (logged_in / logged_out / switch_user / server_down) on every page.

1.2 Success Criteria (Hub-scoped slice of source RFC)

  • Hub loads the mekari-session SDK on authenticated pages, passing the current user_sso_id (available as this.$auth.user.sso_id, see components/layouts/main/InitComponent.vue:1138).
  • Hub signs out when the SDK reports logged_out (no SSO session, or >2h idle).
  • Hub re-authenticates / adjusts company when the SDK reports switch_user.
  • Behaviour is gated by a centralized_session toggle; OFF = today's behaviour, byte-for-byte.
  • server_down falls back to the msli local-storage heuristic, then to sign-out, to keep session behaviour consistent across Mekari.

1.3 Out of Scope

  • Building the @mekari/sdk package, Session Manager (Golang), dedicated Redis, SSO Kong path account.mekari.com/sm/* — owned by Account & Launchpad.
  • Auto-revoke of access/refresh tokens on inactivity (explicitly out of scope in source RFC).
  • Multiple-sessions-per-account UX (server-side; rolls out in parallel, no Hub FE change required for the base integration).
  • Hub backend changes for current_company sync — see §5 OQ-1 (cross-squad, unverified; blocks the company-sync half of this RFC).

1.4 Detail 1.A — PRD Section Coverage

Source RFC sectionCovered inNote
1. Overview (web + FE-app bugs)§1.1Hub is a web-session product
Success Criteria§1.2Hub-scoped subset
Out of Scope§1.3inherited
Dependencies (SDK, Session Mgr, Redis)§1.6, §2.0upstream, READ-ONLY for Hub
2. Technical Design — Proposal§2Hub uses SDK on all authed pages
SDK usage / contract / events§2.4, §2.6four events handled
Local Storage msli§2.5, §2.6fallback handler
Cookies _mekari_account§2.0, §3set by SSO/Rails; Hub only reads via iframe
FE Product Integration Flows§2.6 sequencesHub = OAuth2 auth-code flow
Web Session Flow (client_credentials)n/a — Hub uses auth-code flowsee ADR-3
OAuth2 Authorization Code Flow§2.6matches existing sso-callback
User Logout / Switch Account§2.6, §2.7maps to doSignOut / re-auth
Database Model (no change)§2.8no DB; FE-only
3. HA & Security§3Hub-side CSP/referrer + observability
4. Rollout Plan§4Hub = "Next Chapter" consumer (step 5)
5. Open Questions§5extended with Hub-specific OQs
FE Implementation Scope (per repo)§4.CHub execution plan

1.5 Detail 1.A — UI / Consumer Surface Coverage

SurfaceRole(s)BackingNote
Every authenticated Hub page (under layouts/hub.vue)allSDK loaded once via boot componentSDK injects iframe; no visible UI
"User has changed" notification (on switch_user)allPixel toast / mp-broadcastsource RFC step: "continue with user-has-changed notification"
Sign-out redirectallexisting doSignOut()reused
No new page / routeFE behavioural change only

1.5b Detail 1.A — Role Coverage

RoleBehaviourNote
super_adminsame event handlingno role-specific branch in SDK flow
adminsame
supervisorsame; existing online-status side-effects on sign-out (SwitchAccount.vue:383) preserved
agentsame; same online-status side-effects

Authorization is not endpoint-gated by this RFC (no new authz surface) — see §3 Role × Endpoint Authorization (n/a).

1.6 Detail 1.B — Decisions Closed (index → §2 ADR blocks)

#DecisionChosenADR
1Where to load the SDKBoot component (InitComponent.vue) under layouts/hub.vueADR-1
2How to load the external SDK scriptNuxt plugin pattern (cf. plugins/hotjar.js) + dynamic importADR-2
3Which session-sync flow Hub followsOAuth2 Authorization Code flowADR-3
4Feature-flag source for centralized_sessionstore/organization.js feature_flag mapADR-4
5Map SDK events onto existing auth primitivesReuse $auth.logout / doSignOut / middleware/login redirectADR-5
6server_down behaviourmsli heuristic → then sign-outADR-6

1.7 Detail 1.C — Per-Story Change Map

StoryLayer scopeChangesAcceptance criteriaRFC anchors
Load SDK with user_sso_id on authed pagesFE-onlyplugins/mekari-session.js (new), components/inbox/.../MekariSession.vue or mixin (new), wire in InitComponent.vueUnit test: SDK constructed with sso_id from $auth.user; toggle OFF → not constructed§2.4, §4.C-2
Gate behind centralized_session toggleFE-onlyread organization.feature_flag.centralized_sessionTest: OFF → zero SDK calls (no iframe)§2.3 ADR-4, §4.C-1
Handle logged_outRuntime / behaviorcall existing doSignOut()Test: event → doSignOut invoked once§2.6, §4.C-3
Handle switch_userRuntime / behaviorrevoke tokens → auth-code re-login → notifyTest: event → $auth.logout + redirect to SSO auth§2.6, §4.C-4
Handle server_downRuntime / behaviormsli heuristic, then doSignOut()Test: msli>2h → sign-out; msli<2h+valid → stay§2.6, §4.C-5
Handle logged_in + company syncFE + BEcall Hub BE current-company sync endpoint (new, OQ-1)Test (FE): on logged_in, sync action dispatched§2.4, §5 OQ-1
Wire logout to also hit SSO sign_outFE-onlyalready done (SwitchAccount.vue:409)reuse — no changen/a — already implemented
msli write on confirmed loginFE-onlywrite msli timestamp on logged_inTest: localStorage msli set to now§2.5, §4.C-5

2. Technical Design

2.0 Repo Reading Guide (read before writing)

Repo Map (slice this RFC touches)

flowchart LR
subgraph hub["local/hub (FE — write here)"]
layout["layouts/hub.vue\n(authed shell, read)"]
init["components/layouts/main/InitComponent.vue\n(boot, modify)"]
sess["plugins/mekari-session.js\n(new)"]
mixin["assets/mixins/session/centralizedSession.js\n(new)"]
auth["schemes/hubAuthScheme.js\n(read — logout/refresh)"]
sw["components/layouts/main/SwitchAccount.vue\n(read — doSignOut, EventBus)"]
mwlogin["middleware/login.js\n(read — auth-code redirect)"]
org["store/organization.js\n(read — feature_flag)"]
ep["common/constants/endpoint.js\n(modify — current_company key)"]
end
subgraph al["account.mekari.com (Account & Launchpad — READ-ONLY)"]
sdk["@mekari/sdk Session\n(/sm/sdk.js)"]
sm["Session Manager /sm/current\n(iframe + postMessage)"]
end
layout --> init
init --> mixin
mixin --> sess
sess --> sdk
sdk -. iframe + postMessage .-> sm
mixin --> auth
mixin --> sw
mixin --> mwlogin
mixin --> org
mixin --> ep

Existing Code Anchors

FileWhat to learn
layouts/hub.vue:1Authenticated shell; renders InitComponent; gated by $auth.loggedIn
components/layouts/main/InitComponent.vueBoot component; has this.$auth.user.sso_id (:1138); cross-tab logout relay (:602-611) emits EventBus user-sign-out
components/layouts/main/SwitchAccount.vue:376doSignOut() — full sign-out (MQTT, FCM, moengage, $auth.logout, redirect to SSO sign_out)
components/layouts/main/SwitchAccount.vue:299EventBus.$on('user-sign-out', this.doSignOut) + $off at :302 — the relay hook to reuse
schemes/hubAuthScheme.js:36logout() — revokes via endpoint + clears all token cookies (removeChatTokenCookie etc.)
middleware/login.js:22SSO authorization-code redirect (/auth/?client_id=...&redirect_uri=.../sso-callback) — the re-auth pattern
middleware/sso-callback.js:130doSSOValidation() — auth-code callback handler; already uses window.opener.postMessage (:23)
store/organization.js:30feature_flag: {} state + getter (:44); flags come from org payload
store/preferences.js:91alt flag source: getFeatureFlag action (server settings) returning res.data[param]
plugins/hotjar.js:1Pattern for a third-party SDK boot plugin registered in nuxt.config.js:96
utils/general.js:104storeUserLoggedInLocalStorage — example local-storage helper pattern for msli
common/constants/endpoint.jsCanonical API URL registry (per AGENTS.md) — register current_company key here

Patterns to Follow

ConcernReference fileNote
Third-party SDK bootplugins/hotjar.js:1-3init in a Nuxt plugin; register in nuxt.config.js:84 plugins array
Cross-tab / decoupled eventscomponents/layouts/main/InitComponent.vue:610, SwitchAccount.vue:299-302EventBus.$on/$off with beforeDestroy cleanup (AGENTS.md leak rule)
Feature flag (org payload)store/organization.js:30,44v-if / computed getter; never v-show
Sign-outSwitchAccount.vue:376-414reuse doSignOut(); do not re-implement
Re-auth (auth-code)middleware/login.js:19-24redirect to SSO /auth with redirect_uri=.../sso-callback
Token revoke / resetschemes/hubAuthScheme.js:36-48, :24-34$auth.logout(payload) then reset()
Mixin scopingAGENTS.md "Mixins" rowassets/mixins/<domain>/; remove listeners in beforeDestroy

Detail 2.0 — Source Verification (anti-hallucination)

ClaimEvidence
user_sso_id available as $auth.user.sso_idcomponents/layouts/main/InitComponent.vue:1138 const ssoId = this.$auth.user.sso_id; also middleware/utils.js:10 $auth.user.sso_id
Hub uses OAuth2 authorization-code flow with SSOmiddleware/sso-callback.js:14 builds data.auth = route.query.code; middleware/login.js:22 redirect with response_type=code
Sign-out already redirects to SSO sign_outcomponents/layouts/main/SwitchAccount.vue:407-410 window.location.replace(\${SSO_ACCOUNT_URL}/sign_out...`)`
EventBus relay exists for sign-outSwitchAccount.vue:299 $on('user-sign-out', this.doSignOut); InitComponent.vue:610 $emit('user-sign-out')
Custom auth scheme owns logout/refreshschemes/hubAuthScheme.js:23 class HubAuthScheme extends LocalScheme; logout :36, refresh :210
Org feature_flag map existsstore/organization.js:30 feature_flag: {}, :1665 state.feature_flag = payload
postMessage already used in callbackmiddleware/sso-callback.js:23 window.opener.postMessage({ type: 'SSO_LOGIN_SUCCESS' }, '*')
Third-party SDK boot patternplugins/hotjar.js:1-3; registered nuxt.config.js:96
Test/build commandspackage.json:21 "test": "jest --coverage", :9 "build": "nuxt build", :19 "lint"
@mekari/sdk not yet a dependencypackage.json:34-60 deps list contains @mekari/pixel only, no @mekari/sdk → SDK add is new
No existing current_company usage in Hub FErepo grep `current_company
SSO env vars existnuxt.config.js:250 env block; SSO_ACCOUNT_URL/SSO_URL/SSO_UNIFIED_CLIENT_ID used across middleware/*, SwitchAccount.vue

2.1 Current State

Each page under layouts/hub.vue boots InitComponent.vue (MQTT, FCM, identity). Session is purely local: bearer token cookie + $auth state. SSO is contacted only at login (middleware/login.js) and logout (SwitchAccount.vue). No continuous SSO session check exists.

2.2 Proposal

Add a thin, toggle-gated centralized-session mixin booted from InitComponent.vue. When organization.feature_flag.centralized_session is on, it instantiates the mekari-session SDK with current_user = $auth.user.sso_id, subscribes to the four SDK events, and maps each event onto Hub's existing auth primitives (sign-out, auth-code re-login, company sync). The SDK itself injects the iframe to account.mekari.com/sm/current and relays state via postMessage; Hub only consumes the resulting events. Hub uses the OAuth2 Authorization Code variant (ADR-3) because that is already how Hub authenticates.

2.3 Technical Decisions (ADR format)

ADR-1 — Where to load the SDK

  • Context: SDK must run on "all pages" of the authenticated app; Hub already has a single boot component per authed shell.
  • Options: (a) InitComponent.vue boot component under layouts/hub.vue; (b) a Nuxt global middleware; (c) per-page.
  • Decision: (a). The SDK lifecycle lives in a mixin used by InitComponent.vue.
  • Rationale: InitComponent already centralises boot side-effects (MQTT/FCM/ identity) and already reads $auth.user.sso_id (:1138). One mount point = one iframe, matching SDK "caching disabled, 5s" expectation without per-route churn.
  • Consequences: SDK runs only inside authed shell (correct — unauthed pages have no sso_id). Login/SSO-callback pages keep current behaviour.
  • Reversibility: High — remove the mixin import + toggle.

ADR-2 — How to load the external script

  • Context: SDK ships as CJS+ESM and is also served from https://account.mekari.com/sm/sdk.js.
  • Options: (a) npm @mekari/sdk import; (b) runtime <script src> injection like plugins/hotjar.js; (c) both (npm with CDN fallback).
  • Decision: (a) npm @mekari/sdk import behind a dynamic import() inside the toggle guard, mirroring lazy-load discipline.
  • Rationale: Versioned, tree-shakeable, testable (mockable in Jest). CDN URL kept as [REQUIRED] env for CSP allow-listing, not as the load path.
  • Consequences: Adds @mekari/sdk to package.json; needs A&L to publish the package + grant registry access (OQ-3).
  • Reversibility: High.

ADR-3 — Which session-sync flow Hub follows

  • Context: Source RFC defines a Web-Session (client_credentials, BE→SSO) flow and an OAuth2 Authorization-Code flow.
  • Options: (a) Web-session/client_credentials; (b) OAuth2 auth-code.
  • Decision: (b) OAuth2 Authorization Code.
  • Rationale: Hub already authenticates via auth-code (middleware/sso-callback.js:14 consumes route.query.code; middleware/login.js:22 requests response_type=code). Reusing it means switch_user re-auth and current_company fetch follow the users/me/current_company (auth-code) contract, not users/{id}/current_company.
  • Consequences: logged_out/server_down must revoke tokens then sign out (auth-code semantics), which $auth.logout already does.
  • Reversibility: Medium — switching to client_credentials would require a Hub BE proxy endpoint instead.

ADR-4 — Feature-flag source

  • Context: Two flag mechanisms exist: org payload feature_flag (store/organization.js:30) and server-settings preferences/getFeatureFlag (store/preferences.js:91).
  • Options: (a) org payload map; (b) preferences server-setting.
  • Decision: (a) organization.feature_flag.centralized_session.
  • Rationale: AGENTS.md mandates org-payload flags as the standard; read synchronously via getter; supports per-company rollout (matches source RFC "enable per company/application").
  • Consequences: Backend must add centralized_session to the org payload (OQ-2). Until then the toggle reads undefined → treated as OFF (safe default).
  • Reversibility: High.

ADR-5 — Reuse existing auth primitives for event handling

  • Context: Each SDK event maps onto an action Hub already performs.
  • Options: (a) reuse doSignOut() / $auth.logout / middleware/login redirect; (b) build new session-control code.
  • Decision: (a) reuse.
  • Rationale: doSignOut() already tears down MQTT/FCM/moengage and redirects to SSO sign_out; duplicating it risks leaks. EventBus 'user-sign-out' is the existing decoupled trigger (SwitchAccount.vue:299).
  • Consequences: Mixin emits EventBus.$emit('user-sign-out') for logout paths rather than calling sign-out directly, matching the cross-tab relay pattern.
  • Reversibility: High.

ADR-6 — server_down behaviour

  • Context: SDK emits server_down after backoff exhaustion; source RFC defines an msli (Mekari Session Logged-In) local-storage fallback.
  • Options: (a) immediate sign-out; (b) msli heuristic then sign-out; (c) ignore.
  • Decision: (b). If msli exists and now - msli < 2h AND the local Hub token is still valid → treat as logged_in (stay); else sign out.
  • Rationale: Matches source RFC "Retry-backoff fallback" and the stated goal "keep consistent session behavior across Mekari" without hard-logging-out users on a transient Session-Manager outage.
  • Consequences: Hub must write msli on every confirmed logged_in.
  • Reversibility: High.

Minimum coverage checklist: Storage → n/a — FE-only, no DB (§2.8); local storage msli + cookies (read) covered. Sync vs async → SDK events are async, handled via listeners (ADR-5). Caching → SDK iframe caching is A&L-owned ("max 5s"); Hub caches nothing. Third-party → npm import + CSP allow-list (ADR-2, §3). Consistency → eventual (poll-on-load via SDK). Multi-tenancy → sso_id comparison is the isolation key (event switch_user). Reuse vs new → ADR-5 (reuse auth primitives); only one new endpoint key (OQ-1).

2.4 APIs

Outbound (Hub → others)

MethodURL (key)StatusOwnerNote
iframe GETaccount.mekari.com/sm/currentnew (A&L)Account & Launchpadinvoked by SDK, not Hub code directly
GET@mekari/sdk Session.refresh() (throttled)new (A&L)A&Lextends SSO session on user activity
GETHub BE current-company sync → endpoint.*.user.currentCompanynew-with-justificationHub BE (OQ-1)no existing key (repo grep: 0 hits); needed to set company on logged_in/after switch_user
GET (existing)SSO /auth/?response_type=code...&redirect_uri=.../sso-callbackreusedSSOre-auth on switch_user (middleware/login.js:22)
GET (existing)${SSO_ACCOUNT_URL}/sign_outreusedSSOlogout (SwitchAccount.vue:409)

new-with-justification (current-company): reuse is impossible because Hub has no current-company endpoint today (verified by repo grep). The exact BE contract (/me/current_company per ADR-3, auth-code variant) must be confirmed by Hub BE — OQ-1.

Inbound (others → Hub)

ChannelFromPayloadNote
postMessage (window)Session Manager iframe (/sm/current)session state incl. user_sso_idconsumed inside the SDK, surfaced to Hub as the 4 events below; Hub does not parse postMessage directly
SDK event@mekari/sdk Session{ status, current_user, ... }see §2.6

SDK contract consumed by Hub

// assets/mixins/session/centralizedSession.js (new — shape per source RFC)
import { Session } from '@mekari/sdk'

const session = new Session({ current_user: this.$auth.user.sso_id })
session.on('event', (data, error) => {
// data.status ∈ { logged_in, logged_out, switch_user, server_down }
// NOTE: source RFC sample uses data.status === 'logout' while the events
// section lists 'logged_out' — reconcile with A&L (OQ-4) before coding.
})
session.refresh() // throttled per X (TBD by A&L)

2.5 Local Storage & Cookies (Hub-side)

KeyTypeOwnerHub use
mslilocalStorage (timestamp)Hub writeswritten = Date.now() on confirmed logged_in; read in server_down fallback (ADR-6)
_mekari_accountcookieSSO/RailsHub never reads directly; used by SDK iframe
qontak._token.hubcookieHub (hubAuthScheme.js:152)existing token validity check in server_down fallback

2.6 Sequence Diagrams

Happy path — page load, logged_in + company sync

sequenceDiagram
autonumber
participant U as User
participant Hub as Hub FE (InitComponent + mixin)
participant SDK as @mekari/sdk
participant IF as SM iframe (/sm/current)
participant SM as Session Manager (A&L)
participant R as Redis (session)
participant BE as Hub BE
participant SSO as SSO API

U->>Hub: open authed page
Hub->>Hub: toggle centralized_session ON?
Hub->>SDK: new Session({current_user: sso_id})
SDK->>IF: inject iframe (_mekari_account cookie)
IF->>SM: GET /sm/current
SM->>R: validate + update last_request_at (<2h)
R-->>SM: session ok (user_sso_id)
SM-->>IF: render page w/ user_sso_id
IF-->>SDK: postMessage(user_sso_id)
SDK-->>Hub: event {status: logged_in}
Hub->>Hub: localStorage msli = now
Hub->>BE: GET current_company (OQ-1)
BE->>SSO: GET /v1.1/users/me/current_company
SSO-->>BE: company
BE-->>Hub: company → set → allow interaction

Failure path — server_down (Session Manager unreachable)

sequenceDiagram
autonumber
participant Hub as Hub FE (mixin)
participant SDK as @mekari/sdk
participant SM as Session Manager

SDK->>SM: GET /sm/current (timeout / 5xx)
Note over SDK,SM: retry with backoff (A&L-owned); ~exhaust ≤ a few s
SDK-->>Hub: event {status: server_down}
alt msli exists AND now - msli < 2h AND qontak._token.hub valid
Hub->>Hub: treat as logged_in (stay) — no sign-out
else stale or no token
Hub->>Hub: EventBus.$emit('user-sign-out') → doSignOut()
end

switch_user — re-auth via authorization code

sequenceDiagram
autonumber
participant Hub as Hub FE (mixin)
participant SDK as @mekari/sdk
participant Auth as $auth (hubAuthScheme)
participant SSO as SSO

SDK-->>Hub: event {status: switch_user, current_user: other_sso_id}
Hub->>Hub: remove msli
Hub->>Auth: $auth.logout({token}) (revoke + clear cookies)
Hub->>SSO: redirect /auth?response_type=code&redirect_uri=.../sso-callback
SSO-->>Hub: /sso-callback?code=... (autologin via valid _mekari_account)
Hub->>Hub: doSSOValidation() → new session → company sync
Hub->>Hub: toast "Your account has changed"

logged_out

sequenceDiagram
autonumber
participant Hub as Hub FE (mixin)
participant SDK as @mekari/sdk
SDK-->>Hub: event {status: logged_out}
Hub->>Hub: remove msli
Hub->>Hub: EventBus.$emit('user-sign-out') → doSignOut() → SSO /sign_out

2.7 State Surface Contract & State Machine

stateDiagram-v2
[*] --> Unknown
Unknown --> LoggedIn: event logged_in (sso_id match)
Unknown --> LoggedOut: event logged_out
LoggedIn --> SwitchUser: event switch_user (sso_id mismatch)
LoggedIn --> LoggedOut: event logged_out
LoggedIn --> Degraded: event server_down
Degraded --> LoggedIn: msli<2h & token valid
Degraded --> LoggedOut: msli stale / invalid token
SwitchUser --> LoggedIn: re-auth complete (new sso_id)
LoggedOut --> [*]: doSignOut → SSO /sign_out
StateVisibilityTransitionsNote
LoggedInnormal app→SwitchUser/LoggedOut/Degradedwrites msli
SwitchUserbrief redirect→LoggedInrevoke + auth-code
Degradedapp stays (fallback)→LoggedIn/LoggedOutserver_down
LoggedOutredirect to SSOterminaldoSignOut()

2.8 Database Model

n/a — FE-only, no schema change (matches source RFC "Database Model: No database changes"). No erDiagram.

2.9 Branch & Skip Catalog

BranchOwnerBehaviour
toggle OFFHub FESDK never constructed; today's behaviour verbatim
sso_id absent ($auth not ready)Hub FEskip SDK init until $auth.user.sso_id present
server_down + msli freshHub FEstay logged in (no sign-out)
multiple-sessions-per-accountA&L (server)n/a — no Hub FE branch

2.10 Responsibility Boundary (cross-squad)

StepOwner
SDK package, iframe, /sm/current, Redis, Kong pathAccount & Launchpad
current_company SSO contract + Hub BE proxyHub BE (OQ-1)
org payload centralized_session flagHub BE (OQ-2)
SDK wiring, event handling, toggle, msli, observabilityHub FE (this RFC)

3. High-Availability & Security

3.1 Performance (Hub-side)

Hub adds one SDK init + one iframe load per authed shell mount. The 6k RPS / 50ms latency targets in the source RFC apply to A&L's /sm/current, not Hub. Hub requirement: SDK init must not block first paint — load lazily after $auth.loggedIn (ADR-1) and never gate route rendering on it (toggle-guarded).

3.2 Security Implications (Hub-side)

CheckRule / action
iframe origin (A05)SDK iframe must target account.mekari.com only; add CSP frame-src/child-src allow-list entry for account.mekari.com
script source (A05)CSP script-src allow-list account.mekari.com for /sm/sdk.js if CDN-loaded; npm import avoids this (ADR-2)
postMessage (A03/A07)SDK validates the iframe origin internally; Hub must NOT add its own wildcard postMessage listener for session data (Hub already uses postMessage('*') at sso-callback.js:23 for a different purpose — keep separate)
token handling (A02)no token logged; $auth.logout clears cookies (hubAuthScheme.js:43-46)
referrer/originsource RFC option 2 (referrer/origin whitelist) is A&L-side; Hub's prod domain must be added to A&L's whitelist before rollout (OQ-5)

CSP/whitelist update mechanism is A&L-owned (source RFC §3); Hub's contribution is supplying its production domain(s) for the allow-list.

3.3 Observability (Hub-side)

  • Datadog RUM already initialised (plugins/datadog-rum.ts, nuxt.config.js:92). Emit a RUM custom action per SDK event (centralized_session.<event>).
  • Mixpanel v2 (assets/mixins/metric/mixpanelMixin.js, AGENTS.md) — log switch_user and forced sign-outs for funnel analysis.
  • Alert: spike in centralized_session.server_down (proxy for A&L outage).

4. Backwards Compatibility & Rollout Plan

4.A Compatibility

Toggle OFF ⇒ no SDK, no iframe, no behavioural change — fully backwards compatible. Hub is rollout step 5 ("The Next Chapter") in the source RFC, after the Launchpad pilot (step 4) proves the SDK. Hub's prod domain must be added to the SDK CSP frame-ancestors / referrer-origin whitelist before enabling.

4.B Pre-merge Verification (from package.json)

npm run lint # package.json:19
npm run test # package.json:21 (jest --coverage)
npm run build # package.json:9 (nuxt build) — must exit 0

4.C Agent Execution Plan (ordered chunks)

Chunk 1 — Toggle plumbing

  • Files: store/organization.js (confirm feature_flag getter exposes centralized_session; no code change if generic), new computed in mixin.
  • Acceptance: unit test — getter returns false when flag absent.

Chunk 2 — SDK boot plugin + mixin (TDD first)

  • Files (new): plugins/mekari-session.js, assets/mixins/session/centralizedSession.js, assets/mixins/session/__tests__/centralizedSession.spec.js.
  • Files (modify): package.json (add @mekari/sdk), nuxt.config.js:84 (register plugin), nuxt.config.js:250 (add MEKARI_SESSION_SDK_URL env for CSP).
  • Commands: npm run test -- --testPathPattern="centralizedSession".
  • Acceptance: toggle ON + sso_id present ⇒ Session constructed with current_user = sso_id; toggle OFF ⇒ Session never constructed (mock asserts 0 calls).

Chunk 3 — Wire into boot + logged_out/logged_in

  • Files (modify): components/layouts/main/InitComponent.vue (use mixin after $auth.loggedIn).
  • Acceptance: logged_outEventBus.$emit('user-sign-out') called once; logged_inlocalStorage.msli set to a numeric timestamp.

Chunk 4 — switch_user re-auth

  • Files: mixin handler; reuse middleware/login.js redirect URL builder.
  • Acceptance: switch_user$auth.logout invoked, then window.location redirect to SSO /auth?response_type=code...redirect_uri=.../sso-callback; msli removed; toast emitted.

Chunk 5 — server_down fallback + msli

  • Files: mixin handler + small utils helper (cf. utils/general.js:104).
  • Acceptance: now-msli<2h & valid token ⇒ no sign-out; else user-sign-out emitted. Test both branches.

Chunk 6 — company sync (BLOCKED on OQ-1)

  • Files: common/constants/endpoint.js (register key), store/organization.js or store/users action for current_company.
  • Acceptance: logged_in ⇒ sync action dispatched; sets company. Do not start until OQ-1 resolved.

Chunk 7 — observability

  • Files: mixin (RUM/Mixpanel calls).
  • Acceptance: each event emits a RUM action; verifiable via mocked tracker.

Order rationale: toggle (1) → SDK/mixin (2) → boot+core events (3) → switch (4) → fallback (5) → company sync (6, gated) → telemetry (7).

4.D Verification & Rollback Recipe

Post-deploy signals

  • Datadog RUM action centralized_session.logged_in appears for piloted company.
  • centralized_session.server_down rate ≈ 0 under normal A&L health.
  • No regression in Hub login funnel (Mixpanel v2).

Rollback (numbered, agent-executable)

  1. Set org feature_flag.centralized_session = false for affected company (instant, no deploy) → SDK stops constructing.
  2. If code-level: revert the InitComponent wiring PR (Chunk 3) — toggle already makes this safe; revert removes the mixin import entirely.
  3. Confirm centralized_session.* RUM actions drop to zero.
  4. Ask A&L to remove Hub's domain from the SDK whitelist if fully reverting.

Rollback respects order: disable flag (1) before reverting code (2); no other Hub layer depends on this mixin.


5. Concerns, Questions, or Known Limitations

#SeverityQuestion / limitationOwner
OQ-1[critical]Hub BE has no current-company endpoint (repo grep: 0 hits). Need confirmed BE contract for users/me/current_company proxy (auth-code variant per ADR-3). Blocks Chunk 6.Hub BE
OQ-2[critical]centralized_session flag is not yet in Hub's org payload (store/organization.js). Without it the toggle is permanently OFF.Hub BE
OQ-3[critical]@mekari/sdk is not in package.json and not published/accessible to Hub's registry yet. Cannot import until A&L publishes + grants access.A&L
OQ-4[important]Source RFC contradicts itself: sample uses data.status === 'logout'; events list says logged_out. Need canonical event names + payload shape before coding handlers.A&L
OQ-5[important]Hub production domain(s) must be added to SDK CSP frame-ancestors / referrer-origin whitelist before enable.A&L + Hub
OQ-6[important]CSP frame-src/script-src for account.mekari.com — confirm Hub's current CSP source (header vs meta) and where to edit.Hub FE / Infra
OQ-7[nice-to-have]Session.refresh() throttle interval is TBD in source RFC; Hub needs the value to wire activity-based refresh.A&L
OQ-8[nice-to-have]Infosec approver name + Slack discussion thread unfilled in metadata.Hub

Known limitation: until OQ-1/OQ-2/OQ-3 close, only Chunks 1–5 (session detect + sign-out/switch/fallback, toggle-gated, no company sync) are agent-executable.


6. Comment logs

DateAuthorNote
2026-06-27Syafrizal MuhammadInitial Hub-FE integration draft derived from A&L source RFC

7. Ready for agent execution

Ready for agent execution: no

Blocking gates (must resolve before flipping to yes):

  • OQ-1 [critical] — Hub BE current-company endpoint contract unverified (blocks company sync / Chunk 6).
  • OQ-2 [critical]centralized_session org-payload flag does not exist yet (toggle inert).
  • OQ-3 [critical]@mekari/sdk not published / not in package.json; cannot import.

Non-blocking but needed for full completion: OQ-4 (event-name reconciliation), OQ-5/OQ-6 (CSP/whitelist), OQ-7 (refresh throttle).

Chunks 1–5 are executable today against the live repo behind the (currently OFF) toggle; Chunk 6 is gated on OQ-1/OQ-2.