backend-manager 5.0.189 → 5.0.191

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/CLAUDE.md CHANGED
@@ -65,24 +65,32 @@ src/
65
65
  stripe.js # Stripe SDK init, fetchResource, toUnified*, resolvePriceId
66
66
  paypal.js # PayPal fetchResource, toUnified* (custom_id parsing)
67
67
  test.js # Test processor (delegates to Stripe shapes)
68
- functions/core/ # Built-in functions
68
+ events/ # All event-driven code
69
+ auth/ # Auth event handlers (hookable)
70
+ before-create.js # Disposable email blocking + IP rate limiting
71
+ before-signin.js # Activity update + sign-in analytics
72
+ on-create.js # User doc creation
73
+ on-delete.js # User doc deletion + marketing cleanup
74
+ utils.js # Shared utilities (retryWrite, runAuthHook)
75
+ cron/ # Cron job runners
76
+ runner.js # Shared cron job runner (BEM + consumer hooks)
77
+ daily.js # Daily cron entry point
78
+ daily/{job}.js # Individual daily cron jobs
79
+ frequent.js # Frequent cron entry point
80
+ frequent/{job}.js # Individual frequent cron jobs
81
+ firestore/ # Firestore triggers
82
+ payments-webhooks/ # Webhook processing pipeline
83
+ on-write.js # Orchestrator: fetch→transform→transition→write
84
+ analytics.js # Payment analytics tracking (GA4, Meta, TikTok)
85
+ transitions/ # State transition detection + handlers
86
+ index.js # Transition detection logic
87
+ send-email.js # Shared email helper for handlers
88
+ subscription/ # Subscription transition handlers
89
+ one-time/ # One-time payment transition handlers
90
+ functions/core/ # Built-in functions
69
91
  actions/
70
- api.js # Main bm_api handler
71
- api/{category}/{action}.js # API command handlers
72
- events/
73
- auth/ # Auth event handlers
74
- firestore/ # Firestore triggers
75
- payments-webhooks/ # Webhook processing pipeline
76
- on-write.js # Orchestrator: fetch→transform→transition→write
77
- analytics.js # Payment analytics tracking (GA4, Meta, TikTok)
78
- transitions/ # State transition detection + handlers
79
- index.js # Transition detection logic
80
- send-email.js # Shared email helper for handlers
81
- subscription/ # Subscription transition handlers
82
- one-time/ # One-time payment transition handlers
83
- cron/
84
- daily.js # Daily cron runner
85
- daily/{job}.js # Individual cron jobs
92
+ api.js # Main bm_api handler
93
+ api/{category}/{action}.js # API command handlers
86
94
  routes/ # Built-in routes
87
95
  admin/
88
96
  post/ # POST /admin/post - Create blog posts via GitHub
@@ -142,6 +150,11 @@ functions/
142
150
  {endpoint}/
143
151
  index.js # Schema definition
144
152
  hooks/
153
+ auth/
154
+ before-create.js # Custom pre-signup checks (can block)
155
+ before-signin.js # Custom pre-signin checks (can block)
156
+ on-create.js # Post-signup side effects (non-blocking)
157
+ on-delete.js # Post-deletion side effects (non-blocking)
145
158
  cron/
146
159
  daily/
147
160
  {job}.js # Custom daily jobs
