Skip to main content

RFC: Centralized Web Session — Launchpad SDK Integration

Document Conventions (do not remove)

Follows the Qontak RFC Template governance (metadata table + sections 1–6 + Comment logs) and is agent-execution-ready (§1 Design/PRD derivation, §2 Repo Reading Guide, mermaid diagrams, §4 Execution Plan + Verification/Rollback). YAML frontmatter and the metadata table must agree. ISO-8601 dates.

Scope note. This is the frontend RFC for the Launchpad pilot only (Rollout step 4 "Taking Off"). The Session Manager service (sm.mekari.com/current), its Redis, and the gateway routing to it are upstream backend dependencies owned by SSO/Account — they are tracked here as external contracts, not built by this RFC. The mekari-account-web-sdk package itself is a bundled git dependency of Launchpad (no CDN, no <script> tag) — it ships inside this RFC's own build, not as a separate backend deliverable. CRM / Hub / Hub Chat v2 integration is Rollout step 5 and is out of scope for execution (mapped in Detail 1.C as deferred).

Metadata

FieldValueNotes
StatusRFCIDEA | RFC | ABANDON | AGREED
Type / Sub-typefrontend / new-featureLaunchpad SPA integration
TitleCentralized Web Session — Launchpad SDK Integration
OwnerAccount & Launchpad (AL)Pilot product squad
AuthorsSyafrizal Muhammad
ReviewersAL squad reviewer; SSO/Session Manager BE reviewer[REQUIRED] names
Approver(s)AL tech lead; infosec approverinfosec mandatory (SDK/iframe/CSP)
Submitted2026-06-27
Last updated2026-06-27
Target release[REQUIRED]
Related DocumentsCentralized Web Session PRDConfluence PT space
Discussion[REQUIRED]

Sections at a Glance

§SectionFrontend hints
1OverviewProblem, success criteria, PRD-to-behavior derivation, decisions index, per-story change map
2Technical DesignRepo Reading Guide, topology, SDK contract, event routing, state surface, sequence diagrams, state machine, branch catalog
3High-Availability & SecurityCSP/frame-ancestors, referrer/origin, token handling, OWASP
4Backwards Compatibility & RolloutFeature flag, execution plan, verification & rollback recipe
5Concerns / Open QuestionsSeverity-tagged blockers
6Comment logsReview history
7Ready for agent executionGate marker

1. Overview

1.1 Problem

Mekari products keep independent web sessions and never observe SSO logout. A user who logs out on SSO (or switches account on SSO) stays "logged in" on Launchpad with stale company/account context. Concretely (PRD §1): after an SSO logout the user remains active on the product, and after an SSO account switch the product still shows the previous company. Launchpad today holds the SSO tokens in cookies (app/common/composables/useAuthCookies.ts:5-30) and never re-validates them against SSO between navigations — authenticated.global.ts only refreshes an expired access token, it never asks "is this SSO session still the one I think it is?".

This RFC integrates the mekari-account-web-sdk Session SDK (bundled git dependency; see §2.4, R1/R2 of the latest review reconciliation) into Launchpad so the SPA detects, on every page, whether the SSO session is logged_in, logged_out, or server_down. The SDK has no switch_user status — an SSO account switch is delivered as logged_out (§2.4) — and Launchpad reacts consistently with its existing OAuth2 authorization-code auth.

1.2 Success Criteria (from PRD)

#CriterionFE-observable assertion
SC1SDK reports session status across productsLaunchpad reacts to all 3 SDK statuses — logged_in, logged_out, server_down (test-asserted in event-handler spec)
SC2Session expires after 2h idleOn logged_out event Launchpad runs sign-out flow (no manual reload needed)
SC3Session re-validated periodicallyAchieved via the Session constructor's interval option (ms, ≥1000; recommended 5*60*1000 per PRD constraint 6.9) — the SDK reloads its hidden iframe on that fixed timer. There is no session.refresh() API (OQ-6 moot).
SC41 account → multiple sessionsNo FE change required; Launchpad treats each browser independently (Detail 1.C n/a — BE-owned)

1.3 Out of Scope

  • Auto-revoking access/refresh tokens on idle logout (PRD out-of-scope).
  • Building the Session Manager service or its Redis (backend; tracked as a dependency). The mekari-account-web-sdk package is likewise not built by this RFC — it is an existing git dependency this RFC bundles (§2.4), not a BE build.
  • CRM / Hub / Hub Chat v2 wiring (Rollout step 5 — deferred; mapped in 1.C).
  • Multiple-sessions-per-account FE work (no FE change; BE/SSO concern).

