backend-manager 5.0.187 → 5.0.189

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.187",
3
+ "version": "5.0.189",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -39,6 +39,6 @@ async function main() {
39
39
  }
40
40
 
41
41
  main().catch((e) => {
42
- console.error('Failed to update disposable domains:', e.message);
43
- process.exit(1);
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
44
  });
@@ -12,13 +12,30 @@ const MAX_SIGNUPS_PER_DAY = 2;
12
12
  * Why not create user doc here?
13
13
  * - Admin SDK (used for tests) does NOT trigger beforeUserCreated
14
14
  * - on-create fires for ALL user creations, making it more reliable
15
+ *
16
+ * Available parameters (1st gen):
17
+ *
18
+ * user (AuthUserRecord):
19
+ * uid, email, emailVerified, displayName, photoURL, phoneNumber, disabled,
20
+ * metadata: { creationTime, lastSignInTime },
21
+ * providerData: [{ uid, displayName, email, photoURL, providerId, phoneNumber }],
22
+ * passwordHash, passwordSalt, customClaims, tenantId, tokensValidAfterTime, multiFactor
23
+ *
24
+ * context (AuthEventContext):
25
+ * ipAddress, userAgent, locale, eventId, eventType, authType, resource, timestamp,
26
+ * additionalUserInfo: { providerId, profile, username, isNewUser, recaptchaScore, email, phoneNumber },
27
+ * credential: { providerId, signInMethod, claims, idToken, accessToken, refreshToken, expirationTime, secret } | null,
28
+ * emailType, smsType, params
29
+ *
30
+ * Note: recaptchaScore requires reCAPTCHA Enterprise (Google Cloud level), NOT the Firebase SMS fraud toggle.
31
+ * Note: credential tokens (idToken, accessToken, refreshToken) require opt-in via BlockingOptions.
15
32
  */
