@jwilleke/ngdpbase-types 5.21.0 → 5.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/packages/ngdpbase-types/index.d.ts +5 -0
- package/lib/src/mail/MailProvider.d.ts +2 -0
- package/lib/src/managers/AddonsManager.d.ts +3 -3
- package/lib/src/managers/AttachmentManager.d.ts +64 -12
- package/lib/src/managers/AuthManager.d.ts +29 -6
- package/lib/src/managers/BackgroundJobManager.d.ts +65 -1
- package/lib/src/managers/BaseManager.d.ts +14 -0
- package/lib/src/managers/BaseManager.js +9 -0
- package/lib/src/managers/CommentManager.d.ts +10 -1
- package/lib/src/managers/DatabaseManager.d.ts +24 -17
- package/lib/src/managers/DatabaseManager.js +72 -29
- package/lib/src/managers/EmailManager.d.ts +42 -0
- package/lib/src/managers/FootnoteManager.d.ts +10 -1
- package/lib/src/managers/NotificationManager.d.ts +13 -0
- package/lib/src/managers/OrganizationManager.d.ts +40 -6
- package/lib/src/managers/PersonManager.d.ts +106 -9
- package/lib/src/managers/RoleManager.d.ts +19 -1
- package/lib/src/managers/SessionStatsManager.d.ts +94 -0
- package/lib/src/managers/UserManager.d.ts +36 -1
- package/lib/src/providers/BaseAttachmentProvider.d.ts +14 -0
- package/lib/src/providers/BasicAttachmentProvider.d.ts +26 -2
- package/lib/src/providers/SqliteDatabaseProvider.d.ts +7 -0
- package/lib/src/providers/SqliteDatabaseProvider.js +16 -0
- package/lib/src/providers/SqliteJobStateProvider.d.ts +10 -0
- package/lib/src/providers/attachmentFilesBackup.d.ts +52 -0
- package/lib/src/providers/sqliteBackup.d.ts +6 -0
- package/lib/src/providers/sqliteBackup.js +16 -6
- package/lib/src/security/permissions.generated.d.ts +4 -0
- package/lib/src/types/Asset.d.ts +10 -0
- package/lib/src/types/Config.d.ts +11 -0
- package/lib/src/types/Organization.d.ts +7 -0
- package/lib/src/types/Person.d.ts +27 -3
- package/lib/src/types/PersonProvider.d.ts +6 -0
- package/lib/src/types/WikiEngine.d.ts +9 -21
- package/lib/src/types/express.d.ts +2 -0
- package/lib/src/utils/addonPageSync.d.ts +24 -0
- package/lib/src/utils/administrators.d.ts +21 -0
- package/lib/src/utils/auditEventNames.generated.d.ts +4 -0
- package/lib/src/utils/auditEventNames.generated.js +4 -0
- package/lib/src/utils/backupCrypto.d.ts +28 -0
- package/lib/src/utils/backupKey.d.ts +23 -0
- package/lib/src/utils/backupKey.js +37 -0
- package/lib/src/utils/contactAccess.d.ts +58 -0
- package/lib/src/utils/databaseKey.d.ts +7 -0
- package/lib/src/utils/databaseKey.js +7 -0
- package/lib/src/utils/directoryBackup.d.ts +78 -0
- package/lib/src/utils/emailForLog.d.ts +20 -0
- package/lib/src/utils/maintenanceState.d.ts +153 -0
- package/lib/src/utils/privateStoreTakeout.d.ts +2 -2
- package/lib/src/utils/sessionAccount.d.ts +26 -0
- package/lib/src/utils/sessionEnd.d.ts +1 -1
- package/lib/src/utils/siteRestore.d.ts +110 -0
- package/lib/src/utils/siteRestore.js +309 -0
- package/lib/src/utils/treeArchive.d.ts +44 -0
- package/lib/src/utils/username.d.ts +7 -1
- package/package.json +2 -2
- package/lib/src/utils/startupState.d.ts +0 -65
- package/lib/src/utils/startupState.js +0 -115
|
@@ -461,6 +461,14 @@ declare class UserManager extends BaseManager {
|
|
|
461
461
|
limit?: number;
|
|
462
462
|
activeOnly?: boolean;
|
|
463
463
|
}, ctx: ActorContext): Promise<Omit<User, 'password'>[]>;
|
|
464
|
+
/**
|
|
465
|
+
* The account lifecycle reaches PersonManager's door under a system context
|
|
466
|
+
* (#1930): keeping an account's person in step with the account is core's
|
|
467
|
+
* own bookkeeping, not the caller's contact management, so it never depends
|
|
468
|
+
* on the caller holding `contact-manage`. The reason names the account and
|
|
469
|
+
* who changed it, so the record says why.
|
|
470
|
+
*/
|
|
471
|
+
private personSyncContext;
|
|
464
472
|
/**
|
|
465
473
|
* Persist a Person record paired with a newly-created User. The install's
|
|
466
474
|
* anchor org (when configured) is referenced via `memberOf`; without it
|
|
@@ -468,12 +476,39 @@ declare class UserManager extends BaseManager {
|
|
|
468
476
|
* thrown — auth must still succeed if Person storage is degraded.
|
|
469
477
|
*/
|
|
470
478
|
private syncPersonOnCreate;
|
|
479
|
+
/**
|
|
480
|
+
* At sign-up, offer the new account a person with no account who has the
|
|
481
|
+
* same email (#1930): PersonManager mails an "Is this you?" link, and joins
|
|
482
|
+
* them only when the account confirms. Skipped when email is off. Never
|
|
483
|
+
* fails the account write.
|
|
484
|
+
*/
|
|
485
|
+
private offerContactLink;
|
|
471
486
|
private syncPersonOnUpdate;
|
|
472
487
|
private syncPersonOnDelete;
|
|
473
488
|
createSession(username: string, additionalData?: Record<string, unknown>): Promise<string>;
|
|
474
489
|
getSession(sessionId: string): Promise<UserSession | null>;
|
|
475
490
|
deleteSession(sessionId: string): Promise<void>;
|
|
476
|
-
|
|
491
|
+
/**
|
|
492
|
+
* End every signed-in session of `username` in the live session store
|
|
493
|
+
* (#1937): the account was deleted or turned off (`AuthManager.closeAccess`).
|
|
494
|
+
* Each goes through `endSession` (its keys dropped, `authentication-logout`
|
|
495
|
+
* recorded with the reason) and is removed from the store. Returns how many.
|
|
496
|
+
* The provider's own session list was cleared here before; nothing writes it,
|
|
497
|
+
* and the store express-session reads was never touched.
|
|
498
|
+
*/
|
|
499
|
+
deleteUserSessions(username: string, reason: 'account-deleted' | 'account-disabled', from?: {
|
|
500
|
+
ipAddress?: string;
|
|
501
|
+
}): Promise<number>;
|
|
502
|
+
/**
|
|
503
|
+
* End every signed-in session in the store that no longer belongs to its
|
|
504
|
+
* account (#1937): the account is gone, turned off, another account of the
|
|
505
|
+
* same name, or its password changed since (`checkSessionAccount`, the
|
|
506
|
+
* per-request rule). Run once at startup, so sessions left from before the
|
|
507
|
+
* fix, or from a close the store missed, do not wait for their next request.
|
|
508
|
+
*/
|
|
509
|
+
endStaleSessions(): Promise<number>;
|
|
510
|
+
/** End and remove each signed-in session `which` names a reason for; none without a session store. */
|
|
511
|
+
private endStoredSessions;
|
|
477
512
|
backup(): Promise<BackupData>;
|
|
478
513
|
restore(backupData: BackupData): Promise<void>;
|
|
479
514
|
}
|
|
@@ -3,6 +3,8 @@ import type { StoreFileEntry, 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';
|
|
6
|
+
import type { AttachmentFilesBackupData, AttachmentFilesBackupOutcome } from './attachmentFilesBackup.js';
|
|
7
|
+
import type { PartOutcome } from '../utils/siteRestore.js';
|
|
6
8
|
/**
|
|
7
9
|
* File information for attachment uploads
|
|
8
10
|
*/
|
|
@@ -187,6 +189,18 @@ declare abstract class BaseAttachmentProvider extends BaseProvider implements At
|
|
|
187
189
|
* @returns {Promise<void>}
|
|
188
190
|
*/
|
|
189
191
|
abstract restore(backupData: Record<string, unknown>): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Write the attachment files themselves beside the backup document (#1876),
|
|
194
|
+
* streamed, bytes as stored. A provider without it can't be backed up whole,
|
|
195
|
+
* and AttachmentManager's backup says so.
|
|
196
|
+
*/
|
|
197
|
+
backupFiles?(directory: string, stem: string): Promise<AttachmentFilesBackupOutcome>;
|
|
198
|
+
/** Stage the files and metadata from a backup for the next start, as part of a site restore (#1876). */
|
|
199
|
+
stageFilesRestore?(providerBackup: Record<string, unknown>, files: AttachmentFilesBackupData, directory: string, siteRestore: string): Promise<{
|
|
200
|
+
skippedVaults: string[];
|
|
201
|
+
}>;
|
|
202
|
+
/** What the start did with a staged restore of the files (#1876), once; null when there was none. */
|
|
203
|
+
takeRestoreAtStart?(): PartOutcome | null;
|
|
190
204
|
/**
|
|
191
205
|
* Shutdown the provider (cleanup resources)
|
|
192
206
|
* @returns {Promise<void>}
|
|
@@ -5,6 +5,8 @@ import type { AssetProvider, AssetRecord, AssetQuery, AssetPage, AssetInput, Ass
|
|
|
5
5
|
import type { CreativeWork } from '../types/Schema.js';
|
|
6
6
|
import type { ActorContext } from '../context/ActorContext.js';
|
|
7
7
|
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
8
|
+
import { type PartOutcome } from '../utils/siteRestore.js';
|
|
9
|
+
import { type AttachmentFilesBackupData, type AttachmentFilesBackupOutcome } from './attachmentFilesBackup.js';
|
|
8
10
|
/**
|
|
9
11
|
* Schema.org Person metadata
|
|
10
12
|
*/
|
|
@@ -122,6 +124,8 @@ export declare function safeStoredExtension(originalName: string, mimeType?: str
|
|
|
122
124
|
*/
|
|
123
125
|
declare class BasicAttachmentProvider extends BaseAttachmentProvider implements AssetProvider {
|
|
124
126
|
private storageDirectory;
|
|
127
|
+
/** What the start did with a staged restore of the files (#1876). */
|
|
128
|
+
private restoreAtStart;
|
|
125
129
|
/** Pre-#1386 attachments/{legacyprivateroot}/{user}/ — migrate-FROM and read fallback only. */
|
|
126
130
|
private legacyPrivateDir;
|
|
127
131
|
private pagesDirectory;
|
|
@@ -149,8 +153,15 @@ declare class BasicAttachmentProvider extends BaseAttachmentProvider implements
|
|
|
149
153
|
/**
|
|
150
154
|
* Initialize the provider
|
|
151
155
|
* All configuration access via ConfigurationManager
|
|
156
|
+
*
|
|
157
|
+
* @param options.readOnly - Open for reading only (#1852): load the index and
|
|
158
|
+
* change nothing on disk. No staged restore is applied, no folder is
|
|
159
|
+
* created, and neither start-up migration (legacy private files, stale
|
|
160
|
+
* paths) runs. For an operator report such as the files dry run.
|
|
152
161
|
*/
|
|
153
|
-
initialize(
|
|
162
|
+
initialize(options?: {
|
|
163
|
+
readOnly?: boolean;
|
|
164
|
+
}): Promise<void>;
|
|
154
165
|
/**
|
|
155
166
|
* Verify that the attachment storage directory is accessible.
|
|
156
167
|
*
|
|
@@ -210,7 +221,7 @@ declare class BasicAttachmentProvider extends BaseAttachmentProvider implements
|
|
|
210
221
|
* @param fileBuffer - File data
|
|
211
222
|
* @param fileInfo - { originalName, mimeType, size }
|
|
212
223
|
* @param metadata - Schema.org CreativeWork metadata
|
|
213
|
-
* @param user -
|
|
224
|
+
* @param user - The uploader: `username` is recorded as author and editor (#1926)
|
|
214
225
|
* @param options - Privacy-aware storage options
|
|
215
226
|
* @returns Complete attachment metadata
|
|
216
227
|
* @private
|
|
@@ -435,6 +446,19 @@ declare class BasicAttachmentProvider extends BaseAttachmentProvider implements
|
|
|
435
446
|
* @returns Backup data
|
|
436
447
|
*/
|
|
437
448
|
backup(): Promise<BackupData>;
|
|
449
|
+
/** Where the attachment files are, for their backup and staged restore (#1876). */
|
|
450
|
+
private filePlaces;
|
|
451
|
+
/** The files themselves, beside the backup document (#1876): the shared pool and every vault's, as stored. */
|
|
452
|
+
backupFiles(directory: string, stem: string): Promise<AttachmentFilesBackupOutcome>;
|
|
453
|
+
/**
|
|
454
|
+
* Stage the files and the shared metadata from a backup for the next start
|
|
455
|
+
* (#1876). The metadata is the document's; the live files and metadata are
|
|
456
|
+
* not touched.
|
|
457
|
+
*/
|
|
458
|
+
stageFilesRestore(providerBackup: Record<string, unknown>, files: AttachmentFilesBackupData, directory: string, siteRestore: string): Promise<{
|
|
459
|
+
skippedVaults: string[];
|
|
460
|
+
}>;
|
|
461
|
+
takeRestoreAtStart(): PartOutcome | null;
|
|
438
462
|
/**
|
|
439
463
|
* Restore provider data from backup
|
|
440
464
|
* @param backupData - Backup data
|
|
@@ -36,6 +36,13 @@ declare class SqliteDatabaseProvider extends BaseDatabaseProvider<SqliteHandle>
|
|
|
36
36
|
busyTimeoutMs?: number;
|
|
37
37
|
});
|
|
38
38
|
private static unlock;
|
|
39
|
+
/**
|
|
40
|
+
* Read a database file once, read-only, under `key`, and close it (#1876):
|
|
41
|
+
* nothing is migrated or written. Used for the copy a restore kept as
|
|
42
|
+
* `*.pre-restore`, to read what the site had recorded just before the
|
|
43
|
+
* restore replaced it.
|
|
44
|
+
*/
|
|
45
|
+
static readOnce<T>(file: string, key: string, read: (db: SqliteHandle) => T): T;
|
|
39
46
|
/** Read an existing file read-only: the key opens it and it passes the quick check, or this throws. */
|
|
40
47
|
private static probe;
|
|
41
48
|
/**
|
|
@@ -62,6 +62,22 @@ class SqliteDatabaseProvider extends BaseDatabaseProvider {
|
|
|
62
62
|
db.pragma("cipher='sqlcipher'");
|
|
63
63
|
db.pragma(`key='${key.replace(/'/g, "''")}'`);
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Read a database file once, read-only, under `key`, and close it (#1876):
|
|
67
|
+
* nothing is migrated or written. Used for the copy a restore kept as
|
|
68
|
+
* `*.pre-restore`, to read what the site had recorded just before the
|
|
69
|
+
* restore replaced it.
|
|
70
|
+
*/
|
|
71
|
+
static readOnce(file, key, read) {
|
|
72
|
+
const db = new Database(file, { readonly: true, fileMustExist: true });
|
|
73
|
+
try {
|
|
74
|
+
SqliteDatabaseProvider.unlock(db, key);
|
|
75
|
+
return read(db);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
db.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
65
81
|
/** Read an existing file read-only: the key opens it and it passes the quick check, or this throws. */
|
|
66
82
|
static probe(file, key) {
|
|
67
83
|
const db = new Database(file, { readonly: true, fileMustExist: true });
|
|
@@ -39,3 +39,13 @@ declare class SqliteJobStateProvider implements JobStateProvider {
|
|
|
39
39
|
releaseLock(jobId: string, slot: string, owner: string): Promise<void>;
|
|
40
40
|
}
|
|
41
41
|
export default SqliteJobStateProvider;
|
|
42
|
+
/**
|
|
43
|
+
* Every job's runs started after `since`, from a database handle (#1876): the
|
|
44
|
+
* application database a restore kept as `*.pre-restore`, read just after it
|
|
45
|
+
* was replaced, so the restore's notice names the runs the restored job
|
|
46
|
+
* history no longer has. The same table `listRuns` reads.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runsStartedAfter(db: Handle, since: string): Array<{
|
|
49
|
+
jobId: string;
|
|
50
|
+
run: JobRunRecord;
|
|
51
|
+
}>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type PartOutcome } from '../utils/siteRestore.js';
|
|
2
|
+
import { type PrivateStoreLayout } from '../utils/privateStorePath.js';
|
|
3
|
+
/** Where the attachment files live on this site. */
|
|
4
|
+
export interface AttachmentFilePlaces {
|
|
5
|
+
/** The shared pool folder. */
|
|
6
|
+
pool: string;
|
|
7
|
+
/** The shared pool's metadata file (inside the pool by default). */
|
|
8
|
+
metadataFile: string;
|
|
9
|
+
/** The pages folder, which holds the vaults. */
|
|
10
|
+
pagesDirectory: string | null;
|
|
11
|
+
layout: PrivateStoreLayout;
|
|
12
|
+
}
|
|
13
|
+
/** The files part of AttachmentManager's entry in the backup document. */
|
|
14
|
+
export interface AttachmentFilesBackupData {
|
|
15
|
+
format: 1;
|
|
16
|
+
pool: {
|
|
17
|
+
archive: string;
|
|
18
|
+
files: number;
|
|
19
|
+
bytes: number;
|
|
20
|
+
};
|
|
21
|
+
vaults: {
|
|
22
|
+
archive: string;
|
|
23
|
+
files: number;
|
|
24
|
+
bytes: number;
|
|
25
|
+
stores: string[];
|
|
26
|
+
};
|
|
27
|
+
/** `backupKeyFingerprint()` of the key both archives are encrypted under; absent when they are not. */
|
|
28
|
+
keyFingerprint?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface AttachmentFilesBackupOutcome {
|
|
31
|
+
data: AttachmentFilesBackupData;
|
|
32
|
+
/** Parts left out (a symbolic link, a vault folder whose name can't be a path segment), each named. */
|
|
33
|
+
warnings: string[];
|
|
34
|
+
}
|
|
35
|
+
/** Write the attachment files beside the backup document: the pool, then every vault's. Never base64, never whole in memory. */
|
|
36
|
+
export declare function backupAttachmentFiles(places: AttachmentFilePlaces, directory: string, stem: string): Promise<AttachmentFilesBackupOutcome>;
|
|
37
|
+
/**
|
|
38
|
+
* Stage the attachment files from a backup for the next start, and record
|
|
39
|
+
* the part in the site restore. All or nothing: on any failure every staged
|
|
40
|
+
* path is removed and nothing is recorded.
|
|
41
|
+
*
|
|
42
|
+
* @returns the vaults the backup holds that this site doesn't have (not staged).
|
|
43
|
+
*/
|
|
44
|
+
export declare function stageAttachmentFiles(places: AttachmentFilePlaces, directory: string, data: AttachmentFilesBackupData, metadataJson: string, siteRestore: string): Promise<{
|
|
45
|
+
skippedVaults: string[];
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* Put staged attachment files in place, at start, before the metadata is
|
|
49
|
+
* read: only the pool, its metadata file and vault attachment paths may be
|
|
50
|
+
* replaced, whatever the manifest says.
|
|
51
|
+
*/
|
|
52
|
+
export declare function applyAttachmentFilesAtStart(places: AttachmentFilePlaces, siteRestore: string): PartOutcome | null;
|
|
@@ -126,3 +126,9 @@ export interface ApplyOutcome {
|
|
|
126
126
|
* be replaced, so a tampered manifest cannot rename an arbitrary file.
|
|
127
127
|
*/
|
|
128
128
|
export declare function applyStagedRestore(applicationFile: string, allowed: (live: string) => boolean): ApplyOutcome | null;
|
|
129
|
+
/**
|
|
130
|
+
* Set a staged restore aside without applying any of it (#1876): every
|
|
131
|
+
* `*.db.staged` beside the application database and the manifest become
|
|
132
|
+
* `*.incomplete`. Used when the site restore it belongs to is not applied.
|
|
133
|
+
*/
|
|
134
|
+
export declare function setAsideStagedRestore(applicationFile: string, reason: string): ApplyOutcome;
|
|
@@ -307,12 +307,7 @@ export function applyStagedRestore(applicationFile, allowed) {
|
|
|
307
307
|
move(f, `${f}.incomplete`);
|
|
308
308
|
return { applied: [], refused: `staged database files with no restore manifest (an interrupted staging): ${leftovers.map((f) => path.basename(f)).join(', ')} — set aside as *.incomplete` };
|
|
309
309
|
}
|
|
310
|
-
const setAside = (reason) =>
|
|
311
|
-
for (const f of stray())
|
|
312
|
-
move(f, `${f}.incomplete`);
|
|
313
|
-
move(manifestFile, `${manifestFile}.incomplete`);
|
|
314
|
-
return { applied: [], refused: reason };
|
|
315
|
-
};
|
|
310
|
+
const setAside = (reason) => setAsideStagedRestore(applicationFile, reason);
|
|
316
311
|
let manifest;
|
|
317
312
|
try {
|
|
318
313
|
manifest = JSON.parse(readFileSync(manifestFile, 'utf8'));
|
|
@@ -351,6 +346,21 @@ export function applyStagedRestore(applicationFile, allowed) {
|
|
|
351
346
|
unlinkSync(manifestFile);
|
|
352
347
|
return { applied };
|
|
353
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Set a staged restore aside without applying any of it (#1876): every
|
|
351
|
+
* `*.db.staged` beside the application database and the manifest become
|
|
352
|
+
* `*.incomplete`. Used when the site restore it belongs to is not applied.
|
|
353
|
+
*/
|
|
354
|
+
export function setAsideStagedRestore(applicationFile, reason) {
|
|
355
|
+
const manifestFile = manifestPath(applicationFile);
|
|
356
|
+
const dir = path.dirname(applicationFile);
|
|
357
|
+
const staged = existsSync(dir) ? readdirSync(dir).filter((f) => f.endsWith('.db.staged')).map((f) => path.join(dir, f)) : [];
|
|
358
|
+
for (const f of staged)
|
|
359
|
+
move(f, `${f}.incomplete`);
|
|
360
|
+
if (existsSync(manifestFile))
|
|
361
|
+
move(manifestFile, `${manifestFile}.incomplete`);
|
|
362
|
+
return { applied: [], refused: reason };
|
|
363
|
+
}
|
|
354
364
|
/** A rename; a copy and unlink when the two paths are on different devices. */
|
|
355
365
|
function move(from, to) {
|
|
356
366
|
try {
|
|
@@ -22,6 +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 contacts: persons (with or without an account) and organizations, and a person's affiliations, through PersonManager and OrganizationManager (#1930). Reading them is contact-read. Admins by default; an add-on grants it to its own roles in its own policy */
|
|
26
|
+
| 'contact-manage'
|
|
27
|
+
/** List, search and view contacts: persons (with or without an account) and organizations, and their affiliations, through PersonManager and OrganizationManager, and the /admin/contacts and /admin/organizations views (#1930). Admins and demo admins by default; an add-on grants it to its own roles in its own policy */
|
|
28
|
+
| 'contact-read'
|
|
25
29
|
/** Browse and view the media library (not delegated to anonymous visitors) */
|
|
26
30
|
| 'media-read'
|
|
27
31
|
/** Create new pages */
|
package/lib/src/types/Asset.d.ts
CHANGED
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
* while AttachmentManager / MediaManager remain thin wrappers.
|
|
7
7
|
*/
|
|
8
8
|
import type { ActorContext } from '../context/ActorContext.js';
|
|
9
|
+
/**
|
|
10
|
+
* What an attachment record says when it was stored with no uploader, in the
|
|
11
|
+
* pool's `author.name`, and what `uploadedBy` shows for a record with none.
|
|
12
|
+
* Only records written before #1926 carry it: a new upload names its
|
|
13
|
+
* uploader's username or is refused. Not a user; the files layer reads it as
|
|
14
|
+
* "no owner" (#1852).
|
|
15
|
+
*/
|
|
16
|
+
export declare const UNKNOWN_UPLOADER = "Unknown";
|
|
9
17
|
/**
|
|
10
18
|
* Capabilities that an AssetProvider may declare.
|
|
11
19
|
* Used to advertise which optional operations the provider supports.
|
|
@@ -207,6 +215,8 @@ export interface AssetRecord {
|
|
|
207
215
|
author?: string;
|
|
208
216
|
/** ISO 8601 timestamp of the last metadata or file modification — schema.org/dateModified */
|
|
209
217
|
dateModified?: string;
|
|
218
|
+
/** Username of whoever last changed it, when the provider records one — schema.org/editor */
|
|
219
|
+
editor?: string;
|
|
210
220
|
/** Licensing and rights information */
|
|
211
221
|
usageRights?: AssetUsageRights;
|
|
212
222
|
/** Download counts and access statistics */
|
|
@@ -116,6 +116,11 @@ export interface WikiConfig {
|
|
|
116
116
|
*/
|
|
117
117
|
'ngdpbase.auth.throttle.max-lock-minutes': number;
|
|
118
118
|
'ngdpbase.mail.rate-limit.enabled': boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Outbound email failures: one administrator notice per this many minutes,
|
|
121
|
+
* counting every failure inside it (#1916). Default: 60.
|
|
122
|
+
*/
|
|
123
|
+
'ngdpbase.mail.failure-notice.window-minutes': number;
|
|
119
124
|
/**
|
|
120
125
|
* Anti-spam: max submissions per IP per `window-minutes` window before
|
|
121
126
|
* the 429 trips (#670 Phase E). Default: 5.
|
|
@@ -305,6 +310,12 @@ export interface ApplicationOrganizationConfig {
|
|
|
305
310
|
'ngdpbase.application.persons.provider.default': string;
|
|
306
311
|
/** Active Person provider class (lowercased; falls back to .default) */
|
|
307
312
|
'ngdpbase.application.persons.provider': string;
|
|
313
|
+
/** #1930: the most results `PersonManager.search` returns when the caller gives no limit */
|
|
314
|
+
'ngdpbase.application.persons.search.limit': number;
|
|
315
|
+
/** #1930: minutes the emailed "Is this you?" sign-up link stays usable */
|
|
316
|
+
'ngdpbase.application.persons.link-offer.ttl-minutes': number;
|
|
317
|
+
/** #1930: the most results `OrganizationManager.search` returns when the caller gives no limit */
|
|
318
|
+
'ngdpbase.application.organization.search.limit': number;
|
|
308
319
|
/** Storage directory for OrganizationRole JSON files (one per (org, namedPosition) pair) */
|
|
309
320
|
'ngdpbase.application.roles.storagedir': string;
|
|
310
321
|
/** Default Role provider class (lowercased) */
|
|
@@ -71,6 +71,13 @@ export interface Organization {
|
|
|
71
71
|
/** Allow extension fields (additionalProperty, etc.) */
|
|
72
72
|
[key: string]: unknown;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* What a caller gives `OrganizationManager.create` (#1930): the fields, with
|
|
76
|
+
* `@id` optional (it defaults to the `url`, else a new `urn:uuid:`).
|
|
77
|
+
*/
|
|
78
|
+
export type OrganizationInput = Partial<Organization> & {
|
|
79
|
+
name: string;
|
|
80
|
+
};
|
|
74
81
|
/**
|
|
75
82
|
* Patch shape for update operations.
|
|
76
83
|
* `@context`/`@type` are immutable; `@id` cannot be changed via update.
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* (where the uuid matches the v4 segment of `@id`).
|
|
7
7
|
*
|
|
8
8
|
* `@id` uses the urn:uuid:<v4> form because persons typically lack a
|
|
9
|
-
* canonical web URL. `identifier`
|
|
10
|
-
*
|
|
9
|
+
* canonical web URL. `identifier` is the person's account (`User.username`)
|
|
10
|
+
* when they have one, and `person:<uuid>` when they do not (#1930) — a form
|
|
11
|
+
* no username may take (`ACCOUNTLESS_PERSON_PREFIX`, `src/utils/username.ts`).
|
|
11
12
|
*
|
|
12
13
|
* No embedded role-records — those live in the future person-contacts
|
|
13
14
|
* addon (#602) and are folded in at JSON-LD export time.
|
|
@@ -15,6 +16,18 @@
|
|
|
15
16
|
* @see https://schema.org/Person
|
|
16
17
|
*/
|
|
17
18
|
import type { PostalAddress, ContactPoint } from './Organization.js';
|
|
19
|
+
/**
|
|
20
|
+
* One organization a person is affiliated with (#1930): schema.org
|
|
21
|
+
* `affiliation`, with the person's role or title there when one is known.
|
|
22
|
+
* The organization is named by its `@id`; OrganizationManager lists an
|
|
23
|
+
* organization's people by reading these, so the link is stored once, here.
|
|
24
|
+
*/
|
|
25
|
+
export interface PersonAffiliation {
|
|
26
|
+
/** The organization's `@id`. */
|
|
27
|
+
organization: string;
|
|
28
|
+
/** The person's role or title there (schema.org `roleName`), when known. */
|
|
29
|
+
role?: string;
|
|
30
|
+
}
|
|
18
31
|
/**
|
|
19
32
|
* Canonical Person record.
|
|
20
33
|
*
|
|
@@ -26,7 +39,7 @@ export interface Person {
|
|
|
26
39
|
'@type': 'Person';
|
|
27
40
|
/** urn:uuid:<v4> */
|
|
28
41
|
'@id': string;
|
|
29
|
-
/**
|
|
42
|
+
/** The account's username, or `person:<uuid>` for a person with no account (#1930). */
|
|
30
43
|
identifier: string;
|
|
31
44
|
/** Display name */
|
|
32
45
|
name?: string;
|
|
@@ -46,6 +59,8 @@ export interface Person {
|
|
|
46
59
|
image?: string;
|
|
47
60
|
/** Other URLs that identify this person (LinkedIn, GitHub, etc.) */
|
|
48
61
|
sameAs?: string[];
|
|
62
|
+
/** Organizations this person is affiliated with, each with an optional role (#1930). */
|
|
63
|
+
affiliation?: PersonAffiliation[];
|
|
49
64
|
/** Allow extension fields (additionalProperty, etc.) */
|
|
50
65
|
[key: string]: unknown;
|
|
51
66
|
}
|
|
@@ -54,3 +69,12 @@ export interface Person {
|
|
|
54
69
|
* `@context`/`@type` are immutable; `@id` and `identifier` cannot be changed via update.
|
|
55
70
|
*/
|
|
56
71
|
export type PersonUpdate = Partial<Omit<Person, '@context' | '@type' | '@id' | 'identifier'>>;
|
|
72
|
+
/**
|
|
73
|
+
* What a caller gives `PersonManager.create` (#1930): the person's fields.
|
|
74
|
+
* `identifier` is left out for a person with no account (one is generated);
|
|
75
|
+
* UserManager passes the account's username when it creates the person for a
|
|
76
|
+
* new account.
|
|
77
|
+
*/
|
|
78
|
+
export type PersonInput = PersonUpdate & {
|
|
79
|
+
identifier?: string;
|
|
80
|
+
};
|
|
@@ -18,6 +18,12 @@ export interface PersonProvider extends BaseProvider {
|
|
|
18
18
|
getByIdentifier(identifier: string): Promise<Person | null>;
|
|
19
19
|
/** Persist a new Person. Returns the stored record. */
|
|
20
20
|
create(person: Person): Promise<Person>;
|
|
21
|
+
/**
|
|
22
|
+
* Write the whole record in place of the one with the same `@id` (#1930).
|
|
23
|
+
* The only write that may change `identifier`: PersonManager uses it to join
|
|
24
|
+
* a person to an account.
|
|
25
|
+
*/
|
|
26
|
+
replace(person: Person): Promise<Person>;
|
|
21
27
|
/** Apply a partial update by `@id`. Returns the updated record, or null if not found. */
|
|
22
28
|
update(id: string, patch: PersonUpdate): Promise<Person | null>;
|
|
23
29
|
/** Delete by `@id`. Returns true if a record was removed. */
|
|
@@ -8,6 +8,7 @@ import { WikiConfig } from './Config.js';
|
|
|
8
8
|
import type { Logger } from 'winston';
|
|
9
9
|
import type { Application, Request, RequestHandler, Router, RouterOptions } from 'express';
|
|
10
10
|
import type express from 'express';
|
|
11
|
+
import type { MaintenanceState } from '../utils/maintenanceState.js';
|
|
11
12
|
/**
|
|
12
13
|
* All known manager names as a union type
|
|
13
14
|
* Use with getManager() for type-safe access when combined with explicit type parameter
|
|
@@ -95,28 +96,15 @@ export interface WikiEngine {
|
|
|
95
96
|
*/
|
|
96
97
|
shutdown(): Promise<void>;
|
|
97
98
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* Also a site-critical add-on that did not load (#1883, operator
|
|
106
|
-
* 2026-10-18): its fix may lie outside the UI (a key in the instance .env),
|
|
107
|
-
* and the admin notification says so.
|
|
108
|
-
*/
|
|
109
|
-
blockConfiguration(reason: string, options?: {
|
|
110
|
-
publicMessage?: string;
|
|
111
|
-
}): void;
|
|
112
|
-
/** Configuration values that could not be used, in the order found (#1152). */
|
|
113
|
-
getBlockingConditions(): readonly string[];
|
|
114
|
-
/**
|
|
115
|
-
* What visitors see in place of the reasons, when a reason was recorded with
|
|
116
|
-
* a `publicMessage` (#1883: a site-critical add-on that did not load); null
|
|
117
|
-
* when every reason may be shown.
|
|
99
|
+
* Whether the site is in maintenance mode, and why (#1906): the one door.
|
|
100
|
+
* A manager that needs the site closed calls `maintenance.enter(reason,
|
|
101
|
+
* { message?, estimatedDuration? })`, and `maintenance.leave(reason)` when
|
|
102
|
+
* the reason clears. Reasons are for administrators only; visitors get
|
|
103
|
+
* `maintenance.visitorView(configManager)`. Replaces `blockConfiguration`
|
|
104
|
+
* (#1152), still recorded rather than thrown so `initialize()` finishes and
|
|
105
|
+
* `/admin`, `/login` and `/logout` stay reachable.
|
|
118
106
|
*/
|
|
119
|
-
|
|
107
|
+
readonly maintenance: MaintenanceState;
|
|
120
108
|
/**
|
|
121
109
|
* Get all registered manager names
|
|
122
110
|
* @returns Array of manager names
|
|
@@ -30,6 +30,8 @@ declare module 'express-session' {
|
|
|
30
30
|
privateStoreHandle?: string;
|
|
31
31
|
/** The account's password-change generation when this session signed in (#1482). */
|
|
32
32
|
sessionGeneration?: number;
|
|
33
|
+
/** The `createdAt` of the account this session signed in to (#1937): a later account of the same name is not it. */
|
|
34
|
+
accountCreatedAt?: string;
|
|
33
35
|
/** How this session signed in: provider, factors with their times, amr / aal / acr (#1523). */
|
|
34
36
|
signIn?: SignInRecord;
|
|
35
37
|
/** A pending WebAuthn challenge (#448): single-use, tied to its purpose, short-lived. */
|
|
@@ -31,6 +31,30 @@ export declare function evaluateSeededAddonPage(args: {
|
|
|
31
31
|
liveContent: string;
|
|
32
32
|
storedHash: string | undefined;
|
|
33
33
|
}): SeededAddonPageStatus;
|
|
34
|
+
/**
|
|
35
|
+
* How a shipped item's sync status reads on an admin screen: the status words
|
|
36
|
+
* and the edit protection of Admin → Required Pages, which every screen that
|
|
37
|
+
* syncs shipped content shares (`views/_sync-status.ejs`, `views/_sync-button.ejs`;
|
|
38
|
+
* shipped forms, #1918).
|
|
39
|
+
*
|
|
40
|
+
* - `status` — `modified` when the live copy differs from what ships,
|
|
41
|
+
* `current` when it is the same.
|
|
42
|
+
* - `userModified` — edited on this site: Sync does not replace it; Force Sync
|
|
43
|
+
* does, after a confirm.
|
|
44
|
+
*/
|
|
45
|
+
export interface SyncRowState {
|
|
46
|
+
status: 'current' | 'modified';
|
|
47
|
+
userModified: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The one mapping from `evaluateSeededAddonPage`'s answer to the admin screen's
|
|
51
|
+
* words (#1918). `userModifiedFlag` is a page's explicit `user-modified`
|
|
52
|
+
* frontmatter, which marks it edited even when the hash cannot tell.
|
|
53
|
+
*
|
|
54
|
+
* @param seedStatus - `evaluateSeededAddonPage`'s answer for the live copy
|
|
55
|
+
* @param userModifiedFlag - The live copy is explicitly marked edited
|
|
56
|
+
*/
|
|
57
|
+
export declare function syncRowState(seedStatus: SeededAddonPageStatus, userModifiedFlag?: boolean): SyncRowState;
|
|
34
58
|
/**
|
|
35
59
|
* Default edit access for a page ngdpbase ships (#971, #1411): a `general`
|
|
36
60
|
* page is instance-owned and gets no stamp; any other category — system,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who an administrator notification goes to.
|
|
3
|
+
*
|
|
4
|
+
* __An administrator is whoever holds `admin-system`__, asked per user through
|
|
5
|
+
* the policy (#1431 7b) — never a role name. Used by every report that must
|
|
6
|
+
* reach administrators and nobody else: a damaged page (#1431 step 7) and
|
|
7
|
+
* failing outbound email (#1916).
|
|
8
|
+
*
|
|
9
|
+
* An empty answer means "nobody to tell", and the caller must then log only:
|
|
10
|
+
* an empty `targetUsers` list means ALL users in NotificationManager, which
|
|
11
|
+
* would announce an operator problem to every reader.
|
|
12
|
+
*
|
|
13
|
+
* Null when the managers needed to answer are not registered (tests, a
|
|
14
|
+
* half-booted engine) — the caller treats that like "nobody to tell".
|
|
15
|
+
*/
|
|
16
|
+
type EngineLike = {
|
|
17
|
+
getManager: (name: string) => unknown;
|
|
18
|
+
} | null | undefined;
|
|
19
|
+
/** Usernames of every user who holds `admin-system`; null when it cannot be asked. */
|
|
20
|
+
export declare function administratorUsernames(engine: EngineLike): Promise<string[] | null>;
|
|
21
|
+
export {};
|
|
@@ -36,6 +36,8 @@ export declare const AUDIT_EVENT: {
|
|
|
36
36
|
readonly BACKUP_CREATE: "backup-create";
|
|
37
37
|
/** A restore of a backup's databases started from Admin → Backup: which backup and who asked; recorded before the fresh backup and the staging, which cannot proceed without it (#1723) */
|
|
38
38
|
readonly BACKUP_RESTORE: "backup-restore";
|
|
39
|
+
/** A staged restore (site or databases-only) put in place at start: which backup, when it was taken, who started the restore and which parts were applied; written into the restored audit log, so the log itself says a restore happened (#1876) */
|
|
40
|
+
readonly BACKUP_RESTORE_APPLIED: "backup-restore-applied";
|
|
39
41
|
/** A staged database restore cancelled before the restart applied it: which backup, who staged it and who cancelled; recorded first, which cannot proceed without it (#1723) */
|
|
40
42
|
readonly BACKUP_RESTORE_CANCEL: "backup-restore-cancel";
|
|
41
43
|
/** A staged database restore replaced by another before the restart applied it: which backup replaced which, and who asked; recorded first, which cannot proceed without it (#1723) */
|
|
@@ -48,6 +50,8 @@ export declare const AUDIT_EVENT: {
|
|
|
48
50
|
readonly CONFIG_CHANGE: "config-change";
|
|
49
51
|
/** Every custom configuration value discarded; recorded before the reset, which cannot proceed without it */
|
|
50
52
|
readonly CONFIG_RESET: "config-reset";
|
|
53
|
+
/** Contact created, changed, deleted or linked to an account; metadata names the kind (person or organization), the operation and the fields changed, never their values */
|
|
54
|
+
readonly CONTACT_MANAGE: "contact-manage";
|
|
51
55
|
/** A page's footnote list changed — action add, import, transfer, update or delete; page content written on someone's behalf (#1233) */
|
|
52
56
|
readonly FOOTNOTE_EDIT: "footnote-edit";
|
|
53
57
|
/** A background job finished successfully */
|
|
@@ -43,6 +43,8 @@ export const AUDIT_EVENT = {
|
|
|
43
43
|
BACKUP_CREATE: 'backup-create',
|
|
44
44
|
/** A restore of a backup's databases started from Admin → Backup: which backup and who asked; recorded before the fresh backup and the staging, which cannot proceed without it (#1723) */
|
|
45
45
|
BACKUP_RESTORE: 'backup-restore',
|
|
46
|
+
/** A staged restore (site or databases-only) put in place at start: which backup, when it was taken, who started the restore and which parts were applied; written into the restored audit log, so the log itself says a restore happened (#1876) */
|
|
47
|
+
BACKUP_RESTORE_APPLIED: 'backup-restore-applied',
|
|
46
48
|
/** A staged database restore cancelled before the restart applied it: which backup, who staged it and who cancelled; recorded first, which cannot proceed without it (#1723) */
|
|
47
49
|
BACKUP_RESTORE_CANCEL: 'backup-restore-cancel',
|
|
48
50
|
/** A staged database restore replaced by another before the restart applied it: which backup replaced which, and who asked; recorded first, which cannot proceed without it (#1723) */
|
|
@@ -55,6 +57,8 @@ export const AUDIT_EVENT = {
|
|
|
55
57
|
CONFIG_CHANGE: 'config-change',
|
|
56
58
|
/** Every custom configuration value discarded; recorded before the reset, which cannot proceed without it */
|
|
57
59
|
CONFIG_RESET: 'config-reset',
|
|
60
|
+
/** Contact created, changed, deleted or linked to an account; metadata names the kind (person or organization), the operation and the fields changed, never their values */
|
|
61
|
+
CONTACT_MANAGE: 'contact-manage',
|
|
58
62
|
/** A page's footnote list changed — action add, import, transfer, update or delete; page content written on someone's behalf (#1233) */
|
|
59
63
|
FOOTNOTE_EDIT: 'footnote-edit',
|
|
60
64
|
/** A background job finished successfully */
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
2
|
+
/** The first bytes of every encrypted backup file. */
|
|
3
|
+
export declare const BACKUP_FILE_MAGIC: Buffer<ArrayBuffer>;
|
|
4
|
+
/** What an encrypted backup file's name ends with. */
|
|
5
|
+
export declare const ENCRYPTED_SUFFIX = ".enc";
|
|
6
|
+
/** A backup file made under a different backup key than the one set now. */
|
|
7
|
+
export declare class BackupKeyMismatchError extends Error {
|
|
8
|
+
readonly fileFingerprint: string;
|
|
9
|
+
readonly keyFingerprint: string;
|
|
10
|
+
constructor(fileFingerprint: string, keyFingerprint: string);
|
|
11
|
+
}
|
|
12
|
+
/** Whether these bytes (a file's first) are an encrypted backup file. */
|
|
13
|
+
export declare function isEncryptedBackup(head: Buffer): boolean;
|
|
14
|
+
/** Whether the file on disk is an encrypted backup file, read from its first bytes only. */
|
|
15
|
+
export declare function fileIsEncrypted(file: string): Promise<boolean>;
|
|
16
|
+
/** A stream that encrypts what is written to it under `backupKey`. */
|
|
17
|
+
export declare function encryptStream(backupKey: string): Transform;
|
|
18
|
+
/**
|
|
19
|
+
* A stream that decrypts an encrypted backup file written to it. Fails —
|
|
20
|
+
* naming why — on a file that is not one, a different backup key
|
|
21
|
+
* ({@link BackupKeyMismatchError}), any record that does not verify, and a
|
|
22
|
+
* file cut short or with bytes after its last record.
|
|
23
|
+
*/
|
|
24
|
+
export declare function decryptStream(backupKey: string): Transform;
|
|
25
|
+
/** Encrypt a whole buffer (the backup document, already in memory). */
|
|
26
|
+
export declare function encryptBuffer(data: Buffer, backupKey: string): Promise<Buffer>;
|
|
27
|
+
/** Decrypt a whole buffer. */
|
|
28
|
+
export declare function decryptBuffer(data: Buffer, backupKey: string): Promise<Buffer>;
|