@infuro/cms-core 1.0.38 → 1.0.40

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.
@@ -6189,6 +6189,225 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
6189
6189
  };
6190
6190
  }
6191
6191
  chunkUSNT2KNT_cjs.__name(createCrudByIdHandler, "createCrudByIdHandler");
6192
+ var VENDOR_INVITE_METADATA_TOKEN = "inviteToken";
6193
+ var VENDOR_INVITE_METADATA_EXPIRES = "inviteExpiresAt";
6194
+ var VENDOR_INVITE_METADATA_SENT = "inviteSentAt";
6195
+ async function loadOwnerVendorForUser(dataSource, vendorEntity, userId) {
6196
+ const repo = dataSource.getRepository(vendorEntity);
6197
+ const row = await repo.findOne({
6198
+ where: {
6199
+ userId,
6200
+ deleted: false
6201
+ }
6202
+ });
6203
+ if (!row) return null;
6204
+ const v = row;
6205
+ return {
6206
+ id: v.id,
6207
+ inviteStatus: v.inviteStatus,
6208
+ metadata: v.metadata && typeof v.metadata === "object" && !Array.isArray(v.metadata) ? v.metadata : null
6209
+ };
6210
+ }
6211
+ chunkUSNT2KNT_cjs.__name(loadOwnerVendorForUser, "loadOwnerVendorForUser");
6212
+ function invalidatedInviteMessage() {
6213
+ return "This invitation is no longer valid. Ask an admin to resend the invite.";
6214
+ }
6215
+ chunkUSNT2KNT_cjs.__name(invalidatedInviteMessage, "invalidatedInviteMessage");
6216
+ async function validateInviteTokenForActivation(dataSource, entityMap, token) {
6217
+ const trimmedToken = String(token ?? "").trim();
6218
+ if (!trimmedToken) {
6219
+ return {
6220
+ ok: false,
6221
+ error: "Invalid invite link. Token is missing."
6222
+ };
6223
+ }
6224
+ const userRepo = entityMap.users ? dataSource.getRepository(entityMap.users) : null;
6225
+ if (entityMap.vendors && userRepo) {
6226
+ const vendorMatch = await findVendorByInviteToken(dataSource, entityMap.vendors, trimmedToken);
6227
+ if (vendorMatch) {
6228
+ const vendorRepo = dataSource.getRepository(entityMap.vendors);
6229
+ const freshVendor = await vendorRepo.findOne({
6230
+ where: {
6231
+ id: vendorMatch.id,
6232
+ deleted: false
6233
+ }
6234
+ });
6235
+ const v = freshVendor;
6236
+ if (!v?.userId) {
6237
+ return {
6238
+ ok: false,
6239
+ error: invalidatedInviteMessage()
6240
+ };
6241
+ }
6242
+ if (v.inviteStatus === "accepted") {
6243
+ return {
6244
+ ok: false,
6245
+ error: "This invitation has already been accepted"
6246
+ };
6247
+ }
6248
+ if (v.inviteStatus === "expired") {
6249
+ await vendorRepo.update(v.id, {
6250
+ inviteStatus: "pending"
6251
+ });
6252
+ v.inviteStatus = "pending";
6253
+ }
6254
+ const user2 = await userRepo.findOne({
6255
+ where: {
6256
+ id: v.userId
6257
+ },
6258
+ select: [
6259
+ "id",
6260
+ "email",
6261
+ "blocked",
6262
+ "password"
6263
+ ]
6264
+ });
6265
+ if (!user2) return {
6266
+ ok: false,
6267
+ error: "User not found"
6268
+ };
6269
+ const hasPassword2 = !!user2.password;
6270
+ if (!user2.blocked && hasPassword2) {
6271
+ return {
6272
+ ok: false,
6273
+ error: "User is already active"
6274
+ };
6275
+ }
6276
+ return {
6277
+ ok: true,
6278
+ target: {
6279
+ userId: user2.id,
6280
+ email: String(user2.email),
6281
+ vendorId: v.id
6282
+ }
6283
+ };
6284
+ }
6285
+ }
6286
+ let email;
6287
+ try {
6288
+ email = Buffer.from(trimmedToken, "base64").toString("utf8").trim();
6289
+ } catch {
6290
+ return {
6291
+ ok: false,
6292
+ error: invalidatedInviteMessage()
6293
+ };
6294
+ }
6295
+ if (!email.includes("@")) {
6296
+ return {
6297
+ ok: false,
6298
+ error: invalidatedInviteMessage()
6299
+ };
6300
+ }
6301
+ if (!userRepo) {
6302
+ return {
6303
+ ok: false,
6304
+ error: "User not found"
6305
+ };
6306
+ }
6307
+ const user = await userRepo.findOne({
6308
+ where: {
6309
+ email
6310
+ },
6311
+ select: [
6312
+ "id",
6313
+ "email",
6314
+ "blocked",
6315
+ "password"
6316
+ ]
6317
+ });
6318
+ if (!user) return {
6319
+ ok: false,
6320
+ error: "User not found"
6321
+ };
6322
+ const hasPassword = !!user.password;
6323
+ if (!user.blocked && hasPassword) {
6324
+ return {
6325
+ ok: false,
6326
+ error: "User is already active"
6327
+ };
6328
+ }
6329
+ if (entityMap.vendors) {
6330
+ const ownerVendor = await loadOwnerVendorForUser(dataSource, entityMap.vendors, user.id);
6331
+ if (ownerVendor && readVendorInviteToken(ownerVendor.metadata)) {
6332
+ return {
6333
+ ok: false,
6334
+ error: invalidatedInviteMessage()
6335
+ };
6336
+ }
6337
+ }
6338
+ return {
6339
+ ok: true,
6340
+ target: {
6341
+ userId: user.id,
6342
+ email: String(user.email)
6343
+ }
6344
+ };
6345
+ }
6346
+ chunkUSNT2KNT_cjs.__name(validateInviteTokenForActivation, "validateInviteTokenForActivation");
6347
+ function buildVendorInviteLink(baseUrl, token) {
6348
+ const base = baseUrl.replace(/\/+$/, "");
6349
+ return `${base}/admin/invite?token=${encodeURIComponent(token)}`;
6350
+ }
6351
+ chunkUSNT2KNT_cjs.__name(buildVendorInviteLink, "buildVendorInviteLink");
6352
+ function applyRotatingVendorInvite(existing) {
6353
+ const next = {
6354
+ ...existing ?? {}
6355
+ };
6356
+ next[VENDOR_INVITE_METADATA_TOKEN] = crypto2.randomBytes(32).toString("hex");
6357
+ next[VENDOR_INVITE_METADATA_SENT] = (/* @__PURE__ */ new Date()).toISOString();
6358
+ delete next[VENDOR_INVITE_METADATA_EXPIRES];
6359
+ return next;
6360
+ }
6361
+ chunkUSNT2KNT_cjs.__name(applyRotatingVendorInvite, "applyRotatingVendorInvite");
6362
+ function clearVendorInviteMetadata(existing) {
6363
+ const next = {
6364
+ ...existing ?? {}
6365
+ };
6366
+ delete next[VENDOR_INVITE_METADATA_TOKEN];
6367
+ delete next[VENDOR_INVITE_METADATA_EXPIRES];
6368
+ delete next[VENDOR_INVITE_METADATA_SENT];
6369
+ return Object.keys(next).length > 0 ? next : null;
6370
+ }
6371
+ chunkUSNT2KNT_cjs.__name(clearVendorInviteMetadata, "clearVendorInviteMetadata");
6372
+ function readVendorInviteToken(metadata) {
6373
+ const raw = metadata?.[VENDOR_INVITE_METADATA_TOKEN];
6374
+ return typeof raw === "string" && raw.trim() ? raw.trim() : null;
6375
+ }
6376
+ chunkUSNT2KNT_cjs.__name(readVendorInviteToken, "readVendorInviteToken");
6377
+ async function findVendorByInviteToken(dataSource, vendorEntity, token) {
6378
+ const trimmed = token.trim();
6379
+ if (!trimmed) return null;
6380
+ const repo = dataSource.getRepository(vendorEntity);
6381
+ const row = await repo.createQueryBuilder("v").where("v.deleted = false").andWhere(`v.metadata->>'${VENDOR_INVITE_METADATA_TOKEN}' = :token`, {
6382
+ token: trimmed
6383
+ }).getOne();
6384
+ if (!row) return null;
6385
+ const v = row;
6386
+ return {
6387
+ id: v.id,
6388
+ userId: v.userId ?? null,
6389
+ inviteStatus: v.inviteStatus,
6390
+ metadata: v.metadata && typeof v.metadata === "object" && !Array.isArray(v.metadata) ? v.metadata : null
6391
+ };
6392
+ }
6393
+ chunkUSNT2KNT_cjs.__name(findVendorByInviteToken, "findVendorByInviteToken");
6394
+ async function completeVendorInviteAccept(dataSource, vendorEntity, vendorId) {
6395
+ const repo = dataSource.getRepository(vendorEntity);
6396
+ const row = await repo.findOne({
6397
+ where: {
6398
+ id: vendorId,
6399
+ deleted: false
6400
+ }
6401
+ });
6402
+ if (!row) return;
6403
+ const metadata = row.metadata;
6404
+ const cleared = clearVendorInviteMetadata(metadata);
6405
+ await repo.update(vendorId, {
6406
+ metadata: cleared,
6407
+ inviteStatus: "accepted"
6408
+ });
6409
+ }
6410
+ chunkUSNT2KNT_cjs.__name(completeVendorInviteAccept, "completeVendorInviteAccept");
6192
6411
 
