@7365admin1/core 3.48.1-staging.204 → 3.48.1-staging.206
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/resident-ownership-scope.md +5 -0
- package/.changeset/revoke-sessions-when-access-is-taken-away.md +63 -0
- package/dist/index.d.ts +13 -12
- package/dist/index.js +7016 -6845
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4122 -3939
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/e2e/harness.mjs +70 -1
- package/test/e2e/resident-ownership-scope.e2e.test.mjs +561 -0
- package/test/e2e/session-revocation.e2e.test.mjs +350 -0
- package/test/resident-ownership-scope.test.mjs +186 -0
- package/test/session-revoke.test.mjs +254 -0
|
@@ -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(): {
|
|
@@ -993,8 +994,8 @@ declare const TPrice: z.ZodObject<{
|
|
|
993
994
|
updatedAt: z.ZodOptional<z.ZodDate>;
|
|
994
995
|
deletedAt: z.ZodOptional<z.ZodDate>;
|
|
995
996
|
}, "strip", z.ZodTypeAny, {
|
|
996
|
-
name: string;
|
|
997
997
|
type: "other" | "one-time-payment" | "monthly-subscription" | "yearly-subscription";
|
|
998
|
+
name: string;
|
|
998
999
|
value: number;
|
|
999
1000
|
_id?: ObjectId | undefined;
|
|
1000
1001
|
createdAt?: Date | undefined;
|
|
@@ -1016,8 +1017,8 @@ declare const TPrice: z.ZodObject<{
|
|
|
1016
1017
|
type TPrice = z.infer<typeof TPrice>;
|
|
1017
1018
|
declare function usePriceModel(db: Db): {
|
|
1018
1019
|
createPrice: (value: Pick<TPrice, "type" | "name" | "value">) => {
|
|
1019
|
-
name: string;
|
|
1020
1020
|
type: "other" | "one-time-payment" | "monthly-subscription" | "yearly-subscription";
|
|
1021
|
+
name: string;
|
|
1021
1022
|
value: number;
|
|
1022
1023
|
_id?: ObjectId | undefined;
|
|
1023
1024
|
createdAt?: Date | undefined;
|
|
@@ -1037,8 +1038,8 @@ declare function usePriceModel(db: Db): {
|
|
|
1037
1038
|
saleValue?: number | undefined;
|
|
1038
1039
|
saleExpiry?: Date | undefined;
|
|
1039
1040
|
}, {
|
|
1040
|
-
name: string;
|
|
1041
1041
|
type: "other" | "one-time-payment" | "monthly-subscription" | "yearly-subscription";
|
|
1042
|
+
name: string;
|
|
1042
1043
|
value: number;
|
|
1043
1044
|
_id?: ObjectId | undefined;
|
|
1044
1045
|
createdAt?: Date | undefined;
|
|
@@ -1048,8 +1049,8 @@ declare function usePriceModel(db: Db): {
|
|
|
1048
1049
|
saleExpiry?: Date | undefined;
|
|
1049
1050
|
}>;
|
|
1050
1051
|
collection: Collection<{
|
|
1051
|
-
name: string;
|
|
1052
1052
|
type: "other" | "one-time-payment" | "monthly-subscription" | "yearly-subscription";
|
|
1053
|
+
name: string;
|
|
1053
1054
|
value: number;
|
|
1054
1055
|
_id?: ObjectId | undefined;
|
|
1055
1056
|
createdAt?: Date | undefined;
|
|
@@ -1226,8 +1227,8 @@ declare const TInvoice: z.ZodObject<{
|
|
|
1226
1227
|
createdAt: z.ZodOptional<z.ZodDefault<z.ZodDate>>;
|
|
1227
1228
|
updatedAt: z.ZodOptional<z.ZodDate>;
|
|
1228
1229
|
}, "strip", z.ZodTypeAny, {
|
|
1229
|
-
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1230
1230
|
type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
|
|
1231
|
+
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1231
1232
|
items: {
|
|
1232
1233
|
description: string;
|
|
1233
1234
|
total: number;
|
|
@@ -1263,8 +1264,8 @@ declare const TInvoice: z.ZodObject<{
|
|
|
1263
1264
|
dueDate: Date;
|
|
1264
1265
|
_id?: string | ObjectId | undefined;
|
|
1265
1266
|
createdAt?: Date | undefined;
|
|
1266
|
-
status?: "pending" | "cancelled" | "paid" | "overdue" | undefined;
|
|
1267
1267
|
type?: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment" | undefined;
|
|
1268
|
+
status?: "pending" | "cancelled" | "paid" | "overdue" | undefined;
|
|
1268
1269
|
updatedAt?: Date | undefined;
|
|
1269
1270
|
metadata?: {
|
|
1270
1271
|
description?: string | undefined;
|
|
@@ -1280,8 +1281,8 @@ type TInvoice = z.infer<typeof TInvoice>;
|
|
|
1280
1281
|
declare function useInvoiceModel(db: Db): {
|
|
1281
1282
|
createInvoice: (data: TInvoice) => TInvoice;
|
|
1282
1283
|
collection: Collection<{
|
|
1283
|
-
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1284
1284
|
type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
|
|
1285
|
+
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1285
1286
|
items: {
|
|
1286
1287
|
description: string;
|
|
1287
1288
|
total: number;
|
|
@@ -1312,8 +1313,8 @@ declare function useInvoiceRepo(): {
|
|
|
1312
1313
|
createUniqueIndex: () => Promise<void>;
|
|
1313
1314
|
add: (value: TInvoice, session?: ClientSession) => Promise<void>;
|
|
1314
1315
|
getByDueDate: (dueDate: Date, status?: TInvoice["status"]) => Promise<{
|
|
1315
|
-
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1316
1316
|
type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
|
|
1317
|
+
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1317
1318
|
items: {
|
|
1318
1319
|
description: string;
|
|
1319
1320
|
total: number;
|
|
@@ -1348,8 +1349,8 @@ declare function useInvoiceRepo(): {
|
|
|
1348
1349
|
pages: number;
|
|
1349
1350
|
pageRange: string;
|
|
1350
1351
|
} | {
|
|
1351
|
-
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1352
1352
|
type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
|
|
1353
|
+
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1353
1354
|
items: {
|
|
1354
1355
|
description: string;
|
|
1355
1356
|
total: number;
|
|
@@ -1374,8 +1375,8 @@ declare function useInvoiceRepo(): {
|
|
|
1374
1375
|
descriptions?: string | undefined;
|
|
1375
1376
|
}>;
|
|
1376
1377
|
getOverdueInvoices: (BATCH_SIZE?: number) => Promise<{
|
|
1377
|
-
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1378
1378
|
type: "other" | "organization-subscription" | "affiliate-subscription" | "one-time-payment";
|
|
1379
|
+
status: "pending" | "cancelled" | "paid" | "overdue";
|
|
1379
1380
|
items: {
|
|
1380
1381
|
description: string;
|
|
1381
1382
|
total: number;
|
|
@@ -9860,8 +9861,8 @@ declare function useUserRepoV2(): {
|
|
|
9860
9861
|
createTextIndex: () => Promise<void>;
|
|
9861
9862
|
createUniqueIndex: () => Promise<void>;
|
|
9862
9863
|
createUser: (value: TUser, session?: ClientSession) => Promise<ObjectId>;
|
|
9863
|
-
getUserByEmail: (email: string) => Promise<
|
|
9864
|
-
getUserByEmailStatus: (email: string) => Promise<
|
|
9864
|
+
getUserByEmail: (email: string) => Promise<mongodb.WithId<bson.Document> | TUser | null>;
|
|
9865
|
+
getUserByEmailStatus: (email: string) => Promise<mongodb.WithId<bson.Document> | TUser | null>;
|
|
9865
9866
|
getUserById: (id: string | ObjectId) => Promise<TUser>;
|
|
9866
9867
|
getUsers: ({ search, page, limit, sort, type, status, }: {
|
|
9867
9868
|
search?: string | undefined;
|