@jwilleke/ngdpbase-types 5.42.0 → 5.44.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 (74) hide show
  1. package/lib/src/context/ApiContext.d.ts +10 -3
  2. package/lib/src/context/ApiContext.js +37 -4
  3. package/lib/src/context/BaseContext.d.ts +20 -20
  4. package/lib/src/context/BaseContext.js +41 -24
  5. package/lib/src/context/JobContext.d.ts +31 -0
  6. package/lib/src/context/JobContext.js +34 -1
  7. package/lib/src/ice/runExport.d.ts +8 -3
  8. package/lib/src/ice/searchExportSource.d.ts +44 -0
  9. package/lib/src/ice/types.d.ts +6 -0
  10. package/lib/src/managers/AddonsManager.d.ts +0 -28
  11. package/lib/src/managers/AgentTokenManager.d.ts +8 -0
  12. package/lib/src/managers/AttachmentManager.d.ts +11 -0
  13. package/lib/src/managers/AuditManager.d.ts +0 -1
  14. package/lib/src/managers/AuthManager.d.ts +38 -0
  15. package/lib/src/managers/AuthManager.js +84 -12
  16. package/lib/src/managers/BackupManager.d.ts +9 -2
  17. package/lib/src/managers/BackupManager.js +42 -19
  18. package/lib/src/managers/BaseManager.d.ts +17 -3
  19. package/lib/src/managers/BaseManager.js +12 -8
  20. package/lib/src/managers/ConfigurationManager.d.ts +28 -0
  21. package/lib/src/managers/ConsentManager.d.ts +1 -1
  22. package/lib/src/managers/DatabaseManager.js +17 -8
  23. package/lib/src/managers/EmailManager.d.ts +1 -1
  24. package/lib/src/managers/ExportManager.d.ts +1 -44
  25. package/lib/src/managers/ExportManager.js +3 -115
  26. package/lib/src/managers/ImportManager.d.ts +7 -0
  27. package/lib/src/managers/PageManager.d.ts +194 -23
  28. package/lib/src/managers/PolicyEvaluator.d.ts +182 -24
  29. package/lib/src/managers/ShareManager.d.ts +3 -2
  30. package/lib/src/managers/ThemeManager.d.ts +20 -1
  31. package/lib/src/managers/UserManager.d.ts +19 -5
  32. package/lib/src/managers/ValidationManager.d.ts +3 -1
  33. package/lib/src/providers/AgentTokenAuthProvider.js +1 -1
  34. package/lib/src/providers/BaseAuthProvider.d.ts +2 -0
  35. package/lib/src/providers/FileLoggingProvider.d.ts +12 -1
  36. package/lib/src/providers/FileLoggingProvider.js +37 -4
  37. package/lib/src/security/PolicyDecisionPoint.d.ts +107 -4
  38. package/lib/src/security/PolicyInformationPoint.d.ts +113 -7
  39. package/lib/src/security/ownedRecords.d.ts +69 -0
  40. package/lib/src/security/permissions.generated.d.ts +8 -0
  41. package/lib/src/security/signedOutWrites.d.ts +69 -0
  42. package/lib/src/security/signedOutWrites.js +146 -0
  43. package/lib/src/types/Page.d.ts +0 -7
  44. package/lib/src/types/Policy.d.ts +65 -8
  45. package/lib/src/types/Policy.js +6 -0
  46. package/lib/src/types/Provider.d.ts +12 -0
  47. package/lib/src/types/User.d.ts +6 -0
  48. package/lib/src/utils/addonConfigLayer.d.ts +6 -3
  49. package/lib/src/utils/addonConfigLayer.js +61 -3
  50. package/lib/src/utils/addonHost.d.ts +25 -0
  51. package/lib/src/utils/addonHost.js +29 -1
  52. package/lib/src/utils/addonPageSync.d.ts +0 -12
  53. package/lib/src/utils/auditEventNames.generated.d.ts +2 -0
  54. package/lib/src/utils/auditEventNames.generated.js +2 -0
  55. package/lib/src/utils/auditEvents.d.ts +2 -0
  56. package/lib/src/utils/auditEvents.js +3 -1
  57. package/lib/src/utils/availability.d.ts +15 -0
  58. package/lib/src/utils/availability.js +88 -0
  59. package/lib/src/utils/frontmatterAccess.d.ts +15 -24
  60. package/lib/src/utils/legacyStoreKeys.d.ts +16 -0
  61. package/lib/src/utils/pageTitleConflict.d.ts +32 -0
  62. package/lib/src/utils/pageWritePolicies.d.ts +74 -0
  63. package/lib/src/utils/permissionCatalog.d.ts +2 -0
  64. package/lib/src/utils/permissionCatalog.js +19 -0
  65. package/lib/src/utils/plainAttention.d.ts +19 -0
  66. package/lib/src/utils/plainAttention.js +18 -0
  67. package/lib/src/utils/policyAttributes.d.ts +232 -0
  68. package/lib/src/utils/policyAttributes.js +404 -0
  69. package/lib/src/utils/privateStoreAccess.d.ts +24 -4
  70. package/lib/src/utils/privateStoreAccess.js +19 -4
  71. package/lib/src/utils/roleGrants.d.ts +1 -1
  72. package/lib/src/utils/username.d.ts +13 -2
  73. package/lib/src/utils/username.js +18 -3
  74. package/package.json +7 -7
