@jwilleke/ngdpbase-types 5.41.1 → 5.43.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 (70) hide show
  1. package/lib/src/context/ApiContext.js +1 -1
  2. package/lib/src/context/BaseContext.d.ts +20 -20
  3. package/lib/src/context/BaseContext.js +41 -24
  4. package/lib/src/context/JobContext.d.ts +31 -0
  5. package/lib/src/context/JobContext.js +34 -1
  6. package/lib/src/managers/AddonsManager.d.ts +8 -28
  7. package/lib/src/managers/AgentTokenManager.d.ts +8 -0
  8. package/lib/src/managers/AttachmentManager.d.ts +59 -0
  9. package/lib/src/managers/AuditManager.d.ts +477 -0
  10. package/lib/src/managers/AuthManager.d.ts +247 -15
  11. package/lib/src/managers/AuthManager.js +588 -45
  12. package/lib/src/managers/ConfigurationManager.d.ts +15 -0
  13. package/lib/src/managers/ConsentManager.d.ts +15 -0
  14. package/lib/src/managers/ImportManager.d.ts +15 -0
  15. package/lib/src/managers/PageManager.d.ts +185 -23
  16. package/lib/src/managers/PolicyEvaluator.d.ts +99 -24
  17. package/lib/src/managers/ShareManager.d.ts +3 -2
  18. package/lib/src/managers/UserManager.d.ts +19 -5
  19. package/lib/src/managers/ValidationManager.d.ts +3 -1
  20. package/lib/src/providers/AgentTokenAuthProvider.js +1 -1
  21. package/lib/src/providers/BaseAttachmentProvider.d.ts +4 -0
  22. package/lib/src/providers/BaseAuditProvider.d.ts +492 -0
  23. package/lib/src/providers/BaseAuthProvider.d.ts +4 -0
  24. package/lib/src/providers/BaseCredentialsProvider.d.ts +2 -2
  25. package/lib/src/providers/BaseCredentialsProvider.js +1 -1
  26. package/lib/src/providers/BasicAttachmentProvider.d.ts +9 -0
  27. package/lib/src/providers/FileCredentialsProvider.js +1 -1
  28. package/lib/src/providers/NullAuditProvider.d.ts +82 -0
  29. package/lib/src/providers/TotpAuthProvider.d.ts +55 -0
  30. package/lib/src/providers/TotpAuthProvider.js +145 -0
  31. package/lib/src/security/PolicyDecisionPoint.d.ts +57 -1
  32. package/lib/src/security/PolicyInformationPoint.d.ts +86 -6
  33. package/lib/src/security/permissions.generated.d.ts +8 -0
  34. package/lib/src/types/Consent.d.ts +17 -4
  35. package/lib/src/types/Consent.js +11 -1
  36. package/lib/src/types/Page.d.ts +0 -7
  37. package/lib/src/types/Policy.d.ts +67 -6
  38. package/lib/src/types/Policy.js +6 -0
  39. package/lib/src/types/Provider.d.ts +18 -0
  40. package/lib/src/types/User.d.ts +6 -0
  41. package/lib/src/types/express.d.ts +22 -0
  42. package/lib/src/utils/addonPageSync.d.ts +0 -12
  43. package/lib/src/utils/auditChain.d.ts +83 -0
  44. package/lib/src/utils/auditEventNames.generated.d.ts +2 -0
  45. package/lib/src/utils/auditEventNames.generated.js +2 -0
  46. package/lib/src/utils/auditEvents.d.ts +2 -0
  47. package/lib/src/utils/auditEvents.js +3 -1
  48. package/lib/src/utils/auditHeadWitness.d.ts +97 -0
  49. package/lib/src/utils/auditLifecycle.d.ts +71 -0
  50. package/lib/src/utils/browserDescription.d.ts +18 -0
  51. package/lib/src/utils/browserDescription.js +12 -0
  52. package/lib/src/utils/frontmatterAccess.d.ts +15 -24
  53. package/lib/src/utils/legacyStoreKeys.d.ts +16 -0
  54. package/lib/src/utils/pageWritePolicies.d.ts +55 -0
  55. package/lib/src/utils/permissionCatalog.d.ts +2 -0
  56. package/lib/src/utils/permissionCatalog.js +19 -0
  57. package/lib/src/utils/policyAttributes.d.ts +141 -0
  58. package/lib/src/utils/policyAttributes.js +219 -0
  59. package/lib/src/utils/postureRecord.d.ts +73 -0
  60. package/lib/src/utils/privateStoreAccess.d.ts +24 -4
  61. package/lib/src/utils/privateStoreAccess.js +19 -4
  62. package/lib/src/utils/privateStorePath.d.ts +6 -0
  63. package/lib/src/utils/privateStorePath.js +8 -0
  64. package/lib/src/utils/securityPosture.d.ts +52 -0
  65. package/lib/src/utils/tlsConfig.d.ts +59 -0
  66. package/lib/src/utils/totpSeedCipher.d.ts +4 -0
  67. package/lib/src/utils/totpSeedCipher.js +45 -0
  68. package/lib/src/utils/username.d.ts +13 -2
  69. package/lib/src/utils/username.js +18 -3
  70. package/package.json +5 -2
