@7365admin1/core 3.48.1-staging.201 → 3.48.1-staging.203

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.
@@ -0,0 +1,35 @@
1
+ ---
2
+ "@7365admin1/core": patch
3
+ ---
4
+
5
+ Decide the HRM Labs location server-side, so an entitled caller cannot read
6
+ another site's roster.
7
+
8
+ `requireSiteReach(req, siteId, ATTENDANCE_READ)` guards all four manpower-attendance
9
+ reads, but `siteId` is not what selects the roster — it only picks the local
10
+ `manpower-settings` row. The roster is selected by `locationName`, the field HRM Labs
11
+ is asked for, and that came straight from the request body's `siteName` with nothing
12
+ checking it against `siteId`.
13
+
14
+ So the two could disagree. A caller entitled to site A could send site A's `siteId`,
15
+ pass the guard, and pair it with **another** site's `siteName` — and read that site's
16
+ employee roster back: names, staff numbers, job titles, NRICs
17
+ (`identificationNumber`) and the check-in/check-out photographs. The exposure had been
18
+ narrowed from anyone on the internet to any signed-in account; this closes it.
19
+
20
+ The location now comes from `config.siteName`, the settings row the caller was already
21
+ entitled to, via one `locationOf()` helper called in each of the four functions. That
22
+ is the same source the screen reads from: the security app fills `siteId` and
23
+ `siteName` from one HRM Labs site-list entry (`site.id` / `site.text`),
24
+ `manpowerMonitoringSchema` makes `siteName` required on both write paths, and
25
+ `TManpowerMonitoringUpdate` cannot change it — so for every legitimate row the pair
26
+ already agreed and the server simply stops asking.
27
+
28
+ A row with no stored name is refused rather than falling back to the body; a fallback
29
+ would re-open the hole for exactly the rows an attacker would want to name for
30
+ themselves.
31
+
32
+ Nothing legitimate changes. The Joi schemas still accept `siteName`, so no client needs
33
+ a coordinated release — it is accepted and ignored. The three internal callers
34
+ (`manpower.event.ts` and the two `cron.util.ts` sweeps) get more reliable, not less:
35
+ they were passing `manpower-remarks.siteName`, which that model marks optional.
@@ -0,0 +1,38 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope invitation creation to an organisation the caller can reach, and stop a
6
+ non-staff caller minting a Seven365 staff invitation on the v1 endpoints.
7
+
8
+ Six endpoints took `metadata.org` — and the `app` that becomes the accepted
9
+ membership's `type` — straight from the request body, behind `requireAuth` and
10
+ nothing else:
11
+
12
+ - `POST /api/auth/invite` (`auth.route.ts:29`)
13
+ - `POST /api/auth/invite/user` (`auth.route.ts:30`)
14
+ - `POST /api/auth/invite/member` (`auth.route.ts:31`)
15
+ - `POST /api/auth/invite/service-provider/with-org` (`auth.route.ts:33`)
16
+ - `POST /api/auth/v2/invite` (`auth-v2.route.ts:22`)
17
+ - `POST /api/auth/v2/invite/organization` (`auth-v2.route.ts:23`)
18
+
19
+ Two things followed from that.
20
+
21
+ **The organisation.** The roles/members fix lets somebody holding an invitation
22
+ into an organisation reach it, because the client onboarding wizard needs that.
23
+ An attacker could therefore issue themselves the invitation the gate asks for —
24
+ one authenticated write naming any victim organisation — and walk straight back
25
+ into the chain that fix closed.
26
+
27
+ **`app`.** "admin" is what makes a membership Seven365 staff, and
28
+ `member.service.ts acceptInvite` writes `type: invite.metadata.app` verbatim.
29
+ `createMemberDirect` already refuses a staff membership from a non-staff caller;
30
+ the invitation road to the same place was open on the v1 mounts. The v2 twin
31
+ asked; the v1 mounts, which are the ones the web apps call, did not.
32
+
33
+ All six now call `requireInviteReach` (`utils/console-authz.util.ts`): Seven365
34
+ staff, a live member of the organisation, or somebody holding an invitation into
35
+ it — plus `requirePlatformStaff` when the invitation names the staff app. An
36
+ invitation that names no organisation is unchanged; it grants no organisation,
37
+ exactly as an org-less `GET /api/roles` does. The two service-provider invites
38
+ that already resolved their caller from the session are unchanged.
package/dist/index.js CHANGED
@@ -18337,6 +18337,14 @@ async function requireOrgReach(req, orgId) {
18337
18337
  return id;
18338
18338
  throw new import_node_server_utils41.UnauthorizedError("Not authorized.");
18339
18339
  }
