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 +1 -1
- package/scripts/update-disposable-domains.js +2 -2
- package/src/manager/events/auth/before-create.js +19 -5
- package/src/manager/events/auth/before-signin.js +18 -1
- package/src/manager/events/auth/on-create.js +15 -30
- package/src/manager/events/auth/on-delete.js +15 -30
- package/src/manager/events/auth/utils.js +29 -0
package/package.json
CHANGED
|
@@ -39,6 +39,6 @@ async function main() {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
main().catch((e) => {
|
|
42
|
-
console.
|
|
43
|
-
|
|
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}
|
|
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:
|
|
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}
|
|
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 =
|
|
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}
|
|
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
|
-
}
|
|
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 =
|
|
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}
|
|
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
|
-
}
|
|
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 };
|