@jwilleke/ngdpbase-types 5.24.0 → 5.26.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.
Files changed (63) hide show
  1. package/README.md +2 -2
  2. package/lib/packages/ngdpbase-types/index.d.ts +6 -2
  3. package/lib/packages/ngdpbase-types/testing.d.ts +7 -2
  4. package/lib/packages/ngdpbase-types/testing.js +7 -2
  5. package/lib/src/http/egressPolicy.js +61 -0
  6. package/lib/src/http/guardedElasticsearch.d.ts +27 -0
  7. package/lib/src/http/guardedElasticsearch.js +99 -0
  8. package/lib/src/managers/AddonsManager.d.ts +119 -2
  9. package/lib/src/managers/AttachmentManager.d.ts +4 -5
  10. package/lib/src/managers/AuthManager.d.ts +150 -32
  11. package/lib/src/managers/BackgroundJobManager.d.ts +22 -12
  12. package/lib/src/managers/BackupManager.d.ts +779 -0
  13. package/lib/src/managers/BackupManager.js +2042 -0
  14. package/lib/src/managers/BaseManager.d.ts +156 -6
  15. package/lib/src/managers/BaseManager.js +64 -2
  16. package/lib/src/managers/CommentManager.d.ts +6 -6
  17. package/lib/src/managers/ConsentManager.d.ts +130 -0
  18. package/lib/src/managers/DatabaseManager.d.ts +6 -0
  19. package/lib/src/managers/DatabaseManager.js +25 -15
  20. package/lib/src/managers/FootnoteManager.d.ts +6 -6
  21. package/lib/src/managers/OrganizationManager.d.ts +8 -6
  22. package/lib/src/managers/PageManager.d.ts +16 -0
  23. package/lib/src/managers/PersonManager.d.ts +8 -6
  24. package/lib/src/managers/RoleManager.d.ts +6 -8
  25. package/lib/src/providers/BaseAttachmentProvider.d.ts +4 -4
  26. package/lib/src/providers/BaseBackupProvider.d.ts +77 -0
  27. package/lib/src/providers/BaseBackupProvider.js +52 -0
  28. package/lib/src/providers/BasicAttachmentProvider.d.ts +4 -4
  29. package/lib/src/providers/FileBackupProvider.d.ts +34 -0
  30. package/lib/src/providers/FileBackupProvider.js +116 -0
  31. package/lib/src/providers/FileConsentProvider.d.ts +18 -0
  32. package/lib/src/providers/SqliteDatabaseProvider.d.ts +2 -2
  33. package/lib/src/providers/SqliteDatabaseProvider.js +12 -11
  34. package/lib/src/providers/attachmentFilesBackup.d.ts +5 -4
  35. package/lib/src/providers/databaseMigrations.js +45 -0
  36. package/lib/src/security/PolicyDecisionPoint.d.ts +14 -0
  37. package/lib/src/security/PolicyInformationPoint.d.ts +8 -0
  38. package/lib/src/types/Consent.d.ts +90 -0
  39. package/lib/src/types/express.d.ts +6 -0
  40. package/lib/src/utils/addonHost.d.ts +51 -3
  41. package/lib/src/utils/addonHost.js +29 -2
  42. package/lib/src/utils/addonPageSync.d.ts +2 -2
  43. package/lib/src/utils/auditEventNames.generated.d.ts +20 -0
  44. package/lib/src/utils/auditEventNames.generated.js +20 -0
  45. package/lib/src/utils/backupCrypto.js +275 -0
  46. package/lib/src/utils/backupDocument.d.ts +36 -0
  47. package/lib/src/utils/backupDocument.js +99 -0
  48. package/lib/src/utils/browserDescription.d.ts +11 -0
  49. package/lib/src/utils/directoryBackup.d.ts +48 -48
  50. package/lib/src/utils/directoryBackup.js +291 -0
  51. package/lib/src/utils/instanceEnvBackup.d.ts +46 -0
  52. package/lib/src/utils/instanceEnvBackup.js +120 -0
  53. package/lib/src/utils/instanceEnvSecret.js +79 -0
  54. package/lib/src/utils/ipLocation.d.ts +11 -0
  55. package/lib/src/utils/schedule.js +171 -0
  56. package/lib/src/utils/secretFileMode.js +78 -0
  57. package/lib/src/utils/siteRestore.d.ts +13 -16
  58. package/lib/src/utils/siteRestore.js +33 -6
  59. package/lib/src/utils/treeArchive.d.ts +2 -0
  60. package/lib/src/utils/treeArchive.js +242 -0
  61. package/lib/src/utils/version.js +408 -0
  62. package/package.json +6 -1
  63. package/lib/src/utils/administrators.d.ts +0 -21
