@jwilleke/ngdpbase-types 5.30.0 → 5.31.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/README.md +1 -1
- package/lib/packages/ngdpbase-types/index.d.ts +5 -4
- package/lib/src/context/JobContext.d.ts +9 -0
- package/lib/src/context/JobContext.js +3 -1
- package/lib/src/context/bootActions.js +2 -0
- 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/BackgroundJobManager.d.ts +17 -0
- package/lib/src/managers/IdentityManager.d.ts +64 -4
- package/lib/src/managers/ImportManager.d.ts +2 -5
- package/lib/src/managers/OrganizationManager.d.ts +9 -5
- package/lib/src/managers/PolicyEvaluator.d.ts +7 -0
- package/lib/src/managers/UserManager.d.ts +13 -0
- package/lib/src/providers/BaseAttachmentProvider.d.ts +2 -8
- package/lib/src/providers/BasicAttachmentProvider.d.ts +3 -8
- package/lib/src/security/PolicyDecisionPoint.d.ts +8 -0
- package/lib/src/security/PolicyInformationPoint.d.ts +1 -1
- package/lib/src/types/Person.d.ts +39 -6
- 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/availability.d.ts +120 -0
- package/lib/src/utils/availability.js +174 -0
- package/lib/src/utils/keyedSerial.js +41 -0
- package/lib/src/utils/roleGrants.d.ts +18 -3
- package/lib/src/utils/sessionSecurity.d.ts +116 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ import type { AddonModule, AddonHost, WikiEngine } from '@jwilleke/ngdpbase-type
|
|
|
11
11
|
import { buildAddonHost, openAddonTestDatabase, toCsv, formatters } from '@jwilleke/ngdpbase-types/testing';
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
- The main entry is types only. Importing it at run time fails, on purpose. An add-on takes core's values from `host` (`AddonHost`: the request and audit helpers, the logger, `guardedFetch`, the egress tools, `isAtLeast`) and a manager's job from `engine.getManager` (such as `PageManager.pageUrl` or `PolicyDecisionPoint.usersHolding`); it never imports core's values ([#1813](https://github.com/jwilleke/ngdpbase/issues/1813)).
|
|
14
|
+
- The main entry is types only. Importing it at run time fails, on purpose. An add-on takes core's values from `host` (`AddonHost`: the request and audit helpers, the logger, `guardedFetch`, the egress tools, `isAtLeast`, `zonedParts`) and a manager's job from `engine.getManager` (such as `PageManager.pageUrl` or `PolicyDecisionPoint.usersHolding`); it never imports core's values ([#1813](https://github.com/jwilleke/ngdpbase/issues/1813)).
|
|
15
15
|
- `/testing` is for tests. `buildAddonHost(slug, defaults, setEnabledForPerson?, engine?)` is the real host; `host.setEnabledForPerson` calls the setter you pass (it refuses without one), and `engine` names the system principal `host.systemContext(reason)` uses (core's fallback without one). `openAddonTestDatabase(owner, { migrations, ledgerTable })` is the handle `openAddonDatabase` returns, in memory. `toCsv` is `ExportManager.toCsv`. `formatters` is a plugin's `context.formatters`.
|
|
16
16
|
|
|
17
17
|
The add-on guide is [docs/guides/addons-developer-guide.md](https://github.com/jwilleke/ngdpbase/blob/master/docs/guides/addons-developer-guide.md).
|
|
@@ -23,8 +23,9 @@ export type { PermissionSubject } from '../../src/managers/UserManager.js';
|
|
|
23
23
|
export type { Decision } from '../../src/types/Policy.js';
|
|
24
24
|
export type { PluginContext, PluginParams, PluginFormatters } from '../../src/managers/PluginManager.js';
|
|
25
25
|
export type { SimplePlugin } from '../../src/plugins/types.js';
|
|
26
|
+
export type { KeyedSerial } from '../../src/utils/keyedSerial.js';
|
|
26
27
|
export type { AuditEvent } from '../../src/utils/auditEvents.js';
|
|
27
|
-
export type { JobRunContext } from '../../src/managers/BackgroundJobManager.js';
|
|
28
|
+
export type { default as BackgroundJobManager, JobDefinition, JobResult, JobRunContext } from '../../src/managers/BackgroundJobManager.js';
|
|
28
29
|
export type { default as DatabaseManager, AddonDatabaseOptions } from '../../src/managers/DatabaseManager.js';
|
|
29
30
|
export type { Migration } from '../../src/providers/sqliteMigrations.js';
|
|
30
31
|
export type { AddonDatabaseDeclaration, DatabaseEncrypt } from '../../src/utils/addonDatabaseRules.js';
|
|
@@ -36,16 +37,16 @@ export type { PageAction } from '../../src/utils/pageUrl.js';
|
|
|
36
37
|
export type { default as ConsentManager, ConsentScopeStatus } from '../../src/managers/ConsentManager.js';
|
|
37
38
|
export type { AddonConsentRegistration, ConsentDocument, ConsentRecord, ConsentRequiredError, ConsentScope, ConsentSeen, ConsentStatus } from '../../src/types/Consent.js';
|
|
38
39
|
export type { default as ImportManager } from '../../src/managers/ImportManager.js';
|
|
39
|
-
export type { IceShim, IceReceiver, IceItem, IceKind, IceWarning, ShimDirection, ShimAccepts, ShimTools, ShimReadResult, ReceiveInfo, ReceiveOutcome, IceCitation, KeptOriginal, OriginalChoice, IceOriginalChoices, IceUpload, IceSource, IceRunRequest, IceRunReport, IceFileReport, IceFileStatus } from '../../src/ice/types.js';
|
|
40
|
+
export type { IceShim, IceReceiver, IceItem, IceKind, IceWarning, ShimDirection, ShimAccepts, ShimTools, ShimReadResult, ReceiveInfo, ReceiveOutcome, IceCitation, KeptOriginal, OriginalChoice, IceOriginalChoices, OriginalKeep, IceUpload, IceSource, IceRunRequest, IceRunReport, IceFileReport, IceFileStatus } from '../../src/ice/types.js';
|
|
40
41
|
export type { default as UserManager, UserUpdateInput, UserUpdateFn } from '../../src/managers/UserManager.js';
|
|
41
42
|
export type { User, UserPreferences } from '../../src/types/User.js';
|
|
42
43
|
export type { default as AuthManager, InviteOutcome, InvitationView } from '../../src/managers/AuthManager.js';
|
|
43
|
-
export type { default as IdentityManager, LinkOfferOutcome } from '../../src/managers/IdentityManager.js';
|
|
44
|
+
export type { default as IdentityManager, LinkOfferOutcome, AffiliationQuery, IdentitySearch, IdentityMatch } from '../../src/managers/IdentityManager.js';
|
|
44
45
|
export type {
|
|
45
46
|
/** @deprecated Renamed `IdentityManager` (#2038); this alias goes after one release. */
|
|
46
47
|
default as PersonManager } from '../../src/managers/IdentityManager.js';
|
|
47
48
|
export type { default as OrganizationManager } from '../../src/managers/OrganizationManager.js';
|
|
48
|
-
export type { Person, PersonAffiliation, PersonInput, PersonUpdate } from '../../src/types/Person.js';
|
|
49
|
+
export type { Person, PersonAffiliation, LegacyPersonAffiliation, PersonChange, PersonInput, PersonUpdate } from '../../src/types/Person.js';
|
|
49
50
|
export type { Organization, OrganizationInput, OrganizationUpdate, ContactPoint, ContactPointEntry, PostalAddress } from '../../src/types/Organization.js';
|
|
50
51
|
export type { ContactRefusedError } from '../../src/utils/contactAccess.js';
|
|
51
52
|
export type { default as RoleManager } from '../../src/managers/RoleManager.js';
|
|
@@ -71,6 +71,15 @@ export interface JobContext {
|
|
|
71
71
|
requestedAt: string;
|
|
72
72
|
/** Why, for origins with no person to ask. Free text, for the audit record. */
|
|
73
73
|
reason?: string;
|
|
74
|
+
/**
|
|
75
|
+
* The scheduled job this run acts as (#1999): its id. Set by
|
|
76
|
+
* BackgroundJobManager on a scheduled slot's context, never on a run
|
|
77
|
+
* started by hand. When a policy names the job (`{ "type": "job", "value":
|
|
78
|
+
* "<id>" }`), its decisions are made as `job:<id>`, holding only what such
|
|
79
|
+
* policies grant; when none does, as the system principal, as before. Only
|
|
80
|
+
* the system principal's context can act as a job.
|
|
81
|
+
*/
|
|
82
|
+
job?: string;
|
|
74
83
|
}
|
|
75
84
|
/** The shape a request-bound context exposes; both WikiContext and ApiContext satisfy it. */
|
|
76
85
|
export interface RequestIdentity {
|
|
@@ -147,7 +147,9 @@ export function toPermissionSubject(ctx) {
|
|
|
147
147
|
resolveRolesNow: true,
|
|
148
148
|
...(ctx.viaToken ? { viaToken: ctx.viaToken } : {}),
|
|
149
149
|
...(ctx.viaShare ? { viaShare: ctx.viaShare } : {}),
|
|
150
|
-
...(ctx.viaProxy ? { viaProxy: ctx.viaProxy } : {})
|
|
150
|
+
...(ctx.viaProxy ? { viaProxy: ctx.viaProxy } : {}),
|
|
151
|
+
// #1999: the job a scheduled run acts as, for the PIP to resolve.
|
|
152
|
+
...(ctx.job ? { job: ctx.job } : {})
|
|
151
153
|
};
|
|
152
154
|
}
|
|
153
155
|
/** One line for a log or an audit record: who, from where, and how. */
|
|
@@ -64,6 +64,8 @@ export function attributedTo(ctx) {
|
|
|
64
64
|
origin: ctx.origin,
|
|
65
65
|
reason: ctx.reason ?? null,
|
|
66
66
|
requestedAt: ctx.requestedAt,
|
|
67
|
+
// #1999: a scheduled run names the job it acts as on every row it records.
|
|
68
|
+
...(ctx.job ? { job: ctx.job } : {}),
|
|
67
69
|
// #2059: the delegation a request-origin job was queued under, by kind,
|
|
68
70
|
// with the same precedence as a request's own record (`actorOf`).
|
|
69
71
|
...(ctx.viaToken ? { delegated: 'token' } : ctx.viaShare ? { delegated: 'share' } : proxyMeta)
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import { type ZipReadLimits } from '../utils/zipArchive.js';
|
|
2
2
|
import type { IceUpload } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A file's media type by its extension (#2061): how the engine labels a file
|
|
5
|
+
* it unpacks from a zip, and how the folder import labels a file it reads.
|
|
6
|
+
* A typed table, not configuration: it states what each format is, which is
|
|
7
|
+
* not a site's choice. What a site decides is which types it takes, and that
|
|
8
|
+
* is already its attachment allow-list (and a receiver's `originals.types`).
|
|
9
|
+
*/
|
|
10
|
+
export declare const MEDIA_TYPE_BY_EXTENSION: Readonly<Record<string, string>>;
|
|
11
|
+
/** A file's media type by its extension alone: {@link MEDIA_TYPE_BY_EXTENSION}, else `application/octet-stream`. */
|
|
12
|
+
export declare function mediaTypeByExtension(name: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* The media type of a file unpacked from a zip (#2061): its extension's,
|
|
15
|
+
* checked against its bytes where that is cheap. JSON must start with `{` or
|
|
16
|
+
* `[` (NDJSON with `{`), XML with `<`; one that does not is
|
|
17
|
+
* `application/octet-stream`. Then the upload door's own rule
|
|
18
|
+
* (`resolveUploadMime`): bytes that show a type decide (an XML file with an
|
|
19
|
+
* `<?xml` prolog is `text/xml`), and a claim they do not bear out is
|
|
20
|
+
* `application/octet-stream`. So the type a receiver is handed is the type
|
|
21
|
+
* the door stores.
|
|
22
|
+
*/
|
|
23
|
+
export declare function memberMediaType(name: string, bytes: Buffer): string;
|
|
3
24
|
/** A file's fingerprint: SHA-256 of its bytes, hex. */
|
|
4
25
|
export declare function fingerprint(bytes: Buffer): string;
|
|
5
26
|
/**
|
|
@@ -15,8 +36,9 @@ export declare function isNoise(filePath: string, patterns: readonly string[]):
|
|
|
15
36
|
export declare function isZip(upload: IceUpload): boolean;
|
|
16
37
|
/**
|
|
17
38
|
* Unpack the zips among the uploads into the files they hold, named
|
|
18
|
-
* `archive.zip/inner/path
|
|
19
|
-
*
|
|
39
|
+
* `archive.zip/inner/path`, each with its media type ({@link memberMediaType},
|
|
40
|
+
* #2061). A nested zip is unpacked too. Everything else is passed through,
|
|
41
|
+
* with the type its request gave it. Limits are per archive (`ZipReadLimits`), so a small
|
|
20
42
|
* archive that expands enormously stops at the budget. An archive that cannot
|
|
21
43
|
* be read is returned in `unreadable`, with why; the rest go on.
|
|
22
44
|
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The allowance a keep at upload carries to the upload door (#2061).
|
|
3
|
+
*
|
|
4
|
+
* A receiver that keeps originals in its own encrypted vault (phr) declares
|
|
5
|
+
* the media types it keeps (`originals.types`). The site's attachment
|
|
6
|
+
* allow-list does not name them (FHIR JSON, C-CDA XML), so the door would
|
|
7
|
+
* refuse the keep. The exemption is narrow, and lives here, not in a flag:
|
|
8
|
+
*
|
|
9
|
+
* - the engine opens one {@link OriginalKeep} for one receiver, one user and
|
|
10
|
+
* one `keepOriginal` call, and closes it when the call returns;
|
|
11
|
+
* - the receiver passes it to `AttachmentManager.uploadAttachment` as its
|
|
12
|
+
* `keep` option, unchanged;
|
|
13
|
+
* - the door honours it only while it is open, and only for a file going
|
|
14
|
+
* into that user's own vault of a system-category the receiver's add-on
|
|
15
|
+
* declares ({@link keepAllowsInto}). Anywhere else it changes nothing.
|
|
16
|
+
*
|
|
17
|
+
* An object the engine did not open is never honoured, whatever it says.
|
|
18
|
+
*/
|
|
19
|
+
import type { ActorContext } from '../context/ActorContext.js';
|
|
20
|
+
import type { OriginalKeep, RegisteredReceiver } from './types.js';
|
|
21
|
+
/** Open a keep for one call of `receiver.keepOriginal` with `ctx`. Close it with {@link closeOriginalKeep}. */
|
|
22
|
+
export declare function openOriginalKeep(receiver: RegisteredReceiver, ctx: ActorContext): OriginalKeep;
|
|
23
|
+
/** The call is over: the keep is honoured no more. */
|
|
24
|
+
export declare function closeOriginalKeep(keep: OriginalKeep): void;
|
|
25
|
+
/** Where an upload is going, as the door resolved it. */
|
|
26
|
+
export interface KeepDestination {
|
|
27
|
+
/** Who is uploading: the request's username. */
|
|
28
|
+
uploader: string;
|
|
29
|
+
/** The vault's owner. */
|
|
30
|
+
owner: string;
|
|
31
|
+
/** The add-on whose system-category declares the vault (`StoreKind.owner`); null when no category does. */
|
|
32
|
+
vaultAddon: string | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Does this keep apply to this upload? Only an open keep, for a file going
|
|
36
|
+
* into its user's own vault of a system-category its receiver's add-on
|
|
37
|
+
* declares, uploaded by that user. A `false` leaves the upload exactly as
|
|
38
|
+
* any other: the site's allow-list decides.
|
|
39
|
+
*/
|
|
40
|
+
export declare function keepAllowsInto(keep: OriginalKeep | undefined, to: KeepDestination): boolean;
|
package/lib/src/ice/types.d.ts
CHANGED
|
@@ -130,6 +130,34 @@ export interface IceOriginalChoices {
|
|
|
130
130
|
allowed: OriginalChoice[];
|
|
131
131
|
/** One of `allowed`. Default: `ngdpbase.import.engine.default-original` when allowed, else the first allowed. */
|
|
132
132
|
default?: OriginalChoice;
|
|
133
|
+
/**
|
|
134
|
+
* The media types this receiver keeps (#2061), exactly, e.g.
|
|
135
|
+
* `['application/json', 'application/x-ndjson', 'text/xml']`: no wildcards.
|
|
136
|
+
* For a keep at upload into the receiver's own vault, the upload door
|
|
137
|
+
* allows these even when the site's attachment allow-list does not
|
|
138
|
+
* ({@link OriginalKeep}). Every other upload still uses the site's list.
|
|
139
|
+
*/
|
|
140
|
+
types?: string[];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* What a keep at upload carries to the upload door (#2061): the engine opens
|
|
144
|
+
* one for one receiver, one user and one call of its `keepOriginal`, and
|
|
145
|
+
* closes it when the call returns. Pass it on unchanged as the upload's
|
|
146
|
+
* `keep` option. The door honours only one the engine has open, and only
|
|
147
|
+
* for a file going into this user's own vault of a system-category the
|
|
148
|
+
* receiver's add-on declares: there it allows `types` beyond the site's
|
|
149
|
+
* attachment allow-list, and the file is always served as a download.
|
|
150
|
+
* Anywhere else it changes nothing.
|
|
151
|
+
*/
|
|
152
|
+
export interface OriginalKeep {
|
|
153
|
+
/** The receiver keeping, `<slug>.<name>`. */
|
|
154
|
+
readonly receiver: string;
|
|
155
|
+
/** The add-on that registered it: only a vault its own system-category declares. */
|
|
156
|
+
readonly addon: string;
|
|
157
|
+
/** The user the upload is for: only their own vault. */
|
|
158
|
+
readonly user: string;
|
|
159
|
+
/** The receiver's `originals.types`. */
|
|
160
|
+
readonly types: readonly string[];
|
|
133
161
|
}
|
|
134
162
|
/** A citation to the origin (D11): the receiver renders it, as a footnote on a page. */
|
|
135
163
|
export interface IceCitation {
|
|
@@ -180,8 +208,12 @@ export interface IceReceiver {
|
|
|
180
208
|
/**
|
|
181
209
|
* Keep the original file itself (D11: phr keeps originals in its own
|
|
182
210
|
* store). Without this, `keep` hands the original to the files receiver.
|
|
211
|
+
* `original.mediaType` is the engine's (#2061): the request's type for a
|
|
212
|
+
* file uploaded on its own, the engine's for one unpacked from a zip.
|
|
213
|
+
* `keep` is given for a keep at upload (`keepsEncrypted`): pass it to the
|
|
214
|
+
* upload door unchanged, as the upload's `keep` option.
|
|
183
215
|
*/
|
|
184
|
-
keepOriginal?(original: IceItem, ctx: ActorContext): Promise<ReceiveOutcome>;
|
|
216
|
+
keepOriginal?(original: IceItem, ctx: ActorContext, keep?: OriginalKeep): Promise<ReceiveOutcome>;
|
|
185
217
|
/**
|
|
186
218
|
* `keepOriginal` writes into an encrypted store (phr's vault), which only a
|
|
187
219
|
* request holding the user's unlocked key can write (#2034). The engine then
|
|
@@ -10,7 +10,8 @@ import type { WikiEngine } from '../types/WikiEngine.js';
|
|
|
10
10
|
import type { CatalogSource, CatalogQuery, CatalogPage, CreativeWork, SchemaType, RebuildOpts } from '../types/Schema.js';
|
|
11
11
|
import type { User } from '../providers/BaseAttachmentProvider.js';
|
|
12
12
|
import { type AssetQuery, type AssetRecord } from '../types/Asset.js';
|
|
13
|
-
import type { StoreFileEntry, StoreFileLocation } from '../types/Provider.js';
|
|
13
|
+
import type { StoreFileEntry, StoreFileInput, StoreFileLocation } from '../types/Provider.js';
|
|
14
|
+
import type { OriginalKeep } from '../ice/types.js';
|
|
14
15
|
/**
|
|
15
16
|
* Base attachment provider interface
|
|
16
17
|
*/
|
|
@@ -44,13 +45,7 @@ interface BaseAttachmentProvider {
|
|
|
44
45
|
getProviderInfo(): {
|
|
45
46
|
features: string[];
|
|
46
47
|
};
|
|
47
|
-
storeFileInStore(location: StoreFileLocation, bytes: Buffer, file:
|
|
48
|
-
originalName: string;
|
|
49
|
-
mimeType: string;
|
|
50
|
-
description: string;
|
|
51
|
-
author?: string;
|
|
52
|
-
pageName?: string;
|
|
53
|
-
}): Promise<StoreFileEntry>;
|
|
48
|
+
storeFileInStore(location: StoreFileLocation, bytes: Buffer, file: StoreFileInput): Promise<StoreFileEntry>;
|
|
54
49
|
getFileInStore(location: StoreFileLocation, id: string): Promise<{
|
|
55
50
|
entry: StoreFileEntry;
|
|
56
51
|
bytes: Buffer;
|
|
@@ -104,6 +99,15 @@ export interface UploadOptions {
|
|
|
104
99
|
private?: boolean;
|
|
105
100
|
/** Store id when private with no private page; default from ConfigurationManager defaultstoreid */
|
|
106
101
|
store?: string;
|
|
102
|
+
/**
|
|
103
|
+
* An import's keep at upload (#2061): the `OriginalKeep` the engine handed
|
|
104
|
+
* the receiver's `keepOriginal`, passed on unchanged. Honoured only while
|
|
105
|
+
* the engine holds it open, for a file going into its user's own vault of a
|
|
106
|
+
* system-category the receiver's add-on declares: there the receiver's
|
|
107
|
+
* declared types are allowed beyond the site's allow-list, and the file is
|
|
108
|
+
* served as a download. Anywhere else it changes nothing.
|
|
109
|
+
*/
|
|
110
|
+
keep?: OriginalKeep;
|
|
107
111
|
}
|
|
108
112
|
/**
|
|
109
113
|
* Mention object (WebPage reference)
|
|
@@ -282,6 +282,14 @@ declare class BackgroundJobManager extends BaseManager {
|
|
|
282
282
|
private compileScheduled;
|
|
283
283
|
/** A run's time limit: the job's own, else `fallback`, capped by `ngdpbase.jobs.max-timeout-ms` when set. */
|
|
284
284
|
private timeoutFor;
|
|
285
|
+
/**
|
|
286
|
+
* The time zone the scheduler runs a schedule in when the schedule names
|
|
287
|
+
* none (#1998): `ngdpbase.default.timezone`, UTC when it is not set. Read
|
|
288
|
+
* each call, as the schedules are. An add-on's job passes it, with its
|
|
289
|
+
* `ctx.slot`, to `host.zonedParts` to learn the slot's local month or day.
|
|
290
|
+
* A schedule that names its own `tz` runs in that zone instead.
|
|
291
|
+
*/
|
|
292
|
+
scheduleTimeZone(): string;
|
|
285
293
|
private compile;
|
|
286
294
|
/**
|
|
287
295
|
* `ngdpbase.jobs.<id>.schedule` in place of the registered schedule. One
|
|
@@ -319,6 +327,15 @@ declare class BackgroundJobManager extends BaseManager {
|
|
|
319
327
|
private startDrain;
|
|
320
328
|
/** Every scheduled job, its state and its recent history. */
|
|
321
329
|
listScheduledJobs(): Promise<ScheduledJobView[]>;
|
|
330
|
+
/**
|
|
331
|
+
* Every scheduled job's id and display name, as `listScheduledJobs` names
|
|
332
|
+
* them, without reading their state — for the admin roles page's Jobs part
|
|
333
|
+
* (#2067).
|
|
334
|
+
*/
|
|
335
|
+
scheduledJobNames(): Array<{
|
|
336
|
+
id: string;
|
|
337
|
+
displayName: string;
|
|
338
|
+
}>;
|
|
322
339
|
/**
|
|
323
340
|
* One scheduled job as `listScheduledJobs` shows it, without its run
|
|
324
341
|
* history, or null when no job of that id is scheduled. For a manager that
|
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
import BaseManager, { type BackupFolder, type ManagerStats } from './BaseManager.js';
|
|
2
2
|
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
3
|
-
import type { Person,
|
|
3
|
+
import type { Person, PersonAffiliation, PersonChange, PersonInput } from '../types/Person.js';
|
|
4
4
|
import type { PersonProvider } from '../types/PersonProvider.js';
|
|
5
5
|
import { type ActorContext } from '../context/ActorContext.js';
|
|
6
|
+
/** What `search(ctx, { affiliation })` looks for (#2039). */
|
|
7
|
+
export interface AffiliationQuery {
|
|
8
|
+
/** The organization, by its `@id` (or `{ '@id' }`). */
|
|
9
|
+
memberOf: string | {
|
|
10
|
+
'@id': string;
|
|
11
|
+
};
|
|
12
|
+
/** The title, compared without case or surrounding spaces. Left out: any title, or none. */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** True (the default): only affiliations held today, the first and last day included. False: any, past or future too. */
|
|
15
|
+
current?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** A search by affiliation (#2039). `limit` as for a text search; 0 is no limit. */
|
|
18
|
+
export interface IdentitySearch {
|
|
19
|
+
affiliation: AffiliationQuery;
|
|
20
|
+
limit?: number;
|
|
21
|
+
}
|
|
22
|
+
/** One identity a search by affiliation found: a user or a contact, and the affiliations that matched. */
|
|
23
|
+
export interface IdentityMatch {
|
|
24
|
+
identity: Person;
|
|
25
|
+
/** True for a user (an identity with credentials), false for a contact. */
|
|
26
|
+
hasCredentials: boolean;
|
|
27
|
+
affiliations: PersonAffiliation[];
|
|
28
|
+
}
|
|
6
29
|
/** What the confirmation page shows: the person the link would join. */
|
|
7
30
|
export interface LinkOfferDetails {
|
|
8
31
|
personName: string | null;
|
|
@@ -52,6 +75,7 @@ declare class IdentityManager extends BaseManager {
|
|
|
52
75
|
private storageDir;
|
|
53
76
|
private byIdentifierCache;
|
|
54
77
|
private byIdCache;
|
|
78
|
+
private allCache;
|
|
55
79
|
private metricsRef;
|
|
56
80
|
/** Sign-up link offers by token (#1930). In memory: a restart drops them, and an administrator links by hand. */
|
|
57
81
|
private linkOffers;
|
|
@@ -59,6 +83,18 @@ declare class IdentityManager extends BaseManager {
|
|
|
59
83
|
private readonly accountRecords;
|
|
60
84
|
constructor(engine: WikiEngine);
|
|
61
85
|
initialize(config?: Record<string, unknown>): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Once, at start-up (#2039): every identity record still holding an
|
|
88
|
+
* affiliation in the old `{ organization, role? }` shape is rewritten as
|
|
89
|
+
* schema.org `OrganizationRole` (organization → `memberOf`, role →
|
|
90
|
+
* `roleName`), each rewrite recorded as `identity-manage` (`person-update`,
|
|
91
|
+
* field `affiliation`) under the system context. Nothing is dropped: any
|
|
92
|
+
* other field an old entry carries is kept on it. A record with no old entry
|
|
93
|
+
* is not touched, so a second start finds nothing to do. A record that
|
|
94
|
+
* cannot be rewritten is logged and left as it is; reads convert it anyway.
|
|
95
|
+
* Runs before any request, so nothing else writes these records meanwhile.
|
|
96
|
+
*/
|
|
97
|
+
private convertLegacyAffiliations;
|
|
62
98
|
/** `ngdpbase.application.persons.storagedir`, resolved: where the records are. */
|
|
63
99
|
private configuredStorageDir;
|
|
64
100
|
/**
|
|
@@ -79,6 +115,8 @@ declare class IdentityManager extends BaseManager {
|
|
|
79
115
|
*/
|
|
80
116
|
getByIdentifier(ctx: ActorContext, identifier: string): Promise<Person | null>;
|
|
81
117
|
private cachedById;
|
|
118
|
+
/** Every record, affiliations in the current shape, read from the provider once per write (#2039). */
|
|
119
|
+
private allRecords;
|
|
82
120
|
private cachedByIdentifier;
|
|
83
121
|
/** #1006: how many people this instance holds. Count only, never records. */
|
|
84
122
|
getManagerStats(): Promise<ManagerStats>;
|
|
@@ -93,11 +131,20 @@ declare class IdentityManager extends BaseManager {
|
|
|
93
131
|
* `query`, ignoring case; an empty query matches everyone. `accountless`
|
|
94
132
|
* narrows to persons with no account. At most `limit` results
|
|
95
133
|
* (`ngdpbase.application.persons.search.limit` by default).
|
|
134
|
+
*
|
|
135
|
+
* Or by affiliation (#2039): `search(ctx, { affiliation: { memberOf, title?,
|
|
136
|
+
* current? } })` finds every identity, users and contacts alike, with an
|
|
137
|
+
* affiliation to that organization: with that title when one is given, and
|
|
138
|
+
* held today (the first and last day included) unless `current` is false.
|
|
139
|
+
* Each match says whether it has credentials and which affiliations matched.
|
|
140
|
+
* Checked like any read of others' records: `identity-read`.
|
|
96
141
|
*/
|
|
97
142
|
search(ctx: ActorContext, query: string, options?: {
|
|
98
143
|
limit?: number;
|
|
99
144
|
accountless?: boolean;
|
|
100
145
|
}): Promise<Person[]>;
|
|
146
|
+
search(ctx: ActorContext, query: IdentitySearch): Promise<IdentityMatch[]>;
|
|
147
|
+
private searchByAffiliation;
|
|
101
148
|
/**
|
|
102
149
|
* Create a person. With no `identifier` the person has no account and gets
|
|
103
150
|
* `person:<uuid>`. An `identifier` must be an existing account that has no
|
|
@@ -108,8 +155,13 @@ declare class IdentityManager extends BaseManager {
|
|
|
108
155
|
* Change a person's fields, affiliations included. `@id` and `identifier`
|
|
109
156
|
* never change here. The caller's own identity record under
|
|
110
157
|
* `profile-manage`, anyone else's under `identity-manage` (#2018).
|
|
158
|
+
*
|
|
159
|
+
* Affiliations (#2039): one with the site's own organization is a fact, so
|
|
160
|
+
* adding, changing or removing one also asks `identity-manage`, even on the
|
|
161
|
+
* caller's own record; one with any other organization is a claim, which a
|
|
162
|
+
* user may set on their own record under `profile-manage`.
|
|
111
163
|
*/
|
|
112
|
-
update(ctx: ActorContext, id: string, patch:
|
|
164
|
+
update(ctx: ActorContext, id: string, patch: PersonChange): Promise<Person | null>;
|
|
113
165
|
delete(ctx: ActorContext, id: string): Promise<boolean>;
|
|
114
166
|
/**
|
|
115
167
|
* An administrator joins a person who has no account to an account (#1930),
|
|
@@ -150,10 +202,18 @@ declare class IdentityManager extends BaseManager {
|
|
|
150
202
|
*/
|
|
151
203
|
private join;
|
|
152
204
|
/**
|
|
153
|
-
* Each affiliation
|
|
154
|
-
*
|
|
205
|
+
* Each affiliation is a schema.org `OrganizationRole` (#2039): `memberOf` an
|
|
206
|
+
* organization that exists, an optional `roleName` (the title, trimmed free
|
|
207
|
+
* text) and optional `startDate` / `endDate` (`YYYY-MM-DD`, the start not
|
|
208
|
+
* after the end; a null end is an ongoing one, stored without it). An entry
|
|
209
|
+
* in the old `{ organization, role? }` shape is converted (one release, for
|
|
210
|
+
* add-ons not yet switched). Throws on any other shape. The organization is
|
|
211
|
+
* looked up as core's own check, under the system context, so a user may
|
|
212
|
+
* name one on their own record without `identity-read`.
|
|
155
213
|
*/
|
|
156
214
|
private checkedAffiliations;
|
|
215
|
+
/** The site's own organization's `@id` (`OrganizationManager.getInstallOrg`): an affiliation with it is a fact (#2039). */
|
|
216
|
+
private siteOrganizationId;
|
|
157
217
|
private configNumber;
|
|
158
218
|
/**
|
|
159
219
|
* Clear all caches. Called on every write — the simplest correct
|
|
@@ -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
|
*/
|
|
@@ -2,7 +2,7 @@ import BaseManager, { type BackupFolder, type ManagerStats } from './BaseManager
|
|
|
2
2
|
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
3
3
|
import type { Organization, OrganizationInput, OrganizationUpdate } from '../types/Organization.js';
|
|
4
4
|
import type { OrganizationProvider } from '../types/OrganizationProvider.js';
|
|
5
|
-
import type { Person } from '../types/Person.js';
|
|
5
|
+
import type { Person, PersonAffiliation } from '../types/Person.js';
|
|
6
6
|
import type { ActorContext } from '../context/ActorContext.js';
|
|
7
7
|
/**
|
|
8
8
|
* Subset of install-form data this manager needs to seed the anchor org.
|
|
@@ -107,13 +107,17 @@ declare class OrganizationManager extends BaseManager {
|
|
|
107
107
|
limit?: number;
|
|
108
108
|
}): Promise<Organization[]>;
|
|
109
109
|
/**
|
|
110
|
-
* The
|
|
111
|
-
* `affiliation` (#1930): the link is stored
|
|
112
|
-
*
|
|
110
|
+
* The identities affiliated with an organization, users and contacts alike,
|
|
111
|
+
* read from each identity record's `affiliation` (#1930): the link is stored
|
|
112
|
+
* once, on the identity record. One entry per affiliation, each with its
|
|
113
|
+
* schema.org `OrganizationRole` (title as `roleName`, dates) (#2039), past
|
|
114
|
+
* and future ones included; `IdentityManager.search(ctx, { affiliation })`
|
|
115
|
+
* answers who holds one today. Searching identities is IdentityManager's,
|
|
116
|
+
* so this asks it.
|
|
113
117
|
*/
|
|
114
118
|
people(ctx: ActorContext, organizationId: string): Promise<Array<{
|
|
115
119
|
person: Person;
|
|
116
|
-
|
|
120
|
+
affiliation: PersonAffiliation;
|
|
117
121
|
}>>;
|
|
118
122
|
/**
|
|
119
123
|
* Create an organization. `name` is required; `@id` defaults to the `url`,
|
|
@@ -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
|
|
@@ -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
|
|
@@ -220,6 +222,8 @@ declare class UserManager extends BaseManager {
|
|
|
220
222
|
private passwordSalt?;
|
|
221
223
|
/** One read-merge-write at a time per account, keyed by the normalized username (#2025). */
|
|
222
224
|
private readonly accountWrites;
|
|
225
|
+
/** Whether this process has already reported the withheld session cookie (#2071). */
|
|
226
|
+
private sessionCookieWithheldReported;
|
|
223
227
|
/**
|
|
224
228
|
* Creates a new UserManager instance
|
|
225
229
|
*
|
|
@@ -253,6 +257,15 @@ declare class UserManager extends BaseManager {
|
|
|
253
257
|
* its safe message and other routes their own.
|
|
254
258
|
*/
|
|
255
259
|
private assertEmailFree;
|
|
260
|
+
/**
|
|
261
|
+
* The session cookie is `secure` and a request came over plain http, so
|
|
262
|
+
* express-session sent no cookie and nobody can sign in (#2071). Sessions
|
|
263
|
+
* are this manager's, so the report is too: an error in the log, the
|
|
264
|
+
* dashboard's "needs your attention", and a notification for admins, once
|
|
265
|
+
* per process. Every later call is one boolean check, because app.ts calls
|
|
266
|
+
* this on each such request.
|
|
267
|
+
*/
|
|
268
|
+
reportSessionCookieWithheld(): void;
|
|
256
269
|
/**
|
|
257
270
|
* Accounts that share an email, saved before #1748 refused it. They are not
|
|
258
271
|
* changed — an admin decides — but they are never silent: the manager is
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -59,6 +59,14 @@ export declare class PolicyDecisionPoint extends BaseManager {
|
|
|
59
59
|
* not something to resolve silently here.
|
|
60
60
|
*/
|
|
61
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;
|
|
62
70
|
/**
|
|
63
71
|
* What each role permits: role name → the actions its holders are granted.
|
|
64
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
|