@7365admin1/core 3.47.1-staging.122 → 3.47.1-staging.124
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/.changeset/hid-facial-sync-authorization.md +12 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +180 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +180 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/hid-authz.test.mjs +4 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Authorize the HID facial-data sync handler in its own body
|
|
6
|
+
|
|
7
|
+
`syncReaderFacialData` reached `staging` with no authorization check inside the
|
|
8
|
+
handler. The API-core router does apply `requireAuth` + `authorizeManage`, so it
|
|
9
|
+
was not reachable by an ordinary signed-in user — but this is a published
|
|
10
|
+
package, and the guard and the handler sat in two different repositories. It now
|
|
11
|
+
runs the same `manage`-level check the router applies, with the site taken from
|
|
12
|
+
the stored reader. The guard test pins it by name.
|
package/dist/index.d.ts
CHANGED
|
@@ -10199,13 +10199,16 @@ declare function useHidAmicoService(): {
|
|
|
10199
10199
|
pages: number;
|
|
10200
10200
|
pageRange: string;
|
|
10201
10201
|
}>;
|
|
10202
|
-
listReaderUsers: (readerId: string, page?: number, limit?: number, search?: string, status?: "mapped" | "unmapped" | "") => Promise<{
|
|
10202
|
+
listReaderUsers: (readerId: string, page?: number, limit?: number, search?: string, status?: "mapped" | "unmapped" | "", includeVisitors?: boolean) => Promise<{
|
|
10203
10203
|
items: {
|
|
10204
10204
|
reader: string;
|
|
10205
10205
|
hidUserId: string;
|
|
10206
10206
|
registration: string;
|
|
10207
10207
|
metadata: {
|
|
10208
|
+
facialData?: string | undefined;
|
|
10208
10209
|
name: string;
|
|
10210
|
+
hidUserTypeId: unknown;
|
|
10211
|
+
facialEnrolled: boolean;
|
|
10209
10212
|
};
|
|
10210
10213
|
isAdministrator: boolean;
|
|
10211
10214
|
_id?: bson.ObjectId | undefined;
|
|
@@ -10217,7 +10220,7 @@ declare function useHidAmicoService(): {
|
|
|
10217
10220
|
hasMore: boolean;
|
|
10218
10221
|
pageRange: string;
|
|
10219
10222
|
}>;
|
|
10220
|
-
listReaderAccessLogs: (readerId: string, page?: number, limit?: number, search?: string, status?: "authorized" | "not_authorized" | "unknown" | "", method?: "facial" | "qr_code" | "id_password" | "pin" | "card" | "", tab?: "resident" | "visitor" | "administrator") => Promise<{
|
|
10223
|
+
listReaderAccessLogs: (readerId: string, page?: number, limit?: number, search?: string, status?: "authorized" | "not_authorized" | "unknown" | "", method?: "facial" | "qr_code" | "id_password" | "pin" | "card" | "", tab?: "all" | "unmapped" | "mapped" | "resident" | "visitor" | "administrator") => Promise<{
|
|
10221
10224
|
items: unknown[];
|
|
10222
10225
|
page: number;
|
|
10223
10226
|
limit: number;
|
|
@@ -10331,6 +10334,13 @@ declare function useHidAmicoService(): {
|
|
|
10331
10334
|
contentType: any;
|
|
10332
10335
|
base64: string;
|
|
10333
10336
|
}>;
|
|
10337
|
+
syncReaderFacialData: (readerId: string) => Promise<{
|
|
10338
|
+
readerId: string;
|
|
10339
|
+
facialDetected: number;
|
|
10340
|
+
syncedCount: number;
|
|
10341
|
+
unmappedCount: number;
|
|
10342
|
+
message: string;
|
|
10343
|
+
}>;
|
|
10334
10344
|
listUserCards: (readerId: string, hidUserIdValue: string | number) => Promise<{
|
|
10335
10345
|
readerId: string;
|
|
10336
10346
|
hidUserId: number;
|
|
@@ -10476,6 +10486,7 @@ declare function useHidAmicoController(): {
|
|
|
10476
10486
|
deleteReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
10477
10487
|
testReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
10478
10488
|
syncReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
10489
|
+
syncReaderFacialData: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
10479
10490
|
configureReaderIntegration: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
10480
10491
|
setReaderMonitor: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
10481
10492
|
setOperatingMode: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -42850,7 +42850,8 @@ var schemaHidAmicoReaderUserQuery = import_joi64.default.object({
|
|
|
42850
42850
|
page: import_joi64.default.number().integer().min(1).default(1),
|
|
42851
42851
|
limit: import_joi64.default.number().integer().min(1).max(100).default(10),
|
|
42852
42852
|
search: import_joi64.default.string().trim().max(100).allow("").default(""),
|
|
42853
|
-
status: import_joi64.default.string().valid("mapped", "unmapped").allow("").default("")
|
|
42853
|
+
status: import_joi64.default.string().valid("mapped", "unmapped").allow("").default(""),
|
|
42854
|
+
includeVisitors: import_joi64.default.boolean().default(false)
|
|
42854
42855
|
});
|
|
42855
42856
|
var schemaHidAmicoAccessLogQuery = import_joi64.default.object({
|
|
42856
42857
|
page: import_joi64.default.number().integer().min(1).default(1),
|
|
@@ -42858,7 +42859,7 @@ var schemaHidAmicoAccessLogQuery = import_joi64.default.object({
|
|
|
42858
42859
|
search: import_joi64.default.string().trim().max(100).allow("").default(""),
|
|
42859
42860
|
status: import_joi64.default.string().valid("authorized", "not_authorized", "unknown").allow("").default(""),
|
|
42860
42861
|
method: import_joi64.default.string().valid("facial", "qr_code", "id_password", "pin", "card").allow("").default(""),
|
|
42861
|
-
tab: import_joi64.default.string().valid("resident", "visitor", "administrator").default("
|
|
42862
|
+
tab: import_joi64.default.string().valid("all", "unmapped", "mapped", "resident", "visitor", "administrator").default("all")
|
|
42862
42863
|
});
|
|
42863
42864
|
var schemaHidAmicoOperatingMode = import_joi64.default.object({
|
|
42864
42865
|
mode: import_joi64.default.string().valid("standalone", "online").required()
|
|
@@ -44371,12 +44372,17 @@ function getSecretKey() {
|
|
|
44371
44372
|
function getReaderCapabilities(readerModel, firmwareVersion) {
|
|
44372
44373
|
return {
|
|
44373
44374
|
card: hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
|
|
44374
|
-
|
|
44375
|
+
// VL35LF and VL70LF are biometric readers. Some deployed readers report
|
|
44376
|
+
// their device firmware as 2.x even though their face API is available.
|
|
44377
|
+
facial: readerModel === "VL35LF" || readerModel === "VL70LF",
|
|
44375
44378
|
qr: hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
|
|
44376
44379
|
pin: hidFirmwareAtLeast(firmwareVersion, "6.9.0"),
|
|
44377
44380
|
intercom: readerModel === "VL70LF"
|
|
44378
44381
|
};
|
|
44379
44382
|
}
|
|
44383
|
+
function readerSupportsFacial(reader) {
|
|
44384
|
+
return reader.capabilities?.facial === true || reader.readerModel === "VL35LF" || reader.readerModel === "VL70LF";
|
|
44385
|
+
}
|
|
44380
44386
|
function getMonitorConfiguration(callbackToken) {
|
|
44381
44387
|
const configured = String(process.env.HID_AMICO_MONITOR_URL || "").trim();
|
|
44382
44388
|
if (!configured) {
|
|
@@ -44839,26 +44845,6 @@ function getHidPortals(response) {
|
|
|
44839
44845
|
areaToId: Number(portal.area_to_id)
|
|
44840
44846
|
})).filter((portal) => Number.isSafeInteger(portal.id) && portal.id > 0 && portal.name);
|
|
44841
44847
|
}
|
|
44842
|
-
function getHidObjectTotal(response, object) {
|
|
44843
|
-
const root = isUnknownRecord2(response) ? response : {};
|
|
44844
|
-
const data = isUnknownRecord2(root.data) ? root.data : root;
|
|
44845
|
-
const nested = isUnknownRecord2(data.data) ? data.data : {};
|
|
44846
|
-
const objectData = isUnknownRecord2(data[object]) ? data[object] : {};
|
|
44847
|
-
for (const candidate of [
|
|
44848
|
-
data.total,
|
|
44849
|
-
data.total_count,
|
|
44850
|
-
data.count,
|
|
44851
|
-
nested.total,
|
|
44852
|
-
nested.total_count,
|
|
44853
|
-
nested.count,
|
|
44854
|
-
objectData.total
|
|
44855
|
-
]) {
|
|
44856
|
-
const total = Number(candidate);
|
|
44857
|
-
if (Number.isSafeInteger(total) && total >= 0)
|
|
44858
|
-
return total;
|
|
44859
|
-
}
|
|
44860
|
-
return null;
|
|
44861
|
-
}
|
|
44862
44848
|
async function upsertHidObject(client, object, id, values) {
|
|
44863
44849
|
const existing = await client.loadObjects({
|
|
44864
44850
|
object,
|
|
@@ -45234,6 +45220,18 @@ var HidAmicoClient = class {
|
|
|
45234
45220
|
base64: buffer.toString("base64")
|
|
45235
45221
|
};
|
|
45236
45222
|
}
|
|
45223
|
+
async listUserImageIds() {
|
|
45224
|
+
if (!this.session) {
|
|
45225
|
+
await this.login();
|
|
45226
|
+
}
|
|
45227
|
+
const path5 = this.url("/user_list_images.fcgi?get_timestamp=0");
|
|
45228
|
+
const response = await this.get(path5, "json").catch(() => this.post(path5, {}));
|
|
45229
|
+
const payload = isUnknownRecord2(response.data) ? response.data : {};
|
|
45230
|
+
const userIds = Array.isArray(payload.user_ids) ? payload.user_ids : [];
|
|
45231
|
+
return [...new Set(
|
|
45232
|
+
userIds.map((value) => Number(value)).filter((value) => Number.isSafeInteger(value) && value > 0)
|
|
45233
|
+
)];
|
|
45234
|
+
}
|
|
45237
45235
|
async setUserImage(hidUserId, image, timestamp, match) {
|
|
45238
45236
|
if (!this.session) {
|
|
45239
45237
|
await this.login();
|
|
@@ -45809,8 +45807,9 @@ function useHidAmicoService() {
|
|
|
45809
45807
|
return reader;
|
|
45810
45808
|
}
|
|
45811
45809
|
function assertReaderCapability(reader, capability) {
|
|
45812
|
-
if (reader.capabilities?.[capability])
|
|
45810
|
+
if (reader.capabilities?.[capability] || capability === "facial" && readerSupportsFacial(reader)) {
|
|
45813
45811
|
return;
|
|
45812
|
+
}
|
|
45814
45813
|
const requirement = capability === "pin" ? "firmware 6.9.0 or newer" : capability === "intercom" ? "a VL70LF reader" : "firmware 6.5.1 or newer";
|
|
45815
45814
|
throw new import_node_server_utils131.BadRequestError(
|
|
45816
45815
|
`This reader has not reported ${capability} support (${requirement}). Reconfigure the reader to refresh its capabilities.`
|
|
@@ -46426,7 +46425,7 @@ function useHidAmicoService() {
|
|
|
46426
46425
|
async function listIdentities(value) {
|
|
46427
46426
|
return repo.listIdentities(value);
|
|
46428
46427
|
}
|
|
46429
|
-
async function listReaderUsers(readerId, page = 1, limit = 10, search = "", status = "") {
|
|
46428
|
+
async function listReaderUsers(readerId, page = 1, limit = 10, search = "", status = "", includeVisitors = false) {
|
|
46430
46429
|
const reader = await getActiveReader(readerId);
|
|
46431
46430
|
const offset = (page - 1) * limit;
|
|
46432
46431
|
const client = new HidAmicoClient(reader);
|
|
@@ -46434,7 +46433,7 @@ function useHidAmicoService() {
|
|
|
46434
46433
|
await client.login();
|
|
46435
46434
|
const normalizedSearch = search.trim();
|
|
46436
46435
|
const numericSearch = /^\d{1,15}$/.test(normalizedSearch) ? Number(normalizedSearch) : null;
|
|
46437
|
-
const userWhere = {
|
|
46436
|
+
const userWhere = {};
|
|
46438
46437
|
if (numericSearch !== null && Number.isSafeInteger(numericSearch)) {
|
|
46439
46438
|
userWhere.id = numericSearch;
|
|
46440
46439
|
} else if (normalizedSearch) {
|
|
@@ -46447,13 +46446,14 @@ function useHidAmicoService() {
|
|
|
46447
46446
|
const response = await client.loadObjects({
|
|
46448
46447
|
object: "users",
|
|
46449
46448
|
where: { users: userWhere },
|
|
46450
|
-
limit,
|
|
46451
|
-
offset,
|
|
46449
|
+
limit: 500,
|
|
46450
|
+
offset: 0,
|
|
46452
46451
|
order: ["id", "ascending"]
|
|
46453
46452
|
});
|
|
46454
|
-
|
|
46455
|
-
reportedTotal =
|
|
46456
|
-
|
|
46453
|
+
const deviceRows = getHidObjectRows(response, "users").filter((row) => includeVisitors || Number(row.user_type_id) !== 1);
|
|
46454
|
+
reportedTotal = deviceRows.length;
|
|
46455
|
+
rows = deviceRows.slice(offset, offset + limit);
|
|
46456
|
+
hasMore = offset + rows.length < deviceRows.length;
|
|
46457
46457
|
} else {
|
|
46458
46458
|
const linkedIdentities = await repo.listActiveIdentities(readerId);
|
|
46459
46459
|
const mappedUserIds = new Set(
|
|
@@ -46474,6 +46474,8 @@ function useHidAmicoService() {
|
|
|
46474
46474
|
});
|
|
46475
46475
|
const batch = getHidObjectRows(response, "users");
|
|
46476
46476
|
batch.forEach((row) => {
|
|
46477
|
+
if (!includeVisitors && Number(row.user_type_id) === 1)
|
|
46478
|
+
return;
|
|
46477
46479
|
const mapped = mappedUserIds.has(String(row.id ?? ""));
|
|
46478
46480
|
if (status === "mapped" && mapped || status === "unmapped" && !mapped) {
|
|
46479
46481
|
matchedRows.push(row);
|
|
@@ -46486,7 +46488,7 @@ function useHidAmicoService() {
|
|
|
46486
46488
|
hasMore = matchedRows.length > offset + limit || !exhausted;
|
|
46487
46489
|
}
|
|
46488
46490
|
const userIds = rows.map((row) => String(row.id ?? "")).filter(Boolean);
|
|
46489
|
-
const [identities, administratorFlags] = await Promise.all([
|
|
46491
|
+
const [identities, administratorFlags, facialUserIds] = await Promise.all([
|
|
46490
46492
|
repo.findIdentitiesByHidUserIds(readerId, userIds),
|
|
46491
46493
|
Promise.all(rows.map(async (row) => {
|
|
46492
46494
|
const userId = Number(row.id);
|
|
@@ -46499,13 +46501,17 @@ function useHidAmicoService() {
|
|
|
46499
46501
|
offset: 0
|
|
46500
46502
|
});
|
|
46501
46503
|
return getHidObjectRows(roleResponse, "user_roles").length > 0;
|
|
46502
|
-
}))
|
|
46504
|
+
})),
|
|
46505
|
+
readerSupportsFacial(reader) ? client.listUserImageIds().catch(() => []) : Promise.resolve([])
|
|
46503
46506
|
]);
|
|
46504
46507
|
const identityMap = new Map(
|
|
46505
46508
|
identities.map((identity) => [String(identity.hidUserId), identity])
|
|
46506
46509
|
);
|
|
46510
|
+
const facialUserIdSet = new Set(facialUserIds.map(String));
|
|
46507
46511
|
const items = rows.map((row, index) => {
|
|
46508
46512
|
const identity = identityMap.get(String(row.id ?? ""));
|
|
46513
|
+
const identityMetadata = identity && isUnknownRecord2(identity.metadata) ? identity.metadata : {};
|
|
46514
|
+
const facialEnrolled = facialUserIdSet.has(String(row.id ?? "")) || identityMetadata.facialEnrolled === true;
|
|
46509
46515
|
return {
|
|
46510
46516
|
...row,
|
|
46511
46517
|
...identity || {},
|
|
@@ -46513,8 +46519,11 @@ function useHidAmicoService() {
|
|
|
46513
46519
|
hidUserId: String(row.id ?? identity?.hidUserId ?? ""),
|
|
46514
46520
|
registration: String(row.registration ?? identity?.registration ?? ""),
|
|
46515
46521
|
metadata: {
|
|
46516
|
-
...
|
|
46517
|
-
name: String(row.name ??
|
|
46522
|
+
...identityMetadata,
|
|
46523
|
+
name: String(row.name ?? identityMetadata.name ?? ""),
|
|
46524
|
+
hidUserTypeId: row.user_type_id ?? identityMetadata.hidUserTypeId,
|
|
46525
|
+
facialEnrolled,
|
|
46526
|
+
...facialEnrolled ? { facialData: String(row.id ?? "") } : {}
|
|
46518
46527
|
},
|
|
46519
46528
|
isAdministrator: administratorFlags[index]
|
|
46520
46529
|
};
|
|
@@ -46533,7 +46542,7 @@ function useHidAmicoService() {
|
|
|
46533
46542
|
await client.logout();
|
|
46534
46543
|
}
|
|
46535
46544
|
}
|
|
46536
|
-
async function listReaderAccessLogs(readerId, page = 1, limit = 10, search = "", status = "", method = "", tab = "
|
|
46545
|
+
async function listReaderAccessLogs(readerId, page = 1, limit = 10, search = "", status = "", method = "", tab = "all") {
|
|
46537
46546
|
const reader = await getActiveReader(readerId);
|
|
46538
46547
|
const offset = (page - 1) * limit;
|
|
46539
46548
|
const client = new HidAmicoClient(reader);
|
|
@@ -46557,7 +46566,7 @@ function useHidAmicoService() {
|
|
|
46557
46566
|
const normalizedSearch = search.trim().toLocaleLowerCase();
|
|
46558
46567
|
const enrichRows = async (rows) => {
|
|
46559
46568
|
const userIds = [...new Set(rows.map((row) => toPositiveInteger(row.user_id ?? row.userId)).filter((value) => value !== null))];
|
|
46560
|
-
const [identities, roleResponse] = await Promise.all([
|
|
46569
|
+
const [identities, roleResponse, userResponse] = await Promise.all([
|
|
46561
46570
|
repo.findIdentitiesByHidUserIds(readerId, userIds.map(String)),
|
|
46562
46571
|
userIds.length ? client.loadObjects({
|
|
46563
46572
|
object: "user_roles",
|
|
@@ -46569,7 +46578,17 @@ function useHidAmicoService() {
|
|
|
46569
46578
|
},
|
|
46570
46579
|
limit: Math.max(1, userIds.length),
|
|
46571
46580
|
offset: 0
|
|
46572
|
-
}) : Promise.resolve({ user_roles: [] })
|
|
46581
|
+
}) : Promise.resolve({ user_roles: [] }),
|
|
46582
|
+
userIds.length ? client.loadObjects({
|
|
46583
|
+
object: "users",
|
|
46584
|
+
where: {
|
|
46585
|
+
users: {
|
|
46586
|
+
id: userIds.length === 1 ? userIds[0] : { IN: userIds }
|
|
46587
|
+
}
|
|
46588
|
+
},
|
|
46589
|
+
limit: Math.max(1, userIds.length),
|
|
46590
|
+
offset: 0
|
|
46591
|
+
}) : Promise.resolve({ users: [] })
|
|
46573
46592
|
]);
|
|
46574
46593
|
const identityMap = new Map(
|
|
46575
46594
|
identities.map((identity) => [String(identity.hidUserId), identity])
|
|
@@ -46577,11 +46596,19 @@ function useHidAmicoService() {
|
|
|
46577
46596
|
const administratorIds = new Set(
|
|
46578
46597
|
getHidObjectRows(roleResponse, "user_roles").map((role) => String(role.user_id ?? "")).filter(Boolean)
|
|
46579
46598
|
);
|
|
46599
|
+
const readerUserById = new Map(
|
|
46600
|
+
getHidObjectRows(userResponse, "users").map((user) => [String(user.id ?? ""), user]).filter(([userId]) => Boolean(userId))
|
|
46601
|
+
);
|
|
46580
46602
|
return rows.map((row) => {
|
|
46581
46603
|
const userId = String(row.user_id ?? row.userId ?? "");
|
|
46582
46604
|
const identity = identityMap.get(userId) || null;
|
|
46605
|
+
const readerUser = readerUserById.get(userId);
|
|
46606
|
+
const existingName = String(row.user_name ?? row.userName ?? "").trim();
|
|
46607
|
+
const readerUserName = String(readerUser?.name ?? "").trim();
|
|
46583
46608
|
return {
|
|
46584
46609
|
...row,
|
|
46610
|
+
user_name: existingName || readerUserName,
|
|
46611
|
+
user_type_id: row.user_type_id ?? readerUser?.user_type_id ?? "",
|
|
46585
46612
|
identity,
|
|
46586
46613
|
isAdministrator: administratorIds.has(userId),
|
|
46587
46614
|
normalizedMethod: normalizeHidAccessLogMethod(row),
|
|
@@ -46592,7 +46619,12 @@ function useHidAmicoService() {
|
|
|
46592
46619
|
const matchesRequestedFilters = (item) => {
|
|
46593
46620
|
const identity = isUnknownRecord2(item.identity) ? item.identity : null;
|
|
46594
46621
|
const itemTab = normalizeHidAccessLogTab(identity, Boolean(item.isAdministrator));
|
|
46595
|
-
|
|
46622
|
+
const isMapped = Boolean(identity);
|
|
46623
|
+
if (tab === "unmapped" && isMapped)
|
|
46624
|
+
return false;
|
|
46625
|
+
if (tab === "mapped" && !isMapped)
|
|
46626
|
+
return false;
|
|
46627
|
+
if (["resident", "visitor", "administrator"].includes(tab) && itemTab !== tab)
|
|
46596
46628
|
return false;
|
|
46597
46629
|
if (status && item.normalizedStatus !== status)
|
|
46598
46630
|
return false;
|
|
@@ -47133,6 +47165,98 @@ function useHidAmicoService() {
|
|
|
47133
47165
|
await client.logout();
|
|
47134
47166
|
}
|
|
47135
47167
|
}
|
|
47168
|
+
async function getReaderFacialUserIds(client) {
|
|
47169
|
+
try {
|
|
47170
|
+
return await client.listUserImageIds();
|
|
47171
|
+
} catch {
|
|
47172
|
+
const usersResponse = await client.loadObjects({
|
|
47173
|
+
object: "users",
|
|
47174
|
+
where: { users: {} },
|
|
47175
|
+
limit: 500,
|
|
47176
|
+
offset: 0,
|
|
47177
|
+
order: ["id", "ascending"]
|
|
47178
|
+
});
|
|
47179
|
+
const userIds = getHidObjectRows(usersResponse, "users").map((user) => Number(user.id)).filter((userId) => Number.isSafeInteger(userId) && userId > 0);
|
|
47180
|
+
const facialUserIds = [];
|
|
47181
|
+
const batchSize = 4;
|
|
47182
|
+
for (let offset = 0; offset < userIds.length; offset += batchSize) {
|
|
47183
|
+
const batch = userIds.slice(offset, offset + batchSize);
|
|
47184
|
+
const results = await Promise.all(batch.map(async (userId) => {
|
|
47185
|
+
try {
|
|
47186
|
+
const image = await client.getUserImage(userId);
|
|
47187
|
+
return image.base64 ? userId : null;
|
|
47188
|
+
} catch {
|
|
47189
|
+
return null;
|
|
47190
|
+
}
|
|
47191
|
+
}));
|
|
47192
|
+
facialUserIds.push(...results.filter((userId) => userId !== null));
|
|
47193
|
+
}
|
|
47194
|
+
return facialUserIds;
|
|
47195
|
+
}
|
|
47196
|
+
}
|
|
47197
|
+
async function syncReaderFacialData(readerId) {
|
|
47198
|
+
const reader = await getActiveReader(readerId);
|
|
47199
|
+
assertReaderCapability(reader, "facial");
|
|
47200
|
+
const client = new HidAmicoClient(reader);
|
|
47201
|
+
try {
|
|
47202
|
+
await client.login();
|
|
47203
|
+
const facialUserIds = await getReaderFacialUserIds(client);
|
|
47204
|
+
const activeIdentities = await repo.listActiveIdentities(readerId);
|
|
47205
|
+
const identitiesByUserId = new Map(
|
|
47206
|
+
activeIdentities.filter((identity) => identity._id && identity.hidUserId !== void 0).map((identity) => [String(identity.hidUserId), identity])
|
|
47207
|
+
);
|
|
47208
|
+
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
47209
|
+
let syncedCount = 0;
|
|
47210
|
+
let unmappedCount = 0;
|
|
47211
|
+
for (const hidUserId of facialUserIds) {
|
|
47212
|
+
const identity = identitiesByUserId.get(String(hidUserId));
|
|
47213
|
+
if (!identity?._id) {
|
|
47214
|
+
unmappedCount += 1;
|
|
47215
|
+
continue;
|
|
47216
|
+
}
|
|
47217
|
+
const metadata = isUnknownRecord2(identity.metadata) ? identity.metadata : {};
|
|
47218
|
+
await repo.updateIdentity(identity._id, {
|
|
47219
|
+
metadata: {
|
|
47220
|
+
...metadata,
|
|
47221
|
+
facialData: String(hidUserId),
|
|
47222
|
+
facialEnrolled: true,
|
|
47223
|
+
facialSyncedAt: syncedAt
|
|
47224
|
+
}
|
|
47225
|
+
});
|
|
47226
|
+
syncedCount += 1;
|
|
47227
|
+
}
|
|
47228
|
+
const message = [
|
|
47229
|
+
facialUserIds.length ? `Detected ${facialUserIds.length} facial enrollment(s) on the HID reader.` : "No facial enrollments found on the HID reader.",
|
|
47230
|
+
syncedCount ? `${syncedCount} mapped HID user(s) updated.` : "",
|
|
47231
|
+
unmappedCount ? `${unmappedCount} facial enrollment(s) still need HID User Mapping.` : ""
|
|
47232
|
+
].filter(Boolean).join(" ");
|
|
47233
|
+
await repo.updateById(readerId, { status: "active", lastSeenAt: /* @__PURE__ */ new Date() });
|
|
47234
|
+
await repo.addEvent({
|
|
47235
|
+
reader: readerId,
|
|
47236
|
+
site: reader.site,
|
|
47237
|
+
type: "facial_sync",
|
|
47238
|
+
payload: { facialDetected: facialUserIds.length, syncedCount, unmappedCount }
|
|
47239
|
+
});
|
|
47240
|
+
return {
|
|
47241
|
+
readerId,
|
|
47242
|
+
facialDetected: facialUserIds.length,
|
|
47243
|
+
syncedCount,
|
|
47244
|
+
unmappedCount,
|
|
47245
|
+
message
|
|
47246
|
+
};
|
|
47247
|
+
} catch (error) {
|
|
47248
|
+
await repo.addEvent({
|
|
47249
|
+
reader: readerId,
|
|
47250
|
+
site: reader.site,
|
|
47251
|
+
type: "facial_sync_failed",
|
|
47252
|
+
status: "failed",
|
|
47253
|
+
payload: { message: getErrorMessage2(error) }
|
|
47254
|
+
});
|
|
47255
|
+
throw error;
|
|
47256
|
+
} finally {
|
|
47257
|
+
await client.logout();
|
|
47258
|
+
}
|
|
47259
|
+
}
|
|
47136
47260
|
async function setVisitorImage(readerId, visitorId, image, options = {}) {
|
|
47137
47261
|
assertHidFacialImage(image);
|
|
47138
47262
|
const reader = await getActiveReader(readerId);
|
|
@@ -48284,6 +48408,7 @@ function useHidAmicoService() {
|
|
|
48284
48408
|
setVisitorImage,
|
|
48285
48409
|
deleteVisitorImage,
|
|
48286
48410
|
getUserImage,
|
|
48411
|
+
syncReaderFacialData,
|
|
48287
48412
|
listUserCards,
|
|
48288
48413
|
assignUserCard,
|
|
48289
48414
|
enrollUserCard,
|
|
@@ -84298,6 +84423,19 @@ function useHidAmicoController() {
|
|
|
84298
84423
|
next(error);
|
|
84299
84424
|
}
|
|
84300
84425
|
}
|
|
84426
|
+
async function syncReaderFacialData(req, res, next) {
|
|
84427
|
+
const { error, value } = schemaHidAmicoReaderIdParams.validate(req.params);
|
|
84428
|
+
if (error) {
|
|
84429
|
+
next(new import_node_server_utils286.BadRequestError(error.message));
|
|
84430
|
+
return;
|
|
84431
|
+
}
|
|
84432
|
+
try {
|
|
84433
|
+
await authorizeReaderAccess(req, value.readerId, "manage");
|
|
84434
|
+
res.json({ data: await service.syncReaderFacialData(value.readerId) });
|
|
84435
|
+
} catch (error2) {
|
|
84436
|
+
next(error2);
|
|
84437
|
+
}
|
|
84438
|
+
}
|
|
84301
84439
|
async function listLogs(req, res, next) {
|
|
84302
84440
|
const params = schemaHidAmicoReaderIdParams.validate(req.params);
|
|
84303
84441
|
const query2 = schemaHidAmicoLogQuery.validate(req.query);
|
|
@@ -84409,7 +84547,8 @@ function useHidAmicoController() {
|
|
|
84409
84547
|
query2.value.page,
|
|
84410
84548
|
query2.value.limit,
|
|
84411
84549
|
query2.value.search,
|
|
84412
|
-
query2.value.status
|
|
84550
|
+
query2.value.status,
|
|
84551
|
+
query2.value.includeVisitors
|
|
84413
84552
|
));
|
|
84414
84553
|
} catch (error) {
|
|
84415
84554
|
next(error);
|
|
@@ -84987,6 +85126,7 @@ function useHidAmicoController() {
|
|
|
84987
85126
|
deleteReader,
|
|
84988
85127
|
testReader,
|
|
84989
85128
|
syncReader,
|
|
85129
|
+
syncReaderFacialData,
|
|
84990
85130
|
configureReaderIntegration,
|
|
84991
85131
|
setReaderMonitor,
|
|
84992
85132
|
setOperatingMode,
|