@infuro/cms-core 1.0.39 → 1.0.41

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 {
6546
+ const validation = await validateInviteTokenForActivation(dataSource, entityMap, token);
6547
+ if (!validation.ok) {
6331
6548
  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) {
6355
- 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
@@ -12012,6 +12266,13 @@ function createVendorOnboardHandlers(config) {
12012
12266
  status: 400
12013
12267
  });
12014
12268
  }
12269
+ if (body.activation === "password" || body.user?.password) {
12270
+ return json({
12271
+ error: "Setting a password during vendor onboard is no longer supported. Create the vendor, then send an invite or copy the invite link so the owner can set their own password."
12272
+ }, {
12273
+ status: 400
12274
+ });
12275
+ }
12015
12276
  const profile = parseVendorProfileFromBody(body.vendor, {
12016
12277
  defaultRegistrationStatus: "approved"
12017
12278
  });
@@ -12021,34 +12282,8 @@ function createVendorOnboardHandlers(config) {
12021
12282
  }, {
12022
12283
  status: 400
12023
12284
  });
12024
- const activation = body.activation === "password" ? "password" : "invite";
12025
- const sendOwnerEmail = body.sendOwnerEmail !== false && body.sendInviteEmail !== false;
12026
- let ownerPasswordHash = null;
12027
- if (activation === "password") {
12028
- const plain = body.user?.password?.trim();
12029
- if (!plain) {
12030
- return json({
12031
- error: "Password is required when activating with a set password"
12032
- }, {
12033
- status: 400
12034
- });
12035
- }
12036
- if (plain.length < minPasswordLength) {
12037
- return json({
12038
- error: `Password must be at least ${minPasswordLength} characters`
12039
- }, {
12040
- status: 400
12041
- });
12042
- }
12043
- if (!hashPassword) {
12044
- return json({
12045
- error: "Password hashing is not configured on the server"
12046
- }, {
12047
- status: 501
12048
- });
12049
- }
12050
- ownerPasswordHash = await hashPassword(plain);
12051
- }
12285
+ const activation = "invite";
12286
+ const ownerPasswordHash = null;
12052
12287
  const slug = trimOrNull(body.vendor?.slug) || slugify(vendorName);
