@jwilleke/ngdpbase-types 5.26.0 → 5.28.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/packages/ngdpbase-types/index.d.ts +8 -2
- package/lib/packages/ngdpbase-types/testing.d.ts +7 -3
- package/lib/packages/ngdpbase-types/testing.js +7 -3
- package/lib/src/context/ActorContext.d.ts +15 -2
- package/lib/src/context/ActorContext.js +20 -0
- package/lib/src/converters/DocxConverter.d.ts +30 -0
- package/lib/src/converters/MarkdownConverter.d.ts +31 -0
- package/lib/src/export/ChromeServicePdfProvider.d.ts +52 -0
- package/lib/src/export/ChromeServicePdfProvider.js +153 -0
- package/lib/src/export/LocalChromePdfProvider.d.ts +25 -0
- package/lib/src/export/LocalChromePdfProvider.js +33 -0
- package/lib/src/export/PdfProvider.d.ts +51 -0
- package/lib/src/export/PdfProvider.js +37 -0
- package/lib/src/export/pdfEngine.d.ts +113 -0
- package/lib/src/export/pdfEngine.js +195 -0
- package/lib/src/ice/converterShim.d.ts +5 -0
- package/lib/src/ice/fileHandling.d.ts +30 -0
- package/lib/src/ice/ledger.d.ts +37 -0
- package/lib/src/ice/runImport.d.ts +51 -0
- package/lib/src/ice/types.d.ts +284 -0
- package/lib/src/mail/MailProvider.d.ts +15 -0
- package/lib/src/mail/NodemailerMailProvider.d.ts +1 -1
- package/lib/src/managers/AddonsManager.d.ts +25 -5
- package/lib/src/managers/AssetManager.d.ts +7 -3
- package/lib/src/managers/EmailManager.d.ts +32 -5
- package/lib/src/managers/ExportManager.d.ts +62 -0
- package/lib/src/managers/ExportManager.js +152 -2
- package/lib/src/managers/ImportManager.d.ts +611 -0
- package/lib/src/managers/OrganizationManager.d.ts +5 -3
- package/lib/src/managers/PageManager.d.ts +77 -1
- package/lib/src/managers/PersonManager.d.ts +36 -11
- package/lib/src/managers/RoleManager.d.ts +76 -17
- package/lib/src/managers/UserManager.d.ts +55 -2
- package/lib/src/security/PolicyDecisionPoint.d.ts +18 -9
- package/lib/src/security/PolicyInformationPoint.d.ts +48 -10
- package/lib/src/types/Config.d.ts +2 -0
- package/lib/src/types/User.d.ts +1 -1
- package/lib/src/utils/addonHost.d.ts +23 -2
- package/lib/src/utils/addonHost.js +25 -2
- package/lib/src/utils/auditEventNames.generated.d.ts +10 -10
- package/lib/src/utils/auditEventNames.generated.js +10 -10
- package/lib/src/utils/contactAccess.d.ts +30 -0
- package/lib/src/utils/frontmatterAccess.d.ts +54 -11
- package/lib/src/utils/importRunSummary.d.ts +64 -0
- package/lib/src/utils/keyedSerial.d.ts +31 -0
- package/lib/src/utils/ncmNotify.d.ts +30 -0
- package/lib/src/utils/privateStoreAccess.js +1 -3
- package/lib/src/utils/privateStoreImport.d.ts +58 -0
- package/lib/src/utils/roleChangeError.d.ts +25 -0
- package/lib/src/utils/subjectMayDo.d.ts +2 -2
- package/lib/src/utils/zipArchive.d.ts +71 -0
- package/package.json +1 -1
|
@@ -184,6 +184,15 @@ export type PageRead<T> = {
|
|
|
184
184
|
export interface PageConvertResult extends NcmResult {
|
|
185
185
|
fixes: FixChange[];
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* A write refused because the page's `audience` leaves the writer out (#1964).
|
|
189
|
+
* Someone outside a page's audience may neither read nor edit it; an
|
|
190
|
+
* administrator (`admin-system`) is the exception. Routes answer it with 403.
|
|
191
|
+
*/
|
|
192
|
+
export declare class PageAudienceRefusedError extends Error {
|
|
193
|
+
readonly pageName: string;
|
|
194
|
+
constructor(pageName: string);
|
|
195
|
+
}
|
|
187
196
|
export declare class PageContentValidationError extends Error {
|
|
188
197
|
readonly validationErrors: FilterValidationError[];
|
|
189
198
|
constructor(pageName: string, validationErrors: FilterValidationError[]);
|
|
@@ -797,10 +806,22 @@ declare class PageManager extends BaseManager implements CatalogSource {
|
|
|
797
806
|
* the normal getPage() path sanitises or normalises away. Returns null
|
|
798
807
|
* when the page is unknown or the provider doesn't support raw reads.
|
|
799
808
|
*/
|
|
800
|
-
getRawPageContent(identifier: string): Promise<{
|
|
809
|
+
getRawPageContent(identifier: string, ctx: ActorContext): Promise<{
|
|
801
810
|
filePath: string;
|
|
802
811
|
content: string;
|
|
803
812
|
} | null>;
|
|
813
|
+
/**
|
|
814
|
+
* The write door's audience check (#1964, operator 2026-10-23): someone a
|
|
815
|
+
* page's `audience` leaves out may neither read nor edit it, by any route —
|
|
816
|
+
* the editor, the raw editor, the API, a rename, a delete. The rule is the
|
|
817
|
+
* PIP's (`audienceAdmits`, the same one Tier 1 applies), asked with the
|
|
818
|
+
* caller's context; an administrator (`admin-system`) is admitted. A page
|
|
819
|
+
* that states no audience is not checked here — the routes ask the full
|
|
820
|
+
* page decision before they call the door.
|
|
821
|
+
*
|
|
822
|
+
* @throws PageAudienceRefusedError when the audience leaves `ctx` out
|
|
823
|
+
*/
|
|
824
|
+
private assertAudienceAdmits;
|
|
804
825
|
/**
|
|
805
826
|
* Admin-override save (#689): the admin "Edit raw" textarea, split into
|
|
806
827
|
* frontmatter and body and written through the door (#1462).
|
|
@@ -1107,6 +1128,38 @@ declare class PageManager extends BaseManager implements CatalogSource {
|
|
|
1107
1128
|
* const deleted = await pageManager.deletePage('Old Page', req.userContext);
|
|
1108
1129
|
*/
|
|
1109
1130
|
deletePage(identifier: string, ctx: ActorContext): Promise<boolean>;
|
|
1131
|
+
/**
|
|
1132
|
+
* Delete the pages a test made (#2008): every shared page carrying the
|
|
1133
|
+
* `test-artifact` system keyword (#1355), and never a page without it.
|
|
1134
|
+
* `only` narrows the set further — a deleted test account's profile is
|
|
1135
|
+
* `{ author, systemCategory: 'user-profile' }`.
|
|
1136
|
+
*
|
|
1137
|
+
* Each page is recorded as `page-delete` before it is deleted (on-failure
|
|
1138
|
+
* refuse: a page whose record cannot be written is not deleted), then goes
|
|
1139
|
+
* through {@link deletePage}, so the delete is soft and the shared indexes
|
|
1140
|
+
* follow. One page's failure never stops the rest; it is reported.
|
|
1141
|
+
*
|
|
1142
|
+
* The caller decides who may ask: the sweep route asks `admin-system`, and
|
|
1143
|
+
* an account delete has already been decided as `user-delete`. Private
|
|
1144
|
+
* vault pages are not in the shared index, so they are not reached here.
|
|
1145
|
+
*
|
|
1146
|
+
* @param ctx - Who is acting; named in every record
|
|
1147
|
+
* @param only - Narrow to one author and/or system-category
|
|
1148
|
+
*/
|
|
1149
|
+
deleteTestArtifacts(ctx: ActorContext, only?: {
|
|
1150
|
+
author?: string;
|
|
1151
|
+
systemCategory?: string;
|
|
1152
|
+
}): Promise<{
|
|
1153
|
+
deleted: Array<{
|
|
1154
|
+
title: string;
|
|
1155
|
+
uuid: string;
|
|
1156
|
+
}>;
|
|
1157
|
+
failed: Array<{
|
|
1158
|
+
title: string;
|
|
1159
|
+
uuid: string;
|
|
1160
|
+
error: string;
|
|
1161
|
+
}>;
|
|
1162
|
+
}>;
|
|
1110
1163
|
/**
|
|
1111
1164
|
* The name a page was reached by, as the shared indexes key it: a private
|
|
1112
1165
|
* page's path (#1456), else its title — the caller may have used a uuid or
|
|
@@ -1290,6 +1343,29 @@ declare class PageManager extends BaseManager implements CatalogSource {
|
|
|
1290
1343
|
* @param action - Legacy verb (`view`, `edit`) or policy action; default `view`
|
|
1291
1344
|
*/
|
|
1292
1345
|
filterPagesFor(subject: ActorContext | PermissionSubject | null | undefined, names: readonly string[], action?: string): Promise<string[]>;
|
|
1346
|
+
/**
|
|
1347
|
+
* The `addon:<slug>` entries on a page that can match nobody (#1964): the
|
|
1348
|
+
* slugs it names in `audience` or `access` that are not a loaded per-person
|
|
1349
|
+
* add-on. Such an entry still decides — it refuses everyone it would have
|
|
1350
|
+
* let in — so the author is told.
|
|
1351
|
+
*
|
|
1352
|
+
* @param metadata - The page's frontmatter
|
|
1353
|
+
* @returns The slugs, in the order the page names them
|
|
1354
|
+
*/
|
|
1355
|
+
unmatchableAddonAudience(metadata: Partial<PageFrontmatter> | null | undefined): string[];
|
|
1356
|
+
/** Warn, naming the page and each slug, about {@link unmatchableAddonAudience}. */
|
|
1357
|
+
private warnUnmatchableAddonAudience;
|
|
1358
|
+
/**
|
|
1359
|
+
* At startup, once add-ons have loaded (#1964): warn about every page whose
|
|
1360
|
+
* `audience` or `access` names an add-on that can match nobody — unknown,
|
|
1361
|
+
* not per-person, or not loaded.
|
|
1362
|
+
*
|
|
1363
|
+
* @returns Each page and slug warned about
|
|
1364
|
+
*/
|
|
1365
|
+
reportUnmatchableAddonAudiences(): Promise<Array<{
|
|
1366
|
+
page: string;
|
|
1367
|
+
slug: string;
|
|
1368
|
+
}>>;
|
|
1293
1369
|
/**
|
|
1294
1370
|
* Get all page titles (explicit alias for getAllPages)
|
|
1295
1371
|
* Prefer this for new code that only needs page names.
|
|
@@ -23,8 +23,13 @@ export type LinkOfferOutcome = 'sent' | 'no-match' | 'several' | 'unavailable' |
|
|
|
23
23
|
* `linkAccount` take the caller's context first and ask the PDP: `contact-read`
|
|
24
24
|
* to list, search or get; `contact-manage` to change (`linkAccount` also `user-edit`). Each change is recorded as
|
|
25
25
|
* the `contact-manage` audit event. A refusal throws `ContactRefusedError`.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
*
|
|
27
|
+
* Every lookup takes the caller's context too (#2018): `getById`,
|
|
28
|
+
* `getByIdentifier` and `getByUserIdentifier`. A user reads their own identity
|
|
29
|
+
* record, and `update` changes it, under `profile-manage`; anyone else's needs
|
|
30
|
+
* `contact-read` / `contact-manage`. Core's own reads (RoleManager resolving
|
|
31
|
+
* roles on every request, UserManager's account lifecycle) pass the system
|
|
32
|
+
* context, allowed without a policy evaluation (`requireRecordAccess`).
|
|
28
33
|
*
|
|
29
34
|
* A later sign-up joins a person with no account in one of two ways: the new
|
|
30
35
|
* account confirms an emailed link (`offerSignUpLink` → `confirmSignUpLink`),
|
|
@@ -32,8 +37,8 @@ export type LinkOfferOutcome = 'sent' | 'no-match' | 'several' | 'unavailable' |
|
|
|
32
37
|
* person keeps its `@id` and takes the account; the person made for the new
|
|
33
38
|
* account is folded into it and removed, so there is never a second person.
|
|
34
39
|
*
|
|
35
|
-
* `getByUserIdentifier(username)` remains as a lazy-migration entry
|
|
36
|
-
* for legacy users that pre-date the sync wiring.
|
|
40
|
+
* `getByUserIdentifier(ctx, username)` remains as a lazy-migration entry
|
|
41
|
+
* point for legacy users that pre-date the sync wiring.
|
|
37
42
|
*/
|
|
38
43
|
declare class PersonManager extends BaseManager {
|
|
39
44
|
readonly description = "Canonical Person records (#617)";
|
|
@@ -56,15 +61,26 @@ declare class PersonManager extends BaseManager {
|
|
|
56
61
|
*/
|
|
57
62
|
backupFolders(): BackupFolder[];
|
|
58
63
|
getProvider(): PersonProvider | null;
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
/**
|
|
65
|
+
* One identity record by `@id` (#2018): the caller's own under
|
|
66
|
+
* `profile-manage`, anyone else's under `contact-read`, core's own work
|
|
67
|
+
* under the system context. A refusal throws `ContactRefusedError`.
|
|
68
|
+
*/
|
|
69
|
+
getById(ctx: ActorContext, id: string): Promise<Person | null>;
|
|
70
|
+
/**
|
|
71
|
+
* The identity record whose `identifier` is `identifier` (a username, or a
|
|
72
|
+
* contact's `person:<uuid>`), decided as {@link getById} before it is read.
|
|
73
|
+
*/
|
|
74
|
+
getByIdentifier(ctx: ActorContext, identifier: string): Promise<Person | null>;
|
|
75
|
+
private cachedById;
|
|
76
|
+
private cachedByIdentifier;
|
|
61
77
|
/** #1006: how many people this instance holds. Count only, never records. */
|
|
62
78
|
getManagerStats(): Promise<ManagerStats>;
|
|
63
79
|
/** True when the person has no account: its identifier is `person:<uuid>` (#1930). */
|
|
64
80
|
static isAccountless(person: Pick<Person, 'identifier'>): boolean;
|
|
65
81
|
/** Every person, for a caller who may read contacts (`contact-read`). */
|
|
66
82
|
list(ctx: ActorContext): Promise<Person[]>;
|
|
67
|
-
/** One person by `@id
|
|
83
|
+
/** One person by `@id`: {@link getById}, the caller's own under `profile-manage`, anyone else's under `contact-read`. */
|
|
68
84
|
get(ctx: ActorContext, id: string): Promise<Person | null>;
|
|
69
85
|
/**
|
|
70
86
|
* Persons whose name (`name`, `givenName`, `familyName`) or `email` contains
|
|
@@ -82,7 +98,11 @@ declare class PersonManager extends BaseManager {
|
|
|
82
98
|
* person yet: that is how UserManager makes the person for a new account.
|
|
83
99
|
*/
|
|
84
100
|
create(ctx: ActorContext, input: PersonInput): Promise<Person>;
|
|
85
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Change a person's fields, affiliations included. `@id` and `identifier`
|
|
103
|
+
* never change here. The caller's own identity record under
|
|
104
|
+
* `profile-manage`, anyone else's under `contact-manage` (#2018).
|
|
105
|
+
*/
|
|
86
106
|
update(ctx: ActorContext, id: string, patch: PersonUpdate): Promise<Person | null>;
|
|
87
107
|
delete(ctx: ActorContext, id: string): Promise<boolean>;
|
|
88
108
|
/**
|
|
@@ -123,7 +143,10 @@ declare class PersonManager extends BaseManager {
|
|
|
123
143
|
* to it, and that person is removed. Never a second person.
|
|
124
144
|
*/
|
|
125
145
|
private join;
|
|
126
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Each affiliation names an organization that exists, looked up with the
|
|
148
|
+
* caller's context; `role` is trimmed text. Throws on any other shape.
|
|
149
|
+
*/
|
|
127
150
|
private checkedAffiliations;
|
|
128
151
|
private configNumber;
|
|
129
152
|
/**
|
|
@@ -136,9 +159,11 @@ declare class PersonManager extends BaseManager {
|
|
|
136
159
|
*
|
|
137
160
|
* Returns the Person paired to `username`. If none exists and a matching
|
|
138
161
|
* User does, a Person is created from the User's email/displayName and
|
|
139
|
-
* persisted. Subsequent calls return the persisted record.
|
|
162
|
+
* persisted. Subsequent calls return the persisted record. Decided as
|
|
163
|
+
* {@link getByIdentifier} (#2018): a signed-in user's own under
|
|
164
|
+
* `profile-manage` ("your receipts"), anyone else's under `contact-read`.
|
|
140
165
|
*/
|
|
141
|
-
getByUserIdentifier(username: string): Promise<Person | null>;
|
|
166
|
+
getByUserIdentifier(ctx: ActorContext, username: string): Promise<Person | null>;
|
|
142
167
|
private requireProvider;
|
|
143
168
|
private getMetrics;
|
|
144
169
|
private normalizeProviderName;
|
|
@@ -12,6 +12,9 @@ declare class RoleManager extends BaseManager {
|
|
|
12
12
|
private storageDir;
|
|
13
13
|
private memberCache;
|
|
14
14
|
private byOrgPositionCache;
|
|
15
|
+
private cacheGeneration;
|
|
16
|
+
/** #2015: one membership change at a time per role record, read included. */
|
|
17
|
+
private readonly membership;
|
|
15
18
|
private metricsRef;
|
|
16
19
|
constructor(engine: WikiEngine);
|
|
17
20
|
initialize(config?: Record<string, unknown>): Promise<void>;
|
|
@@ -42,7 +45,8 @@ declare class RoleManager extends BaseManager {
|
|
|
42
45
|
backupFolders(): BackupFolder[];
|
|
43
46
|
/** Provider accessor, mainly for tests. */
|
|
44
47
|
getProvider(): RoleProvider | null;
|
|
45
|
-
|
|
48
|
+
/** One role record by `@id`: its members are identity records, so `contact-read`, or core's own work (#2018). */
|
|
49
|
+
getById(ctx: ActorContext, id: string): Promise<Role | null>;
|
|
46
50
|
getByOrgAndPosition(organizationId: string, namedPosition: string): Promise<Role | null>;
|
|
47
51
|
listByMember(personId: string): Promise<Role[]>;
|
|
48
52
|
list(): Promise<Role[]>;
|
|
@@ -65,8 +69,13 @@ declare class RoleManager extends BaseManager {
|
|
|
65
69
|
*
|
|
66
70
|
* Returns `[]` when PersonManager is unavailable, the user has no Person
|
|
67
71
|
* record, no record lists them, or the lookup throws.
|
|
72
|
+
*
|
|
73
|
+
* `ctx` is who is asking (#2018), decided by PersonManager's door: a user
|
|
74
|
+
* about themselves under `profile-manage`, about anyone else under
|
|
75
|
+
* `contact-read`; core's own work passes the system context. A refusal is
|
|
76
|
+
* thrown as `ContactRefusedError`, never answered as "no roles".
|
|
68
77
|
*/
|
|
69
|
-
resolveUserRoles(username: string): Promise<string[]>;
|
|
78
|
+
resolveUserRoles(ctx: ActorContext, username: string): Promise<string[]>;
|
|
70
79
|
/**
|
|
71
80
|
* The `required-aal` levels the OPERATOR set in their own config (#448,
|
|
72
81
|
* #1523), as opposed to the shipped defaults. An operator's level that no
|
|
@@ -86,15 +95,26 @@ declare class RoleManager extends BaseManager {
|
|
|
86
95
|
* `anonymous`) requires nothing beyond being signed in.
|
|
87
96
|
*/
|
|
88
97
|
roleRequiredAal(): Record<string, 1 | 2 | 3>;
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
/** Whether `username` holds `roleName` now, asked by `ctx`; decided as {@link resolveUserRoles} (#2018). */
|
|
99
|
+
hasRole(ctx: ActorContext, username: string, roleName: string): Promise<boolean>;
|
|
100
|
+
/**
|
|
101
|
+
* Assign one role to an existing account, and record it (#1204). A role
|
|
102
|
+
* that cannot be given is refused (#2015): the error is thrown, admins are
|
|
103
|
+
* notified, and the attempt is recorded as a failed user-edit.
|
|
104
|
+
*/
|
|
91
105
|
assignRole(username: string, roleName: string, ctx: ActorContext): Promise<boolean>;
|
|
92
|
-
/** Remove one role from an existing account, and record it (#1204). */
|
|
106
|
+
/** Remove one role from an existing account, and record it (#1204); refused like `assignRole` when it cannot be done. */
|
|
93
107
|
removeRole(username: string, roleName: string, ctx: ActorContext): Promise<boolean>;
|
|
94
108
|
/**
|
|
95
109
|
* Make the user's memberships go from `oldRoles` to `newRoles`. Used by
|
|
96
110
|
* UserManager when an account is created or edited; the account write
|
|
97
111
|
* records its own audit event, so this records none.
|
|
112
|
+
*
|
|
113
|
+
* All or nothing (#2015): when any change cannot be made, the ones already
|
|
114
|
+
* made are undone and a `RoleChangeError` is thrown, after it is logged as
|
|
115
|
+
* an error and admins are notified. That includes a change that cannot even
|
|
116
|
+
* be attempted: no PersonManager, no Person record for the username, or no
|
|
117
|
+
* anchor Organization.
|
|
98
118
|
*/
|
|
99
119
|
applyRoleDiff(username: string, oldRoles: string[], newRoles: string[]): Promise<void>;
|
|
100
120
|
/**
|
|
@@ -108,28 +128,67 @@ declare class RoleManager extends BaseManager {
|
|
|
108
128
|
* a role change and is recorded as one, under the system context `ctx`,
|
|
109
129
|
* deferred until the audit sink is up when this runs at boot.
|
|
110
130
|
*
|
|
131
|
+
* #2015: each account gets the role or not. One that does not is recorded as
|
|
132
|
+
* a failed user-edit, and every miss is an error in the log and one error
|
|
133
|
+
* notification for admins naming the accounts to fix by hand. The boot goes
|
|
134
|
+
* on: nobody asked, so there is nobody to refuse.
|
|
135
|
+
*
|
|
111
136
|
* @returns how many accounts were given the role (0 once it has been given)
|
|
112
137
|
*/
|
|
113
138
|
grantToEveryAccountOnce(roleName: string, usernames: readonly string[], ctx: JobContext): Promise<number>;
|
|
114
139
|
/**
|
|
115
140
|
* Move every membership held by one Person `@id` to another (#1930): when
|
|
116
141
|
* PersonManager joins an account to an existing person, the roles the
|
|
117
|
-
* account's person held go with the account.
|
|
118
|
-
*
|
|
142
|
+
* account's person held go with the account. Returns how many records
|
|
143
|
+
* changed. PersonManager's join records the change. All or nothing (#2015).
|
|
119
144
|
*/
|
|
120
145
|
moveMemberships(fromPersonId: string, toPersonId: string): Promise<number>;
|
|
121
|
-
/** Remove a deleted account's Person from every role it held. */
|
|
122
|
-
removeAllMemberships(username: string): Promise<void>;
|
|
123
146
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* role
|
|
129
|
-
|
|
147
|
+
* Remove a deleted account's Person from every role it held. All or nothing
|
|
148
|
+
* (#2015): when any removal fails the others are put back and it throws, so
|
|
149
|
+
* the account delete is refused. Returns the undo, for a delete that fails
|
|
150
|
+
* after this: it gives every role back. An account with no Person holds no
|
|
151
|
+
* role, so there is nothing to remove.
|
|
152
|
+
*/
|
|
153
|
+
removeAllMemberships(username: string): Promise<() => Promise<void>>;
|
|
154
|
+
/** The roles `username` gains and loses, all or nothing; `notify` false lets a batch send one notice. */
|
|
155
|
+
private changeRoles;
|
|
156
|
+
/**
|
|
157
|
+
* The Person `@id` and anchor Organization a username's role changes need
|
|
158
|
+
* (#1027). Either missing refuses the change (#2015): an account must not
|
|
159
|
+
* be left without the role it was given, nor keep one it was meant to lose.
|
|
160
|
+
*/
|
|
161
|
+
private membershipTarget;
|
|
162
|
+
/** One member change and its inverse. */
|
|
163
|
+
private memberChange;
|
|
164
|
+
/**
|
|
165
|
+
* Make each change in turn (#2015). When one fails, undo the ones made, in
|
|
166
|
+
* reverse, and throw. Returns the changes made, for a later undo.
|
|
167
|
+
*/
|
|
168
|
+
private allOrNothing;
|
|
169
|
+
/** Undo `done`, newest first; returns the labels that could not be undone. */
|
|
170
|
+
private undoAll;
|
|
171
|
+
/**
|
|
172
|
+
* A role change that did not happen is always an error (operator,
|
|
173
|
+
* 2026-10-23): logged at error level, and an error notification for admins
|
|
174
|
+
* unless the caller sends one for a whole batch. Returns the error to throw.
|
|
175
|
+
*/
|
|
176
|
+
private roleFailure;
|
|
177
|
+
/** An error notification for admins; a notification that cannot be sent is logged, never thrown. */
|
|
178
|
+
private reportToAdmins;
|
|
179
|
+
/** #2015: the membership queue's key for one role record. */
|
|
180
|
+
private membershipKey;
|
|
181
|
+
/**
|
|
182
|
+
* Make `personId` a member of the role record, or not, one change at a time
|
|
183
|
+
* per record (#2015). The record is read inside the queue, so a change never
|
|
184
|
+
* works from a member list another change has since replaced. A missing
|
|
185
|
+
* record is created in `createIn` for an add; with no `createIn` an add to a
|
|
186
|
+
* missing record throws, and a removal from one changes nothing. False,
|
|
187
|
+
* writing nothing, when the person already is (or is not) a member.
|
|
130
188
|
*/
|
|
131
|
-
private
|
|
132
|
-
|
|
189
|
+
private setMember;
|
|
190
|
+
/** assignRole / removeRole: the change, recorded as a user-edit whether it happened or not (#1204, #2015). */
|
|
191
|
+
private recordedRoleChange;
|
|
133
192
|
/**
|
|
134
193
|
* The role record for (installOrg, namedPosition), created when missing.
|
|
135
194
|
*
|
|
@@ -153,7 +153,7 @@ interface UserCreateInput {
|
|
|
153
153
|
/**
|
|
154
154
|
* User update input data
|
|
155
155
|
*/
|
|
156
|
-
interface UserUpdateInput {
|
|
156
|
+
export interface UserUpdateInput {
|
|
157
157
|
email?: string;
|
|
158
158
|
displayName?: string;
|
|
159
159
|
password?: string;
|
|
@@ -166,6 +166,16 @@ interface UserUpdateInput {
|
|
|
166
166
|
profilePage?: string;
|
|
167
167
|
[key: string]: unknown;
|
|
168
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* A change worked out from the account as it is now (#2025), the function form
|
|
171
|
+
* of `updateUser`. It runs inside the account's key, is handed a copy of the
|
|
172
|
+
* account without the password hash (an add-on's own fields included, as
|
|
173
|
+
* `UserUpdateInput` allows them), and returns the fields to merge, or
|
|
174
|
+
* `undefined` to change nothing.
|
|
175
|
+
*/
|
|
176
|
+
export type UserUpdateFn = (current: Omit<User, 'password'> & {
|
|
177
|
+
[key: string]: unknown;
|
|
178
|
+
}) => UserUpdateInput | undefined | Promise<UserUpdateInput | undefined>;
|
|
169
179
|
/**
|
|
170
180
|
* UserManager - Handles user authentication, authorization, and roles
|
|
171
181
|
*
|
|
@@ -208,6 +218,8 @@ declare class UserManager extends BaseManager {
|
|
|
208
218
|
private provider;
|
|
209
219
|
private providerClass?;
|
|
210
220
|
private passwordSalt?;
|
|
221
|
+
/** One read-merge-write at a time per account, keyed by the normalized username (#2025). */
|
|
222
|
+
private readonly accountWrites;
|
|
211
223
|
/**
|
|
212
224
|
* Creates a new UserManager instance
|
|
213
225
|
*
|
|
@@ -358,6 +370,15 @@ declare class UserManager extends BaseManager {
|
|
|
358
370
|
upgradePasswordHash(username: string, newHash: string, legacy: boolean): Promise<void>;
|
|
359
371
|
/** Count a completed sign-in on the account: last sign-in and sign-in count (#1758; AuthManager calls it for a sign-in, never a re-check). */
|
|
360
372
|
recordSignIn(username: string): Promise<void>;
|
|
373
|
+
/** Run `task` inside the account's key: one read-merge-write at a time per account (#2025). Not re-entrant. */
|
|
374
|
+
private inAccountKey;
|
|
375
|
+
/**
|
|
376
|
+
* Read an account, change it and write it back, inside its key (#2025): for
|
|
377
|
+
* core's own bookkeeping (sign-in counters, the hash upgrade, the profile
|
|
378
|
+
* page), which is not an edit and is not recorded. False when there is no
|
|
379
|
+
* such account.
|
|
380
|
+
*/
|
|
381
|
+
private patchAccount;
|
|
361
382
|
/**
|
|
362
383
|
* Check if a display name conflicts with existing page names or other users
|
|
363
384
|
* @param {string} displayName - Display name to check
|
|
@@ -400,6 +421,12 @@ declare class UserManager extends BaseManager {
|
|
|
400
421
|
* @returns {Promise<Omit<User, 'password'>>} Created user (without password)
|
|
401
422
|
*/
|
|
402
423
|
createUser(userData: UserCreateInput, ctx: ActorContext): Promise<Omit<User, 'password'>>;
|
|
424
|
+
/**
|
|
425
|
+
* Take back an account whose roles could not be given (#2015): its Person,
|
|
426
|
+
* then the account. A failure here is an error in the log; the role failure
|
|
427
|
+
* that caused it is what the caller is told.
|
|
428
|
+
*/
|
|
429
|
+
private undoAccountCreate;
|
|
403
430
|
/**
|
|
404
431
|
* Update user
|
|
405
432
|
*/
|
|
@@ -419,7 +446,31 @@ declare class UserManager extends BaseManager {
|
|
|
419
446
|
* which accounts exist.
|
|
420
447
|
*/
|
|
421
448
|
resetPasswordWithRecoveryWords(username: string, words: string, newPassword: string, ctx: ActorContext): Promise<boolean>;
|
|
422
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Change an account (#2025: one change at a time per account).
|
|
451
|
+
*
|
|
452
|
+
* `updates` is the fields to merge, or a function that works them out from
|
|
453
|
+
* the account as it is now: `(current) => updates`. The function runs inside
|
|
454
|
+
* the account's `KeyedSerial` key, between the read and the write, so two
|
|
455
|
+
* changes to one account never read the same old record. It gets a copy of
|
|
456
|
+
* the account without the password hash, and returns the fields to merge, or
|
|
457
|
+
* `undefined` to change nothing (nothing is written or recorded). It must not
|
|
458
|
+
* call `updateUser` for the same account: the key is not re-entrant.
|
|
459
|
+
* Either form is checked, applied and recorded the same way.
|
|
460
|
+
*
|
|
461
|
+
* Returns the account as written (as read, when nothing changed), without
|
|
462
|
+
* the password hash: the hash never leaves UserManager except to the
|
|
463
|
+
* password check (`storedPasswordHash`).
|
|
464
|
+
*/
|
|
465
|
+
updateUser(username: string, updates: UserUpdateInput | UserUpdateFn, ctx: ActorContext): Promise<Omit<User, 'password'>>;
|
|
466
|
+
/**
|
|
467
|
+
* The steps of an account change that come after the write, outside the
|
|
468
|
+
* account's key: they reach other managers, which may change the account
|
|
469
|
+
* themselves.
|
|
470
|
+
*/
|
|
471
|
+
private finishEdit;
|
|
472
|
+
/** Read, check and write one account change. Runs inside the account's key (#2025). */
|
|
473
|
+
private editAccount;
|
|
423
474
|
/**
|
|
424
475
|
* Delete user
|
|
425
476
|
*/
|
|
@@ -469,6 +520,8 @@ declare class UserManager extends BaseManager {
|
|
|
469
520
|
* who changed it, so the record says why.
|
|
470
521
|
*/
|
|
471
522
|
private personSyncContext;
|
|
523
|
+
/** Core's bookkeeping for an account write, the read of its roles included (#2018): the system context, saying what and who changed the account. */
|
|
524
|
+
private lifecycleContext;
|
|
472
525
|
/**
|
|
473
526
|
* Persist a Person record paired with a newly-created User. The install's
|
|
474
527
|
* anchor org (when configured) is referenced via `memberOf`; without it
|
|
@@ -27,8 +27,15 @@ import type { PermissionSubject, JobSubject } from '../managers/UserManager.js';
|
|
|
27
27
|
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
28
28
|
import type { Decision, DecisionRequest, Policy } from '../types/Policy.js';
|
|
29
29
|
import { type RoleGrant } from '../utils/roleGrants.js';
|
|
30
|
+
import { type ActorContext } from '../context/ActorContext.js';
|
|
30
31
|
export declare const POLICIES_KEY = "ngdpbase.access.policies";
|
|
31
32
|
export declare const POLICIES_ENABLED_KEY = "ngdpbase.access.policies.enabled";
|
|
33
|
+
/**
|
|
34
|
+
* What `decide`, `permits` and `holds` take (#2022): the caller's own context
|
|
35
|
+
* — a request's `PermissionSubject` or a job's `JobContext` — or a subject
|
|
36
|
+
* already in decision form (`JobSubject`). A caller passes what it was given.
|
|
37
|
+
*/
|
|
38
|
+
type DecisionInput = ActorContext | JobSubject | null | undefined;
|
|
32
39
|
export declare class PolicyDecisionPoint extends BaseManager {
|
|
33
40
|
/**
|
|
34
41
|
* A manager so every PEP can reach it through `engine.getManager`, the same
|
|
@@ -84,10 +91,11 @@ export declare class PolicyDecisionPoint extends BaseManager {
|
|
|
84
91
|
* of {@link decide} for a capability. It was `UserManager.hasPermission`
|
|
85
92
|
* until #1431 step 14; the decision was already here, only the door moved.
|
|
86
93
|
*
|
|
87
|
-
* Takes
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
94
|
+
* Takes the caller's context as it was given (#2022): a `PermissionSubject`
|
|
95
|
+
* — the request's own identity, forwarded from `req.userContext`
|
|
96
|
+
* (WikiContext, ApiContext, ParseContext), roles already resolved by the
|
|
97
|
+
* session middleware — or a `JobContext` for work with no request (#631),
|
|
98
|
+
* which the PDP turns into a subject itself. Never convert one first.
|
|
91
99
|
*
|
|
92
100
|
* #1173 Part B: the username-string overload this method once accepted is
|
|
93
101
|
* gone. A string cannot carry `viaToken`, so the agent-token ceiling below
|
|
@@ -98,12 +106,13 @@ export declare class PolicyDecisionPoint extends BaseManager {
|
|
|
98
106
|
* separate question {@link userHoldsPermission} — "does the named user hold
|
|
99
107
|
* this?" — which is a lookup about somebody else, not an authorisation.
|
|
100
108
|
*
|
|
101
|
-
* @param subject - The
|
|
102
|
-
* `viaToken` present when a bearer token authenticated it
|
|
109
|
+
* @param subject - The caller's context: the request's subject, with
|
|
110
|
+
* `viaToken` present when a bearer token authenticated it,
|
|
111
|
+
* or a job's context.
|
|
103
112
|
* @param action - Action/permission to check (e.g., 'page-create', 'user-read')
|
|
104
113
|
* @returns True if the subject may perform the action under current policy
|
|
105
114
|
*/
|
|
106
|
-
permits(subject:
|
|
115
|
+
permits(subject: DecisionInput, action: string): Promise<boolean>;
|
|
107
116
|
/**
|
|
108
117
|
* Does the subject HOLD this permission (#1635)? Policy alone — the
|
|
109
118
|
* step-up freshness rule is not applied. A different question from
|
|
@@ -112,7 +121,7 @@ export declare class PolicyDecisionPoint extends BaseManager {
|
|
|
112
121
|
* person whose sign-in is not fresh still sees the way to the prompt that
|
|
113
122
|
* makes it fresh. A door never asks this.
|
|
114
123
|
*/
|
|
115
|
-
holds(subject:
|
|
124
|
+
holds(subject: DecisionInput, action: string): Promise<boolean>;
|
|
116
125
|
/**
|
|
117
126
|
* Step-up (#1525, #1635), decided here — in the one place every door asks —
|
|
118
127
|
* rather than by routes in front of the doors. A permission marked
|
|
@@ -252,7 +261,7 @@ export declare class PolicyDecisionPoint extends BaseManager {
|
|
|
252
261
|
* time, not at enqueue time (#631, #1212).
|
|
253
262
|
* 4. __The policies__, through `PolicyEvaluator`.
|
|
254
263
|
*/
|
|
255
|
-
decide(subject:
|
|
264
|
+
decide(subject: DecisionInput, request: DecisionRequest): Promise<Decision>;
|
|
256
265
|
/** `decide`, with the step-up rule applied or not — {@link holds} is the only caller that leaves it out. */
|
|
257
266
|
private evaluate;
|
|
258
267
|
}
|