Fix #6508: two console crashes in the import "map users later" workflow.

A) Import "map members": clicking a searched user threw
   `TypeError: Cannot read properties of undefined (reading '_id')` at
   `click .js-select-import`. The handler read `Template.currentData()._id`, which is
   undefined when the click lands on a CHILD of the anchor (the avatar or the name
   span). Read the WeKan user id from the anchor's `data-id` (event.currentTarget)
   instead, and guard it — robust regardless of which inner node was clicked.

B) Admin Panel / People: saving an IMPORTED (placeholder) user as an active person
   with an email threw `TypeError: Cannot read properties of undefined (reading '0')`.
   The profile submit handler read `user.emails[0].verified` (and `.address`), but an
   imported placeholder user (and some SSO users) have no `emails` array at all. Read
   the primary email defensively (missing OR empty array) via a `primaryEmail` guard.

Tests: tests/importMapUserFixes.test.cjs; tests/importMemberMapping.test.cjs updated
for the data-id mapping.

Thanks to AmigaAbattoir and xet7 !

Fixes #6508,
This commit is contained in:
Lauri Ojansivu committed 2026-07-23 00:52:46 +03:00
1 parent 1697584693
commit 2f07b29336
4 files changed
+69 -13

No files matched your search

+10 -5
View File
@@ -682,11 +682,16 @@ function importMapToUser(wekanId) {
}
Template.importMapMembersAddPopup.events({
'click .js-select-import'(event, tpl) {
// The clicked search result is a WeKan user document; map to its _id. (Was
// `__originalId`, which is never set on the results, so mapping silently did
// nothing and the suggestion could not be selected — #6493-adjacent import fix.)
importMapToUser(Template.currentData()._id);
'click .js-select-import'(event) {
// Map to the WeKan user _id carried on the .js-select-import anchor's data-id.
// Reading it from the anchor (event.currentTarget) is robust to the click
// landing on a child node (the avatar or the name span), where
// Template.currentData() is undefined and `.­_id` threw "Cannot read properties
// of undefined (reading '_id')" (#6508). (`data-id` is set to {{_id}} in the
// template; the earlier fix from `__originalId` to `_id` was correct but read
// the wrong context.)
const id = event.currentTarget.getAttribute('data-id');
if (id) importMapToUser(id);
},
// Enter selects the first (highlighted) search result, so a name can be assigned
// by keyboard without a mouse click.
+11 -6
View File
@@ -1376,15 +1376,20 @@ Template.editUserPopup.events({
const isChangePassword = password.length > 0;
const isChangeUserName = username !== user.username;
const isChangeInitials = initials.length > 0;
const isChangeEmailVerified = verified !== user.emails[0].verified;
// If previously email address has not been set, it is undefined,
// check for undefined, and allow adding email address.
// An imported (placeholder) user, and some SSO users, have NO `emails` array at
// all, so `user.emails[0]` threw "Cannot read properties of undefined (reading
// '0')" when an admin gave such a user an email in Admin Panel / People (#6508).
// Read the primary email defensively (missing array OR empty array).
const primaryEmail =
Array.isArray(user.emails) && user.emails.length ? user.emails[0] : null;
const isChangeEmailVerified =
verified !== (primaryEmail ? primaryEmail.verified : undefined);
// If no email was set before, allow adding one (compare against `false`).
const isChangeEmail =
email.toLowerCase() !==
(typeof user.emails !== 'undefined'
? user.emails[0].address.toLowerCase()
: false);
(primaryEmail ? primaryEmail.address.toLowerCase() : false);
// Build user teams list
let userTeamsList = userTeams.split(",");
+43
View File
@@ -0,0 +1,43 @@
'use strict';
// Regression for #6508 (import / map imported users later): two console-crash bugs.
// A) Import "map members": clicking a search result threw
// "Cannot read properties of undefined (reading '_id')" at click .js-select-import,
// because it read Template.currentData()._id — undefined when the click lands on a
// child node (the avatar or the name span). Read the _id from the anchor's data-id.
// B) Admin Panel / People: saving an IMPORTED (placeholder) user as active with an
// email threw "Cannot read properties of undefined (reading '0')" because the
// submit handler read user.emails[0] and a placeholder user has no `emails` array.
// Run: node tests/importMapUserFixes.test.cjs
const assert = require('assert');
const fs = require('fs');
const path = require('path');
let passed = 0;
function check(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
const read = rel => fs.readFileSync(path.join(__dirname, '..', rel), 'utf8');
check('A: .js-select-import maps by the anchor data-id, not Template.currentData()', () => {
const js = read('client/components/import/import.js');
const at = js.indexOf("'click .js-select-import'");
assert.ok(at > -1, 'the select-import handler exists');
const block = js.slice(at, at + 700);
assert.ok(/event\.currentTarget\.getAttribute\('data-id'\)/.test(block),
'reads the _id from the anchor data-id');
assert.ok(/if \(id\) importMapToUser\(id\)/.test(block), 'guards the id before mapping');
assert.ok(!/Template\.currentData\(\)\._id/.test(js),
'must not read _id from Template.currentData() (undefined on a child click)');
});
check('B: People submit reads the primary email defensively (no unguarded emails[0])', () => {
const js = read('client/components/settings/peopleBody.js');
assert.ok(/Array\.isArray\(user\.emails\) && user\.emails\.length \? user\.emails\[0\] : null/.test(js),
'primary email is read defensively (missing / empty emails array)');
assert.ok(/primaryEmail \? primaryEmail\.verified : undefined/.test(js), 'verified compares against the guarded email');
assert.ok(/primaryEmail \? primaryEmail\.address\.toLowerCase\(\) : false/.test(js), 'address compares against the guarded email');
// The old unguarded access must be gone.
assert.ok(!/verified !== user\.emails\[0\]\.verified/.test(js), 'the unguarded user.emails[0].verified access is removed');
});
console.log(`\nimportMapUserFixes: ${passed} checks passed`);
+5 -2
View File
@@ -30,9 +30,12 @@ test('selecting a searched user maps its _id (not the never-set __originalId)',
// The click handler maps the clicked user document by _id.
const clickIdx = js.indexOf("'click .js-select-import'");
assert.ok(clickIdx > -1, 'the select-import click handler exists');
const clickBlock = js.slice(clickIdx, clickIdx + 600);
const clickBlock = js.slice(clickIdx, clickIdx + 1000);
assert.ok(/mapSelectedMember|importMapToUser/.test(clickBlock), 'the click maps the selected member');
assert.ok(/Template\.currentData\(\)\._id/.test(js), 'maps the search result by its _id');
// #6508: read the _id from the anchor's data-id (robust to a click on a child
// node), not Template.currentData() which is undefined there and threw.
assert.ok(/getAttribute\('data-id'\)/.test(clickBlock), 'maps the search result by its data-id');
assert.ok(!/Template\.currentData\(\)\._id/.test(js), 'must not read _id from Template.currentData() (child-click undefined)');
});
test('Enter selects the first search result (keyboard, no mouse needed)', () => {