@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/CHANGELOG.md +327 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/SECURITY.md +138 -0
- package/dist/AforoEmbed-BqQOFsl3.d.cts +67 -0
- package/dist/AforoEmbed-CmqjlYlt.d.ts +67 -0
- package/dist/index.cjs +12320 -0
- package/dist/index.d.cts +1228 -0
- package/dist/index.d.ts +1228 -0
- package/dist/index.mjs +12286 -0
- package/dist/loader.js +3 -0
- package/dist/loader.mjs +3 -0
- package/dist/sri.json +15 -0
- package/dist/types-Dm6cp_Cc.d.cts +1185 -0
- package/dist/types-Dm6cp_Cc.d.ts +1185 -0
- package/dist/vanilla/index.cjs +12567 -0
- package/dist/vanilla/index.d.cts +22 -0
- package/dist/vanilla/index.d.ts +22 -0
- package/dist/vanilla/index.mjs +12543 -0
- package/dist/vue/index.cjs +12634 -0
- package/dist/vue/index.d.cts +157 -0
- package/dist/vue/index.d.ts +157 -0
- package/dist/vue/index.mjs +12605 -0
- package/dist/widgets/checkout-flow.js +55 -0
- package/dist/widgets/invoice-list.js +60 -0
- package/dist/widgets/payment-method.js +55 -0
- package/dist/widgets/pricing-card.js +60 -0
- package/dist/widgets/subscribe-button.js +61 -0
- package/dist/widgets/subscription-manager.js +55 -0
- package/dist/widgets/upgrade-cancel.js +55 -0
- package/dist/widgets/usage-meter.js +55 -0
- package/loader.sri.txt +1 -0
- package/package.json +118 -0
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 <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 };
|