@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.
- package/dist/admin.cjs +108 -5
- package/dist/admin.js +109 -6
- package/dist/api.cjs +32 -32
- package/dist/api.d.cts +4 -1
- package/dist/api.d.ts +4 -1
- package/dist/api.js +1 -1
- package/dist/auth.cjs +12 -12
- package/dist/auth.js +1 -1
- package/dist/{chunk-SBRBR3VO.js → chunk-4BG7XYSH.js} +1 -0
- package/dist/{chunk-E6WVP7BK.cjs → chunk-ETZN4OQI.cjs} +1 -0
- package/dist/{chunk-PYUXVU75.js → chunk-RCMUL6SZ.js} +530 -75
- package/dist/{chunk-VJDUKAJ3.cjs → chunk-T7GCOO5Y.cjs} +531 -73
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +210 -198
- package/dist/index.d.cts +84 -68
- package/dist/index.d.ts +84 -68
- package/dist/index.js +3 -3
- package/package.json +147 -147
|
@@ -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
|
-
|
|
6328
|
-
|
|
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:
|
|
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,
|
|
6556
|
+
await linkUnclaimedContactToUser(dataSource, entityMap.contacts, userId, email);
|
|
6363
6557
|
}
|
|
6364
|
-
if (beforeActivate) await beforeActivate(email,
|
|
6558
|
+
if (beforeActivate) await beforeActivate(email, userId);
|
|
6365
6559
|
const hashedPassword = await hashPassword(password);
|
|
6366
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
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
|
|
12104
|
-
|
|
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
|
-
|
|
12144
|
-
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
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:
|
|
12510
|
+
status: 500
|
|
12151
12511
|
});
|
|
12152
|
-
|
|
12153
|
-
|
|
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
|
-
|
|
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: "
|
|
12543
|
+
error: "Owner has already accepted the invite"
|
|
12160
12544
|
}, {
|
|
12161
|
-
status:
|
|
12545
|
+
status: 400
|
|
12162
12546
|
});
|
|
12163
12547
|
}
|
|
12164
|
-
if (
|
|
12548
|
+
if (v.inviteStatus === "none") {
|
|
12165
12549
|
return json({
|
|
12166
|
-
error: "
|
|
12550
|
+
error: "This vendor was not created with an invite link"
|
|
12167
12551
|
}, {
|
|
12168
|
-
status:
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
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;
|