abmp-npm 1.1.63 → 1.1.66

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.
@@ -231,9 +231,17 @@ async function getInterestAll() {
231
231
  throw e;
232
232
  }
233
233
  }
234
+ async function clearCollection(collectionName) {
235
+ try {
236
+ await wixData.truncate(collectionName);
237
+ } catch (err) {
238
+ throw new Error(`Failed to clearCollection ${collectionName} with error: ${err.message}`);
239
+ }
240
+ }
234
241
 
235
242
  module.exports = {
236
243
  buildMembersSearchQuery,
237
244
  fetchAllItemsInParallel,
238
245
  getInterestAll,
246
+ clearCollection,
239
247
  };
package/backend/consts.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const PAC_API_URL = 'https://members.abmp.com/eweb/api/Wix';
2
+ const SSO_TOKEN_AUTH_API_URL = 'https://members.professionalassistcorp.com/';
2
3
 
3
4
  /**
4
5
  * Valid configuration keys for getSiteConfigs function
@@ -38,4 +39,5 @@ module.exports = {
38
39
  PAC_API_URL,
39
40
  COMPILED_FILTERS_FIELDS,
40
41
  MEMBERSHIPS_TYPES,
42
+ SSO_TOKEN_AUTH_API_URL,
41
43
  };
@@ -2,7 +2,7 @@ const { taskManager } = require('psdev-task-manager');
2
2
 
3
3
  const { CONFIG_KEYS } = require('../consts');
4
4
  const { fetchPACMembers } = require('../pac-api-methods');
5
- const { TASKS_NAMES } = require('../tasks');
5
+ const { TASKS_NAMES } = require('../tasks/consts');
6
6
  const { getSiteConfigs } = require('../utils');
7
7
 
8
8
  const { bulkProcessAndSaveMemberData } = require('./bulk-process-methods');
@@ -10,6 +10,7 @@ const wixData = {
10
10
  save: auth.elevate(items.save),
11
11
  remove: auth.elevate(items.remove),
12
12
  get: auth.elevate(items.get),
13
+ truncate: auth.elevate(items.truncate),
13
14
  //TODO: add other methods here as needed
14
15
  };
15
16
 
@@ -0,0 +1,72 @@
1
+ const { COLLECTIONS } = require('../../public/consts');
2
+ const { clearCollection } = require('../cms-data-methods');
3
+ const { getSecret } = require('../utils');
4
+
5
+ const { migrateInterests } = require('./interests');
6
+
7
+ const createHTTPFunctionsHelpers = wixHTTPFunctionsMethods => {
8
+ const { created, serverError, forbidden, ok, badRequest } = wixHTTPFunctionsMethods;
9
+ const responseOptions = {
10
+ headers: {
11
+ 'Content-Type': 'application/json',
12
+ },
13
+ };
14
+
15
+ const isRequestAuthenticated = async request => {
16
+ const AUTH_TOKEN = await getSecret('migrate-api-key');
17
+ return request.headers.authorization === 'Bearer ' + AUTH_TOKEN;
18
+ };
19
+
20
+ const withAuth = handler => async request => {
21
+ if (!(await isRequestAuthenticated(request))) {
22
+ return forbidden({
23
+ ...responseOptions,
24
+ body: { error: 'Unauthorized' },
25
+ });
26
+ }
27
+ return handler(request);
28
+ };
29
+
30
+ const migrateInterestsHandler = async _request => {
31
+ try {
32
+ const result = await migrateInterests();
33
+ return created({
34
+ ...responseOptions,
35
+ body: {
36
+ result,
37
+ },
38
+ });
39
+ } catch (error) {
40
+ console.error('Error migrating interests:', error);
41
+ return serverError(error);
42
+ }
43
+ };
44
+
45
+ const clearCollectionHandler = async request => {
46
+ try {
47
+ const collectionName = request.query.collectionName;
48
+ if (!collectionName || !Object.values(COLLECTIONS).includes(collectionName)) {
49
+ return badRequest({
50
+ ...responseOptions,
51
+ body: { error: 'Invalid collection name' },
52
+ });
53
+ }
54
+ const result = await clearCollection(collectionName);
55
+ return ok({
56
+ ...responseOptions,
57
+ body: {
58
+ result,
59
+ },
60
+ });
61
+ } catch (error) {
62
+ return serverError(error);
63
+ }
64
+ };
65
+
66
+ return {
67
+ post_migrateInterests: withAuth(migrateInterestsHandler),
68
+ delete_clearCollection: withAuth(clearCollectionHandler),
69
+ };
70
+ };
71
+
72
+ module.exports = { createHTTPFunctionsHelpers };
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ ...require('./httpFunctions'),
3
+ };
@@ -0,0 +1,37 @@
1
+ const { COLLECTIONS } = require('../../public/consts');
2
+ const { clearCollection } = require('../cms-data-methods');
3
+ const { CONFIG_KEYS } = require('../consts');
4
+ const { wixData } = require('../elevated-modules');
5
+ const { getHeaders } = require('../pac-api-methods');
6
+ const { getSiteConfigs } = require('../utils');
7
+
8
+ const getInterests = async () => {
9
+ const [url, headers] = await Promise.all([
10
+ getSiteConfigs(CONFIG_KEYS.INTERESTS_API_URL),
11
+ getHeaders(),
12
+ ]);
13
+ const fetchOptions = {
14
+ method: 'get',
15
+ headers: headers,
16
+ };
17
+ try {
18
+ const response = await fetch(url, fetchOptions);
19
+ return await response.json();
20
+ } catch (e) {
21
+ console.error('Error getting interests:', e);
22
+ throw e;
23
+ }
24
+ };
25
+
26
+ async function migrateInterests() {
27
+ const [interests, _] = await Promise.all([
28
+ getInterests(),
29
+ clearCollection(COLLECTIONS.INTERESTS),
30
+ ]);
31
+ const interestData = interests.map(val => ({ title: val.interest }));
32
+ return await wixData.bulkInsert(COLLECTIONS.INTERESTS, interestData);
33
+ }
34
+
35
+ module.exports = {
36
+ migrateInterests,
37
+ };
package/backend/index.js CHANGED
@@ -10,6 +10,7 @@ module.exports = {
10
10
  ...require('./members-data-methods'), //TODO: remove it once we finish NPM movement
11
11
  ...require('./cms-data-methods'), //TODO: remove it once we finish NPM movement
12
12
  ...require('./routers'),
13
- ...require('./sso-methods'),
13
+ ...require('./login'),
14
14
  ...require('./data-hooks'),
15
+ ...require('./http-functions'),
15
16
  };
@@ -0,0 +1,19 @@
1
+ const { loginQAMember } = require('./qa-login-methods');
2
+ const { authenticateSSOToken } = require('./sso-methods');
3
+
4
+ const createLoginMethods = generateSessionToken => {
5
+ //There is no generateSessionToken SDK version, and the signOn of @wix/identity returns 403 error regardless that the permissions are valid
6
+ //Therefore, as a workaround we need to inject the Velo version of generateSessionToken to the login methods.
7
+ const injectGenerateSessionTokenToMethod =
8
+ method =>
9
+ async (...args) =>
10
+ await method(...args, generateSessionToken);
11
+ return {
12
+ loginQAMember: injectGenerateSessionTokenToMethod(loginQAMember),
13
+ authenticateSSOToken: injectGenerateSessionTokenToMethod(authenticateSSOToken),
14
+ };
15
+ };
16
+
17
+ module.exports = {
18
+ createLoginMethods,
19
+ };
@@ -0,0 +1,72 @@
1
+ const { getMemberByEmail, getQAUsers } = require('../members-data-methods');
2
+ const { getSecret } = require('../utils');
3
+
4
+ const validateQAUser = async userEmail => {
5
+ const qaUsers = await getQAUsers();
6
+ const matchingUser = qaUsers.find(user => user.email === userEmail);
7
+ if (!matchingUser) {
8
+ return { error: `Invalid user email: ${userEmail}` };
9
+ }
10
+ return { valid: true, user: matchingUser };
11
+ };
12
+
13
+ /**
14
+ * Login a QA user
15
+ * @param {Object} params - The parameters for the login
16
+ * @param {string} params.userEmail - The email of the user to login
17
+ * @param {string} params.secret - The secret of the user to login
18
+ * @param {Function} generateSessionToken - a dependency of the method, injected by the createLoginMethods function
19
+ * @returns {Promise<Object>} The result of the login
20
+ */
21
+ const loginQAMember = async ({ userEmail, secret }, generateSessionToken) => {
22
+ try {
23
+ const userValidation = await validateQAUser(userEmail);
24
+ if (userValidation.error) {
25
+ return { success: false, error: userValidation.error };
26
+ }
27
+
28
+ const qaSecret = await getSecret('ABMP_QA_SECRET');
29
+ if (secret !== qaSecret) {
30
+ return { success: false, error: 'Invalid secret' };
31
+ }
32
+
33
+ const token = await generateSessionToken(userValidation.user, qaSecret);
34
+
35
+ const result = await getMemberCMSId(userEmail);
36
+ if (!result.success) {
37
+ return { success: false, error: result.error };
38
+ }
39
+
40
+ return {
41
+ success: true,
42
+ token,
43
+ memberCMSId: result.memberCMSId,
44
+ };
45
+ } catch (error) {
46
+ console.error('QA login error:', error);
47
+ return { error: 'Failed to generate session token' };
48
+ }
49
+ };
50
+
51
+ async function getMemberCMSId(userEmail) {
52
+ try {
53
+ const userValidation = await validateQAUser(userEmail);
54
+ if (userValidation.error) {
55
+ return { success: false, error: userValidation.error };
56
+ }
57
+
58
+ const member = await getMemberByEmail(userEmail);
59
+
60
+ if (!member) {
61
+ return { success: false, error: `No Member found in DB matching email: ${userEmail}` };
62
+ }
63
+ return { success: true, memberCMSId: member._id };
64
+ } catch (error) {
65
+ console.error('Error getting member CMS ID:', error);
66
+ return { success: false, error: 'Failed to retrieve member data' };
67
+ }
68
+ }
69
+
70
+ module.exports = {
71
+ loginQAMember,
72
+ };
@@ -0,0 +1,158 @@
1
+ const { createHmac } = require('crypto');
2
+
3
+ const { decode } = require('jwt-js-decode');
4
+
5
+ const { CONFIG_KEYS, SSO_TOKEN_AUTH_API_URL } = require('../consts');
6
+ const { MEMBER_ACTIONS } = require('../daily-pull/consts');
7
+ const { getCurrentMember } = require('../members-area-methods');
8
+ const { getMemberByContactId, getSiteMemberId } = require('../members-data-methods');
9
+ const {
10
+ formatDateToMonthYear,
11
+ getAddressDisplayOptions,
12
+ isStudent,
13
+ getSiteConfigs,
14
+ getSecret,
15
+ } = require('../utils');
16
+
17
+ /**
18
+ * Validates member token and retrieves member data
19
+ * @param {string} memberIdInput - The member ID to validate
20
+ * @returns {Promise<{memberData: Object|null, isValid: boolean}>} Validation result with member data
21
+ */
22
+ async function validateMemberToken(memberIdInput) {
23
+ const invalidTokenResponse = { memberData: null, isValid: false };
24
+
25
+ if (!memberIdInput) {
26
+ return invalidTokenResponse;
27
+ }
28
+
29
+ try {
30
+ const member = await getCurrentMember();
31
+ if (!member || !member._id) {
32
+ console.log(
33
+ 'member not found from members.getCurrentMember() for memberIdInput',
34
+ memberIdInput
35
+ );
36
+ return invalidTokenResponse;
37
+ }
38
+
39
+ const [dbMember, siteConfigs] = await Promise.all([
40
+ getMemberByContactId(member._id),
41
+ getSiteConfigs(),
42
+ ]);
43
+ const siteAssociation = siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION];
44
+ const membersExternalPortalUrl = siteConfigs[CONFIG_KEYS.MEMBERS_EXTERNAL_PORTAL_URL];
45
+ console.log('dbMember by contact id is:', dbMember);
46
+ console.log('member._id', member._id);
47
+
48
+ if (!dbMember?._id) {
49
+ const errorMessage = `No record found in DB for logged in Member [Corrupted Data - Duplicate Members? ] - There is no match in DB for currentMember: ${JSON.stringify(
50
+ { memberIdInput, currentMemberId: member._id }
51
+ )}`;
52
+ console.error(errorMessage);
53
+ throw new Error('CORRUPTED_MEMBER_DATA');
54
+ }
55
+
56
+ console.log(`Id found in DB for memberIdInput :${memberIdInput} is ${dbMember?._id}`);
57
+
58
+ const memberData = dbMember;
59
+
60
+ // Format membership dates
61
+ memberData.memberships = memberData.memberships.map(membership => ({
62
+ ...membership,
63
+ membersince: formatDateToMonthYear(membership.membersince),
64
+ isSiteAssociation: membership.association === siteAssociation,
65
+ }));
66
+
67
+ const savedMemberId = memberData?._id;
68
+ const isValid = savedMemberId === memberIdInput;
69
+
70
+ if (!savedMemberId || !isValid) {
71
+ return invalidTokenResponse;
72
+ }
73
+
74
+ // Check if member is dropped
75
+ if (memberData.action === MEMBER_ACTIONS.DROP) {
76
+ return invalidTokenResponse;
77
+ }
78
+
79
+ // Add computed properties
80
+ memberData.addressDisplayOption = getAddressDisplayOptions(memberData);
81
+ console.log('memberData', memberData);
82
+ memberData.isStudent = isStudent(memberData);
83
+
84
+ return { memberData, isValid, membersExternalPortalUrl };
85
+ } catch (error) {
86
+ console.error('Error in validateMemberToken:', error);
87
+ throw error;
88
+ }
89
+ }
90
+ async function checkAndFetchSSO(token) {
91
+ const SSO_TOKEN_AUTH_API_KEY = await getSecret('SSO_TOKEN_AUTH_API_KEY');
92
+ const signature = createHmac('sha256', SSO_TOKEN_AUTH_API_KEY).update(token).digest('hex');
93
+ const professionalassistcorpUrl = `${SSO_TOKEN_AUTH_API_URL}/eweb/SSOToken.ashx?token=${token}&Partner=Wix&Signature=${signature}`;
94
+ const options = {
95
+ method: 'get',
96
+ };
97
+ try {
98
+ const httpResponse = await fetch(professionalassistcorpUrl, options);
99
+ console.log('httpResponse status', httpResponse.status);
100
+ if (!httpResponse.ok) {
101
+ throw new Error('Fetch did not succeed with status: ' + httpResponse.status);
102
+ }
103
+ const responseToken = await httpResponse.text();
104
+ return responseToken;
105
+ } catch (error) {
106
+ console.error('Error in checkAndFetchSSO', error);
107
+ return null;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Authenticate an SSO token
113
+ * @param {Object} params - The parameters for the authentication
114
+ * @param {string} params.token - The token to authenticate
115
+ * @param {Function} generateSessionToken - a dependency of the method, injected by the createLoginMethods function
116
+ * @returns {Promise<Object>} The result of the authentication
117
+ */
118
+ const authenticateSSOToken = async ({ token }, generateSessionToken) => {
119
+ const responseToken = await checkAndFetchSSO(token);
120
+ const isValidToken = Boolean(
121
+ responseToken && typeof responseToken === 'string' && responseToken?.trim()
122
+ );
123
+ const toLogTokenData = {
124
+ isValidToken,
125
+ tokenData: responseToken
126
+ ? {
127
+ length: responseToken.length,
128
+ preview: responseToken.substring(0, 50),
129
+ }
130
+ : 'No token',
131
+ };
132
+ console.log('checkAndFetchSSO responseToken data', JSON.stringify(toLogTokenData, null, 2));
133
+ if (isValidToken) {
134
+ const jwt = decode(responseToken);
135
+ const payload = jwt.payload;
136
+ const membersData = await getSiteMemberId(payload);
137
+ console.log('membersDataCollectionId', membersData._id);
138
+ const sessionToken = await generateSessionToken(membersData.email);
139
+ const authObj = {
140
+ type: 'success',
141
+ memberId: membersData._id,
142
+ sessionToken,
143
+ };
144
+ return authObj;
145
+ } else {
146
+ console.log('invalid Token responseToken is: ', responseToken);
147
+ return {
148
+ type: 'error',
149
+ memberId: '',
150
+ sessionToken: '',
151
+ };
152
+ }
153
+ };
154
+
155
+ module.exports = {
156
+ validateMemberToken,
157
+ authenticateSSOToken,
158
+ };
@@ -2,7 +2,7 @@ const { COLLECTIONS } = require('../public/consts');
2
2
 
3
3
  const { MEMBERSHIPS_TYPES } = require('./consts');
4
4
  const { updateMemberContactInfo } = require('./contacts-methods');
5
- const { MEMBER_ACTIONS } = require('./daily-pull');
5
+ const { MEMBER_ACTIONS } = require('./daily-pull/consts');
6
6
  const { wixData } = require('./elevated-modules');
7
7
  const { createSiteMember } = require('./members-area-methods');
8
8
  const {
@@ -296,6 +296,27 @@ async function urlExists(url, excludeMemberId) {
296
296
  }
297
297
  }
298
298
 
299
+ /**
300
+ * Checks URL uniqueness for a member
301
+ * @param {string} url - The URL to check
302
+ * @param {string} memberId - The member ID to exclude from the check
303
+ * @returns {Promise<Object>} Result object with isUnique boolean
304
+ */
305
+ async function checkUrlUniqueness(url, memberId) {
306
+ if (!url || !memberId) {
307
+ throw new Error('Missing required parameters: url and memberId are required');
308
+ }
309
+
310
+ try {
311
+ const trimmedUrl = url.trim();
312
+ const exists = await urlExists(trimmedUrl, memberId);
313
+
314
+ return { isUnique: !exists };
315
+ } catch (error) {
316
+ console.error('Error checking URL uniqueness:', error);
317
+ throw new Error(`Failed to check URL uniqueness: ${error.message}`);
318
+ }
319
+ }
299
320
  /**
300
321
  * Get all members with external profile images
301
322
  * @returns {Promise<Array>} - Array of member IDs
@@ -400,6 +421,74 @@ const getMembersByIds = async memberIds => {
400
421
  }
401
422
  };
402
423
 
424
+ const getMemberByEmail = async email => {
425
+ try {
426
+ const members = await wixData
427
+ .query(COLLECTIONS.MEMBERS_DATA)
428
+ .eq('email', email)
429
+ .limit(2)
430
+ .find()
431
+ .then(res => res.items);
432
+ if (members.length > 1) {
433
+ throw new Error(
434
+ `[getMemberByEmail] Multiple members found with email ${email} membersIds are : [${members.map(member => member.memberId).join(', ')}]`
435
+ );
436
+ }
437
+ return members[0] || null;
438
+ } catch (error) {
439
+ console.error('Error getting member by email:', error);
440
+ throw new Error(`Failed to get member by email: ${error.message}`);
441
+ }
442
+ };
443
+
444
+ const getQAUsers = async () => {
445
+ try {
446
+ return await wixData
447
+ .query(COLLECTIONS.QA_USERS)
448
+ .include('member')
449
+ .find()
450
+ .then(res => res.items.map(item => item.member));
451
+ } catch (error) {
452
+ console.error('Error getting QA users:', error);
453
+ throw new Error(`Failed to get QA users: ${error.message}`);
454
+ }
455
+ };
456
+ async function getSiteMemberId(data) {
457
+ try {
458
+ console.log('data', data);
459
+ const memberId = data?.pac?.cst_recno;
460
+ if (!memberId) {
461
+ const errorMessage = `Member ID is missing in passed data ${JSON.stringify(data)}`;
462
+ console.error(errorMessage);
463
+ throw new Error(errorMessage);
464
+ }
465
+ const queryMemberResult = await wixData
466
+ .query(COLLECTIONS.MEMBERS_DATA)
467
+ .eq('memberId', Number(memberId))
468
+ .find()
469
+ .then(res => res.items);
470
+ if (!queryMemberResult.length || queryMemberResult.length > 1) {
471
+ throw new Error(
472
+ `Invalid Members count found in DB for email ${data.email} members count is : [${
473
+ queryMemberResult.length
474
+ }] membersIds are : [${queryMemberResult.map(member => member.memberId).join(', ')}]`
475
+ );
476
+ }
477
+ let memberData = queryMemberResult[0];
478
+ console.log('memberData', memberData);
479
+ const isNewUser = !memberData.contactId;
480
+ if (isNewUser) {
481
+ const memberDataWithContactId = await createContactAndMemberIfNew(memberData);
482
+ console.log('memberDataWithContactId', memberDataWithContactId);
483
+ memberData = memberDataWithContactId;
484
+ }
485
+ return memberData;
486
+ } catch (error) {
487
+ console.error('Error in getSiteMemberId', error.message);
488
+ throw error;
489
+ }
490
+ }
491
+
403
492
  module.exports = {
404
493
  findMemberByWixDataId,
405
494
  createContactAndMemberIfNew,
@@ -415,4 +504,8 @@ module.exports = {
415
504
  getAllMembersWithoutContactFormEmail,
416
505
  getAllUpdatedLoginEmails,
417
506
  getMembersByIds,
507
+ getMemberByEmail,
508
+ getQAUsers,
509
+ getSiteMemberId,
510
+ checkUrlUniqueness,
418
511
  };
@@ -12,7 +12,7 @@ const createRoutersHandlers = wixRouterMethods => {
12
12
  notFound,
13
13
  sendStatus,
14
14
  WixRouterSitemapEntry: _WixRouterSitemapEntry,
15
- } = wixRouterMethods;
15
+ } = wixRouterMethods; // These dependencies needs to be injected as they do not have an SDK equivalent for now
16
16
 
17
17
  async function profileRouter(request) {
18
18
  const slug = request.path[0];
package/backend/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- const { elevate } = require('@wix/essentials');
1
+ const { auth } = require('@wix/essentials');
2
2
  const { secrets } = require('@wix/secrets');
3
3
  const { site } = require('@wix/urls');
4
4
  const { encode } = require('ngeohash');
@@ -8,8 +8,7 @@ const { formatAddress, generateId } = require('../public/Utils/sharedUtils');
8
8
 
9
9
  const { CONFIG_KEYS, GEO_HASH_PRECISION, MEMBERSHIPS_TYPES } = require('./consts');
10
10
  const { wixData } = require('./elevated-modules');
11
- const { urlExists } = require('./members-data-methods');
12
- const elevatedGetSecretValue = elevate(secrets.getSecretValue);
11
+ const elevatedGetSecretValue = auth.elevate(secrets.getSecretValue);
13
12
 
14
13
  /**
15
14
  * Retrieves site configuration values from the database
@@ -154,28 +153,6 @@ const normalizeUrlForComparison = url => {
154
153
  return url.toLowerCase().replace(/-\d+$/, '');
155
154
  };
156
155
 
157
- /**
158
- * Checks URL uniqueness for a member
159
- * @param {string} url - The URL to check
160
- * @param {string} memberId - The member ID to exclude from the check
161
- * @returns {Promise<Object>} Result object with isUnique boolean
162
- */
163
- async function checkUrlUniqueness(url, memberId) {
164
- if (!url || !memberId) {
165
- throw new Error('Missing required parameters: url and memberId are required');
166
- }
167
-
168
- try {
169
- const trimmedUrl = url.trim();
170
- const exists = await urlExists(trimmedUrl, memberId);
171
-
172
- return { isUnique: !exists };
173
- } catch (error) {
174
- console.error('Error checking URL uniqueness:', error);
175
- throw new Error(`Failed to check URL uniqueness: ${error.message}`);
176
- }
177
- }
178
-
179
156
  async function getSecret(secretKey) {
180
157
  return await elevatedGetSecretValue(secretKey).value;
181
158
  }
@@ -219,7 +196,6 @@ module.exports = {
219
196
  isValidArray,
220
197
  normalizeUrlForComparison,
221
198
  queryAllItems,
222
- checkUrlUniqueness,
223
199
  formatDateToMonthYear,
224
200
  isStudent,
225
201
  hasStudentMembership,
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "abmp-npm",
3
- "version": "1.1.63",
3
+ "version": "1.1.66",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
+ "check-cycles": "madge --circular .",
6
7
  "test": "echo \"Error: no test specified\" && exit 1",
7
- "lint": "eslint .",
8
+ "lint": "npm run check-cycles && eslint .",
8
9
  "lint:fix": "eslint . --fix",
9
10
  "format": "prettier --write \"**/*.{js,json,md}\"",
10
11
  "format:check": "prettier --check \"**/*.{js,json,md}\"",
@@ -23,6 +24,7 @@
23
24
  "eslint-plugin-promise": "^7.1.0",
24
25
  "globals": "^15.10.0",
25
26
  "husky": "^9.1.6",
27
+ "madge": "^8.0.0",
26
28
  "prettier": "^3.3.3"
27
29
  },
28
30
  "dependencies": {
@@ -30,15 +32,19 @@
30
32
  "@wix/crm": "^1.0.1061",
31
33
  "@wix/data": "^1.0.303",
32
34
  "@wix/essentials": "^0.1.28",
35
+ "@wix/identity": "^1.0.178",
33
36
  "@wix/media": "^1.0.213",
34
37
  "@wix/members": "^1.0.365",
35
38
  "@wix/secrets": "^1.0.62",
36
39
  "@wix/site-location": "^1.31.0",
40
+ "@wix/site-members": "^1.32.0",
41
+ "@wix/site-storage": "^1.22.0",
37
42
  "@wix/site-window": "^1.44.0",
38
43
  "@wix/urls": "^1.0.57",
39
44
  "aws4": "^1.13.2",
40
45
  "axios": "^1.13.1",
41
46
  "crypto": "^1.0.1",
47
+ "jwt-js-decode": "^1.9.0",
42
48
  "lodash": "^4.17.21",
43
49
  "ngeohash": "^0.6.3",
44
50
  "phone": "^3.1.67",
@@ -0,0 +1,20 @@
1
+ const { window: wixWindow, rendering } = require('@wix/site-window');
2
+
3
+ const { LIGHTBOX_NAMES } = require('../public/consts');
4
+ const { checkAndLogin } = require('../public/sso-auth-methods');
5
+
6
+ async function loadingPageOnReady(authenticateSSOToken) {
7
+ const renderingEnv = await rendering.env();
8
+ //This calls needs to triggered on client side, otherwise PAC API will return 401 error
9
+ if (renderingEnv === 'browser') {
10
+ //Need to pass authenticateSSOToken to checkAndLogin so it will run as a web method not a public one.
11
+ await checkAndLogin(authenticateSSOToken).catch(error => {
12
+ wixWindow.openLightbox(LIGHTBOX_NAMES.LOGIN_ERROR_ALERT);
13
+ console.error(`Something went wrong while logging in: ${error}`);
14
+ });
15
+ }
16
+ }
17
+
18
+ module.exports = {
19
+ loadingPageOnReady,
20
+ };
@@ -0,0 +1,39 @@
1
+ const { location: wixLocationFrontend } = require('@wix/site-location');
2
+ const { authentication } = require('@wix/site-members');
3
+ const { local } = require('@wix/site-storage');
4
+
5
+ async function qaPageOnReady({ $w: _$w, loginQAMember }) {
6
+ try {
7
+ const { userEmail, secret, redirectTo, ...restQueryParams } = await wixLocationFrontend.query();
8
+
9
+ if (!userEmail || !secret) {
10
+ throw new Error('Missing required parameters: userEmail and/or secret');
11
+ }
12
+
13
+ const result = await loginQAMember({ userEmail, secret });
14
+
15
+ if (!result.success || !result.token) {
16
+ throw new Error(result.error || 'Login failed');
17
+ }
18
+
19
+ await authentication.applySessionToken(result.token);
20
+ console.log('QA user logged in successfully');
21
+
22
+ await local.setItem('memberId', result.memberCMSId);
23
+ const queryParams = new URLSearchParams({ ...restQueryParams, token: result.memberCMSId });
24
+ const redirectUrl = redirectTo ? `/${redirectTo}?${queryParams.toString()}` : '/';
25
+
26
+ await wixLocationFrontend.to(redirectUrl);
27
+ } catch (error) {
28
+ console.error('QA login failed:', error);
29
+
30
+ const qaTextElement = _$w('#qaText');
31
+ if (qaTextElement) {
32
+ qaTextElement.text = 'Login failed: ' + (error.message || 'Unknown error');
33
+ }
34
+ }
35
+ }
36
+
37
+ module.exports = {
38
+ qaPageOnReady,
39
+ };
package/pages/index.js CHANGED
@@ -3,4 +3,6 @@ module.exports = {
3
3
  ...require('./Profile.js'),
4
4
  ...require('./Home.js'),
5
5
  ...require('./personalDetails.js'),
6
+ ...require('./QAPage.js'),
7
+ ...require('./LoadingPage.js'),
6
8
  };
package/public/consts.js CHANGED
@@ -12,6 +12,7 @@ const COLLECTIONS = {
12
12
  INTERESTS: 'interests',
13
13
  STATE_CITY_MAP: 'City',
14
14
  UPDATED_LOGIN_EMAILS: 'updatedLoginEmails',
15
+ QA_Users: 'QA_Users', //Make QA users configurable per site
15
16
  };
16
17
 
17
18
  /**
@@ -0,0 +1,43 @@
1
+ const { location: wixLocationFrontend } = require('@wix/site-location');
2
+ const { authentication } = require('@wix/site-members');
3
+ const { local } = require('@wix/site-storage');
4
+
5
+ const { PAGES_PATHS } = require('./consts');
6
+
7
+ const checkAndLogin = async authenticateSSOToken => {
8
+ const query = await wixLocationFrontend.query();
9
+ const token = query['token']?.trim();
10
+ try {
11
+ if (token) {
12
+ const authObj = await authenticateSSOToken({ token });
13
+ console.log('authObj', authObj);
14
+ if (authObj.type == 'success') {
15
+ console.log('success');
16
+ await Promise.all([
17
+ authentication.applySessionToken(authObj?.sessionToken),
18
+ local.setItem('memberId', authObj.memberId),
19
+ ]);
20
+ console.log('memberId', authObj.memberId);
21
+ const queryParams = {
22
+ ...query,
23
+ token: authObj?.memberId,
24
+ };
25
+ const redirectTo = `${PAGES_PATHS.MEMBERS_FORM}?${new URLSearchParams(queryParams).toString()}`;
26
+ await wixLocationFrontend.to(`/${redirectTo}`);
27
+ } else {
28
+ console.error('Something went wrong while logging in');
29
+ throw new Error('Authentication failed - invalid response from server');
30
+ }
31
+ } else {
32
+ console.log('checkAndLogin: No token found');
33
+ throw new Error('No authentication token found in URL');
34
+ }
35
+ } catch (error) {
36
+ console.error('Error in checkAndLogin', error);
37
+ throw error;
38
+ }
39
+ };
40
+
41
+ module.exports = {
42
+ checkAndLogin,
43
+ };
@@ -1,88 +0,0 @@
1
- const { CONFIG_KEYS } = require('./consts');
2
- const { MEMBER_ACTIONS } = require('./daily-pull');
3
- const { getCurrentMember } = require('./members-area-methods');
4
- const { getMemberByContactId } = require('./members-data-methods');
5
- const {
6
- formatDateToMonthYear,
7
- getAddressDisplayOptions,
8
- isStudent,
9
- getSiteConfigs,
10
- } = require('./utils');
11
-
12
- /**
13
- * Validates member token and retrieves member data
14
- * @param {string} memberIdInput - The member ID to validate
15
- * @returns {Promise<{memberData: Object|null, isValid: boolean}>} Validation result with member data
16
- */
17
- async function validateMemberToken(memberIdInput) {
18
- const invalidTokenResponse = { memberData: null, isValid: false };
19
-
20
- if (!memberIdInput) {
21
- return invalidTokenResponse;
22
- }
23
-
24
- try {
25
- const member = await getCurrentMember();
26
- if (!member || !member._id) {
27
- console.log(
28
- 'member not found from members.getCurrentMember() for memberIdInput',
29
- memberIdInput
30
- );
31
- return invalidTokenResponse;
32
- }
33
-
34
- const [dbMember, siteConfigs] = await Promise.all([
35
- getMemberByContactId(member._id),
36
- getSiteConfigs(),
37
- ]);
38
- const siteAssociation = siteConfigs[CONFIG_KEYS.SITE_ASSOCIATION];
39
- const membersExternalPortalUrl = siteConfigs[CONFIG_KEYS.MEMBERS_EXTERNAL_PORTAL_URL];
40
- console.log('dbMember by contact id is:', dbMember);
41
- console.log('member._id', member._id);
42
-
43
- if (!dbMember?._id) {
44
- const errorMessage = `No record found in DB for logged in Member [Corrupted Data - Duplicate Members? ] - There is no match in DB for currentMember: ${JSON.stringify(
45
- { memberIdInput, currentMemberId: member._id }
46
- )}`;
47
- console.error(errorMessage);
48
- throw new Error('CORRUPTED_MEMBER_DATA');
49
- }
50
-
51
- console.log(`Id found in DB for memberIdInput :${memberIdInput} is ${dbMember?._id}`);
52
-
53
- const memberData = dbMember;
54
-
55
- // Format membership dates
56
- memberData.memberships = memberData.memberships.map(membership => ({
57
- ...membership,
58
- membersince: formatDateToMonthYear(membership.membersince),
59
- isSiteAssociation: membership.association === siteAssociation,
60
- }));
61
-
62
- const savedMemberId = memberData?._id;
63
- const isValid = savedMemberId === memberIdInput;
64
-
65
- if (!savedMemberId || !isValid) {
66
- return invalidTokenResponse;
67
- }
68
-
69
- // Check if member is dropped
70
- if (memberData.action === MEMBER_ACTIONS.DROP) {
71
- return invalidTokenResponse;
72
- }
73
-
74
- // Add computed properties
75
- memberData.addressDisplayOption = getAddressDisplayOptions(memberData);
76
- console.log('memberData', memberData);
77
- memberData.isStudent = isStudent(memberData);
78
-
79
- return { memberData, isValid, membersExternalPortalUrl };
80
- } catch (error) {
81
- console.error('Error in validateMemberToken:', error);
82
- throw error;
83
- }
84
- }
85
-
86
- module.exports = {
87
- validateMemberToken,
88
- };