@jwilleke/ngdpbase-types 5.29.1 → 5.30.1
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/README.md +1 -1
- package/lib/packages/ngdpbase-types/index.d.ts +8 -3
- package/lib/src/context/JobContext.d.ts +20 -1
- package/lib/src/context/JobContext.js +18 -2
- package/lib/src/context/bootActions.js +5 -1
- package/lib/src/ice/fileHandling.d.ts +24 -2
- package/lib/src/ice/originalKeep.d.ts +40 -0
- package/lib/src/ice/types.d.ts +33 -1
- package/lib/src/managers/AttachmentManager.d.ts +12 -8
- package/lib/src/managers/AuthManager.d.ts +111 -0
- package/lib/src/managers/BackgroundJobManager.d.ts +8 -0
- package/lib/src/managers/BackupManager.d.ts +6 -0
- package/lib/src/managers/BackupManager.js +38 -11
- package/lib/src/managers/{PersonManager.d.ts → IdentityManager.d.ts} +20 -16
- package/lib/src/managers/ImportManager.d.ts +2 -5
- package/lib/src/managers/OrganizationManager.d.ts +2 -2
- package/lib/src/managers/PolicyEvaluator.d.ts +7 -0
- package/lib/src/managers/RoleManager.d.ts +7 -7
- package/lib/src/managers/UserManager.d.ts +17 -5
- package/lib/src/providers/BaseAttachmentProvider.d.ts +2 -8
- package/lib/src/providers/BaseCredentialsProvider.d.ts +7 -1
- package/lib/src/providers/BasicAttachmentProvider.d.ts +3 -8
- package/lib/src/security/PolicyDecisionPoint.d.ts +12 -0
- package/lib/src/security/PolicyInformationPoint.d.ts +1 -1
- package/lib/src/security/permissions.generated.d.ts +4 -4
- package/lib/src/types/Config.d.ts +1 -1
- package/lib/src/types/Person.d.ts +1 -1
- package/lib/src/types/PersonProvider.d.ts +2 -2
- package/lib/src/types/Policy.d.ts +2 -2
- package/lib/src/types/Provider.d.ts +21 -0
- package/lib/src/utils/addonConfigLayer.d.ts +16 -0
- package/lib/src/utils/addonConfigLayer.js +40 -3
- package/lib/src/utils/addonHost.d.ts +21 -3
- package/lib/src/utils/addonHost.js +10 -4
- package/lib/src/utils/auditEventNames.generated.d.ts +10 -2
- package/lib/src/utils/auditEventNames.generated.js +10 -2
- package/lib/src/utils/auditEvents.d.ts +16 -3
- package/lib/src/utils/auditEvents.js +10 -11
- package/lib/src/utils/availability.d.ts +120 -0
- package/lib/src/utils/availability.js +174 -0
- package/lib/src/utils/contactAccess.d.ts +9 -9
- package/lib/src/utils/keyedSerial.js +41 -0
- package/lib/src/utils/privateStoreUnlock.d.ts +1 -1
- package/package.json +1 -1
|
@@ -11,8 +11,12 @@ export interface LinkOfferDetails {
|
|
|
11
11
|
/** The outcome of offering a sign-up link (#1930). */
|
|
12
12
|
export type LinkOfferOutcome = 'sent' | 'no-match' | 'several' | 'unavailable' | 'failed';
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* IdentityManager — the identity record of every digital identity, user or
|
|
15
|
+
* contact (#617), and the door for them (#1930). Named PersonManager until
|
|
16
|
+
* #2038; `getManager('PersonManager')` still answers with it for one release
|
|
17
|
+
* (`MANAGER_ALIASES` in `src/core/Engine.ts`). What is stored keeps its
|
|
18
|
+
* "person" names: `data/persons/`, `ngdpbase.application.persons.*`, the
|
|
19
|
+
* `Person` types, `person:<uuid>`, the `person-*` audit actions.
|
|
16
20
|
*
|
|
17
21
|
* A person may have an account or not. Each account has exactly one person
|
|
18
22
|
* (`identifier` = the username); a person with no account has the identifier
|
|
@@ -20,14 +24,14 @@ export type LinkOfferOutcome = 'sent' | 'no-match' | 'several' | 'unavailable' |
|
|
|
20
24
|
* the account lifecycle under a system context.
|
|
21
25
|
*
|
|
22
26
|
* The door: `create`, `update`, `delete`, `get`, `list`, `search` and
|
|
23
|
-
* `linkAccount` take the caller's context first and ask the PDP: `
|
|
24
|
-
* to list, search or get; `
|
|
25
|
-
* the `
|
|
27
|
+
* `linkAccount` take the caller's context first and ask the PDP: `identity-read`
|
|
28
|
+
* to list, search or get; `identity-manage` to change (`linkAccount` also `user-edit`). Each change is recorded as
|
|
29
|
+
* the `identity-manage` audit event. A refusal throws `ContactRefusedError`.
|
|
26
30
|
*
|
|
27
31
|
* Every lookup takes the caller's context too (#2018): `getById`,
|
|
28
32
|
* `getByIdentifier` and `getByUserIdentifier`. A user reads their own identity
|
|
29
33
|
* record, and `update` changes it, under `profile-manage`; anyone else's needs
|
|
30
|
-
* `
|
|
34
|
+
* `identity-read` / `identity-manage`. Core's own reads (RoleManager resolving
|
|
31
35
|
* roles on every request, UserManager's account lifecycle) pass the system
|
|
32
36
|
* context, allowed without a policy evaluation (`requireRecordAccess`).
|
|
33
37
|
*
|
|
@@ -40,8 +44,8 @@ export type LinkOfferOutcome = 'sent' | 'no-match' | 'several' | 'unavailable' |
|
|
|
40
44
|
* `getByUserIdentifier(ctx, username)` returns a user's identity record and
|
|
41
45
|
* makes it when the user has none (#2037): every user has exactly one.
|
|
42
46
|
*/
|
|
43
|
-
declare class
|
|
44
|
-
readonly description = "
|
|
47
|
+
declare class IdentityManager extends BaseManager {
|
|
48
|
+
readonly description = "Identity records of users and contacts (#617)";
|
|
45
49
|
private provider;
|
|
46
50
|
private providerClass?;
|
|
47
51
|
/** `ngdpbase.application.persons.storagedir`, resolved at start: what a backup copies (#1876). */
|
|
@@ -65,7 +69,7 @@ declare class PersonManager extends BaseManager {
|
|
|
65
69
|
getProvider(): PersonProvider | null;
|
|
66
70
|
/**
|
|
67
71
|
* One identity record by `@id` (#2018): the caller's own under
|
|
68
|
-
* `profile-manage`, anyone else's under `
|
|
72
|
+
* `profile-manage`, anyone else's under `identity-read`, core's own work
|
|
69
73
|
* under the system context. A refusal throws `ContactRefusedError`.
|
|
70
74
|
*/
|
|
71
75
|
getById(ctx: ActorContext, id: string): Promise<Person | null>;
|
|
@@ -80,9 +84,9 @@ declare class PersonManager extends BaseManager {
|
|
|
80
84
|
getManagerStats(): Promise<ManagerStats>;
|
|
81
85
|
/** True when the person has no account: its identifier is `person:<uuid>` (#1930). */
|
|
82
86
|
static isAccountless(person: Pick<Person, 'identifier'>): boolean;
|
|
83
|
-
/** Every person, for a caller who may read contacts (`
|
|
87
|
+
/** Every person, for a caller who may read contacts (`identity-read`). */
|
|
84
88
|
list(ctx: ActorContext): Promise<Person[]>;
|
|
85
|
-
/** One person by `@id`: {@link getById}, the caller's own under `profile-manage`, anyone else's under `
|
|
89
|
+
/** One person by `@id`: {@link getById}, the caller's own under `profile-manage`, anyone else's under `identity-read`. */
|
|
86
90
|
get(ctx: ActorContext, id: string): Promise<Person | null>;
|
|
87
91
|
/**
|
|
88
92
|
* Persons whose name (`name`, `givenName`, `familyName`) or `email` contains
|
|
@@ -103,13 +107,13 @@ declare class PersonManager extends BaseManager {
|
|
|
103
107
|
/**
|
|
104
108
|
* Change a person's fields, affiliations included. `@id` and `identifier`
|
|
105
109
|
* never change here. The caller's own identity record under
|
|
106
|
-
* `profile-manage`, anyone else's under `
|
|
110
|
+
* `profile-manage`, anyone else's under `identity-manage` (#2018).
|
|
107
111
|
*/
|
|
108
112
|
update(ctx: ActorContext, id: string, patch: PersonUpdate): Promise<Person | null>;
|
|
109
113
|
delete(ctx: ActorContext, id: string): Promise<boolean>;
|
|
110
114
|
/**
|
|
111
115
|
* An administrator joins a person who has no account to an account (#1930),
|
|
112
|
-
* for when the email differs or none was given. Asks `
|
|
116
|
+
* for when the email differs or none was given. Asks `identity-manage` and
|
|
113
117
|
* `user-edit`. The person keeps its `@id`; the account's own person, if it
|
|
114
118
|
* has one, is folded in and removed.
|
|
115
119
|
*/
|
|
@@ -163,11 +167,11 @@ declare class PersonManager extends BaseManager {
|
|
|
163
167
|
* store) gets it here, from the account's display name and email.
|
|
164
168
|
*
|
|
165
169
|
* The read is decided as {@link getByIdentifier} (#2018): a signed-in user's
|
|
166
|
-
* own under `profile-manage`, anyone else's under `
|
|
170
|
+
* own under `profile-manage`, anyone else's under `identity-read`, core's own
|
|
167
171
|
* work under the system context. Making the missing record is core's
|
|
168
172
|
* bookkeeping, not the caller's contact management, so it goes through
|
|
169
173
|
* {@link create} under the system context, naming the caller, and is
|
|
170
|
-
* recorded as `
|
|
174
|
+
* recorded as `identity-manage`. One at a time per username, so two lookups
|
|
171
175
|
* never make two records. Null when there is no account by that name.
|
|
172
176
|
* Never a second record: a contact with the same email is left alone
|
|
173
177
|
* (an administrator links it, {@link linkAccount}).
|
|
@@ -178,4 +182,4 @@ declare class PersonManager extends BaseManager {
|
|
|
178
182
|
private normalizeProviderName;
|
|
179
183
|
}
|
|
180
184
|
export { ContactRefusedError } from '../utils/contactAccess.js';
|
|
181
|
-
export default
|
|
185
|
+
export default IdentityManager;
|
|
@@ -267,7 +267,8 @@ export type TakeoutImportReport = {
|
|
|
267
267
|
export interface UsableReceiver {
|
|
268
268
|
id: string;
|
|
269
269
|
label?: string;
|
|
270
|
-
|
|
270
|
+
/** What the screen offers; the kept types (`types`, #2061) are the upload door's business, not the screen's. */
|
|
271
|
+
originals: Required<Omit<IceOriginalChoices, 'types'>>;
|
|
271
272
|
}
|
|
272
273
|
/**
|
|
273
274
|
* Import Manager class
|
|
@@ -546,10 +547,6 @@ declare class ImportManager extends BaseManager {
|
|
|
546
547
|
* @returns Stats about imported attachments
|
|
547
548
|
*/
|
|
548
549
|
private importPageAttachments;
|
|
549
|
-
/**
|
|
550
|
-
* Get MIME type from file extension
|
|
551
|
-
*/
|
|
552
|
-
private getMimeType;
|
|
553
550
|
/**
|
|
554
551
|
* Get file extensions to process based on options
|
|
555
552
|
*/
|
|
@@ -88,13 +88,13 @@ declare class OrganizationManager extends BaseManager {
|
|
|
88
88
|
* problem actually bites.
|
|
89
89
|
*/
|
|
90
90
|
getInstallOrg(): Promise<Organization | null>;
|
|
91
|
-
/** One organization by `@id`, for a caller who may read contacts (`
|
|
91
|
+
/** One organization by `@id`, for a caller who may read contacts (`identity-read`), or core's own work (#2018). */
|
|
92
92
|
getById(ctx: ActorContext, id: string): Promise<Organization | null>;
|
|
93
93
|
/** One organization by its file name, decided as {@link getById} (#2018). */
|
|
94
94
|
getByFile(ctx: ActorContext, filename: string): Promise<Organization | null>;
|
|
95
95
|
/** #1006: how many organizations this instance holds. Count only. */
|
|
96
96
|
getManagerStats(): Promise<ManagerStats>;
|
|
97
|
-
/** Every organization, for a caller who may read contacts (`
|
|
97
|
+
/** Every organization, for a caller who may read contacts (`identity-read`). */
|
|
98
98
|
list(ctx: ActorContext): Promise<Organization[]>;
|
|
99
99
|
/** One organization by `@id`: {@link getById}. */
|
|
100
100
|
get(ctx: ActorContext, id: string): Promise<Organization | null>;
|
|
@@ -34,6 +34,13 @@ interface AccessContext {
|
|
|
34
34
|
* policy naming it decides nothing (docs/access-policies.md).
|
|
35
35
|
*/
|
|
36
36
|
export declare const RESOURCE_MATCHERS: Record<string, (pattern: string, pageName: string, attributes?: ResourceAttributes) => boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* How each policy subject type matches (#1999), one matcher per type. These
|
|
39
|
+
* are the only subject types a decision matches, and the only ones
|
|
40
|
+
* PolicyValidator accepts: a type with no matcher here would be a policy that
|
|
41
|
+
* grants nothing.
|
|
42
|
+
*/
|
|
43
|
+
export declare const SUBJECT_MATCHERS: Record<string, (value: string, userContext: UserContext | undefined) => boolean>;
|
|
37
44
|
/**
|
|
38
45
|
* The resource types configuration declares (#1504), for the evaluator and the
|
|
39
46
|
* validator alike. When the key is not set at all — a configuration older than
|
|
@@ -45,7 +45,7 @@ declare class RoleManager extends BaseManager {
|
|
|
45
45
|
backupFolders(): BackupFolder[];
|
|
46
46
|
/** Provider accessor, mainly for tests. */
|
|
47
47
|
getProvider(): RoleProvider | null;
|
|
48
|
-
/** One role record by `@id`: its members are identity records, so `
|
|
48
|
+
/** One role record by `@id`: its members are identity records, so `identity-read`, or core's own work (#2018). */
|
|
49
49
|
getById(ctx: ActorContext, id: string): Promise<Role | null>;
|
|
50
50
|
getByOrgAndPosition(organizationId: string, namedPosition: string): Promise<Role | null>;
|
|
51
51
|
listByMember(personId: string): Promise<Role[]>;
|
|
@@ -67,13 +67,13 @@ declare class RoleManager extends BaseManager {
|
|
|
67
67
|
* The pseudo-roles `'Authenticated'` and `'All'` are NOT added here — the
|
|
68
68
|
* caller adds those when constructing `userContext.roles`.
|
|
69
69
|
*
|
|
70
|
-
* Returns `[]` when
|
|
70
|
+
* Returns `[]` when IdentityManager is unavailable, there is no account by
|
|
71
71
|
* that username, no record lists them, or the lookup throws. An account
|
|
72
72
|
* missing its identity record gets it here (#2037).
|
|
73
73
|
*
|
|
74
|
-
* `ctx` is who is asking (#2018), decided by
|
|
74
|
+
* `ctx` is who is asking (#2018), decided by IdentityManager's door: a user
|
|
75
75
|
* about themselves under `profile-manage`, about anyone else under
|
|
76
|
-
* `
|
|
76
|
+
* `identity-read`; core's own work passes the system context. A refusal is
|
|
77
77
|
* thrown as `ContactRefusedError`, never answered as "no roles".
|
|
78
78
|
*/
|
|
79
79
|
resolveUserRoles(ctx: ActorContext, username: string): Promise<string[]>;
|
|
@@ -114,7 +114,7 @@ declare class RoleManager extends BaseManager {
|
|
|
114
114
|
* All or nothing (#2015): when any change cannot be made, the ones already
|
|
115
115
|
* made are undone and a `RoleChangeError` is thrown, after it is logged as
|
|
116
116
|
* an error and admins are notified. That includes a change that cannot even
|
|
117
|
-
* be attempted: no
|
|
117
|
+
* be attempted: no IdentityManager, no Person record for the username, or no
|
|
118
118
|
* anchor Organization.
|
|
119
119
|
*/
|
|
120
120
|
applyRoleDiff(username: string, oldRoles: string[], newRoles: string[]): Promise<void>;
|
|
@@ -139,9 +139,9 @@ declare class RoleManager extends BaseManager {
|
|
|
139
139
|
grantToEveryAccountOnce(roleName: string, usernames: readonly string[], ctx: JobContext): Promise<number>;
|
|
140
140
|
/**
|
|
141
141
|
* Move every membership held by one Person `@id` to another (#1930): when
|
|
142
|
-
*
|
|
142
|
+
* IdentityManager joins an account to an existing person, the roles the
|
|
143
143
|
* account's person held go with the account. Returns how many records
|
|
144
|
-
* changed.
|
|
144
|
+
* changed. IdentityManager's join records the change. All or nothing (#2015).
|
|
145
145
|
*/
|
|
146
146
|
moveMemberships(fromPersonId: string, toPersonId: string): Promise<number>;
|
|
147
147
|
/**
|
|
@@ -132,6 +132,8 @@ export interface JobSubject {
|
|
|
132
132
|
viaToken?: AgentTokenGrant;
|
|
133
133
|
viaShare?: ShareGrant;
|
|
134
134
|
viaProxy?: ProxyClaim;
|
|
135
|
+
/** #1999: the scheduled job a run acts as (`JobContext.job`). */
|
|
136
|
+
job?: string;
|
|
135
137
|
}
|
|
136
138
|
/**
|
|
137
139
|
* User creation input data
|
|
@@ -343,9 +345,9 @@ declare class UserManager extends BaseManager {
|
|
|
343
345
|
/**
|
|
344
346
|
* Every account has exactly one identity record, made by `createUser`
|
|
345
347
|
* (#2037). One that has none (written straight into the user store, or
|
|
346
|
-
* whose record was lost) gets it at boot, once, through
|
|
348
|
+
* whose record was lost) gets it at boot, once, through IdentityManager's
|
|
347
349
|
* door under the system context, each creation recorded as
|
|
348
|
-
* `
|
|
350
|
+
* `identity-manage`. The identity records are listed once and only the
|
|
349
351
|
* missing ones are made, so a large user store costs one read. A failure
|
|
350
352
|
* is logged and the boot goes on: the role paths make the record when the
|
|
351
353
|
* account is next used (`getByUserIdentifier`).
|
|
@@ -457,6 +459,16 @@ declare class UserManager extends BaseManager {
|
|
|
457
459
|
* which accounts exist.
|
|
458
460
|
*/
|
|
459
461
|
resetPasswordWithRecoveryWords(username: string, words: string, newPassword: string, ctx: ActorContext): Promise<boolean>;
|
|
462
|
+
/**
|
|
463
|
+
* A user's first password, chosen by themselves (#2049: after an
|
|
464
|
+
* invitation signed them in). Only for their own account, signed in as
|
|
465
|
+
* themselves (no token, share link or proxy), under `account-security`,
|
|
466
|
+
* and only while the account has no password: a password already set is
|
|
467
|
+
* changed on the profile, which asks for the current one. `false`,
|
|
468
|
+
* changing nothing, otherwise. The length rule is the route's, as for
|
|
469
|
+
* every password form. Recorded like any password change (`user-edit`).
|
|
470
|
+
*/
|
|
471
|
+
setFirstPassword(username: string, newPassword: string, ctx: PermissionSubject): Promise<boolean>;
|
|
460
472
|
/**
|
|
461
473
|
* Change an account (#2025: one change at a time per account).
|
|
462
474
|
*
|
|
@@ -529,10 +541,10 @@ declare class UserManager extends BaseManager {
|
|
|
529
541
|
activeOnly?: boolean;
|
|
530
542
|
}, ctx: ActorContext): Promise<Omit<User, 'password'>[]>;
|
|
531
543
|
/**
|
|
532
|
-
* The account lifecycle reaches
|
|
544
|
+
* The account lifecycle reaches IdentityManager's door under a system context
|
|
533
545
|
* (#1930): keeping an account's person in step with the account is core's
|
|
534
546
|
* own bookkeeping, not the caller's contact management, so it never depends
|
|
535
|
-
* on the caller holding `
|
|
547
|
+
* on the caller holding `identity-manage`. The reason names the account and
|
|
536
548
|
* who changed it, so the record says why.
|
|
537
549
|
*/
|
|
538
550
|
private personSyncContext;
|
|
@@ -547,7 +559,7 @@ declare class UserManager extends BaseManager {
|
|
|
547
559
|
private syncPersonOnCreate;
|
|
548
560
|
/**
|
|
549
561
|
* At sign-up, offer the new account a person with no account who has the
|
|
550
|
-
* same email (#1930):
|
|
562
|
+
* same email (#1930): IdentityManager mails an "Is this you?" link, and joins
|
|
551
563
|
* them only when the account confirms. Skipped when email is off. Never
|
|
552
564
|
* fails the account write.
|
|
553
565
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AttachmentMetadata, AttachmentProvider } from '../types/index.js';
|
|
2
|
-
import type { StoreFileEntry, StoreFileLocation } from '../types/Provider.js';
|
|
2
|
+
import type { StoreFileEntry, StoreFileInput, StoreFileLocation } from '../types/Provider.js';
|
|
3
3
|
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
4
4
|
import { ProviderInfo } from './BasePageProvider.js';
|
|
5
5
|
import BaseProvider from './BaseProvider.js';
|
|
@@ -150,13 +150,7 @@ declare abstract class BaseAttachmentProvider extends BaseProvider implements At
|
|
|
150
150
|
*/
|
|
151
151
|
getAttachmentByFilename(_filename: string): Promise<AttachmentMetadata | null>;
|
|
152
152
|
/** Store a file in a store. The same bytes already in THAT store return the existing entry. */
|
|
153
|
-
storeFileInStore(_location: StoreFileLocation, _bytes: Buffer, _file:
|
|
154
|
-
originalName: string;
|
|
155
|
-
mimeType: string;
|
|
156
|
-
description: string;
|
|
157
|
-
author?: string;
|
|
158
|
-
pageName?: string;
|
|
159
|
-
}): Promise<StoreFileEntry>;
|
|
153
|
+
storeFileInStore(_location: StoreFileLocation, _bytes: Buffer, _file: StoreFileInput): Promise<StoreFileEntry>;
|
|
160
154
|
/** One file of a store, with its bytes; null when the store does not list it. */
|
|
161
155
|
getFileInStore(_location: StoreFileLocation, _id: string): Promise<{
|
|
162
156
|
entry: StoreFileEntry;
|
|
@@ -24,8 +24,14 @@ export declare const CREDENTIALS_KEY_ENV = "NGDPBASE_CREDENTIALS_KEY";
|
|
|
24
24
|
* represented, `subject` the grant id, `secret` the grant's terms as JSON
|
|
25
25
|
* (whom it is for, the permissions, who made it, when it ends). Never a way
|
|
26
26
|
* in; AuthManager reads and writes it only through its proxy-grant methods.
|
|
27
|
+
*
|
|
28
|
+
* `invitation` (#2049): a one-time link that lets a new user set up their
|
|
29
|
+
* sign-in — `username` the account invited, `subject` the SHA-256 of the
|
|
30
|
+
* link's token (the token itself is never stored), `secret` the terms as JSON
|
|
31
|
+
* (who sent it, when it ends). Not a sign-in method; AuthManager reads and
|
|
32
|
+
* writes it only through its invitation methods.
|
|
27
33
|
*/
|
|
28
|
-
export type CredentialKind = 'passkey' | 'totp' | 'email' | 'sms' | 'device' | 'identity' | 'proxy';
|
|
34
|
+
export type CredentialKind = 'passkey' | 'totp' | 'email' | 'sms' | 'device' | 'identity' | 'proxy' | 'invitation';
|
|
29
35
|
export declare const CREDENTIAL_KINDS: readonly CredentialKind[];
|
|
30
36
|
/** One credential, as stored. */
|
|
31
37
|
export interface CredentialRecord {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import BaseAttachmentProvider, { FileInfo, User, AttachmentResult } from './BaseAttachmentProvider.js';
|
|
2
|
-
import type { ProviderInfo, StoreFileEntry, StoreFileLocation } from '../types/Provider.js';
|
|
2
|
+
import type { ProviderInfo, StoreFileEntry, StoreFileInput, StoreFileLocation } from '../types/Provider.js';
|
|
3
3
|
import { AttachmentMetadata } from '../types/index.js';
|
|
4
4
|
import type { AssetProvider, AssetRecord, AssetQuery, AssetPage, AssetInput, AssetMetadata, AssetMetadataPatch } from '../types/Asset.js';
|
|
5
5
|
import type { CreativeWork } from '../types/Schema.js';
|
|
@@ -213,6 +213,7 @@ declare class BasicAttachmentProvider extends BaseAttachmentProvider implements
|
|
|
213
213
|
/**
|
|
214
214
|
* Validate file against constraints
|
|
215
215
|
* @param fileInfo - File information
|
|
216
|
+
* @param alsoAllowed - Types allowed beyond the site's list: only an import's kept original into its receiver's own vault (#2061)
|
|
216
217
|
* @private
|
|
217
218
|
*/
|
|
218
219
|
private validateFile;
|
|
@@ -261,13 +262,7 @@ declare class BasicAttachmentProvider extends BaseAttachmentProvider implements
|
|
|
261
262
|
private requirePagesDirectory;
|
|
262
263
|
private readStoreIndex;
|
|
263
264
|
private writeStoreIndex;
|
|
264
|
-
storeFileInStore(location: StoreFileLocation, bytes: Buffer, file:
|
|
265
|
-
originalName: string;
|
|
266
|
-
mimeType: string;
|
|
267
|
-
description: string;
|
|
268
|
-
author?: string;
|
|
269
|
-
pageName?: string;
|
|
270
|
-
}): Promise<StoreFileEntry>;
|
|
265
|
+
storeFileInStore(location: StoreFileLocation, bytes: Buffer, file: StoreFileInput): Promise<StoreFileEntry>;
|
|
271
266
|
getFileInStore(location: StoreFileLocation, id: string): Promise<{
|
|
272
267
|
entry: StoreFileEntry;
|
|
273
268
|
bytes: Buffer;
|
|
@@ -43,6 +43,10 @@ export declare class PolicyDecisionPoint extends BaseManager {
|
|
|
43
43
|
* derived, never stored.
|
|
44
44
|
*/
|
|
45
45
|
constructor(engine: WikiEngine);
|
|
46
|
+
/** Each `<policy id> <retired name>` already warned about (#2038): said once per process. */
|
|
47
|
+
private readonly warnedRetired;
|
|
48
|
+
/** Warn, once per policy and name, that a policy names a retired permission and what it is read as (#2038). */
|
|
49
|
+
private warnRetiredOnce;
|
|
46
50
|
/**
|
|
47
51
|
* The policies in force right now, highest priority first.
|
|
48
52
|
*
|
|
@@ -55,6 +59,14 @@ export declare class PolicyDecisionPoint extends BaseManager {
|
|
|
55
59
|
* not something to resolve silently here.
|
|
56
60
|
*/
|
|
57
61
|
policies(): Policy[];
|
|
62
|
+
/**
|
|
63
|
+
* Whether a policy in force names the scheduled job `jobId` as a subject
|
|
64
|
+
* (`{ "type": "job", "value": jobId }`, #1999), allow or deny. When one
|
|
65
|
+
* does, the job's runs act as the job itself; when none does, as the system
|
|
66
|
+
* principal. Read live, so a site that removes the policy gives the job the
|
|
67
|
+
* system roles back at its next decision.
|
|
68
|
+
*/
|
|
69
|
+
namesJob(jobId: string): boolean;
|
|
58
70
|
/**
|
|
59
71
|
* What each role permits: role name → the actions its holders are granted.
|
|
60
72
|
*
|
|
@@ -178,7 +178,7 @@ declare class PolicyInformationPoint extends BaseManager {
|
|
|
178
178
|
* resolves to the anonymous subject: the job then holds exactly what a
|
|
179
179
|
* visitor holds, which is the safe answer for someone who no longer exists.
|
|
180
180
|
*/
|
|
181
|
-
resolveSubjectNow(username: string | undefined): Promise<UserContext>;
|
|
181
|
+
resolveSubjectNow(username: string | undefined, job?: string): Promise<UserContext>;
|
|
182
182
|
/**
|
|
183
183
|
* The subject as a page's own rules match it (#2022): for a job's subject
|
|
184
184
|
* (`resolveRolesNow`, #1212), its username with the roles its account
|
|
@@ -22,10 +22,10 @@ export type CorePermission =
|
|
|
22
22
|
| 'comment-create'
|
|
23
23
|
/** Change any configuration setting, and download a backup (#1525). Admin only by default; asks for a fresh sign-in when listed in ngdpbase.auth.step-up */
|
|
24
24
|
| 'config-manage'
|
|
25
|
-
/** Create, edit, delete and link
|
|
26
|
-
| '
|
|
27
|
-
/** List, search and view
|
|
28
|
-
| '
|
|
25
|
+
/** Create, edit, delete and link identity records, of users and contacts alike, and organizations, and an identity's affiliations, through IdentityManager and OrganizationManager (#1930). Reading them is identity-read. Was contact-manage until #2038; a policy naming that is still read as this for one release. Admins by default; an add-on grants it to its own roles in its own policy */
|
|
26
|
+
| 'identity-manage'
|
|
27
|
+
/** List, search and view identity records, of users and contacts alike, and organizations, and their affiliations, through IdentityManager and OrganizationManager, and the /admin/contacts and /admin/organizations views (#1930). Was contact-read until #2038; a policy naming that is still read as this for one release. Admins and demo admins by default; an add-on grants it to its own roles in its own policy */
|
|
28
|
+
| 'identity-read'
|
|
29
29
|
/** Browse and view the media library (not delegated to anonymous visitors) */
|
|
30
30
|
| 'media-read'
|
|
31
31
|
/** Create new pages */
|
|
@@ -312,7 +312,7 @@ export interface ApplicationOrganizationConfig {
|
|
|
312
312
|
'ngdpbase.application.persons.provider.default': string;
|
|
313
313
|
/** Active Person provider class (lowercased; falls back to .default) */
|
|
314
314
|
'ngdpbase.application.persons.provider': string;
|
|
315
|
-
/** #1930: the most results `
|
|
315
|
+
/** #1930: the most results `IdentityManager.search` returns when the caller gives no limit */
|
|
316
316
|
'ngdpbase.application.persons.search.limit': number;
|
|
317
317
|
/** #1930: minutes the emailed "Is this you?" sign-up link stays usable */
|
|
318
318
|
'ngdpbase.application.persons.link-offer.ttl-minutes': number;
|
|
@@ -70,7 +70,7 @@ export interface Person {
|
|
|
70
70
|
*/
|
|
71
71
|
export type PersonUpdate = Partial<Omit<Person, '@context' | '@type' | '@id' | 'identifier'>>;
|
|
72
72
|
/**
|
|
73
|
-
* What a caller gives `
|
|
73
|
+
* What a caller gives `IdentityManager.create` (#1930): the person's fields.
|
|
74
74
|
* `identifier` is left out for a person with no account (one is generated);
|
|
75
75
|
* UserManager passes the account's username when it creates the person for a
|
|
76
76
|
* new account.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Storage seam for Person records (#617)
|
|
3
3
|
*
|
|
4
4
|
* File system is the v1 default. Future providers (LDAP, external directory)
|
|
5
|
-
* implement the same interface and register without touching
|
|
5
|
+
* implement the same interface and register without touching IdentityManager.
|
|
6
6
|
*/
|
|
7
7
|
import type { BaseProvider } from './Provider.js';
|
|
8
8
|
import type { Person, PersonUpdate } from './Person.js';
|
|
@@ -20,7 +20,7 @@ export interface PersonProvider extends BaseProvider {
|
|
|
20
20
|
create(person: Person): Promise<Person>;
|
|
21
21
|
/**
|
|
22
22
|
* Write the whole record in place of the one with the same `@id` (#1930).
|
|
23
|
-
* The only write that may change `identifier`:
|
|
23
|
+
* The only write that may change `identifier`: IdentityManager uses it to join
|
|
24
24
|
* a person to an account.
|
|
25
25
|
*/
|
|
26
26
|
replace(person: Person): Promise<Person>;
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* again because a `.d.ts` may not import a `.ts`. Four copies of one shape is
|
|
13
13
|
* how a field gets added to one and missed in the others.
|
|
14
14
|
*/
|
|
15
|
-
/** Who a policy grants to. `role`
|
|
15
|
+
/** Who a policy grants to. The evaluator matches `role` and `job` (#1999), its `SUBJECT_MATCHERS`. */
|
|
16
16
|
export interface PolicySubject {
|
|
17
|
-
/** `role`,
|
|
17
|
+
/** `role`, or `job` for a scheduled job acting as itself (#1999). */
|
|
18
18
|
type: string;
|
|
19
19
|
value: string;
|
|
20
20
|
}
|
|
@@ -582,6 +582,27 @@ export interface StoreFileEntry {
|
|
|
582
582
|
dateModified: string;
|
|
583
583
|
/** Pages the file was uploaded onto. */
|
|
584
584
|
mentions: string[];
|
|
585
|
+
/**
|
|
586
|
+
* An import's kept original (#2061): always served as a download
|
|
587
|
+
* (`Content-Disposition: attachment`, `nosniff`), never shown inline.
|
|
588
|
+
*/
|
|
589
|
+
download?: true;
|
|
590
|
+
}
|
|
591
|
+
/** What `storeFileInStore` is told about a file it stores in a private store. */
|
|
592
|
+
export interface StoreFileInput {
|
|
593
|
+
originalName: string;
|
|
594
|
+
mimeType: string;
|
|
595
|
+
description: string;
|
|
596
|
+
author?: string;
|
|
597
|
+
pageName?: string;
|
|
598
|
+
/**
|
|
599
|
+
* Set only by AttachmentManager for an import's keep into the receiver's
|
|
600
|
+
* own vault (#2061): `types` are allowed beyond the site's allow-list, and
|
|
601
|
+
* the file is marked {@link StoreFileEntry.download}.
|
|
602
|
+
*/
|
|
603
|
+
keptOriginal?: {
|
|
604
|
+
types: readonly string[];
|
|
605
|
+
};
|
|
585
606
|
}
|
|
586
607
|
/**
|
|
587
608
|
* A page in a private store (#1456): its entry in the store's OWN page index,
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
* classify only its own keys, `ngdpbase.addons.<slug>` and what is under it
|
|
52
52
|
* (paths inside its values included). An entry for any other key refuses the
|
|
53
53
|
* addon, since it could unhide core's or another addon's secret.
|
|
54
|
+
*
|
|
55
|
+
* A fifth, `ngdpbase.access.policies` job subjects (#1999): an enabled addon's
|
|
56
|
+
* policy may name only its own scheduled jobs, `{ "type": "job", "value":
|
|
57
|
+
* "<slug>.<job>" }`. A policy naming core's job or another addon's refuses
|
|
58
|
+
* the addon, since it would grant that job permissions its owner never chose.
|
|
54
59
|
*/
|
|
55
60
|
import { type AddonDatabaseDeclaration } from './addonDatabaseRules.js';
|
|
56
61
|
export declare const ADDONS_PATH_KEY = "ngdpbase.managers.addons-manager.addons-path";
|
|
@@ -119,6 +124,17 @@ export interface AddonSensitiveValuesLayer {
|
|
|
119
124
|
* enabled addon's entries reach the merge, so only an enabled addon is checked.
|
|
120
125
|
*/
|
|
121
126
|
export declare function addonSensitiveValuesLayer(addons: readonly AddonDefaults[]): AddonSensitiveValuesLayer;
|
|
127
|
+
export interface AddonJobPolicyLayer {
|
|
128
|
+
/** Addons whose policies name a job that is not their own, by slug, with why: never loaded. */
|
|
129
|
+
refused: Map<string, string>;
|
|
130
|
+
problems: string[];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Which jobs an addon's policies may name (#1999): its own, ids starting
|
|
134
|
+
* `<slug>.`, the prefix its job ids carry. Only an enabled addon's policies
|
|
135
|
+
* reach the merge, so only an enabled addon is checked.
|
|
136
|
+
*/
|
|
137
|
+
export declare function addonJobPolicyLayer(addons: readonly AddonDefaults[]): AddonJobPolicyLayer;
|
|
122
138
|
/** The configuration key that makes an addon site-critical (#1883). */
|
|
123
139
|
export declare function addonSiteCriticalKey(slug: string): string;
|
|
124
140
|
export interface AddonSiteCriticalLayer {
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
* classify only its own keys, `ngdpbase.addons.<slug>` and what is under it
|
|
52
52
|
* (paths inside its values included). An entry for any other key refuses the
|
|
53
53
|
* addon, since it could unhide core's or another addon's secret.
|
|
54
|
+
*
|
|
55
|
+
* A fifth, `ngdpbase.access.policies` job subjects (#1999): an enabled addon's
|
|
56
|
+
* policy may name only its own scheduled jobs, `{ "type": "job", "value":
|
|
57
|
+
* "<slug>.<job>" }`. A policy naming core's job or another addon's refuses
|
|
58
|
+
* the addon, since it would grant that job permissions its owner never chose.
|
|
54
59
|
*/
|
|
55
60
|
import fs from 'fs';
|
|
56
61
|
import path from 'path';
|
|
@@ -313,6 +318,37 @@ export function addonSensitiveValuesLayer(addons) {
|
|
|
313
318
|
}
|
|
314
319
|
return layer;
|
|
315
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Which jobs an addon's policies may name (#1999): its own, ids starting
|
|
323
|
+
* `<slug>.`, the prefix its job ids carry. Only an enabled addon's policies
|
|
324
|
+
* reach the merge, so only an enabled addon is checked.
|
|
325
|
+
*/
|
|
326
|
+
export function addonJobPolicyLayer(addons) {
|
|
327
|
+
const layer = { refused: new Map(), problems: [] };
|
|
328
|
+
for (const addon of addons) {
|
|
329
|
+
if (!addon.enabled)
|
|
330
|
+
continue;
|
|
331
|
+
const policies = addon.defaults['ngdpbase.access.policies'];
|
|
332
|
+
if (!Array.isArray(policies))
|
|
333
|
+
continue;
|
|
334
|
+
const foreign = [];
|
|
335
|
+
for (const policy of policies) {
|
|
336
|
+
const subjects = isPlainObject(policy) && Array.isArray(policy.subjects) ? policy.subjects : [];
|
|
337
|
+
for (const subject of subjects) {
|
|
338
|
+
if (!isPlainObject(subject) || subject.type !== 'job')
|
|
339
|
+
continue;
|
|
340
|
+
if (typeof subject.value !== 'string' || !subject.value.startsWith(`${addon.slug}.`))
|
|
341
|
+
foreign.push(`'${String(subject.value)}' (policy '${String(policy.id)}')`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (foreign.length === 0)
|
|
345
|
+
continue;
|
|
346
|
+
const reason = `its policies name the job(s) ${foreign.join(', ')}, which are not its own; an add-on's policy names only its own jobs, ${addon.slug}.<job>`;
|
|
347
|
+
layer.refused.set(addon.slug, reason);
|
|
348
|
+
layer.problems.push(`${addonProblemSubject(addon.slug, addon.enabled)}: ${reason}; the add-on is not loaded`);
|
|
349
|
+
}
|
|
350
|
+
return layer;
|
|
351
|
+
}
|
|
316
352
|
/** The configuration key that makes an addon site-critical (#1883). */
|
|
317
353
|
export function addonSiteCriticalKey(slug) {
|
|
318
354
|
return `ngdpbase.addons.${slug}.site-critical`;
|
|
@@ -402,9 +438,10 @@ export function mergeWithAddonLayer(shipped, addons, custom) {
|
|
|
402
438
|
const databaseLayer = addonDatabaseLayer(addons, custom);
|
|
403
439
|
const criticalLayer = addonSiteCriticalLayer(addons, custom);
|
|
404
440
|
const sensitiveLayer = addonSensitiveValuesLayer(addons);
|
|
441
|
+
const jobPolicyLayer = addonJobPolicyLayer(addons);
|
|
405
442
|
const siteCritical = criticalLayer.siteCritical;
|
|
406
443
|
const refused = new Map(layer.refused);
|
|
407
|
-
for (const [slug, reason] of [...databaseLayer.refused, ...criticalLayer.refused, ...sensitiveLayer.refused])
|
|
444
|
+
for (const [slug, reason] of [...databaseLayer.refused, ...criticalLayer.refused, ...sensitiveLayer.refused, ...jobPolicyLayer.refused])
|
|
408
445
|
refused.set(slug, refused.has(slug) ? `${refused.get(slug)}; ${reason}` : reason);
|
|
409
446
|
const databases = databaseLayer.declarations.map((d) => ({ ...d, enabled: d.enabled && !refused.has(d.declaredBy) }));
|
|
410
447
|
const withAddons = addons
|
|
@@ -418,14 +455,14 @@ export function mergeWithAddonLayer(shipped, addons, custom) {
|
|
|
418
455
|
return deepMergeObjects(acc, rest);
|
|
419
456
|
}, shipped);
|
|
420
457
|
if (Object.keys(layer.entries).length === 0) {
|
|
421
|
-
return { merged: deepMergeConfigs(withAddons, custom), problems: [...layer.problems, ...databaseLayer.problems, ...criticalLayer.problems, ...sensitiveLayer.problems], refused, databases, siteCritical };
|
|
458
|
+
return { merged: deepMergeConfigs(withAddons, custom), problems: [...layer.problems, ...databaseLayer.problems, ...criticalLayer.problems, ...sensitiveLayer.problems, ...jobPolicyLayer.problems], refused, databases, siteCritical };
|
|
422
459
|
}
|
|
423
460
|
const shippedCategories = isPlainObject(withAddons[SYSTEM_CATEGORY_KEY]) ? withAddons[SYSTEM_CATEGORY_KEY] : {};
|
|
424
461
|
const layered = { ...withAddons, [SYSTEM_CATEGORY_KEY]: deepMergeObjects(shippedCategories, layer.entries) };
|
|
425
462
|
const merged = deepMergeObjects(layered, custom);
|
|
426
463
|
const stood = ownerDeclarationsStand(merged[SYSTEM_CATEGORY_KEY], layer, customCategories);
|
|
427
464
|
merged[SYSTEM_CATEGORY_KEY] = stood.categories;
|
|
428
|
-
return { merged: merged, problems: [...layer.problems, ...stood.problems, ...databaseLayer.problems, ...criticalLayer.problems, ...sensitiveLayer.problems], refused, databases, siteCritical };
|
|
465
|
+
return { merged: merged, problems: [...layer.problems, ...stood.problems, ...databaseLayer.problems, ...criticalLayer.problems, ...sensitiveLayer.problems, ...jobPolicyLayer.problems], refused, databases, siteCritical };
|
|
429
466
|
}
|
|
430
467
|
/**
|
|
431
468
|
* The full merge for a caller with the two files already read (the manager,
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
* - `registerShim` and `registerReceiver` (#1979, ICE) register into the import
|
|
19
19
|
* engine under this add-on's slug, and only the names its manifest declares;
|
|
20
20
|
* - the egress tools (`resolveEgressPolicy`, `validateUrl`,
|
|
21
|
-
* `createGuardedElasticsearchClient`, `refusedNodeMessage`)
|
|
22
|
-
* are core's own pure functions, handed on
|
|
23
|
-
* imports them from `dist/` (#1813);
|
|
21
|
+
* `createGuardedElasticsearchClient`, `refusedNodeMessage`), `isAtLeast`
|
|
22
|
+
* and `zonedParts` (#1998) are core's own pure functions, handed on
|
|
23
|
+
* unchanged, so an add-on never imports them from `dist/` (#1813);
|
|
24
|
+
* - `keyedSerial()` returns a new `KeyedSerial`, core's own per-key queue, so
|
|
25
|
+
* an add-on's manager changes each of its records one at a time (#2065);
|
|
24
26
|
* - the object is `Object.freeze`d, so no add-on can replace a value.
|
|
25
27
|
*
|
|
26
28
|
* Additive only: nothing here is removed or renamed without a major release.
|
|
@@ -36,6 +38,8 @@ import { resolveEgressPolicy } from '../http/egressPolicy.js';
|
|
|
36
38
|
import { validateUrl } from '../http/ssrf.js';
|
|
37
39
|
import { createGuardedElasticsearchClient, refusedNodeMessage } from '../http/guardedElasticsearch.js';
|
|
38
40
|
import { isAtLeast } from './version.js';
|
|
41
|
+
import { zonedParts } from './availability.js';
|
|
42
|
+
import { KeyedSerial } from './keyedSerial.js';
|
|
39
43
|
import { recordAuditEvent } from './auditEvents.js';
|
|
40
44
|
import { addonAuditEventName } from './auditEventNames.js';
|
|
41
45
|
import type { IceReceiver, IceShim } from '../ice/types.js';
|
|
@@ -80,6 +84,20 @@ export interface AddonHost {
|
|
|
80
84
|
readonly refusedNodeMessage: typeof refusedNodeMessage;
|
|
81
85
|
/** Whether an `x.y.z` version is at least another; throws on a version that is not `x.y.z`. */
|
|
82
86
|
readonly isAtLeast: typeof isAtLeast;
|
|
87
|
+
/**
|
|
88
|
+
* #1998: an instant in a named time zone, as its local parts: `year`,
|
|
89
|
+
* `month` (1–12), `date` (`YYYY-MM-DD`), `day` (weekday name) and `minutes`
|
|
90
|
+
* since midnight. A scheduled job passes its `ctx.slot` and
|
|
91
|
+
* `BackgroundJobManager.scheduleTimeZone()` to learn which local month or
|
|
92
|
+
* day the slot falls in. An unknown zone answers in UTC.
|
|
93
|
+
*/
|
|
94
|
+
readonly zonedParts: typeof zonedParts;
|
|
95
|
+
/**
|
|
96
|
+
* #2065: a new `KeyedSerial` (core's own) at each call; take one per manager.
|
|
97
|
+
* Run each read-modify-write of one of your records inside `run(recordId, fn)`,
|
|
98
|
+
* the read included. Not re-entrant: `fn` must not `run` the same key again.
|
|
99
|
+
*/
|
|
100
|
+
readonly keyedSerial: () => KeyedSerial;
|
|
83
101
|
/**
|
|
84
102
|
* #1979 (ICE, D6): register an import shim, as `<slug>.<shim.name>`. Call it
|
|
85
103
|
* in `register`. Refused when the manifest's `shims` does not name it.
|