backend-manager 5.9.5 → 5.9.6

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.
Files changed (244) hide show
  1. package/package.json +8 -1
  2. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/metadata.json +14 -0
  3. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.html +486 -0
  4. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.md +41 -0
  5. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.mjml +97 -0
  6. package/{test/email/fixtures/clean.json → src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/structure.json} +16 -4
  7. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/summary.md +1 -0
  8. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/metadata.json +14 -0
  9. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.html +486 -0
  10. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.md +41 -0
  11. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.mjml +97 -0
  12. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/structure.json +42 -0
  13. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/summary.md +1 -0
  14. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/metadata.json +14 -0
  15. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.html +486 -0
  16. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.md +41 -0
  17. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.mjml +97 -0
  18. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/structure.json +42 -0
  19. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/summary.md +1 -0
  20. package/src/test/fixtures/firebase-project/.temp/test-mode.json +6 -0
  21. package/src/test/fixtures/firebase-project/database-debug.log +12 -0
  22. package/src/test/fixtures/firebase-project/firestore-debug.log +136 -0
  23. package/src/test/fixtures/firebase-project/pubsub-debug.log +17 -0
  24. package/.codeclimate.yml +0 -32
  25. package/.nvmrc +0 -1
  26. package/CHANGELOG.md +0 -1440
  27. package/PROGRESS.md +0 -93
  28. package/TODO-2.md +0 -121
  29. package/TODO-CHARGEBLAST.md +0 -32
  30. package/TODO-WEBHOOK-KEY-LEGACY-REMOVAL.md +0 -15
  31. package/TODO-WEBHOOK-KEY-UPGRADE.md +0 -138
  32. package/TODO-email-auth.md +0 -14
  33. package/TODO.md +0 -187
  34. package/plans/mcp2.md +0 -247
  35. package/plans/suspended-subscription-dead-zone.md +0 -97
  36. package/scripts/test-helper-providers.js +0 -162
  37. package/scripts/update-disposable-domains.js +0 -44
  38. package/templates/_.env +0 -42
  39. package/templates/_.gitignore +0 -60
  40. package/templates/backend-manager-config.json +0 -249
  41. package/templates/database.rules.json +0 -83
  42. package/templates/firebase.json +0 -66
  43. package/templates/firestore.indexes.json +0 -4
  44. package/templates/firestore.rules +0 -112
  45. package/templates/public/404.html +0 -26
  46. package/templates/public/index.html +0 -24
  47. package/templates/remoteconfig.template.json +0 -1
  48. package/templates/storage-lifecycle-config-1-day.json +0 -9
  49. package/templates/storage-lifecycle-config-30-days.json +0 -9
  50. package/templates/storage.rules +0 -8
  51. package/test/_init/accounts-validation.js +0 -58
  52. package/test/_legacy/ai/index.js +0 -231
  53. package/test/_legacy/ai/test.jpg +0 -0
  54. package/test/_legacy/ai/test.pdf +0 -0
  55. package/test/_legacy/index.js +0 -31
  56. package/test/_legacy/payment-resolver/chargebee/orders/refunded.json +0 -0
  57. package/test/_legacy/payment-resolver/chargebee/orders/unpaid.json +0 -98
  58. package/test/_legacy/payment-resolver/chargebee/subscriptions/active.json +0 -578
  59. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-in-trial.json +0 -38
  60. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-skipped-to-active.json +0 -135
  61. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active-to-cancelled.json +0 -42
  62. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active.json +0 -44
  63. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-cancelled.json +0 -39
  64. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-refund.json +0 -143
  65. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-suspended.json +0 -48
  66. package/test/_legacy/payment-resolver/coinbase/orders/regular.json +0 -204
  67. package/test/_legacy/payment-resolver/coinbase/subscriptions/cancelled.json +0 -204
  68. package/test/_legacy/payment-resolver/coinbase/subscriptions/paid-2.json +0 -125
  69. package/test/_legacy/payment-resolver/index.js +0 -1558
  70. package/test/_legacy/payment-resolver/paypal/orders/refunded.json +0 -0
  71. package/test/_legacy/payment-resolver/paypal/orders/regular.json +0 -120
  72. package/test/_legacy/payment-resolver/paypal/subscriptions/active-refund-previous-stmnt.json +0 -323
  73. package/test/_legacy/payment-resolver/paypal/subscriptions/active.json +0 -192
  74. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-in-trial.json +0 -125
  75. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-not-complete.json +0 -76
  76. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue-2.json +0 -114
  77. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue.json +0 -114
  78. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active-to-cancelled.json +0 -111
  79. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active.json +0 -132
  80. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-cancelled.json +0 -107
  81. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-expired.json +0 -91
  82. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-refund.json +0 -147
  83. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-suspended.json +0 -120
  84. package/test/_legacy/payment-resolver/stripe/orders/refunded.json +0 -0
  85. package/test/_legacy/payment-resolver/stripe/orders/regular.json +0 -91
  86. package/test/_legacy/payment-resolver/stripe/subscriptions/active.json +0 -421
  87. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-in-trial.json +0 -349
  88. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active-failed-authorization.json +0 -430
  89. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active.json +0 -319
  90. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-cancelled.json +0 -319
  91. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-refund.json +0 -414
  92. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-suspended.json +0 -319
  93. package/test/_legacy/payment-resolver/stripe/subscriptions/unsure.json +0 -339
  94. package/test/_legacy/test-new +0 -1178
  95. package/test/_legacy/test.md +0 -89
  96. package/test/_legacy/usage.js +0 -175
  97. package/test/_legacy/user.js +0 -31
  98. package/test/ai/tools-live.js +0 -170
  99. package/test/boot/emulator-boots.js +0 -37
  100. package/test/content/blog-generate.js +0 -160
  101. package/test/email/campaign-send.js +0 -45
  102. package/test/email/consent-lifecycle.js +0 -257
  103. package/test/email/feedback-and-plain-send.js +0 -52
  104. package/test/email/fixtures/editorial.json +0 -30
  105. package/test/email/fixtures/field-report.json +0 -53
  106. package/test/email/marketing-lifecycle.js +0 -132
  107. package/test/email/newsletter-generate.js +0 -819
  108. package/test/email/newsletter-templates.js +0 -491
  109. package/test/email/templates.js +0 -207
  110. package/test/email/transactional-send.js +0 -34
  111. package/test/email/transactional.js +0 -560
  112. package/test/email/validation.js +0 -710
  113. package/test/events/auth-delete-race.js +0 -201
  114. package/test/events/payments/journey-payments-cancel-endpoint.js +0 -100
  115. package/test/events/payments/journey-payments-cancel.js +0 -182
  116. package/test/events/payments/journey-payments-discount.js +0 -89
  117. package/test/events/payments/journey-payments-failure.js +0 -146
  118. package/test/events/payments/journey-payments-legacy-product.js +0 -149
  119. package/test/events/payments/journey-payments-one-time-failure.js +0 -111
  120. package/test/events/payments/journey-payments-one-time.js +0 -136
  121. package/test/events/payments/journey-payments-plan-change.js +0 -144
  122. package/test/events/payments/journey-payments-refund-webhook.js +0 -180
  123. package/test/events/payments/journey-payments-suspend.js +0 -181
  124. package/test/events/payments/journey-payments-trial-cancel.js +0 -130
  125. package/test/events/payments/journey-payments-trial.js +0 -171
  126. package/test/events/payments/journey-payments-uid-resolution.js +0 -132
  127. package/test/events/payments/journey-payments-upgrade.js +0 -135
  128. package/test/fixtures/chargebee/invoice-one-time.json +0 -27
  129. package/test/fixtures/chargebee/subscription-active.json +0 -44
  130. package/test/fixtures/chargebee/subscription-cancelled.json +0 -42
  131. package/test/fixtures/chargebee/subscription-in-trial.json +0 -41
  132. package/test/fixtures/chargebee/subscription-legacy-plan.json +0 -41
  133. package/test/fixtures/chargebee/subscription-non-renewing.json +0 -41
  134. package/test/fixtures/chargebee/subscription-paused.json +0 -42
  135. package/test/fixtures/chargebee/webhook-payment-failed.json +0 -51
  136. package/test/fixtures/chargebee/webhook-subscription-created.json +0 -47
  137. package/test/fixtures/paypal/order-approved.json +0 -62
  138. package/test/fixtures/paypal/order-completed.json +0 -110
  139. package/test/fixtures/paypal/subscription-active.json +0 -76
  140. package/test/fixtures/paypal/subscription-cancelled.json +0 -50
  141. package/test/fixtures/paypal/subscription-suspended.json +0 -65
  142. package/test/fixtures/stripe/checkout-session-completed.json +0 -130
  143. package/test/fixtures/stripe/invoice-payment-failed.json +0 -148
  144. package/test/fixtures/stripe/invoice-subscription-payment-failed.json +0 -28
  145. package/test/fixtures/stripe/subscription-active.json +0 -161
  146. package/test/fixtures/stripe/subscription-canceled.json +0 -161
  147. package/test/fixtures/stripe/subscription-trialing.json +0 -161
  148. package/test/functions/admin/database-read.js +0 -134
  149. package/test/functions/admin/database-write.js +0 -159
  150. package/test/functions/admin/edit-post.js +0 -366
  151. package/test/functions/admin/firestore-query.js +0 -207
  152. package/test/functions/admin/firestore-read.js +0 -129
  153. package/test/functions/admin/firestore-write.js +0 -105
  154. package/test/functions/admin/get-stats.js +0 -115
  155. package/test/functions/admin/send-email.js +0 -119
  156. package/test/functions/admin/send-notification.js +0 -208
  157. package/test/functions/admin/write-repo-content.js +0 -223
  158. package/test/functions/general/add-marketing-contact.js +0 -335
  159. package/test/functions/general/fetch-post.js +0 -54
  160. package/test/functions/general/generate-uuid.js +0 -114
  161. package/test/functions/test/authenticate.js +0 -64
  162. package/test/functions/user/create-custom-token.js +0 -73
  163. package/test/functions/user/delete.js +0 -135
  164. package/test/functions/user/get-active-sessions.js +0 -111
  165. package/test/functions/user/get-subscription-info.js +0 -99
  166. package/test/functions/user/regenerate-api-keys.js +0 -156
  167. package/test/functions/user/resolve.js +0 -52
  168. package/test/functions/user/sign-out-all-sessions.js +0 -94
  169. package/test/functions/user/sign-up.js +0 -252
  170. package/test/functions/user/submit-feedback.js +0 -104
  171. package/test/functions/user/validate-settings.js +0 -82
  172. package/test/helpers/ai-request-payload.js +0 -300
  173. package/test/helpers/ai-test-provider.js +0 -202
  174. package/test/helpers/ai-tools-format.js +0 -383
  175. package/test/helpers/content/blog-auto-publisher.js +0 -484
  176. package/test/helpers/content/feed-parser.js +0 -528
  177. package/test/helpers/content/ghostii-blocks.js +0 -134
  178. package/test/helpers/content/ghostii-feed-integration.js +0 -404
  179. package/test/helpers/content/ghostii-write-article.js +0 -243
  180. package/test/helpers/environment.js +0 -230
  181. package/test/helpers/infer-contact.js +0 -113
  182. package/test/helpers/merge-line-files.js +0 -271
  183. package/test/helpers/payment/chargebee/parse-webhook.js +0 -413
  184. package/test/helpers/payment/chargebee/to-unified-one-time.js +0 -147
  185. package/test/helpers/payment/chargebee/to-unified-subscription.js +0 -648
  186. package/test/helpers/payment/paypal/parse-webhook.js +0 -539
  187. package/test/helpers/payment/paypal/to-unified-one-time.js +0 -382
  188. package/test/helpers/payment/paypal/to-unified-subscription.js +0 -820
  189. package/test/helpers/payment/stripe/parse-webhook.js +0 -447
  190. package/test/helpers/payment/stripe/to-unified-one-time.js +0 -306
  191. package/test/helpers/payment/stripe/to-unified-subscription.js +0 -708
  192. package/test/helpers/sanitize.js +0 -222
  193. package/test/helpers/slugify.js +0 -394
  194. package/test/helpers/storage.js +0 -194
  195. package/test/helpers/user.js +0 -755
  196. package/test/helpers/webhook-forward.js +0 -418
  197. package/test/mcp/discovery.js +0 -53
  198. package/test/mcp/oauth.js +0 -161
  199. package/test/mcp/protocol.js +0 -268
  200. package/test/mcp/roles.js +0 -188
  201. package/test/mcp/utils.js +0 -245
  202. package/test/routes/admin/create-post.js +0 -364
  203. package/test/routes/admin/database.js +0 -131
  204. package/test/routes/admin/deduplicate-image-alts.js +0 -190
  205. package/test/routes/admin/email.js +0 -114
  206. package/test/routes/admin/firestore-query.js +0 -204
  207. package/test/routes/admin/firestore.js +0 -127
  208. package/test/routes/admin/infer-contact.js +0 -218
  209. package/test/routes/admin/notification.js +0 -198
  210. package/test/routes/admin/post-resize-image.js +0 -184
  211. package/test/routes/admin/post.js +0 -368
  212. package/test/routes/admin/repo-content.js +0 -223
  213. package/test/routes/admin/stats.js +0 -112
  214. package/test/routes/content/post.js +0 -54
  215. package/test/routes/general/uuid.js +0 -115
  216. package/test/routes/marketing/campaign.js +0 -125
  217. package/test/routes/marketing/contact.js +0 -370
  218. package/test/routes/marketing/email-preferences.js +0 -292
  219. package/test/routes/marketing/push-send.js +0 -32
  220. package/test/routes/marketing/webhook-forward.js +0 -61
  221. package/test/routes/marketing/webhook.js +0 -639
  222. package/test/routes/payments/cancel.js +0 -163
  223. package/test/routes/payments/discount.js +0 -80
  224. package/test/routes/payments/dispute-alert.js +0 -320
  225. package/test/routes/payments/intent.js +0 -336
  226. package/test/routes/payments/portal.js +0 -92
  227. package/test/routes/payments/refund.js +0 -179
  228. package/test/routes/payments/trial-eligibility.js +0 -71
  229. package/test/routes/payments/webhook.js +0 -107
  230. package/test/routes/test/authenticate.js +0 -59
  231. package/test/routes/test/schema.js +0 -554
  232. package/test/routes/test/usage.js +0 -342
  233. package/test/routes/user/api-keys.js +0 -156
  234. package/test/routes/user/delete.js +0 -136
  235. package/test/routes/user/feedback.js +0 -104
  236. package/test/routes/user/sessions.js +0 -199
  237. package/test/routes/user/settings-validate.js +0 -82
  238. package/test/routes/user/signup.js +0 -542
  239. package/test/routes/user/subscription.js +0 -99
  240. package/test/routes/user/token.js +0 -73
  241. package/test/routes/user/user.js +0 -157
  242. package/test/rules/notifications.js +0 -410
  243. package/test/rules/payments-carts.js +0 -371
  244. package/test/rules/user.js +0 -396