1.4 Detail 1.A — PRD Section Coverage

PRD sectionCovered inStatus
1. Overview (issues)§1.1covered
Success Criteria§1.2covered
Out of Scope§1.3covered
Dependencies (SDK, Session Manager, Redis)§2.2 topology, §5 OQ-2/OQ-4covered (SDK dependency resolved; Session Manager + company-sync remain external)
2. Technical Design — Current/Proposal§2.4, §2.5covered
How to use the SDK§2.4covered
Local Storage (msli)§2.6covered (SDK-owned only — Launchpad does not touch it)
Cookies (_mekari_account)§2.6covered
SDK contract (events)§2.4covered
FE Product Integration — SDK Flow§2.7 seqcovered
FE — Web Session Flow§2.7n/a — Launchpad is OAuth2 (see ADR-3)
FE — OAuth2 Authorization Code Flow§2.7 seqcovered (Launchpad's model)
User Logout From Product§2.7, §2.9covered
User Switch Account§2.4 (delivered as logged_out)covered — the SDK cannot distinguish an account switch from a logout and never exposes the incoming ssoId, so Launchpad runs the normal sign-out flow (R5)
Database Model (no change)§2.8covered
3. HA & Security§3covered (FE-side: CSP, referrer)
4. Rollout Plan§4covered (step 4 only; 5 deferred)
5. Open Questions§5covered
FE Implementation Scope§4.C execution plancovered

UI / Consumer Surface Coverage

SurfaceTriggerBacking readCoverage
Every authenticated Launchpad pageSDK loaded app-wide via pluginSDK iframe → Session Manager§2.7 SDK flow
Session-expired toast + redirectlogged_out / server_downexisting authenticated.global.ts:97-110 pattern§2.9

Role Coverage

RoleBehavior deltaCoverage
All authenticated Launchpad rolesIdentical session lifecycle; no role-specific branch§2 — n/a — session is role-agnostic

Launchpad permission roles (permission.global.ts) are orthogonal to session validity; this RFC adds no role × session matrix beyond the existing permission middleware.

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

#DecisionChosenADR
1Feature-flag mechanismNew centralized_session key in configs/{development,production}.jsonruntimeConfig.publicADR-1
2Where SDK loadsNew Nuxt plugin ~/plugins/centralized-session.ts, registered after auth.tsADR-2
3Which PRD flow appliesOAuth2 authorization-code flow (Launchpad confirmed response_type=code)ADR-3
4currentUser sourcelaunchpad.sso_id cookie / authProfile.sso_idADR-4
5current-company syncReuse /users/me (authStore.fetchAuthLaunchpad) unless OQ-4 forces new endpoint. The SDK exposes no company data at all — this stays a separate BE/SSO dependency, not SDK-driven (R10).ADR-5
6Event→action routingCentral composable useCentralizedSession, single session.on("event", (data) => { switch(data.status) {...} }) subscription (the SDK's only valid event name is the literal "event" — per-status subscriptions are a silent no-op), reuses authStore.resetAuth + existing logout redirectADR-6

1.6 Detail 1.C — Per-Story Change Map

StoryLayer scopeChangesAcceptance criteriaRFC anchorsNotes
Detect SSO logout on LaunchpadFE-onlyuseCentralizedSession composable; plugin loadlogged_out event → sign-out redirect; spec passes§2.4, §4.C ch.3Also covers an SSO account switch — the SDK reports it as logged_out (R5); there is no separate "account switch" story.
Periodic session re-validationFE-onlypass interval (recommended 5*60*1000ms) to the Session constructorSDK re-checks on a fixed timer via hidden iframe reload; no manual/throttled refresh call exists§2.4, §4.C ch.5Replaces the earlier session.refresh() design — the SDK has no refresh() API (R6; OQ-6 moot).
server_down fail-openFE-onlyon server_down, take no destructive action (PRD constraint 6.10); the SDK owns msli internally and Launchpad must not read/write itno forced sign-out; log + monitor only§2.6, §4.C ch.5Removed the consumer-side msli predicate (R7) — by the time a consumer observes server_down, the SDK's own msli fallback has already lapsed.
Feature gateConfigcentralized_session flag in configs/{development,production}.jsonflag off → zero behavior change (spec)§4.A, §4.C ch.1
Multiple sessions / accountn/a — BE-ownednonen/aSC4
CRM/Hub/HubChat integrationCross-squadper-repo SDK wiringn/adeferred — Rollout step 5

2. Technical Design

2.0 Repo Reading Guide (read before writing code)

Repo Map (slice this RFC touches)

flowchart LR
subgraph LP["FE: qontak-launchpad-fe (MAIN — write here)"]
cfg["configs/{development,production}.json (modified)"]
nuxt["nuxt.config.ts (modified)"]
plug["plugins/centralized-session.ts (new)"]
comp["composables/useCentralizedSession.ts (new)"]
cookies["composables/useAuthCookies.ts (read)"]
client["composables/useClient.ts (read)"]
store["store/authStore.ts (modified: resetAuth reuse)"]
mw["middleware/authenticated.global.ts (read/modified)"]
sso["features/sso-callback/composable/ssoCallback.ts (read)"]
sdk["mekari-account-web-sdk Session (bundled git dependency, no CDN)"]
plug --> comp
plug --> sdk
comp --> store
comp --> cookies
comp --> mw
end
subgraph EXT["External (BE dep — READ-ONLY contract)"]
sm["Session Manager + iframe (sm.mekari.com/current)"]
end
sdk -.iframe+postMessage.-> sm

Existing Code Anchors

#File:LineWhat to learn
1app/common/composables/useClient.ts:9-15,98-143HTTP wrapper + 401 singleton refresh; do not duplicate refresh logic
2app/common/composables/useAuthCookies.ts:5-30Token cookie keys (global_sso_token, global_sso_refresh_token) + launchpad.sso_id (line 30)
3app/common/store/authStore.ts:30,135-149useAuthStore, setProfileAuth sets LAUNCHPAD_SSO_ID, resetAuth clears state
4app/middleware/authenticated.global.ts:43-120Existing refresh + session-expired toast + logout redirect to CHATPANEL_URL/logout
5app/features/sso-callback/composable/ssoCallback.ts:3-15OAuth2 code-flow redirect URL shape (response_type=code&scope=sso:profile)
6app/plugins/auth.ts + nuxt.config.ts:89-95Plugin registration + load order (auth runs last)
7app/plugins/pixel.ts, app/plugins/mixpanel.tsPattern for initializing a 3rd-party SDK in a Nuxt plugin
8nuxt.config.ts:131-149runtimeConfig.public + env spread (line 148) — where flags surface
9configs/development.json (+ production.json)Env config files; add centralized_session here. There is no configs/local.json in this repo — corrected from an earlier draft (ADR-1).
10app/common/composables/useErrorHandler.spec.ts, useAuthCookies.spec.tsVitest + vi.mock pattern for composable specs

Patterns to Follow

ConcernReference filePattern
State managementapp/common/store/authStore.ts:30Pinia defineStore setup-style, ref() state, returned actions
3rd-party SDK initapp/plugins/pixel.ts:1-10, app/plugins/mixpanel.tsdefineNuxtPlugin, init inside plugin, register in nuxt.config.ts:89-95
HTTP / refreshapp/common/composables/useClient.ts:98-112singleton refreshPromise to dedupe refresh
Error normalizationapp/common/composables/useErrorHandler.tsroute API errors through handleError
User-facing noticeapp/middleware/authenticated.global.ts:97-101toast.notify from @mekari/pixel3
Logout redirectapp/middleware/authenticated.global.ts:109,116-119window.location.replace(CHATPANEL_URL/logout) / navigateTo(... {external:true})
Cookie accessapp/common/composables/useAuthCookies.ts:9-30useCookie with prod COOKIE_DOMAIN else localhost
Testapp/common/composables/useErrorHandler.spec.tsVitest, vi.mock, co-located .spec.ts

No in-repo pattern exists for iframe injection / postMessage listening — the mekari-account-web-sdk encapsulates the iframe and its own window message listener; Launchpad code never injects an iframe or listens for message directly. Launchpad only consumes SDK events via session.on("event", ...). (No fabricated pattern.)

Reading Order for the Agent

  1. app/common/composables/useAuthCookies.ts
  2. app/common/store/authStore.ts
  3. app/common/composables/useClient.ts
  4. app/middleware/authenticated.global.ts
  5. app/features/sso-callback/composable/ssoCallback.ts
  6. app/plugins/auth.ts then app/plugins/pixel.ts
  7. nuxt.config.ts (plugins + runtimeConfig)
  8. configs/development.json
  9. app/common/composables/useErrorHandler.spec.ts

Source Verification

ClaimEvidence
HTTP client + 401 refresh singletonuseClient.ts:43 $fetch, :51 if (errorStatus === 401), :98 performTokenRefresh, :100 if (refreshPromise)
Token cookie keysuseAuthCookies.ts:5 "global_sso_token", :6 "global_sso_refresh_token", :30 useCookie("launchpad.sso_id")
Auth store + sso_id writeauthStore.ts:30 defineStore("auth-profile"...), :139 LAUNCHPAD_SSO_ID.value = payload?.sso_id ?? "", :142 resetAuth
Launchpad is OAuth2 code flowssoCallback.ts:6 ...response_type=code&scope=sso:profile&redirect_uri=... ; mirrored in authenticated.global.ts:80
Plugin registration/ordernuxt.config.ts:89-95 plugins array, auth.ts last
3rd-party SDK init patternplugins/pixel.ts:1-10 defineNuxtPlugin, nuxtApp.vueApp.use(PixelPlugin, ...)
Flags surface via env spreadnuxt.config.ts:148 ...CONFIGENVIRONMENT.env into runtimeConfig.public
Config env files existconfigs/development.json, production.json (listed; no local.json in this repo — corrected)
Logout redirect targetauthenticated.global.ts:109 window.location.replace(\${config.public.CHATPANEL_URL}/logout`)`
Test command + frameworkpackage.json:16 "test": "vitest --dom --pool=forks", :19 vue-tsc --noEmit, specs in app/common/composables/*.spec.ts
mekari-account-web-sdk not yet addedpackage.json:63-77 deps — not present yet, but available as a real git dependency mekari-account-web-sdk v0.3.0 (resolved — OQ-1; add via npm install git+https://<user>:<pass>@bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk#<version>)
Session Manager / Redis NOT in FE reposabsent from all 4 repos (→ OQ-2)
current_company not called in Launchpadno match in repo; company from authStore.ts:88 data.value.dataauthProfile.company_id (→ OQ-4)

Cross-Service Responsibility Map

StepActionOwner
Maintain mekari-account-web-sdk (git dependency, no CDN)SDK package sourceSSO/Account (BE)
Session Manager sm.mekari.com/current + Redis + timeoutableiframe page, session storeSSO/Account (BE)
Gateway routing to sm.mekari.com + MAGgatewayPlatform/SSO
CSP frame-src/child-src whitelist update mechanismconfig repoSSO/Account (BE) + infosec
Consume SDK events, gate behind flag, wire sign-outLaunchpad FE (this RFC)AL squad
CRM / Hub / Hub Chat v2 wiringper-reporespective squads (deferred)

Existing Contracts check (endpoints)

ContractTagEvidence / justification
GET {apiBaseUrl}/users/me (profile + company)reusedauthStore.ts:81 — primary company source
POST {apiBaseUrl}/seamless/refresh_sso_tokenreuseduseClient.ts:120, authenticated.global.ts:47
SSO /auth/?...response_type=codereusedssoCallback.ts:6
{SSO}/sign_out?client_id=... / CHATPANEL_URL/logoutreusedexisting logout redirect
sm.mekari.com/current (iframe, via SDK)new-with-justificationprovided by SDK iframe; Launchpad never calls directly — only consumes events (BE dep, OQ-2)
SSO /v1.1/users/me/current_companynew-with-justificationPRD-specified for OAuth2 flow; reuse /users/me preferred unless OQ-4 requires it

2.1 Current

Launchpad (ssr: false SPA, nuxt.config.ts:77) holds SSO tokens in cookies and only re-validates by refreshing an expired access token in authenticated.global.ts. No mechanism observes SSO logout or account switch.

2.2 Infrastructure / Deployment Topology

flowchart TB
user["User Browser"]
subgraph LPnet["launchpad.qontak.com / launchpad.mekari.io (SPA, static)"]
spa["Launchpad SPA (bundles mekari-account-web-sdk Session, no CDN)"]
end
subgraph smnet["sm.mekari.com (via gateway/MAG)"]
gw["Gateway /current"]
smcur["Session Manager /current (iframe page, Golang)"]
redis[("Dedicated Session Redis<br/>cache.t3.small, RDB")]
end
api["api.mekari.io Kong → Launchpad BE /users/me, /seamless/refresh_sso_token"]
user --> spa
spa -.iframe.-> gw --> smcur --> redis
smcur -.postMessage.-> spa
spa -->|profile/company, token refresh| api

Service use cases & third-party connections

ServiceUse cases (FE-relevant)FE calls3rd-party
Launchpad SPArender app; consume SDK events; gate by flag/users/me, /seamless/refresh_sso_token, SSO /auth, /sign_outmekari-account-web-sdk (bundled; owns the iframe internally)
Session Manager (BE dep)validate session in Redis, update last_request_at, render ssoIdn/a (iframe, SDK-mediated)Redis

2.3 Database Model

No database changes (PRD §2 "Database Model: No database changes"). FE does not add or manage a localStorage key — msli is owned entirely by the SDK (§2.6). n/a — no schema.

2.4 SDK Contract (consumed by Launchpad)

Ground truth verified against mekari-account-web-sdk v0.3.0 (src/session.ts, src/index.ts, README.md):

import { Session } from "mekari-account-web-sdk"

const session = new Session({
currentUser: "<user sso ID>", // ADR-4: launchpad.sso_id — camelCase, NOT current_user
interval: 5 * 60 * 1000, // ms, >=1000; periodic re-check via hidden iframe reload (PRD 6.9). No session.refresh() API exists.
})

session.on("event", (data) => {
// "event" is the ONLY valid event name; per-status names are a silent no-op.
// Callback is single-arg — there is no second "error" parameter.
switch (data.status) {
// route by data.status — see table below
}
})

// Teardown: session.destroy() (the SDK is a singleton — a 2nd `new Session()`
// call returns the 1st instance and ignores new options; re-init requires
// destroy() first — R11).
Event (data.status)MeaningLaunchpad action (OAuth2 flow, ADR-3/ADR-6)
logged_inSDK's internal check found data.ssoId === currentUsercontinue; ensure company synced via /users/me (ADR-5)
logged_outany other case — a plain logout or an SSO account switch. The SDK cannot distinguish the two and never exposes the incoming/other user's ssoId to the consumer.sign-out flow (reuse authenticated.global.ts:109 redirect). Any post-re-login company re-sync is a separate BE/SSO concern (ADR-5/OQ-4), not SDK-driven (R5/R10).
server_downthe SDK's internal checkTimeout window (default 2000ms) elapsed with no iframe response, and its internal msli fallback (SDK-owned, 2h expiry) also lapsedfail-open (PRD constraint 6.10) — take no destructive action, do not force sign-out; log + monitor only (§3)

There is no switch_user status in the real SDK. An earlier draft of this RFC modeled a dedicated switch_user event, handler, sequence diagram, and state — all removed per the latest review SDK reconciliation (R5): the SDK's Status type is exactly logged_in \| logged_out \| server_down.

2.5 State Surface Contract

StateSourceSurfaced toWhere
currentUserlaunchpad.sso_id cookie / authProfile.sso_idSDK constructoruseCentralizedSession
session statusSDK event data.statusroute guard / handleruseCentralizedSession
msli (SDK-internal, do not touch)localStorage timestampSDK's own fallback logic onlyowned by the SDK, not by Launchpad code
company context/users/meauthProfile.company_idUIauthStore (reused)

2.6 Local Storage & Cookies

KeyTypeOwnerNotes
msli (Mekari Session Logged In)localStorage timestamp, 2h expirySDK internally — set on logged_in, cleared on logged_outLaunchpad must not read or write this key. A consumer write would collide with the SDK's own checkTimeout fallback (R7). By the time a consumer would observe server_down, the SDK's msli is already stale — there is no consumer-side fallback predicate to build.
_mekari_accountcookie on sm.mekari.comSSO (Rails)not readable by Launchpad JS (cross-domain); the SDK's own iframe consumes it internally — Launchpad has no fallback logic keyed on it
global_sso_token / _refresh_tokencookie .qontak.*Launchpad existinguseAuthCookies.ts:5-7
launchpad.sso_idcookieLaunchpad existinguseAuthCookies.ts:30currentUser source

2.7 Sequence Diagrams

Happy path — logged_in (OAuth2 flow)

sequenceDiagram
participant B as Browser (Launchpad SPA)
participant SDK as mekari-account-web-sdk Session
participant IF as SM iframe (sm.mekari.com)
participant SM as Session Manager
participant R as Session Redis
participant API as Launchpad BE (/users/me)
B->>SDK: new Session({currentUser: launchpad.sso_id, interval: 5*60*1000})
SDK->>IF: open hidden iframe sm.mekari.com/current
IF->>SM: GET /current
SM->>R: validate + update last_request_at (<2h)
R-->>SM: ok
SM-->>IF: render page w/ ssoId (cache max 5s)
IF-->>SDK: postMessage({source:'mekari-account-web-sdk', ssoId})
SDK->>SDK: ssoId === currentUser -> set internal msli=now
SDK-->>B: session.on("event") -> {status:'logged_in'}
B->>API: GET /users/me (sync company) [reused]
API-->>B: profile {company_id}
Note over B: continue, no redirect

Failure path — server_down (SDK-internal fallback exhausted)

sequenceDiagram
participant B as Browser (Launchpad SPA)
participant SDK as mekari-account-web-sdk Session
participant IF as SM iframe (sm.mekari.com)
B->>SDK: new Session({currentUser, interval})
SDK->>IF: reload hidden iframe sm.mekari.com/current
IF--xSDK: no postMessage within checkTimeout (default 2000ms)
SDK->>SDK: internal msli check (SDK-owned, NOT consumer-owned)
alt internal msli < 2h
SDK-->>B: session.on("event") -> {status:'logged_in'} (SDK-internal degrade)
else
SDK-->>B: session.on("event") -> {status:'server_down'}
B->>B: fail-open (PRD 6.10) - no destructive action, no forced sign-out — log + monitor
end

An earlier draft also modeled a switch_user sequence (destroy session + re-auth + "user changed" toast). Removed per R5 — the real SDK has no switch_user status. Behavioral intent is preserved via the logged_out path above: an SSO account switch runs the normal sign-out flow; any post-re-login company re-sync is a separate BE/SSO concern (ADR-5/OQ-4), not SDK-driven.

2.8 Session State Machine

stateDiagram-v2
[*] --> Initializing: page load, flag on
Initializing --> LoggedIn: event logged_in
Initializing --> LoggedOut: event logged_out (incl. SSO account switch - R5)
Initializing --> ServerDown: event server_down
LoggedIn --> LoggedOut: event logged_out (2h idle / SSO logout / SSO account switch)
ServerDown --> LoggedIn: SDK-internal msli<2h (SDK-owned, not consumer)
ServerDown --> ServerDown: fail-open - no destructive action (R7 / PRD 6.10)
LoggedOut --> [*]: redirect to sign-out

2.9 Branch & Skip Catalog

BranchConditionActionOwner
Flag offcentralized_session falseSDK not loaded; behavior unchangedFE
Excluded pagessso-callback, login, expired (authenticated.global.ts:11-20)skip session checkFE
logged_out (incl. SSO account switch)SDK cannot distinguish — see §2.4sign-out flowFE
server_downSDK exhausted its internal checkTimeout + internal msli fallbackfail-open (PRD 6.10) — no destructive action, no forced sign-out; log + monitorFE

3. High-Availability & Security

The HA/perf targets for sm.mekari.com/current (6k RPS, p95 < 100ms, Redis < 2ms) are backend-owned (PRD §3) — Launchpad does not host that endpoint. FE security:

AreaControlOWASP
SDK/iframe originThe SDK is bundled (no script-src entry needed — R2). Page CSP frame-src/child-src whitelists sm.mekari.com for the hidden iframe; SM-side frame-ancestors whitelists launchpad.* (whitelist update = BE config repo; canonical host resolved — R8)A05 Misconfig
Token handlingTokens stay in existing cookies; no new token storage; never log tokens (AGENTS.md)A02 Crypto / A09 Logging
postMessageOpen security finding (upstream). The SDK's own window message listener checks only event.data.source === "mekari-account-web-sdk" — it does not validate event.origin. Launchpad cannot mitigate this: the SDK owns the listener and Launchpad only consumes session.on("event", ...). Recorded as an infosec finding to the SDK owners (Account & Launchpad) — tracked at OQ-5, not a "confirm" item (R9).A08 Integrity / A07 Ident. & Auth. Failures
XSS via iframe contentiframe renders empty page w/ ssoId only; no untrusted HTML injected into LaunchpadA03 Injection
Referrer/origin (alt)optional referrer/origin validation at SM (BE)A05

Monitoring (FE-side): count SDK event types + server_down rate via existing analytics plugin (plugins/mixpanel.ts); alert on server_down spike. Use console.error/console.warn for unexpected SDK states per AGENTS.md (no console.log in committed code).


4. Backwards Compatibility & Rollout Plan

4.A Feature flag contract (ADR-1)

Add "centralized_session": false to configs/development.json and configs/production.json env blocks (this repo has no configs/local.json — corrected from an earlier draft); it surfaces at runtimeConfig.public.centralized_session via the existing spread (nuxt.config.ts:148). Default off → zero behavior change. Flip per-env to pilot. (No env-var-only or remote-config system exists — ADR-1.)

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

StepCommandSource
Lintpnpm run lintpackage.json:14
Type checkpnpm run type-checkpackage.json:19
Unit testspnpm run testpackage.json:16
Buildpnpm run buildpackage.json:7

4.C Agent Execution Plan (ordered chunks)

Blocked prerequisite: chunks 2–5 import mekari-account-web-sdk — a resolved, installable git dependency (OQ-1 resolved; R1/R2/R12) — but full end-to-end verification still requires the live Session Manager (sm.mekari.com/current, OQ-2). The agent can now implement chunks 1–5 for real (add the real dependency, wire the real Session constructor/events) and unit-test them against a mocked Session where the live SM isn't reachable, but cannot run an end-to-end integration test until OQ-2 resolves.

Chunk 1 — Feature flag (Config)

  • Files: configs/development.json, configs/production.json, nuxt.config.ts (expose typed key if needed)
  • Commands: pnpm run type-check && pnpm run test
  • Acceptance: runtimeConfig.public.centralized_session === false by default; flag-off path renders app unchanged (spec).

Chunk 2 — SDK loader plugin (ADR-2)

  • Files: package.json (add mekari-account-web-sdk git dependency); new app/plugins/centralized-session.ts; modify nuxt.config.ts:89-95 (register after auth.ts)
  • Commands: pnpm run lint && pnpm run build
  • Acceptance: when flag on, mekari-account-web-sdk Session instantiated once with currentUser from launchpad.sso_id and interval set; when off, not instantiated (spec asserts no Session construction side-effect).

Chunk 3 — Event router composable (ADR-6)

  • Files (new): app/common/composables/useCentralizedSession.ts + .spec.ts
  • Commands: pnpm run test -- app/common/composables/useCentralizedSession.spec.ts
  • Acceptance (TDD, red first): spec mocks the SDK's single "event" subscription and asserts — logged_out → sign-out redirect (covers both plain logout and an SSO account switch, since the SDK cannot distinguish them — R5); logged_in → no redirect; server_down → fail-open (no redirect, no destructive action — R7). All 3 branches covered.

Chunk 4 — sign-out cleanup wiring

  • Files: app/common/composables/useCentralizedSession.ts, reuse authStore.resetAuth (authStore.ts:142), reuse logout redirect (authenticated.global.ts:109)
  • Commands: pnpm run test && pnpm run type-check
  • Acceptance: spec asserts token cookies cleared before the sign-out redirect. (No msli removal here — the SDK owns and clears msli internally on logged_out; Launchpad must not touch it — R7.)

Chunk 5 — periodic re-validation + server_down fail-open

  • Files: app/plugins/centralized-session.ts (pass interval: 5*60*1000 to the Session constructor — no separate refresh call exists), useCentralizedSession.ts
  • Commands: pnpm run test
  • Acceptance: Session constructed once with interval set (fake timers assert the SDK's own periodic iframe re-check fires — there is no Launchpad-driven refresh() call); on server_down, spec asserts no sign-out and no destructive action (fail-open, PRD 6.10). (OQ-6 is moot — R6.)

Chunk 6 — pilot enable

  • Files: configs/production.json (flip flag for pilot cohort)
  • Commands: pnpm run build
  • Acceptance: post-deploy SDK event metrics emit; server_down rate within alert threshold.

4.D Verification & Rollback Recipe

Post-deploy signals: SDK event counts (mixpanel), server_down rate, no spike in session-expired toasts; manual: SSO logout in another tab → Launchpad tab redirects to sign-out within one navigation.

Rollback (ordered):

  1. Set centralized_session: false in configs/production.json and redeploy (SDK stops being constructed and its iframe stops opening; app reverts to current behavior).
  2. Confirm no iframe request to sm.mekari.com/current in prod (the SDK bundle itself always ships in the JS bundle regardless of flag — R2 — but Session is only constructed, and the iframe only opened, when the flag is on).
  3. Confirm session-expired toast/redirect rate returns to baseline.
  4. Revert the chunk PRs if code-level rollback needed (FE-only; safe — no DB/back-compat coupling).

4.E Rollout stages (PRD §4, FE scope = step 4)

StageAudienceGo/No-go
4 Taking OffLaunchpad pilot cohort (flag on)SDK events healthy, server_down < threshold
5 Next ChapterCRM/Hub/HubChat (deferred)per-repo RFC; add domain to CSP whitelist

5. Concerns / Open Questions

  • OQ-1 [RESOLVED] mekari-account-web-sdk (formerly cited as @mekari/sdk) is not in package.json (:63-77) and needs a published CDN URL — resolved by the real SDK mekari-account-web-sdk v0.3.0 (session.ts): install via npm install git+https://<user>:<pass>@bitbucket.org/mid-kelola-indonesia/mekari-account-web-sdk#<version>. It is a bundled git dependency — there is no CDN, no /sm/sdk.js, no <script> tag. Unblocks chunks 2–5 for real implementation (§4.C).
  • OQ-2 [critical — still open] Session Manager service + Redis + gateway routing to sm.mekari.com not present in any FE repo; Launchpad cannot self-verify the iframe postMessage contract end-to-end. Need: a staging sm.mekari.com/current endpoint to integration-test against. This is the sole remaining hard blocker for chunks 2–6 end-to-end (§7).
  • OQ-3 [RESOLVED] PRD names two iframe paths — /sm/current vs /sessionmanager/current — resolved: the SDK's default sessionUrl is https://sm.mekari.com/current (session.ts). This is the canonical path — not account.mekari.com/sm/current, not /sessionmanager/current.
  • OQ-4 [important — still open] Launchpad currently derives company from /users/me (authStore.ts:88), not /v1.1/users/me/current_company. Confirm whether reuse suffices (ADR-5) or the PRD's current_company endpoint is required for OAuth2 flow. This is a genuine BE/SSO dependency, independent of the SDK — the SDK exposes no company data at all (R10).
  • OQ-5 [SECURITY FINDING — open, upstream] The SDK does not validate event.origin; it checks only event.data.source === "mekari-account-web-sdk" (session.ts). Launchpad cannot mitigate this — the SDK owns the window message listener. Recorded as an open infosec finding to the SDK owners (Account & Launchpad), OWASP A08/A07 (§3). This is a resolved-negative finding to escalate, not a "confirm" question.
  • OQ-6 [MOOT] session.refresh() throttle interval "TBD" — moot: there is no refresh() API. Periodic re-validation is achieved via the constructor's interval option (recommended 5*60*1000ms per PRD constraint 6.9).
  • OQ-7 [MOOT] stale currentUser/switch_user pre-detection — moot: there is no switch_user status. A different SSO user surfaces as logged_out regardless of what currentUser was passed at construction, and the SDK never exposes the other user's ssoId to the consumer (R5).

6. Comment logs

DateAuthorComment
2026-06-27Syafrizal MuhammadInitial draft (FE Launchpad pilot scope).
2026-07-02latest review (SDK reconciliation)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:

  • G-D1/Dependency: OQ-1 (SDK package) and OQ-3 (canonical iframe path) are now resolvedmekari-account-web-sdk v0.3.0 is a real, installable git dependency and the canonical path is sm.mekari.com/current. The remaining hard blocker is OQ-2 — a live/staging Session Manager (sm.mekari.com/current) to integration-test the postMessage contract end-to-end.
  • Chunks 1–5 can now be implemented for real (real dependency, real Session constructor/events) and unit-tested against a mocked Session; only true end-to-end verification (chunk 6 pilot enable, and any assertion depending on a live SM response) is gated on OQ-2.
  • A separate, non-blocking dependency remains open: OQ-4 (current_company / company-sync endpoint host) — it does not block chunks 1–5 coding, only the final endpoint choice for the logged_in company-resync assertion.

Resolve OQ-2 (stand up a staging Session Manager) → re-run §7. Optionally hand to rfc-reviewer for a second-pass score.