@jwilleke/ngdpbase-types 5.22.0 → 5.23.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/lib/src/managers/AuthManager.d.ts +5 -2
- package/lib/src/managers/SessionStatsManager.d.ts +94 -0
- package/lib/src/managers/UserManager.d.ts +21 -1
- package/lib/src/types/express.d.ts +2 -0
- package/lib/src/utils/sessionAccount.d.ts +26 -0
- package/lib/src/utils/sessionEnd.d.ts +1 -1
- package/package.json +2 -2
|
@@ -620,7 +620,9 @@ declare class AuthManager extends BaseManager {
|
|
|
620
620
|
* Disable and delete both come through here, from UserManager, so nothing
|
|
621
621
|
* an account holds is left reachable by one path and not the other.
|
|
622
622
|
*
|
|
623
|
-
* - Both: every session of the account ends
|
|
623
|
+
* - Both: every session of the account ends, destroyed in the live session
|
|
624
|
+
* store (#1937); a session the store could not reach is still refused on
|
|
625
|
+
* its next request (`checkSessionAccount`).
|
|
624
626
|
* - Disable: its agent tokens, app grants and shares are suspended by the
|
|
625
627
|
* live owner-is-active checks that refuse them, and its credentials stay,
|
|
626
628
|
* so turning the account back on restores them.
|
|
@@ -1039,7 +1041,8 @@ declare class AuthManager extends BaseManager {
|
|
|
1039
1041
|
/**
|
|
1040
1042
|
* Stamp the session with the account's password-change generation (#1482):
|
|
1041
1043
|
* at every sign-in, and on the session that changed the password, so that
|
|
1042
|
-
* one stays signed in while every other session of the account ends.
|
|
1044
|
+
* one stays signed in while every other session of the account ends. Also
|
|
1045
|
+
* binds the session to the account itself (`accountCreatedAt`, #1937).
|
|
1043
1046
|
*/
|
|
1044
1047
|
stampSessionGeneration(req: Request, username: string): Promise<void>;
|
|
1045
1048
|
/**
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionStatsManager — in-process reads of the session store (#1246).
|
|
3
|
+
*
|
|
4
|
+
* Two things want to know how many sessions exist and who holds them: the
|
|
5
|
+
* `/api/session-count` and `/api/session-users` routes, and SessionsPlugin,
|
|
6
|
+
* which renders those numbers into a page. The plugin used to get them by
|
|
7
|
+
* making an HTTP request to this server's own address through a bare global
|
|
8
|
+
* `fetch` — an outbound call outside `src/http/` (#1133) that `guardedFetch`
|
|
9
|
+
* cannot take, because loopback is refused unconditionally (#1186), and one
|
|
10
|
+
* that silently rendered `0` in any container whose configured host was not
|
|
11
|
+
* reachable from inside the process.
|
|
12
|
+
*
|
|
13
|
+
* The store is an in-process object. Nothing about reading it needs a socket.
|
|
14
|
+
* app.ts attaches the store it built for express-session; the routes keep
|
|
15
|
+
* reading `req.sessionStore` (the same object) and both go through the same
|
|
16
|
+
* two helpers, so there is exactly one implementation of "count sessions" and
|
|
17
|
+
* "list session users".
|
|
18
|
+
*/
|
|
19
|
+
import BaseManager from './BaseManager.js';
|
|
20
|
+
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
21
|
+
/** The two optional store methods express-session stores may implement. */
|
|
22
|
+
export interface SessionStoreLike {
|
|
23
|
+
length?(callback: (err: unknown, count?: number) => void): void;
|
|
24
|
+
all?(callback: (err: unknown, sessions?: unknown) => void): void;
|
|
25
|
+
/** session-file-store: the session file names, not the sessions. */
|
|
26
|
+
list?(callback: (err: unknown, files?: string[]) => void): void;
|
|
27
|
+
get?(sid: string, callback: (err: unknown, session?: unknown) => void): void;
|
|
28
|
+
destroy?(sid: string, callback: (err: unknown) => void): void;
|
|
29
|
+
/** session-file-store keeps its file extension here (default `.json`). */
|
|
30
|
+
options?: {
|
|
31
|
+
fileExtension?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface SessionCount {
|
|
35
|
+
sessionCount: number;
|
|
36
|
+
distinctUsers: number;
|
|
37
|
+
}
|
|
38
|
+
export interface SessionUsers {
|
|
39
|
+
users: string[];
|
|
40
|
+
anonymous: number;
|
|
41
|
+
total: number;
|
|
42
|
+
}
|
|
43
|
+
/** Thrown when the store implements neither `length` nor `all`. */
|
|
44
|
+
export declare class SessionStoreUnsupportedError extends Error {
|
|
45
|
+
constructor(what: string);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Session count and distinct-user count. Prefers `length` (cheap), falls back
|
|
49
|
+
* to `all`. Under `length` the distinct-user figure equals the session count,
|
|
50
|
+
* which is what the route always reported on that path.
|
|
51
|
+
*/
|
|
52
|
+
export declare function countSessions(store: SessionStoreLike): Promise<SessionCount>;
|
|
53
|
+
/**
|
|
54
|
+
* Authenticated usernames (sorted, distinct) and the anonymous session count.
|
|
55
|
+
* Needs `all`; under `length` alone the list is empty and every session is
|
|
56
|
+
* reported as anonymous.
|
|
57
|
+
*/
|
|
58
|
+
export declare function listSessionUsers(store: SessionStoreLike): Promise<SessionUsers>;
|
|
59
|
+
/** A session in the store, with its id. */
|
|
60
|
+
export interface StoredSession {
|
|
61
|
+
sid: string;
|
|
62
|
+
session: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Every session in the store with its id (#1937): `all()` keyed by id where
|
|
66
|
+
* the store has it, else list and read each (the file store). A store whose
|
|
67
|
+
* `all()` returns a bare array gives no ids, so it cannot be listed this way.
|
|
68
|
+
*/
|
|
69
|
+
export declare function storedSessions(store: SessionStoreLike): Promise<StoredSession[]>;
|
|
70
|
+
/**
|
|
71
|
+
* The private-store handles held by sessions still in the store (#1626).
|
|
72
|
+
* Uses `all()` where the store has it, else lists and reads each session:
|
|
73
|
+
* the file store has no `all()`, so the sweep failed on every run there and
|
|
74
|
+
* no orphaned key was ever dropped.
|
|
75
|
+
*/
|
|
76
|
+
export declare function liveSessionHandles(store: SessionStoreLike): Promise<Set<string>>;
|
|
77
|
+
declare class SessionStatsManager extends BaseManager {
|
|
78
|
+
private store;
|
|
79
|
+
constructor(engine: WikiEngine);
|
|
80
|
+
/** app.ts hands over the store it built for express-session. */
|
|
81
|
+
attachStore(store: SessionStoreLike): void;
|
|
82
|
+
hasStore(): boolean;
|
|
83
|
+
/** @throws Error when no store is attached; SessionStoreUnsupportedError when it cannot count. */
|
|
84
|
+
count(): Promise<SessionCount>;
|
|
85
|
+
/** @throws Error when no store is attached; SessionStoreUnsupportedError when it cannot list. */
|
|
86
|
+
users(): Promise<SessionUsers>;
|
|
87
|
+
/** @throws Error when no store is attached; SessionStoreUnsupportedError when it cannot list. */
|
|
88
|
+
liveHandles(): Promise<Set<string>>;
|
|
89
|
+
/** Every session in the store with its id (#1937). @throws Error when no store is attached; SessionStoreUnsupportedError when it cannot list by id. */
|
|
90
|
+
sessions(): Promise<StoredSession[]>;
|
|
91
|
+
/** Remove one session from the store (#1937). @throws Error when no store is attached, or SessionStoreUnsupportedError when it cannot destroy. */
|
|
92
|
+
destroy(sid: string): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
export default SessionStatsManager;
|
|
@@ -488,7 +488,27 @@ declare class UserManager extends BaseManager {
|
|
|
488
488
|
createSession(username: string, additionalData?: Record<string, unknown>): Promise<string>;
|
|
489
489
|
getSession(sessionId: string): Promise<UserSession | null>;
|
|
490
490
|
deleteSession(sessionId: string): Promise<void>;
|
|
491
|
-
|
|
491
|
+
/**
|
|
492
|
+
* End every signed-in session of `username` in the live session store
|
|
493
|
+
* (#1937): the account was deleted or turned off (`AuthManager.closeAccess`).
|
|
494
|
+
* Each goes through `endSession` (its keys dropped, `authentication-logout`
|
|
495
|
+
* recorded with the reason) and is removed from the store. Returns how many.
|
|
496
|
+
* The provider's own session list was cleared here before; nothing writes it,
|
|
497
|
+
* and the store express-session reads was never touched.
|
|
498
|
+
*/
|
|
499
|
+
deleteUserSessions(username: string, reason: 'account-deleted' | 'account-disabled', from?: {
|
|
500
|
+
ipAddress?: string;
|
|
501
|
+
}): Promise<number>;
|
|
502
|
+
/**
|
|
503
|
+
* End every signed-in session in the store that no longer belongs to its
|
|
504
|
+
* account (#1937): the account is gone, turned off, another account of the
|
|
505
|
+
* same name, or its password changed since (`checkSessionAccount`, the
|
|
506
|
+
* per-request rule). Run once at startup, so sessions left from before the
|
|
507
|
+
* fix, or from a close the store missed, do not wait for their next request.
|
|
508
|
+
*/
|
|
509
|
+
endStaleSessions(): Promise<number>;
|
|
510
|
+
/** End and remove each signed-in session `which` names a reason for; none without a session store. */
|
|
511
|
+
private endStoredSessions;
|
|
492
512
|
backup(): Promise<BackupData>;
|
|
493
513
|
restore(backupData: BackupData): Promise<void>;
|
|
494
514
|
}
|
|
@@ -30,6 +30,8 @@ declare module 'express-session' {
|
|
|
30
30
|
privateStoreHandle?: string;
|
|
31
31
|
/** The account's password-change generation when this session signed in (#1482). */
|
|
32
32
|
sessionGeneration?: number;
|
|
33
|
+
/** The `createdAt` of the account this session signed in to (#1937): a later account of the same name is not it. */
|
|
34
|
+
accountCreatedAt?: string;
|
|
33
35
|
/** How this session signed in: provider, factors with their times, amr / aal / acr (#1523). */
|
|
34
36
|
signIn?: SignInRecord;
|
|
35
37
|
/** A pending WebAuthn challenge (#448): single-use, tied to its purpose, short-lived. */
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type AccountEndReason = 'account-deleted' | 'account-disabled' | 'password-changed';
|
|
2
|
+
interface AccountLike {
|
|
3
|
+
createdAt?: unknown;
|
|
4
|
+
isActive?: boolean;
|
|
5
|
+
sessionGeneration?: number;
|
|
6
|
+
}
|
|
7
|
+
interface SessionLike {
|
|
8
|
+
accountCreatedAt?: unknown;
|
|
9
|
+
signedInAt?: unknown;
|
|
10
|
+
sessionGeneration?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** The value a session is bound to: the account's creation time, or '' for a record without one. */
|
|
13
|
+
export declare function accountBindingOf(account: AccountLike | null | undefined): string;
|
|
14
|
+
/**
|
|
15
|
+
* What one request (or the sweep) does to a signed-in session, given its
|
|
16
|
+
* account as stored now (null when there is none). `bind` is set on a session
|
|
17
|
+
* signed in before the binding existed that still belongs to its account: the
|
|
18
|
+
* caller writes it to `accountCreatedAt`, once.
|
|
19
|
+
*/
|
|
20
|
+
export declare function checkSessionAccount(stored: SessionLike | Record<string, unknown>, account: AccountLike | null | undefined): {
|
|
21
|
+
end: AccountEndReason;
|
|
22
|
+
} | {
|
|
23
|
+
end: null;
|
|
24
|
+
bind?: string;
|
|
25
|
+
};
|
|
26
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SessionEndReason = 'logout' | 'idle-timeout' | 'max-age' | 'password-changed' | 'superseded';
|
|
1
|
+
export type SessionEndReason = 'logout' | 'idle-timeout' | 'max-age' | 'password-changed' | 'superseded' | 'account-deleted' | 'account-disabled';
|
|
2
2
|
export declare function endSession(engine: {
|
|
3
3
|
getManager(name: string): unknown;
|
|
4
4
|
}, session: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwilleke/ngdpbase-types",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.23.0",
|
|
4
4
|
"description": "TypeScript types for ngdpbase add-ons, and a test kit of core's own values. A dev dependency, versioned with ngdpbase.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Jim Willeke",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"ipaddr.js": "^2.5.0",
|
|
47
47
|
"jose": "^6.2.12",
|
|
48
48
|
"linkedom": "^0.18.13",
|
|
49
|
-
"markdown-it": "^14.3.
|
|
49
|
+
"markdown-it": "^14.3.3",
|
|
50
50
|
"minimatch": "^10.2.6",
|
|
51
51
|
"minisearch": "7.2.0",
|
|
52
52
|
"multer": "^2.4.0",
|