propro-utils 1.5.75 → 1.5.77
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/middlewares/account_info.js +161 -72
- package/package.json +1 -1
- package/src/server/middleware/cookieUtils.js +138 -63
|
@@ -105,108 +105,197 @@ const DEFAULT_THEME = {
|
|
|
105
105
|
defaultColor: '#ffffff',
|
|
106
106
|
fontSize: '16px',
|
|
107
107
|
name: 'Default Theme',
|
|
108
|
+
isDefault: true,
|
|
108
109
|
};
|
|
109
110
|
|
|
110
111
|
/**
|
|
111
|
-
* Checks
|
|
112
|
-
*
|
|
113
|
-
* If the user exists but doesn't have global styles, creates them.
|
|
112
|
+
* Checks and initializes a user account with all required associated data.
|
|
113
|
+
* Handles user creation, global styles, folders, and themes in an optimized way.
|
|
114
114
|
*
|
|
115
|
-
* @param {string} accountId - The account ID of the user to check/create
|
|
116
|
-
* @returns {Promise<Object>} A promise that resolves to the user object
|
|
117
|
-
* @throws {Error} If there's an issue with database operations
|
|
115
|
+
* @param {string} accountId - The account ID of the user to check/create
|
|
116
|
+
* @returns {Promise<Object>} A promise that resolves to the complete user object
|
|
117
|
+
* @throws {Error} If there's an issue with database operations or invalid input
|
|
118
118
|
*/
|
|
119
119
|
const checkIfUserExists = async accountId => {
|
|
120
|
+
// Input validation
|
|
121
|
+
if (!accountId || typeof accountId !== 'string') {
|
|
122
|
+
throw new Error('Invalid accountId provided');
|
|
123
|
+
}
|
|
124
|
+
|
|
120
125
|
try {
|
|
121
|
-
|
|
122
|
-
const userStyleSchema
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
// Load all required schemas in parallel
|
|
127
|
+
const [userSchema, userStyleSchema, folderSchema, themeSchema] =
|
|
128
|
+
await Promise.all([
|
|
129
|
+
ServiceManager.getService('UserSchema'),
|
|
130
|
+
ServiceManager.getService('UserStyleSchema'),
|
|
131
|
+
ServiceManager.getService('FolderSchema'),
|
|
132
|
+
ServiceManager.getService('ThemeSchema'),
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
if (!userSchema) {
|
|
136
|
+
throw new Error('UserSchema service not available');
|
|
137
|
+
}
|
|
125
138
|
|
|
126
|
-
|
|
139
|
+
// Find existing user with populated fields
|
|
140
|
+
const user = await userSchema
|
|
127
141
|
.findOne({ accountId })
|
|
128
142
|
.populate('userGlobalStyles')
|
|
129
|
-
.populate('theme')
|
|
143
|
+
.populate('theme')
|
|
144
|
+
.lean();
|
|
130
145
|
|
|
131
146
|
if (user) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
const updates = [];
|
|
148
|
+
|
|
149
|
+
// Handle userGlobalStyles
|
|
150
|
+
if (userStyleSchema) {
|
|
151
|
+
if (!user.userGlobalStyles) {
|
|
152
|
+
updates.push(
|
|
153
|
+
createUserGlobalStyles(userStyleSchema, accountId).then(
|
|
154
|
+
async styles => {
|
|
155
|
+
await userSchema.updateOne(
|
|
156
|
+
{ _id: user._id },
|
|
157
|
+
{ userGlobalStyles: styles._id }
|
|
158
|
+
);
|
|
159
|
+
return styles;
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
);
|
|
163
|
+
} else if (user.userGlobalStyles.styleShortcuts?.length === 0) {
|
|
164
|
+
updates.push(
|
|
165
|
+
userStyleSchema.updateOne(
|
|
166
|
+
{ _id: user.userGlobalStyles._id },
|
|
167
|
+
{ styleShortcuts: defaultUserGlobalStyleShortcuts }
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
141
171
|
}
|
|
142
|
-
// Check if user has any folders
|
|
143
|
-
const userFolders = await folderSchema.find({ user_id: user.id });
|
|
144
|
-
const defaultFolderNames = defaultFolders.map(folder => folder.name);
|
|
145
|
-
const userFolderNames = userFolders.map(folder => folder.name);
|
|
146
172
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
173
|
+
// Handle folders
|
|
174
|
+
if (folderSchema) {
|
|
175
|
+
updates.push(
|
|
176
|
+
(async () => {
|
|
177
|
+
const existingFolders = await folderSchema
|
|
178
|
+
.find({ user_id: user.id })
|
|
179
|
+
.select('name')
|
|
180
|
+
.lean();
|
|
181
|
+
|
|
182
|
+
const existingFolderNames = new Set(
|
|
183
|
+
existingFolders.map(f => f.name)
|
|
184
|
+
);
|
|
185
|
+
const foldersToCreate = defaultFolders.filter(
|
|
186
|
+
folder => !existingFolderNames.has(folder.name)
|
|
187
|
+
);
|
|
150
188
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
189
|
+
if (foldersToCreate.length > 0) {
|
|
190
|
+
return folderSchema.insertMany(
|
|
191
|
+
foldersToCreate.map(folder => ({
|
|
192
|
+
...folder,
|
|
193
|
+
user_id: user.id,
|
|
194
|
+
}))
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
})()
|
|
159
198
|
);
|
|
160
199
|
}
|
|
161
200
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
201
|
+
// Handle themes
|
|
202
|
+
if (themeSchema) {
|
|
203
|
+
updates.push(
|
|
204
|
+
(async () => {
|
|
205
|
+
const defaultThemeExists = await themeSchema.exists({
|
|
206
|
+
name: 'Default Theme',
|
|
207
|
+
accountId,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (!defaultThemeExists) {
|
|
211
|
+
const defaultTheme = await themeSchema.create({
|
|
212
|
+
...DEFAULT_THEME,
|
|
213
|
+
accountId,
|
|
214
|
+
userId: user.id,
|
|
215
|
+
id: uuidv4(),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await userSchema.updateOne(
|
|
219
|
+
{ _id: user._id },
|
|
220
|
+
{ theme: [defaultTheme._id] }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
})()
|
|
224
|
+
);
|
|
176
225
|
}
|
|
177
226
|
|
|
178
|
-
|
|
227
|
+
// Wait for all updates to complete
|
|
228
|
+
await Promise.all(updates);
|
|
229
|
+
|
|
230
|
+
// Return fresh user data after updates
|
|
231
|
+
return userSchema
|
|
232
|
+
.findOne({ accountId })
|
|
233
|
+
.populate('userGlobalStyles')
|
|
234
|
+
.populate('theme');
|
|
179
235
|
}
|
|
180
236
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
);
|
|
237
|
+
// Create new user with all associated data
|
|
238
|
+
const userId = uuidv4();
|
|
239
|
+
const creationTasks = [];
|
|
185
240
|
|
|
186
|
-
user
|
|
241
|
+
// Prepare user global styles
|
|
242
|
+
let userGlobalStyles;
|
|
243
|
+
if (userStyleSchema) {
|
|
244
|
+
creationTasks.push(
|
|
245
|
+
createUserGlobalStyles(userStyleSchema, accountId).then(styles => {
|
|
246
|
+
userGlobalStyles = styles;
|
|
247
|
+
return styles;
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Prepare default theme
|
|
253
|
+
let defaultTheme;
|
|
254
|
+
if (themeSchema) {
|
|
255
|
+
creationTasks.push(
|
|
256
|
+
themeSchema
|
|
257
|
+
.create({
|
|
258
|
+
...DEFAULT_THEME,
|
|
259
|
+
accountId,
|
|
260
|
+
userId,
|
|
261
|
+
id: uuidv4(),
|
|
262
|
+
})
|
|
263
|
+
.then(theme => {
|
|
264
|
+
defaultTheme = theme;
|
|
265
|
+
return theme;
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Wait for parallel creation tasks
|
|
271
|
+
await Promise.all(creationTasks);
|
|
272
|
+
|
|
273
|
+
// Create the user with all references
|
|
274
|
+
const newUser = await userSchema.create({
|
|
187
275
|
accountId,
|
|
188
|
-
id:
|
|
276
|
+
id: userId,
|
|
189
277
|
verified: false,
|
|
190
|
-
userGlobalStyles: userGlobalStyles
|
|
278
|
+
userGlobalStyles: userGlobalStyles?._id,
|
|
279
|
+
theme: defaultTheme ? [defaultTheme._id] : undefined,
|
|
191
280
|
});
|
|
192
281
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
282
|
+
// Create folders if schema available
|
|
283
|
+
if (folderSchema) {
|
|
284
|
+
await createDefaultFolders(folderSchema, newUser.id);
|
|
285
|
+
}
|
|
196
286
|
|
|
197
|
-
|
|
198
|
-
|
|
287
|
+
// Return complete user object with populated fields
|
|
288
|
+
return userSchema
|
|
289
|
+
.findById(newUser._id)
|
|
290
|
+
.populate('userGlobalStyles')
|
|
291
|
+
.populate('theme');
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('Error in checkIfUserExists:', {
|
|
199
294
|
accountId,
|
|
200
|
-
|
|
201
|
-
|
|
295
|
+
error: error.message,
|
|
296
|
+
stack: error.stack,
|
|
202
297
|
});
|
|
203
|
-
user
|
|
204
|
-
await user.save();
|
|
205
|
-
|
|
206
|
-
return user;
|
|
207
|
-
} catch (error) {
|
|
208
|
-
console.error('Error in checkIfUserExists:', error);
|
|
209
|
-
throw new Error('Failed to get or create user');
|
|
298
|
+
throw new Error(`Failed to get or create user: ${error.message}`);
|
|
210
299
|
}
|
|
211
300
|
};
|
|
212
301
|
|
package/package.json
CHANGED
|
@@ -57,16 +57,32 @@ const sanitizeUser = user => {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Sets
|
|
61
|
-
*
|
|
62
|
-
* @
|
|
63
|
-
* @param {Object} tokens - The authentication tokens.
|
|
64
|
-
* @param {Object} account - The user's account information.
|
|
65
|
-
* @param {Object} user - The user's information.
|
|
66
|
-
* @param {string} appUrl - The URL of the client application.
|
|
60
|
+
* Sets a cookie using Chrome Extension API
|
|
61
|
+
* @param {Object} details - Cookie details
|
|
62
|
+
* @returns {Promise} Promise that resolves when cookie is set
|
|
67
63
|
*/
|
|
68
|
-
const
|
|
69
|
-
|
|
64
|
+
const setChromeExtensionCookie = details => {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
try {
|
|
67
|
+
chrome.cookies.set(details, cookie => {
|
|
68
|
+
if (chrome.runtime.lastError) {
|
|
69
|
+
reject(chrome.runtime.lastError);
|
|
70
|
+
} else {
|
|
71
|
+
resolve(cookie);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Not in extension context
|
|
76
|
+
resolve(null);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Sets cookies for both web and extension contexts
|
|
83
|
+
*/
|
|
84
|
+
const setAuthCookies = async (res, tokens, account, user, appUrl) => {
|
|
85
|
+
if (!tokens?.refresh?.token || !tokens?.access?.token) {
|
|
70
86
|
throw new Error('Invalid tokens object');
|
|
71
87
|
}
|
|
72
88
|
if (!account) {
|
|
@@ -82,96 +98,131 @@ const setAuthCookies = (res, tokens, account, user, appUrl) => {
|
|
|
82
98
|
const accessMaxAge =
|
|
83
99
|
new Date(tokens.access.expires).getTime() - currentDateTime.getTime();
|
|
84
100
|
|
|
101
|
+
// Domain configuration
|
|
85
102
|
let domain;
|
|
86
103
|
try {
|
|
87
104
|
domain = appUrl ? new URL(appUrl).hostname : undefined;
|
|
88
|
-
if (domain
|
|
105
|
+
if (domain?.includes('mapmap.app')) {
|
|
89
106
|
domain = '.mapmap.app';
|
|
90
107
|
}
|
|
108
|
+
if (domain?.includes('localhost')) {
|
|
109
|
+
domain = undefined;
|
|
110
|
+
}
|
|
91
111
|
} catch (error) {
|
|
92
|
-
console.error('Invalid appUrl:', error);
|
|
112
|
+
console.error('Invalid appUrl:', { error, appUrl });
|
|
93
113
|
domain = undefined;
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
const commonAttributes = {
|
|
97
|
-
secure:
|
|
98
|
-
sameSite:
|
|
99
|
-
domain
|
|
117
|
+
secure: true,
|
|
118
|
+
sameSite: 'None',
|
|
119
|
+
domain,
|
|
120
|
+
path: '/',
|
|
100
121
|
};
|
|
101
122
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
...commonAttributes,
|
|
115
|
-
httpOnly: true,
|
|
116
|
-
maxAge: accessMaxAge,
|
|
117
|
-
});
|
|
123
|
+
const httpOnlyCookies = {
|
|
124
|
+
'x-refresh-token': {
|
|
125
|
+
value: tokens.refresh.token,
|
|
126
|
+
maxAge: refreshMaxAge,
|
|
127
|
+
httpOnly: true,
|
|
128
|
+
},
|
|
129
|
+
'x-access-token': {
|
|
130
|
+
value: tokens.access.token,
|
|
131
|
+
maxAge: accessMaxAge,
|
|
132
|
+
httpOnly: true,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
118
135
|
|
|
119
|
-
// Set non-httpOnly cookies
|
|
120
136
|
const sanitizedUser = sanitizeUser(user);
|
|
121
137
|
const sanitizedAccount = { ...account };
|
|
122
138
|
delete sanitizedAccount.passwordHistory;
|
|
123
139
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
const regularCookies = {
|
|
141
|
+
user: {
|
|
142
|
+
value: safeStringify(sanitizedUser),
|
|
143
|
+
maxAge: refreshMaxAge,
|
|
144
|
+
},
|
|
145
|
+
account: {
|
|
146
|
+
value: safeStringify(sanitizedAccount),
|
|
147
|
+
maxAge: refreshMaxAge,
|
|
148
|
+
},
|
|
149
|
+
has_account_token: {
|
|
150
|
+
value: JSON.stringify({ value: 'true', expires: accessMaxAge }),
|
|
151
|
+
maxAge: accessMaxAge,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
128
154
|
|
|
129
|
-
|
|
130
|
-
...
|
|
131
|
-
|
|
132
|
-
|
|
155
|
+
try {
|
|
156
|
+
Object.entries({ ...httpOnlyCookies, ...regularCookies }).forEach(
|
|
157
|
+
([name, config]) => {
|
|
158
|
+
res.cookie(name, config.value, {
|
|
159
|
+
...commonAttributes,
|
|
160
|
+
...config,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
);
|
|
133
164
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
165
|
+
const extensionCookiePromises = Object.entries({
|
|
166
|
+
...httpOnlyCookies,
|
|
167
|
+
...regularCookies,
|
|
168
|
+
}).map(([name, config]) => {
|
|
169
|
+
return setChromeExtensionCookie({
|
|
170
|
+
url: `https://${domain || 'mapmap.app'}`,
|
|
171
|
+
name,
|
|
172
|
+
value: config.value,
|
|
173
|
+
secure: true,
|
|
174
|
+
httpOnly: !!config.httpOnly,
|
|
175
|
+
sameSite: 'no_restriction',
|
|
176
|
+
path: '/',
|
|
177
|
+
expirationDate: Math.floor((Date.now() + config.maxAge) / 1000),
|
|
178
|
+
domain: domain?.startsWith('.') ? domain : `.${domain || 'mapmap.app'}`,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await Promise.allSettled(extensionCookiePromises);
|
|
142
183
|
|
|
143
|
-
|
|
144
|
-
|
|
184
|
+
console.log('Auth cookies set successfully', {
|
|
185
|
+
domain,
|
|
186
|
+
sameSite: commonAttributes.sameSite,
|
|
187
|
+
cookieNames: [
|
|
188
|
+
...Object.keys(httpOnlyCookies),
|
|
189
|
+
...Object.keys(regularCookies),
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('Error setting cookies:', {
|
|
194
|
+
error: error.message,
|
|
195
|
+
stack: error.stack,
|
|
196
|
+
});
|
|
197
|
+
throw new Error('Failed to set authentication cookies');
|
|
198
|
+
}
|
|
145
199
|
};
|
|
146
200
|
|
|
147
201
|
/**
|
|
148
|
-
* Clears
|
|
149
|
-
*
|
|
150
|
-
* @param {Object} res - The response object.
|
|
151
|
-
* @param {string} appUrl - The URL of the client application.
|
|
202
|
+
* Clears cookies from both web and extension contexts
|
|
152
203
|
*/
|
|
153
|
-
const clearAuthCookies = (res, appUrl) => {
|
|
204
|
+
const clearAuthCookies = async (res, appUrl) => {
|
|
154
205
|
let domain;
|
|
155
206
|
try {
|
|
156
207
|
domain = appUrl ? new URL(appUrl).hostname : undefined;
|
|
157
|
-
if (domain
|
|
208
|
+
if (domain?.includes('mapmap.app')) {
|
|
158
209
|
domain = '.mapmap.app';
|
|
159
210
|
}
|
|
211
|
+
if (domain?.includes('localhost')) {
|
|
212
|
+
domain = undefined;
|
|
213
|
+
}
|
|
160
214
|
} catch (error) {
|
|
161
215
|
console.error('Invalid appUrl:', error);
|
|
162
216
|
domain = undefined;
|
|
163
217
|
}
|
|
164
218
|
|
|
165
219
|
const commonAttributes = {
|
|
166
|
-
secure:
|
|
167
|
-
sameSite:
|
|
168
|
-
domain
|
|
220
|
+
secure: true,
|
|
221
|
+
sameSite: 'None',
|
|
222
|
+
domain,
|
|
223
|
+
path: '/',
|
|
169
224
|
};
|
|
170
225
|
|
|
171
|
-
if (commonAttributes.sameSite === 'None') {
|
|
172
|
-
commonAttributes.secure = true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
226
|
const cookieNames = [
|
|
176
227
|
'x-refresh-token',
|
|
177
228
|
'x-access-token',
|
|
@@ -180,11 +231,35 @@ const clearAuthCookies = (res, appUrl) => {
|
|
|
180
231
|
'has_account_token',
|
|
181
232
|
];
|
|
182
233
|
|
|
234
|
+
// Clear web cookies
|
|
183
235
|
cookieNames.forEach(cookieName => {
|
|
184
236
|
res.clearCookie(cookieName, commonAttributes);
|
|
185
237
|
});
|
|
186
238
|
|
|
187
|
-
|
|
239
|
+
try {
|
|
240
|
+
const extensionClearPromises = cookieNames.map(
|
|
241
|
+
name =>
|
|
242
|
+
new Promise(resolve => {
|
|
243
|
+
chrome.cookies.remove(
|
|
244
|
+
{
|
|
245
|
+
url: `https://${domain || 'mapmap.app'}`,
|
|
246
|
+
name,
|
|
247
|
+
},
|
|
248
|
+
resolve
|
|
249
|
+
);
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
await Promise.allSettled(extensionClearPromises);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
// Not in extension context, ignore
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log('Auth cookies cleared successfully', {
|
|
259
|
+
domain,
|
|
260
|
+
cookieNames,
|
|
261
|
+
sameSite: commonAttributes.sameSite,
|
|
262
|
+
});
|
|
188
263
|
};
|
|
189
264
|
|
|
190
265
|
module.exports = {
|