propro-utils 1.5.76 → 1.5.78
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
|
@@ -109,105 +109,194 @@ const DEFAULT_THEME = {
|
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
* Checks
|
|
113
|
-
*
|
|
114
|
-
* 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.
|
|
115
114
|
*
|
|
116
|
-
* @param {string} accountId - The account ID of the user to check/create
|
|
117
|
-
* @returns {Promise<Object>} A promise that resolves to the user object
|
|
118
|
-
* @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
|
|
119
118
|
*/
|
|
120
119
|
const checkIfUserExists = async accountId => {
|
|
120
|
+
// Input validation
|
|
121
|
+
if (!accountId || typeof accountId !== 'string') {
|
|
122
|
+
throw new Error('Invalid accountId provided');
|
|
123
|
+
}
|
|
124
|
+
|
|
121
125
|
try {
|
|
122
|
-
|
|
123
|
-
const userStyleSchema
|
|
124
|
-
|
|
125
|
-
|
|
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
|
+
}
|
|
126
138
|
|
|
127
|
-
|
|
139
|
+
// Find existing user with populated fields
|
|
140
|
+
const user = await userSchema
|
|
128
141
|
.findOne({ accountId })
|
|
129
142
|
.populate('userGlobalStyles')
|
|
130
|
-
.populate('theme')
|
|
143
|
+
.populate('theme')
|
|
144
|
+
.lean();
|
|
131
145
|
|
|
132
146
|
if (user) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
}
|
|
142
171
|
}
|
|
143
|
-
// Check if user has any folders
|
|
144
|
-
const userFolders = await folderSchema.find({ user_id: user.id });
|
|
145
|
-
const defaultFolderNames = defaultFolders.map(folder => folder.name);
|
|
146
|
-
const userFolderNames = userFolders.map(folder => folder.name);
|
|
147
172
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
);
|
|
151
188
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
189
|
+
if (foldersToCreate.length > 0) {
|
|
190
|
+
return folderSchema.insertMany(
|
|
191
|
+
foldersToCreate.map(folder => ({
|
|
192
|
+
...folder,
|
|
193
|
+
user_id: user.id,
|
|
194
|
+
}))
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
})()
|
|
160
198
|
);
|
|
161
199
|
}
|
|
162
200
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
isDefault: true,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
await userSchema.updateOne(
|
|
220
|
+
{ _id: user._id },
|
|
221
|
+
{ theme: [defaultTheme._id] }
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
})()
|
|
225
|
+
);
|
|
177
226
|
}
|
|
178
227
|
|
|
179
|
-
|
|
228
|
+
// Wait for all updates to complete
|
|
229
|
+
await Promise.all(updates);
|
|
230
|
+
|
|
231
|
+
// Return fresh user data after updates
|
|
232
|
+
return userSchema
|
|
233
|
+
.findOne({ accountId })
|
|
234
|
+
.populate('userGlobalStyles')
|
|
235
|
+
.populate('theme');
|
|
180
236
|
}
|
|
181
237
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
);
|
|
238
|
+
// Create new user with all associated data
|
|
239
|
+
const userId = uuidv4();
|
|
240
|
+
const creationTasks = [];
|
|
186
241
|
|
|
187
|
-
user
|
|
242
|
+
// Prepare user global styles
|
|
243
|
+
let userGlobalStyles;
|
|
244
|
+
if (userStyleSchema) {
|
|
245
|
+
creationTasks.push(
|
|
246
|
+
createUserGlobalStyles(userStyleSchema, accountId).then(styles => {
|
|
247
|
+
userGlobalStyles = styles;
|
|
248
|
+
return styles;
|
|
249
|
+
})
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Prepare default theme
|
|
254
|
+
let defaultTheme;
|
|
255
|
+
if (themeSchema) {
|
|
256
|
+
creationTasks.push(
|
|
257
|
+
themeSchema
|
|
258
|
+
.create({
|
|
259
|
+
...DEFAULT_THEME,
|
|
260
|
+
accountId,
|
|
261
|
+
userId,
|
|
262
|
+
id: uuidv4(),
|
|
263
|
+
})
|
|
264
|
+
.then(theme => {
|
|
265
|
+
defaultTheme = theme;
|
|
266
|
+
return theme;
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Wait for parallel creation tasks
|
|
272
|
+
await Promise.all(creationTasks);
|
|
273
|
+
|
|
274
|
+
// Create the user with all references
|
|
275
|
+
const newUser = await userSchema.create({
|
|
188
276
|
accountId,
|
|
189
|
-
id:
|
|
277
|
+
id: userId,
|
|
190
278
|
verified: false,
|
|
191
|
-
userGlobalStyles: userGlobalStyles
|
|
279
|
+
userGlobalStyles: userGlobalStyles?._id,
|
|
280
|
+
theme: defaultTheme ? [defaultTheme._id] : undefined,
|
|
192
281
|
});
|
|
193
282
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
283
|
+
// Create folders if schema available
|
|
284
|
+
if (folderSchema) {
|
|
285
|
+
await createDefaultFolders(folderSchema, newUser.id);
|
|
286
|
+
}
|
|
197
287
|
|
|
198
|
-
|
|
199
|
-
|
|
288
|
+
// Return complete user object with populated fields
|
|
289
|
+
return userSchema
|
|
290
|
+
.findById(newUser._id)
|
|
291
|
+
.populate('userGlobalStyles')
|
|
292
|
+
.populate('theme');
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error('Error in checkIfUserExists:', {
|
|
200
295
|
accountId,
|
|
201
|
-
|
|
202
|
-
|
|
296
|
+
error: error.message,
|
|
297
|
+
stack: error.stack,
|
|
203
298
|
});
|
|
204
|
-
user
|
|
205
|
-
await user.save();
|
|
206
|
-
|
|
207
|
-
return user;
|
|
208
|
-
} catch (error) {
|
|
209
|
-
console.error('Error in checkIfUserExists:', error);
|
|
210
|
-
throw new Error('Failed to get or create user');
|
|
299
|
+
throw new Error(`Failed to get or create user: ${error.message}`);
|
|
211
300
|
}
|
|
212
301
|
};
|
|
213
302
|
|
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 = {
|