16
33
  module.exports = async ({ Manager, assistant, user, context, libraries }) => {
17
34
  const startTime = Date.now();
18
35
  const { functions } = libraries;
19
36
  const ipAddress = context.ipAddress || '';
20
37
 
21
- assistant.log(`beforeCreate: ${user.uid}`, { email: user.email, ip: ipAddress });
38
+ assistant.log(`beforeCreate: ${user.uid} (${user.email})`, user, context);
22
39
 
23
40
  // Block disposable email domains
24
41
  if (isDisposable(user.email)) {
@@ -54,8 +71,5 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
54
71
  usage.increment('signups');
55
72
  await usage.update();
56
73
 
57
- assistant.log(`beforeCreate: Rate limit passed for ${ipAddress}, allowing user creation (${Date.now() - startTime}ms)`);
58
-
59
- // Allow user creation to proceed
60
- // User doc will be created by on-create.js
74
+ assistant.log(`beforeCreate: Completed for ${user.uid} (${Date.now() - startTime}ms)`);
61
75
  };
@@ -6,12 +6,29 @@
6
6
  *
7
7
  * TODO: Add mailer.sync(uid) here with 1x/day rate limit to keep marketing
8
8
  * contact data (name, country, subscription fields) fresh between sessions.
9
+ *
10
+ * Available parameters (1st gen):
11
+ *
12
+ * user (AuthUserRecord):
13
+ * uid, email, emailVerified, displayName, photoURL, phoneNumber, disabled,
14
+ * metadata: { creationTime, lastSignInTime },
15
+ * providerData: [{ uid, displayName, email, photoURL, providerId, phoneNumber }],
16
+ * passwordHash, passwordSalt, customClaims, tenantId, tokensValidAfterTime, multiFactor
17
+ *
18
+ * context (AuthEventContext):
19
+ * ipAddress, userAgent, locale, eventId, eventType, authType, resource, timestamp,
20
+ * additionalUserInfo: { providerId, profile, username, isNewUser, recaptchaScore, email, phoneNumber },
21
+ * credential: { providerId, signInMethod, claims, idToken, accessToken, refreshToken, expirationTime, secret } | null,
22
+ * emailType, smsType, params
23
+ *
24
+ * Note: recaptchaScore requires reCAPTCHA Enterprise (Google Cloud level), NOT the Firebase SMS fraud toggle.
25
+ * Note: credential tokens (idToken, accessToken, refreshToken) require opt-in via BlockingOptions.
9
26
  */
10
27
  module.exports = async ({ Manager, assistant, user, context, libraries }) => {
11
28
  const startTime = Date.now();
12
29
  const { admin } = libraries;
13
30
 
14
- assistant.log(`beforeSignIn: ${user.uid}`, { email: user.email, ip: context.ipAddress });
31
+ assistant.log(`beforeSignIn: ${user.uid} (${user.email})`, user, context);
15
32
 
16
33
  const now = new Date();
17
34
 
@@ -1,5 +1,4 @@
1
- const MAX_RETRIES = 3;
2
- const RETRY_DELAY_MS = 1000;
1
+ const { retryWrite, MAX_RETRIES } = require('./utils.js');
3
2
 
4
3
  /**
5
4
  * onCreate - Create user doc
@@ -16,12 +15,23 @@ const RETRY_DELAY_MS = 1000;
16
15
  *
17
16
  * Non-critical work (welcome emails, marketing contact) is handled
18
17
  * by the user/signup endpoint, which the frontend calls after account creation.
18
+ *
19
+ * Available parameters (1st gen):
20
+ *
21
+ * user (UserRecord — firebase-admin):
22
+ * uid, email, emailVerified, displayName, photoURL, phoneNumber, disabled,
23
+ * metadata: { creationTime, lastSignInTime, lastRefreshTime },
24
+ * providerData: [{ uid, displayName, email, photoURL, providerId, phoneNumber }],
25
+ * passwordHash, passwordSalt, customClaims, tenantId, tokensValidAfterTime, multiFactor
26
+ *
27
+ * context (EventContext — NOT AuthEventContext, no ipAddress/userAgent/locale):
28
+ * eventId, eventType, timestamp, resource: { service, name }, params
19
29
  */
20
30
  module.exports = async ({ Manager, assistant, user, context, libraries }) => {
21
31
  const startTime = Date.now();
22
32
  const { admin } = libraries;
23
33
 
24
- assistant.log(`onCreate: ${user.uid}`, { email: user.email });
34
+ assistant.log(`onCreate: ${user.uid} (${user.email})`, user, context);
25
35
 
26
36
  // Skip anonymous users
27
37
  if (user.providerData?.every(p => p.providerId === 'anonymous')) {
@@ -66,9 +76,9 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
66
76
 
67
77
  // Write user doc with retry
68
78
  try {
69
- await retryWrite(assistant, async () => {
79
+ await retryWrite(assistant, 'onCreate', async () => {
70
80
  await admin.firestore().doc(`users/${user.uid}`).set(userRecord);
71
- }, MAX_RETRIES, RETRY_DELAY_MS);
81
+ });
72
82
 
73
83
  assistant.log(`onCreate: Successfully created user doc for ${user.uid} (${Date.now() - startTime}ms)`);
74
84
  } catch (error) {
@@ -102,28 +112,3 @@ function extractProviderName(user) {
102
112
  last: parts.slice(1).join(' ') || null,
103
113
  };
104
114
  }
105
-
106
- /**
107
- * Retry a function up to maxRetries times with exponential backoff
108
- */
109
- async function retryWrite(assistant, fn, maxRetries, delayMs) {
110
- let lastError;
111
-
112
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
113
- try {
114
- await fn();
115
- return; // Success
116
- } catch (error) {
117
- lastError = error;
118
- assistant.error(`onCreate: Write attempt ${attempt}/${maxRetries} failed:`, error);
119
-
120
- if (attempt < maxRetries) {
121
- const delay = delayMs * Math.pow(2, attempt - 1); // Exponential backoff: 1s, 2s, 4s
122
- assistant.log(`onCreate: Retrying in ${delay}ms...`);
123
- await new Promise(resolve => setTimeout(resolve, delay));
124
- }
125
- }
126
- }
127
-
128
- throw lastError; // All retries failed
129
- }
@@ -1,5 +1,4 @@
1
- const MAX_RETRIES = 3;
2
- const RETRY_DELAY_MS = 1000;
1
+ const { retryWrite, MAX_RETRIES } = require('./utils.js');
3
2
 
4
3
  /**
5
4
  * onDelete - Delete user doc
@@ -11,12 +10,23 @@ const RETRY_DELAY_MS = 1000;
11
10
  * - Checks if user doc exists before attempting delete
12
11
  * - Retries up to 3 times with exponential backoff on failure
13
12
  * - Logs timing for performance monitoring
13
+ *
14
+ * Available parameters (1st gen):
15
+ *
16
+ * user (UserRecord — firebase-admin):
17
+ * uid, email, emailVerified, displayName, photoURL, phoneNumber, disabled,
18
+ * metadata: { creationTime, lastSignInTime, lastRefreshTime },
19
+ * providerData: [{ uid, displayName, email, photoURL, providerId, phoneNumber }],
20
+ * passwordHash, passwordSalt, customClaims, tenantId, tokensValidAfterTime, multiFactor
21
+ *
22
+ * context (EventContext — NOT AuthEventContext, no ipAddress/userAgent/locale):
23
+ * eventId, eventType, timestamp, resource: { service, name }, params
14
24
  */
15
25
  module.exports = async ({ Manager, assistant, user, context, libraries }) => {
16
26
  const startTime = Date.now();
17
27
  const { admin } = libraries;
18
28
 
19
- assistant.log(`onDelete: ${user.uid}`, { email: user.email });
29
+ assistant.log(`onDelete: ${user.uid} (${user.email})`, user, context);
20
30
 
21
31
  // Check if user doc exists before attempting delete
22
32
  const existingDoc = await admin.firestore().doc(`users/${user.uid}`)
@@ -33,9 +43,9 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
33
43
 
34
44
  // Delete user doc with retry
35
45
  try {
36
- await retryWrite(assistant, async () => {
46
+ await retryWrite(assistant, 'onDelete', async () => {
37
47
  await admin.firestore().doc(`users/${user.uid}`).delete();
38
- }, MAX_RETRIES, RETRY_DELAY_MS);
48
+ });
39
49
 
40
50
  assistant.log(`onDelete: Successfully deleted user doc for ${user.uid}`);
41
51
  } catch (error) {
@@ -62,28 +72,3 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
62
72
 
63
73
  assistant.log(`onDelete: Completed for ${user.uid} (${Date.now() - startTime}ms)`);
64
74
  };
65
-
66
- /**
67
- * Retry a function up to maxRetries times with exponential backoff
68
- */
69
- async function retryWrite(assistant, fn, maxRetries, delayMs) {
70
- let lastError;
71
-
72
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
73
- try {
74
- await fn();
75
- return; // Success
76
- } catch (error) {
77
- lastError = error;
78
- assistant.error(`onDelete: Write attempt ${attempt}/${maxRetries} failed:`, error);
79
-
80
- if (attempt < maxRetries) {
81
- const delay = delayMs * Math.pow(2, attempt - 1); // Exponential backoff: 1s, 2s, 4s
82
- assistant.log(`onDelete: Retrying in ${delay}ms...`);
83
- await new Promise(resolve => setTimeout(resolve, delay));
84
- }
85
- }
86
- }
87
-
88
- throw lastError; // All retries failed
89
- }
@@ -0,0 +1,29 @@
1
+ const MAX_RETRIES = 3;
2
+ const RETRY_DELAY_MS = 1000;
3
+
4
+ /**
5
+ * Retry a function up to maxRetries times with exponential backoff
6
+ */
7
+ async function retryWrite(assistant, tag, fn) {
8
+ let lastError;
9
+
10
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
11
+ try {
12
+ await fn();
13
+ return; // Success
14
+ } catch (error) {
15
+ lastError = error;
16
+ assistant.error(`${tag}: Write attempt ${attempt}/${MAX_RETRIES} failed:`, error);
17
+
18
+ if (attempt < MAX_RETRIES) {
19
+ const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1); // Exponential backoff: 1s, 2s, 4s
20
+ assistant.log(`${tag}: Retrying in ${delay}ms...`);
21
+ await new Promise(resolve => setTimeout(resolve, delay));
22
+ }
23
+ }
24
+ }
25
+
26
+ throw lastError; // All retries failed
27
+ }
28
+
29
+ module.exports = { retryWrite, MAX_RETRIES };