@@ -1,6 +1,8 @@
1
1
  import BaseManager from './BaseManager.js';
2
- import type { Policy, PolicySubject, PolicyResource } from '../types/Policy.js';
2
+ import type { Policy, PolicySubject, PolicyResource, PolicyCondition } from '../types/Policy.js';
3
+ import { type DeclaredEnvironmentAttribute, type DeclaredSubjectAttribute } from '../utils/policyAttributes.js';
3
4
  import { WikiEngine } from '../types/WikiEngine.js';
5
+ import type { ActorContext } from '../context/ActorContext.js';
4
6
  /**
5
7
  * User context for policy evaluation
6
8
  */
@@ -17,15 +19,61 @@ interface UserContext {
17
19
  * PIP (#1504): today a page's `system-category`.
18
20
  */
19
21
  export type ResourceAttributes = Record<string, unknown>;
22
+ /**
23
+ * Reads one attribute a condition names, `subject:id` or `resource:owner`
24
+ * (#2255). Supplied by the PDP from the PIP; `undefined` or `null` is a
25
+ * missing attribute. Asked only when a matching policy has a condition. A
26
+ * reader may be async (an identity is resolved through IdentityManager), and
27
+ * the evaluator awaits every read.
28
+ */
29
+ export type AttributeReader = (name: string) => unknown | Promise<unknown>;
20
30
  /**
21
31
  * Access evaluation context
22
32
  */
23
33
  interface AccessContext {
24
34
  pageName: string;
35
+ /**
36
+ * The action asked. `actions` widens it: a policy granting any of them
37
+ * matches (#2255: a page write is `page-edit` or `pushed-pages-edit`).
38
+ */
25
39
  action: string;
40
+ actions?: readonly string[];
26
41
  userContext?: UserContext;
27
42
  attributes?: ResourceAttributes;
43
+ /** For a policy's `condition`; without one every condition is unknown. */
44
+ readAttribute?: AttributeReader;
28
45
  }
46
+ /**
47
+ * What is wrong with a policy's condition, one sentence each (#2255, #2261,
48
+ * #2330); empty when it is fine. Each sentence says what is wrong, then what to
49
+ * change. A node is one XACML function in lowercase: a comparison such as
50
+ * `string-equal` over two arguments, or `and`, `or` or `not` over other
51
+ * conditions, nested at most {@link MAX_CONDITION_DEPTH} deep, at most
52
+ * {@link MAX_CONDITION_NODES} function nodes in all, an `is-in` list of at most
53
+ * {@link MAX_IN_ITEMS} and an `and` or `or` of at most {@link MAX_GROUP_CHILDREN}.
54
+ * An argument is an attribute (`attributeid`, `datatype`) or a literal (`value`,
55
+ * `datatype`); `datatype` is required, must be the type the attribute is declared with
56
+ * and the type the function takes. Each attribute must be declared: a
57
+ * `subject:<name>` in `ngdpbase.access.subject-attributes` (`id` always, `aal`
58
+ * as shipped), or a `resource:<name>` declared on the resource type the policy names (the
59
+ * `page` type when it names a page, or that add-on's type when it names only
60
+ * that type, #1823), or an `environment:<name>` in `ngdpbase.access.environment-attributes`. The old
61
+ * shape (`attribute` with `equals` and the like) is a problem that shows the
62
+ * new one. A condition with a problem never holds: an allow with it does not
63
+ * apply and a deny with it does, as for a missing attribute.
64
+ */
65
+ export declare function conditionProblems(condition: unknown, pageAttributes: Record<string, {
66
+ type?: string;
67
+ }>, subjectAttributes?: Record<string, DeclaredSubjectAttribute>, environmentAttributes?: Record<string, DeclaredEnvironmentAttribute>, resourceTypeLabel?: string): string[];
68
+ /**
69
+ * Is the condition true, false or unknown (#2255, #2261)? `true` or `false`,
70
+ * or `undefined` when it cannot be told because an attribute it reads is
71
+ * missing, or an argument lacks the datatype its function takes; the caller
72
+ * fails closed on that. Each node is judged on its own: a missing attribute
73
+ * makes its comparison unknown, a true branch decides `or`, a false branch
74
+ * decides `and`, and `not` of unknown is unknown.
75
+ */
76
+ export declare function conditionHolds(condition: PolicyCondition | undefined, read: AttributeReader | undefined, depth?: number): Promise<boolean | undefined>;
29
77
  /**
30
78
  * How each policy resource type matches (#1504). The TYPES are declared in
31
79
  * configuration, `ngdpbase.access.resource-types`, and read through
@@ -40,7 +88,31 @@ export declare const RESOURCE_MATCHERS: Record<string, (pattern: string, pageNam
40
88
  * PolicyValidator accepts: a type with no matcher here would be a policy that
41
89
  * grants nothing.
42
90
  */
43
- export declare const SUBJECT_MATCHERS: Record<string, (value: string, userContext: UserContext | undefined) => boolean>;
91
+ export declare const SUBJECT_MATCHERS: Record<string, (value: string | undefined, userContext: UserContext | undefined) => boolean>;
92
+ /**
93
+ * The subject types that take no `value` (#2261), for the validator. A value
94
+ * on one is refused rather than ignored.
95
+ */
96
+ export declare const VALUELESS_SUBJECT_TYPES: readonly string[];
97
+ /**
98
+ * Which resource attributes a policy's condition may read (#1823).
99
+ *
100
+ * A policy that names exactly one add-on type (no page matcher) uses that
101
+ * type's attributes. A policy on a core type, including `system-category`,
102
+ * still reads the `page` attributes: those conditions are about the page.
103
+ */
104
+ export declare function conditionAttributeSource(resources: readonly {
105
+ type?: string;
106
+ }[] | undefined, types: Record<string, {
107
+ attributes?: Record<string, {
108
+ type?: string;
109
+ }>;
110
+ }> | null): {
111
+ attributes: Record<string, {
112
+ type?: string;
113
+ }>;
114
+ label: string;
115
+ };
44
116
  /**
45
117
  * The resource types configuration declares (#1504), for the evaluator and the
46
118
  * validator alike. When the key is not set at all — a configuration older than
@@ -56,14 +128,19 @@ interface EvaluationResult {
56
128
  allowed: boolean;
57
129
  reason: string;
58
130
  policyName: string | null;
131
+ /** The deciding policies' ids (#2255): the denies that matched, else the allows. Empty when none matched. */
132
+ policyIds: string[];
133
+ /** Allows that matched subject, resource and action but whose condition was false or unknown (#2288). Absent when none. */
134
+ conditionNotMet?: string[];
59
135
  }
60
136
  /**
61
137
  * PolicyEvaluator - Evaluates access policies against a given context.
62
138
  *
63
139
  * PolicyEvaluator mimics how JSPWiki uses Java's built-in security framework
64
140
  * (java.security) to load and evaluate security policies from a policy file.
65
- * It evaluates policies in priority order and returns the first matching policy's
66
- * decision.
141
+ * Among the policies that match, a deny overrides any allow, the
142
+ * rule-combining algorithm `deny-overrides` (#2330), and no match is a deny (#2255). Each decision names the policies
143
+ * that decided it.
67
144
  *
68
145
  * @class PolicyEvaluator
69
146
  * @extends BaseManager
@@ -85,6 +162,17 @@ interface EvaluationResult {
85
162
  */
86
163
  declare class PolicyEvaluator extends BaseManager {
87
164
  private configManager;
165
+ /** The headline of the access-policies item now up, so marking the last deny takes down this one and no other (#2355). */
166
+ private reportedReason;
167
+ /**
168
+ * Each condition checked and compiled, by the condition object configuration
169
+ * holds (#2279), kept while `ConfigurationManager.getRevision()` is
170
+ * `revision`. Any change to the configuration (a save, a reload, a reset, a
171
+ * restore, an add-on's runtime value) changes the revision, and the next
172
+ * decision starts again from nothing. The policies themselves are still read
173
+ * live for every decision; only a condition's compiled form is reused.
174
+ */
175
+ private preparedConditions;
88
176
  /**
89
177
  * The policies in force NOW. Only the PDP interprets them (#1431 step 14b);
90
178
  * this evaluator is the PDP's matching engine and asks it for the list, read
@@ -112,24 +200,73 @@ declare class PolicyEvaluator extends BaseManager {
112
200
  */
113
201
  initialize(): Promise<void>;
114
202
  /**
115
- * Evaluates all relevant policies to make an access decision.
116
- *
117
- * Policies are evaluated in priority order (highest first). The first matching
118
- * policy determines the access decision. If no policies match, access is denied.
119
- *
120
- * @async
121
- * @param {AccessContext} context - The context of the access request
122
- * @returns {Promise<EvaluationResult>} Evaluation result with decision and reason
203
+ * Say at boot what a developer should look at in the policies after #2255,
204
+ * one warning each: stored policies the upgrade rewrote, policies it left
205
+ * that now decide differently (a deny) or reach pushed pages, and conditions
206
+ * that never hold. These are log lines only; categories no policy lets anyone
207
+ * change are an upgrade check (`upgradeChecks/uneditablePages.ts`).
208
+ */
209
+ private reportPageWritePolicies;
210
+ /** The stored marks whose rule is still a deny in force (#2355). A mark for a rule since removed is not kept: the same rule added later must be shown again. */
211
+ private marksOfExistingDenies;
212
+ /** Save the marks without those of rules that are gone (#2355); nothing is written when none is. */
213
+ private dropMarksOfGoneRules;
214
+ /**
215
+ * An administrator marks a deny as checked (#2355). Kept as the rule's id and a fingerprint of
216
+ * its content in `ngdpbase.access.checked-denies`, written through ConfigurationManager with the
217
+ * person's context, which records the change. It hides the item and nothing else: no decision
218
+ * reads it. Answers `changed` when the rule is no longer what the page showed (nothing is
219
+ * saved) and `missing` when no deny has that id.
220
+ */
221
+ markDenyChecked(ctx: ActorContext, id: string, fingerprint: string): Promise<'checked' | 'changed' | 'missing'>;
222
+ /**
223
+ * Evaluates all relevant policies to make an access decision (#2255).
123
224
  *
124
- * @example
125
- * const result = await evaluator.evaluateAccess({
126
- * pageName: 'AdminPanel',
127
- * action: 'page:edit',
128
- * userContext: { username: 'user', roles: ['editor'] }
129
- * });
130
- * console.log('Allowed:', result.allowed, 'Reason:', result.reason);
225
+ * Deny overrides allow (the rule-combining algorithm, #2330): if any matching
226
+ * deny applies, the answer is deny; otherwise any matching allow permits;
227
+ * otherwise no policy decided. A policy with a `condition` applies only when it holds;
228
+ * an unknown condition (a missing attribute) keeps an allow from applying
229
+ * and lets a deny apply, so a missing attribute fails closed.
131
230
  */
132
231
  evaluateAccess(context: AccessContext): Promise<EvaluationResult>;
232
+ /**
233
+ * The policies with their conditions checked and compiled (#2279): a
234
+ * condition already compiled under the configuration in force is reused,
235
+ * and `prepare` runs only for the rest. With no configuration revision to
236
+ * key on, everything is prepared again.
237
+ */
238
+ private prepared;
239
+ /**
240
+ * Checks and compiles each policy's condition (#2261). `prepared()` keeps
241
+ * the result until the configuration changes (#2279).
242
+ */
243
+ private prepare;
244
+ /**
245
+ * Decide one add-on record type by id (#1823).
246
+ *
247
+ * Only policies that name this type are considered, and a policy with no
248
+ * resources does not match: a page grant cannot cover the type, and a
249
+ * blanket policy cannot either. The id is matched with the same globs as a
250
+ * page name, a leading dot included. A core type (`page`, `system-category`, `vault`,
251
+ * `shared-folder`) is not decided here.
252
+ */
253
+ evaluateDeclaredResource(context: {
254
+ resourceType: string;
255
+ id: string;
256
+ action: string;
257
+ actions?: readonly string[];
258
+ userContext?: UserContext;
259
+ readAttribute?: AttributeReader;
260
+ }): Promise<EvaluationResult>;
261
+ /** True when a policy resource names this type and its pattern covers the id. No resources is not a match. */
262
+ private namesDeclaredType;
263
+ private resourceTypeMap;
264
+ /**
265
+ * Deny overrides allow, over policies that already match subject, resource
266
+ * and action (#2255). Every matching deny whose condition is true or
267
+ * unknown decides; failing that, every allow whose condition is true.
268
+ */
269
+ private combine;
133
270
  /**
134
271
  * Checks if a single policy matches the given context.
135
272
  *
@@ -189,12 +326,33 @@ declare class PolicyEvaluator extends BaseManager {
189
326
  * `filter` shape, compiled once and applied to many resources.
190
327
  *
191
328
  * Policies that cannot match this subject or this action are dropped here,
192
- * once; the predicate matches resources only, in the same order as
193
- * `evaluateAccess` so first-match-wins gives the same answer. No log line
194
- * per call: a listing asks this ~18k times, and the per-policy INFO line
195
- * `evaluateAccess` writes is right for one decision and wrong for a filter.
329
+ * once; the predicate matches resources only and combines them exactly as
330
+ * `evaluateAccess` does (deny overrides allow, #2255), so a list and a
331
+ * single decision agree. No log line per call: a listing asks this ~18k
332
+ * times, and the INFO line `evaluateAccess` writes is right for one
333
+ * decision and wrong for a filter.
334
+ */
335
+ compile(userContext: UserContext | undefined, action: string, actions?: readonly string[]): (pageName: string, attributes?: ResourceAttributes, readAttribute?: AttributeReader) => Promise<EvaluationResult>;
336
+ /** The attributes declared on the `page` resource type (#2255), for checking a condition. */
337
+ declaredPageAttributes(): Record<string, {
338
+ type?: string;
339
+ }>;
340
+ /** The subject attributes a condition may read (#2261): `id` always, and those declared in `ngdpbase.access.subject-attributes`. */
341
+ declaredSubjectAttributes(): Record<string, DeclaredSubjectAttribute>;
342
+ /** The environment attributes a condition may read (#2261): those declared in `ngdpbase.access.environment-attributes`. */
343
+ declaredEnvironmentAttributes(): Record<string, DeclaredEnvironmentAttribute>;
344
+ /**
345
+ * One line for each declared `schedule:` attribute whose schedule is not in
346
+ * `ngdpbase.schedules` (the attribute is missing and a condition on it fails
347
+ * closed) or has no rules (it reads as always open), for the boot warning.
196
348
  */
197
- compile(userContext: UserContext | undefined, action: string): (pageName: string, attributes?: ResourceAttributes) => EvaluationResult;
349
+ private scheduleProblems;
350
+ /** Each policy in force whose condition has a problem, with the problems (#2255): for the boot warning. */
351
+ conditionProblemsInForce(): Array<{
352
+ id: string;
353
+ effect: Policy['effect'];
354
+ problems: string[];
355
+ }>;
198
356
  matchesResource(resources: PolicyResource[] | undefined, pageName: string, attributes?: ResourceAttributes): boolean;
199
357
  /**
200
358
  * Checks if the action matches the policy's actions.
@@ -219,8 +219,9 @@ export default class ShareManager extends BaseManager {
219
219
  * True when a page must never appear in any share:
220
220
  * - `private: true` (decision 1)
221
221
  * - `owner-only` in user-keywords (decision 1)
222
- * - `audience` or per-action `access` frontmatter (decision 3 — a share
223
- * must not silently widen an author's chosen audience)
222
+ * - `audience` frontmatter (decision 3 — a share must not silently widen
223
+ * an author's chosen audience). A page's `access` field is not read
224
+ * since #2255.
224
225
  */
225
226
  private isPageExcluded;
226
227
  /** Prepare and lock everything a link to an encrypted vault covers. `ownerCtx` must hold the vault's key. */
@@ -5,6 +5,12 @@ export interface ThemeInfo {
5
5
  author: string;
6
6
  /** Optional Google Fonts (or other) stylesheet URLs to inject in <head> */
7
7
  fonts?: string[];
8
+ /**
9
+ * Optional CSS filenames in this theme's css/ directory. Loaded after
10
+ * core.css so a site theme can adjust layout without editing the shared
11
+ * sheet. Basenames only; paths are rejected.
12
+ */
13
+ stylesheets?: string[];
8
14
  }
9
15
  export interface ThemePaths {
10
16
  /** Active theme name (folder name under themes/) */
@@ -23,6 +29,8 @@ export interface ThemePaths {
23
29
  themeInfo: ThemeInfo | null;
24
30
  /** Optional font stylesheet URLs from theme.json fonts[] */
25
31
  fontUrls: string[];
32
+ /** Theme CSS files to load after core.css. Public URLs. */
33
+ themeStylesheets: string[];
26
34
  }
27
35
  /**
28
36
  * Returns a cached ThemeManager for the given active theme + themes directory.
@@ -44,11 +52,22 @@ export declare class ThemeManager {
44
52
  constructor(activeTheme: string, themesDir: string);
45
53
  private loadThemeInfo;
46
54
  private buildPaths;
55
+ /**
56
+ * Publish theme.json stylesheets[] as /themes/<theme>/css/<file>.
57
+ * Only a basename ending in .css that exists in that directory is kept,
58
+ * so a theme cannot point the browser at another path.
59
+ */
60
+ private resolveThemeStylesheets;
47
61
  /** Resolve variables.css path — falls back to empty placeholder if theme has no custom vars */
48
62
  private resolveVariablesCssPath;
49
63
  /** Resolve favicon path: prefer favicon.svg → favicon.png → public fallback */
50
64
  private resolveFaviconPath;
51
- /** Resolve logo path: prefer logo.svg → logo.png → favicon.svg → favicon.png → public fallback */
65
+ /**
66
+ * Resolve the header logo path: prefer logo-header.svg → logo-header.png →
67
+ * logo.svg → logo.png → favicon.svg → favicon.png → public fallback.
68
+ * logo-header.* lets a theme show a different copy in the header than the
69
+ * logo.* file that pages may link to.
70
+ */
52
71
  private resolveLogoPath;
53
72
  get paths(): ThemePaths;
54
73
  /** List all available themes (subdirs of themes/ that have a theme.json) */
@@ -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
  }
@@ -74,6 +74,8 @@ export interface ViaToken {
74
74
  name: string;
75
75
  /** Scope ceiling this token may act within */
76
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;
77
79
  }
78
80
  /**
79
81
  * Returned by verify() on success.
@@ -21,6 +21,15 @@ declare class FileLoggingProvider extends BaseLoggingProvider {
21
21
  */
22
22
  createTransports(config: LoggingProviderConfig): Transport[];
23
23
  createFormat(): Logform.Format;
24
+ /** CR and LF as the visible two-character sequences `\r` and `\n`, so a value cannot start a line of its own (#2294). */
25
+ private static oneLine;
26
+ /**
27
+ * A stack keeps one frame per line, but its message portion (`Name: message`, which can span lines and can
28
+ * hold a caller-sent value) must not start a log line of its own (#2294). Only a line that begins with
29
+ * optional whitespace and `at ` starts a new output line; every other line is joined to the one before it
30
+ * with a visible `\n`.
31
+ */
32
+ private static stackLines;
24
33
  /**
25
34
  * Render whatever a caller passed as the second argument to `logger.*`.
26
35
  *
@@ -30,7 +39,9 @@ declare class FileLoggingProvider extends BaseLoggingProvider {
30
39
  * line would be noise at best.
31
40
  *
32
41
  * A `stack` is emitted on its own lines because a one-line stack is
33
- * unreadable, and reading the stack is the entire reason this exists.
42
+ * unreadable, and reading the stack is the entire reason this exists. Its
43
+ * frames keep their own lines; CR and LF in its message portion are escaped,
44
+ * as are those in the other keys and their string values.
34
45
  *
35
46
  * @param info - The winston info object for one log record
36
47
  * @returns The rendered metadata, or an empty string when there is none
@@ -45,7 +45,17 @@ class FileLoggingProvider extends BaseLoggingProvider {
45
45
  // #1461: private page titles never reach a log line or /admin/logs.
46
46
  redactPrivateNamesFormat(), format.printf((info) => {
47
47
  const ts = typeof info.timestamp === 'string' ? info.timestamp : JSON.stringify(info.timestamp);
48
- const msg = typeof info.message === 'string' ? info.message : JSON.stringify(info.message);
48
+ // #2294: a caller-sent value (a page name) in the message could carry a CR or LF and start a log line of
49
+ // its own. Escaped here, once, for every transport and every call site; the escape is visible, not removed.
50
+ // Never throws: a logger that throws breaks its caller. Any non-string message (undefined, symbol, function,
51
+ // a circular object) is coerced with String(), and a formatting error falls back to a fixed marker.
52
+ let msg;
53
+ try {
54
+ msg = FileLoggingProvider.oneLine(typeof info.message === 'string' ? info.message : String(info.message));
55
+ }
56
+ catch {
57
+ msg = '[unprintable log message]';
58
+ }
49
59
  // #1141: render the metadata too. This printf used to emit only
50
60
  // timestamp/level/message, so the second argument of every
51
61
  // `logger.error(msg, { error, stack })` call in the codebase was
@@ -57,6 +67,27 @@ class FileLoggingProvider extends BaseLoggingProvider {
57
67
  return meta ? `${ts} [${info.level}]: ${msg} ${meta}` : `${ts} [${info.level}]: ${msg}`;
58
68
  }));
59
69
  }
70
+ /** CR and LF as the visible two-character sequences `\r` and `\n`, so a value cannot start a line of its own (#2294). */
71
+ static oneLine(text) {
72
+ return text.replace(/[\r\n]/g, (c) => (c === '\r' ? '\\r' : '\\n'));
73
+ }
74
+ /**
75
+ * A stack keeps one frame per line, but its message portion (`Name: message`, which can span lines and can
76
+ * hold a caller-sent value) must not start a log line of its own (#2294). Only a line that begins with
77
+ * optional whitespace and `at ` starts a new output line; every other line is joined to the one before it
78
+ * with a visible `\n`.
79
+ */
80
+ static stackLines(stack) {
81
+ const out = [];
82
+ stack.split('\n').forEach((raw, i) => {
83
+ const line = FileLoggingProvider.oneLine(raw);
84
+ if (i === 0 || /^\s*at /.test(raw))
85
+ out.push(line);
86
+ else
87
+ out[out.length - 1] += `\\n${line}`;
88
+ });
89
+ return out.join('\n');
90
+ }
60
91
  /**
61
92
  * Render whatever a caller passed as the second argument to `logger.*`.
62
93
  *
@@ -66,7 +97,9 @@ class FileLoggingProvider extends BaseLoggingProvider {
66
97
  * line would be noise at best.
67
98
  *
68
99
  * A `stack` is emitted on its own lines because a one-line stack is
69
- * unreadable, and reading the stack is the entire reason this exists.
100
+ * unreadable, and reading the stack is the entire reason this exists. Its
101
+ * frames keep their own lines; CR and LF in its message portion are escaped,
102
+ * as are those in the other keys and their string values.
70
103
  *
71
104
  * @param info - The winston info object for one log record
72
105
  * @returns The rendered metadata, or an empty string when there is none
@@ -82,10 +115,10 @@ class FileLoggingProvider extends BaseLoggingProvider {
82
115
  if (value === undefined)
83
116
  continue;
84
117
  if (key === 'stack' && typeof value === 'string') {
85
- stack = value;
118
+ stack = FileLoggingProvider.stackLines(value);
86
119
  continue;
87
120
  }
88
- parts.push(`${key}=${typeof value === 'string' ? value : JSON.stringify(value)}`);
121
+ parts.push(`${FileLoggingProvider.oneLine(key)}=${typeof value === 'string' ? FileLoggingProvider.oneLine(value) : JSON.stringify(value)}`);
89
122
  }
90
123
  const inline = parts.length > 0 ? parts.join(' ') : '';
91
124
  if (stack) {