Skip to content

feat(pos): add secure dashboard bridge - #608

Merged
ignaciosantise merged 8 commits into
mainfrom
fix/secure-pos-credentials
Sep 2, 2026
Merged

feat(pos): add secure dashboard bridge#608
ignaciosantise merged 8 commits into
mainfrom
fix/secure-pos-credentials

Conversation

@ignaciosantise

@ignaciosantise ignaciosantise commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a locked-origin, in-memory dashboard bridge for POS web API operations
  • remove URL and legacy postMessage credential intake while retaining native and standalone Settings flows
  • make bridge-managed merchant identity read-only in Settings and hide the local API-key UI

Interaction

sequenceDiagram
    participant POS as POS iframe
    participant Dashboard as Dashboard browser
    participant Action as Dashboard server action
    participant Proxy as POS /api proxy

    POS->>Dashboard: pos-ready (protocol v1)
    Dashboard->>POS: pos-bridge-config (merchant ID only)
    POS->>Dashboard: pos-api-request (request ID + fixed operation)
    Dashboard->>Action: authenticated, validated request
    Action->>Proxy: server-side request with partner API key
    Proxy-->>Action: payment result or structured error
    Action-->>Dashboard: bridge result
    Dashboard->>POS: pos-api-response (matching request ID)
Loading

The partner API key never enters the POS iframe, browser storage, browser network requests, or postMessage payloads.

Validation

  • npm run lint
  • npm test -- --runInBand __tests__/hooks/use-pos-bridge.test.ts __tests__/services/pos-bridge.test.ts __tests__/services/web-bridge-services.test.ts __tests__/utils/pos-bridge-ui.test.ts

Full test suite has one pre-existing payment-error copy assertion failure; full format check has two unrelated existing formatting findings.

@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pos-demo Ready Ready Preview Sep 2, 2026 8:00pm UTC
2 Skipped Deployments
Project Deployment Actions Updated
react-native-appkit Ignored Ignored Preview Sep 2, 2026 8:00pm UTC
react-native-walletkit Ignored Ignored Preview Sep 2, 2026 8:00pm UTC

Request Review

@claude

claude Bot commented Aug 28, 2026

Copy link
Copy Markdown

Claude finished @ignaciosantise's task in 3m 50s —— View job


PR Review: feat(pos): add secure dashboard bridge

  • Gather context and read changed files
  • Analyze security, correctness, and code quality
  • Run breaking changes subagent
  • Run data classification subagent
  • Post findings

🚨 PR Too Large
Files: 16 Lines: 1541 Severity: HIGH Category: maintainability
This PR combines bridge transport layer, hook migration, service wiring, and UI integration. Suggest splitting into 3 focused PRs:

  • PR 1: Bridge service layer — pos-bridge.ts, usePosBridgeStore.ts, pos-bridge-ui.ts + their tests
  • PR 2: Hook migration — use-url-credentials.ts + test updates
  • PR 3: UI integration — settings.tsx, index.tsx, payment-failure.tsx, payment.web.ts, transactions.web.ts
Found 4 issue(s)

Issue 1: Legacy pos-credentials postMessage protocol removed — dashboard integrations break silently

ID: brk-use-url-credentials-remove-pos-credentials-protocol-a3f2
File: dapps/pos-app/hooks/use-url-credentials.ts:33
Severity: HIGH
Category: breaking_change

Context:

  • Pattern: The previous useUrlCredentials hook accepted { type: "pos-credentials", merchantId?, customerApiKey? } from the parent and applied credentials. The new hook silently drops all messages of this type.
  • Risk: Any parent/dashboard embedding this POS via iframe and sending pos-credentials will receive no acknowledgment; the POS stays unconfigured. Payments fail with "Merchant ID is not configured".
  • Impact: All existing dashboard integrations using the old postMessage credential protocol.
  • Trigger: Immediately on upgrade — POS broadcasts pos-ready but never responds to pos-credentials.

