@aforoai/storefront-widgets 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SECURITY.md ADDED
@@ -0,0 +1,138 @@
1
+ # Security Policy
2
+
3
+ This document describes the security model of `@aforoai/storefront-widgets` and
4
+ the procedure for reporting vulnerabilities. For broader platform-wide security
5
+ posture, see [`SECURITY.md`](https://github.com/aforoai/aforo-nextgen-UI/blob/main/SECURITY.md)
6
+ in the root of the Aforo UI repository.
7
+
8
+ ## Supported Versions
9
+
10
+ The widget SDK follows semver. We provide security fixes for the latest minor
11
+ release within the current major (`v1.x`). Older majors are not patched once a
12
+ new major ships — customers should upgrade to the current major.
13
+
14
+ | Version | Supported |
15
+ |---------|--------------------|
16
+ | 0.1.x | :white_check_mark: |
17
+ | < 0.1 | :x: |
18
+
19
+ > **Pre-1.0 caveat.** Until the SDK reaches 1.0 (after the remaining widget
20
+ > bodies ship), each minor release may carry incremental security hardening.
21
+ > Pin to a specific version in production and watch the changelog.
22
+
23
+ ## Reporting a Vulnerability
24
+
25
+ If you believe you have found a security vulnerability in
26
+ `@aforoai/storefront-widgets`, **do not file a public GitHub issue**. Report it
27
+ privately:
28
+
29
+ **Email:** `security@aforo.ai`
30
+
31
+ PGP-encrypted reports are welcome. Request our key by emailing the same address
32
+ with subject `PGP key request`.
33
+
34
+ ### What to include
35
+
36
+ - A clear description of the issue and its impact
37
+ - Steps to reproduce (proof-of-concept code, request/response examples,
38
+ screenshots)
39
+ - Affected widget(s), affected version(s), and browser/runtime
40
+ - Your contact info so we can follow up
41
+ - Any disclosure timeline you have in mind (we follow coordinated disclosure)
42
+
43
+ ### What to expect
44
+
45
+ - **Acknowledgement**: within 2 business days
46
+ - **Initial triage**: within 5 business days, with severity assessment
47
+ (Critical / High / Medium / Low)
48
+ - **Fix timeline**: Critical → ASAP (typically &lt;72h), High → within 14 days,
49
+ Medium/Low → next routine release
50
+ - **Disclosure**: we coordinate public disclosure timing with the reporter;
51
+ default 90-day embargo
52
+
53
+ We do not currently run a paid bug-bounty program. We will credit reporters
54
+ publicly (with their consent) in the relevant release notes.
55
+
56
+ ## Security Model
57
+
58
+ The plugin tier sits between Aforo's headless API (T6, full integration
59
+ flexibility) and the managed storefront (T1, zero integration). Customers
60
+ embed Aforo-served scripts on their pages, so the security boundary is
61
+ carefully drawn.
62
+
63
+ ### Identity and trust
64
+
65
+ - **Embed key.** Each tenant mints embed keys in Embed Studio. The key is a
66
+ public identifier — its security comes from a server-side **domain
67
+ allowlist**. A key only works when served from a registered origin. See
68
+ the [Authentication docs](https://docs.aforo.ai/docs/embed-authentication).
69
+ - **Bridge token.** Customers' own backends mint short-lived bridge tokens
70
+ (signed with a server-side key) to identify their customer to Aforo. Aforo
71
+ exchanges the bridge token for an in-memory session JWT. The customer's
72
+ long-lived signing key never reaches the browser.
73
+ - **Session JWT.** Stored in memory only — never in `localStorage` or
74
+ `sessionStorage`, never in cookies. Cleared on tab close or explicit
75
+ `AforoSession.destroy()`.
76
+ - **Magic-link mode** (Prompt 9). For customer portals without their own
77
+ session, Aforo can mint a session JWT via an email magic link with anti-
78
+ enumeration rate limits and 15-minute one-time-use tokens. See
79
+ [`MagicLinkFallback`](https://docs.aforo.ai/docs/embed-authentication#magic-link-mode).
80
+
81
+ ### Browser-side integrity
82
+
83
+ - **Subresource Integrity (SRI).** Every bundle ships with a SHA-384 hash
84
+ published at `https://embed.aforo.ai/v1/sri.json` and per-release in the
85
+ changelog. Customers MUST pin the `integrity=` attribute on their
86
+ `<script>` tag. See [Security docs](https://docs.aforo.ai/docs/embed-security).
87
+ - **HTTPS only.** The loader refuses to bootstrap on `http://` pages.
88
+ - **CSP.** Customers add `script-src https://embed.aforo.ai` (and
89
+ `connect-src https://api.aforo.ai`) to their Content-Security-Policy.
90
+ The widgets do not require `unsafe-inline` or `unsafe-eval`. See the
91
+ [CSP guidance](https://docs.aforo.ai/docs/embed-security#content-security-policy).
92
+ - **No dynamic code.** No `eval()`, no `Function()` constructor, no dynamic
93
+ `import()` of untrusted URLs.
94
+ - **Sandboxed iframe (iframe tier).** When customers choose the iframe tier
95
+ (Phase 1, not yet shipped), the widget runs in an `<iframe sandbox="">`
96
+ with explicit `allow-scripts` only — never `allow-same-origin`.
97
+
98
+ ### Network and data
99
+
100
+ - **Per-tenant rate limits.** Magic-link request: 5/hour per email,
101
+ 10/minute per IP. Bridge exchange: 60/minute per key.
102
+ See the [magic-link auth runbook](https://github.com/aforoai/aforo-nextgen-storefront-service/blob/main/docs/runbooks/2026-05-29-embed-magic-link-smoke.md).
103
+ - **Anti-enumeration.** Magic-link request endpoint always returns the same
104
+ 202 envelope regardless of whether the email exists — attackers cannot
105
+ use it as a user-registration probe.
106
+ - **postMessage origin validation.** All inbound and outbound events validate
107
+ the parent-page origin against the embed key's allowed-domains list.
108
+ postMessage with `targetOrigin: '*'` is **never** used — every emit uses the
109
+ resolved parent origin.
110
+ - **Webhook authority.** postMessage events fired into the parent page are
111
+ **advisory only**. Customers MUST validate state changes via Aforo's
112
+ signed webhooks before mutating their own database. The SDK explicitly
113
+ surfaces this in the [`events` docs](https://docs.aforo.ai/docs/embed-events#webhook-vs-postmessage).
114
+ - **Idempotency keys.** Every mutation (subscribe, pay invoice, cart action)
115
+ accepts an idempotency key — flaky-network retry never double-charges.
116
+
117
+ ### Out of scope (customer responsibility)
118
+
119
+ The following are the **customer's responsibility**, not the widget SDK's:
120
+
121
+ - Securing the customer's own bridge-token signing key (HSM, KMS, secret
122
+ manager — never check it into source)
123
+ - Securing the customer's own portal authentication (Aforo only sees the
124
+ bridge token after the customer has authenticated their user)
125
+ - 3DS / SCA challenge UX beyond the Phase 0 redirect fallback (Phase 1
126
+ ships an inline 3DS modal)
127
+ - The customer's own Content-Security-Policy configuration on their pages
128
+ - Browser extensions / supply-chain attacks against the customer's own
129
+ build pipeline
130
+
131
+ ## Past Advisories
132
+
133
+ None to date.
134
+
135
+ ## Acknowledgements
136
+
137
+ Thanks to the security researchers and customers who have responsibly
138
+ disclosed issues. Specific credits will be added here as advisories ship.
@@ -0,0 +1,67 @@
1
+ import { aD as WidgetMountConfig, Q as AforoEventType, N as AforoEventPayload, aC as WidgetId } from './types-Dm6cp_Cc.cjs';
2
+
3
+ /**
4
+ * AforoEmbed — vanilla-JS + script-tag mount API.
5
+ *
6
+ * AforoEmbed.configure({ tenantSlug, embedKey }) — sets global defaults
7
+ * AforoEmbed.mount(element, { widget, config }) — mounts a widget
8
+ * AforoEmbed.unmount(element) — teardown
9
+ * AforoEmbed.on(eventType, handler) — typed subscriber
10
+ * AforoEmbed.version — VERSION constant
11
+ *
12
+ * Idempotent: `mount(el, …)` twice on the same element is a no-op
13
+ * (FR-SDK-9). React Strict Mode's dev-time double-effect is safe.
14
+ *
15
+ * Attached to `window.aforoEmbed` when running in browser context. The
16
+ * loader script (`loader/loader.ts`) imports this module + assigns it
17
+ * before scanning the DOM for `data-aforo-widget` placeholders.
18
+ */
19
+
20
+ type GlobalConfig = {
21
+ tenantSlug?: string;
22
+ embedKey?: string;
23
+ bridgeToken?: string;
24
+ embedBaseUrl?: string;
25
+ };
26
+ type EventHandler = (event: AforoEventPayload) => void;
27
+ /**
28
+ * Lazy registry — a widget bundle (Prompts 4-7) calls
29
+ * `AforoEmbed._registerWidget('pricing-card', mountFn)` at load time. The
30
+ * registry is internal: the registration function is NOT part of the
31
+ * locked public surface (asserted by sdk-contract.test.ts).
32
+ */
33
+ type MountFn = (element: HTMLElement, config: WidgetMountConfig) => () => void;
34
+ /** Internal: widget bundles register here. Not exported publicly. */
35
+ declare function registerWidget(id: WidgetId, mountFn: MountFn): void;
36
+ declare const AforoEmbed: {
37
+ version: string;
38
+ /**
39
+ * Set platform-wide defaults. Calling repeatedly merges (last call wins
40
+ * per key). Useful when the SPA learns the tenant context after page
41
+ * load (FR-TIER-10).
42
+ */
43
+ configure(cfg: GlobalConfig): void;
44
+ /** Read-only snapshot of the current global config. */
45
+ getConfig(): GlobalConfig;
46
+ /**
47
+ * Mount a widget into the given element. Idempotent — re-mounting the
48
+ * same element is a no-op.
49
+ */
50
+ mount(element: HTMLElement | null, config: WidgetMountConfig): boolean;
51
+ /** Unmount a previously-mounted widget. */
52
+ unmount(element: HTMLElement | null): boolean;
53
+ /**
54
+ * Subscribe to widget events. Returns an unsubscribe function.
55
+ *
56
+ * Pass `'*'` to receive every aforo.* event.
57
+ */
58
+ on(eventType: AforoEventType, handler: EventHandler): () => void;
59
+ /** Internal: bundle registration hook. Not part of the public surface. */
60
+ _registerWidget: typeof registerWidget;
61
+ /** Internal: list registered widget ids. Tests only. */
62
+ _registeredWidgetsForTesting(): WidgetId[];
63
+ /** Internal: reset registry. Tests only. */
64
+ _resetForTesting(): void;
65
+ };
66
+
67
+ export { AforoEmbed as A };
@@ -0,0 +1,67 @@
1
+ import { aD as WidgetMountConfig, Q as AforoEventType, N as AforoEventPayload, aC as WidgetId } from './types-Dm6cp_Cc.js';
2
+
3
+ /**
4
+ * AforoEmbed — vanilla-JS + script-tag mount API.
5
+ *
6
+ * AforoEmbed.configure({ tenantSlug, embedKey }) — sets global defaults
7
+ * AforoEmbed.mount(element, { widget, config }) — mounts a widget
8
+ * AforoEmbed.unmount(element) — teardown
9
+ * AforoEmbed.on(eventType, handler) — typed subscriber
10
+ * AforoEmbed.version — VERSION constant
11
+ *
12
+ * Idempotent: `mount(el, …)` twice on the same element is a no-op
13
+ * (FR-SDK-9). React Strict Mode's dev-time double-effect is safe.
14
+ *
15
+ * Attached to `window.aforoEmbed` when running in browser context. The
16
+ * loader script (`loader/loader.ts`) imports this module + assigns it
17
+ * before scanning the DOM for `data-aforo-widget` placeholders.
18
+ */
19
+
20
+ type GlobalConfig = {
21
+ tenantSlug?: string;
22
+ embedKey?: string;
23
+ bridgeToken?: string;
24
+ embedBaseUrl?: string;
25
+ };
26
+ type EventHandler = (event: AforoEventPayload) => void;
27
+ /**
28
+ * Lazy registry — a widget bundle (Prompts 4-7) calls
29
+ * `AforoEmbed._registerWidget('pricing-card', mountFn)` at load time. The
30
+ * registry is internal: the registration function is NOT part of the
31
+ * locked public surface (asserted by sdk-contract.test.ts).
32
+ */
33
+ type MountFn = (element: HTMLElement, config: WidgetMountConfig) => () => void;
34
+ /** Internal: widget bundles register here. Not exported publicly. */
35
+ declare function registerWidget(id: WidgetId, mountFn: MountFn): void;
36
+ declare const AforoEmbed: {
37
+ version: string;
38
+ /**
39
+ * Set platform-wide defaults. Calling repeatedly merges (last call wins
40
+ * per key). Useful when the SPA learns the tenant context after page
41
+ * load (FR-TIER-10).
42
+ */
43
+ configure(cfg: GlobalConfig): void;
44
+ /** Read-only snapshot of the current global config. */
45
+ getConfig(): GlobalConfig;
46
+ /**
47
+ * Mount a widget into the given element. Idempotent — re-mounting the
48
+ * same element is a no-op.
49
+ */
50
+ mount(element: HTMLElement | null, config: WidgetMountConfig): boolean;
51
+ /** Unmount a previously-mounted widget. */
52
+ unmount(element: HTMLElement | null): boolean;
53
+ /**
54
+ * Subscribe to widget events. Returns an unsubscribe function.
55
+ *
56
+ * Pass `'*'` to receive every aforo.* event.
57
+ */
58
+ on(eventType: AforoEventType, handler: EventHandler): () => void;
59
+ /** Internal: bundle registration hook. Not part of the public surface. */
60
+ _registerWidget: typeof registerWidget;
61
+ /** Internal: list registered widget ids. Tests only. */
62
+ _registeredWidgetsForTesting(): WidgetId[];
63
+ /** Internal: reset registry. Tests only. */
64
+ _resetForTesting(): void;
65
+ };
66
+
67
+ export { AforoEmbed as A };