@@ -19,6 +19,12 @@ export interface AgentTokenGrant {
19
19
  id: string;
20
20
  name: string;
21
21
  scopes: string[];
22
+ /**
23
+ * The assurance level stored with the token when it was made (#2261): the
24
+ * level of the minting session at that moment, never higher. Absent on a
25
+ * token made before it was stored, which then has no `subject:aal`.
26
+ */
27
+ aal?: 1 | 2 | 3;
22
28
  }
23
29
  /**
24
30
  * A person acting for another under a proxy grant (#1762 slice 2), as a
@@ -112,6 +118,12 @@ export interface PermissionSubject {
112
118
  * account's cap, since it is the actor who signs in (#1762).
113
119
  */
114
120
  aalCap?: import('../providers/BaseAuthProvider.js').Aal;
121
+ /**
122
+ * Set by the bearer middleware on a subject authenticated by a JWT that
123
+ * records no sign-in level (Authentik, #2287): its `subject:aal` is missing,
124
+ * not AAL 1. Set from the verified result, never from a request field.
125
+ */
126
+ bearerWithoutLevel?: true;
115
127
  }
116
128
  /**
117
129
  * The subject of work with no request behind it (#631, #1212).
@@ -134,6 +146,8 @@ export interface JobSubject {
134
146
  viaProxy?: ProxyClaim;
135
147
  /** #1999: the scheduled job a run acts as (`JobContext.job`). */
136
148
  job?: string;
149
+ /** #2261: the level stored with the job's request (`JobContext.aal`); absent when it stored none. */
150
+ grantedAal?: 1 | 2 | 3;
137
151
  }
138
152
  /**
139
153
  * User creation input data
@@ -426,11 +440,11 @@ declare class UserManager extends BaseManager {
426
440
  /**
427
441
  * Create a user page for a new user.
428
442
  *
429
- * The page is written AS the new user (#1462): it is their page, and the
430
- * door takes a new page's author from who is acting. Written as the admin
431
- * who created the account — or as whoever ran self-registration — the
432
- * profile would be authored by them, and `author-lock` would leave the
433
- * account's own owner unable to edit it.
443
+ * The page is the new user's (#1462): they are its author. Authored by the
444
+ * admin who created the account — or by whoever ran self-registration —
445
+ * `author-lock` would leave the account's own owner unable to edit it.
446
+ * It is written by a system job through the write door (#1815), with the
447
+ * new user named as the one it is made for.
434
448
  *
435
449
  * @param {User} user - User object
436
450
  * @param ctx - Who created the account, for the log line only
@@ -48,8 +48,10 @@ export declare const DEFAULT_SHARE_MAX_DAYS = 15;
48
48
  * - `public`: a `storageLocation` without a vault; its pages can never be
49
49
  * private.
50
50
  * - `vault`: a `storageLocation.privatestore`; its pages may be private.
51
+ * - `shared-folder`: `storageLocation.sharedfolder: true` (#2255); its pages
52
+ * live in shared folders (the `shared` category).
51
53
  */
52
- export type CategoryStored = 'none' | 'public' | 'vault';
54
+ export type CategoryStored = 'none' | 'public' | 'vault' | 'shared-folder';
53
55
  /**
54
56
  * Where an entry's pages are stored. The one rule every reader of "is this
55
57
  * system-category stored here, and can it be private" goes through. A
@@ -39,7 +39,7 @@ export class AgentTokenAuthProvider {
39
39
  return null;
40
40
  return {
41
41
  username: record.owner,
42
- viaToken: { id: record.id, name: record.name, scopes: record.scopes }
42
+ viaToken: { id: record.id, name: record.name, scopes: record.scopes, ...(record.aal ? { aal: record.aal } : {}) }
43
43
  };
44
44
  }
45
45
  }
@@ -160,6 +160,10 @@ declare abstract class BaseAttachmentProvider extends BaseProvider implements At
160
160
  filesInStoreForPage(_location: StoreFileLocation, _pageName: string): Promise<StoreFileEntry[]>;
161
161
  /** Delete a file from a store: its bytes and its index entry. Null when the store does not list it. */