18340
+ async function requireInviteReach(req, org, app) {
18341
+ const apps = Array.isArray(app) ? app : [app];
18342
+ if (apps.some((a) => a === PLATFORM_STAFF_MEMBER_TYPE)) {
18343
+ await requirePlatformStaff(req);
18344
+ }
18345
+ if (org)
18346
+ await requireOrgReach(req, org);
18347
+ }
18340
18348
 
18341
18349
  // src/utils/user-response.util.ts
18342
18350
  function withoutCredentials(user) {
@@ -26092,6 +26100,7 @@ function useVerificationController() {
26092
26100
  const siteId = req.body.siteId ?? "";
26093
26101
  const siteName = req.body.siteName ?? "";
26094
26102
  try {
26103
+ await requireInviteReach(req, org, app);
26095
26104
  await _createUserInvite({
26096
26105
  email,
26097
26106
  metadata: {
@@ -26145,6 +26154,7 @@ function useVerificationController() {
26145
26154
  siteId,
26146
26155
  siteName
26147
26156
  } = value;
26157
+ await requireInviteReach(req, org, app);
26148
26158
  const verificationIds = await _createSimpleUserInvite({
26149
26159
  email,
26150
26160
  metadata: {
@@ -26208,6 +26218,7 @@ function useVerificationController() {
26208
26218
  const siteId = req.body.siteId ?? "";
26209
26219
  const siteName = req.body.siteName ?? "";
26210
26220
  try {
26221
+ await requireInviteReach(req, org, app);
26211
26222
  await _createSimpleMemberInvite({
26212
26223
  email,
26213
26224
  metadata: {
@@ -26332,6 +26343,7 @@ function useVerificationController() {
26332
26343
  const siteId = req.body.siteId ?? "";
26333
26344
  const siteName = req.body.siteName ?? "";
26334
26345
  try {
26346
+ await requireInviteReach(req, orgId, app);
26335
26347
  await _createSimpleServiceProviderInvite({
26336
26348
  email,
26337
26349
  app,
@@ -78077,6 +78089,15 @@ var {
78077
78089
  totalCountPerStatus: totalCountPerStatus2,
78078
78090
  chartCountData: chartCountData2
78079
78091
  } = hrmlabs_attendance_util_default;
78092
+ function locationOf(config2) {
78093
+ const name = typeof config2?.siteName === "string" ? config2.siteName.trim() : "";
78094
+ if (!name) {
78095
+ throw new Error(
78096
+ "Manpower monitoring settings for this site have no HRM Labs location name."
78097
+ );
78098
+ }
78099
+ return name;
78100
+ }
78080
78101
  function useHrmLabsAttendanceSrvc() {
78081
78102
  const { getManpowerSettingsBySiteId: _getManpowerSettingsBySiteId } = useManpowerMonitoringRepo();
78082
78103
  const { getManpowerDesignationsBySiteId: _getManpowerDesignationsBySiteId } = useManpowerDesignationRepo();
@@ -78084,7 +78105,6 @@ function useHrmLabsAttendanceSrvc() {
78084
78105
  const {
78085
78106
  startDate,
78086
78107
  endDate,
78087
- siteName,
78088
78108
  siteId,
78089
78109
  shiftName,
78090
78110
  search,
@@ -78114,6 +78134,7 @@ function useHrmLabsAttendanceSrvc() {
78114
78134
  message: "Manpower monitoring settings not found for the provided site ID."
78115
78135
  };
78116
78136
  }
78137
+ const siteName = locationOf(config2);
78117
78138
  const shiftType = config2.shiftType === "2-shifts" ? "2-shifts" : "3-shifts";
78118
78139
  const shiftData = config2.shifts[shiftType].find(
78119
78140
  (s) => s.name === shiftName
@@ -78183,7 +78204,7 @@ function useHrmLabsAttendanceSrvc() {
78183
78204
  }
78184
78205
  }
78185
78206
  async function getAttendanceDataCount(payload) {
78186
- const { startDate, endDate, siteName, siteId, search, serviceProviderId } = payload;
78207
+ const { startDate, endDate, siteId, search, serviceProviderId } = payload;
78187
78208
  const username = process.env.HRMLABS_USERNAME;
78188
78209
  const domain = process.env.HRMLABS_DOMAIN;
78189
78210
  const password = process.env.HRMLABS_PASSWORD;
@@ -78206,6 +78227,7 @@ function useHrmLabsAttendanceSrvc() {
78206
78227
  message: "Manpower monitoring settings not found for the provided site ID."
78207
78228
  };
78208
78229
  }
78230
+ const siteName = locationOf(config2);
78209
78231
  const shiftType = config2.shiftType;
78210
78232
  const shiftData = config2.shifts[shiftType];
78211
78233
  if (!shiftData) {
@@ -78296,7 +78318,7 @@ function useHrmLabsAttendanceSrvc() {
78296
78318
  }
78297
78319
  }
78298
78320
  async function getAllAttendance(payload) {
78299
- const { startDate, endDate, siteName, siteId, search, serviceProviderId } = payload;
78321
+ const { startDate, endDate, siteId, search, serviceProviderId } = payload;
78300
78322
  let { shiftName } = payload;
78301
78323
  const username = process.env.HRMLABS_USERNAME;
78302
78324
  const domain = process.env.HRMLABS_DOMAIN;
@@ -78319,6 +78341,7 @@ function useHrmLabsAttendanceSrvc() {
78319
78341
  message: "Manpower monitoring settings not found for the provided site ID."
78320
78342
  };
78321
78343
  }
78344
+ const siteName = locationOf(config2);
78322
78345
  const designationConfig = await _getManpowerDesignationsBySiteId(
78323
78346
  siteId,
78324
78347
  serviceProviderId
@@ -78470,7 +78493,6 @@ function useHrmLabsAttendanceSrvc() {
78470
78493
  const {
78471
78494
  startDate,
78472
78495
  endDate,
78473
- siteName,
78474
78496
  siteId,
78475
78497
  search,
78476
78498
  serviceProviderId,
@@ -78498,6 +78520,7 @@ function useHrmLabsAttendanceSrvc() {
78498
78520
  message: "Manpower monitoring settings not found for the provided site ID."
78499
78521
  };
78500
78522
  }
78523
+ const siteName = locationOf(config2);
78501
78524
  const designationConfig = await _getManpowerDesignationsBySiteId(
78502
78525
  siteId,
78503
78526
  serviceProviderId
@@ -81041,9 +81064,7 @@ function useVerificationControllerV2() {
81041
81064
  }
81042
81065
  const { email, app, role, name, org, siteId, siteName } = value;
81043
81066
  try {
81044
- if (app === PLATFORM_STAFF_MEMBER_TYPE) {
81045
- await requirePlatformStaff(req);
81046
- }
81067
+ await requireInviteReach(req, org, app);
81047
81068
  await _createUserInvite({
81048
81069
  email,
81049
81070
  metadata: {
@@ -81115,6 +81136,7 @@ function useVerificationControllerV2() {
81115
81136
  }
81116
81137
  try {
81117
81138
  const { email, app, role, name, org, siteId, siteName } = value;
81139
+ await requireInviteReach(req, org, app);
81118
81140
  await _createOrganizationInvite({
81119
81141
  email,
81120
81142
  metadata: {