@7365admin1/core 3.48.1-staging.205 → 3.48.1-staging.207

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,39 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Scope the organisation list to the caller, and put the promo-code list behind the staff gate.
6
+
7
+ `GET /api/organizations` and `GET /api/promo-codes` performed no authorization
8
+ at all — `requireAuth` and nothing more — while every sibling handler in the
9
+ same two files guarded. So any signed-in account on the platform, a resident or
10
+ a cleaner included, could page through the name, e-mail, nature and status of
11
+ **every Seven365 client**, and read off **every discount code** we have ever
12
+ minted, with its rate and its expiry, and then redeem whichever is worth most.
13
+
14
+ The two rules are different, because the callers are different.
15
+
16
+ **`GET /api/organizations` is now a SCOPE, not a refusal.** Seven365 staff see
17
+ every organisation exactly as before; anybody else sees the organisations they
18
+ hold a live membership of, plus any registered under their own verified account
19
+ e-mail. The e-mail half is not decoration: `web-app-main
20
+ pages/verify/email/index.vue:322` — the org-owner self-signup — lists
21
+ organisations to find the one it created a line earlier, at which point that
22
+ brand-new client owner is a member of nothing. Refusing them would have traded
23
+ the leak for a broken signup. The comparison is the one
24
+ `getOrgsByEmail` already makes, against the address on the caller's own `users`
25
+ row; nothing the caller sends takes part in it. It also removes a real bug from
26
+ that flow: the page takes `items[0]` of a newest-first list, so two client
27
+ owners signing up in the same moment could be handed each other's organisation
28
+ as their `defaultOrg`.
29
+
30
+ The scope is part of the Redis cache key as well as the query, so one caller's
31
+ narrowed page is never replayed to the next, and `countDocuments` runs the
32
+ scoped query so pagination cannot walk past the scope.
33
+
34
+ **`GET /api/promo-codes` is Seven365 staff only,** together with
35
+ `GET /api/promo-codes/id/:id`, which is the same disclosure one record at a
36
+ time. Both are read solely by the staff console. A client at checkout is
37
+ unaffected: `web-app-org pages/checkout.vue` and
38
+ `subscription/manage-seats.vue` type a code and validate it through
39
+ `GET /api/promo-codes/code`, which stays signed-in-only and is untouched.
@@ -0,0 +1,63 @@
1
+ ---
2
+ "@7365admin1/core": minor
3
+ ---
4
+
5
+ Make revoking somebody's access actually end their session.
6
+
7
+ A session in this product is an opaque `sid` whose only record of truth is a
8
+ Redis key `sid:<sid>` holding a copy of the user document. `requireAuth`
9
+ authenticates a request by finding that key and nothing else — it never reads
10
+ the database — and it re-`EXPIRE`s the key to four hours on every request, so a
11
+ session that is in use never ages out on its own.
12
+
13
+ **Signing out was never the problem.** `logout()` deletes the key, so a
14
+ signed-out session is refused; that half already worked. Every other way access
15
+ is taken away did not.
16
+
17
+ **Suspending somebody changed a column and nothing else.**
18
+ `suspendResidentById` writes `users.status = "suspended"` through
19
+ `updateUserFieldById`, which cleared the user-document cache and left the
20
+ `sid:` key untouched. The person carried on working, and every request they made
21
+ pushed the four-hour expiry out another four hours. On top of that,
22
+ `refreshToken()` minted a **brand new** session through `issueSession` with no
23
+ account-status check at all — the mobile refresh token is seven days and
24
+ rotating, so a suspended account's phone could keep issuing itself fresh
25
+ sessions indefinitely. Suspension was not a delay, it was a no-op for anyone
26
+ already signed in.
27
+
28
+ **Two of the statuses the product writes were never refused at sign-in either.**
29
+ Both `login()`s checked `suspended` and `pending` only, so `rejected` (written by
30
+ `reviewPerson`, whose email is the one that hides the login link) and `deleted`
31
+ (`UserStatus.DELETED`, the soft-delete marker) could sign in fresh with a
32
+ password.
33
+
34
+ The fix is the two halves of that, at the two choke points that already exist:
35
+
36
+ - **Minting refuses a non-live account** — `assertAccountLive` in `issueSession`
37
+ in both auth services, which is the one place every sign-in path in this
38
+ package goes through (login and mobile refresh, v1 and v2), for the same
39
+ reason `assertOrganizationNotSuspended` lives there.
40
+ - **Revoking destroys what already exists** — `revokeAccountAccess` from
41
+ `updateUserFieldById`, the only path in either user repository that writes
42
+ `users.status`. It deletes every `sid:` key the person holds and every refresh
43
+ token in `tokens`, so nothing can mint a replacement.
44
+
45
+ Finding every session a person holds needs an index: `users.sid` only ever
46
+ remembers the most recent sign-in, v2 never writes it, and nothing reads it — a
47
+ fix built on that column would revoke the phone and leave the browser signed in.
48
+ The index is a Redis set (`user-sessions:<userId>`), so there is no schema
49
+ change and no migration; `users.sid` stays as the fallback that catches v1
50
+ sessions minted before the set existed.
51
+
52
+ The blocking statuses are `suspended`, `pending`, `rejected` and `deleted`, as a
53
+ deny-list — `status` is optional on the model and legacy rows carry `""` or no
54
+ field at all, and an allow-list would lock every one of them out. `resubmit` is
55
+ deliberately live: that email keeps the login link, because resubmitting the
56
+ application is done signed in.
57
+
58
+ Nothing is logged out by deploying this — no existing session is touched, and
59
+ the two sign-in messages for `suspended` and `pending` are unchanged, because
60
+ `login()` still checks those two itself before minting. What changes for a
61
+ signed-in user is only that a `rejected` or soft-deleted account can no longer
62
+ obtain a session, and that a suspension now takes effect at once rather than
63
+ never.
package/dist/index.d.ts CHANGED
@@ -67,6 +67,7 @@ declare function useSessionRepo(): {
67
67
  token: string;
68
68
  }>;