162
162
  deleteFileInStore(_location: StoreFileLocation, _id: string): Promise<StoreFileEntry | null>;
163
+ /** The ids a takeout import gave a store's files that could not keep their own, as takeout id → id here (#2188). */
164
+ readStoreRenumbered(_location: StoreFileLocation): Promise<Map<string, string>>;
165
+ /** Adds takeout id → id pairs to {@link readStoreRenumbered}'s (#2188). */
166
+ noteStoreRenumbered(_location: StoreFileLocation, _pairs: ReadonlyMap<string, string>): Promise<void>;
163
167
  /**
164
168
  * Refresh internal cache/index
165
169
  * Re-scans storage and rebuilds indexes
@@ -0,0 +1,492 @@
1
+ import { AuditEvent } from '../types/index.js';
2
+ import type { ProviderInfo } from '../types/Provider.js';
3
+ import type { WikiEngine } from '../types/WikiEngine.js';
4
+ import BaseProvider, { type ProviderDurability } from './BaseProvider.js';
5
+ import { type ActorContext } from '../context/ActorContext.js';
6
+ /**
7
+ * Audit search filters
8
+ */
9
+ export interface AuditFilters {
10
+ /** Filter by username — the account acted on */
11
+ user?: string;
12
+ /** Filter by the person who actually acted, under a proxy grant (#1762) */
13
+ actor?: string;
14
+ /** Filter by event type */
15
+ eventType?: string;
16
+ /** Filter by result (allow, deny, error) */
17
+ result?: string;
18
+ /** Filter by severity level */
19
+ severity?: string;
20
+ /** Start date filter */
21
+ startDate?: Date;
22
+ /** End date filter */
23
+ endDate?: Date;
24
+ /** Filter by resource */
25
+ resource?: string;
26
+ /** Filter by action */
27
+ action?: string;
28
+ /** Maximum results to return */
29
+ limit?: number;
30
+ /** Results offset for pagination */
31
+ offset?: number;
32
+ /** Sort field */
33
+ sortBy?: string;
34
+ /** Sort order (asc/desc) */
35
+ sortOrder?: 'asc' | 'desc';
36
+ }
37
+ /**
38
+ * Audit search results
39
+ */
40
+ export interface AuditSearchResults {
41
+ /** Array of audit events */
42
+ results: AuditEvent[];
43
+ /** Total matching events */
44
+ total: number;
45
+ /** Result limit */
46
+ limit: number;
47
+ /** Result offset */
48
+ offset: number;
49
+ /** Whether more results are available */
50
+ hasMore: boolean;
51
+ }
52
+ /**
53
+ * Audit statistics
54
+ */
55
+ export interface AuditStats {
56
+ /** Total number of events */
57
+ totalEvents: number;
58
+ /** Events by type */
59
+ eventsByType?: Record<string, number>;
60
+ /** Events by result */
61
+ eventsByResult?: Record<string, number>;
62
+ /** Events by severity */
63
+ eventsBySeverity?: Record<string, number>;
64
+ /** Events by user */
65
+ eventsByUser?: Record<string, number>;
66
+ /** Recent activity entries */
67
+ recentActivity?: AuditEvent[];
68
+ /** Number of security incidents (high/critical severity) */
69
+ securityIncidents?: number;
70
+ /** Additional statistics */
71
+ [key: string]: unknown;
72
+ }
73
+ /**
74
+ * Paging for the entries about one person (#1537): the newest first.
75
+ */
76
+ export interface PersonEntriesOptions {
77
+ limit?: number;
78
+ offset?: number;
79
+ /**
80
+ * The user's identity `@id` (#2041). From that release on a row names the
81
+ * owner of its resource by `@id` in `metadata.owner`; rows written before
82
+ * name the owner's username. A row is about the user under either.
83
+ */
84
+ ownerId?: string;
85
+ }
86
+ /** Where the chain head is published (#1138), shared by every storing provider. */
87
+ export declare const WITNESS_DESTINATION_KEY = "ngdpbase.audit.chain-witness.destination";
88
+ export declare const WITNESS_INTERVAL_KEY = "ngdpbase.audit.chain-witness.interval-minutes";
89
+ /** #1122, #1537: how often retention runs after boot. Hourly is far finer than a day-scale window. */
90
+ export declare const AUDIT_RETENTION_INTERVAL_MS: number;
91
+ /**
92
+ * The shape every storing provider writes (#1119, #1537): one shape, so a
93
+ * record reads, exports and verifies the same whichever provider wrote it.
94
+ */
95
+ export interface StoredAuditRecord {
96
+ id: string;
97
+ timestamp: string;
98
+ level?: string;
99
+ eventType: string;
100
+ user?: string;
101
+ /** Who actually did it when that is not `user` — under a proxy grant (#1762). */
102
+ actor?: string;
103
+ userId?: string;
104
+ sessionId?: string;
105
+ ipAddress?: string;
106
+ userAgent?: string;
107
+ resource?: string;
108
+ resourceType?: string;
109
+ action?: string;
110
+ result?: string;
111
+ reason?: string;
112
+ policyId?: string;
113
+ policyName?: string;
114
+ context?: Record<string, unknown>;
115
+ metadata?: Record<string, unknown>;
116
+ duration?: number;
117
+ severity?: string;
118
+ }
119
+ /**
120
+ * Normalise an incoming event into the stored record shape (#1119). Runs
121
+ * BEFORE the base stamps the chain: the id and timestamp assigned here are
122
+ * part of what gets hashed, and assigning them afterwards would mean the hash
123
+ * covered a record different from the one written.
124
+ */
125
+ export declare function toStoredAuditRecord(auditEvent: Record<string, unknown>): StoredAuditRecord;
126
+ /**
127
+ * The owner of the resource a record is about (#1537, #1825): `metadata.owner`,
128
+ * where token, private-store and share records carry it today. Null when the
129
+ * record names none.
130
+ */
131
+ export declare function recordOwner(record: Record<string, unknown>): string | null;
132
+ /**
133
+ * The username of the owner of the resource a record is about (#2041):
134
+ * `metadata.ownerUser`, written beside `metadata.owner` (the identity `@id`)
135
+ * so the rows about an account still list after it is deleted. Null when the
136
+ * record names none (every row written before #2041).
137
+ */
138
+ export declare function recordOwnerUser(record: Record<string, unknown>): string | null;
139
+ /**
140
+ * Whether a record is about the user `person` (#1537): the user is its `user`
141
+ * (the account acted on, or the user represented under a proxy grant), its
142
+ * `actor` (who actually did it, under a proxy grant), or the owner of the
143
+ * resource it names ({@link recordOwner}). The owner is the user's identity
144
+ * `@id` (`ownerId`) on rows written from #2041 on, with their username
145
+ * beside it ({@link recordOwnerUser}), and their username on rows written
146
+ * before, which are never rewritten; any of them matches. The username
147
+ * beside the `@id` is what lists the rows of an account deleted since.
148
+ */
149
+ export declare function recordIsAbout(record: Record<string, unknown>, person: string, ownerId?: string): boolean;
150
+ /**
151
+ * Audit backup data
152
+ */
153
+ export interface AuditBackupData {
154
+ /** Provider name */
155
+ provider: string;
156
+ /** Initialization state */
157
+ initialized: boolean;
158
+ /** Backup timestamp */
159
+ timestamp: string;
160
+ /** Additional backup data */
161
+ [key: string]: unknown;
162
+ }
163
+ /**
164
+ * BaseAuditProvider - Abstract base class for audit providers
165
+ *
166
+ * Provides the interface that all audit providers must implement.
167
+ * Follows the provider pattern established in CacheManager, AttachmentManager, and PageManager.
168
+ *
169
+ * Audit providers implement different storage backends (file, database, cloud logging)
170
+ *
171
+ * @class BaseAuditProvider
172
+ * @abstract
173
+ *
174
+ * @property {WikiEngine} engine - Reference to the wiki engine
175
+ * @property {boolean} initialized - Whether provider has been initialized
176
+ *
177
+ * @see {@link FileAuditProvider} for file-based implementation
178
+ * @see {@link DatabaseAuditProvider} for database implementation
179
+ * @see {@link AuditManager} for usage
180
+ *
181
+ * @example
182
+ * class MyAuditProvider extends BaseAuditProvider {
183
+ * async initialize(): Promise<void> {
184
+ * const config = this.engine.getManager('ConfigurationManager');
185
+ * this.auditPath = config.getProperty('audit.path');
186
+ * this.initialized = true;
187
+ * }
188
+ *
189
+ * async logAuditEvent(event: AuditEvent): Promise<string> {
190
+ * // Implementation
191
+ * return event.id;
192
+ * }
193
+ * }
194
+ */
195
+ /** What a provider reports about itself. */
196
+ export interface AuditReport {
197
+ /** Alteration of a stored record is detectable. Deletion of the tail is not — see #1138. */
198
+ tamperEvident: boolean;
199
+ /** How records reach disk, or null when the provider has not stated it. */
200
+ durability: ProviderDurability | null;
201
+ queryable: boolean;
202
+ /**
203
+ * Where and when the chain head was last published, or null when nothing
204
+ * publishes it (#1138).
205
+ *
206
+ * NOT a boolean. The old `offBox: boolean` was hardcoded `false`, and making
207
+ * it `true` would have been a claim nothing on this machine can establish —
208
+ * no code here can verify that a path leaves the box. So this reports the
209
+ * facts and the reader judges, exactly as `durability` does after #1148.
210
+ *
211
+ * Without a witness, truncation of the tail is undetectable: removing records
212
+ * from the end breaks no link.
213
+ */
214
+ headWitness: {
215
+ destination: string;
216
+ intervalMinutes: number;
217
+ lastPublishedAt: string | null;
218
+ lastSeq: number | null;
219
+ } | null;
220
+ /**
221
+ * Whether the records are encrypted where they are stored (#1537). Stated
222
+ * only by a provider that knows: the database provider reports whether the
223
+ * application database is SQLCipher-encrypted. Absent means not stated.
224
+ */
225
+ encryptedAtRest?: boolean;
226
+ }
227
+ declare abstract class BaseAuditProvider extends BaseProvider {
228
+ /** Reference to the wiki engine */
229
+ protected engine: WikiEngine;
230
+ /** Whether provider has been initialized */
231
+ protected initialized: boolean;
232
+ /**
233
+ * Create a new audit provider
234
+ *
235
+ * @constructor
236
+ * @param {WikiEngine} engine - The WikiEngine instance
237
+ * @throws {Error} If engine is not provided
238
+ */
239
+ constructor(engine: WikiEngine);
240
+ /**
241
+ * Initialize the audit provider
242
+ *
243
+ * Implementations should load configuration from ConfigurationManager:
244
+ * const configManager = this.engine.getManager('ConfigurationManager');
245
+ * const value = configManager.getProperty('key', 'default');
246
+ *
247
+ * Do NOT read configuration files directly.
248
+ *
249
+ * @async
250
+ * @abstract
251
+ * @returns {Promise<void>}
252
+ * @throws {Error} Always throws - must be implemented by subclass
253
+ */
254
+ abstract initialize(): Promise<void>;
255
+ /**
256
+ * Get provider information
257
+ *
258
+ * @returns {ProviderInfo} Provider metadata
259
+ */
260
+ getProviderInfo(): ProviderInfo;
261
+ /**
262
+ * What this provider actually guarantees (#1119).
263
+ *
264
+ * Reportable rather than inferable. A security property that has to be
265
+ * deduced from configuration is not one an instance can be assessed on — so
266
+ * an operator or an assessor asks the system instead of reading the config
267
+ * and hoping.
268
+ *
269
+ * `tamperEvident` follows from the chain and is therefore true for every
270
+ * storing provider and false for the inert one, without a subclass having to
271
+ * remember to say so.
272
+ */
273
+ getGuarantees(): AuditReport;
274
+ /** Sequence of the last record this provider stamped (#1119). */
275
+ protected chainSeq: number;
276
+ /** Hash of the last record this provider stamped (#1119). */
277
+ protected chainPrevHash: string;
278
+ /**
279
+ * Whether this provider chains its records (#1119).
280
+ *
281
+ * True for anything that stores events. `NullAuditProvider` overrides it: it
282
+ * keeps nothing, so it has nothing to chain — a legitimate state rather than
283
+ * a hole, provided the instance cannot then CLAIM tamper evidence, which is
284
+ * what `getProviderInfo().guarantees` is for.
285
+ */
286
+ protected chainEnabled(): boolean;
287
+ /**
288
+ * Resume the chain from storage, or null to start a new one (#1119).
289
+ *
290
+ * Called once, lazily, before the first record is stamped. A provider that
291
+ * cannot resume returns null and starts a fresh chain — honest, and visible
292
+ * as a `seq` restarting at 1 rather than as a silent break.
293
+ *
294
+ * @protected
295
+ */
296
+ protected loadChainHead(): Promise<{
297
+ seq: number;
298
+ hash: string;
299
+ } | null>;
300
+ /** Whether the chain head has been resumed yet. */
301
+ private chainResumed;
302
+ /**
303
+ * Record an audit event.
304
+ *
305
+ * __Deliberately concrete and not overridable in spirit.__ Integrity is a
306
+ * property of the CONTRACT, not of one implementation: stamped in a subclass
307
+ * it would protect that subclass only, and whether an instance were
308
+ * tamper-evident would depend on which storage backend was configured. "It
309
+ * depends on your backend" is not an answer that survives an assessment.
310
+ *
311
+ * A subclass implements {@link writeEvent} — storage — and never sees the
312
+ * un-stamped record, so it cannot skip the integrity step. Adding a fifth
313
+ * provider gets tamper evidence without having to remember it.
314
+ *
315
+ * @param auditEvent - The event to record
316
+ * @returns The stored event's id
317
+ */
318
+ logAuditEvent(auditEvent: AuditEvent): Promise<string>;
319
+ /**
320
+ * Begin a new chain, recording that the old one was abandoned (#1124).
321
+ *
322
+ * __Never called automatically.__ A system that silently repairs its own
323
+ * audit chain is worse than one that stays visibly broken, so this exists
324
+ * only for an explicit operator action — `npm run audit:restart-chain`.
325
+ *
326
+ * The marker is written INTO the log as an ordinary record, which is what
327
+ * stops it being an escape hatch: an attacker who wants a clean chain has to
328
+ * leave a record saying they broke it. It does not repair the past — the
329
+ * abandoned segment stays in the file and stays unverifiable. It only says
330
+ * the discontinuity is known, by whom, and why.
331
+ *
332
+ * @param reason - Why the chain is being restarted. Recorded verbatim.
333
+ * @param ctx - Who authorised it (#1179): the operator's context, never a bare name.
334
+ * @returns The id of the marker record.
335
+ */
336
+ restartChain(reason: string, ctx: ActorContext): Promise<string>;
337
+ /** #1138: when the chain head was last published, and what was published. */
338
+ private witnessPublishedAtMs;
339
+ private witnessLast;
340
+ /**
341
+ * Publish the chain head to the configured destination (#1138), after the
342
+ * records it names are stored — never before, or the next verification
343
+ * would report truncation.
344
+ *
345
+ * The chain cannot detect truncation of its own tail — removing records from
346
+ * the end breaks no link — and it cannot be fixed locally, because an
347
+ * attacker who owns the machine owns anything the machine wrote locally. So
348
+ * the head goes to wherever the operator points it, and this code takes no
349
+ * view on whether that is genuinely off-box (D13): it reports the
350
+ * destination and never claims what the destination is (D21).
351
+ *
352
+ * Failure to publish is logged and swallowed. An instance that refuses to
353
+ * serve because it could not write a witness would be trading a working
354
+ * system for a detection property, which is the wrong way round — and the
355
+ * absence of a fresh witness is itself visible in getGuarantees().
356
+ */
357
+ protected publishChainHead(): Promise<void>;
358
+ /**
359
+ * Where the chain head is published, or null when nothing publishes it
360
+ * (#1138). Facts, not a claim — see the note on AuditReport.headWitness.
361
+ */
362
+ protected headWitnessReport(): AuditReport['headWitness'];
363
+ /**
364
+ * One export format for every provider: JSON as stored, or CSV through
365
+ * ExportManager's writer — quoting, and no cell a spreadsheet would run as a
366
+ * formula.
367
+ */
368
+ protected formatExport(results: readonly AuditEvent[], format: 'json' | 'csv'): string;
369
+ /**
370
+ * The entries about one person, newest first, a page at a time (#1537):
371
+ * those where the person is the `user`, the `actor`, or the resource's
372
+ * owner ({@link recordIsAbout}). A provider that cannot answer says so,
373
+ * rather than returning an empty page that reads as "nothing happened".
374
+ */
375
+ searchEntriesAbout(_person: string, _options?: PersonEntriesOptions): Promise<AuditSearchResults>;
376
+ /**
377
+ * The stored record with this id, or null (#1523: a remembered browser's
378
+ * row names the audit record of its sign-in, and is checked against it).
379
+ * Null too from a provider that cannot look a record up by id.
380
+ */
381
+ findRecordById(_id: string): Promise<Record<string, unknown> | null>;
382
+ /**
383
+ * Normalise an incoming event into the record that will be stored (#1119).
384
+ *
385
+ * Identity by default. A provider that assigns its own id, timestamp or
386
+ * field shape overrides this rather than doing it in {@link writeEvent},
387
+ * because the stamp must cover exactly what is written.
388
+ *
389
+ * @protected
390
+ */
391
+ protected prepareRecord(record: Record<string, unknown>): Record<string, unknown>;
392
+ /**
393
+ * Store an already-stamped record.
394
+ *
395
+ * @async
396
+ * @abstract
397
+ * @param record - The record to store, with seq/prevHash/hash already applied
398
+ * @returns {Promise<string>} Event ID
399
+ */
400
+ abstract writeEvent(record: Record<string, unknown>): Promise<string>;
401
+ /**
402
+ * Search audit logs
403
+ *
404
+ * @async
405
+ * @abstract
406
+ * @param {AuditFilters} filters - Search filters
407
+ * @param {Record<string, any>} options - Search options
408
+ * @returns {Promise<AuditSearchResults>} Search results
409
+ * @throws {Error} Always throws - must be implemented by subclass
410
+ */
411
+ abstract searchAuditLogs(filters?: AuditFilters, options?: Record<string, unknown>): Promise<AuditSearchResults>;
412
+ /**
413
+ * Get audit statistics
414
+ *
415
+ * @async
416
+ * @abstract
417
+ * @param {AuditFilters} filters - Optional filters
418
+ * @returns {Promise<AuditStats>} Audit statistics
419
+ * @throws {Error} Always throws - must be implemented by subclass
420
+ */
421
+ abstract getAuditStats(filters?: AuditFilters): Promise<AuditStats>;
422
+ /**
423
+ * Export audit logs
424
+ *
425
+ * @async
426
+ * @abstract
427
+ * @param {AuditFilters} filters - Export filters
428
+ * @param {string} format - Export format ('json', 'csv')
429
+ * @returns {Promise<string>} Exported data
430
+ * @throws {Error} Always throws - must be implemented by subclass
431
+ */
432
+ abstract exportAuditLogs(filters?: AuditFilters, format?: 'json' | 'csv'): Promise<string>;
433
+ /**
434
+ * Flush pending audit events to storage
435
+ *
436
+ * @async
437
+ * @abstract
438
+ * @returns {Promise<void>}
439
+ * @throws {Error} Always throws - must be implemented by subclass
440
+ */
441
+ abstract flush(): Promise<void>;
442
+ /**
443
+ * Clean up old audit logs based on retention policy
444
+ *
445
+ * @async
446
+ * @abstract
447
+ * @returns {Promise<void>}
448
+ * @throws {Error} Always throws - must be implemented by subclass
449
+ */
450
+ abstract cleanup(): Promise<void>;
451
+ /**
452
+ * Check if the audit provider is healthy
453
+ *
454
+ * @async
455
+ * @abstract
456
+ * @returns {Promise<boolean>} True if healthy
457
+ * @throws {Error} Always throws - must be implemented by subclass
458
+ */
459
+ abstract isHealthy(): Promise<boolean>;
460
+ /**
461
+ * Close/cleanup the audit provider
462
+ *
463
+ * @async
464
+ * @abstract
465
+ * @returns {Promise<void>}
466
+ * @throws {Error} Always throws - must be implemented by subclass
467
+ */
468
+ abstract close(): Promise<void>;
469
+ /**
470
+ * Backup audit configuration and state (optional)
471
+ *
472
+ * Default implementation provides basic backup data.
473
+ * Subclasses can override to include provider-specific data.
474
+ *
475
+ * @async
476
+ * @returns {Promise<AuditBackupData>} Backup data
477
+ */
478
+ backup(): Promise<AuditBackupData>;
479
+ /**
480
+ * Restore audit from backup (optional)
481
+ *
482
+ * Default implementation does nothing.
483
+ * Subclasses can override if they support restore functionality.
484
+ *
485
+ * @async
486
+ * @param {AuditBackupData} _backupData - Backup data
487
+ * @returns {Promise<void>}
488
+ */
489
+ restore(_backupData: AuditBackupData): Promise<void>;
490
+ }
491
+ export default BaseAuditProvider;
492
+ export { WikiEngine, ProviderInfo };
@@ -46,6 +46,8 @@ export interface AuthVerifyCredentials {
46
46
  password?: string;
47
47
  /** One-time token — used by MagicLinkAuthProvider */
48
48
  token?: string;
49
+ /** A six-digit authenticator-app code — used by TotpAuthProvider (#421). */
50
+ code?: string;
49
51
  /** The account chosen for a link to a shared address (#2073) — used by MagicLinkAuthProvider. */
50
52
  account?: string;
51
53
  /** OAuth `state` from the callback — used by OidcAuthProvider */
@@ -72,6 +74,8 @@ export interface ViaToken {
72
74
  name: string;
73
75
  /** Scope ceiling this token may act within */
74
76
  scopes: string[];
77
+ /** The assurance level stored with the token when it was made (#2261); absent on an older token. */
78
+ aal?: 1 | 2 | 3;
75
79
  }
76
80
  /**
77
81
  * Returned by verify() on success.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Passwords stay on the user record and are never copied here (decided
5
5
  * 2026-10-02). This store holds passkeys, verified email / phone addresses
6
- * and known devices (TOTP seeds once #421 is built) — one row each, many per
6
+ * known devices and TOTP seeds (#421, encrypted) — one row each, many per
7
7
  * account.
8
8
  *
9
9
  * Every row is signed with `NGDPBASE_CREDENTIALS_KEY` (the instance `.env`).
@@ -40,7 +40,7 @@ export interface CredentialRecord {
40
40
  kind: CredentialKind;
41
41
  /** What identifies it within its kind: a passkey's credential id, an address, a device token's hash, an outside identity's `iss#sub`. */
42
42
  subject: string;
43
- /** Kind-specific: a public key and counter, or empty (an encrypted TOTP seed once #421 is built). */
43
+ /** Kind-specific: a public key and counter, an encrypted TOTP seed (#421), or empty. */
44
44
  secret: string;
45
45
  /** What the person calls it — "Jim's iPhone". */
46
46
  label: string;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Passwords stay on the user record and are never copied here (decided
5
5
  * 2026-10-02). This store holds passkeys, verified email / phone addresses
6
- * and known devices (TOTP seeds once #421 is built) — one row each, many per
6
+ * known devices and TOTP seeds (#421, encrypted) — one row each, many per
7
7
  * account.
8
8
  *
9
9
  * Every row is signed with `NGDPBASE_CREDENTIALS_KEY` (the instance `.env`).
@@ -276,6 +276,15 @@ declare class BasicAttachmentProvider extends BaseAttachmentProvider implements
276
276
  private requirePagesDirectory;
277
277
  private readStoreIndex;
278
278
  private writeStoreIndex;
279
+ private storeRenumberedFile;
280
+ /**
281
+ * The ids a takeout import last gave the store's files that could not keep
282
+ * their own (#2188), as takeout id → id here. Kept apart from the index, so
283
+ * it outlives a delete of the file. Ids only.
284
+ */
285
+ readStoreRenumbered(location: StoreFileLocation): Promise<Map<string, string>>;
286
+ /** Adds `pairs` (takeout id → id here) to {@link readStoreRenumbered}'s; a later pair for an id replaces the earlier. */
287
+ noteStoreRenumbered(location: StoreFileLocation, pairs: ReadonlyMap<string, string>): Promise<void>;
279
288
  storeFileInStore(location: StoreFileLocation, bytes: Buffer, file: StoreFileInput): Promise<StoreFileEntry>;
280
289
  getFileInStore(location: StoreFileLocation, id: string): Promise<{
281
290
  entry: StoreFileEntry;
@@ -9,7 +9,7 @@
9
9
  * missing or wrong is reported and can never sign anyone in; it is kept in
10
10
  * quarantine and written back unchanged (#1633), so a lost key costs no
11
11
  * data. `scripts/retrust-credentials.ts` re-signs them on purpose.
12
- * - Nothing here is a password hash. Public keys, (when TOTP is built, #421, encrypted seeds) and token
12
+ * - Nothing here is a password hash. Public keys, encrypted TOTP seeds (#421) and token
13
13
  * hashes are low value to a reader; a writer is what signing stops.
14
14
  */
15
15
  import { createHmac, timingSafeEqual } from 'node:crypto';