Skip to main content

RFC: Centralized Web Session — Hub FE Integration with mekari-account-web-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-account-web-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-07-02ISO-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-account-web-sdk SDK into Hub so Hub reacts to SSO session state (logged_in / logged_out / server_down) on every page. An SSO account switch is delivered by the SDK as logged_out — the SDK cannot distinguish a switch from a plain logout and does not expose the incoming user's ssoId to consumers (see R5 / §2.6) — so it is handled by the same sign-out path, not a dedicated event.

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

  • Hub constructs the mekari-account-web-sdk Session on authenticated pages with currentUser: this.$auth.user.sso_id (available at components/layouts/main/InitComponent.vue:1138).
  • Hub signs out when the SDK reports logged_out (no SSO session, or >2h idle).
  • An SSO account switch is delivered as logged_out (indistinguishable from a plain logout — R5); Hub runs the same sign-out flow. Any company re-sync after the user re-authenticates is a separate BE/SSO concern, not driven by the SDK (§2.10).
  • Behaviour is gated by a centralized_session toggle; OFF = today's behaviour, byte-for-byte.
  • On server_down, Hub takes no action (fail-open, per PRD constraint 6.10) — the SDK owns its own internal msli fallback, and Hub must not read/write that key or otherwise duplicate the SDK's retry logic.

