diff --git a/server/integrations/partsManagement/endpoints/partsManagementProvisioning.js b/server/integrations/partsManagement/endpoints/partsManagementProvisioning.js index 374002a79..de4cb8b0b 100644 --- a/server/integrations/partsManagement/endpoints/partsManagementProvisioning.js +++ b/server/integrations/partsManagement/endpoints/partsManagementProvisioning.js @@ -77,9 +77,8 @@ const generateResetLink = async (email) => { */ const ensureExternalIdUnique = async (externalId) => { const resp = await client.request(CHECK_EXTERNAL_SHOP_ID, { key: externalId }); - if (resp.bodyshops.length) { - throw { status: 400, message: `external_shop_id '${externalId}' is already in use.` }; - } + + return !!resp.bodyshops.length; }; /** @@ -225,10 +224,25 @@ const patchPartsManagementProvisioning = async (req, res) => { */ const partsManagementProvisioning = async (req, res) => { const { logger } = req; - const body = { ...req.body, userEmail: req.body.userEmail?.toLowerCase() }; + + // Trim and normalize email early + const body = { + ...req.body, + userEmail: req.body.userEmail?.trim().toLowerCase() + }; + + const trim = (value) => (typeof value === "string" ? value.trim() : value); + const trimIfString = (value) => + value !== null && value !== undefined && typeof value === "string" ? value.trim() : value; try { + // Ensure email is present and trimmed before checking registration + if (!body.userEmail) { + throw { status: 400, message: "userEmail is required" }; + } + await ensureEmailNotRegistered(body.userEmail); + requireFields(body, [ "external_shop_id", "shopname", @@ -242,28 +256,68 @@ const partsManagementProvisioning = async (req, res) => { "userEmail" ]); - // TODO add in check for early access - await ensureExternalIdUnique(body.external_shop_id); + // Trim all top-level string fields + const trimmedBody = { + ...body, + external_shop_id: trim(body.external_shop_id), + shopname: trim(body.shopname), + address1: trim(body.address1), + address2: trimIfString(body.address2), + city: trim(body.city), + state: trim(body.state), + zip_post: trim(body.zip_post), + country: trim(body.country), + email: trim(body.email), + phone: trim(body.phone), + timezone: trimIfString(body.timezone), + logoUrl: trimIfString(body.logoUrl), + userPassword: body.userPassword, // passwords should NOT be trimmed (preserves intentional spaces if any, though rare) + vendors: Array.isArray(body.vendors) + ? body.vendors.map((v) => ({ + name: trim(v.name), + street1: trimIfString(v.street1), + street2: trimIfString(v.street2), + city: trimIfString(v.city), + state: trimIfString(v.state), + zip: trimIfString(v.zip), + country: trimIfString(v.country), + email: trimIfString(v.email), + cost_center: trimIfString(v.cost_center), + phone: trimIfString(v.phone), + dmsid: trimIfString(v.dmsid), + discount: v.discount ?? 0, + due_date: v.due_date ?? null, + favorite: v.favorite ?? [], + active: v.active ?? true + })) + : [] + }; - logger.log("admin-create-shop-user", "debug", body.userEmail, null, { + const duplicateCheck = await ensureExternalIdUnique(trimmedBody.external_shop_id); + + if (duplicateCheck) { + throw { status: 400, message: `external_shop_id '${trimmedBody.external_shop_id}' is already in use.` }; + } + + logger.log("admin-create-shop-user", "debug", trimmedBody.userEmail, null, { request: req.body, ioadmin: true }); const shopInput = { - shopname: body.shopname, - address1: body.address1, - address2: body.address2 || null, - city: body.city, - state: body.state, - zip_post: body.zip_post, - country: body.country, - email: body.email, - external_shop_id: body.external_shop_id, - timezone: body.timezone || DefaultNewShop.timezone, - phone: body.phone, + shopname: trimmedBody.shopname, + address1: trimmedBody.address1, + address2: trimmedBody.address2, + city: trimmedBody.city, + state: trimmedBody.state, + zip_post: trimmedBody.zip_post, + country: trimmedBody.country, + email: trimmedBody.email, + external_shop_id: trimmedBody.external_shop_id, + timezone: trimmedBody.timezone || DefaultNewShop.timezone, + phone: trimmedBody.phone, logo_img_path: { - src: body.logoUrl, + src: trimmedBody.logoUrl || null, // allow empty logo width: "", height: "", headerMargin: DefaultNewShop.logo_img_path.headerMargin @@ -288,35 +342,37 @@ const partsManagementProvisioning = async (req, res) => { appt_alt_transport: DefaultNewShop.appt_alt_transport, md_jobline_presets: DefaultNewShop.md_jobline_presets, vendors: { - data: body.vendors.map((v) => ({ + data: trimmedBody.vendors.map((v) => ({ name: v.name, - street1: v.street1 || null, - street2: v.street2 || null, - city: v.city || null, - state: v.state || null, - zip: v.zip || null, - country: v.country || null, - email: v.email || null, - discount: v.discount ?? 0, - due_date: v.due_date ?? null, - cost_center: v.cost_center || null, - favorite: v.favorite ?? [], - phone: v.phone || null, - active: v.active ?? true, - dmsid: v.dmsid || null + street1: v.street1, + street2: v.street2, + city: v.city, + state: v.state, + zip: v.zip, + country: v.country, + email: v.email, + discount: v.discount, + due_date: v.due_date, + cost_center: v.cost_center, + favorite: v.favorite, + phone: v.phone, + active: v.active, + dmsid: v.dmsid })) } }; const newShopId = await insertBodyshop(shopInput); - const userRecord = await createFirebaseUser(body.userEmail, body.userPassword); + const userRecord = await createFirebaseUser(trimmedBody.userEmail, trimmedBody.userPassword); let resetLink = null; - if (!body.userPassword) resetLink = await generateResetLink(body.userEmail); + if (!trimmedBody.userPassword) { + resetLink = await generateResetLink(trimmedBody.userEmail); + } - const createdUser = await insertUserAssociation(userRecord.uid, body.userEmail, newShopId); + const createdUser = await insertUserAssociation(userRecord.uid, trimmedBody.userEmail, newShopId); return res.status(200).json({ - shop: { id: newShopId, shopname: body.shopname }, + shop: { id: newShopId, shopname: trimmedBody.shopname }, user: { id: createdUser.id, email: createdUser.email, @@ -324,7 +380,7 @@ const partsManagementProvisioning = async (req, res) => { } }); } catch (err) { - logger.log("admin-create-shop-user-error", "error", body.userEmail, null, { + logger.log("admin-create-shop-user-error", "error", body.userEmail || "unknown", null, { message: err.message, detail: err.detail || err });