69
69
  revokeFamily: (familyId: string, session?: ClientSession) => Promise<void>;
70
+ revokeAllForUser: (userId: string | ObjectId, session?: ClientSession) => Promise<void>;
70
71
  };
71
72
 
72
73
  declare function useAuthService(): {
@@ -701,13 +702,17 @@ declare function useOrgRepo(): {
701
702
  createUniqueIndex: () => Promise<void>;
702
703
  add: (value: TOrg, session?: ClientSession) => Promise<ObjectId>;
703
704
  update: (id: string, value: Partial<TOrg>, session?: ClientSession) => Promise<string>;
704
- getAll: ({ search, page, limit, sort, status, nature, }: {
705
+ getAll: ({ search, page, limit, sort, status, nature, scope, }: {
705
706
  search?: string | undefined;
706
707
  page?: number | undefined;
707
708
  limit?: number | undefined;
708
709
  sort?: Record<string, number> | undefined;
709
710
  status?: string | undefined;
710
711
  nature?: string | undefined;
712
+ scope?: {
713
+ orgIds?: string[] | undefined;
714
+ email?: string | undefined;
715
+ } | undefined;
711
716
  }) => Promise<{
712
717
  items: any[];
713
718
  pages: number;
@@ -993,8 +998,8 @@ declare const TPrice: z.ZodObject<{
993
998
  updatedAt: z.ZodOptional<z.ZodDate>;
994
999
  deletedAt: z.ZodOptional<z.ZodDate>;
995
1000
  }, "strip", z.ZodTypeAny, {
996
- name: string;
997
1001
  type: "other" | "one-time-payment" | "monthly-subscription" | "yearly-subscription";
1002
+ name: string;
998
1003
  value: number;
999
1004
  _id?: ObjectId | undefined;
1000
1005
  createdAt?: Date | undefined;
@@ -1016,8 +1021,8 @@ declare const TPrice: z.ZodObject<{
1016
1021
  type TPrice = z.infer<typeof TPrice>;
1017
1022
  declare function usePriceModel(db: Db): {
1018
1023
  createPrice: (value: Pick<TPrice, "type" | "name" | "value">) => {
1019
- name: string;
1020
1024
  type: "other" | "one-time-payment" | "monthly-subscription" | "yearly-subscription";
1025
+ name: string;
1021
1026
  value: number;
1022
1027
  _id?: ObjectId | undefined;
1023
1028
  createdAt?: Date | undefined;
@@ -1037,8 +1042,8 @@ declare function usePriceModel(db: Db): {
1037
1042
  saleValue?: number | undefined;
1038
1043
  saleExpiry?: Date | undefined;
1039
1044
  }, {
1040
- name: string;
1041
1045
  type: "other" | "one-time-payment" | "monthly-subscription" | "yearly-subscription";
1046
+ name: string;
1042
1047
  value: number;
1043
1048
  _id?: ObjectId | undefined;
1044
1049
  createdAt?: Date | undefined;
@@ -1048,8 +1053,8 @@ declare function usePriceModel(db: Db): {
1048
1053
  saleExpiry?: Date | undefined;
1049
1054
  }>;
1050
1055
  collection: Collection<{
1051
- name: string;
1052
1056
  type: "other" | "one-time-payment" | "monthly-subscription" | "yearly-subscription";
1057
+ name: string;
1053
1058
  value: number;
1054
1059
  _id?: ObjectId | undefined;
1055
1060
  createdAt?: Date | undefined;
@@ -1226,8 +1231,8 @@ declare const TInvoice: z.ZodObject<{
1226
1231
  createdAt: z.ZodOptional<z.ZodDefault<z.ZodDate>>;
1227
1232
  updatedAt: z.ZodOptional<z.ZodDate>;
1228
1233
  }, "strip", z.ZodTypeAny, {
1229
- status: "pending" | "cancelled" | "paid" | "overdue";
1230
1234
  type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
1235
+ status: "pending" | "cancelled" | "paid" | "overdue";
1231
1236
  items: {
1232
1237
  description: string;
1233
1238
  total: number;
@@ -1263,8 +1268,8 @@ declare const TInvoice: z.ZodObject<{
1263
1268
  dueDate: Date;
1264
1269
  _id?: string | ObjectId | undefined;
1265
1270
  createdAt?: Date | undefined;
1266
- status?: "pending" | "cancelled" | "paid" | "overdue" | undefined;
1267
1271
  type?: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment" | undefined;
1272
+ status?: "pending" | "cancelled" | "paid" | "overdue" | undefined;
1268
1273
  updatedAt?: Date | undefined;
1269
1274
  metadata?: {
1270
1275
  description?: string | undefined;
@@ -1280,8 +1285,8 @@ type TInvoice = z.infer<typeof TInvoice>;
1280
1285
  declare function useInvoiceModel(db: Db): {
1281
1286
  createInvoice: (data: TInvoice) => TInvoice;
1282
1287
  collection: Collection<{
1283
- status: "pending" | "cancelled" | "paid" | "overdue";
1284
1288
  type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
1289
+ status: "pending" | "cancelled" | "paid" | "overdue";
1285
1290
  items: {
1286
1291
  description: string;
1287
1292
  total: number;
@@ -1312,8 +1317,8 @@ declare function useInvoiceRepo(): {
1312
1317
  createUniqueIndex: () => Promise<void>;
1313
1318
  add: (value: TInvoice, session?: ClientSession) => Promise<void>;
1314
1319
  getByDueDate: (dueDate: Date, status?: TInvoice["status"]) => Promise<{
1315
- status: "pending" | "cancelled" | "paid" | "overdue";
1316
1320
  type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
1321
+ status: "pending" | "cancelled" | "paid" | "overdue";
1317
1322
  items: {
1318
1323
  description: string;
1319
1324
  total: number;
@@ -1348,8 +1353,8 @@ declare function useInvoiceRepo(): {
1348
1353
  pages: number;
1349
1354
  pageRange: string;
1350
1355
  } | {
1351
- status: "pending" | "cancelled" | "paid" | "overdue";
1352
1356
  type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
1357
+ status: "pending" | "cancelled" | "paid" | "overdue";
1353
1358
  items: {
1354
1359
  description: string;
1355
1360
  total: number;
@@ -1374,8 +1379,8 @@ declare function useInvoiceRepo(): {
1374
1379
  descriptions?: string | undefined;
1375
1380
  }>;
1376
1381
  getOverdueInvoices: (BATCH_SIZE?: number) => Promise<{
1377
- status: "pending" | "cancelled" | "paid" | "overdue";
1378
1382
  type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
1383
+ status: "pending" | "cancelled" | "paid" | "overdue";
1379
1384
  items: {
1380
1385
  description: string;
1381
1386
  total: number;
@@ -9860,8 +9865,8 @@ declare function useUserRepoV2(): {
9860
9865
  createTextIndex: () => Promise<void>;
9861
9866
  createUniqueIndex: () => Promise<void>;
9862
9867
  createUser: (value: TUser, session?: ClientSession) => Promise<ObjectId>;
9863
- getUserByEmail: (email: string) => Promise<TUser | mongodb.WithId<bson.Document> | null>;
9864
- getUserByEmailStatus: (email: string) => Promise<TUser | mongodb.WithId<bson.Document> | null>;
9868
+ getUserByEmail: (email: string) => Promise<mongodb.WithId<bson.Document> | TUser | null>;
9869
+ getUserByEmailStatus: (email: string) => Promise<mongodb.WithId<bson.Document> | TUser | null>;
9865
9870
  getUserById: (id: string | ObjectId) => Promise<TUser>;
9866
9871
  getUsers: ({ search, page, limit, sort, type, status, }: {
9867
9872
  search?: string | undefined;