1.3 Out of Scope

  • Building the mekari-account-web-sdk package, Session Manager (Golang), dedicated Redis, SSO Kong path sm.mekari.com/* — 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). This dependency is independent of the SDK (§2.10, R10).

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.6three statuses handled (logged_in/logged_out/server_down); no switch_user (R5)
Local Storage msli§2.5, §2.6SDK-owned internally; Hub does not read/write it (R7)
Cookies _mekari_account§2.0, §3set by SSO/Rails, consumed SM-service-side; not observable by the SDK's client code or by Hub
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 (an SSO account switch surfaces as logged_out, indistinguishable — R5)
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
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 the SDK is installedBundled git dependency (mekari-account-web-sdk) — no CDN alternativeADR-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 behaviourFail-open (no action taken)ADR-6

1.7 Detail 1.C — Per-Story Change Map

StoryLayer scopeChangesAcceptance criteriaRFC anchors
Load SDK with user_sso_id on authed pagesFE-onlyplugins/centralized-session.js (new), components/inbox/.../MekariSession.vue or mixin (new), wire in InitComponent.vueUnit test: SDK constructed with currentUser from $auth.user.sso_id; 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 server_downRuntime / behaviorfail-open — no action taken (ADR-6)Test: event → no doSignOut / no state change (assert no-op)§2.6, §4.C-5
Handle logged_in + company syncFE + BEcall Hub BE current-company sync endpoint (new, OQ-1; decoupled from the SDK — R10)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

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/centralized-session.js\n(new)"]
mixin["assets/mixins/session/centralizedSession.js\n(new)"]
sdk["mekari-account-web-sdk Session\n(bundled git dependency, imported)"]
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 sm_ext["sm.mekari.com (Account & Launchpad — READ-ONLY)"]
sm["Session Manager /current\n(iframe + postMessage)"]
end
layout --> init
init --> mixin
mixin --> sess
sess --> sdk
mixin --> 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 (:24)
store/organization.js:30feature_flag: {} state + getter (:41); 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
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,41v-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:15 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:24 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-account-web-sdk not yet a dependencypackage.json:34-60 deps list contains @mekari/pixel@^1.1.14 only, no mekari-account-web-sdk → SDK add is new (bundled git dependency, ADR-2)
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
SDK real contract (constructor/options/events/statuses)verified against mekari-account-web-sdk v0.3.0 src/session.ts, src/index.ts, README.md (latest review reconciliation) — see §2.4

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-account-web-sdk Session with currentUser: $auth.user.sso_id, subscribes to the single session.on('event', (data) => ...) channel, and maps data.status onto Hub's existing auth primitive: logged_out → the existing sign-out relay (EventBus.$emit ('user-sign-out')doSignOut()). The SDK itself injects a hidden iframe pointed at https://sm.mekari.com/current and relays state via postMessage handled entirely inside the SDK (the SDK checks only event.data.source, not event.origin — a security finding, see §3.2/OQ-9); Hub only consumes the resulting session.on('event', ...) callback, never postMessage directly.

An SSO account switch is delivered by the SDK as logged_out — the SDK cannot distinguish a switch from a logout and never exposes the other user's ssoId — so Hub runs its normal sign-out flow in that case too; any post-re-login company re-sync is a separate BE/SSO concern (§2.10), not SDK-driven.

Hub uses the OAuth2 Authorization Code variant (ADR-3) because that is already how Hub authenticates; this is unrelated to the SDK, which exposes no login/auth-code API of its own (its public surface is exactly constructor, destroy(), on(), off()).

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 without per-route churn; this also matches the SDK's singleton behaviour — a 2nd new Session() call returns the 1st instance and ignores new options — so a single, well-defined mount point avoids accidental re-construction (R11).
  • Consequences: SDK runs only inside authed shell (correct — unauthed pages have no sso_id). Login/SSO-callback pages keep current behaviour. On full teardown (e.g. sign-out), the mixin must call session.destroy() — not .off() alone — since destroy() is what resets the singleton and tears down the iframe + internal window listener (R11).
  • Reversibility: High — remove the mixin import + toggle.

ADR-2 — How the SDK is installed

  • Context: The SDK, mekari-account-web-sdk (v0.3.0), is distributed as a private git dependency: npm install git+https://<user>:<pass>@bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk#<version>, imported as import { Session } from 'mekari-account-web-sdk'. There is no CDN, no <script src> build, and no npm-registry package — this is not a choice between alternatives, it is the SDK's only distribution mechanism.
  • Decision: Add mekari-account-web-sdk to package.json as a git dependency pinned to a version tag; import it (statically, or via dynamic import() inside the toggle guard to keep the bundle lazy).
  • Rationale: Bundled at build time — versioned by git ref, tree-shakeable, mockable in Jest (jest.mock('mekari-account-web-sdk')). No CSP script-src allow-listing is needed for the SDK itself (see §3.2).
  • Consequences: Adds mekari-account-web-sdk to package.json dependencies. Previously tracked as OQ-3 ("SDK not published to a registry"); resolved — it exists as a git dependency, not an npm-registry package, so no registry access is needed (R12).
  • Reversibility: High — remove the dependency + mixin import.

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:15 consumes route.query.code; middleware/login.js:22 requests response_type=code). Reusing it means the current_company fetch (a separate BE/SSO concern, §2.10 — not SDK-driven, R10) follows the users/me/current_company (auth-code) contract, not users/{id}/current_company.
  • Consequences: logged_out must revoke tokens then sign out (auth-code semantics), which $auth.logout already does. server_down does not trigger this — it is fail-open, no destructive action (ADR-6).
  • 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 handled SDK status 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') on logged_out rather than calling sign-out directly, matching the cross-tab relay pattern.
  • Reversibility: High.

ADR-6 — server_down behaviour

  • Context: The SDK emits server_down when no iframe message arrives within checkTimeout (constructor option, default 2000ms) and its own internal msli fallback (localStorage key "msli", 2h expiry, owned and read/written by the SDK itself) is also stale. By the time a consumer receives server_down, the SDK has already exhausted its own grace period. PRD constraint 6.10 mandates fail-open behaviour for this path.
  • Options: (a) immediate sign-out; (b) a consumer-side msli heuristic then sign-out; (c) fail-open — take no action.
  • Decision: (c) fail-open. Hub takes no destructive action on server_down (no sign-out, no company change).
  • Rationale: msli is owned internally by the SDK — a consumer read/write of the same key would collide with and corrupt the SDK's own fallback logic (R7). Since the SDK already applies its own 2h-grace msli check before ever surfacing server_down, a second consumer-side heuristic is redundant and unsafe. Fail-open also directly satisfies PRD constraint 6.10.
  • Consequences: Hub does not read or write msli, does not check qontak._token.hub validity in this branch, and does not sign the user out on server_down. Optionally log/alert for observability (§3.3).
  • Reversibility: High.

Minimum coverage checklist: Storage → n/a — FE-only, no DB (§2.8); msli is SDK-owned internally, Hub does not read/write it (R7); cookies are read-only where relevant. Sync vs async → SDK events are async, handled via a single session.on('event', ...) listener (ADR-5). Caching → periodic re-validation is via the SDK's own interval constructor option (ms, ≥1000); Hub configures/caches nothing itself — the source RFC's 5s/6k-RPS/50ms figures belong to A&L's /current service, not the SDK (ADR-1, ADR-6). Third-party → bundled git dependency, no CSP script-src needed (ADR-2, §3). Consistency → eventual (iframe reload on mount + interval). Multi-tenancy → currentUser (ssoId) comparison happens internally in the SDK; a different SSO user surfaces to Hub only as logged_out, never as a distinct event (R5). Reuse vs new → ADR-5 (reuse auth primitives); only one new endpoint key (OQ-1, decoupled from the SDK — R10).

2.4 APIs

Outbound (Hub → others)

MethodURL (key)StatusOwnerNote
iframe GETsm.mekari.com/currentnew (A&L)Account & Launchpadinvoked by SDK, not Hub code directly
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; a separate BE/SSO concern, not SDK-driven (R10)
GET (existing)SSO /auth/?response_type=code...&redirect_uri=.../sso-callbackreusedSSOre-auth entry point (middleware/login.js:22) — used for normal login, not triggered by any SDK event
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.mekari.com/current)session stateconsumed inside the SDK; the SDK validates only event.data.source === 'mekari-account-web-sdk', NOT event.origin (security finding — §3.2/OQ-9). Surfaced to Hub as the statuses below; Hub does not parse postMessage directly
SDK eventmekari-account-web-sdk Session{ status, sessionId? } (single-arg callback, no error param)see §2.6

SDK contract consumed by Hub

// assets/mixins/session/centralizedSession.js (new)
import { Session } from 'mekari-account-web-sdk'

const session = new Session({ currentUser: this.$auth.user.sso_id })
session.on('event', (data) => {
// data.status is EXACTLY one of: logged_in | logged_out | server_down
// (verified: mekari-account-web-sdk v0.3.0, session.ts — resolved OQ-4).
// There is no `switch_user` status and no second `error` argument. A
// different SSO user (account switch) surfaces indistinguishably as
// `logged_out` — the SDK never exposes the other user's ssoId (R5).
})
// Periodic re-validation, if desired, is configured via the constructor's
// `interval` option (ms, >= 1000; PRD constraint 6.9 recommends 5*60*1000).
// There is no activity-driven refresh method on the SDK's public API
// (constructor, destroy(), on(), off() only) — OQ-7 is moot (R6).

2.5 Local Storage & Cookies (Hub-side)

KeyTypeOwnerHub use
mslilocalStorage (timestamp, 2h expiry)Owned internally by the SDKHub must NOT read or write this key — set on logged_in / cleared on logged_out by the SDK itself; a consumer write would collide with and corrupt the SDK's own server_down fallback (R7)
_mekari_accountcookieSSO/Rails, consumed SM-service-sideNot observable by the SDK's client-side code or by Hub JS — it travels with the browser's cookie jar to sm.mekari.com and is validated server-side by Session Manager, not read/parsed by SDK or Hub code
qontak._token.hubcookieHub (hubAuthScheme.js:152)existing Hub session token; unaffected by SDK events (no server_down validity check needed — ADR-6 is fail-open)

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-account-web-sdk
participant IF as SM iframe (sm.mekari.com/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({currentUser: sso_id})
SDK->>IF: inject hidden iframe
IF->>SM: GET /current
SM->>R: validate + update last_request_at (<2h)
R-->>SM: session ok (ssoId)
SM-->>IF: render page w/ ssoId
IF-->>SDK: postMessage(session state)
SDK-->>Hub: event {status: logged_in}
Hub->>BE: GET current_company (OQ-1 — separate BE/SSO concern, R10)
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, fail-open)

sequenceDiagram
autonumber
participant Hub as Hub FE (mixin)
participant SDK as mekari-account-web-sdk
participant SM as Session Manager

SDK->>SM: GET /current (iframe reload)
Note over SDK,SM: no reply within checkTimeout (default 2000ms) triggers the SDK's OWN internal msli fallback
SDK-->>Hub: event {status: server_down}
Hub->>Hub: fail-open — no action taken (no sign-out, no company change — ADR-6 / PRD 6.10)

logged_out (includes SSO account switch)

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

Note: an SSO account switch is also delivered as logged_out — the SDK cannot distinguish it from a plain logout and never exposes the other user's ssoId — so the same handling applies (R5). There is no dedicated switch event or handler.

2.7 State Surface Contract & State Machine

stateDiagram-v2
[*] --> Unknown
Unknown --> LoggedIn: event logged_in
Unknown --> LoggedOut: event logged_out
LoggedIn --> LoggedOut: event logged_out (incl. SSO account switch — indistinguishable, R5)
LoggedIn --> LoggedIn: event server_down (fail-open — no action, ADR-6)
LoggedOut --> [*]: doSignOut → SSO /sign_out
StateVisibilityTransitionsNote
LoggedInnormal app→ LoggedOut; self-loop on server_down (no-op)fail-open per ADR-6; SDK owns msli internally
LoggedOutredirect to SSOterminaldoSignOut(); also reached when SSO reports a different user (account switch), since the SDK cannot distinguish the two (R5)

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 (any)Hub FEfail-open — no action taken (ADR-6); the SDK owns its own msli-based grace period internally
sign-out / mixin teardownHub FEcall session.destroy() (not .off() alone) — resets the singleton and tears down the iframe + window listener (R11)
multiple-sessions-per-accountA&L (server)n/a — no Hub FE branch

2.10 Responsibility Boundary (cross-squad)

StepOwner
SDK (bundled git dependency), iframe (sm.mekari.com/current), Redis, SSO Kong pathAccount & Launchpad
current_company SSO contract + Hub BE proxy (decoupled from the SDK — R10, not driven by any SDK event)Hub BE (OQ-1)
org payload centralized_session flagHub BE (OQ-2)
SDK wiring, event handling, toggle, 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 /current service, not the SDK itself. 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 targets sm.mekari.com only (SDK's default sessionUrl); add CSP frame-src/child-src allow-list entry for sm.mekari.com
postMessage (A03/A07)Open upstream security finding: the SDK's window message listener does NOT validate event.origin — it checks only event.data.source === 'mekari-account-web-sdk'. Hub cannot mitigate this; the SDK owns the listener internally. Tracked as an explicit open infosec item for Account & Launchpad (OWASP A08/A07) — see OQ-9. Hub must NOT add its own wildcard postMessage listener for session data (Hub already uses postMessage('*') at sso-callback.js:24 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 sm.mekari.com frame-ancestors/referrer-origin 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.<status>).
  • Mixpanel v2 (assets/mixins/metric/mixpanelMixin.js, AGENTS.md) — log forced sign-outs (logged_out) for funnel analysis.
  • Alert: spike in centralized_session.server_down (proxy for A&L outage; fail-open means no user-facing action is taken, so this is purely a health signal).

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 SM (sm.mekari.com) 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/centralized-session.js, assets/mixins/session/centralizedSession.js, assets/mixins/session/__test__/centralizedSession.spec.js.
  • Files (modify): package.json (add mekari-account-web-sdk as a git dependency, ADR-2), nuxt.config.js:84 (register plugin).
  • Commands: npm run test -- --testPathPattern="centralizedSession".
  • Acceptance: toggle ON + sso_id present ⇒ Session constructed with currentUser = 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_in ⇒ handled (no msli write — SDK-owned, R7).

Chunk 5 — server_down fallback (fail-open)

  • Files: mixin handler only — no consumer-side msli helper (SDK owns msli internally, R7).
  • Acceptance: server_down ⇒ mixin takes no action — assert EventBus.$emit/doSignOut are NOT called.

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) → 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 sm.mekari.com 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. Independent of the SDK (R10).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[resolved]@mekari/sdk not published/accessible RESOLVED: the real SDK is mekari-account-web-sdk v0.3.0, distributed as a git dependency (npm install git+https://...#<version>) — no CDN, no npm-registry access needed (ADR-2). Resolved by real-SDK verification, latest review.A&L
OQ-4[resolved]Source RFC contradicts itself (logout vs logged_out) RESOLVED: canonical Status enum is EXACTLY logged_in | logged_out | server_down; single-arg callback, no error param, no switch_user (verified: mekari-account-web-sdk v0.3.0 session.ts, latest review).
OQ-5[important]Hub production domain(s) must be added to SM (sm.mekari.com) CSP frame-ancestors / referrer-origin whitelist before enable.A&L + Hub
OQ-6[important]CSP frame-src/child-src for sm.mekari.com — confirm Hub's current CSP source (header vs meta) and where to edit.Hub FE / Infra
OQ-7[moot]throttle interval for the previously-assumed activity-refresh call MOOT: no such method exists on the SDK's public API (constructor, destroy(), on(), off() only). Periodic re-validation uses the constructor's interval option (ms, ≥1000; PRD 6.9 suggests 5*60*1000) — resolved (R6).
OQ-8[important]Infosec approver name + Slack discussion thread unfilled in metadata.Hub
OQ-9[important]New upstream infosec finding: the SDK's window message listener does not validate event.origin (checks only event.data.source). Hub cannot mitigate this client-side. Needs remediation/acceptance from Account & Launchpad (OWASP A08/A07).A&L / Infosec

Known limitation: until OQ-1/OQ-2 close, only Chunks 1, 2, 3, 5, 7 (session detect + sign-out + server_down fail-open, toggle-gated, no company sync) are agent-executable; Chunk 6 remains blocked.


6. Comment logs

DateAuthorNote
2026-06-27Syafrizal MuhammadInitial Hub-FE integration draft derived from A&L source RFC
2026-07-02latest review reconciliationReconciled 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 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).

Non-blocking but needed for full completion: OQ-5/OQ-6 (CSP/whitelist), OQ-9 (upstream event.origin infosec finding, tracked with A&L).

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