package/plans/mcp2.md DELETED
@@ -1,247 +0,0 @@
1
- # MCP Role-Based Tool Scoping + Consumer Extensibility
2
-
3
- ## Context
4
-
5
- The BEM MCP server currently works only for admins — a single `BACKEND_MANAGER_KEY` grants access to all 19 tools. Regular users can't connect at all, and consumer BEM projects have no way to add custom MCP tools. This plan adds:
6
-
7
- 1. **Role-based tool scoping** — tools tagged `admin`, `user`, or `public`; connections only see tools matching their role
8
- 2. **User authentication** — seamless OAuth sign-in flow + API key as long-lived credential
9
- 3. **Consumer MCP tools** — a single `functions/mcp.js` file in consumer projects
10
-
11
- ## Architecture
12
-
13
- ### Role Model
14
-
15
- Every tool gets a `role` field:
16
-
17
- | Role | Who sees it | Examples |
18
- |------|-------------|---------|
19
- | `admin` | Admin key connections only | firestore_read, send_email, run_cron |
20
- | `user` | Authenticated users + admins | get_user, get_subscription, cancel_subscription |
21
- | `public` | Everyone (including unauthenticated) | generate_uuid, health_check |
22
-
23
- Admin sees ALL tools. User sees `user` + `public`. Unauthenticated sees only `public`. Defense-in-depth — even if someone calls an admin tool by name, the route still rejects with 403.
24
-
25
- ### Auth: How Users Connect (OAuth + Consumer's Website)
26
-
27
- **The key insight:** users already have an API key (`api.privateKey`) on their Firestore account doc. It's long-lived, already works with `assistant.authenticate()`, and already encodes identity + access level. This is the credential.
28
-
29
- **The UX:** Claude Code/Desktop opens a browser → user signs in on their familiar site → redirected back to Claude. Done. No copy-pasting tokens.
30
-
31
- **The OAuth flow (step by step):**
32
-
33
- ```
34
- 1. Claude discovers endpoints:
35
- GET /.well-known/oauth-authorization-server
36
- → { authorization_endpoint, token_endpoint, ... }
37
-
38
- 2. Claude opens browser to BEM authorize endpoint:
39
- GET /backend-manager/mcp/authorize?redirect_uri=CLAUDE_CALLBACK&state=STATE
40
-
41
- 3. BEM authorize checks the request:
42
- - If client_id === BACKEND_MANAGER_KEY → auto-redirect (admin, unchanged)
43
- - Otherwise → redirect to consumer's /token page:
44
- https://app.example.com/token?redirect_uri=CLAUDE_CALLBACK&state=STATE
45
-
46
- 4. User lands on their familiar website sign-in page.
47
- Signs in with Google / email+password / whatever.
48
- After sign-in, page gets Firebase ID token via webManager.auth().getIdToken()
49
-
50
- 5. Consumer's /token page redirects to CLAUDE_CALLBACK:
51
- CLAUDE_CALLBACK?code=FIREBASE_ID_TOKEN&state=STATE
52
-
53
- 6. Claude exchanges code for access token:
54
- POST /backend-manager/mcp/token
55
- { code: FIREBASE_ID_TOKEN }
56
-
57
- 7. BEM token endpoint:
58
- - If code === BACKEND_MANAGER_KEY → return it as access_token (admin, unchanged)
59
- - Otherwise → verify Firebase ID token with admin.auth().verifyIdToken()
60
- → look up user doc → return user's api.privateKey as access_token
61
-
62
- 8. Claude uses the API key for all future MCP requests:
63
- Authorization: Bearer {api.privateKey}
64
- ```
65
-
66
- **Why API key, not JWT:**
67
- - Firebase ID tokens expire in 1 hour — MCP connection would break constantly
68
- - The API key never expires (until regenerated)
69
- - `assistant.authenticate()` already handles API key → user lookup
70
- - User can revoke by regenerating from account settings
71
-
72
- **Why redirect to consumer's website instead of embedding Firebase Auth:**
73
- - User sees their familiar sign-in page (branded, trusted)
74
- - No need to embed Firebase Auth SDK in BEM's authorize HTML
75
- - UJM already has a `/token` page — just needs a small update to support `redirect_uri` param
76
- - Works with any auth provider the consumer has configured
77
-
78
- ### Auth Classification (Fast, No DB Call)
79
-
80
- `resolveAuthInfo(token)` classifies the Bearer token for tool filtering:
81
-
82
- - Token matches `BACKEND_MANAGER_KEY` → `role: 'admin'`
83
- - Any other non-empty token → `role: 'user'` (API key from the OAuth flow)
84
- - No token → `role: 'public'`
85
-
86
- No DB call needed — actual validation happens at the route level when a tool is called.
87
-
88
- ### Consumer MCP Tools
89
-
90
- Consumer tools live in a **single file**: `functions/mcp.js`. Keeps things simple since most consumer MCP tools are just route delegations pointing at routes already defined in `functions/routes/`.
91
-
92
- ```js
93
- // functions/mcp.js
94
- module.exports = [
95
- // Route delegation — points at an existing route (works on stdio + HTTP)
96
- {
97
- name: 'get_sponsorship',
98
- description: 'Get sponsorship details by ID',
99
- role: 'user',
100
- method: 'GET',
101
- path: 'sponsorship',
102
- inputSchema: {
103
- type: 'object',
104
- properties: {
105
- id: { type: 'string', description: 'Sponsorship ID' },
106
- },
107
- required: ['id'],
108
- },
109
- },
110
-
111
- // Handler mode — runs code directly (HTTP transport only, has full Manager context)
112
- {
113
- name: 'newsletter_stats',
114
- description: 'Get newsletter stats for the past N days',
115
- role: 'admin',
116
- inputSchema: {
117
- type: 'object',
118
- properties: {
119
- days: { type: 'number', description: 'Days to look back', default: 30 },
120
- },
121
- },
122
- handler: async ({ Manager, assistant, user, params, libraries }) => {
123
- const cutoff = Date.now() - (params.days || 30) * 86400000;
124
- const snapshot = await libraries.admin.firestore()
125
- .collection('newsletters')
126
- .where('metadata.created.timestampUNIX', '>=', Math.floor(cutoff / 1000))
127
- .get();
128
- return { total: snapshot.docs.length };
129
- },
130
- },
131
- ];
132
- ```
133
-
134
- Consumer tools with the same name as a built-in tool override it (same precedence as consumer routes).
135
-
136
- ## File Changes
137
-
138
- ### 1. `src/mcp/tools.js` — Add `role` to every tool
139
-
140
- | Tool | Role |
141
- |------|------|
142
- | `firestore_read/write/query` | `admin` |
143
- | `send_email`, `send_notification` | `admin` |
144
- | `sync_users`, `list_campaigns`, `create_campaign` | `admin` |
145
- | `get_stats`, `run_cron`, `create_post`, `create_backup`, `run_hook` | `admin` |
146
- | `get_user`, `get_subscription` | `user` |
147
- | `cancel_subscription`, `refund_payment` | `user` |
148
- | `generate_uuid`, `health_check` | `public` |
149
-
150
- ### 2. NEW: `src/mcp/utils.js` — Shared utilities
151
-
152
- - **`resolveAuthInfo(token)`** — classifies token → `{ role, authType, token }`. Admin key check is instant; everything else = user.
153
- - **`filterToolsByRole(tools, role)`** — admin→all, user→user+public, public→public only.
154
- - **`loadConsumerTools(cwd)`** — checks for `${cwd}/mcp.js`, `require()`s it if it exists, validates shape, returns array.
155
- - **`buildToolMap(builtinTools, consumerTools)`** — merges into a Map; consumer tools override same-name built-ins.
156
-
157
- ### 3. `src/mcp/client.js` — Support user auth tokens
158
-
159
- Extend constructor to accept `{ baseUrl, backendManagerKey, userToken }`.
160
-
161
- In `call()`:
162
- - Has `backendManagerKey` → current behavior (key in query/body)
163
- - Has `userToken` (no backendManagerKey) → put token in `Authorization: Bearer` header + `authenticationToken` query param for GET
164
- - Neither → unauthenticated request
165
-
166
- ### 4. `src/mcp/index.js` — Stdio server with role filtering
167
-
168
- - Import utils, call `resolveAuthInfo()` to determine role
169
- - Call `loadConsumerTools(options.cwd)` if `cwd` is provided
170
- - Merge + filter tools by role in `ListToolsRequestSchema` handler
171
- - In `CallToolRequestSchema`: `path`-based tools via BEMClient. Handler-only tools return error explaining they require HTTP transport.
172
- - Accept new `options.token` for user connections
173
-
174
- ### 5. `src/mcp/handler.js` — HTTP handler with OAuth user flow
175
-
176
- **`handleAuthorize()`** — the big change:
177
- - If `client_id` matches admin key → auto-redirect (current behavior, unchanged)
178
- - Otherwise → **redirect to consumer's website** for sign-in:
179
- - Build consumer auth URL from `Manager.config.brand.url` (or a configurable `mcp.authUrl` in backend-manager-config.json)
180
- - Redirect to: `{consumerUrl}/token?redirect_uri={originalRedirectUri}&state={state}`
181
- - The consumer's `/token` page handles sign-in, gets Firebase ID token, redirects back to Claude's callback
182
-
183
- **`handleToken()`** — exchanges Firebase ID token for API key:
184
- - If code matches admin key → return as `access_token` (unchanged)
185
- - Otherwise → treat code as Firebase ID token:
186
- 1. `admin.auth().verifyIdToken(code)` to get UID
187
- 2. `admin.firestore().doc('users/{uid}').get()` to get user doc
188
- 3. Return `user.api.privateKey` as the `access_token`
189
- 4. If user has no API key, generate one and save it
190
-
191
- **`handleMcpProtocol()`** — role-based tool filtering:
192
- - Extract Bearer token, call `resolveAuthInfo()`
193
- - Allow public connections (no 401 for empty tokens — just show public tools)
194
- - Discover + merge consumer tools (cached at module scope)
195
- - Filter tools by role in `ListToolsRequestSchema`
196
- - In `CallToolRequestSchema`:
197
- - `path`-based tools: BEMClient HTTP call (current behavior)
198
- - `handler`-based tools: execute directly with `{ Manager, assistant, user, params, libraries }`
199
-
200
- **Rename `isValidKey()` → `isAdminKey()`** for clarity.
201
-
202
- ### 6. `src/cli/commands/mcp.js` — New CLI flags
203
-
204
- - `--token` (`-t`): user's API key (for user-level connections)
205
- - Pass `cwd: functionsDir` to `startServer()` for consumer tool discovery
206
- - When `--token` is provided, create BEMClient with userToken instead of backendManagerKey
207
-
208
- ### 7. Consumer's UJM `/token` page — Small update (separate repo)
209
-
210
- The UJM `/token` page needs to support the MCP OAuth redirect flow:
211
- - Detect `redirect_uri` and `state` query params
212
- - After sign-in, get Firebase ID token via `webManager.auth().getIdToken()`
213
- - Redirect to `redirect_uri?code={idToken}&state={state}`
214
-
215
- This is a small addition to the existing `/token` page layout. Files:
216
- - `ultimate-jekyll-manager/src/defaults/dist/_layouts/blueprint/auth/token.html`
217
- - `ultimate-jekyll-manager/src/defaults/dist/_layouts/themes/classy/frontend/pages/auth/token.html`
218
-
219
- ### 8. `docs/mcp.md` — Documentation
220
-
221
- - Add "Roles" section explaining admin/user/public scoping
222
- - Add "User Authentication" section with the OAuth flow diagram
223
- - Add "Consumer MCP Tools" section with `functions/mcp.js` format + examples
224
- - Add CLI examples for user connections
225
- - Add guide for configuring the consumer auth URL
226
-
227
- ## Implementation Order
228
-
229
- 1. `src/mcp/utils.js` (new) — foundation utilities
230
- 2. `src/mcp/tools.js` — add `role` to all 19 tools
231
- 3. `src/mcp/client.js` — user token support
232
- 4. `src/mcp/index.js` — stdio role filtering + consumer tools
233
- 5. `src/cli/commands/mcp.js` — new CLI flags
234
- 6. `src/mcp/handler.js` — HTTP role filtering + OAuth user flow + consumer tool handlers
235
- 7. UJM `/token` page update (separate repo)
236
- 8. `docs/mcp.md` — documentation
237
-
238
- ## Verification
239
-
240
- 1. **Existing admin flow**: `npx mgr mcp` with `BACKEND_MANAGER_KEY` → all 19 tools listed, all callable
241
- 2. **User flow (stdio)**: `npx mgr mcp --token <api-key>` → only user+public tools listed
242
- 3. **Public flow**: `npx mgr mcp` (no key, no token) → only public tools listed
243
- 4. **Consumer tools**: Create `functions/mcp.js` in a consumer project → tools appear in listing
244
- 5. **HTTP OAuth flow**: Connect from Claude Code/Desktop → redirected to consumer site → sign in → redirected back → user tools available
245
- 6. **Token exchange**: POST to `/mcp/token` with Firebase ID token → returns API key as access_token
246
- 7. **Role enforcement**: User calling an admin tool by name → unknown tool error (tool not in filtered list)
247
- 8. **Run `npx mgr test`** to verify no regressions
@@ -1,97 +0,0 @@
1
- # Suspended Subscription Dead Zone
2
-
3
- ## Issue
4
-
5
- A user with a `suspended` subscription was trapped in a loop:
6
- - **Trying to subscribe** → "You already have a subscription. Please cancel your existing subscription before purchasing a new one." (400)
7
- - **Trying to cancel** → "No active paid subscription found" (400)
8
-
9
- No way out.
10
-
11
- ## Affected User
12
-
13
- - **UID:** `t9AeAe7QUhNXAUYRV1vUbOU0QVV2`
14
- - **Brand:** Somiibo
15
-
16
- The user had two stale subscriptions from different processors:
17
-
18
- | Field | Processor | Status | Resource ID | Last Webhook |
19
- |---|---|---|---|---|
20
- | `subscription` | PayPal | `suspended` | `I-Y41DMAGNWGP1` | `BILLING.SUBSCRIPTION.SUSPENDED` (2026-04-19) |
21
- | `plan` (legacy) | Chargebee | `suspended` | `169xhEU1pYXAt3dsj` | `subscription-profile-fixer` (2024-02-28) |
22
-
23
- PayPal suspended the subscription (likely failed payment) but never sent a `BILLING.SUBSCRIPTION.CANCELLED` webhook. PayPal doesn't auto-cancel suspended subscriptions — it leaves them in limbo until the user resolves payment or manually cancels in PayPal. The local status stayed `suspended` forever.
24
-
25
- ## Root Cause
26
-
27
- Asymmetric status gates between the intent and cancel endpoints:
28
-
29
- ### Intent gate (`POST /payments/intent`)
30
- ```js
31
- // Blocked if product is NOT basic AND status is NOT cancelled
32
- if (subProductId !== 'basic' && subStatus !== 'cancelled') { ... }
33
- ```
34
- - `active` → blocked (correct)
35
- - `suspended` → blocked (correct — user should cancel first)
36
- - `cancelled` → allowed (correct)
37
-
38
- ### Cancel gate (`POST /payments/cancel`)
39
- ```js
40
- // Blocked if status is NOT active
41
- if (subscription.status !== 'active') { ... }
42
- ```
43
- - `active` → allowed (correct)
44
- - `suspended` → **blocked** (the bug)
45
- - `cancelled` → blocked (correct — nothing to cancel)
46
-
47
- The intent gate treats `suspended` as "has a subscription" (must cancel first), but the cancel gate treats `suspended` as "no subscription to cancel." Dead zone.
48
-
49
- ## The Fix
50
-
51
- ### 1. Cancel gate now accepts `suspended`
52
-
53
- ```js
54
- // Before
55
- if (!subscription || subscription.status !== 'active' || ...)
56
-
57
- // After
58
- if (!subscription || (subscription.status !== 'active' && subscription.status !== 'suspended') || ...)
59
- ```
60
-
61
- ### 2. Fallback for dead processor subscriptions
62
-
63
- When cancelling a `suspended` subscription, the processor API call might fail — the subscription could be expired/deleted on the processor's side while our local state is stale. The cancel endpoint now catches processor errors for suspended subscriptions and directly resets the user doc:
64
-
65
- ```js
66
- if (subscription.status === 'suspended') {
67
- // Processor rejected — subscription is already dead on their end
68
- // Directly reset the user doc to cancelled
69
- await admin.firestore().doc(`users/${uid}`).set({
70
- subscription: {
71
- status: 'cancelled',
72
- product: { id: 'basic', name: 'Basic' },
73
- cancellation: { pending: false, date: { timestamp, timestampUNIX } },
74
- },
75
- }, { merge: true });
76
- }
77
- ```
78
-
79
- ### 3. New test coverage
80
-
81
- - Test account `cancel-suspended` with `status: 'suspended'` and test processor payment details
82
- - Test `allows-suspended-subscription` verifying the cancel endpoint accepts and processes suspended subscriptions
83
-
84
- ## Status Matrix (after fix)
85
-
86
- | Status | Can subscribe? | Can cancel? | Can delete account? |
87
- |---|---|---|---|
88
- | `active` (basic) | Yes | No (nothing to cancel) | Yes |
89
- | `active` (paid) | No (cancel first) | Yes | No |
90
- | `suspended` | No (cancel first) | **Yes** (fixed) | No |
91
- | `cancelled` | Yes | No (already cancelled) | Yes |
92
-
93
- ## Files Changed
94
-
95
- - `src/manager/routes/payments/cancel/post.js` — gate + fallback
96
- - `src/test/test-accounts.js` — `cancel-suspended` account
97
- - `test/routes/payments/cancel.js` — `allows-suspended-subscription` test
@@ -1,162 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Test Helper — direct provider lookups + removals via API
5
- *
6
- * Lets the live-test checklist verify SendGrid + Beehiiv state without
7
- * clicking around in dashboards. Reuses the same provider helpers that
8
- * BEM uses in production (findContact / removeContact).
9
- *
10
- * Usage (run from a consumer project's functions/ dir):
11
- *
12
- * node ../../../backend-manager/scripts/test-helper-providers.js find user@example.com
13
- * node ../../../backend-manager/scripts/test-helper-providers.js purge user@example.com
14
- *
15
- * Or symlink: `ln -s ../../../backend-manager/scripts/test-helper-providers.js ./prov`
16
- * then: `node ./prov find user@example.com`
17
- *
18
- * - find: prints whether the contact exists in SendGrid + Beehiiv (and the data shape).
19
- * - purge: removes the contact from BOTH providers (idempotent; safe if absent).
20
- *
21
- * The script picks up:
22
- * - SENDGRID_API_KEY, BEEHIIV_API_KEY from <cwd>/.env (functions/.env)
23
- * - marketing.campaigns.listId, marketing.newsletter.publicationId from <cwd>/backend-manager-config.json
24
- * - service-account from <cwd>/service-account.json
25
- *
26
- * Exit codes: 0 ok, 1 usage error, 2 provider error.
27
- */
28
-
29
- const path = require('path');
30
- const fs = require('fs');
31
-
32
- const cwd = process.cwd();
33
- const envPath = path.join(cwd, '.env');
34
- const configPath = path.join(cwd, 'backend-manager-config.json');
35
- const serviceAccountPath = path.join(cwd, 'service-account.json');
36
-
37
- // --- sanity checks ---
38
- if (!fs.existsSync(envPath)) {
39
- console.error(`✗ Missing ${envPath} — run from the consumer's functions/ dir`);
40
- process.exit(1);
41
- }
42
- if (!fs.existsSync(configPath)) {
43
- console.error(`✗ Missing ${configPath} — run from the consumer's functions/ dir`);
44
- process.exit(1);
45
- }
46
- if (!fs.existsSync(serviceAccountPath)) {
47
- console.error(`✗ Missing ${serviceAccountPath} — run from the consumer's functions/ dir`);
48
- process.exit(1);
49
- }
50
-
51
- // --- parse args ---
52
- const [, , cmd, email] = process.argv;
53
- const VALID_CMDS = ['find', 'purge'];
54
-
55
- if (!cmd || !VALID_CMDS.includes(cmd) || !email) {
56
- console.error('Usage:');
57
- console.error(' node test-helper-providers.js find <email>');
58
- console.error(' node test-helper-providers.js purge <email>');
59
- process.exit(1);
60
- }
61
-
62
- // --- bootstrap env (dotenv style, minimal) ---
63
- require('dotenv').config({ path: envPath });
64
-
65
- if (!process.env.SENDGRID_API_KEY) {
66
- console.error('✗ SENDGRID_API_KEY not set in .env');
67
- process.exit(1);
68
- }
69
- if (!process.env.BEEHIIV_API_KEY) {
70
- console.error('✗ BEEHIIV_API_KEY not set in .env');
71
- process.exit(1);
72
- }
73
-
74
- // --- bootstrap Manager so providers can read Manager.config.marketing.* ---
75
- // The providers require '../../../index.js' which is the Manager singleton.
76
- // We need to load BEM from the consumer's node_modules (not the BEM repo's own src)
77
- // so it picks up the consumer's config + service account.
78
- let Manager;
79
- try {
80
- const BackendManager = require(path.join(cwd, 'node_modules', 'backend-manager'));
81
- Manager = (new BackendManager()).init({}, { setupFunctionsLegacy: false, log: false });
82
- } catch (e) {
83
- console.error('✗ Failed to bootstrap Manager from consumer node_modules:', e.message);
84
- process.exit(2);
85
- }
86
-
87
- // --- load providers via the BEM Manager.libraries surface (preferred) or direct path ---
88
- const sendgridProviderPath = path.join(cwd, 'node_modules', 'backend-manager', 'src', 'manager', 'libraries', 'email', 'providers', 'sendgrid.js');
89
- const beehiivProviderPath = path.join(cwd, 'node_modules', 'backend-manager', 'src', 'manager', 'libraries', 'email', 'providers', 'beehiiv.js');
90
-
91
- const sendgrid = require(sendgridProviderPath);
92
- const beehiiv = require(beehiivProviderPath);
93
-
94
- // --- commands ---
95
- async function find(email) {
96
- console.log(`Looking up ${email} on both providers...`);
97
-
98
- const [sgContact, bhContact] = await Promise.all([
99
- sendgrid.findContact(email).catch(e => ({ _error: e.message })),
100
- beehiiv.findContact(email).catch(e => ({ _error: e.message })),
101
- ]);
102
-
103
- console.log('\n── SendGrid ──');
104
- if (sgContact?._error) {
105
- console.log(` ✗ error: ${sgContact._error}`);
106
- } else if (!sgContact) {
107
- console.log(' ⊘ not found');
108
- } else {
109
- console.log(` ✓ found — id=${sgContact.id || '?'}, email=${sgContact.email || '?'}`);
110
- }
111
-
112
- console.log('\n── Beehiiv ──');
113
- if (bhContact?._error) {
114
- console.log(` ✗ error: ${bhContact._error}`);
115
- } else if (!bhContact) {
116
- console.log(' ⊘ not found');
117
- } else {
118
- console.log(` ✓ found — id=${bhContact.id || '?'}, email=${bhContact.email || '?'}, status=${bhContact.status || '?'}`);
119
- }
120
-
121
- return { sgContact, bhContact };
122
- }
123
-
124
- async function purge(email) {
125
- console.log(`Removing ${email} from both providers...`);
126
-
127
- const [sgResult, bhResult] = await Promise.all([
128
- sendgrid.removeContact(email).catch(e => ({ _error: e.message })),
129
- beehiiv.removeContact(email).catch(e => ({ _error: e.message })),
130
- ]);
131
-
132
- console.log('\n── SendGrid ──');
133
- if (sgResult?._error) {
134
- console.log(` ✗ error: ${sgResult._error}`);
135
- } else {
136
- console.log(` ✓ ${JSON.stringify(sgResult)}`);
137
- }
138
-
139
- console.log('\n── Beehiiv ──');
140
- if (bhResult?._error) {
141
- console.log(` ✗ error: ${bhResult._error}`);
142
- } else {
143
- console.log(` ✓ ${JSON.stringify(bhResult)}`);
144
- }
145
-
146
- return { sgResult, bhResult };
147
- }
148
-
149
- // --- main ---
150
- (async () => {
151
- try {
152
- if (cmd === 'find') {
153
- await find(email);
154
- } else if (cmd === 'purge') {
155
- await purge(email);
156
- }
157
- process.exit(0);
158
- } catch (e) {
159
- console.error('✗ Unexpected error:', e);
160
- process.exit(2);
161
- }
162
- })();
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Fetches the latest disposable email domain list from GitHub and saves it as JSON.
5
- *
6
- * Source: https://github.com/disposable-email-domains/disposable-email-domains
7
- * (curated, ~5k domains, low false-positive rate)
8
- *
9
- * Run manually: node scripts/update-disposable-domains.js
10
- * Runs automatically on: npm prepublishOnly
11
- */
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- const SOURCE_URL = 'https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/main/disposable_email_blocklist.conf';
16
- const OUTPUT_PATH = path.join(__dirname, '..', 'src', 'manager', 'libraries', 'email', 'data', 'disposable-domains.json');
17
-
18
- async function main() {
19
- console.log('Fetching disposable domain list...');
20
-
21
- const response = await fetch(SOURCE_URL);
22
-
23
- if (!response.ok) {
24
- throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
25
- }
26
-
27
- const text = await response.text();
28
- const domains = text
29
- .trim()
30
- .split('\n')
31
- .map(d => d.trim().toLowerCase())
32
- .filter(Boolean);
33
-
34
- const unique = [...new Set(domains)].sort();
35
-
36
- fs.writeFileSync(OUTPUT_PATH, JSON.stringify(unique, null, 2) + '\n');
37
-
38
- console.log(`Updated disposable-domains.json: ${unique.length} domains`);
39
- }
40
-
41
- main().catch((e) => {
42
- console.warn('Warning: Failed to update disposable domains:', e.message);
43
- console.warn('Using existing list. Run manually later: node scripts/update-disposable-domains.js');
44
- });
package/templates/_.env DELETED
@@ -1,42 +0,0 @@
1
- # ========== Default Values ==========
2
- # GitHub
3
- GH_TOKEN=""
4
-
5
- # Backend Manager
6
- BACKEND_MANAGER_KEY=""
7
- BACKEND_MANAGER_WEBHOOK_KEY=""
8
- BACKEND_MANAGER_NAMESPACE=""
9
- BACKEND_MANAGER_OPENAI_API_KEY=""
10
- BACKEND_MANAGER_ANTHROPIC_API_KEY=""
11
-
12
- # AI
13
- OPENAI_API_KEY=""
14
- ANTHROPIC_API_KEY=""
15
- CLAUDE_CODE_OAUTH_TOKEN=""
16
-
17
- # Payment Processors
18
- PAYPAL_CLIENT_SECRET=""
19
- STRIPE_SECRET_KEY=""
20
- CHARGEBEE_API_KEY=""
21
- COINBASE_API_KEY=""
22
-
23
- # Analytics
24
- GOOGLE_ANALYTICS_SECRET=""
25
- META_ACCESS_TOKEN=""
26
- TIKTOK_ACCESS_TOKEN=""
27
-
28
- # Security
29
- CLOUDFLARE_TOKEN=""
30
- RECAPTCHA_SECRET_KEY=""
31
-
32
- # Email & Newsletter
33
- SENDGRID_API_KEY=""
34
- BEEHIIV_API_KEY=""
35
- NEVERBOUNCE_API_KEY=""
36
- ZEROBOUNCE_API_KEY=""
37
- UNSUBSCRIBE_HMAC_KEY=""
38
- APOLLO_API_KEY=""
39
-
40
- # ========== Custom Values ==========
41
- # Add your custom environment variables below this line
42
- # ...
@@ -1,60 +0,0 @@
1
- # ========== Default Values ==========
2
- # macOS
3
- .DS_Store
4
-
5
- # Logs
6
- logs
7
- *.log
8
- npm-debug.log*
9
- yarn-debug.log*
10
- yarn-error.log*
11
- firebase-debug.log*
12
-
13
- # Firebase cache
14
- .firebase/
15
-
16
- # Firebase config
17
- .firebaserc
18
-
19
- # Runtime data
20
- pids
21
- *.pid
22
- *.seed
23
- *.pid.lock
24
-
25
- # Coverage directory
26
- coverage
27
- .nyc_output
28
-
29
- # Dependency directories
30
- node_modules/
31
-
32
- # Optional npm cache directory
33
- .npm
34
-
35
- # Optional eslint cache
36
- .eslintcache
37
-
38
- # Output of 'npm pack'
39
- *.tgz
40
-
41
- # Yarn Integrity file
42
- .yarn-integrity
43
-
44
- # dotenv environment variables file
45
- .env*
46
-
47
- # BEM specific
48
- .runtimeconfig.json
49
- service-account*.json
50
- bem-reload-trigger.js
51
- .temp/
52
- _legacy
53
-
54
- # Root proxy (generated by setup)
55
- /package.json
56
- /package-lock.json
57
-
58
- # ========== Custom Values ==========
59
- # Add your custom ignore patterns below this line
60
- # ...