@@ -416,6 +429,80 @@ Job.prototype.main = function () {
416
429
  module.exports = Job;
417
430
  ```
418
431
 
432
+ ### Auth Hooks (Consumer Project)
433
+
434
+ Auth hooks let consumer projects inject custom logic into BEM's auth event lifecycle. BEM runs its core handler first, then looks for a matching hook at `hooks/auth/{event-name}.js`.
435
+
436
+ | Hook | File | Behavior |
437
+ |------|------|----------|
438
+ | `before-create` | `hooks/auth/before-create.js` | Runs after BEM's disposable email + rate limit checks. **Can throw `HttpsError` to block signup.** |
439
+ | `before-signin` | `hooks/auth/before-signin.js` | Runs after BEM's activity update. **Can throw `HttpsError` to block sign-in.** |
440
+ | `on-create` | `hooks/auth/on-create.js` | Runs after BEM creates the user doc. **Non-blocking** — errors are caught and logged. |
441
+ | `on-delete` | `hooks/auth/on-delete.js` | Runs after BEM deletes the user doc. **Non-blocking** — errors are caught and logged. |
442
+
443
+ Hook signature (same as BEM's internal handlers):
444
+ ```javascript
445
+ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
446
+ // user: AuthUserRecord (uid, email, providerData, etc.)
447
+ // context: AuthEventContext for blocking functions (ipAddress, userAgent, additionalUserInfo)
448
+ // EventContext for triggers (eventId, eventType, timestamp — no IP/userAgent)
449
+ // libraries: { admin, functions, ... }
450
+ };
451
+ ```
452
+
453
+ #### Blocking hook example (before-create)
454
+
455
+ ```javascript
456
+ // hooks/auth/before-create.js — Only allow Google OAuth signups
457
+ const ENFORCE = true;
458
+
459
+ const ALLOWED_PROVIDERS = ['google.com'];
460
+
461
+ module.exports = async ({ assistant, user, context, libraries }) => {
462
+ if (!ENFORCE) { return; }
463
+
464
+ const { functions } = libraries;
465
+ const provider = context.additionalUserInfo?.providerId;
466
+
467
+ if (!ALLOWED_PROVIDERS.includes(provider)) {
468
+ assistant.error(`hook/before-create: Blocked provider '${provider}' for ${user.email}`);
469
+ throw new functions.auth.HttpsError('permission-denied', 'Please sign up with Google.');
470
+ }
471
+ };
472
+ ```
473
+
474
+ #### Non-blocking hook example (on-create)
475
+
476
+ ```javascript
477
+ // hooks/auth/on-create.js — Auto-delete spam referrals
478
+ const powertools = require('node-powertools');
479
+
480
+ const ENFORCE = true;
481
+ const BLOCKED_AFFILIATE_CODES = ['iLvQjmvm'];
482
+
483
+ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
484
+ if (!ENFORCE) { return; }
485
+
486
+ const { admin } = libraries;
487
+ const uid = user.uid;
488
+
489
+ // Poll until signup route attaches attribution.affiliate.code
490
+ let referredBy = null;
491
+
492
+ await powertools.poll(async () => {
493
+ const userDoc = await admin.firestore().doc(`users/${uid}`).get().catch(() => null);
494
+ if (!userDoc?.exists) { return true; }
495
+ referredBy = userDoc.data()?.attribution?.affiliate?.code;
496
+ return !!referredBy;
497
+ }, { interval: 10000, timeout: 60000 }).catch(() => {});
498
+
499
+ if (!referredBy || !BLOCKED_AFFILIATE_CODES.includes(referredBy)) { return; }
500
+
501
+ // Delete spam account (triggers on-delete for cleanup)
502
+ await admin.auth().deleteUser(uid).catch(e => assistant.error('Delete failed:', e));
503
+ };
504
+ ```
505
+
419
506
  ## Common Operations
420
507
 
421
508
  ### Authenticate User
@@ -482,7 +569,9 @@ Manager.handlers.bm_api = function (mod, position) {
482
569
  | Schemas | `schemas/{name}/` | `index.js` or `{method}.js` |
483
570
  | API Commands | `actions/api/{category}/` | `{action}.js` |
484
571
  | Auth Events | `events/auth/` | `{event}.js` |
485
- | Cron Jobs | `cron/daily/` or `hooks/cron/daily/` | `{job}.js` |
572
+ | Auth Hooks (consumer) | `hooks/auth/` | `{event}.js` |
573
+ | Cron Jobs (BEM) | `events/cron/daily/` | `{job}.js` |
574
+ | Cron Jobs (consumer) | `hooks/cron/daily/` | `{job}.js` |
486
575
 
487
576
  ## Admin Post Route
488
577
 
@@ -1302,6 +1391,12 @@ marketing: {
1302
1391
  | Rate limiting | `src/manager/helpers/usage.js` |
1303
1392
  | User properties + schema | `src/manager/helpers/user.js` |
1304
1393
  | Batch utilities | `src/manager/helpers/utilities.js` |
1394
+ | Auth: before-create | `src/manager/events/auth/before-create.js` |
1395
+ | Auth: before-signin | `src/manager/events/auth/before-signin.js` |
1396
+ | Auth: on-create | `src/manager/events/auth/on-create.js` |
1397
+ | Auth: on-delete | `src/manager/events/auth/on-delete.js` |
1398
+ | Auth: shared utilities | `src/manager/events/auth/utils.js` |
1399
+ | Cron runner | `src/manager/events/cron/runner.js` |
1305
1400
  | Main API handler | `src/manager/functions/core/actions/api.js` |
1306
1401
  | Config template | `templates/backend-manager-config.json` |
1307
1402
  | CLI entry | `src/cli/index.js` |
package/TODO-2.md CHANGED
@@ -23,6 +23,9 @@ waht about when they request a cancel
23
23
  Read cancellation-requested.js
24
24
  The category is order/cancellation-requested (line 13).
25
25
 
26
+ ---
27
+ MIRROR settigns in BEM JSON so that usage reset can properly get MIRRED DOCS liek slapform forms or chatsy agents DOCS
28
+
26
29
  ---
27
30
  GHOSTII REVAMP
28
31
  * better logic for generating posts. better model? claude?
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.189",
3
+ "version": "5.0.191",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -1,4 +1,5 @@
1
1
  const { isDisposable } = require('../../libraries/email/validation.js');
2
+ const { runAuthHook } = require('./utils.js');
2
3
 
3
4
  const ERROR_TOO_MANY_ATTEMPTS = 'Unable to create account at this time. Please try again later.';
4
5
  const ERROR_DISPOSABLE_EMAIL = 'This email domain is not allowed. Please use a different email address.';
@@ -71,5 +72,8 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
71
72
  usage.increment('signups');
72
73
  await usage.update();
73
74
 
75
+ // Run consumer hook (can throw HttpsError to block signup)
76
+ await runAuthHook('before-create', { Manager, assistant, user, context, libraries });
77
+
74
78
  assistant.log(`beforeCreate: Completed for ${user.uid} (${Date.now() - startTime}ms)`);
75
79
  };
@@ -24,6 +24,8 @@
24
24
  * Note: recaptchaScore requires reCAPTCHA Enterprise (Google Cloud level), NOT the Firebase SMS fraud toggle.
25
25
  * Note: credential tokens (idToken, accessToken, refreshToken) require opt-in via BlockingOptions.
26
26
  */
27
+ const { runAuthHook } = require('./utils.js');
28
+
27
29
  module.exports = async ({ Manager, assistant, user, context, libraries }) => {
28
30
  const startTime = Date.now();
29
31
  const { admin } = libraries;
@@ -60,5 +62,8 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
60
62
  assistant.log(`beforeSignIn: Updated user activity`);
61
63
  }
62
64
 
65
+ // Run consumer hook (can throw HttpsError to block sign-in)
66
+ await runAuthHook('before-signin', { Manager, assistant, user, context, libraries });
67
+
63
68
  assistant.log(`beforeSignIn: Completed for ${user.uid} (${Date.now() - startTime}ms)`);
64
69
  };
@@ -1,4 +1,4 @@
1
- const { retryWrite, MAX_RETRIES } = require('./utils.js');
1
+ const { retryWrite, runAuthHook, MAX_RETRIES } = require('./utils.js');
2
2
 
3
3
  /**
4
4
  * onCreate - Create user doc
@@ -87,6 +87,11 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
87
87
  // Don't reject - the user was already created in Auth
88
88
  // The user/signup endpoint will handle creating the doc if it's missing
89
89
  }
90
+
91
+ // Run consumer hook (non-blocking — errors logged but don't fail)
92
+ await runAuthHook('on-create', { Manager, assistant, user, context, libraries }).catch(e => {
93
+ assistant.error('onCreate: Consumer hook error:', e);
94
+ });
90
95
  };
91
96
 
92
97
  /**
@@ -1,4 +1,4 @@
1
- const { retryWrite, MAX_RETRIES } = require('./utils.js');
1
+ const { retryWrite, runAuthHook, MAX_RETRIES } = require('./utils.js');
2
2
 
3
3
  /**
4
4
  * onDelete - Delete user doc
@@ -70,5 +70,10 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
70
70
  uuid: user.uid,
71
71
  }).event('user_delete', {});
72
72
 
73
+ // Run consumer hook (non-blocking — errors logged but don't fail)
74
+ await runAuthHook('on-delete', { Manager, assistant, user, context, libraries }).catch(e => {
75
+ assistant.error('onDelete: Consumer hook error:', e);
76
+ });
77
+
73
78
  assistant.log(`onDelete: Completed for ${user.uid} (${Date.now() - startTime}ms)`);
74
79
  };
@@ -1,3 +1,5 @@
1
+ const jetpack = require('fs-jetpack');
2
+
1
3
  const MAX_RETRIES = 3;
2
4
  const RETRY_DELAY_MS = 1000;
3
5
 
@@ -26,4 +28,47 @@ async function retryWrite(assistant, tag, fn) {
26
28
  throw lastError; // All retries failed
27
29
  }
28
30
 
29
- module.exports = { retryWrite, MAX_RETRIES };
31
+ /**
32
+ * Run consumer auth hooks from hooks/auth/{eventName}.js
33
+ *
34
+ * Similar to how cron discovers hooks at hooks/cron/{schedule}/*.js,
35
+ * auth hooks are discovered at hooks/auth/{eventName}.js in the consumer project.
36
+ *
37
+ * For blocking functions (before-create, before-signin):
38
+ * - Hook can throw HttpsError to block the operation
39
+ * - Hook runs AFTER BEM's core checks (disposable email, rate limiting, etc.)
40
+ *
41
+ * For trigger functions (on-create, on-delete):
42
+ * - Hook errors are logged but don't block the operation
43
+ *
44
+ * Hook signature:
45
+ * module.exports = async ({ Manager, assistant, user, context, libraries }) => { ... }
46
+ *
47
+ * Consumer project structure:
48
+ * functions/
49
+ * hooks/
50
+ * auth/
51
+ * before-create.js — runs after BEM checks, can block signup
52
+ * before-signin.js — runs after BEM signin logic, can block signin
53
+ * on-create.js — runs after BEM creates user doc
54
+ * on-delete.js — runs after BEM deletes user doc
55
+ */
56
+ async function runAuthHook(eventName, args) {
57
+ const { Manager, assistant } = args;
58
+ const hookPath = `${Manager.cwd}/hooks/auth/${eventName}.js`;
59
+
60
+ // Check if hook file exists
61
+ if (!jetpack.exists(hookPath)) {
62
+ return;
63
+ }
64
+
65
+ assistant.log(`${eventName}: Running consumer hook @ ${hookPath}`);
66
+
67
+ // Load and execute — passes the same args object the BEM handler received
68
+ const hook = require(hookPath);
69
+ await hook(args);
70
+
71
+ assistant.log(`${eventName}: Consumer hook completed`);
72
+ }
73
+
74
+ module.exports = { retryWrite, runAuthHook, MAX_RETRIES };
@@ -22,7 +22,7 @@ Module.prototype.main = function () {
22
22
  }
23
23
 
24
24
  // Run the cron job
25
- Manager._process((new (require(`../../../cron/${payload.data.payload.id}.js`))()).init(Manager, { context: {}, }))
25
+ Manager._process((new (require(`${Manager.rootDirectory}/events/cron/${payload.data.payload.id}.js`))()).init(Manager, { context: {}, }))
26
26
  .then((res) => {
27
27
  return resolve({data: res});
28
28
  })
@@ -14,8 +14,8 @@ const util = require('util');
14
14
  const core = './functions/core';
15
15
  const wrappers = './functions/wrappers';
16
16
  const _legacy = './functions/_legacy';
17
- const cron = path.resolve(__dirname, './cron');
18
17
  const events = path.resolve(__dirname, './events');
18
+ const cron = path.resolve(events, './cron');
19
19
 
20
20
  const BEM_CONFIG_TEMPLATE_PATH = path.resolve(__dirname, '../../templates/backend-manager-config.json');
21
21
  const BEM_PACKAGE = require('../../package.json');
@@ -2,7 +2,7 @@
2
2
  * Abandoned cart reminder configuration (SSOT)
3
3
  *
4
4
  * Used by:
5
- * - cron/frequent/abandoned-carts.js (processing reminders)
5
+ * - events/cron/frequent/abandoned-carts.js (processing reminders)
6
6
  * - Client-side checkout page (creating cart doc with first delay)
7
7
  */
8
8
  module.exports = {
@@ -22,7 +22,7 @@ module.exports = async ({ assistant, Manager, user, settings, analytics }) => {
22
22
  assistant.log('Running cron job:', settings.id);
23
23
 
24
24
  // Run the cron job
25
- const cronPath = require('path').resolve(__dirname, `../../../cron/${settings.id}.js`);
25
+ const cronPath = `${Manager.rootDirectory}/events/cron/${settings.id}.js`;
26
26
  const cronHandler = require(cronPath);
27
27
  const result = await cronHandler({ Manager, assistant, context: {}, libraries: Manager.libraries }).catch(e => e);
28
28
 
File without changes
File without changes
File without changes