@@ -30,7 +30,7 @@
30
30
  * @see {@link https://github.com/jwilleke/ngdpbase/issues/396}
31
31
  */
32
32
  import BaseManager from './BaseManager.js';
33
- import type { BackupData, ManagerBackupOptions, ManagerRestoreOptions } from './BaseManager.js';
33
+ import type { BackupData, BackupFolder } from './BaseManager.js';
34
34
  import type { WikiEngine } from '../types/WikiEngine.js';
35
35
  import type { PermissionSubject } from './UserManager.js';
36
36
  import type { JobContext } from '../context/JobContext.js';
@@ -107,6 +107,12 @@ export declare function credentialLabel(label: unknown): string;
107
107
  */
108
108
  export interface PendingSignIn {
109
109
  handle: string;
110
+ /**
111
+ * The id every audit record of this sign-in's approval carries (#1532), so
112
+ * the steps read as one trail. Random and separate from the handle and the
113
+ * link token, which are secrets.
114
+ */
115
+ id: string;
110
116
  username: string;
111
117
  first: AuthenticateResult;
112
118
  /** The requesting browser's binding value (an HTTP-only cookie); every step must present it. */
@@ -115,15 +121,54 @@ export interface PendingSignIn {
115
121
  expiresAt: number;
116
122
  requestIp?: string;
117
123
  requestUserAgent?: string;
124
+ /** Roughly where the request came from ("Columbus, United States"), or null when no source says (#1532). */
125
+ requestLocation?: string | null;
118
126
  /** Private-store keys unlocked by the password, held until the sign-in completes or is dropped. */
119
127
  privateStoreHandle?: string;
120
128
  /** The second factor, once satisfied. */
121
129
  approved?: SatisfiedFactor;
122
- /** Refused by the person ("this wasn't me"). */
130
+ /** Refused by the person (Deny). */
123
131
  denied?: boolean;
124
132
  /** When the last approval message went out, for the resend limit. */
125
133
  lastSentAt?: number;
126
134
  }
135
+ /**
136
+ * What an approval request says about a waiting sign-in (#1532): one source
137
+ * for the message and the page.
138
+ */
139
+ export interface ApprovalAbout {
140
+ /** The site's name (`ngdpbase.application-name`). */
141
+ site: string;
142
+ /** When it was asked, in the account holder's own time zone. */
143
+ when: string;
144
+ /** "Chrome on Windows", or null when the browser could not be named. */
145
+ device: string | null;
146
+ /** The IP address it came from (Express's `req.ip`, so `trust-proxy` applies). */
147
+ ip?: string;
148
+ /** Roughly where ("Columbus, United States"), or null when no source says. */
149
+ location: string | null;
150
+ }
151
+ /** What the approval page shows about a waiting sign-in (#1532). */
152
+ export interface ApprovalDetails extends ApprovalAbout {
153
+ username: string;
154
+ requestedAt: number;
155
+ userAgent?: string;
156
+ /** The sentence the message says, as HTML with every value escaped. */
157
+ sentenceHtml: string;
158
+ }
159
+ /**
160
+ * The approval sentence (#1532, decided wording plus the IP and place):
161
+ * "A sign-in request for Willeke Family was sent at 2 Oct 2026, 14:02 EDT
162
+ * from Chrome on Windows at IP address 81.2.69.160, near Columbus, United
163
+ * States." `strong` marks the site and time, `text` every other value; for
164
+ * HTML both escape.
165
+ */
166
+ export declare function approvalSentence(about: ApprovalAbout, strong?: (v: string) => string, text?: (v: string) => string): string;
167
+ /** Who opened the approval page or pressed its button (#1532): recorded, never trusted for anything. */
168
+ export interface ApprovalVisitor {
169
+ ip?: string;
170
+ userAgent?: string;
171
+ }
127
172
  /** A second factor a person has enrolled and can use now. */
128
173
  export interface AvailableSecondFactor {
129
174
  id: 'email-link';
@@ -202,6 +247,18 @@ export type OutsideSignIn =
202
247
  /** The state was already used: no second session. */
203
248
  | {
204
249
  outcome: 'replayed';
250
+ }
251
+ /**
252
+ * #1532: the provider's sign-in reached only AAL1 and the account has a
253
+ * second factor: held as a pending sign-in, as after a password. The route
254
+ * binds the browser (`binding`), keeps `handle` in the session and sends the
255
+ * person to the second-factor page; `next` is where they were going.
256
+ */
257
+ | {
258
+ outcome: 'second-step';
259
+ handle: string;
260
+ binding: string;
261
+ next: string;
205
262
  };
206
263
  /** What the one completion step leaves for the route (#1743): where to go instead, when a pending identity waits to be confirmed. */
207
264
  export interface SignInCompleted {
@@ -1030,6 +1087,12 @@ declare class AuthManager extends BaseManager {
1030
1087
  privateStoreHandle?: string;
1031
1088
  returnTo?: string;
1032
1089
  }): Promise<SignInCompleted>;
1090
+ /**
1091
+ * Whether `username` has core documents to accept (#1603), asked of
1092
+ * ConsentManager. A store that cannot be read asks nothing, loudly: a
1093
+ * broken consent folder must not lock everyone out.
1094
+ */
1095
+ private termsPendingFor;
1033
1096
  /**
1034
1097
  * Start the signed-in session's lifetime (#1767), on the new session after
1035
1098
  * regeneration: `signedInAt`, from which `ngdpbase.session.max-age` caps it
@@ -1117,32 +1180,79 @@ declare class AuthManager extends BaseManager {
1117
1180
  * is not offered. Empty means the sign-in is one step, as before.
1118
1181
  */
1119
1182
  secondFactorsFor(username: string): AvailableSecondFactor[];
1183
+ /**
1184
+ * Whether a successful first step needs a second (#1532, operator
1185
+ * 2026-10-22): everyone who arrives with only a password behind them — a
1186
+ * password typed here, or an outside provider's sign-in at AAL1 — when the
1187
+ * account has a second factor it can use now. Never after an email link
1188
+ * (the second factor would go to the same inbox), and never after a sign-in
1189
+ * already at AAL2 or above (a passkey, a provider whose second factor the
1190
+ * admin trusts).
1191
+ */
1192
+ needsSecondStep(result: AuthenticateResult): boolean;
1193
+ /**
1194
+ * Roughly where `req` came from, for the approval message (#1532):
1195
+ * Cloudflare's location headers when `ngdpbase.server.behind-cloudflare`,
1196
+ * else the GeoLite2 City file at `ngdpbase.server.geolite2-city-file`, else
1197
+ * null. Read when asked, so a change applies at the next sign-in. The IP is
1198
+ * Express's `req.ip`, which honours `ngdpbase.server.trust-proxy`.
1199
+ */
1200
+ approximateLocation(req: Pick<Request, 'ip'> & {
1201
+ get?: (name: string) => string | undefined;
1202
+ }): Promise<string | null>;
1120
1203
  /** Hold a sign-in whose first factor passed, until a second factor completes it. Returns its handle. */
1121
1204
  beginTwoStep(first: AuthenticateResult, ctx: {
1122
1205
  binding: string;
1123
1206
  ip?: string;
1124
1207
  userAgent?: string;
1208
+ location?: string | null;
1125
1209
  privateStoreHandle?: string;
1126
1210
  }): Promise<string>;
1127
1211
  /** The pending sign-in, for the browser that started it; null when gone, expired, or another browser. */
1128
1212
  pendingSignIn(handle: string | undefined, binding: string | undefined): PendingSignIn | null;
1213
+ /**
1214
+ * Record one step of a sign-in's approval (#1532): every record carries the
1215
+ * pending sign-in's `id` (never its handle or the link token), the account
1216
+ * it is for, and who took the step — the IP and browser of the request, the
1217
+ * opener or the presser. `user` is the account signing in: there is no
1218
+ * session yet, so no proxy (whom to act for is chosen after sign-in).
1219
+ */
1220
+ private recordApproval;
1221
+ /**
1222
+ * HTML escaping for the approval message and page (#1532): PluginManager's
1223
+ * formatter (#1820), the one escapeHtml. Its absence is a wiring fault, not
1224
+ * a reason to send a value unescaped.
1225
+ */
1226
+ private escapeHtml;
1227
+ /** Where the sign-in asked from, as the request, send and expiry records say it. */
1228
+ private static askedFrom;
1129
1229
  /**
1130
1230
  * Email a one-time approval link for the pending sign-in (#1523, #1532).
1131
1231
  * The link only opens the approval page; the button there approves, so a
1132
1232
  * mail scanner opening links approves nothing. Limited to one per minute.
1233
+ * Each outcome is recorded: sent (`signinapproval-request`), too soon
1234
+ * (`-throttle`), or not sent (`-sendfail`).
1133
1235
  */
1134
1236
  sendEmailApproval(handle: string, binding: string): Promise<'sent' | 'too-soon' | 'unavailable' | 'failed'>;
1135
- /** What the approval page shows about the waiting sign-in; null for an unknown or used link. */
1136
- approvalDetails(token: string): {
1137
- username: string;
1138
- requestedAt: number;
1139
- ip?: string;
1140
- userAgent?: string;
1141
- } | null;
1237
+ /**
1238
+ * What the approval page shows about the waiting sign-in; null for an
1239
+ * unknown or used link. Opening the page changes nothing, but it is
1240
+ * recorded (`signinapproval-open`) with who opened it (#1532): a mail
1241
+ * scanner opens links too.
1242
+ */
1243
+ approvalDetails(token: string, opener?: ApprovalVisitor): Promise<ApprovalDetails | null>;
1244
+ /**
1245
+ * What an approval request says about a waiting sign-in (#1532), one source
1246
+ * for the message and the page: the site's name, when it was asked in the
1247
+ * account holder's own time zone (their preference, else the site default),
1248
+ * the browser and device it came from (null when the header names
1249
+ * neither), its IP address, and roughly where (null when no source says).
1250
+ */
1251
+ private approvalContext;
1142
1252
  /** Approve the waiting sign-in from its link (the button press). Single use; returns the username, or null. */
1143
- approveByToken(token: string): string | null;
1144
- /** Refuse the waiting sign-in from its link ("this wasn't me"). Returns the username, or null. */
1145
- denyByToken(token: string): string | null;
1253
+ approveByToken(token: string, presser?: ApprovalVisitor): Promise<string | null>;
1254
+ /** Refuse the waiting sign-in from its link (Deny). Returns the username, or null. */
1255
+ denyByToken(token: string, presser?: ApprovalVisitor): Promise<string | null>;
1146
1256
  /**
1147
1257
  * The finished sign-in, once its second factor is satisfied — both factors,
1148
1258
  * assessed together. Single use: the pending entry is gone after this.
@@ -1151,8 +1261,15 @@ declare class AuthManager extends BaseManager {
1151
1261
  result: AuthenticateResult;
1152
1262
  privateStoreHandle?: string;
1153
1263
  } | null;
1264
+ /**
1265
+ * The waiting browser gave up the sign-in (Cancel, #1532): dropped, its
1266
+ * keys locked, and recorded as `signinapproval-abandon`.
1267
+ */
1268
+ abandonTwoStep(handle: string, binding: string, from?: ApprovalVisitor): Promise<void>;
1154
1269
  /** Drop a pending sign-in and lock any keys it held. */
1155
1270
  dropPending(handle: string): void;
1271
+ /** A pending sign-in past its time: dropped, and recorded once as `signinapproval-expire` (#1532). */
1272
+ private expirePending;
1156
1273
  private sweepPending;
1157
1274
  /**
1158
1275
  * Start enrolling the account's email address as a second factor (#1523).
@@ -1265,25 +1382,26 @@ declare class AuthManager extends BaseManager {
1265
1382
  isEnabled(providerId: string): boolean;
1266
1383
  /** Returns all registered providers (for admin UI). */
1267
1384
  getProviders(): AuthProvider[];
1268
- /**
1269
- * The provider list, and the credentials store (#1876): passkeys, TOTP,
1270
- * verified email and phone, devices, linked identities and proxy grants,
1271
- * as its own file beside the document, __always encrypted under
1272
- * `NGDPBASE_BACKUP_KEY`__ (`<stem>.credentials.tar.enc`). Without the key the
1273
- * store is left out and the backup is reported incomplete; it is never in a
1274
- * backup unencrypted. The rows stay signed with `NGDPBASE_CREDENTIALS_KEY`,
1275
- * which is not in the backup: a site restored under another credentials key
1276
- * holds them in quarantine (#1633).
1277
- */
1278
- backup(options?: ManagerBackupOptions): Promise<BackupData>;
1279
- /**
1280
- * Stage the credentials store from a backup for the next start, with the
1281
- * rest of a site restore (#1876). A backup that left it out (no backup key
1282
- * when it was taken) stages nothing: the store on the site stays, and the
1283
- * restore's notification names it as not restored.
1284
- */
1285
- restore(backupData: BackupData, options?: ManagerRestoreOptions): Promise<void>;
1286
- /** Staged for the next start with the rest of a site restore (#1876). */
1287
- restoreIsStaged(): boolean;
1385
+ /** `ngdpbase.auth.credentials.file`, resolved: the credentials store. */
1386
+ private configuredCredentialsFile;
1387
+ /**
1388
+ * The provider list. The credentials store (#1876) — passkeys, TOTP,
1389
+ * verified email and phone, devices, linked identities and proxy grants —
1390
+ * is a declared file (`backupFolders`), which BackupManager writes beside
1391
+ * the document as `data.credentials`.
1392
+ */
1393
+ backup(): Promise<BackupData>;
1394
+ /**
1395
+ * The credentials store, as its own file beside the backup document,
1396
+ * __always encrypted under `NGDPBASE_BACKUP_KEY`__
1397
+ * (`<stem>.credentials.tar.enc`; #1876, declared since #1985). Without the
1398
+ * key it is left out and the backup is reported incomplete; it is never in
1399
+ * a backup unencrypted. A backup that left it out stages nothing: the store
1400
+ * on the site stays, and the restore's notification names it as not
1401
+ * restored. The rows stay signed with `NGDPBASE_CREDENTIALS_KEY`, which is
1402
+ * not in the backup: a site restored under another credentials key holds
1403
+ * them in quarantine (#1633).
1404
+ */
1405
+ backupFolders(): BackupFolder[];
1288
1406
  }
1289
1407
  export default AuthManager;
@@ -1,4 +1,4 @@
1
- import BaseManager, { type BackupData, type ManagerBackupOptions, type ManagerRestoreOptions } from './BaseManager.js';
1
+ import BaseManager, { type BackupData, type BackupFolder } from './BaseManager.js';
2
2
  import type { WikiEngine } from '../types/WikiEngine.js';
3
3
  import { type JobContext } from '../context/JobContext.js';
4
4
  import { type ScheduleInput } from '../utils/schedule.js';
@@ -432,21 +432,31 @@ declare class BackgroundJobManager extends BaseManager {
432
432
  */
433
433
  private settleAfterRestore;
434
434
  /**
435
- * Job state in a backup (#1876). Files (`ngdpbase.jobs.state.dir`): the
436
- * state, run history and checkpoints as `<stem>.jobs.tar` beside the
437
- * document, without the slot locks, which belong to the server that holds
438
- * them. In the database: nothing here — it is in the database backup.
435
+ * Where job state is kept in files, before this manager has started
436
+ * (#1985): null when it is in the database, held for an unavailable
437
+ * database, or a test's own provider. After the start, `stateDir`.
439
438
  */
440
- backup(options?: ManagerBackupOptions): Promise<BackupData>;
441
- /** File job state is staged for the next start with the rest of a site restore (#1876). Database job state comes back with the database. */
442
- restore(backupData: BackupData, options?: ManagerRestoreOptions): Promise<void>;
439
+ private configuredFileStateDir;
440
+ /**
441
+ * File job state (`ngdpbase.jobs.state.dir`) is a declared folder (#1876,
442
+ * #1985): the state, run history and checkpoints as `<stem>.jobs.tar`
443
+ * beside the document, without the slot locks, which belong to the server
444
+ * that holds them. A backup without it (its job state was in the database)
445
+ * stages nothing for it. Job state in the database is in the database
446
+ * backup, and comes back with it.
447
+ */
448
+ backupFolders(): BackupFolder[];
449
+ /** Where job state is when it is not a declared folder: the database, or nowhere while held (#1876). */
450
+ backup(): Promise<BackupData>;
451
+ /** Database job state comes back with the database; file job state is a declared folder BackupManager stages (#1876, #1985). */
443
452
  restoreIsStaged(): boolean;
444
453
  /**
445
- * Put staged file job state in place, at start, before it is opened
446
- * (#1876). The runs in the live state since the backup was taken are read
447
- * first, before it is replaced, for the restore's notification.
454
+ * Whether file job state was put back from a backup at this start (#1876):
455
+ * BaseManager applied it before this manager opened it (#1985). The runs
456
+ * in the state it replaced, started since the backup, are read from where
457
+ * it was kept, for the restore's notification.
448
458
  */
449
- private applyRestoreAtStart;
459
+ private noteRestoreAtStart;
450
460
  /**
451
461
  * The job runs the live application database held, since the backup, when
452
462
  * this start's restore replaced it (#1876): read from the copy DatabaseManager