bulltrackers-module 1.0.615 → 1.0.617
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Firestore helper functions for fetching data from collections
|
|
2
2
|
const { FieldValue } = require('@google-cloud/firestore');
|
|
3
3
|
const { dispatchSyncRequest } = require('../task_engine_helper.js');
|
|
4
|
+
const crypto = require('crypto');
|
|
4
5
|
|
|
5
6
|
// 1. Fetch latest stored snapshots of user data from a user-centric collection
|
|
6
7
|
|
|
@@ -525,12 +526,48 @@ const fetchUserVerificationData = async (firestore, userId) => {
|
|
|
525
526
|
}
|
|
526
527
|
};
|
|
527
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Hash email for use as document ID in lookup collection
|
|
531
|
+
* Uses SHA-256 for consistent hashing
|
|
532
|
+
*/
|
|
533
|
+
const hashEmail = (email) => {
|
|
534
|
+
const normalizedEmail = String(email).toLowerCase().trim();
|
|
535
|
+
return crypto.createHash('sha256').update(normalizedEmail).digest('hex');
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Create or update email-to-CID lookup document for O(1) lookups
|
|
540
|
+
* This is called when an email is verified or when migration finds a match
|
|
541
|
+
*/
|
|
542
|
+
const createEmailLookup = async (firestore, email, cid) => {
|
|
543
|
+
try {
|
|
544
|
+
const hashedEmail = hashEmail(email);
|
|
545
|
+
const lookupRef = firestore.collection('sys_user_lookup').doc(hashedEmail);
|
|
546
|
+
|
|
547
|
+
await lookupRef.set({
|
|
548
|
+
cid: Number(cid),
|
|
549
|
+
emailHash: hashedEmail,
|
|
550
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
551
|
+
updatedAt: FieldValue.serverTimestamp(),
|
|
552
|
+
}, { merge: true });
|
|
553
|
+
|
|
554
|
+
console.log(`[createEmailLookup] Created lookup document for email hash ${hashedEmail} -> CID ${cid}`);
|
|
555
|
+
} catch (error) {
|
|
556
|
+
console.error(`[createEmailLookup] Error creating lookup for ${email}:`, error);
|
|
557
|
+
// Don't throw - lookup creation is non-critical, fallback will work
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
528
561
|
/**
|
|
529
562
|
* Lookup CID by email (server-side only for security)
|
|
530
|
-
*
|
|
531
|
-
*
|
|
563
|
+
*
|
|
564
|
+
* PERFORMANCE: Uses O(1) lookup collection first, falls back to O(N) search if not found
|
|
565
|
+
* MIGRATION: When fallback finds a match, creates lookup document for future O(1) access
|
|
566
|
+
*
|
|
567
|
+
* NOTE: Multiple Firebase accounts (different UIDs) can access the same CID
|
|
568
|
+
* if their emails are in the verification document's email array.
|
|
532
569
|
*/
|
|
533
|
-
const lookupCidByEmail = async (firestore, userEmail) => {
|
|
570
|
+
const lookupCidByEmail = async (firestore, userEmail, firebaseUid = null) => {
|
|
534
571
|
if (!userEmail) {
|
|
535
572
|
throw new Error('Email is required for lookup');
|
|
536
573
|
}
|
|
@@ -538,8 +575,60 @@ const lookupCidByEmail = async (firestore, userEmail) => {
|
|
|
538
575
|
try {
|
|
539
576
|
// Normalize email for comparison
|
|
540
577
|
const normalizedEmail = String(userEmail).toLowerCase().trim();
|
|
578
|
+
const hashedEmail = hashEmail(normalizedEmail);
|
|
579
|
+
|
|
580
|
+
// STEP 1: Try O(1) lookup from denormalized collection
|
|
581
|
+
const lookupRef = firestore.collection('sys_user_lookup').doc(hashedEmail);
|
|
582
|
+
const lookupDoc = await lookupRef.get();
|
|
583
|
+
|
|
584
|
+
if (lookupDoc.exists) {
|
|
585
|
+
const lookupData = lookupDoc.data();
|
|
586
|
+
const cid = lookupData.cid;
|
|
587
|
+
|
|
588
|
+
// Fetch verification data to return full user info
|
|
589
|
+
const verificationRef = firestore.collection('SignedInUsers').doc(String(cid)).collection('verification').doc('data');
|
|
590
|
+
const verificationDoc = await verificationRef.get();
|
|
591
|
+
|
|
592
|
+
if (verificationDoc.exists) {
|
|
593
|
+
const verificationData = verificationDoc.data();
|
|
594
|
+
|
|
595
|
+
// Verify email is still in the verification document (safety check)
|
|
596
|
+
const emails = Array.isArray(verificationData.email)
|
|
597
|
+
? verificationData.email
|
|
598
|
+
: (verificationData.email ? [verificationData.email] : []);
|
|
599
|
+
const normalizedEmails = emails.map(e => String(e).toLowerCase().trim());
|
|
600
|
+
|
|
601
|
+
if (normalizedEmails.includes(normalizedEmail)) {
|
|
602
|
+
// Optional: Validate Firebase UID if provided
|
|
603
|
+
if (firebaseUid && verificationData.firebaseUids && Array.isArray(verificationData.firebaseUids)) {
|
|
604
|
+
if (!verificationData.firebaseUids.includes(firebaseUid)) {
|
|
605
|
+
console.warn(`[lookupCidByEmail] Email ${userEmail} matches CID ${cid} but Firebase UID ${firebaseUid} not in firebaseUids array`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Return result from O(1) lookup
|
|
610
|
+
return {
|
|
611
|
+
cid: verificationData.etoroCID || Number(cid),
|
|
612
|
+
accountSetupComplete: verificationData.accountSetupComplete || false,
|
|
613
|
+
etoroUsername: verificationData.etoroUsername,
|
|
614
|
+
displayName: verificationData.displayName,
|
|
615
|
+
photoURL: verificationData.photoURL,
|
|
616
|
+
verifiedAt: verificationData.verifiedAt,
|
|
617
|
+
setupCompletedAt: verificationData.setupCompletedAt,
|
|
618
|
+
};
|
|
619
|
+
} else {
|
|
620
|
+
// Email not in verification doc - lookup is stale, will fall through to migration
|
|
621
|
+
console.warn(`[lookupCidByEmail] Lookup document exists but email ${normalizedEmail} not in verification doc for CID ${cid}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// STEP 2: Fallback to O(N) search (migration path)
|
|
627
|
+
// This happens when:
|
|
628
|
+
// 1. Lookup document doesn't exist yet (new user or not migrated)
|
|
629
|
+
// 2. Lookup document exists but email not in verification (stale data)
|
|
630
|
+
console.log(`[lookupCidByEmail] Lookup document not found for ${normalizedEmail}, performing O(N) search (migration)`);
|
|
541
631
|
|
|
542
|
-
// Get all SignedInUsers documents
|
|
543
632
|
const signedInUsersSnapshot = await firestore.collection('SignedInUsers').get();
|
|
544
633
|
|
|
545
634
|
// Search through each CID's verification document
|
|
@@ -556,9 +645,24 @@ const lookupCidByEmail = async (firestore, userEmail) => {
|
|
|
556
645
|
// Case-insensitive email comparison
|
|
557
646
|
const normalizedEmails = emails.map(e => String(e).toLowerCase().trim());
|
|
558
647
|
if (normalizedEmails.includes(normalizedEmail)) {
|
|
559
|
-
|
|
648
|
+
const cid = verificationData.etoroCID || Number(userDoc.id);
|
|
649
|
+
|
|
650
|
+
// Optional: Validate Firebase UID if provided
|
|
651
|
+
if (firebaseUid && verificationData.firebaseUids && Array.isArray(verificationData.firebaseUids)) {
|
|
652
|
+
if (!verificationData.firebaseUids.includes(firebaseUid)) {
|
|
653
|
+
console.warn(`[lookupCidByEmail] Email ${userEmail} matches CID ${cid} but Firebase UID ${firebaseUid} not in firebaseUids array`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// STEP 3: Create lookup document for future O(1) access (migration)
|
|
658
|
+
// Create lookup for all emails in the verification document to handle multiple accounts
|
|
659
|
+
for (const email of normalizedEmails) {
|
|
660
|
+
await createEmailLookup(firestore, email, cid);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Return result
|
|
560
664
|
return {
|
|
561
|
-
cid:
|
|
665
|
+
cid: cid,
|
|
562
666
|
accountSetupComplete: verificationData.accountSetupComplete || false,
|
|
563
667
|
etoroUsername: verificationData.etoroUsername,
|
|
564
668
|
displayName: verificationData.displayName,
|
|
@@ -1688,6 +1792,14 @@ const finalizeVerification = async (db, pubsub, userId, username) => {
|
|
|
1688
1792
|
accountSetupComplete: false, // Will be set to true by frontend completeAccountSetup
|
|
1689
1793
|
createdAt: FieldValue.serverTimestamp(),
|
|
1690
1794
|
}, { merge: true });
|
|
1795
|
+
|
|
1796
|
+
// 3. Create lookup documents for all emails (O(1) lookup optimization)
|
|
1797
|
+
// This ensures future lookups are fast even if emails are added later
|
|
1798
|
+
for (const email of emails) {
|
|
1799
|
+
if (email) {
|
|
1800
|
+
await createEmailLookup(db, email, realCID);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1691
1803
|
|
|
1692
1804
|
// 4. Trigger Downstream Systems via Task Engine
|
|
1693
1805
|
if (pubsub) {
|
|
@@ -2612,6 +2724,8 @@ module.exports = {
|
|
|
2612
2724
|
fetchPopularInvestorMasterList,
|
|
2613
2725
|
isDeveloper,
|
|
2614
2726
|
lookupCidByEmail,
|
|
2727
|
+
createEmailLookup,
|
|
2728
|
+
hashEmail,
|
|
2615
2729
|
manageUserWatchlist,
|
|
2616
2730
|
fetchUserVerificationData,
|
|
2617
2731
|
requestPopularInvestorAddition,
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* Middleware to resolve the effective user ID, handling Developer Impersonation.
|
|
3
3
|
* Sets req.targetUserId, req.isImpersonating, and req.actualUserId.
|
|
4
4
|
*
|
|
5
|
+
* SECURITY: This middleware validates that the authenticated Firebase user owns the requested CID.
|
|
6
|
+
* It prevents IDOR (Insecure Direct Object Reference) attacks by:
|
|
7
|
+
* 1. Looking up the CID associated with the Firebase authenticated user's email
|
|
8
|
+
* 2. Forcing req.targetUserId to be the authenticated user's CID (unless developer impersonation)
|
|
9
|
+
* 3. Ignoring any userCid provided in headers/query/body if it doesn't match the authenticated user
|
|
10
|
+
*
|
|
5
11
|
* Public routes (that don't require authentication):
|
|
6
12
|
* - /watchlists/public
|
|
7
13
|
* - /popular-investors/trending
|
|
@@ -9,7 +15,7 @@
|
|
|
9
15
|
* - /popular-investors/master-list
|
|
10
16
|
* - /popular-investors/search
|
|
11
17
|
*/
|
|
12
|
-
const { isDeveloper } = require('../helpers/data-fetchers/firestore.js');
|
|
18
|
+
const { isDeveloper, lookupCidByEmail } = require('../helpers/data-fetchers/firestore.js');
|
|
13
19
|
|
|
14
20
|
// List of public routes that don't require userCid
|
|
15
21
|
// Also includes routes that use Firebase Auth token authentication (like /verification/lookup)
|
|
@@ -60,6 +66,12 @@ const isPublicRoute = (path, originalUrl) => {
|
|
|
60
66
|
|
|
61
67
|
const resolveUserIdentity = async (req, res, next) => {
|
|
62
68
|
try {
|
|
69
|
+
const db = req.app.locals?.db || req.dependencies?.db;
|
|
70
|
+
if (!db) {
|
|
71
|
+
console.error('[IdentityMiddleware] Database not available');
|
|
72
|
+
return res.status(500).json({ error: "Internal server error" });
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
// Check if this is a public route (check both path and originalUrl for Express routing)
|
|
64
76
|
const isPublic = isPublicRoute(req.path, req.originalUrl);
|
|
65
77
|
|
|
@@ -68,45 +80,118 @@ const resolveUserIdentity = async (req, res, next) => {
|
|
|
68
80
|
const hasFirebaseAuth = req.headers.authorization &&
|
|
69
81
|
req.headers.authorization.startsWith('Bearer ');
|
|
70
82
|
|
|
71
|
-
//
|
|
72
|
-
|
|
83
|
+
// SECURITY FIX: If Firebase Auth is present, validate the user owns the requested CID
|
|
84
|
+
// NOTE: Multiple Firebase accounts (different UIDs) can access the same CID
|
|
85
|
+
// if their emails are in the verification document's email array
|
|
86
|
+
let authenticatedUserCid = null;
|
|
87
|
+
if (req.firebaseUser && req.firebaseUser.email) {
|
|
88
|
+
try {
|
|
89
|
+
// Look up the CID associated with the authenticated Firebase user's email
|
|
90
|
+
// Pass Firebase UID for optional validation (email is the source of truth)
|
|
91
|
+
const userData = await lookupCidByEmail(db, req.firebaseUser.email, req.firebaseUser.uid);
|
|
92
|
+
if (userData && userData.cid) {
|
|
93
|
+
authenticatedUserCid = String(userData.cid);
|
|
94
|
+
console.log(`[Identity] Authenticated user ${req.firebaseUser.email} (UID: ${req.firebaseUser.uid}) owns CID ${authenticatedUserCid}`);
|
|
95
|
+
} else {
|
|
96
|
+
console.warn(`[Identity] No CID found for authenticated user ${req.firebaseUser.email}`);
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error(`[Identity] Error looking up CID for ${req.firebaseUser.email}:`, error);
|
|
100
|
+
// Continue - will fail validation below if CID is required
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 1. Identify the requested user ID (from params/headers/body)
|
|
105
|
+
const requestedUserId = req.query.userCid || req.body.userCid || req.headers['x-user-cid'];
|
|
106
|
+
|
|
107
|
+
// SECURITY: For private routes, require Firebase Auth to prevent IDOR attacks
|
|
108
|
+
if (!isPublic && !authenticatedUserCid && !hasFirebaseAuth) {
|
|
109
|
+
// Private route without authentication - reject immediately
|
|
110
|
+
console.warn(`[Identity] Security violation: Private route ${req.path} accessed without Firebase Auth`);
|
|
111
|
+
return res.status(401).json({
|
|
112
|
+
error: "Authentication required. Please provide a valid Firebase ID token in the Authorization header."
|
|
113
|
+
});
|
|
114
|
+
}
|
|
73
115
|
|
|
74
|
-
// For public routes
|
|
75
|
-
if (!
|
|
116
|
+
// For public routes, userCid is optional
|
|
117
|
+
if (!requestedUserId && !isPublic && !hasFirebaseAuth) {
|
|
76
118
|
return res.status(400).json({ error: "Missing user identification (userCid)" });
|
|
77
119
|
}
|
|
78
120
|
|
|
79
|
-
// If no user ID provided and it's a public route
|
|
80
|
-
if (!
|
|
121
|
+
// If no user ID provided and it's a public route, skip identity resolution
|
|
122
|
+
if (!requestedUserId && isPublic) {
|
|
81
123
|
req.actualUserId = null;
|
|
82
124
|
req.targetUserId = null;
|
|
83
125
|
req.isImpersonating = false;
|
|
84
126
|
return next();
|
|
85
127
|
}
|
|
86
128
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
129
|
+
// SECURITY FIX: If Firebase Auth is present, enforce ownership
|
|
130
|
+
// The authenticated user can only access their own CID (unless developer impersonation)
|
|
131
|
+
if (authenticatedUserCid) {
|
|
132
|
+
// If a CID was requested but doesn't match the authenticated user's CID, reject (unless developer impersonation)
|
|
133
|
+
if (requestedUserId && requestedUserId !== authenticatedUserCid) {
|
|
134
|
+
// Check if this is a developer impersonation request
|
|
135
|
+
const impersonateId = req.headers['x-impersonate-cid'] || req.query.impersonateCid;
|
|
136
|
+
|
|
137
|
+
if (impersonateId && impersonateId === requestedUserId) {
|
|
138
|
+
// This is an explicit impersonation request - verify developer status
|
|
139
|
+
const isDev = await isDeveloper(db, authenticatedUserCid);
|
|
140
|
+
if (isDev) {
|
|
141
|
+
req.actualUserId = authenticatedUserCid;
|
|
142
|
+
req.targetUserId = impersonateId;
|
|
143
|
+
req.isImpersonating = true;
|
|
144
|
+
console.log(`[Identity] Dev ${authenticatedUserCid} is impersonating ${impersonateId}`);
|
|
145
|
+
return next();
|
|
146
|
+
} else {
|
|
147
|
+
console.warn(`[Identity] Unauthorized impersonation attempt by ${authenticatedUserCid}`);
|
|
148
|
+
return res.status(403).json({ error: "Unauthorized: Developer privileges required for impersonation" });
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// User is trying to access another user's CID without impersonation header
|
|
152
|
+
console.warn(`[Identity] Security violation: User ${authenticatedUserCid} attempted to access CID ${requestedUserId}`);
|
|
153
|
+
return res.status(403).json({ error: "Unauthorized: You can only access your own data" });
|
|
154
|
+
}
|
|
109
155
|
}
|
|
156
|
+
|
|
157
|
+
// Authenticated user owns the requested CID (or no CID requested) - use authenticated CID
|
|
158
|
+
req.actualUserId = authenticatedUserCid;
|
|
159
|
+
req.targetUserId = authenticatedUserCid;
|
|
160
|
+
req.isImpersonating = false;
|
|
161
|
+
} else if (requestedUserId) {
|
|
162
|
+
// SECURITY FIX: Legacy mode only allowed for public routes
|
|
163
|
+
// For private routes, Firebase Auth is REQUIRED to prevent IDOR attacks
|
|
164
|
+
if (!isPublic) {
|
|
165
|
+
// Private route without authentication - reject the request
|
|
166
|
+
console.warn(`[Identity] Security violation: Private route accessed without Firebase Auth. Requested CID: ${requestedUserId}`);
|
|
167
|
+
return res.status(401).json({
|
|
168
|
+
error: "Authentication required. Please provide a valid Firebase ID token in the Authorization header."
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Legacy mode only for public routes (backward compatibility)
|
|
173
|
+
// Public routes don't expose sensitive data, so this is acceptable
|
|
174
|
+
req.actualUserId = requestedUserId;
|
|
175
|
+
req.targetUserId = requestedUserId;
|
|
176
|
+
req.isImpersonating = false;
|
|
177
|
+
|
|
178
|
+
// Check for developer impersonation (legacy mode - only for public routes)
|
|
179
|
+
const impersonateId = req.headers['x-impersonate-cid'] || req.query.impersonateCid;
|
|
180
|
+
if (impersonateId && impersonateId !== requestedUserId) {
|
|
181
|
+
const isDev = await isDeveloper(db, requestedUserId);
|
|
182
|
+
if (isDev) {
|
|
183
|
+
req.targetUserId = impersonateId;
|
|
184
|
+
req.isImpersonating = true;
|
|
185
|
+
console.log(`[Identity] Dev ${requestedUserId} is impersonating ${impersonateId} (public route)`);
|
|
186
|
+
} else {
|
|
187
|
+
console.warn(`[Identity] Unauthorized impersonation attempt by ${requestedUserId}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
// No CID and not public - this shouldn't happen due to check above, but handle gracefully
|
|
192
|
+
req.actualUserId = null;
|
|
193
|
+
req.targetUserId = null;
|
|
194
|
+
req.isImpersonating = false;
|
|
110
195
|
}
|
|
111
196
|
|
|
112
197
|
next();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const { resolveUserIdentity } = require('../middleware/identity_middleware.js');
|
|
3
|
+
const { verifyFirebaseToken } = require('../middleware/firebase_auth_middleware.js');
|
|
3
4
|
|
|
4
5
|
const notificationRoutes = require('./notifications.js');
|
|
5
6
|
const alertsRoutes = require('./alerts.js'); // <--- NEW
|
|
@@ -19,6 +20,11 @@ module.exports = (dependencies) => {
|
|
|
19
20
|
next();
|
|
20
21
|
});
|
|
21
22
|
|
|
23
|
+
// SECURITY: Verify Firebase Auth token first (if present)
|
|
24
|
+
// This allows identity_middleware to validate CID ownership
|
|
25
|
+
router.use(verifyFirebaseToken);
|
|
26
|
+
|
|
27
|
+
// Then resolve user identity with CID ownership validation
|
|
22
28
|
router.use(resolveUserIdentity);
|
|
23
29
|
|
|
24
30
|
router.use('/notifications', notificationRoutes);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
-
const { initiateVerification, finalizeVerification, fetchUserVerificationData, lookupCidByEmail } = require('../helpers/data-fetchers/firestore.js');
|
|
2
|
+
const { initiateVerification, finalizeVerification, fetchUserVerificationData, lookupCidByEmail, createEmailLookup } = require('../helpers/data-fetchers/firestore.js');
|
|
3
3
|
const { requireFirebaseAuth } = require('../middleware/firebase_auth_middleware.js');
|
|
4
4
|
|
|
5
5
|
const router = express.Router();
|
|
@@ -79,4 +79,57 @@ router.post('/finalize', async (req, res) => {
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
// POST /verification/update-lookup
|
|
83
|
+
// Updates email-to-CID lookup documents when emails are added to verification
|
|
84
|
+
// Called by frontend after updating verification document with new emails
|
|
85
|
+
// This ensures O(1) lookups work immediately without waiting for migration
|
|
86
|
+
router.post('/update-lookup', requireFirebaseAuth, async (req, res) => {
|
|
87
|
+
try {
|
|
88
|
+
const { db } = req.dependencies;
|
|
89
|
+
const { cid, emails } = req.body;
|
|
90
|
+
|
|
91
|
+
if (!cid || !Array.isArray(emails) || emails.length === 0) {
|
|
92
|
+
return res.status(400).json({
|
|
93
|
+
success: false,
|
|
94
|
+
error: 'CID and emails array are required'
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Verify the authenticated user owns this CID
|
|
99
|
+
const userEmail = req.firebaseUser?.email;
|
|
100
|
+
if (userEmail) {
|
|
101
|
+
const userData = await lookupCidByEmail(db, userEmail);
|
|
102
|
+
if (!userData || String(userData.cid) !== String(cid)) {
|
|
103
|
+
return res.status(403).json({
|
|
104
|
+
success: false,
|
|
105
|
+
error: 'Unauthorized: You can only update lookup for your own CID'
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Create lookup documents for all emails
|
|
111
|
+
const results = [];
|
|
112
|
+
for (const email of emails) {
|
|
113
|
+
if (email) {
|
|
114
|
+
try {
|
|
115
|
+
await createEmailLookup(db, email, cid);
|
|
116
|
+
results.push({ email, success: true });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error(`[update-lookup] Failed to create lookup for ${email}:`, error);
|
|
119
|
+
results.push({ email, success: false, error: error.message });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
res.json({
|
|
125
|
+
success: true,
|
|
126
|
+
message: `Updated ${results.filter(r => r.success).length} lookup document(s)`,
|
|
127
|
+
results
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('[Verification Update Lookup] Error:', error);
|
|
131
|
+
res.status(500).json({ success: false, error: error.message });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
82
135
|
module.exports = router;
|