@7365admin1/core 3.67.0 → 3.68.0
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/CHANGELOG.md +56 -1
- package/dist/index.js +80 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +341 -271
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/module-access.test.mjs +27 -0
- package/test/module-gate-kill-switch.test.mjs +48 -0
- package/test/socket-session.test.mjs +119 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @iservice365/core
|
|
2
2
|
|
|
3
|
+
## 3.68.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d7e9298: Manpower live status: an authenticated namespace beside the open one
|
|
8
|
+
|
|
9
|
+
`/manpower` streams every client site's live staffing level — how many guards are
|
|
10
|
+
actually on shift against how many were promised — to anyone who connects, with no
|
|
11
|
+
credential of any kind.
|
|
12
|
+
|
|
13
|
+
This adds `/manpower-live`, carrying exactly the same `manpower-status-update`
|
|
14
|
+
events, which admits a socket only when the handshake presents a session id that
|
|
15
|
+
resolves to a live session. Every refusal is an identical `Unauthorized`, and a
|
|
16
|
+
session store that is down refuses rather than letting everyone in.
|
|
17
|
+
|
|
18
|
+
`/manpower` itself is deliberately unchanged: the Capacitor guard app installed on
|
|
19
|
+
phones today and the Nuxt security screen both connect to it with no credential and
|
|
20
|
+
would be cut off the moment it were tightened. Every update is emitted to both, so
|
|
21
|
+
the two feeds can never disagree, and both namespaces share one connection handler,
|
|
22
|
+
so a client on the authenticated feed still starts the poll and still gets an
|
|
23
|
+
immediate snapshot.
|
|
24
|
+
|
|
25
|
+
A live session is the whole check, deliberately. Scoping the feed to the caller's
|
|
26
|
+
own sites is not possible today: manpower lives in HRM Labs' vendor id space, none
|
|
27
|
+
of those site ids match ours, and no field links the two — a site check would empty
|
|
28
|
+
the screen for the one customer using it. That mapping is a schema change and
|
|
29
|
+
belongs to the leads.
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- 87c8679: Module gate: the kill switch now stops the 409, and the check stops reading when there is nothing to decide
|
|
34
|
+
|
|
35
|
+
Two follow-ups from the module-gate server review, both behaviour-neutral for
|
|
36
|
+
anybody who is not using the kill switch.
|
|
37
|
+
|
|
38
|
+
**`MODULE_LIST_GATE=off` now means off.** Every other part of the gate checked the
|
|
39
|
+
master switch; `mustAcknowledge` did not. So with the gate switched off, a save that
|
|
40
|
+
narrowed a client's module list was still refused with a 409 "acknowledge the impact
|
|
41
|
+
first" — the one lever a lead would reach for in an emergency, not working for the
|
|
42
|
+
one path that blocks a save from going through. Both the organisation and the site
|
|
43
|
+
save paths go through that function, so both are fixed. With the switch at its
|
|
44
|
+
default the 409 behaves exactly as before, and that is asserted.
|
|
45
|
+
|
|
46
|
+
**`moduleDeniedForCaller` no longer reads memberships when no list is enforced.** It
|
|
47
|
+
runs on every governed request and read the caller's memberships before finding out
|
|
48
|
+
whether there was anything to decide — and `members.user` has no index to use. It now
|
|
49
|
+
returns early unless the site or its organisation has an enforced list, which is what
|
|
50
|
+
the notification filter already did. No verdict changes: a client that really has an
|
|
51
|
+
enforced list is still measured exactly as before, and that is asserted too.
|
|
52
|
+
|
|
53
|
+
Also corrects the 3.65.0 changelog, which claimed `MODULE_LIST_GATE` switched all of
|
|
54
|
+
the gate off at once. It did not, for the reason above.
|
|
55
|
+
|
|
3
56
|
## 3.67.0
|
|
4
57
|
|
|
5
58
|
### Minor Changes
|
|
@@ -87,7 +140,9 @@
|
|
|
87
140
|
the keys in `MODULE_GATE_ENFORCE_KEYS`. Work stamped (`at`) before the
|
|
88
141
|
acknowledged save is accepted.
|
|
89
142
|
- `MODULE_LIST_GATE` (code default `on`) switches all of the above off at
|
|
90
|
-
once.
|
|
143
|
+
once. CORRECTION (3.68.0): it did not. `mustAcknowledge` never read it, so
|
|
144
|
+
with the gate off a narrowing save was still refused with a 409. Fixed in
|
|
145
|
+
3.68.0; every other path did check it.
|
|
91
146
|
|
|
92
147
|
### Patch Changes
|
|
93
148
|
|
package/dist/index.js
CHANGED
|
@@ -536,13 +536,13 @@ var require_logger = __commonJS({
|
|
|
536
536
|
"use strict";
|
|
537
537
|
exports.__esModule = true;
|
|
538
538
|
var _utils = require_utils();
|
|
539
|
-
var
|
|
539
|
+
var logger227 = {
|
|
540
540
|
methodMap: ["debug", "info", "warn", "error"],
|
|
541
541
|
level: "info",
|
|
542
542
|
// Maps a given level value to the `methodMap` indexes above.
|
|
543
543
|
lookupLevel: function lookupLevel(level) {
|
|
544
544
|
if (typeof level === "string") {
|
|
545
|
-
var levelMap = _utils.indexOf(
|
|
545
|
+
var levelMap = _utils.indexOf(logger227.methodMap, level.toLowerCase());
|
|
546
546
|
if (levelMap >= 0) {
|
|
547
547
|
level = levelMap;
|
|
548
548
|
} else {
|
|
@@ -553,9 +553,9 @@ var require_logger = __commonJS({
|
|
|
553
553
|
},
|
|
554
554
|
// Can be overridden in the host environment
|
|
555
555
|
log: function log(level) {
|
|
556
|
-
level =
|
|
557
|
-
if (typeof console !== "undefined" &&
|
|
558
|
-
var method =
|
|
556
|
+
level = logger227.lookupLevel(level);
|
|
557
|
+
if (typeof console !== "undefined" && logger227.lookupLevel(logger227.level) <= level) {
|
|
558
|
+
var method = logger227.methodMap[level];
|
|
559
559
|
if (!console[method]) {
|
|
560
560
|
method = "log";
|
|
561
561
|
}
|
|
@@ -566,7 +566,7 @@ var require_logger = __commonJS({
|
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
568
|
};
|
|
569
|
-
exports["default"] =
|
|
569
|
+
exports["default"] = logger227;
|
|
570
570
|
module2.exports = exports["default"];
|
|
571
571
|
}
|
|
572
572
|
});
|
|
@@ -15844,6 +15844,8 @@ function deniedForMembership({
|
|
|
15844
15844
|
return { denied, since: [orgTier.since, siteTier.since].sort().pop() ?? "" };
|
|
15845
15845
|
}
|
|
15846
15846
|
function mustAcknowledge(preview, acknowledged) {
|
|
15847
|
+
if (!moduleListGateOn())
|
|
15848
|
+
return false;
|
|
15847
15849
|
if (acknowledged === true || !preview.newlyDenied.length)
|
|
15848
15850
|
return false;
|
|
15849
15851
|
return !preview.impact || preview.impact.memberCount > 0;
|
|
@@ -85931,6 +85933,52 @@ var isWithinHour = (alertTime, timeNow) => {
|
|
|
85931
85933
|
var import_node_server_utils264 = require("@7365admin1/node-server-utils");
|
|
85932
85934
|
var import_mongodb162 = require("mongodb");
|
|
85933
85935
|
var import_moment_timezone11 = __toESM(require("moment-timezone"));
|
|
85936
|
+
|
|
85937
|
+
// src/utils/socket-session.util.ts
|
|
85938
|
+
var MANPOWER_LIVE_NAMESPACE = "/manpower-live";
|
|
85939
|
+
function sidFromHandshake(handshake) {
|
|
85940
|
+
const cookie = String(handshake?.headers?.cookie ?? "");
|
|
85941
|
+
const fromCookie = /(?:^|;\s*)sid=([^;]*)/.exec(cookie);
|
|
85942
|
+
if (fromCookie) {
|
|
85943
|
+
const value = decodeURIComponent(fromCookie[1]).trim();
|
|
85944
|
+
if (value)
|
|
85945
|
+
return value;
|
|
85946
|
+
}
|
|
85947
|
+
for (const key of ["sid", "token"]) {
|
|
85948
|
+
const value = handshake?.auth?.[key];
|
|
85949
|
+
if (typeof value === "string" && value.trim())
|
|
85950
|
+
return value.trim();
|
|
85951
|
+
}
|
|
85952
|
+
const header = handshake?.headers?.authorization;
|
|
85953
|
+
if (typeof header === "string" && header.trim()) {
|
|
85954
|
+
const value = header.trim();
|
|
85955
|
+
return value.toLowerCase().startsWith("bearer ") ? value.slice("bearer ".length).trim() : value;
|
|
85956
|
+
}
|
|
85957
|
+
return "";
|
|
85958
|
+
}
|
|
85959
|
+
function userIdOfSession(session) {
|
|
85960
|
+
if (!session || typeof session !== "object")
|
|
85961
|
+
return "";
|
|
85962
|
+
const record = session;
|
|
85963
|
+
return String(record.user ?? record._id ?? "").trim();
|
|
85964
|
+
}
|
|
85965
|
+
async function authoriseSocketSession(handshake, readSession) {
|
|
85966
|
+
const sid = sidFromHandshake(handshake);
|
|
85967
|
+
if (!sid)
|
|
85968
|
+
throw new Error("Unauthorized");
|
|
85969
|
+
let session;
|
|
85970
|
+
try {
|
|
85971
|
+
session = await readSession(sid);
|
|
85972
|
+
} catch {
|
|
85973
|
+
throw new Error("Unauthorized");
|
|
85974
|
+
}
|
|
85975
|
+
const userId = userIdOfSession(session);
|
|
85976
|
+
if (!userId)
|
|
85977
|
+
throw new Error("Unauthorized");
|
|
85978
|
+
return userId;
|
|
85979
|
+
}
|
|
85980
|
+
|
|
85981
|
+
// src/events/manpower.event.ts
|
|
85934
85982
|
async function manpowerEvents(io) {
|
|
85935
85983
|
let intervalId = null;
|
|
85936
85984
|
let activeConnections = 0;
|
|
@@ -85991,8 +86039,7 @@ async function manpowerEvents(io) {
|
|
|
85991
86039
|
newStatus = "Over";
|
|
85992
86040
|
else if (actual < expected)
|
|
85993
86041
|
newStatus = "Under";
|
|
85994
|
-
const
|
|
85995
|
-
io.of(namespace2).emit("manpower-status-update", {
|
|
86042
|
+
const update = {
|
|
85996
86043
|
siteId: doc.siteId,
|
|
85997
86044
|
siteName: doc.siteName,
|
|
85998
86045
|
shift: shift.key,
|
|
@@ -86000,7 +86047,9 @@ async function manpowerEvents(io) {
|
|
|
86000
86047
|
actual,
|
|
86001
86048
|
expected,
|
|
86002
86049
|
selectedDateSGT
|
|
86003
|
-
}
|
|
86050
|
+
};
|
|
86051
|
+
io.of("/manpower").emit("manpower-status-update", update);
|
|
86052
|
+
io.of(MANPOWER_LIVE_NAMESPACE).emit("manpower-status-update", update);
|
|
86004
86053
|
}
|
|
86005
86054
|
}
|
|
86006
86055
|
};
|
|
@@ -86020,7 +86069,7 @@ async function manpowerEvents(io) {
|
|
|
86020
86069
|
console.log(" Interval stopped.");
|
|
86021
86070
|
}
|
|
86022
86071
|
};
|
|
86023
|
-
|
|
86072
|
+
const onConnection = (socket) => {
|
|
86024
86073
|
activeConnections += 1;
|
|
86025
86074
|
const date = socket.handshake.query.date;
|
|
86026
86075
|
if (activeConnections === 1) {
|
|
@@ -86035,7 +86084,26 @@ async function manpowerEvents(io) {
|
|
|
86035
86084
|
stopInterval();
|
|
86036
86085
|
}
|
|
86037
86086
|
});
|
|
86087
|
+
};
|
|
86088
|
+
io.of("/manpower").on("connection", onConnection);
|
|
86089
|
+
const live = io.of(MANPOWER_LIVE_NAMESPACE);
|
|
86090
|
+
live.use(async (socket, next) => {
|
|
86091
|
+
try {
|
|
86092
|
+
const { getCache } = (0, import_node_server_utils264.useCache)("sessions");
|
|
86093
|
+
socket.data.userId = await authoriseSocketSession(
|
|
86094
|
+
socket.handshake,
|
|
86095
|
+
(sid) => getCache(`sid:${sid}`)
|
|
86096
|
+
);
|
|
86097
|
+
next();
|
|
86098
|
+
} catch (error) {
|
|
86099
|
+
import_node_server_utils264.logger.log({
|
|
86100
|
+
level: "debug",
|
|
86101
|
+
message: `[manpower] refused a socket: ${error?.message}`
|
|
86102
|
+
});
|
|
86103
|
+
next(new Error("Unauthorized"));
|
|
86104
|
+
}
|
|
86038
86105
|
});
|
|
86106
|
+
live.on("connection", onConnection);
|
|
86039
86107
|
}
|
|
86040
86108
|
|
|
86041
86109
|
// src/services/reddot-payment.service.ts
|
|
@@ -94810,6 +94878,8 @@ async function moduleDeniedForCaller({
|
|
|
94810
94878
|
if (!user || !siteId)
|
|
94811
94879
|
return ALLOW;
|
|
94812
94880
|
const siteDoc = typeof site === "string" ? await data.site(siteId).catch(() => null) : site ?? null;
|
|
94881
|
+
if (!await siteHasEnforcedList(siteDoc, data))
|
|
94882
|
+
return ALLOW;
|
|
94813
94883
|
const gate = await gateAtSite(await data.membershipsOf([user]), siteDoc, siteId, data);
|
|
94814
94884
|
if (gate.exempt)
|
|
94815
94885
|
return ALLOW;
|