Recommendation: Document the migration from pos-credentialspos-bridge-config (which excludes the API key). Consider emitting a protocolVersion in pos-ready so dashboards can detect the break.


Issue 2: URL-based credential injection removed — link/QR-based deployments break silently

ID: brk-use-url-credentials-remove-url-credential-params-b7c1
File: dapps/pos-app/hooks/use-url-credentials.ts:33
Severity: HIGH
Category: breaking_change

Context:

  • Pattern: The previous hook parsed ?merchantId=<base64>&customerApiKey=<base64> on web startup. The new hook performs no URL parameter parsing.
  • Risk: Any integration that launched the POS with encoded credentials in the URL finds them silently ignored. POS starts unconfigured.
  • Impact: Bookmark-based, deep-link, or QR-code deployment workflows.
  • Trigger: On page load — the query parameters are present but never read.

Recommendation: If any integrators use this flow, add a deprecation log. The PR description acknowledges URL intake is intentionally removed, but ensure all consumers are notified.


Issue 3: Race condition — isTerminalConfigured reads stale useSettingsStore.merchantId immediately after bridge configuration

ID: index-bridge-merchant-id-race-7c4a
File: dapps/pos-app/app/index.tsx:49
Severity: MEDIUM
Category: correctness

Context:

  • Pattern: index.tsx calls isTerminalConfigured(merchantId, isCustomerApiKeySet, isBridgeConfigured) where merchantId comes from useSettingsStore. But configureBridge() sets usePosBridgeStore.isConfigured = true synchronously, while useSettingsStore.merchantId is only set asynchronously (inside clearCustomerApiKey().then(...)).
  • Risk: Between the bridge-config message arriving and the .then() callback flushing, React re-renders with isBridgeConfigured=true but merchantId=null. isTerminalConfigured(null, false, true) returns false, redirecting the user to Settings.
  • Impact: Brief flash/redirect-to-Settings immediately after bridge configures, before the async merchant ID is persisted.
  • Trigger: User taps "New Payment" in the narrow async window after bridge init.

Recommendation:

// index.tsx — read bridge merchant ID directly from the bridge store
const merchantId = useSettingsStore((state) => state.merchantId);
const bridgeMerchantId = usePosBridgeStore((state) => state.merchantId);
const isBridgeConfigured = usePosBridgeStore((state) => state.isConfigured);
// Bridge store sets merchantId synchronously; avoid the async settings gap
const effectiveMerchantId = bridgeMerchantId ?? merchantId;

if (!isTerminalConfigured(effectiveMerchantId, isCustomerApiKeySet, isBridgeConfigured))

Fix this →


Issue 4: No pos-bridge-configured confirmation sent back to parent — dashboard cannot detect lock-in

ID: brk-use-url-credentials-remove-credentials-updated-event-e9d4
File: dapps/pos-app/hooks/use-url-credentials.ts:57
Severity: MEDIUM
Category: breaking_change

Context:

  • Pattern: The old hook posted { type: "pos-credentials-updated" } after credentials were applied. The new hook emits nothing after configureBridge succeeds — the parent has no way to confirm that the bridge locked in.
  • Risk: Dashboard UIs that wait for a "configured" confirmation before initiating a payment flow will hang or show incorrect state.
  • Impact: Any dashboard rendering a "POS connected" indicator based on the old event.

Recommendation:

// After configureBridge returns true, post a confirmation back to the locked origin
if (event.source === window.parent && configureBridge(...)) {
  (event.source as Window).postMessage(
    { type: "pos-bridge-configured", protocolVersion: PROTOCOL_VERSION },
    event.origin,  // use locked origin, not "*"
  );
  void clearCustomerApiKey().then(...);
}

