@7365admin1/core 3.64.5-staging.303 → 3.64.5-staging.305
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/manpower-authenticated-namespace.md +27 -0
- package/.changeset/module-gate-kill-switch-and-read.md +26 -0
- 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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Manpower live status: an authenticated namespace beside the open one
|
|
6
|
+
|
|
7
|
+
`/manpower` streams every client site's live staffing level — how many guards are
|
|
8
|
+
actually on shift against how many were promised — to anyone who connects, with no
|
|
9
|
+
credential of any kind.
|
|
10
|
+
|
|
11
|
+
This adds `/manpower-live`, carrying exactly the same `manpower-status-update`
|
|
12
|
+
events, which admits a socket only when the handshake presents a session id that
|
|
13
|
+
resolves to a live session. Every refusal is an identical `Unauthorized`, and a
|
|
14
|
+
session store that is down refuses rather than letting everyone in.
|
|
15
|
+
|
|
16
|
+
`/manpower` itself is deliberately unchanged: the Capacitor guard app installed on
|
|
17
|
+
phones today and the Nuxt security screen both connect to it with no credential and
|
|
18
|
+
would be cut off the moment it were tightened. Every update is emitted to both, so
|
|
19
|
+
the two feeds can never disagree, and both namespaces share one connection handler,
|
|
20
|
+
so a client on the authenticated feed still starts the poll and still gets an
|
|
21
|
+
immediate snapshot.
|
|
22
|
+
|
|
23
|
+
A live session is the whole check, deliberately. Scoping the feed to the caller's
|
|
24
|
+
own sites is not possible today: manpower lives in HRM Labs' vendor id space, none
|
|
25
|
+
of those site ids match ours, and no field links the two — a site check would empty
|
|
26
|
+
the screen for the one customer using it. That mapping is a schema change and
|
|
27
|
+
belongs to the leads.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": patch
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Module gate: the kill switch now stops the 409, and the check stops reading when there is nothing to decide
|
|
6
|
+
|
|
7
|
+
Two follow-ups from the module-gate server review, both behaviour-neutral for
|
|
8
|
+
anybody who is not using the kill switch.
|
|
9
|
+
|
|
10
|
+
**`MODULE_LIST_GATE=off` now means off.** Every other part of the gate checked the
|
|
11
|
+
master switch; `mustAcknowledge` did not. So with the gate switched off, a save that
|
|
12
|
+
narrowed a client's module list was still refused with a 409 "acknowledge the impact
|
|
13
|
+
first" — the one lever a lead would reach for in an emergency, not working for the
|
|
14
|
+
one path that blocks a save from going through. Both the organisation and the site
|
|
15
|
+
save paths go through that function, so both are fixed. With the switch at its
|
|
16
|
+
default the 409 behaves exactly as before, and that is asserted.
|
|
17
|
+
|
|
18
|
+
**`moduleDeniedForCaller` no longer reads memberships when no list is enforced.** It
|
|
19
|
+
runs on every governed request and read the caller's memberships before finding out
|
|
20
|
+
whether there was anything to decide — and `members.user` has no index to use. It now
|
|
21
|
+
returns early unless the site or its organisation has an enforced list, which is what
|
|
22
|
+
the notification filter already did. No verdict changes: a client that really has an
|
|
23
|
+
enforced list is still measured exactly as before, and that is asserted too.
|
|
24
|
+
|
|
25
|
+
Also corrects the 3.65.0 changelog, which claimed `MODULE_LIST_GATE` switched all of
|
|
26
|
+
the gate off at once. It did not, for the reason above.
|
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
|
});
|
|
@@ -15855,6 +15855,8 @@ function deniedForMembership({
|
|
|
15855
15855
|
return { denied, since: [orgTier.since, siteTier.since].sort().pop() ?? "" };
|
|
15856
15856
|
}
|
|
15857
15857
|
function mustAcknowledge(preview, acknowledged) {
|
|
15858
|
+
if (!moduleListGateOn())
|
|
15859
|
+
return false;
|
|
15858
15860
|
if (acknowledged === true || !preview.newlyDenied.length)
|
|
15859
15861
|
return false;
|
|
15860
15862
|
return !preview.impact || preview.impact.memberCount > 0;
|
|
@@ -87847,6 +87849,52 @@ var isWithinHour = (alertTime, timeNow) => {
|
|
|
87847
87849
|
var import_node_server_utils264 = require("@7365admin1/node-server-utils");
|
|
87848
87850
|
var import_mongodb162 = require("mongodb");
|
|
87849
87851
|
var import_moment_timezone11 = __toESM(require("moment-timezone"));
|
|
87852
|
+
|
|
87853
|
+
// src/utils/socket-session.util.ts
|
|
87854
|
+
var MANPOWER_LIVE_NAMESPACE = "/manpower-live";
|
|
87855
|
+
function sidFromHandshake(handshake) {
|
|
87856
|
+
const cookie = String(handshake?.headers?.cookie ?? "");
|
|
87857
|
+
const fromCookie = /(?:^|;\s*)sid=([^;]*)/.exec(cookie);
|
|
87858
|
+
if (fromCookie) {
|
|
87859
|
+
const value = decodeURIComponent(fromCookie[1]).trim();
|
|
87860
|
+
if (value)
|
|
87861
|
+
return value;
|
|
87862
|
+
}
|
|
87863
|
+
for (const key of ["sid", "token"]) {
|
|
87864
|
+
const value = handshake?.auth?.[key];
|
|
87865
|
+
if (typeof value === "string" && value.trim())
|
|
87866
|
+
return value.trim();
|
|
87867
|
+
}
|
|
87868
|
+
const header = handshake?.headers?.authorization;
|
|
87869
|
+
if (typeof header === "string" && header.trim()) {
|
|
87870
|
+
const value = header.trim();
|
|
87871
|
+
return value.toLowerCase().startsWith("bearer ") ? value.slice("bearer ".length).trim() : value;
|
|
87872
|
+
}
|
|
87873
|
+
return "";
|
|
87874
|
+
}
|
|
87875
|
+
function userIdOfSession(session) {
|
|
87876
|
+
if (!session || typeof session !== "object")
|
|
87877
|
+
return "";
|
|
87878
|
+
const record = session;
|
|
87879
|
+
return String(record.user ?? record._id ?? "").trim();
|
|
87880
|
+
}
|
|
87881
|
+
async function authoriseSocketSession(handshake, readSession) {
|
|
87882
|
+
const sid = sidFromHandshake(handshake);
|
|
87883
|
+
if (!sid)
|
|
87884
|
+
throw new Error("Unauthorized");
|
|
87885
|
+
let session;
|
|
87886
|
+
try {
|
|
87887
|
+
session = await readSession(sid);
|
|
87888
|
+
} catch {
|
|
87889
|
+
throw new Error("Unauthorized");
|
|
87890
|
+
}
|
|
87891
|
+
const userId = userIdOfSession(session);
|
|
87892
|
+
if (!userId)
|
|
87893
|
+
throw new Error("Unauthorized");
|
|
87894
|
+
return userId;
|
|
87895
|
+
}
|
|
87896
|
+
|
|
87897
|
+
// src/events/manpower.event.ts
|
|
87850
87898
|
async function manpowerEvents(io) {
|
|
87851
87899
|
let intervalId = null;
|
|
87852
87900
|
let activeConnections = 0;
|
|
@@ -87907,8 +87955,7 @@ async function manpowerEvents(io) {
|
|
|
87907
87955
|
newStatus = "Over";
|
|
87908
87956
|
else if (actual < expected)
|
|
87909
87957
|
newStatus = "Under";
|
|
87910
|
-
const
|
|
87911
|
-
io.of(namespace2).emit("manpower-status-update", {
|
|
87958
|
+
const update = {
|
|
87912
87959
|
siteId: doc.siteId,
|
|
87913
87960
|
siteName: doc.siteName,
|
|
87914
87961
|
shift: shift.key,
|
|
@@ -87916,7 +87963,9 @@ async function manpowerEvents(io) {
|
|
|
87916
87963
|
actual,
|
|
87917
87964
|
expected,
|
|
87918
87965
|
selectedDateSGT
|
|
87919
|
-
}
|
|
87966
|
+
};
|
|
87967
|
+
io.of("/manpower").emit("manpower-status-update", update);
|
|
87968
|
+
io.of(MANPOWER_LIVE_NAMESPACE).emit("manpower-status-update", update);
|
|
87920
87969
|
}
|
|
87921
87970
|
}
|
|
87922
87971
|
};
|
|
@@ -87936,7 +87985,7 @@ async function manpowerEvents(io) {
|
|
|
87936
87985
|
console.log(" Interval stopped.");
|
|
87937
87986
|
}
|
|
87938
87987
|
};
|
|
87939
|
-
|
|
87988
|
+
const onConnection = (socket) => {
|
|
87940
87989
|
activeConnections += 1;
|
|
87941
87990
|
const date = socket.handshake.query.date;
|
|
87942
87991
|
if (activeConnections === 1) {
|
|
@@ -87951,7 +88000,26 @@ async function manpowerEvents(io) {
|
|
|
87951
88000
|
stopInterval();
|
|
87952
88001
|
}
|
|
87953
88002
|
});
|
|
88003
|
+
};
|
|
88004
|
+
io.of("/manpower").on("connection", onConnection);
|
|
88005
|
+
const live = io.of(MANPOWER_LIVE_NAMESPACE);
|
|
88006
|
+
live.use(async (socket, next) => {
|
|
88007
|
+
try {
|
|
88008
|
+
const { getCache } = (0, import_node_server_utils264.useCache)("sessions");
|
|
88009
|
+
socket.data.userId = await authoriseSocketSession(
|
|
88010
|
+
socket.handshake,
|
|
88011
|
+
(sid) => getCache(`sid:${sid}`)
|
|
88012
|
+
);
|
|
88013
|
+
next();
|
|
88014
|
+
} catch (error) {
|
|
88015
|
+
import_node_server_utils264.logger.log({
|
|
88016
|
+
level: "debug",
|
|
88017
|
+
message: `[manpower] refused a socket: ${error?.message}`
|
|
88018
|
+
});
|
|
88019
|
+
next(new Error("Unauthorized"));
|
|
88020
|
+
}
|
|
87954
88021
|
});
|
|
88022
|
+
live.on("connection", onConnection);
|
|
87955
88023
|
}
|
|
87956
88024
|
|
|
87957
88025
|
// src/services/reddot-payment.service.ts
|
|
@@ -96817,6 +96885,8 @@ async function moduleDeniedForCaller({
|
|
|
96817
96885
|
if (!user || !siteId)
|
|
96818
96886
|
return ALLOW;
|
|
96819
96887
|
const siteDoc = typeof site === "string" ? await data.site(siteId).catch(() => null) : site ?? null;
|
|
96888
|
+
if (!await siteHasEnforcedList(siteDoc, data))
|
|
96889
|
+
return ALLOW;
|
|
96820
96890
|
const gate = await gateAtSite(await data.membershipsOf([user]), siteDoc, siteId, data);
|
|
96821
96891
|
if (gate.exempt)
|
|
96822
96892
|
return ALLOW;
|