6193
6412
  // src/api/auth-handlers.ts
6194
6413
  function createForgotPasswordHandler(config) {
@@ -6324,49 +6543,27 @@ function createInviteAcceptHandler(config) {
6324
6543
  }, {
6325
6544
  status: 400
6326
6545
  });
6327
- let email;
6328
- try {
6329
- email = Buffer.from(token, "base64").toString("utf8");
6330
- } catch {
6331
- return json({
6332
- error: "Invalid or expired invite token"
6333
- }, {
6334
- status: 400
6335
- });
6336
- }
6337
- const userRepo = dataSource.getRepository(entityMap.users);
6338
- const user = await userRepo.findOne({
6339
- where: {
6340
- email
6341
- },
6342
- select: [
6343
- "id",
6344
- "blocked",
6345
- "password"
6346
- ]
6347
- });
6348
- if (!user) return json({
6349
- error: "User not found"
6350
- }, {
6351
- status: 400
6352
- });
6353
- const hasPassword = !!user.password;
6354
- if (!user.blocked && hasPassword) {
6546
+ const validation = await validateInviteTokenForActivation(dataSource, entityMap, token);
6547
+ if (!validation.ok) {
6355
6548
  return json({
6356
- error: "User is already active"
6549
+ error: validation.error
6357
6550
  }, {
6358
6551
  status: 400
6359
6552
  });
6360
6553
  }
6554
+ const { userId, email, vendorId } = validation.target;
6361
6555
  if (entityMap.contacts) {
6362
- await linkUnclaimedContactToUser(dataSource, entityMap.contacts, user.id, email);
6556
+ await linkUnclaimedContactToUser(dataSource, entityMap.contacts, userId, email);
6363
6557
  }
6364
- if (beforeActivate) await beforeActivate(email, user.id);
6558
+ if (beforeActivate) await beforeActivate(email, userId);
6365
6559
  const hashedPassword = await hashPassword(password);
6366
- await userRepo.update(user.id, {
6560
+ await dataSource.getRepository(entityMap.users).update(userId, {
6367
6561
  password: hashedPassword,
6368
6562
  blocked: false
6369
6563
  });
6564
+ if (vendorId != null && entityMap.vendors) {
6565
+ await completeVendorInviteAccept(dataSource, entityMap.vendors, vendorId);
6566
+ }
6370
6567
  return json({
6371
6568
  message: "User account activated successfully"
6372
6569
  }, {
@@ -6382,6 +6579,35 @@ function createInviteAcceptHandler(config) {
6382
6579
  }, "POST");
6383
6580
  }
6384
6581
  chunkUSNT2KNT_cjs.__name(createInviteAcceptHandler, "createInviteAcceptHandler");
6582
+ function createInviteValidateHandler(config) {
6583
+ const { dataSource, entityMap, json } = config;
6584
+ return /* @__PURE__ */ chunkUSNT2KNT_cjs.__name(async function GET(request) {
6585
+ try {
6586
+ const token = new URL(request.url).searchParams.get("token");
6587
+ const validation = await validateInviteTokenForActivation(dataSource, entityMap, token ?? "");
6588
+ if (!validation.ok) {
6589
+ return json({
6590
+ valid: false,
6591
+ error: validation.error
6592
+ }, {
6593
+ status: 400
6594
+ });
6595
+ }
6596
+ return json({
6597
+ valid: true
6598
+ });
6599
+ } catch (err) {
6600
+ console.error("[users/invite GET] validate failed", err);
6601
+ return json({
6602
+ valid: false,
6603
+ error: "Server Error"
6604
+ }, {
6605
+ status: 500
6606
+ });
6607
+ }
6608
+ }, "GET");
6609
+ }
6610
+ chunkUSNT2KNT_cjs.__name(createInviteValidateHandler, "createInviteValidateHandler");
6385
6611
  function createChangePasswordHandler(config) {
6386
6612
  const { dataSource, entityMap, json, comparePassword, hashPassword, getSession, minPasswordLength = 6, beforeUpdate } = config;
6387
6613
  return /* @__PURE__ */ chunkUSNT2KNT_cjs.__name(async function POST(request) {
@@ -6460,12 +6686,22 @@ function createUserAuthApiRouter(config) {
6460
6686
  const forgot = createForgotPasswordHandler(config);
6461
6687
  const setPass = createSetPasswordHandler(config);
6462
6688
  const invite = createInviteAcceptHandler(config);
6689
+ const inviteValidate = createInviteValidateHandler(config);
6463
6690
  const changePass = config.getSession ? createChangePasswordHandler({
6464
6691
  ...config,
6465
6692
  getSession: config.getSession,
6466
6693
  beforeUpdate: config.beforeChangePasswordUpdate
6467
6694
  }) : null;
6468
6695
  return {
6696
+ async GET(req, pathname) {
6697
+ const path2 = pathname.replace(/\/$/, "");
6698
+ if (path2 === "invite") return inviteValidate(req);
6699
+ return config.json({
6700
+ error: "Not found"
6701
+ }, {
6702
+ status: 404
6703
+ });
6704
+ },
6469
6705
  async POST(req, pathname) {
6470
6706
  const path2 = pathname.replace(/\/$/, "");
6471
6707
  if (!USER_AUTH_PATHS.includes(path2)) {
@@ -8871,13 +9107,31 @@ function createUsersApiHandlers(config) {
8871
9107
  }, {
8872
9108
  status: 404
8873
9109
  });
8874
- const emailToken = Buffer.from(user.email).toString("base64");
8875
- const inviteLink = `${baseUrl}/admin/invite?token=${emailToken}`;
8876
- await trySendInviteEmail(user.email, inviteLink, user.name ?? "");
9110
+ let inviteLink = `${baseUrl}/admin/invite?token=${Buffer.from(user.email).toString("base64")}`;
8877
9111
  if (entityMap.vendors) {
9112
+ const vendorRepo = dataSource.getRepository(entityMap.vendors);
9113
+ const vendor = await vendorRepo.findOne({
9114
+ where: {
9115
+ userId: user.id,
9116
+ deleted: false
9117
+ }
9118
+ });
9119
+ const v = vendor;
9120
+ if (v && (v.inviteStatus === "pending" || v.inviteStatus === "expired")) {
9121
+ const rotatedMetadata = applyRotatingVendorInvite(v.metadata);
9122
+ await vendorRepo.update(v.id, {
9123
+ metadata: rotatedMetadata,
9124
+ inviteStatus: "pending"
9125
+ });
9126
+ const token = readVendorInviteToken(rotatedMetadata);
9127
+ if (token) {
9128
+ inviteLink = buildVendorInviteLink(baseUrl, token);
9129
+ }
9130
+ }
8878
9131
  const { markVendorInvitePendingForUser } = await import('./vendor-invite-status-77O7ZQ3U.cjs');
8879
9132
  await markVendorInvitePendingForUser(dataSource, entityMap.vendors, user.id);
8880
9133
  }
9134
+ await trySendInviteEmail(user.email, inviteLink, user.name ?? "");
8881
9135
  return json({
8882
9136
  message: "New invite link generated successfully",
8883
9137
  inviteLink
@@ -11821,6 +12075,84 @@ function slugify(input) {
11821
12075
  return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
11822
12076
  }
11823
12077
  chunkUSNT2KNT_cjs.__name(slugify, "slugify");
12078
+ function vendorOnboardErrorResponse(json, err) {
12079
+ const msg = err instanceof Error ? err.message : String(err);
12080
+ console.error("[vendor-onboard]", err);
12081
+ if (msg === "VENDOR_SLUG_EXISTS") {
12082
+ return json({
12083
+ error: "Vendor slug already exists"
12084
+ }, {
12085
+ status: 400
12086
+ });
12087
+ }
12088
+ if (msg === "USER_EMAIL_EXISTS") {
12089
+ return json({
12090
+ error: "User with this email already exists"
12091
+ }, {
12092
+ status: 400
12093
+ });
12094
+ }
12095
+ if (msg === "VENDOR_OWNER_GROUP_MISSING") {
12096
+ return json({
12097
+ error: "Vendor Owner group not found. Run migrations (VendorOwnerRbacSeed)."
12098
+ }, {
12099
+ status: 500
12100
+ });
12101
+ }
12102
+ if (msg === "VENDOR_ROLES_SEED_FAILED") {
12103
+ return json({
12104
+ error: "Failed to seed store roles for vendor. Run migrations."
12105
+ }, {
12106
+ status: 500
12107
+ });
12108
+ }
12109
+ if (/vendor_roles|vendor_role_permissions/i.test(msg)) {
12110
+ return json({
12111
+ error: "Vendor roles tables missing. Run migrations."
12112
+ }, {
12113
+ status: 500
12114
+ });
12115
+ }
12116
+ if (/relation .* does not exist|column .* does not exist/i.test(msg)) {
12117
+ return json({
12118
+ error: "Database schema is outdated on this environment. Run migrations.",
12119
+ detail: msg
12120
+ }, {
12121
+ status: 500
12122
+ });
12123
+ }
12124
+ if (/duplicate key|unique constraint/i.test(msg)) {
12125
+ if (/email/i.test(msg)) {
12126
+ return json({
12127
+ error: "A user or record with this email already exists",
12128
+ detail: msg
12129
+ }, {
12130
+ status: 409
12131
+ });
12132
+ }
12133
+ if (/slug/i.test(msg)) {
12134
+ return json({
12135
+ error: "Vendor slug already exists",
12136
+ detail: msg
12137
+ }, {
12138
+ status: 409
12139
+ });
12140
+ }
12141
+ return json({
12142
+ error: "Duplicate value conflicts with an existing record",
12143
+ detail: msg
12144
+ }, {
12145
+ status: 409
12146
+ });
12147
+ }
12148
+ return json({
12149
+ error: "Server error",
12150
+ detail: msg
12151
+ }, {
12152
+ status: 500
12153
+ });
12154
+ }
12155
+ chunkUSNT2KNT_cjs.__name(vendorOnboardErrorResponse, "vendorOnboardErrorResponse");
11824
12156
  function createVendorOnboardHandlers(config) {
11825
12157
  const { dataSource, entityMap, json, getSessionUser, baseUrl, getCms, hashPassword, minPasswordLength = 6 } = config;
11826
12158
  async function gateAdmin() {
@@ -11989,12 +12321,28 @@ function createVendorOnboardHandlers(config) {
11989
12321
  }
11990
12322
  });
11991
12323
  if (dupVendor) throw new Error("VENDOR_SLUG_EXISTS");
11992
- const ownerGroup = await groupRepo.findOne({
12324
+ let ownerGroup = await groupRepo.findOne({
11993
12325
  where: {
11994
12326
  name: chunkWMXPKKXU_cjs.VENDOR_OWNER_GROUP_NAME,
11995
12327
  deleted: false
11996
12328
  }
11997
12329
  });
12330
+ if (!ownerGroup) {
12331
+ try {
12332
+ await groupRepo.save(groupRepo.create({
12333
+ name: chunkWMXPKKXU_cjs.VENDOR_OWNER_GROUP_NAME,
12334
+ deleted: false
12335
+ }));
12336
+ ownerGroup = await groupRepo.findOne({
12337
+ where: {
12338
+ name: chunkWMXPKKXU_cjs.VENDOR_OWNER_GROUP_NAME,
12339
+ deleted: false
12340
+ }
12341
+ });
12342
+ } catch (seedErr) {
12343
+ console.error("[vendor-onboard] failed to seed Vendor Owner group", seedErr);
12344
+ }
12345
+ }
11998
12346
  if (!ownerGroup) throw new Error("VENDOR_OWNER_GROUP_MISSING");
11999
12347
  const existingUser = await userRepo.findOne({
12000
12348
  where: {
@@ -12032,6 +12380,11 @@ function createVendorOnboardHandlers(config) {
12032
12380
  groupId: ownerGroup.id,
12033
12381
  adminAccess: true
12034
12382
  }));
12383
+ const baseMetadata = buildVendorMetadata(null, {
12384
+ ownerDesignation,
12385
+ termsAccepted: true
12386
+ });
12387
+ const metadata = activation === "invite" ? applyRotatingVendorInvite(baseMetadata) : baseMetadata;
12035
12388
  const vendor = await vendorRepo.save(vendorRepo.create({
12036
12389
  name: vendorName,
12037
12390
  slug,
@@ -12051,10 +12404,7 @@ function createVendorOnboardHandlers(config) {
12051
12404
  gstin: profile.gstin,
12052
12405
  pan: profile.pan,
12053
12406
  registrationStatus: profile.registrationStatus,
12054
- metadata: buildVendorMetadata(null, {
12055
- ownerDesignation,
12056
- termsAccepted: true
12057
- }),
12407
+ metadata,
12058
12408
  active: body.vendor?.active !== false,
12059
12409
  deleted: false,
12060
12410
  userId: newUser.id,
@@ -12083,14 +12433,18 @@ function createVendorOnboardHandlers(config) {
12083
12433
  });
12084
12434
  }
12085
12435
  if (entityMap.customer) {
12086
- await ensureCustomerForUser(em, entityMap.customer, {
12087
- id: newUser.id,
12088
- name: userName,
12089
- email: userEmail,
12090
- phone: trimOrNull(body.vendor?.phone) ?? ownerPhone
12091
- }, {
12092
- phone: trimOrNull(body.vendor?.phone) ?? ownerPhone
12093
- });
12436
+ try {
12437
+ await ensureCustomerForUser(em, entityMap.customer, {
12438
+ id: newUser.id,
12439
+ name: userName,
12440
+ email: userEmail,
12441
+ phone: trimOrNull(body.vendor?.phone) ?? ownerPhone
12442
+ }, {
12443
+ phone: trimOrNull(body.vendor?.phone) ?? ownerPhone
12444
+ });
12445
+ } catch (customerErr) {
12446
+ console.error("[vendor-onboard] ensureCustomerForUser skipped", customerErr);
12447
+ }
12094
12448
  }
12095
12449
  return {
12096
12450
  vendor,
@@ -12100,8 +12454,10 @@ function createVendorOnboardHandlers(config) {
12100
12454
  let inviteLink;
12101
12455
  let emailSent = false;
12102
12456
  if (activation === "invite") {
12103
- const emailToken = Buffer.from(result.user.email).toString("base64");
12104
- inviteLink = `${baseUrl}/admin/invite?token=${emailToken}`;
12457
+ const token = readVendorInviteToken(result.vendor.metadata);
12458
+ if (token) {
12459
+ inviteLink = buildVendorInviteLink(baseUrl, token);
12460
+ }
12105
12461
  }
12106
12462
  if (sendOwnerEmail) {
12107
12463
  emailSent = await trySendVendorOnboardEmails({
@@ -12140,46 +12496,131 @@ function createVendorOnboardHandlers(config) {
12140
12496
  status: 201
12141
12497
  });
12142
12498
  } catch (e) {
12143
- const msg = e instanceof Error ? e.message : String(e);
12144
- if (process.env.NODE_ENV === "development") {
12145
- console.error("[vendor-onboard]", e);
12146
- }
12147
- if (msg === "VENDOR_SLUG_EXISTS") return json({
12148
- error: "Vendor slug already exists"
12499
+ return vendorOnboardErrorResponse(json, e);
12500
+ }
12501
+ },
12502
+ /** POST /api/admin/vendors/:id/resend-invite — rotate owner invite token and resend email */
12503
+ async resendInvite(req, vendorIdStr) {
12504
+ const err = await gateAdmin();
12505
+ if (err) return err;
12506
+ if (!entityMap.vendors || !entityMap.users) {
12507
+ return json({
12508
+ error: "Vendor entities not configured"
12149
12509
  }, {
12150
- status: 400
12510
+ status: 500
12151
12511
  });
12152
- if (msg === "USER_EMAIL_EXISTS") return json({
12153
- error: "User with this email already exists"
12512
+ }
12513
+ const vendorId = Number(vendorIdStr);
12514
+ if (!Number.isFinite(vendorId) || vendorId <= 0) {
12515
+ return json({
12516
+ error: "Invalid vendor id"
12154
12517
  }, {
12155
12518
  status: 400
12156
12519
  });
12157
- if (msg === "VENDOR_OWNER_GROUP_MISSING") {
12520
+ }
12521
+ let sendEmail = true;
12522
+ try {
12523
+ const body = await req.json().catch(() => ({}));
12524
+ if (body.sendEmail === false) sendEmail = false;
12525
+ } catch {
12526
+ }
12527
+ try {
12528
+ const vendorRepo = dataSource.getRepository(entityMap.vendors);
12529
+ const vendor = await vendorRepo.findOne({
12530
+ where: {
12531
+ id: vendorId,
12532
+ deleted: false
12533
+ }
12534
+ });
12535
+ if (!vendor) return json({
12536
+ error: "Vendor not found"
12537
+ }, {
12538
+ status: 404
12539
+ });
12540
+ const v = vendor;
12541
+ if (v.inviteStatus === "accepted") {
12158
12542
  return json({
12159
- error: "Vendor Owner group not found. Run migrations."
12543
+ error: "Owner has already accepted the invite"
12160
12544
  }, {
12161
- status: 500
12545
+ status: 400
12162
12546
  });
12163
12547
  }
12164
- if (msg === "VENDOR_ROLES_SEED_FAILED") {
12548
+ if (v.inviteStatus === "none") {
12165
12549
  return json({
12166
- error: "Failed to seed store roles for vendor. Run migrations."
12550
+ error: "This vendor was not created with an invite link"
12167
12551
  }, {
12168
- status: 500
12552
+ status: 400
12553
+ });
12554
+ }
12555
+ if (v.inviteStatus !== "pending" && v.inviteStatus !== "expired") {
12556
+ return json({
12557
+ error: "Invite cannot be resent for this vendor"
12558
+ }, {
12559
+ status: 400
12560
+ });
12561
+ }
12562
+ if (!v.userId) {
12563
+ return json({
12564
+ error: "Vendor has no owner user linked"
12565
+ }, {
12566
+ status: 400
12567
+ });
12568
+ }
12569
+ const userRepo = dataSource.getRepository(entityMap.users);
12570
+ const owner = await userRepo.findOne({
12571
+ where: {
12572
+ id: v.userId,
12573
+ deleted: false
12574
+ }
12575
+ });
12576
+ if (!owner) return json({
12577
+ error: "Owner user not found"
12578
+ }, {
12579
+ status: 404
12580
+ });
12581
+ const ownerRow = owner;
12582
+ const hasPassword = !!(ownerRow.password && String(ownerRow.password).trim());
12583
+ if (!ownerRow.blocked && hasPassword) {
12584
+ return json({
12585
+ error: "Owner account is already active"
12586
+ }, {
12587
+ status: 400
12169
12588
  });
12170
12589
  }
12171
- if (/vendor_roles|vendor_role_permissions/i.test(msg)) {
12590
+ const rotatedMetadata = applyRotatingVendorInvite(v.metadata);
12591
+ await vendorRepo.update(vendorId, {
12592
+ metadata: rotatedMetadata,
12593
+ inviteStatus: "pending"
12594
+ });
12595
+ const token = readVendorInviteToken(rotatedMetadata);
12596
+ if (!token) {
12172
12597
  return json({
12173
- error: "Vendor roles tables missing. Run migrations."
12598
+ error: "Failed to generate invite token"
12174
12599
  }, {
12175
12600
  status: 500
12176
12601
  });
12177
12602
  }
12603
+ const inviteLink = buildVendorInviteLink(baseUrl, token);
12604
+ let emailSent = false;
12605
+ if (sendEmail) {
12606
+ emailSent = await trySendVendorOnboardEmails({
12607
+ vendorName: v.name,
12608
+ vendorSlug: v.slug,
12609
+ ownerName: ownerRow.name,
12610
+ ownerEmail: ownerRow.email,
12611
+ activation: "invite",
12612
+ inviteLink,
12613
+ sendToOwner: true
12614
+ });
12615
+ }
12178
12616
  return json({
12179
- error: "Server error"
12180
- }, {
12181
- status: 500
12617
+ message: emailSent ? "Invite resent. Previous invite link is no longer valid." : sendEmail ? "New invite link created (email may not have been sent \u2014 check email plugin). Previous link is invalid." : "New invite link created. Previous invite link is no longer valid.",
12618
+ emailSent,
12619
+ inviteLink,
12620
+ inviteStatus: "pending"
12182
12621
  });
12622
+ } catch (e) {
12623
+ return vendorOnboardErrorResponse(json, e);
12183
12624
  }
12184
12625
  },
12185
12626
  /** POST /api/admin/vendor/switch — set active vendor in session (JWT update via client) */
@@ -25543,6 +25984,14 @@ function createCmsApiHandler(config) {
25543
25984
  });
25544
25985
  return vendorHandlers.onboard(req);
25545
25986
  }
25987
+ if (path2[0] === "admin" && path2[1] === "vendors" && path2.length === 4 && path2[3] === "resend-invite" && m === "POST") {
25988
+ if (!vendorHandlers) return config.json({
25989
+ error: "Not found"
25990
+ }, {
25991
+ status: 404
25992
+ });
25993
+ return vendorHandlers.resendInvite(req, path2[2]);
25994
+ }
25546
25995
  if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "switch" && path2.length === 3 && m === "POST") {
25547
25996
  if (!vendorHandlers) return config.json({
25548
25997
  error: "Not found"
@@ -26134,6 +26583,15 @@ function createCmsApiHandler(config) {
26134
26583
  if (m === "PUT" || m === "PATCH") return formSaveHandlers.PUT(req, path2[1]);
26135
26584
  }
26136
26585
  }
26586
+ if (path2[0] === "users" && path2.length === 2 && userAuthRouter) {
26587
+ const authSegment = path2[1];
26588
+ if (m === "GET" && authSegment === "invite" && userAuthRouter.GET) {
26589
+ return userAuthRouter.GET(req, authSegment);
26590
+ }
26591
+ if (m === "POST" && (authSegment === "invite" || authSegment === "forgot-password" || authSegment === "set-password" || authSegment === "reset-password")) {
26592
+ return userAuthRouter.POST(req, authSegment);
26593
+ }
26594
+ }
26137
26595
  if (path2[0] === "users" && usersHandlers) {
26138
26596
  if (path2.length === 1) {
26139
26597
  if (m === "GET") return usersHandlers.list(req);
@@ -26154,9 +26612,6 @@ function createCmsApiHandler(config) {
26154
26612
  return usersHandlers.regenerateInvite(req, path2[1]);
26155
26613
  }
26156
26614
  }
26157
- if (path2[0] === "users" && path2.length === 2 && userAuthRouter && m === "POST") {
26158
- return userAuthRouter.POST(req, path2[1]);
26159
- }
26160
26615
  if (path2[0] === "social-media" && path2[1] === "linkedin" && socialMediaHandlers && path2.length === 3) {
26161
26616
  const tail = path2[2];
26162
26617
  if (tail === "status" && m === "GET") {
@@ -30551,12 +31006,14 @@ exports.BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG = BLOG_METADATA_ENRICHER_LLM_AGENT
30551
31006
  exports.BlogGeneratorService = BlogGeneratorService;
30552
31007
  exports.CMS_ENTITY_MAP = CMS_ENTITY_MAP;
30553
31008
  exports.ZIP_MIME_TYPES = ZIP_MIME_TYPES;
31009
+ exports.applyRotatingVendorInvite = applyRotatingVendorInvite;
30554
31010
  exports.applyVendorCustomersContactFilter = applyVendorCustomersContactFilter;
30555
31011
  exports.assertCaptchaOk = assertCaptchaOk;
30556
31012
  exports.assertContactAllowedForVendorOrder = assertContactAllowedForVendorOrder;
30557
31013
  exports.buildBlogMetadataUserPrompt = buildBlogMetadataUserPrompt;
30558
31014
  exports.buildCronFromSchedule = buildCronFromSchedule;
30559
31015
  exports.buildRssUserPromptFromFeeds = buildRssUserPromptFromFeeds;
31016
+ exports.buildVendorInviteLink = buildVendorInviteLink;
30560
31017
  exports.calculateOrderRefundPreview = calculateOrderRefundPreview;
30561
31018
  exports.calculateRefundFromPolicy = calculateRefundFromPolicy;
30562
31019
  exports.checkEventsEnabled = checkEventsEnabled;
@@ -30601,6 +31058,7 @@ exports.ensureMessagingPluginsOnCms = ensureMessagingPluginsOnCms;
30601
31058
  exports.ensureScheduleQueueWorker = ensureScheduleQueueWorker;
30602
31059
  exports.ensureVendorCustomerForOrderContact = ensureVendorCustomerForOrderContact;
30603
31060
  exports.findActiveRefundPolicyForVendor = findActiveRefundPolicyForVendor;
31061
+ exports.findVendorByInviteToken = findVendorByInviteToken;
30604
31062
  exports.formatTierRange = formatTierRange;
30605
31063
  exports.generateNumericOtp = generateNumericOtp;
30606
31064
  exports.getPublicSettingsGroup = getPublicSettingsGroup;