Comment thread dapps/pos-app/hooks/use-url-credentials.ts Outdated
Comment thread dapps/pos-app/hooks/use-url-credentials.ts Outdated
Comment thread dapps/pos-app/app/index.tsx Outdated
Comment thread dapps/pos-app/hooks/use-pos-bridge.ts
Comment thread dapps/pos-app/.gitignore Outdated
@ignaciosantise
ignaciosantise marked this pull request as ready for review September 1, 2026 19:45
Copilot AI lite review requested due to automatic review settings September 1, 2026 19:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a locked-origin, in-memory postMessage bridge for the pos-app web runtime so embedded POS API operations are executed by the parent dashboard (server-side), while removing URL / legacy postMessage credential intake and adjusting UI flows accordingly.

Changes:

  • Introduces a POS bridge transport (pos-bridge.ts + usePosBridgeStore) and a web hook (use-pos-bridge) to configure it after settings hydration.
  • Routes web payment/transaction services through the bridge when configured, otherwise preserves the existing direct proxy behavior.
  • Updates Settings/Home/Failure UI logic to reflect bridge mode (read-only merchant identity, hides local API-key UI, adjusted setup gating) and replaces legacy URL-credential tests with bridge tests.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
dapps/pos-app/utils/pos-bridge-ui.ts New helper functions to drive bridge-mode UI gating and setup state.
dapps/pos-app/store/usePosBridgeStore.ts Adds runtime-only Zustand store for bridge configuration state (non-persisted).
dapps/pos-app/services/transactions.web.ts Uses the bridge for get-transactions when configured; otherwise keeps proxy flow.
dapps/pos-app/services/pos-bridge.ts Implements the locked parent/origin transport + request/response correlation + timeout.
dapps/pos-app/services/payment.web.ts Routes start/status/cancel payment calls through the bridge when configured.
dapps/pos-app/hooks/use-url-credentials.ts Removes URL/postMessage credential intake hook.
dapps/pos-app/hooks/use-pos-bridge.ts Adds bridge initialization/configuration hook and readiness announcement.
dapps/pos-app/app/settings.tsx Makes bridge-managed merchant ID read-only; hides local key UI in bridge mode.
dapps/pos-app/app/payment-failure.tsx Changes invalid-key routing logic to avoid sending bridge users to Settings.
dapps/pos-app/app/index.tsx Updates “terminal configured” gating to allow bridge mode (no local API key).
dapps/pos-app/app/_layout.tsx Swaps legacy URL-credential hook for the new bridge hook.
dapps/pos-app/tests/utils/pos-bridge-ui.test.ts Adds unit coverage for the new UI helper logic.
dapps/pos-app/tests/services/web-bridge-services.test.ts Adds coverage ensuring web services avoid local key/fetch in bridge mode.
dapps/pos-app/tests/services/pos-bridge.test.ts Adds transport-level tests (validation, locking, timeouts, concurrency).
dapps/pos-app/tests/hooks/use-url-credentials.test.ts Removes tests for the deleted URL/postMessage credential intake flow.
dapps/pos-app/tests/hooks/use-pos-bridge.test.ts Adds tests for bridge hook behavior and legacy credential ignoring.
Suppressed comments (1)

dapps/pos-app/hooks/use-pos-bridge.ts:62

  • If clearCustomerApiKey() throws during initialization (e.g., secure storage unavailable), initialize() rejects and pos-ready is never posted, leaving the embedded POS unable to configure. Catch and ignore (or log) this error so bridge setup can continue.
      if (window.parent !== window) {
        // Do not retain an old local key while an embedded POS is waiting for
        // bridge configuration. No URL or legacy credential fallback exists.
        await clearCustomerApiKey();
      }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread dapps/pos-app/hooks/use-pos-bridge.ts Outdated
Comment thread dapps/pos-app/app/payment-failure.tsx Outdated
@ignaciosantise
ignaciosantise merged commit 39224a0 into main Sep 2, 2026
9 checks passed
@ignaciosantise
ignaciosantise deleted the fix/secure-pos-credentials branch September 2, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants