@7365admin1/core 3.48.1-staging.173 → 3.48.1-staging.175
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/site-billing-configuration-scope.md +5 -0
- package/dist/index.d.ts +47 -2
- package/dist/index.js +191 -114
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -114
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/e2e/harness.mjs +14 -0
- package/test/e2e/site-billing-configuration-scope.e2e.test.mjs +422 -0
- package/test/hid-authz.test.mjs +6 -4
- package/test/org-suspension.test.mjs +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -6253,6 +6253,23 @@ declare function useSiteBillingConfigurationRepo(): {
|
|
|
6253
6253
|
createIndexes: () => Promise<string>;
|
|
6254
6254
|
};
|
|
6255
6255
|
|
|
6256
|
+
/**
|
|
6257
|
+
* Billing configuration is per-estate — the tax rate, the rounding and the
|
|
6258
|
+
* payment settings every bill at that site is calculated from.
|
|
6259
|
+
*
|
|
6260
|
+
* Every one of these five handlers carried `requireAuth` and nothing else. The
|
|
6261
|
+
* record id came from the URL and the site came from the caller's own query or
|
|
6262
|
+
* body, so any signed-in account on the platform could read another client's
|
|
6263
|
+
* billing settings, change their tax rate, move a configuration to a different
|
|
6264
|
+
* estate, or delete it. `createdBy` was read off the raw `user` COOKIE with a
|
|
6265
|
+
* fall back to the body, so the "who set this up" name was client-supplied.
|
|
6266
|
+
*
|
|
6267
|
+
* The rule is the existing one — `requireSiteReach`, i.e. `entitleSite` — asked
|
|
6268
|
+
* about the site the request names on the way in, and about the STORED site for
|
|
6269
|
+
* the id routes, which name no site at all. `PUT` asks twice: once about where
|
|
6270
|
+
* the record is and once about where the body wants to move it, because a MOVE
|
|
6271
|
+
* needs the destination checked too. `createdBy` now comes from the session.
|
|
6272
|
+
*/
|
|
6256
6273
|
declare function useSiteBillingConfigurationController(): {
|
|
6257
6274
|
add: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
6258
6275
|
getAll: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -10913,6 +10930,7 @@ declare function useHidAmicoService(): {
|
|
|
10913
10930
|
includePassword?: boolean;
|
|
10914
10931
|
}) => Promise<THidAmicoReader>;
|
|
10915
10932
|
authorizeReaderUser: (readerId: string, hidUserIdValue: string | number, userId: string) => Promise<THidAmicoReader>;
|
|
10933
|
+
authorizeVisitorForMember: (readerId: string, visitorId: string, userId: string) => Promise<THidAmicoReader>;
|
|
10916
10934
|
authorizeSiteForCaller: (siteId: string, userId: string) => Promise<{
|
|
10917
10935
|
site: mongodb.WithId<bson.Document> | null;
|
|
10918
10936
|
siteObjectId: bson.ObjectId;
|
|
@@ -10936,6 +10954,23 @@ declare function useHidAmicoService(): {
|
|
|
10936
10954
|
pages: number;
|
|
10937
10955
|
pageRange: string;
|
|
10938
10956
|
}>;
|
|
10957
|
+
getMemberReaderInfo: (readerId: string, userId: string) => Promise<{
|
|
10958
|
+
_id: string;
|
|
10959
|
+
name: string;
|
|
10960
|
+
location: string | undefined;
|
|
10961
|
+
status: "deleted" | "active" | "inactive" | "offline" | undefined;
|
|
10962
|
+
portalId: number | undefined;
|
|
10963
|
+
portalName: string | undefined;
|
|
10964
|
+
readerModel: "unknown" | "VL35LF" | "VL70LF" | undefined;
|
|
10965
|
+
cardReadingMode: "unconfigured" | "pacs" | "csn" | "csn_for_mifare" | undefined;
|
|
10966
|
+
capabilities: {
|
|
10967
|
+
card: boolean;
|
|
10968
|
+
facial: boolean;
|
|
10969
|
+
qr: boolean;
|
|
10970
|
+
pin: boolean;
|
|
10971
|
+
intercom: boolean;
|
|
10972
|
+
};
|
|
10973
|
+
}>;
|
|
10939
10974
|
discoverReader: (value: Pick<THidAmicoReader, "baseUrl" | "username" | "password"> & Partial<Pick<THidAmicoReader, "readerModel">>) => Promise<{
|
|
10940
10975
|
deviceId: string;
|
|
10941
10976
|
serial: string;
|
|
@@ -11084,7 +11119,7 @@ declare function useHidAmicoService(): {
|
|
|
11084
11119
|
pages: number;
|
|
11085
11120
|
pageRange: string;
|
|
11086
11121
|
}>;
|
|
11087
|
-
listReaderUsers: (readerId: string, page?: number, limit?: number, search?: string, status?: "mapped" | "unmapped" | "", includeVisitors?: boolean) => Promise<{
|
|
11122
|
+
listReaderUsers: (readerId: string, page?: number, limit?: number, search?: string, status?: "mapped" | "unmapped" | "", includeVisitors?: boolean, userType?: "all" | "user" | "visitor") => Promise<{
|
|
11088
11123
|
items: {
|
|
11089
11124
|
reader: string;
|
|
11090
11125
|
hidUserId: string;
|
|
@@ -11096,7 +11131,6 @@ declare function useHidAmicoService(): {
|
|
|
11096
11131
|
facialEnrolled: boolean;
|
|
11097
11132
|
};
|
|
11098
11133
|
isAdministrator: boolean;
|
|
11099
|
-
_id?: bson.ObjectId | undefined;
|
|
11100
11134
|
}[];
|
|
11101
11135
|
page: number;
|
|
11102
11136
|
limit: number;
|
|
@@ -11153,6 +11187,15 @@ declare function useHidAmicoService(): {
|
|
|
11153
11187
|
validFrom: string;
|
|
11154
11188
|
expiresAt: string;
|
|
11155
11189
|
}>;
|
|
11190
|
+
getVisitorQrForMember: (readerId: string, visitorId: string, userId: string) => Promise<{
|
|
11191
|
+
readerId: string;
|
|
11192
|
+
visitorId: string;
|
|
11193
|
+
qrValue: string;
|
|
11194
|
+
expiresAt: string;
|
|
11195
|
+
portalId: number | undefined;
|
|
11196
|
+
portalName: string | undefined;
|
|
11197
|
+
readerName: string;
|
|
11198
|
+
} | null>;
|
|
11156
11199
|
revokeVisitorQr: (readerId: string, visitorId: string) => Promise<{
|
|
11157
11200
|
readerId: string;
|
|
11158
11201
|
visitorId: string;
|
|
@@ -11365,6 +11408,7 @@ declare function useHidAmicoController(): {
|
|
|
11365
11408
|
authorizeManage: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
|
|
11366
11409
|
authorizeVisitor: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
|
|
11367
11410
|
listReaders: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11411
|
+
getMemberReaderInfo: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11368
11412
|
discoverReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11369
11413
|
createReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11370
11414
|
updateReader: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -11400,6 +11444,7 @@ declare function useHidAmicoController(): {
|
|
|
11400
11444
|
setConfiguration: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11401
11445
|
runObjectOperation: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11402
11446
|
issueVisitorQr: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11447
|
+
getVisitorQr: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11403
11448
|
revokeVisitorQr: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11404
11449
|
getProfileFacial: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
11405
11450
|
setProfileFacial: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -43262,7 +43262,7 @@ var schemaHidAmicoIdentity = import_joi64.default.object({
|
|
|
43262
43262
|
var schemaCreateHidAmicoIdentity = import_joi64.default.object({
|
|
43263
43263
|
site: objectIdSchema2.optional(),
|
|
43264
43264
|
hidUserId: import_joi64.default.alternatives(import_joi64.default.string(), import_joi64.default.number()).optional().allow(null, ""),
|
|
43265
|
-
registration: import_joi64.default.string().trim().
|
|
43265
|
+
registration: import_joi64.default.string().trim().optional().allow(""),
|
|
43266
43266
|
cardNo: import_joi64.default.string().optional().allow(null, ""),
|
|
43267
43267
|
person: objectIdSchema2.optional().allow(null, ""),
|
|
43268
43268
|
user: objectIdSchema2.optional().allow(null, ""),
|
|
@@ -43409,7 +43409,8 @@ var schemaHidAmicoReaderUserQuery = import_joi64.default.object({
|
|
|
43409
43409
|
limit: import_joi64.default.number().integer().min(1).max(100).default(10),
|
|
43410
43410
|
search: import_joi64.default.string().trim().max(100).allow("").default(""),
|
|
43411
43411
|
status: import_joi64.default.string().valid("mapped", "unmapped").allow("").default(""),
|
|
43412
|
-
includeVisitors: import_joi64.default.boolean().default(false)
|
|
43412
|
+
includeVisitors: import_joi64.default.boolean().default(false),
|
|
43413
|
+
userType: import_joi64.default.string().valid("all", "user", "visitor").default("all")
|
|
43413
43414
|
});
|
|
43414
43415
|
var schemaHidAmicoAccessLogQuery = import_joi64.default.object({
|
|
43415
43416
|
page: import_joi64.default.number().integer().min(1).default(1),
|
|
@@ -44928,18 +44929,34 @@ function getSecretKey() {
|
|
|
44928
44929
|
return import_crypto2.default.createHash("sha256").update(secret).digest();
|
|
44929
44930
|
}
|
|
44930
44931
|
function getReaderCapabilities(readerModel, firmwareVersion) {
|
|
44932
|
+
const isAmicoBiometricReader2 = readerModel === "VL35LF" || readerModel === "VL70LF";
|
|
44931
44933
|
return {
|
|
44932
|
-
|
|
44934
|
+
// VL35LF/VL70LF expose the relevant credential functions even when the
|
|
44935
|
+
// installed product firmware reports a 2.x version rather than the legacy
|
|
44936
|
+
// API version scheme.
|
|
44937
|
+
card: isAmicoBiometricReader2 || hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
|
|
44933
44938
|
// VL35LF and VL70LF are biometric readers. Some deployed readers report
|
|
44934
44939
|
// their device firmware as 2.x even though their face API is available.
|
|
44935
|
-
facial:
|
|
44936
|
-
qr: hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
|
|
44937
|
-
pin: hidFirmwareAtLeast(firmwareVersion, "6.9.0"),
|
|
44940
|
+
facial: isAmicoBiometricReader2,
|
|
44941
|
+
qr: isAmicoBiometricReader2 || hidFirmwareAtLeast(firmwareVersion, "6.5.1"),
|
|
44942
|
+
pin: isAmicoBiometricReader2 || hidFirmwareAtLeast(firmwareVersion, "6.9.0"),
|
|
44938
44943
|
intercom: readerModel === "VL70LF"
|
|
44939
44944
|
};
|
|
44940
44945
|
}
|
|
44946
|
+
function isAmicoBiometricReader(reader) {
|
|
44947
|
+
return reader.readerModel === "VL35LF" || reader.readerModel === "VL70LF";
|
|
44948
|
+
}
|
|
44941
44949
|
function readerSupportsFacial(reader) {
|
|
44942
|
-
return reader.capabilities?.facial === true || reader
|
|
44950
|
+
return reader.capabilities?.facial === true || isAmicoBiometricReader(reader);
|
|
44951
|
+
}
|
|
44952
|
+
function readerSupportsCredential(reader, capability) {
|
|
44953
|
+
if (reader.capabilities?.[capability] === true)
|
|
44954
|
+
return true;
|
|
44955
|
+
if (capability === "facial")
|
|
44956
|
+
return readerSupportsFacial(reader);
|
|
44957
|
+
if (capability === "intercom")
|
|
44958
|
+
return reader.readerModel === "VL70LF";
|
|
44959
|
+
return isAmicoBiometricReader(reader);
|
|
44943
44960
|
}
|
|
44944
44961
|
function getMonitorConfiguration(callbackToken) {
|
|
44945
44962
|
const configured = String(process.env.HID_AMICO_MONITOR_URL || "").trim();
|
|
@@ -45095,14 +45112,25 @@ function createVisitorQrCredential(visitorId, readerId) {
|
|
|
45095
45112
|
}
|
|
45096
45113
|
async function configureVisitorQrReader(client) {
|
|
45097
45114
|
const requestedConfiguration = {
|
|
45115
|
+
general: ["online", "local_identification"],
|
|
45098
45116
|
face_id: ["qrcode_legacy_mode_enabled"],
|
|
45099
45117
|
identifier: ["qrcode_identification_enabled", "multi_factor_authentication"]
|
|
45100
45118
|
};
|
|
45101
45119
|
const currentConfiguration = await client.getConfiguration(requestedConfiguration);
|
|
45102
45120
|
const current = isUnknownRecord2(currentConfiguration) ? currentConfiguration : {};
|
|
45121
|
+
const currentGeneral = isUnknownRecord2(current.general) ? current.general : {};
|
|
45103
45122
|
const currentFaceId = isUnknownRecord2(current.face_id) ? current.face_id : {};
|
|
45104
45123
|
const currentIdentifier = isUnknownRecord2(current.identifier) ? current.identifier : {};
|
|
45105
45124
|
let changed = false;
|
|
45125
|
+
if (String(currentGeneral.online ?? "") !== "0" || String(currentGeneral.local_identification ?? "") !== "1") {
|
|
45126
|
+
await client.setConfiguration({
|
|
45127
|
+
general: {
|
|
45128
|
+
online: "0",
|
|
45129
|
+
local_identification: "1"
|
|
45130
|
+
}
|
|
45131
|
+
});
|
|
45132
|
+
changed = true;
|
|
45133
|
+
}
|
|
45106
45134
|
if (String(currentFaceId.qrcode_legacy_mode_enabled ?? "") !== VISITOR_QR_FORMAT) {
|
|
45107
45135
|
await client.setConfiguration({
|
|
45108
45136
|
face_id: { qrcode_legacy_mode_enabled: VISITOR_QR_FORMAT }
|
|
@@ -45122,9 +45150,10 @@ async function configureVisitorQrReader(client) {
|
|
|
45122
45150
|
}
|
|
45123
45151
|
const configuration = await client.getConfiguration(requestedConfiguration);
|
|
45124
45152
|
const savedConfiguration = isUnknownRecord2(configuration) ? configuration : {};
|
|
45153
|
+
const general = isUnknownRecord2(savedConfiguration.general) ? savedConfiguration.general : {};
|
|
45125
45154
|
const faceId = isUnknownRecord2(savedConfiguration.face_id) ? savedConfiguration.face_id : {};
|
|
45126
45155
|
const identifier = isUnknownRecord2(savedConfiguration.identifier) ? savedConfiguration.identifier : {};
|
|
45127
|
-
if (String(faceId.qrcode_legacy_mode_enabled ?? "") !== VISITOR_QR_FORMAT || String(identifier.qrcode_identification_enabled ?? "") !== "1" || String(identifier.multi_factor_authentication ?? "") !== "0") {
|
|
45156
|
+
if (String(general.online ?? "") !== "0" || String(general.local_identification ?? "") !== "1" || String(faceId.qrcode_legacy_mode_enabled ?? "") !== VISITOR_QR_FORMAT || String(identifier.qrcode_identification_enabled ?? "") !== "1" || String(identifier.multi_factor_authentication ?? "") !== "0") {
|
|
45128
45157
|
throw new import_node_server_utils132.BadRequestError(
|
|
45129
45158
|
"The HID reader did not retain alphanumeric QR configuration."
|
|
45130
45159
|
);
|
|
@@ -46357,6 +46386,17 @@ function useHidAmicoService() {
|
|
|
46357
46386
|
return reader;
|
|
46358
46387
|
return authorizeReader(readerId, userId);
|
|
46359
46388
|
}
|
|
46389
|
+
async function authorizeVisitorForMember(readerId, visitorId, userId) {
|
|
46390
|
+
const reader = await entitleReaderForSelf(readerId, userId);
|
|
46391
|
+
const visitor = await visitorRepo.getVisitorTransactionById(visitorId);
|
|
46392
|
+
const visitorSiteId = String(visitor?.site?._id || visitor?.site || "");
|
|
46393
|
+
if (!visitorSiteId || visitorSiteId !== String(reader.site || "")) {
|
|
46394
|
+
throw new import_node_server_utils132.BadRequestError("Visitor and HID reader must belong to the same site.");
|
|
46395
|
+
}
|
|
46396
|
+
if (String(visitor?.createdBy || "") === userId)
|
|
46397
|
+
return reader;
|
|
46398
|
+
return authorizeReader(readerId, userId);
|
|
46399
|
+
}
|
|
46360
46400
|
async function getActiveReader(id) {
|
|
46361
46401
|
const reader = await repo.getById(id, { includePassword: true });
|
|
46362
46402
|
if (reader.enabled === false || reader.status === "inactive" || reader.status === "deleted") {
|
|
@@ -46365,7 +46405,7 @@ function useHidAmicoService() {
|
|
|
46365
46405
|
return reader;
|
|
46366
46406
|
}
|
|
46367
46407
|
function assertReaderCapability(reader, capability) {
|
|
46368
|
-
if (reader
|
|
46408
|
+
if (readerSupportsCredential(reader, capability)) {
|
|
46369
46409
|
return;
|
|
46370
46410
|
}
|
|
46371
46411
|
const requirement = capability === "pin" ? "firmware 6.9.0 or newer" : capability === "intercom" ? "a VL70LF reader" : "firmware 6.5.1 or newer";
|
|
@@ -46809,6 +46849,26 @@ function useHidAmicoService() {
|
|
|
46809
46849
|
async function listReaders(value) {
|
|
46810
46850
|
return repo.list(value);
|
|
46811
46851
|
}
|
|
46852
|
+
async function getMemberReaderInfo(readerId, userId) {
|
|
46853
|
+
const reader = await entitleReaderForSelf(readerId, userId);
|
|
46854
|
+
return {
|
|
46855
|
+
_id: String(reader._id),
|
|
46856
|
+
name: reader.name,
|
|
46857
|
+
location: reader.location,
|
|
46858
|
+
status: reader.status,
|
|
46859
|
+
portalId: reader.portalId,
|
|
46860
|
+
portalName: reader.portalName,
|
|
46861
|
+
readerModel: reader.readerModel,
|
|
46862
|
+
cardReadingMode: reader.cardReadingMode,
|
|
46863
|
+
capabilities: {
|
|
46864
|
+
card: readerSupportsCredential(reader, "card"),
|
|
46865
|
+
facial: readerSupportsFacial(reader),
|
|
46866
|
+
qr: readerSupportsCredential(reader, "qr"),
|
|
46867
|
+
pin: readerSupportsCredential(reader, "pin"),
|
|
46868
|
+
intercom: readerSupportsCredential(reader, "intercom")
|
|
46869
|
+
}
|
|
46870
|
+
};
|
|
46871
|
+
}
|
|
46812
46872
|
async function deleteReader(id) {
|
|
46813
46873
|
return repo.markDeleted(id);
|
|
46814
46874
|
}
|
|
@@ -46983,7 +47043,7 @@ function useHidAmicoService() {
|
|
|
46983
47043
|
async function listIdentities(value) {
|
|
46984
47044
|
return repo.listIdentities(value);
|
|
46985
47045
|
}
|
|
46986
|
-
async function listReaderUsers(readerId, page = 1, limit = 10, search = "", status = "", includeVisitors = false) {
|
|
47046
|
+
async function listReaderUsers(readerId, page = 1, limit = 10, search = "", status = "", includeVisitors = false, userType = "all") {
|
|
46987
47047
|
const reader = await getActiveReader(readerId);
|
|
46988
47048
|
const offset = (page - 1) * limit;
|
|
46989
47049
|
const client = new HidAmicoClient(reader);
|
|
@@ -47000,54 +47060,22 @@ function useHidAmicoService() {
|
|
|
47000
47060
|
let rows = [];
|
|
47001
47061
|
let reportedTotal = null;
|
|
47002
47062
|
let hasMore = false;
|
|
47003
|
-
|
|
47004
|
-
|
|
47005
|
-
|
|
47006
|
-
|
|
47007
|
-
|
|
47008
|
-
|
|
47009
|
-
|
|
47010
|
-
|
|
47011
|
-
|
|
47012
|
-
|
|
47013
|
-
|
|
47014
|
-
|
|
47015
|
-
|
|
47016
|
-
|
|
47017
|
-
|
|
47018
|
-
|
|
47019
|
-
);
|
|
47020
|
-
const matchedRows = [];
|
|
47021
|
-
const targetCount = offset + limit + 1;
|
|
47022
|
-
let deviceOffset = 0;
|
|
47023
|
-
let exhausted = status === "mapped" && mappedUserIds.size === 0;
|
|
47024
|
-
const chunkSize = 100;
|
|
47025
|
-
while (!exhausted && matchedRows.length < targetCount) {
|
|
47026
|
-
const response = await client.loadObjects({
|
|
47027
|
-
object: "users",
|
|
47028
|
-
where: { users: userWhere },
|
|
47029
|
-
limit: chunkSize,
|
|
47030
|
-
offset: deviceOffset,
|
|
47031
|
-
order: ["id", "ascending"]
|
|
47032
|
-
});
|
|
47033
|
-
const batch = getHidObjectRows(response, "users");
|
|
47034
|
-
batch.forEach((row) => {
|
|
47035
|
-
if (!includeVisitors && Number(row.user_type_id) === 1)
|
|
47036
|
-
return;
|
|
47037
|
-
const mapped = mappedUserIds.has(String(row.id ?? ""));
|
|
47038
|
-
if (status === "mapped" && mapped || status === "unmapped" && !mapped) {
|
|
47039
|
-
matchedRows.push(row);
|
|
47040
|
-
}
|
|
47041
|
-
});
|
|
47042
|
-
deviceOffset += batch.length;
|
|
47043
|
-
exhausted = batch.length < chunkSize;
|
|
47044
|
-
}
|
|
47045
|
-
rows = matchedRows.slice(offset, offset + limit);
|
|
47046
|
-
hasMore = matchedRows.length > offset + limit || !exhausted;
|
|
47047
|
-
}
|
|
47048
|
-
const userIds = rows.map((row) => String(row.id ?? "")).filter(Boolean);
|
|
47049
|
-
const [identities, administratorFlags, facialUserIds] = await Promise.all([
|
|
47050
|
-
repo.findIdentitiesByHidUserIds(readerId, userIds),
|
|
47063
|
+
const response = await client.loadObjects({
|
|
47064
|
+
object: "users",
|
|
47065
|
+
where: { users: userWhere },
|
|
47066
|
+
limit: 500,
|
|
47067
|
+
offset: 0,
|
|
47068
|
+
order: ["id", "ascending"]
|
|
47069
|
+
});
|
|
47070
|
+
const deviceRows = getHidObjectRows(response, "users");
|
|
47071
|
+
const filteredRows = deviceRows.filter((row) => {
|
|
47072
|
+
const isVisitor = Number(row.user_type_id) === 1;
|
|
47073
|
+
return userType === "visitor" ? isVisitor : userType === "user" ? !isVisitor : true;
|
|
47074
|
+
});
|
|
47075
|
+
reportedTotal = filteredRows.length;
|
|
47076
|
+
rows = filteredRows.slice(offset, offset + limit);
|
|
47077
|
+
hasMore = offset + rows.length < filteredRows.length;
|
|
47078
|
+
const [administratorFlags, facialUserIds] = await Promise.all([
|
|
47051
47079
|
Promise.all(rows.map(async (row) => {
|
|
47052
47080
|
const userId = Number(row.id);
|
|
47053
47081
|
if (!Number.isSafeInteger(userId) || userId <= 0)
|
|
@@ -47062,24 +47090,17 @@ function useHidAmicoService() {
|
|
|
47062
47090
|
})),
|
|
47063
47091
|
readerSupportsFacial(reader) ? client.listUserImageIds().catch(() => []) : Promise.resolve([])
|
|
47064
47092
|
]);
|
|
47065
|
-
const identityMap = new Map(
|
|
47066
|
-
identities.map((identity) => [String(identity.hidUserId), identity])
|
|
47067
|
-
);
|
|
47068
47093
|
const facialUserIdSet = new Set(facialUserIds.map(String));
|
|
47069
47094
|
const items = rows.map((row, index) => {
|
|
47070
|
-
const
|
|
47071
|
-
const identityMetadata = identity && isUnknownRecord2(identity.metadata) ? identity.metadata : {};
|
|
47072
|
-
const facialEnrolled = facialUserIdSet.has(String(row.id ?? "")) || identityMetadata.facialEnrolled === true;
|
|
47095
|
+
const facialEnrolled = facialUserIdSet.has(String(row.id ?? ""));
|
|
47073
47096
|
return {
|
|
47074
47097
|
...row,
|
|
47075
|
-
...identity || {},
|
|
47076
47098
|
reader: readerId,
|
|
47077
|
-
hidUserId: String(row.id ??
|
|
47078
|
-
registration: String(row.registration ??
|
|
47099
|
+
hidUserId: String(row.id ?? ""),
|
|
47100
|
+
registration: String(row.registration ?? ""),
|
|
47079
47101
|
metadata: {
|
|
47080
|
-
|
|
47081
|
-
|
|
47082
|
-
hidUserTypeId: row.user_type_id ?? identityMetadata.hidUserTypeId,
|
|
47102
|
+
name: String(row.name ?? ""),
|
|
47103
|
+
hidUserTypeId: row.user_type_id,
|
|
47083
47104
|
facialEnrolled,
|
|
47084
47105
|
...facialEnrolled ? { facialData: String(row.id ?? "") } : {}
|
|
47085
47106
|
},
|
|
@@ -47404,6 +47425,7 @@ function useHidAmicoService() {
|
|
|
47404
47425
|
site: reader.site,
|
|
47405
47426
|
type: "visitor_qr_reader_configured",
|
|
47406
47427
|
payload: {
|
|
47428
|
+
operatingMode: "standalone",
|
|
47407
47429
|
qrFormat: VISITOR_QR_FORMAT,
|
|
47408
47430
|
qrcodeIdentificationEnabled: "1",
|
|
47409
47431
|
multiFactorAuthentication: "0"
|
|
@@ -47550,7 +47572,11 @@ function useHidAmicoService() {
|
|
|
47550
47572
|
site: reader.site
|
|
47551
47573
|
});
|
|
47552
47574
|
}
|
|
47553
|
-
await repo.updateById(readerId, {
|
|
47575
|
+
await repo.updateById(readerId, {
|
|
47576
|
+
status: "active",
|
|
47577
|
+
lastSeenAt: /* @__PURE__ */ new Date(),
|
|
47578
|
+
operatingMode: "standalone"
|
|
47579
|
+
});
|
|
47554
47580
|
await repo.addEvent({
|
|
47555
47581
|
reader: readerId,
|
|
47556
47582
|
site: reader.site,
|
|
@@ -47593,6 +47619,32 @@ function useHidAmicoService() {
|
|
|
47593
47619
|
await client.logout();
|
|
47594
47620
|
}
|
|
47595
47621
|
}
|
|
47622
|
+
async function getVisitorQrForMember(readerId, visitorId, userId) {
|
|
47623
|
+
const reader = await authorizeVisitorForMember(readerId, visitorId, userId);
|
|
47624
|
+
const result = await repo.listIdentities({
|
|
47625
|
+
reader: readerId,
|
|
47626
|
+
type: "visitor",
|
|
47627
|
+
status: "active",
|
|
47628
|
+
page: 1,
|
|
47629
|
+
limit: 500
|
|
47630
|
+
});
|
|
47631
|
+
const identities = Array.isArray(result.items) ? result.items : [];
|
|
47632
|
+
const identity = identities.find((item) => String(item.visitor || "") === visitorId);
|
|
47633
|
+
const metadata = identity && isUnknownRecord2(identity.metadata) ? identity.metadata : {};
|
|
47634
|
+
const qrValue = String(metadata.qrValue || identity?.cardNo || "").trim();
|
|
47635
|
+
const expiresAt = String(metadata.expiresAt || "").trim();
|
|
47636
|
+
if (!qrValue || !expiresAt)
|
|
47637
|
+
return null;
|
|
47638
|
+
return {
|
|
47639
|
+
readerId,
|
|
47640
|
+
visitorId,
|
|
47641
|
+
qrValue,
|
|
47642
|
+
expiresAt,
|
|
47643
|
+
portalId: reader.portalId,
|
|
47644
|
+
portalName: reader.portalName,
|
|
47645
|
+
readerName: reader.name
|
|
47646
|
+
};
|
|
47647
|
+
}
|
|
47596
47648
|
async function getProfileFacial(readerId, userId) {
|
|
47597
47649
|
const reader = await getActiveReader(readerId);
|
|
47598
47650
|
const context = await getProfileEnrollmentContext(readerId, reader, userId);
|
|
@@ -47759,47 +47811,25 @@ function useHidAmicoService() {
|
|
|
47759
47811
|
try {
|
|
47760
47812
|
await client.login();
|
|
47761
47813
|
const facialUserIds = await getReaderFacialUserIds(client);
|
|
47762
|
-
const
|
|
47763
|
-
const identitiesByUserId = new Map(
|
|
47764
|
-
activeIdentities.filter((identity) => identity._id && identity.hidUserId !== void 0).map((identity) => [String(identity.hidUserId), identity])
|
|
47765
|
-
);
|
|
47766
|
-
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
47767
|
-
let syncedCount = 0;
|
|
47768
|
-
let unmappedCount = 0;
|
|
47769
|
-
for (const hidUserId of facialUserIds) {
|
|
47770
|
-
const identity = identitiesByUserId.get(String(hidUserId));
|
|
47771
|
-
if (!identity?._id) {
|
|
47772
|
-
unmappedCount += 1;
|
|
47773
|
-
continue;
|
|
47774
|
-
}
|
|
47775
|
-
const metadata = isUnknownRecord2(identity.metadata) ? identity.metadata : {};
|
|
47776
|
-
await repo.updateIdentity(identity._id, {
|
|
47777
|
-
metadata: {
|
|
47778
|
-
...metadata,
|
|
47779
|
-
facialData: String(hidUserId),
|
|
47780
|
-
facialEnrolled: true,
|
|
47781
|
-
facialSyncedAt: syncedAt
|
|
47782
|
-
}
|
|
47783
|
-
});
|
|
47784
|
-
syncedCount += 1;
|
|
47785
|
-
}
|
|
47786
|
-
const message = [
|
|
47787
|
-
facialUserIds.length ? `Detected ${facialUserIds.length} facial enrollment(s) on the HID reader.` : "No facial enrollments found on the HID reader.",
|
|
47788
|
-
syncedCount ? `${syncedCount} mapped HID user(s) updated.` : "",
|
|
47789
|
-
unmappedCount ? `${unmappedCount} facial enrollment(s) still need HID User Mapping.` : ""
|
|
47790
|
-
].filter(Boolean).join(" ");
|
|
47814
|
+
const message = facialUserIds.length ? `Found ${facialUserIds.length} Amico user(s) with facial data. Facial status and images are read directly from the reader.` : "No facial enrollments found on the HID reader.";
|
|
47791
47815
|
await repo.updateById(readerId, { status: "active", lastSeenAt: /* @__PURE__ */ new Date() });
|
|
47792
47816
|
await repo.addEvent({
|
|
47793
47817
|
reader: readerId,
|
|
47794
47818
|
site: reader.site,
|
|
47795
47819
|
type: "facial_sync",
|
|
47796
|
-
payload: {
|
|
47820
|
+
payload: {
|
|
47821
|
+
facialDetected: facialUserIds.length,
|
|
47822
|
+
source: "amico",
|
|
47823
|
+
hidUserIds: facialUserIds
|
|
47824
|
+
}
|
|
47797
47825
|
});
|
|
47798
47826
|
return {
|
|
47799
47827
|
readerId,
|
|
47800
47828
|
facialDetected: facialUserIds.length,
|
|
47801
|
-
|
|
47802
|
-
|
|
47829
|
+
// Retain these response fields for existing clients. A direct refresh
|
|
47830
|
+
// has no unmapped records and does not write a second iService copy.
|
|
47831
|
+
syncedCount: facialUserIds.length,
|
|
47832
|
+
unmappedCount: 0,
|
|
47803
47833
|
message
|
|
47804
47834
|
};
|
|
47805
47835
|
} catch (error) {
|
|
@@ -47976,7 +48006,7 @@ function useHidAmicoService() {
|
|
|
47976
48006
|
async function listUserCards(readerId, hidUserIdValue) {
|
|
47977
48007
|
const reader = await getActiveReader(readerId);
|
|
47978
48008
|
assertReaderCapability(reader, "card");
|
|
47979
|
-
const cardType =
|
|
48009
|
+
const cardType = reader.cardReadingMode === "pacs" || reader.cardReadingMode === "csn" ? reader.cardReadingMode : "unknown";
|
|
47980
48010
|
const hidUserId = Number(hidUserIdValue);
|
|
47981
48011
|
const client = new HidAmicoClient(reader);
|
|
47982
48012
|
try {
|
|
@@ -48936,9 +48966,11 @@ function useHidAmicoService() {
|
|
|
48936
48966
|
return {
|
|
48937
48967
|
authorizeReader,
|
|
48938
48968
|
authorizeReaderUser,
|
|
48969
|
+
authorizeVisitorForMember,
|
|
48939
48970
|
authorizeSiteForCaller,
|
|
48940
48971
|
authorizeHidAccess,
|
|
48941
48972
|
listReaders,
|
|
48973
|
+
getMemberReaderInfo,
|
|
48942
48974
|
discoverReader,
|
|
48943
48975
|
createReader,
|
|
48944
48976
|
updateReader,
|
|
@@ -48958,6 +48990,7 @@ function useHidAmicoService() {
|
|
|
48958
48990
|
deleteIdentity,
|
|
48959
48991
|
runObjectOperation,
|
|
48960
48992
|
issueVisitorQr,
|
|
48993
|
+
getVisitorQrForMember,
|
|
48961
48994
|
revokeVisitorQr,
|
|
48962
48995
|
queueVisitorQrJob,
|
|
48963
48996
|
processVisitorQrGatewayJobs,
|
|
@@ -58864,11 +58897,6 @@ function useSiteBillingConfigurationController() {
|
|
|
58864
58897
|
} = useSiteBillingConfigurationRepo();
|
|
58865
58898
|
const { add: _add } = useSiteBillingConfigurationService();
|
|
58866
58899
|
async function add(req, res, next) {
|
|
58867
|
-
const cookies = req.headers.cookie?.split(";").map((cookie) => cookie.trim().split("=")).reduce(
|
|
58868
|
-
(acc, [key, value2]) => ({ ...acc, [key]: value2 }),
|
|
58869
|
-
{}
|
|
58870
|
-
);
|
|
58871
|
-
req.body.createdBy = cookies?.["user"] ? cookies["user"].toString() : req.body.createdBy;
|
|
58872
58900
|
const data = { ...req.body };
|
|
58873
58901
|
const { error, value } = schemaBillingConfiguration.validate(data);
|
|
58874
58902
|
if (error) {
|
|
@@ -58876,7 +58904,8 @@ function useSiteBillingConfigurationController() {
|
|
|
58876
58904
|
return;
|
|
58877
58905
|
}
|
|
58878
58906
|
try {
|
|
58879
|
-
|
|
58907
|
+
await requireSiteReach(req, value.site);
|
|
58908
|
+
const question = await _add({ ...value, createdBy: callerId(req) });
|
|
58880
58909
|
res.status(201).json({
|
|
58881
58910
|
message: "Site Billing Configuration added successfully.",
|
|
58882
58911
|
data: question
|
|
@@ -58920,6 +58949,7 @@ function useSiteBillingConfigurationController() {
|
|
|
58920
58949
|
}
|
|
58921
58950
|
});
|
|
58922
58951
|
try {
|
|
58952
|
+
await requireSiteReach(req, site);
|
|
58923
58953
|
const data = await _getAll({
|
|
58924
58954
|
search,
|
|
58925
58955
|
page,
|
|
@@ -58948,6 +58978,7 @@ function useSiteBillingConfigurationController() {
|
|
|
58948
58978
|
}
|
|
58949
58979
|
try {
|
|
58950
58980
|
const building = await _getById(id);
|
|
58981
|
+
await requireSiteReach(req, building?.site);
|
|
58951
58982
|
res.json({
|
|
58952
58983
|
message: "Successfully retrieved site billing configuration.",
|
|
58953
58984
|
data: { building }
|
|
@@ -58970,6 +59001,10 @@ function useSiteBillingConfigurationController() {
|
|
|
58970
59001
|
return;
|
|
58971
59002
|
}
|
|
58972
59003
|
try {
|
|
59004
|
+
const current = await _getById(_id);
|
|
59005
|
+
await requireSiteReach(req, current?.site);
|
|
59006
|
+
if (req.body?.site)
|
|
59007
|
+
await requireSiteReach(req, req.body.site);
|
|
58973
59008
|
const result = await _updateById(_id, req.body);
|
|
58974
59009
|
res.status(200).json({ message: result });
|
|
58975
59010
|
return;
|
|
@@ -58989,6 +59024,8 @@ function useSiteBillingConfigurationController() {
|
|
|
58989
59024
|
return;
|
|
58990
59025
|
}
|
|
58991
59026
|
try {
|
|
59027
|
+
const current = await _getById(_id);
|
|
59028
|
+
await requireSiteReach(req, current?.site);
|
|
58992
59029
|
await _deleteById(_id);
|
|
58993
59030
|
res.status(200).json({ message: "Successfully deleted site billing configuration." });
|
|
58994
59031
|
return;
|
|
@@ -85966,6 +86003,20 @@ function useHidAmicoController() {
|
|
|
85966
86003
|
next(error2);
|
|
85967
86004
|
}
|
|
85968
86005
|
}
|
|
86006
|
+
async function getMemberReaderInfo(req, res, next) {
|
|
86007
|
+
const { error, value } = schemaHidAmicoReaderIdParams.validate(req.params);
|
|
86008
|
+
if (error) {
|
|
86009
|
+
next(new import_node_server_utils294.BadRequestError(error.message));
|
|
86010
|
+
return;
|
|
86011
|
+
}
|
|
86012
|
+
try {
|
|
86013
|
+
res.json({
|
|
86014
|
+
data: await service.getMemberReaderInfo(value.readerId, getAuthenticatedUserId(req))
|
|
86015
|
+
});
|
|
86016
|
+
} catch (requestError) {
|
|
86017
|
+
next(requestError);
|
|
86018
|
+
}
|
|
86019
|
+
}
|
|
85969
86020
|
async function createReader(req, res, next) {
|
|
85970
86021
|
const { error, value } = schemaHidAmicoReader.validate(req.body);
|
|
85971
86022
|
if (error) {
|
|
@@ -86153,7 +86204,8 @@ function useHidAmicoController() {
|
|
|
86153
86204
|
query2.value.limit,
|
|
86154
86205
|
query2.value.search,
|
|
86155
86206
|
query2.value.status,
|
|
86156
|
-
query2.value.includeVisitors
|
|
86207
|
+
query2.value.includeVisitors,
|
|
86208
|
+
query2.value.userType
|
|
86157
86209
|
));
|
|
86158
86210
|
} catch (error) {
|
|
86159
86211
|
next(error);
|
|
@@ -86359,6 +86411,11 @@ function useHidAmicoController() {
|
|
|
86359
86411
|
return;
|
|
86360
86412
|
}
|
|
86361
86413
|
try {
|
|
86414
|
+
await service.authorizeVisitorForMember(
|
|
86415
|
+
params.value.readerId,
|
|
86416
|
+
body.value.visitorId,
|
|
86417
|
+
getAuthenticatedUserId(req)
|
|
86418
|
+
);
|
|
86362
86419
|
res.status(201).json({
|
|
86363
86420
|
data: await service.issueVisitorQr(params.value.readerId, body.value)
|
|
86364
86421
|
});
|
|
@@ -86366,6 +86423,24 @@ function useHidAmicoController() {
|
|
|
86366
86423
|
next(error);
|
|
86367
86424
|
}
|
|
86368
86425
|
}
|
|
86426
|
+
async function getVisitorQr(req, res, next) {
|
|
86427
|
+
const { error, value } = schemaHidAmicoVisitorImageParams.validate(req.params);
|
|
86428
|
+
if (error) {
|
|
86429
|
+
next(new import_node_server_utils294.BadRequestError(error.message));
|
|
86430
|
+
return;
|
|
86431
|
+
}
|
|
86432
|
+
try {
|
|
86433
|
+
res.json({
|
|
86434
|
+
data: await service.getVisitorQrForMember(
|
|
86435
|
+
value.readerId,
|
|
86436
|
+
value.visitorId,
|
|
86437
|
+
getAuthenticatedUserId(req)
|
|
86438
|
+
)
|
|
86439
|
+
});
|
|
86440
|
+
} catch (requestError) {
|
|
86441
|
+
next(requestError);
|
|
86442
|
+
}
|
|
86443
|
+
}
|
|
86369
86444
|
async function discoverReader(req, res, next) {
|
|
86370
86445
|
const { error, value } = schemaDiscoverHidAmicoReader.validate(req.body);
|
|
86371
86446
|
if (error) {
|
|
@@ -86399,7 +86474,7 @@ function useHidAmicoController() {
|
|
|
86399
86474
|
return;
|
|
86400
86475
|
}
|
|
86401
86476
|
try {
|
|
86402
|
-
await
|
|
86477
|
+
await authorizeReaderUser(req, value.readerId, value.hidUserId);
|
|
86403
86478
|
res.json({ data: await service.listUserCards(value.readerId, value.hidUserId) });
|
|
86404
86479
|
} catch (requestError) {
|
|
86405
86480
|
next(requestError);
|
|
@@ -86433,7 +86508,7 @@ function useHidAmicoController() {
|
|
|
86433
86508
|
return;
|
|
86434
86509
|
}
|
|
86435
86510
|
try {
|
|
86436
|
-
await
|
|
86511
|
+
await authorizeReaderUser(req, params.value.readerId, params.value.hidUserId);
|
|
86437
86512
|
res.status(201).json({
|
|
86438
86513
|
data: await service.enrollUserCard(
|
|
86439
86514
|
params.value.readerId,
|
|
@@ -86466,7 +86541,7 @@ function useHidAmicoController() {
|
|
|
86466
86541
|
return;
|
|
86467
86542
|
}
|
|
86468
86543
|
try {
|
|
86469
|
-
await
|
|
86544
|
+
await authorizeReaderUser(req, value.readerId, value.hidUserId);
|
|
86470
86545
|
res.json({
|
|
86471
86546
|
data: await service.deleteUserCard(
|
|
86472
86547
|
value.readerId,
|
|
@@ -86725,6 +86800,7 @@ function useHidAmicoController() {
|
|
|
86725
86800
|
authorizeManage,
|
|
86726
86801
|
authorizeVisitor,
|
|
86727
86802
|
listReaders,
|
|
86803
|
+
getMemberReaderInfo,
|
|
86728
86804
|
discoverReader,
|
|
86729
86805
|
createReader,
|
|
86730
86806
|
updateReader,
|
|
@@ -86760,6 +86836,7 @@ function useHidAmicoController() {
|
|
|
86760
86836
|
setConfiguration,
|
|
86761
86837
|
runObjectOperation,
|
|
86762
86838
|
issueVisitorQr,
|
|
86839
|
+
getVisitorQr,
|
|
86763
86840
|
revokeVisitorQr,
|
|
86764
86841
|
getProfileFacial,
|
|
86765
86842
|
setProfileFacial,
|