12053
12288
  if (!slug) return json({
12054
12289
  error: "Could not derive vendor slug"
@@ -12105,7 +12340,7 @@ function createVendorOnboardHandlers(config) {
12105
12340
  email: userEmail,
12106
12341
  phone: ownerPhone,
12107
12342
  password: ownerPasswordHash,
12108
- blocked: activation === "invite",
12343
+ blocked: true,
12109
12344
  groupId: ownerGroup.id,
12110
12345
  adminAccess: true,
12111
12346
  updatedAt: /* @__PURE__ */ new Date()
@@ -12122,10 +12357,14 @@ function createVendorOnboardHandlers(config) {
12122
12357
  email: userEmail,
12123
12358
  phone: ownerPhone,
12124
12359
  password: ownerPasswordHash,
12125
- blocked: activation === "invite",
12360
+ blocked: true,
12126
12361
  groupId: ownerGroup.id,
12127
12362
  adminAccess: true
12128
12363
  }));
12364
+ const metadata = applyRotatingVendorInvite(buildVendorMetadata(null, {
12365
+ ownerDesignation,
12366
+ termsAccepted: true
12367
+ }));
12129
12368
  const vendor = await vendorRepo.save(vendorRepo.create({
12130
12369
  name: vendorName,
12131
12370
  slug,
@@ -12145,10 +12384,7 @@ function createVendorOnboardHandlers(config) {
12145
12384
  gstin: profile.gstin,
12146
12385
  pan: profile.pan,
12147
12386
  registrationStatus: profile.registrationStatus,
12148
- metadata: buildVendorMetadata(null, {
12149
- ownerDesignation,
12150
- termsAccepted: true
12151
- }),
12387
+ metadata,
12152
12388
  active: body.vendor?.active !== false,
12153
12389
  deleted: false,
12154
12390
  userId: newUser.id,
@@ -12196,37 +12432,14 @@ function createVendorOnboardHandlers(config) {
12196
12432
  };
12197
12433
  });
12198
12434
  let inviteLink;
12199
- let emailSent = false;
12200
- if (activation === "invite") {
12201
- const emailToken = Buffer.from(result.user.email).toString("base64");
12202
- inviteLink = `${baseUrl}/admin/invite?token=${emailToken}`;
12435
+ const token = readVendorInviteToken(result.vendor.metadata);
12436
+ if (token) {
12437
+ inviteLink = buildVendorInviteLink(baseUrl, token);
12203
12438
  }
12204
- if (sendOwnerEmail) {
12205
- emailSent = await trySendVendorOnboardEmails({
12206
- vendorName,
12207
- vendorSlug: slug,
12208
- ownerName: userName,
12209
- ownerEmail: userEmail,
12210
- activation,
12211
- inviteLink,
12212
- sendToOwner: true
12213
- });
12214
- } else if (getCms) {
12215
- await trySendVendorOnboardEmails({
12216
- vendorName,
12217
- vendorSlug: slug,
12218
- ownerName: userName,
12219
- ownerEmail: userEmail,
12220
- activation,
12221
- inviteLink,
12222
- sendToOwner: false
12223
- });
12224
- }
12225
- const message = activation === "password" ? emailSent ? "Vendor onboarded successfully. Welcome email sent to the owner." : sendOwnerEmail ? "Vendor onboarded successfully. Owner can sign in with the password you set (welcome email may not have been sent \u2014 check email plugin)." : "Vendor onboarded successfully. Owner can sign in with the password you set." : emailSent ? "Vendor onboarded successfully. Invite email sent." : sendOwnerEmail ? "Vendor onboarded successfully. Invite link created (email may not have been sent \u2014 check email plugin)." : "Vendor onboarded successfully. Share the invite link with the owner.";
12226
12439
  return json({
12227
- message,
12440
+ message: "Vendor created successfully.",
12228
12441
  activation,
12229
- emailSent,
12442
+ emailSent: false,
12230
12443
  vendor: result.vendor,
12231
12444
  user: {
12232
12445
  id: result.user.id,
@@ -12241,6 +12454,137 @@ function createVendorOnboardHandlers(config) {
12241
12454
  return vendorOnboardErrorResponse(json, e);
12242
12455
  }
12243
12456
  },
12457
+ /** POST /api/admin/vendors/:id/resend-invite — rotate owner invite token and resend email */
12458
+ async resendInvite(req, vendorIdStr) {
12459
+ const err = await gateAdmin();
12460
+ if (err) return err;
12461
+ if (!entityMap.vendors || !entityMap.users) {
12462
+ return json({
12463
+ error: "Vendor entities not configured"
12464
+ }, {
12465
+ status: 500
12466
+ });
12467
+ }
12468
+ const vendorId = Number(vendorIdStr);
12469
+ if (!Number.isFinite(vendorId) || vendorId <= 0) {
12470
+ return json({
12471
+ error: "Invalid vendor id"
12472
+ }, {
12473
+ status: 400
12474
+ });
12475
+ }
12476
+ let sendEmail = true;
12477
+ let rotate = true;
12478
+ try {
12479
+ const body = await req.json().catch(() => ({}));
12480
+ if (body.sendEmail === false) sendEmail = false;
12481
+ if (body.rotate === false) rotate = false;
12482
+ } catch {
12483
+ }
12484
+ try {
12485
+ const vendorRepo = dataSource.getRepository(entityMap.vendors);
12486
+ const vendor = await vendorRepo.findOne({
12487
+ where: {
12488
+ id: vendorId,
12489
+ deleted: false
12490
+ }
12491
+ });
12492
+ if (!vendor) return json({
12493
+ error: "Vendor not found"
12494
+ }, {
12495
+ status: 404
12496
+ });
12497
+ const v = vendor;
12498
+ if (v.inviteStatus === "accepted") {
12499
+ return json({
12500
+ error: "Owner has already accepted the invite"
12501
+ }, {
12502
+ status: 400
12503
+ });
12504
+ }
12505
+ if (v.inviteStatus === "none") {
12506
+ return json({
12507
+ error: "This vendor was not created with an invite link"
12508
+ }, {
12509
+ status: 400
12510
+ });
12511
+ }
12512
+ if (v.inviteStatus !== "pending" && v.inviteStatus !== "expired") {
12513
+ return json({
12514
+ error: "Invite cannot be resent for this vendor"
12515
+ }, {
12516
+ status: 400
12517
+ });
12518
+ }
12519
+ if (!v.userId) {
12520
+ return json({
12521
+ error: "Vendor has no owner user linked"
12522
+ }, {
12523
+ status: 400
12524
+ });
12525
+ }
12526
+ const userRepo = dataSource.getRepository(entityMap.users);
12527
+ const owner = await userRepo.findOne({
12528
+ where: {
12529
+ id: v.userId,
12530
+ deleted: false
12531
+ }
12532
+ });
12533
+ if (!owner) return json({
12534
+ error: "Owner user not found"
12535
+ }, {
12536
+ status: 404
12537
+ });
12538
+ const ownerRow = owner;
12539
+ const hasPassword = !!(ownerRow.password && String(ownerRow.password).trim());
12540
+ if (!ownerRow.blocked && hasPassword) {
12541
+ return json({
12542
+ error: "Owner account is already active"
12543
+ }, {
12544
+ status: 400
12545
+ });
12546
+ }
12547
+ const nextMetadata = rotate ? applyRotatingVendorInvite(v.metadata) : {
12548
+ ...v.metadata ?? {}
12549
+ };
12550
+ if (rotate || v.inviteStatus !== "pending") {
12551
+ await vendorRepo.update(vendorId, {
12552
+ metadata: nextMetadata,
12553
+ inviteStatus: "pending"
12554
+ });
12555
+ }
12556
+ const token = readVendorInviteToken(nextMetadata);
12557
+ if (!token) {
12558
+ return json({
12559
+ error: "Failed to generate invite token"
12560
+ }, {
12561
+ status: 500
12562
+ });
12563
+ }
12564
+ const inviteLink = buildVendorInviteLink(baseUrl, token);
12565
+ let emailSent = false;
12566
+ if (sendEmail) {
12567
+ emailSent = await trySendVendorOnboardEmails({
12568
+ vendorName: v.name,
12569
+ vendorSlug: v.slug,
12570
+ ownerName: ownerRow.name,
12571
+ ownerEmail: ownerRow.email,
12572
+ activation: "invite",
12573
+ inviteLink,
12574
+ sendToOwner: true
12575
+ });
12576
+ }
12577
+ const rotatedNote = rotate ? " Previous invite link is no longer valid." : "";
12578
+ return json({
12579
+ message: emailSent ? `Invite email sent.${rotatedNote}` : sendEmail ? `Invite link ready (email may not have been sent \u2014 check email plugin).${rotatedNote}` : rotate ? "New invite link created. Previous invite link is no longer valid." : "Invite link ready.",
12580
+ emailSent,
12581
+ inviteLink,
12582
+ inviteStatus: "pending"
12583
+ });
12584
+ } catch (e) {
12585
+ return vendorOnboardErrorResponse(json, e);
12586
+ }
12587
+ },
12244
12588
  /** POST /api/admin/vendor/switch — set active vendor in session (JWT update via client) */
12245
12589
  async switchVendor(req) {
12246
12590
  const u = await getSessionUser();
@@ -25602,6 +25946,14 @@ function createCmsApiHandler(config) {
25602
25946
  });
25603
25947
  return vendorHandlers.onboard(req);
25604
25948
  }
25949
+ if (path2[0] === "admin" && path2[1] === "vendors" && path2.length === 4 && path2[3] === "resend-invite" && m === "POST") {
25950
+ if (!vendorHandlers) return config.json({
25951
+ error: "Not found"
25952
+ }, {
25953
+ status: 404
25954
+ });
25955
+ return vendorHandlers.resendInvite(req, path2[2]);
25956
+ }
25605
25957
  if (path2[0] === "admin" && path2[1] === "vendor" && path2[2] === "switch" && path2.length === 3 && m === "POST") {
25606
25958
  if (!vendorHandlers) return config.json({
25607
25959
  error: "Not found"
@@ -26193,6 +26545,15 @@ function createCmsApiHandler(config) {
26193
26545
  if (m === "PUT" || m === "PATCH") return formSaveHandlers.PUT(req, path2[1]);
26194
26546
  }
26195
26547
  }
26548
+ if (path2[0] === "users" && path2.length === 2 && userAuthRouter) {
26549
+ const authSegment = path2[1];
26550
+ if (m === "GET" && authSegment === "invite" && userAuthRouter.GET) {
26551
+ return userAuthRouter.GET(req, authSegment);
26552
+ }
26553
+ if (m === "POST" && (authSegment === "invite" || authSegment === "forgot-password" || authSegment === "set-password" || authSegment === "reset-password")) {
26554
+ return userAuthRouter.POST(req, authSegment);
26555
+ }
26556
+ }
26196
26557
  if (path2[0] === "users" && usersHandlers) {
26197
26558
  if (path2.length === 1) {
26198
26559
  if (m === "GET") return usersHandlers.list(req);
@@ -26213,9 +26574,6 @@ function createCmsApiHandler(config) {
26213
26574
  return usersHandlers.regenerateInvite(req, path2[1]);
26214
26575
  }
26215
26576
  }
26216
- if (path2[0] === "users" && path2.length === 2 && userAuthRouter && m === "POST") {
26217
- return userAuthRouter.POST(req, path2[1]);
26218
- }
26219
26577
  if (path2[0] === "social-media" && path2[1] === "linkedin" && socialMediaHandlers && path2.length === 3) {
26220
26578
  const tail = path2[2];
26221
26579
  if (tail === "status" && m === "GET") {
@@ -30610,12 +30968,14 @@ exports.BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG = BLOG_METADATA_ENRICHER_LLM_AGENT
30610
30968
  exports.BlogGeneratorService = BlogGeneratorService;
30611
30969
  exports.CMS_ENTITY_MAP = CMS_ENTITY_MAP;
30612
30970
  exports.ZIP_MIME_TYPES = ZIP_MIME_TYPES;
30971
+ exports.applyRotatingVendorInvite = applyRotatingVendorInvite;
30613
30972
  exports.applyVendorCustomersContactFilter = applyVendorCustomersContactFilter;
30614
30973
  exports.assertCaptchaOk = assertCaptchaOk;
30615
30974
  exports.assertContactAllowedForVendorOrder = assertContactAllowedForVendorOrder;
30616
30975
  exports.buildBlogMetadataUserPrompt = buildBlogMetadataUserPrompt;
30617
30976
  exports.buildCronFromSchedule = buildCronFromSchedule;
30618
30977
  exports.buildRssUserPromptFromFeeds = buildRssUserPromptFromFeeds;
30978
+ exports.buildVendorInviteLink = buildVendorInviteLink;
30619
30979
  exports.calculateOrderRefundPreview = calculateOrderRefundPreview;
30620
30980
  exports.calculateRefundFromPolicy = calculateRefundFromPolicy;
30621
30981
  exports.checkEventsEnabled = checkEventsEnabled;
@@ -30660,6 +31020,7 @@ exports.ensureMessagingPluginsOnCms = ensureMessagingPluginsOnCms;
30660
31020
  exports.ensureScheduleQueueWorker = ensureScheduleQueueWorker;
30661
31021
  exports.ensureVendorCustomerForOrderContact = ensureVendorCustomerForOrderContact;
30662
31022
  exports.findActiveRefundPolicyForVendor = findActiveRefundPolicyForVendor;
31023
+ exports.findVendorByInviteToken = findVendorByInviteToken;
30663
31024
  exports.formatTierRange = formatTierRange;
30664
31025
  exports.generateNumericOtp = generateNumericOtp;
30665
31026
  exports.getPublicSettingsGroup = getPublicSettingsGroup;