@jwilleke/ngdpbase-types 0.0.0 → 5.18.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 (302) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +16 -2
  3. package/lib/packages/ngdpbase-types/index.d.ts +32 -0
  4. package/lib/packages/ngdpbase-types/testing.d.ts +43 -0
  5. package/lib/packages/ngdpbase-types/testing.js +53 -0
  6. package/lib/src/cache/CacheDependencies.d.ts +50 -0
  7. package/lib/src/cache/CacheDependencies.js +50 -0
  8. package/lib/src/cache/ICacheAdapter.d.ts +86 -0
  9. package/lib/src/cache/RegionCache.d.ts +171 -0
  10. package/lib/src/context/ActorContext.d.ts +48 -0
  11. package/lib/src/context/ActorContext.js +33 -0
  12. package/lib/src/context/ApiContext.d.ts +194 -0
  13. package/lib/src/context/ApiContext.js +273 -0
  14. package/lib/src/context/BaseContext.d.ts +110 -0
  15. package/lib/src/context/BaseContext.js +143 -0
  16. package/lib/src/context/JobContext.d.ts +153 -0
  17. package/lib/src/context/JobContext.js +144 -0
  18. package/lib/src/context/WikiContext.d.ts +404 -0
  19. package/lib/src/context/bootActions.d.ts +64 -0
  20. package/lib/src/context/bootActions.js +111 -0
  21. package/lib/src/converters/HtmlConverter.d.ts +70 -0
  22. package/lib/src/converters/IContentConverter.d.ts +94 -0
  23. package/lib/src/converters/JSPWikiConverter.d.ts +117 -0
  24. package/lib/src/converters/conversionWarning.d.ts +15 -0
  25. package/lib/src/converters/ncm/bridge.d.ts +22 -0
  26. package/lib/src/converters/ncm/existing.d.ts +23 -0
  27. package/lib/src/converters/ncm/fix/blocks.d.ts +89 -0
  28. package/lib/src/converters/ncm/fix/bulletMarkers.d.ts +21 -0
  29. package/lib/src/converters/ncm/fix/index.d.ts +36 -0
  30. package/lib/src/converters/ncm/fix/jspwikiBullets.d.ts +20 -0
  31. package/lib/src/converters/ncm/fix/jspwikiCodeMarkers.d.ts +38 -0
  32. package/lib/src/converters/ncm/fix/jspwikiHeadings.d.ts +25 -0
  33. package/lib/src/converters/ncm/fix/jspwikiIndent.d.ts +24 -0
  34. package/lib/src/converters/ncm/fix/jspwikiInsert.d.ts +21 -0
  35. package/lib/src/converters/ncm/fix/jspwikiItalic.d.ts +29 -0
  36. package/lib/src/converters/ncm/fix/jspwikiLinkLeftovers.d.ts +27 -0
  37. package/lib/src/converters/ncm/fix/moreInformationFooter.d.ts +25 -0
  38. package/lib/src/converters/ncm/fix/styleClosers.d.ts +25 -0
  39. package/lib/src/converters/ncm/fix/tightenLists.d.ts +30 -0
  40. package/lib/src/converters/ncm/fix/types.d.ts +33 -0
  41. package/lib/src/converters/ncm/footnotes.d.ts +39 -0
  42. package/lib/src/converters/ncm/images.d.ts +49 -0
  43. package/lib/src/converters/ncm/index.d.ts +20 -0
  44. package/lib/src/converters/ncm/links.d.ts +21 -0
  45. package/lib/src/converters/ncm/normalize.d.ts +39 -0
  46. package/lib/src/converters/ncm/placeholder.d.ts +25 -0
  47. package/lib/src/converters/ncm/tables.d.ts +36 -0
  48. package/lib/src/converters/ncm/types.d.ts +42 -0
  49. package/lib/src/http/egressPolicy.d.ts +52 -0
  50. package/lib/src/http/guardedFetch.d.ts +89 -0
  51. package/lib/src/http/guardedFetch.js +113 -0
  52. package/lib/src/http/guardedLookup.d.ts +48 -0
  53. package/lib/src/http/guardedLookup.js +73 -0
  54. package/lib/src/http/ssrf.d.ts +83 -0
  55. package/lib/src/http/ssrf.js +311 -0
  56. package/lib/src/mail/MailProvider.d.ts +36 -0
  57. package/lib/src/mail/NodemailerMailProvider.d.ts +26 -0
  58. package/lib/src/managers/AddonsManager.d.ts +691 -0
  59. package/lib/src/managers/AgentTokenManager.d.ts +269 -0
  60. package/lib/src/managers/AssetManager.d.ts +116 -0
  61. package/lib/src/managers/AttachmentManager.d.ts +1032 -0
  62. package/lib/src/managers/AuthManager.d.ts +1257 -0
  63. package/lib/src/managers/BackgroundJobManager.d.ts +434 -0
  64. package/lib/src/managers/BaseManager.d.ts +391 -0
  65. package/lib/src/managers/BaseManager.js +415 -0
  66. package/lib/src/managers/CacheManager.d.ts +159 -0
  67. package/lib/src/managers/CatalogManager.d.ts +235 -0
  68. package/lib/src/managers/CommentManager.d.ts +75 -0
  69. package/lib/src/managers/ConfigurationManager.d.ts +747 -0
  70. package/lib/src/managers/DatabaseManager.d.ts +89 -0
  71. package/lib/src/managers/DatabaseManager.js +236 -0
  72. package/lib/src/managers/EmailManager.d.ts +47 -0
  73. package/lib/src/managers/ExportManager.d.ts +129 -0
  74. package/lib/src/managers/ExportManager.js +420 -0
  75. package/lib/src/managers/FootnoteManager.d.ts +118 -0
  76. package/lib/src/managers/MediaManager.d.ts +279 -0
  77. package/lib/src/managers/MetricsManager.d.ts +89 -0
  78. package/lib/src/managers/NotificationManager.d.ts +165 -0
  79. package/lib/src/managers/OidcManager.d.ts +210 -0
  80. package/lib/src/managers/OrganizationManager.d.ts +107 -0
  81. package/lib/src/managers/PageManager.d.ts +1645 -0
  82. package/lib/src/managers/PersonManager.d.ts +48 -0
  83. package/lib/src/managers/PluginManager.d.ts +179 -0
  84. package/lib/src/managers/PluginManager.js +393 -0
  85. package/lib/src/managers/PolicyEvaluator.d.ts +213 -0
  86. package/lib/src/managers/RenderingManager.d.ts +358 -0
  87. package/lib/src/managers/RoleManager.d.ts +134 -0
  88. package/lib/src/managers/SearchManager.d.ts +402 -0
  89. package/lib/src/managers/ShareManager.d.ts +251 -0
  90. package/lib/src/managers/TemplateManager.d.ts +176 -0
  91. package/lib/src/managers/ThemeManager.d.ts +56 -0
  92. package/lib/src/managers/UserManager.d.ts +480 -0
  93. package/lib/src/managers/ValidationManager.d.ts +426 -0
  94. package/lib/src/managers/VariableManager.d.ts +145 -0
  95. package/lib/src/middleware/csrf.d.ts +3 -0
  96. package/lib/src/parsers/LinkParser.d.ts +457 -0
  97. package/lib/src/parsers/MarkupParser.d.ts +769 -0
  98. package/lib/src/parsers/context/ParseContext.d.ts +314 -0
  99. package/lib/src/parsers/data/emoji-map.d.ts +18 -0
  100. package/lib/src/parsers/dom/DOMBuilder.d.ts +210 -0
  101. package/lib/src/parsers/dom/DOMParser.d.ts +252 -0
  102. package/lib/src/parsers/dom/Tokenizer.d.ts +358 -0
  103. package/lib/src/parsers/dom/WikiDocument.d.ts +272 -0
  104. package/lib/src/parsers/dom/handlers/DOMLinkHandler.d.ts +260 -0
  105. package/lib/src/parsers/dom/handlers/DOMPluginHandler.d.ts +212 -0
  106. package/lib/src/parsers/dom/handlers/DOMVariableHandler.d.ts +171 -0
  107. package/lib/src/parsers/filters/BaseFilter.d.ts +269 -0
  108. package/lib/src/parsers/filters/FilterChain.d.ts +320 -0
  109. package/lib/src/parsers/handlers/BaseSyntaxHandler.d.ts +331 -0
  110. package/lib/src/parsers/handlers/HandlerRegistry.d.ts +225 -0
  111. package/lib/src/parsers/handlers/JSPWikiPreprocessor.d.ts +89 -0
  112. package/lib/src/parsers/handlers/LinkParserHandler.d.ts +80 -0
  113. package/lib/src/parsers/handlers/PluginSyntaxHandler.d.ts +78 -0
  114. package/lib/src/parsers/handlers/WikiFormHandler.d.ts +149 -0
  115. package/lib/src/parsers/handlers/WikiTagHandler.d.ts +125 -0
  116. package/lib/src/parsers/jspwikiTableRow.d.ts +39 -0
  117. package/lib/src/parsers/styleBlockSyntax.d.ts +14 -0
  118. package/lib/src/plugins/types.d.ts +79 -0
  119. package/lib/src/providers/AgentTokenAuthProvider.d.ts +40 -0
  120. package/lib/src/providers/AuthentikBearerAuthProvider.d.ts +75 -0
  121. package/lib/src/providers/BaseAttachmentProvider.d.ts +197 -0
  122. package/lib/src/providers/BaseAuthProvider.d.ts +220 -0
  123. package/lib/src/providers/BaseCacheProvider.d.ts +140 -0
  124. package/lib/src/providers/BaseCredentialsProvider.d.ts +84 -0
  125. package/lib/src/providers/BaseDatabaseProvider.d.ts +22 -0
  126. package/lib/src/providers/BaseDatabaseProvider.js +12 -0
  127. package/lib/src/providers/BaseLoggingProvider.d.ts +68 -0
  128. package/lib/src/providers/BaseLoggingProvider.js +37 -0
  129. package/lib/src/providers/BaseMediaProvider.d.ts +308 -0
  130. package/lib/src/providers/BasePageProvider.d.ts +337 -0
  131. package/lib/src/providers/BaseProvider.d.ts +98 -0
  132. package/lib/src/providers/BaseProvider.js +74 -0
  133. package/lib/src/providers/BaseSearchProvider.d.ts +362 -0
  134. package/lib/src/providers/BasicAttachmentProvider.d.ts +599 -0
  135. package/lib/src/providers/CloudflareAccessAuthProvider.d.ts +72 -0
  136. package/lib/src/providers/FileCredentialsProvider.d.ts +33 -0
  137. package/lib/src/providers/FileJobStateProvider.d.ts +58 -0
  138. package/lib/src/providers/FileLoggingProvider.d.ts +43 -0
  139. package/lib/src/providers/FileLoggingProvider.js +120 -0
  140. package/lib/src/providers/FileOidcAdapter.d.ts +25 -0
  141. package/lib/src/providers/FileSystemMediaProvider.d.ts +403 -0
  142. package/lib/src/providers/LunrSearchProvider.d.ts +280 -0
  143. package/lib/src/providers/MagicLinkAuthProvider.d.ts +125 -0
  144. package/lib/src/providers/NullCacheProvider.d.ts +72 -0
  145. package/lib/src/providers/OidcAuthProvider.d.ts +180 -0
  146. package/lib/src/providers/OidcBearerAuthProvider.d.ts +30 -0
  147. package/lib/src/providers/PasskeyAuthProvider.d.ts +58 -0
  148. package/lib/src/providers/PasswordAuthProvider.d.ts +20 -0
  149. package/lib/src/providers/SqliteDatabaseProvider.d.ts +55 -0
  150. package/lib/src/providers/SqliteDatabaseProvider.js +90 -0
  151. package/lib/src/providers/SqliteJobStateProvider.d.ts +41 -0
  152. package/lib/src/providers/databaseMigrations.d.ts +8 -0
  153. package/lib/src/providers/databaseMigrations.js +47 -0
  154. package/lib/src/providers/jobState.d.ts +99 -0
  155. package/lib/src/providers/lunrQueryToMiniSearch.d.ts +32 -0
  156. package/lib/src/providers/mediaFilenameDate.d.ts +22 -0
  157. package/lib/src/providers/sqliteLocation.d.ts +17 -0
  158. package/lib/src/providers/sqliteLocation.js +55 -0
  159. package/lib/src/providers/sqliteMigrations.d.ts +48 -0
  160. package/lib/src/providers/sqliteMigrations.js +72 -0
  161. package/lib/src/rendering/githubAlerts.d.ts +22 -0
  162. package/lib/src/rendering/htmlPolicy.d.ts +76 -0
  163. package/lib/src/rendering/markdownConverter.d.ts +73 -0
  164. package/lib/src/security/PolicyDecisionPoint.d.ts +245 -0
  165. package/lib/src/security/PolicyInformationPoint.d.ts +448 -0
  166. package/lib/src/security/pageActions.d.ts +40 -0
  167. package/lib/src/security/permissions.generated.d.ts +78 -0
  168. package/lib/src/types/Asset.d.ts +414 -0
  169. package/lib/src/types/Catalog.d.ts +61 -0
  170. package/lib/src/types/Comment.d.ts +11 -0
  171. package/lib/src/types/Config.d.ts +388 -0
  172. package/lib/src/types/Organization.d.ts +78 -0
  173. package/lib/src/types/OrganizationProvider.d.ts +38 -0
  174. package/lib/src/types/Page.d.ts +220 -0
  175. package/lib/src/types/Person.d.ts +56 -0
  176. package/lib/src/types/PersonProvider.d.ts +25 -0
  177. package/lib/src/types/Policy.d.ts +139 -0
  178. package/lib/src/types/Policy.js +17 -0
  179. package/lib/src/types/Provider.d.ts +810 -0
  180. package/lib/src/types/Role.d.ts +84 -0
  181. package/lib/src/types/RoleProvider.d.ts +35 -0
  182. package/lib/src/types/Schema.d.ts +425 -0
  183. package/lib/src/types/Share.d.ts +172 -0
  184. package/lib/src/types/Share.js +81 -0
  185. package/lib/src/types/User.d.ts +304 -0
  186. package/lib/src/types/Version.d.ts +189 -0
  187. package/lib/src/types/WikiEngine.d.ts +128 -0
  188. package/lib/src/types/express.d.ts +120 -0
  189. package/lib/src/types/express.js +1 -0
  190. package/lib/src/types/guards.d.ts +78 -0
  191. package/lib/src/types/heic-decode.d.ts +16 -0
  192. package/lib/src/types/index.d.ts +19 -0
  193. package/lib/src/types/linkedom.d.ts +77 -0
  194. package/lib/src/types/markdown-it-plugins.d.ts +21 -0
  195. package/lib/src/utils/DeltaStorage.d.ts +118 -0
  196. package/lib/src/utils/LocaleUtils.d.ts +105 -0
  197. package/lib/src/utils/LocaleUtils.js +294 -0
  198. package/lib/src/utils/PageNameMatcher.d.ts +98 -0
  199. package/lib/src/utils/PathPreflight.d.ts +38 -0
  200. package/lib/src/utils/PathPreflight.js +50 -0
  201. package/lib/src/utils/SectionUtils.d.ts +40 -0
  202. package/lib/src/utils/addonConfigLayer.d.ts +144 -0
  203. package/lib/src/utils/addonConfigLayer.js +382 -0
  204. package/lib/src/utils/addonDatabaseRules.d.ts +25 -0
  205. package/lib/src/utils/addonDatabaseRules.js +57 -0
  206. package/lib/src/utils/addonHost.d.ts +51 -0
  207. package/lib/src/utils/addonHost.js +72 -0
  208. package/lib/src/utils/addonInstaller.d.ts +109 -0
  209. package/lib/src/utils/addonLock.d.ts +37 -0
  210. package/lib/src/utils/addonLock.js +48 -0
  211. package/lib/src/utils/addonPageSync.d.ts +45 -0
  212. package/lib/src/utils/addonProvenance.d.ts +48 -0
  213. package/lib/src/utils/addonTarball.d.ts +41 -0
  214. package/lib/src/utils/addonsPathResolver.d.ts +76 -0
  215. package/lib/src/utils/addonsPathResolver.js +129 -0
  216. package/lib/src/utils/assetMetadataTags.d.ts +66 -0
  217. package/lib/src/utils/atomicWrite.d.ts +28 -0
  218. package/lib/src/utils/auditConfigChange.d.ts +57 -0
  219. package/lib/src/utils/auditEventNames.d.ts +50 -0
  220. package/lib/src/utils/auditEventNames.generated.d.ts +163 -0
  221. package/lib/src/utils/auditEventNames.generated.js +168 -0
  222. package/lib/src/utils/auditEventNames.js +49 -0
  223. package/lib/src/utils/auditEvents.d.ts +265 -0
  224. package/lib/src/utils/auditEvents.js +309 -0
  225. package/lib/src/utils/auditRegistry.d.ts +102 -0
  226. package/lib/src/utils/auditRegistry.js +168 -0
  227. package/lib/src/utils/bip39English.d.ts +2 -0
  228. package/lib/src/utils/configEnvKeys.d.ts +68 -0
  229. package/lib/src/utils/configFiles.d.ts +74 -0
  230. package/lib/src/utils/configFiles.js +150 -0
  231. package/lib/src/utils/delegation.d.ts +48 -0
  232. package/lib/src/utils/deriveUsername.d.ts +21 -0
  233. package/lib/src/utils/explainMediaPath.d.ts +123 -0
  234. package/lib/src/utils/formerTitles.d.ts +69 -0
  235. package/lib/src/utils/frontmatterAccess.d.ts +54 -0
  236. package/lib/src/utils/headlessAdminPassword.d.ts +55 -0
  237. package/lib/src/utils/imageTransform.d.ts +52 -0
  238. package/lib/src/utils/instanceEnvSecret.d.ts +51 -0
  239. package/lib/src/utils/jobSlots.d.ts +43 -0
  240. package/lib/src/utils/keywordNormalizer.d.ts +127 -0
  241. package/lib/src/utils/logger.d.ts +70 -0
  242. package/lib/src/utils/logger.js +106 -0
  243. package/lib/src/utils/magicLinkDeviceState.d.ts +95 -0
  244. package/lib/src/utils/managerUtils.d.ts +40 -0
  245. package/lib/src/utils/migrateLegacyPrivateAttachments.d.ts +15 -0
  246. package/lib/src/utils/orgFilename.d.ts +22 -0
  247. package/lib/src/utils/pageFeature.d.ts +27 -0
  248. package/lib/src/utils/pageFrontmatter.d.ts +21 -0
  249. package/lib/src/utils/pageNotes.d.ts +150 -0
  250. package/lib/src/utils/pageTitleRule.d.ts +37 -0
  251. package/lib/src/utils/pageToArticle.d.ts +59 -0
  252. package/lib/src/utils/pageUrl.d.ts +18 -0
  253. package/lib/src/utils/passwordChange.d.ts +12 -0
  254. package/lib/src/utils/passwordHash.d.ts +60 -0
  255. package/lib/src/utils/permissionCatalog.d.ts +52 -0
  256. package/lib/src/utils/pluginFormatters.d.ts +452 -0
  257. package/lib/src/utils/pluginFormatters.js +714 -0
  258. package/lib/src/utils/privateLinkRewrite.d.ts +42 -0
  259. package/lib/src/utils/privateStoreAccess.d.ts +57 -0
  260. package/lib/src/utils/privateStoreAccess.js +65 -0
  261. package/lib/src/utils/privateStoreCatalogs.d.ts +40 -0
  262. package/lib/src/utils/privateStoreCrypto.d.ts +127 -0
  263. package/lib/src/utils/privateStoreDoor.d.ts +181 -0
  264. package/lib/src/utils/privateStoreExport.d.ts +72 -0
  265. package/lib/src/utils/privateStoreFiles.d.ts +120 -0
  266. package/lib/src/utils/privateStoreMeta.d.ts +64 -0
  267. package/lib/src/utils/privateStoreMigrations.d.ts +37 -0
  268. package/lib/src/utils/privateStorePath.d.ts +259 -0
  269. package/lib/src/utils/privateStorePath.js +436 -0
  270. package/lib/src/utils/privateStoreTakeout.d.ts +76 -0
  271. package/lib/src/utils/privateStoreUnlock.d.ts +175 -0
  272. package/lib/src/utils/proxyClaim.d.ts +59 -0
  273. package/lib/src/utils/proxyClaim.js +30 -0
  274. package/lib/src/utils/redactPrivateNames.d.ts +21 -0
  275. package/lib/src/utils/redactPrivateNames.js +89 -0
  276. package/lib/src/utils/redactSecrets.d.ts +58 -0
  277. package/lib/src/utils/redactSecrets.js +171 -0
  278. package/lib/src/utils/renameLinkRewrite.d.ts +112 -0
  279. package/lib/src/utils/roleGrants.d.ts +35 -0
  280. package/lib/src/utils/safeRedirect.d.ts +32 -0
  281. package/lib/src/utils/schedule.d.ts +54 -0
  282. package/lib/src/utils/secretFileMode.d.ts +17 -0
  283. package/lib/src/utils/seededShippedPages.d.ts +45 -0
  284. package/lib/src/utils/sensitiveValues.d.ts +59 -0
  285. package/lib/src/utils/sensitiveValues.js +106 -0
  286. package/lib/src/utils/sessionEnd.d.ts +12 -0
  287. package/lib/src/utils/sessionGeneration.d.ts +24 -0
  288. package/lib/src/utils/sessionIdle.d.ts +41 -0
  289. package/lib/src/utils/sessionLimits.d.ts +68 -0
  290. package/lib/src/utils/shareLockbox.d.ts +64 -0
  291. package/lib/src/utils/sigstoreTrustedRoot.d.ts +33 -0
  292. package/lib/src/utils/sniffMime.d.ts +21 -0
  293. package/lib/src/utils/storeSearchIndex.d.ts +75 -0
  294. package/lib/src/utils/subjectMayDo.d.ts +21 -0
  295. package/lib/src/utils/systemCategoryRules.d.ts +30 -0
  296. package/lib/src/utils/systemCategoryRules.js +41 -0
  297. package/lib/src/utils/userCreateError.d.ts +62 -0
  298. package/lib/src/utils/username.d.ts +53 -0
  299. package/lib/src/utils/vaultUrlRewrite.d.ts +27 -0
  300. package/lib/src/utils/version.d.ts +84 -0
  301. package/lib/src/utils/videoFrame.d.ts +21 -0
  302. package/package.json +54 -3
@@ -0,0 +1,273 @@
1
+ 'use strict';
2
+ // #1665: the Request augmentation (req.userContext, req.session). Imported here so the
3
+ // emitted ApiContext.d.ts carries it to every add-on that typechecks against dist/.
4
+ import '../types/express.js';
5
+ import { BaseContext } from './BaseContext.js';
6
+ import { STEP_UP_REASON } from '../types/Policy.js';
7
+ // ── ApiError ────────────────────────────────────────────────────────────────
8
+ /**
9
+ * Thrown by ApiContext guard methods (requirePermission, actingUsername).
10
+ * Route handlers should catch this and forward `status` to `res.status()`.
11
+ */
12
+ export class ApiError extends Error {
13
+ status;
14
+ reauth;
15
+ constructor(status, message,
16
+ /**
17
+ * For a step-up refusal (#1745): where the person re-authenticates,
18
+ * returning to the page they were on. Send it with the 403 so the page can
19
+ * offer the way forward, as core routes do (`{ error, reauth }`).
20
+ */
21
+ reauth) {
22
+ super(message);
23
+ this.status = status;
24
+ this.reauth = reauth;
25
+ this.name = 'ApiError';
26
+ }
27
+ }
28
+ /**
29
+ * Send a refusal — the one way it reaches the client, from core routes and
30
+ * every add-on route alike (#1749). JSON is `{ success: false, error, reauth? }`;
31
+ * text is the message, then where to sign in again for a step-up refusal.
32
+ * `page` is for a link the person followed in the browser: a step-up refusal
33
+ * redirects to the re-authenticate page, a 401 to sign-in (returning here),
34
+ * anything else is the message as text. A route never writes its own refusal:
35
+ * the step-up `reauth` is easy to drop.
36
+ */
37
+ export function sendApiError(res, err, mode = 'json') {
38
+ // Only this site's re-authenticate page: an add-on's ApiError cannot send the person elsewhere.
39
+ const reauth = typeof err.reauth === 'string' && err.reauth.startsWith('/auth/reauth?') ? err.reauth : undefined;
40
+ if (mode === 'page') {
41
+ if (reauth) {
42
+ res.redirect(reauth);
43
+ return;
44
+ }
45
+ if (err.status === 401) {
46
+ res.redirect('/login?redirect=' + encodeURIComponent(res.req?.originalUrl || '/'));
47
+ return;
48
+ }
49
+ res.status(err.status).send(err.message);
50
+ return;
51
+ }
52
+ if (mode === 'text') {
53
+ res.status(err.status).send(reauth ? `${err.message}: ${reauth}` : err.message);
54
+ return;
55
+ }
56
+ res.status(err.status).json({ success: false, error: err.message, ...(reauth ? { reauth } : {}) });
57
+ }
58
+ /**
59
+ * The subject a request carries, refused when there is none (#1399).
60
+ *
61
+ * The session middleware writes one on every request — the signed-in user, or
62
+ * the anonymous PRINCIPAL it assigns when nobody has signed in — and the bearer
63
+ * and share paths overwrite it with their own. A request reaching here without
64
+ * one is a failure upstream, not an anonymous caller, and substituting anonymous
65
+ * would be the default actor security-posture P1 forbids.
66
+ */
67
+ function subjectOf(req) {
68
+ const uc = req.userContext;
69
+ if (!uc || typeof uc.username !== 'string' || !uc.username) {
70
+ throw new Error('request has no subject: the session middleware writes one on every request (security-posture P1)');
71
+ }
72
+ return uc;
73
+ }
74
+ // ── ApiContext ───────────────────────────────────────────────────────────────
75
+ export class ApiContext extends BaseContext {
76
+ /** Reference to the wiki engine */
77
+ engine;
78
+ /** Whether the caller has an active authenticated session */
79
+ isAuthenticated;
80
+ /** Caller's username, or null for anonymous */
81
+ username;
82
+ /** Caller's display name, or null */
83
+ displayName;
84
+ /** Caller's email address, or null */
85
+ email;
86
+ /** Caller's assigned roles (always an array, never undefined) */
87
+ roles;
88
+ /**
89
+ * The agent token this request arrived with, when it did (#1173).
90
+ *
91
+ * Absent before this: `from()` copied five fields off `req.userContext` and
92
+ * `viaToken` was not one of them, so every `ctx.hasPermission()` — including
93
+ * every addon API route using `requirePermission()` — resolved against the
94
+ * token OWNER's live roles with the token's scope ceiling unable to run. The
95
+ * same defect #1164 fixed in route code, reached through a context class
96
+ * instead, and invisible to a check that only scans `src/routes/`.
97
+ */
98
+ viaToken;
99
+ /** The share this request presented, when it did (#1222). Carried for the same reason as `viaToken`. */
100
+ viaShare;
101
+ /** The proxy grant this request acts under, when it does (#1762). Carried for the same reason as `viaToken`: a job started from this context keeps it. */
102
+ viaProxy;
103
+ /**
104
+ * The request's subject exactly as the middleware wrote it (#1179/#1382).
105
+ *
106
+ * The fields above are a flattened copy for convenience; this is the context
107
+ * itself, to forward to a manager door that takes one — never rebuilt from
108
+ * the fields, which is the #1173 defect. `null` for a request with no subject.
109
+ */
110
+ subject;
111
+ /**
112
+ * The address the request came from (#1179), read from the subject the
113
+ * middleware wrote — not a copy. Forwarded with the context, so a job or a
114
+ * record an add-on starts from it carries the provenance (#1667).
115
+ */
116
+ get ipAddress() {
117
+ const ip = this.subject?.ipAddress;
118
+ return typeof ip === 'string' && ip ? ip : undefined;
119
+ }
120
+ /**
121
+ * Whether the caller is signed in to a test account (#1805), read from the
122
+ * subject the middleware wrote (the account record's flag). An attribute for
123
+ * an add-on to enforce its own rules from; no policy reads it.
124
+ */
125
+ get isTest() {
126
+ return this.subject?.isTest === true;
127
+ }
128
+ constructor(engine, isAuthenticated, username, displayName, email, roles, viaToken, viaShare, viaProxy, subject = null, request = null) {
129
+ // #1399: the subject goes to BaseContext as given. The flattened fields
130
+ // below are a convenience copy for route code to READ; nothing may build a
131
+ // subject back out of them.
132
+ super(engine, subject);
133
+ this.engine = engine;
134
+ this.isAuthenticated = isAuthenticated;
135
+ this.username = username;
136
+ this.displayName = displayName;
137
+ this.email = email;
138
+ this.roles = roles;
139
+ this.viaToken = viaToken;
140
+ this.viaShare = viaShare;
141
+ this.viaProxy = viaProxy;
142
+ this.subject = subject;
143
+ this.request = request;
144
+ }
145
+ /** The request this context was built from; the step-up answer needs its return page (#1745). */
146
+ request;
147
+ /**
148
+ * Build an ApiContext from an Express request and engine reference.
149
+ *
150
+ * Reads `req.userContext` (set by the session middleware in app.ts) and
151
+ * normalises the values into a fully-typed object. Always succeeds — an
152
+ * unauthenticated request produces a context with `isAuthenticated: false`
153
+ * and an empty/anonymous roles array.
154
+ */
155
+ static from(req, engine) {
156
+ // #1212: a request with no middleware-written context is anonymous, field by field.
157
+ const uc = req.userContext ?? {};
158
+ const isAuthenticated = Boolean(uc['isAuthenticated'] ??
159
+ req.session?.isAuthenticated ??
160
+ false);
161
+ return new ApiContext(engine, isAuthenticated, (uc.username) ?? null, (uc.displayName) ?? null, (uc.email) ?? null, Array.isArray(uc.roles) ? (uc.roles) : [], uc.viaToken, uc.viaShare, uc.viaProxy,
162
+ // #1399: forward the subject the middleware wrote, as it wrote it.
163
+ subjectOf(req), req);
164
+ }
165
+ // ── Guards ────────────────────────────────────────────────────────────────
166
+ /**
167
+ * The username this request acts as, for a feature that belongs to a
168
+ * person (a journal, a reservation). Asked AFTER `requirePermission`: policy
169
+ * decides whether the caller may, this only says whether there is someone
170
+ * to act as (#1430). A permission delegated to anonymous visitors still
171
+ * finds nobody here, so the refusal is a 401, never a crash on a missing
172
+ * name.
173
+ *
174
+ * @example
175
+ * await ctx.requirePermission('page-edit');
176
+ * const username = ctx.actingUsername(); // → 401 if nobody is signed in
177
+ */
178
+ actingUsername() {
179
+ // The anonymous subject carries a username ('anonymous'); it is not a
180
+ // person a journal or a reservation can belong to.
181
+ if (!this.isAuthenticated || !this.username) {
182
+ throw new ApiError(401, 'Authentication required');
183
+ }
184
+ return this.username;
185
+ }
186
+ // #1198: `hasRole` / `requireRole` are gone. A role name is not authority
187
+ // (security-posture.md P2): it skips the policy evaluator, deny policies and
188
+ // the agent-token scope ceiling. Ask `hasPermission` / `requirePermission`.
189
+ /**
190
+ * Returns true if the caller has the given permission.
191
+ *
192
+ * Asks the PDP (`PolicyDecisionPoint.permits`, #1431 step 14) — same canonical
193
+ * `PolicyEvaluator`-backed path that `WikiContext.hasPermission` uses.
194
+ * Honors anonymous/authenticated role expansion, deny policies, resource
195
+ * patterns, and the `'All'`/`'Authenticated'` role semantics. (#630)
196
+ *
197
+ * @example
198
+ * if (await ctx.hasPermission('user-read')) { // include PII fields }
199
+ */
200
+ /**
201
+ * #1399: the door is BaseContext's, which asks UserManager with the subject
202
+ * this context was handed. This method used to rebuild that subject from the
203
+ * flattened fields above — the #1173 defect, in the class where #1173
204
+ * happened. The rebuild listed username, roles, isAuthenticated, viaToken and
205
+ * viaShare, so it dropped `privateStoreHandle` and an addon API route could
206
+ * not see the caller's own sealed pages (#1382).
207
+ *
208
+ * @example
209
+ * if (await ctx.hasPermission('user-read')) { // include PII fields }
210
+ */
211
+ // hasPermission(action) is inherited from BaseContext.
212
+ /**
213
+ * Throws unless policy grants the caller this permission.
214
+ *
215
+ * #1399: the refusal classifies itself — 401 for a caller who has not
216
+ * signed in, 403 for one who has. That is the ONE sanctioned use of
217
+ * `isAuthenticated` (security-posture P2): it never decides, it only says
218
+ * which refusal to send after policy has already said no. Before this,
219
+ * every route paired `requireAuthenticated()` with this call to get the 401,
220
+ * which read as though authentication were half the gate.
221
+ *
222
+ * #1745: a refusal for someone who HOLDS the permission is the step-up one
223
+ * (#1635) — their sign-in is not fresh enough. It carries `reauth`, built
224
+ * where core routes build it (AuthManager.askForFreshSignIn, #1758), and the prompt is
225
+ * audited there; a delegated credential gets no `reauth`. The refusal itself
226
+ * is {@link enforceDeny}'s, shared with managers that ask the PDP directly.
227
+ *
228
+ * @example
229
+ * await ctx.requirePermission('search-user'); // → 401 anonymous, 403 signed in
230
+ */
231
+ async requirePermission(permission) {
232
+ if (await this.hasPermission(permission))
233
+ return;
234
+ // The boolean door carries no reason, so whether this "no" is the step-up
235
+ // one is asked as #1745 always has: holds the permission, may not act now —
236
+ // exactly the condition under which the PDP writes STEP_UP_REASON.
237
+ const stepUp = Boolean(this.request) && await this.holdsPermission(permission);
238
+ await this.enforceDeny(permission, { permit: false, applicable: true, reason: stepUp ? STEP_UP_REASON : 'policy_deny' });
239
+ }
240
+ /**
241
+ * Turn a deny into the refusal (#1431) — the one implementation of refusal
242
+ * shaping, shared by {@link requirePermission} and every manager that asks
243
+ * the PDP itself. Always throws:
244
+ *
245
+ * - a step-up deny (`reason` {@link STEP_UP_REASON}) on a request:
246
+ * `AuthManager.askForFreshSignIn` answers and records the reauth prompt —
247
+ * `ApiError(403, …, reauth)`, or no `reauth` for a delegated credential
248
+ * (agent token, share), which cannot give a fresh sign-in;
249
+ * - any other deny (policy, or a token, share or proxy ceiling): `ApiError(401)`
250
+ * for a caller who has not signed in, `ApiError(403)` for one who has;
251
+ * - an allow: a programming error. A refusal never silently passes.
252
+ *
253
+ * @example
254
+ * const d = await pdp.decide(ctx.getActor(), { action: 'ledger-adjust', resource: { type: 'ledger-entry', id } });
255
+ * if (!d.permit) await ctx.enforceDeny('ledger-adjust', d);
256
+ */
257
+ async enforceDeny(permission, decision) {
258
+ if (decision.permit) {
259
+ throw new Error(`ApiContext.enforceDeny('${permission}') was handed an allow decision: it enforces only a deny`);
260
+ }
261
+ if (decision.reason === STEP_UP_REASON && this.request) {
262
+ // #1758: step-up is AuthManager's — the answer and its reauth-prompt record.
263
+ const authManager = this.engine.getManager('AuthManager');
264
+ const answer = authManager
265
+ ? await authManager.askForFreshSignIn(this.request, this.request.userContext, permission, 'json')
266
+ : { permission, delegated: false, error: 'A fresh sign-in is needed' };
267
+ throw new ApiError(403, answer.error, answer.reauth);
268
+ }
269
+ throw this.isAuthenticated
270
+ ? new ApiError(403, 'Forbidden')
271
+ : new ApiError(401, 'Authentication required');
272
+ }
273
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * BaseContext — the permission door, implemented once (#1399).
3
+ *
4
+ * `WikiContext`, `ApiContext` and `ParseContext` each carry the caller's
5
+ * subject and each answered "may this subject do X?" in its own way. Three
6
+ * implementations of one question is how they drift: `ApiContext.hasPermission`
7
+ * REBUILT the subject from its own flattened fields, which is the #1173 defect
8
+ * in the class where #1173 happened. The rebuild listed `username`, `roles`,
9
+ * `isAuthenticated`, `viaToken` and `viaShare` — so it silently dropped
10
+ * `privateStoreHandle`, and an addon API route could not see the caller's
11
+ * sealed pages at all (#1382).
12
+ *
13
+ * This class holds the subject __exactly as given__ and asks `UserManager`
14
+ * with it. Nothing here copies a field out of a subject and builds a new one.
15
+ *
16
+ * __Not one merged context class.__ The three keep their own state and
17
+ * lifetimes — page and theme, API errors, parse state — and merging them would
18
+ * grow optional fields that are placeholders for most callers, which is the
19
+ * "parameter nothing reads" hazard security-posture P1 names. They share this
20
+ * door and nothing else.
21
+ *
22
+ * __No `hasRole` here.__ A role name is not authority (P2): it skips the
23
+ * policy evaluator, deny policies and the agent-token and share ceilings. A
24
+ * question about a NAMED user is `RoleManager.hasRole` / `userHoldsPermission`,
25
+ * which is a lookup, not a decision.
26
+ */
27
+ import type { PermissionSubject } from '../managers/UserManager.js';
28
+ /** The engine surface this door needs — `getManager`, nothing more. */
29
+ export interface EngineLike {
30
+ getManager<T = unknown>(name: string): T | undefined | null;
31
+ }
32
+ export declare abstract class BaseContext {
33
+ /**
34
+ * The caller's subject as the middleware wrote it, or `null` for a context
35
+ * built without one (a boot-time render, a test fixture).
36
+ *
37
+ * Read it through {@link getActor}; that is the accessor callers and manager
38
+ * doors use, and it is the one place the anonymous case is decided.
39
+ */
40
+ protected readonly _subject: PermissionSubject | null;
41
+ protected readonly engineRef: EngineLike;
42
+ /**
43
+ * #636: per-instance memoisation of the permission door. The PROMISE is
44
+ * cached, not the result, so concurrent callers share one evaluation.
45
+ */
46
+ private readonly _permissionCache;
47
+ protected constructor(engine: EngineLike, subject: PermissionSubject | null);
48
+ /**
49
+ * The subject this context was handed, forwarded — never rebuilt.
50
+ *
51
+ * A caller with no session is `ANONYMOUS_SUBJECT`: a real, named caller that
52
+ * policy evaluates like any other, not an absence. That is why this returns
53
+ * a subject rather than `null` — "nobody in particular" is an answer, and
54
+ * making every call site invent its own fallback is how a default actor gets
55
+ * introduced (P1).
56
+ *
57
+ * The returned object is the stored reference. Do not copy it field by
58
+ * field: a rebuilt subject drops whatever the copier did not think of, which
59
+ * has so far been `viaToken` (#1173), `viaShare` (#1222) and
60
+ * `privateStoreHandle` (#1382).
61
+ */
62
+ getActor(): PermissionSubject;
63
+ /** True when a real subject was supplied — `getActor()` is not the anonymous fallback. */
64
+ hasSubject(): boolean;
65
+ /**
66
+ * May this caller perform `action` at all? The canonical global check,
67
+ * asked of the PDP (#1431 step 14): the token and share ceilings, then the
68
+ * policies, with deny policies and role expansion.
69
+ *
70
+ * For a check against a PAGE, ask `canAccess` on a context that has one —
71
+ * resource attributes beat global policy.
72
+ *
73
+ * #1504: with `attributes` — today `{ 'system-category': … }` — the same
74
+ * door asks about pages OF that kind, so a policy on the `system-category`
75
+ * resource type can decide (e.g. `page-public`: may this person make pages
76
+ * of this system-category public?). It is the policy question alone: unlike
77
+ * `canAccess` it walks none of one page's own tiers (private owner,
78
+ * frontmatter), so a page's owner gets no pass on it.
79
+ */
80
+ hasPermission(action: string, attributes?: {
81
+ 'system-category'?: unknown;
82
+ }): Promise<boolean>;
83
+ /**
84
+ * Does this request's subject HOLD the permission (#1635)? Policy alone,
85
+ * without the step-up freshness rule — for affordances only (show this
86
+ * button, show this section), so a person whose sign-in is not fresh still
87
+ * sees the way to the prompt. A door or action asks {@link hasPermission}.
88
+ */
89
+ holdsPermission(action: string): Promise<boolean>;
90
+ /** One answer per question for this request — `hasPermission` and `hasPermissionOn` share it. */
91
+ private cached;
92
+ /**
93
+ * A capability asked about the page it is for (#1539). For a page in a
94
+ * vault: the container rule first — the owner, never a role, the same rule
95
+ * as Tier 0 of the page tiers — then the capability with the page's
96
+ * `vault`, asked about the page itself as the page tiers do — a `page: *`
97
+ * grant does not match a vault page's name, so only a policy on the `vault`
98
+ * resource type (`vault-owner`) decides there. For any other page, `hasPermission(action)`. A route that acts on
99
+ * a named page asks this instead of the bare capability, so a vault page is
100
+ * not judged by the caller's site-wide role.
101
+ */
102
+ hasPermissionOn(action: string, pageName: string): Promise<boolean>;
103
+ /**
104
+ * The identifiers that match audience-style filters — every role, plus the
105
+ * username when there is one. Used where running the full evaluator per
106
+ * result would be too expensive (search filtering).
107
+ */
108
+ getPrincipals(): string[];
109
+ }
110
+ export default BaseContext;
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+ import { parsePrivatePageName } from '../utils/privateStorePath.js';
3
+ import { permitsInVault } from '../utils/privateStoreAccess.js';
4
+ export class BaseContext {
5
+ /**
6
+ * The caller's subject as the middleware wrote it, or `null` for a context
7
+ * built without one (a boot-time render, a test fixture).
8
+ *
9
+ * Read it through {@link getActor}; that is the accessor callers and manager
10
+ * doors use, and it is the one place the anonymous case is decided.
11
+ */
12
+ _subject;
13
+ engineRef;
14
+ /**
15
+ * #636: per-instance memoisation of the permission door. The PROMISE is
16
+ * cached, not the result, so concurrent callers share one evaluation.
17
+ */
18
+ _permissionCache = new Map();
19
+ constructor(engine, subject) {
20
+ this.engineRef = engine;
21
+ this._subject = subject;
22
+ }
23
+ /**
24
+ * The subject this context was handed, forwarded — never rebuilt.
25
+ *
26
+ * A caller with no session is `ANONYMOUS_SUBJECT`: a real, named caller that
27
+ * policy evaluates like any other, not an absence. That is why this returns
28
+ * a subject rather than `null` — "nobody in particular" is an answer, and
29
+ * making every call site invent its own fallback is how a default actor gets
30
+ * introduced (P1).
31
+ *
32
+ * The returned object is the stored reference. Do not copy it field by
33
+ * field: a rebuilt subject drops whatever the copier did not think of, which
34
+ * has so far been `viaToken` (#1173), `viaShare` (#1222) and
35
+ * `privateStoreHandle` (#1382).
36
+ */
37
+ getActor() {
38
+ if (!this._subject) {
39
+ // #1399, operator 2026-09-20: a null user is a FAILURE, not a visitor.
40
+ // Anonymous is a principal the session middleware ASSIGNS — a real
41
+ // subject with its own role and its own grants. Substituting it here for
42
+ // a missing one would turn a bug upstream into a caller that looks
43
+ // legitimate, which is the default actor P1 forbids and the precise
44
+ // reason #1418 made `req.userContext` required rather than optional.
45
+ // A caller that genuinely acts as nobody passes ANONYMOUS_SUBJECT by
46
+ // name, or a JobContext that states its reason.
47
+ throw new Error('context has no subject: the caller must be forwarded, or ANONYMOUS_SUBJECT passed by name (security-posture P1)');
48
+ }
49
+ return this._subject;
50
+ }
51
+ /** True when a real subject was supplied — `getActor()` is not the anonymous fallback. */
52
+ hasSubject() {
53
+ return this._subject !== null;
54
+ }
55
+ /**
56
+ * May this caller perform `action` at all? The canonical global check,
57
+ * asked of the PDP (#1431 step 14): the token and share ceilings, then the
58
+ * policies, with deny policies and role expansion.
59
+ *
60
+ * For a check against a PAGE, ask `canAccess` on a context that has one —
61
+ * resource attributes beat global policy.
62
+ *
63
+ * #1504: with `attributes` — today `{ 'system-category': … }` — the same
64
+ * door asks about pages OF that kind, so a policy on the `system-category`
65
+ * resource type can decide (e.g. `page-public`: may this person make pages
66
+ * of this system-category public?). It is the policy question alone: unlike
67
+ * `canAccess` it walks none of one page's own tiers (private owner,
68
+ * frontmatter), so a page's owner gets no pass on it.
69
+ */
70
+ async hasPermission(action, attributes) {
71
+ const pdp = this.engineRef.getManager('PolicyDecisionPoint');
72
+ if (!pdp)
73
+ return false;
74
+ // #637: hand over the already-resolved subject so the PDP need not
75
+ // resolve the account and its roles again.
76
+ return this.cached(attributes ? `${action}\u0000${JSON.stringify(attributes)}` : action, () => (attributes
77
+ ? pdp.decide(this.getActor(), { action, resource: { type: 'page', id: '*' }, attributes }).then((d) => d.permit)
78
+ : pdp.permits(this.getActor(), action)));
79
+ }
80
+ /**
81
+ * Does this request's subject HOLD the permission (#1635)? Policy alone,
82
+ * without the step-up freshness rule — for affordances only (show this
83
+ * button, show this section), so a person whose sign-in is not fresh still
84
+ * sees the way to the prompt. A door or action asks {@link hasPermission}.
85
+ */
86
+ async holdsPermission(action) {
87
+ const pdp = this.engineRef.getManager('PolicyDecisionPoint');
88
+ if (!pdp)
89
+ return false;
90
+ // A stand-in decider with no `holds` (test doubles) answers with `permits`,
91
+ // which applies no step-up rule there either.
92
+ return this.cached(`holds\u0000${action}`, () => (typeof pdp.holds === 'function' ? pdp.holds(this.getActor(), action) : pdp.permits(this.getActor(), action)));
93
+ }
94
+ /** One answer per question for this request — `hasPermission` and `hasPermissionOn` share it. */
95
+ cached(key, decide) {
96
+ const hit = this._permissionCache.get(key);
97
+ if (hit)
98
+ return hit;
99
+ const promise = decide();
100
+ this._permissionCache.set(key, promise);
101
+ return promise;
102
+ }
103
+ /**
104
+ * A capability asked about the page it is for (#1539). For a page in a
105
+ * vault: the container rule first — the owner, never a role, the same rule
106
+ * as Tier 0 of the page tiers — then the capability with the page's
107
+ * `vault`, asked about the page itself as the page tiers do — a `page: *`
108
+ * grant does not match a vault page's name, so only a policy on the `vault`
109
+ * resource type (`vault-owner`) decides there. For any other page, `hasPermission(action)`. A route that acts on
110
+ * a named page asks this instead of the bare capability, so a vault page is
111
+ * not judged by the caller's site-wide role.
112
+ */
113
+ async hasPermissionOn(action, pageName) {
114
+ const name = parsePrivatePageName(pageName);
115
+ if (!name)
116
+ return this.hasPermission(action);
117
+ if (!this.hasSubject())
118
+ return false;
119
+ const pdp = this.engineRef.getManager('PolicyDecisionPoint');
120
+ return this.cached(`${action}\u0000${pageName}`, () => permitsInVault(pdp, this.getActor(), action, { owner: name.owner, vault: name.store, resource: pageName }));
121
+ }
122
+ /**
123
+ * The identifiers that match audience-style filters — every role, plus the
124
+ * username when there is one. Used where running the full evaluator per
125
+ * result would be too expensive (search filtering).
126
+ */
127
+ getPrincipals() {
128
+ // Deliberately the RAW subject, not getActor(): with no subject the answer
129
+ // is an empty list, which matches nothing. Falling back to the anonymous
130
+ // subject here would hand back its role names and let a context with no
131
+ // caller match an audience — a filter must fail closed, and unlike the
132
+ // permission door there is no evaluator behind this to say no.
133
+ const subject = this._subject;
134
+ if (!subject)
135
+ return [];
136
+ const principals = Array.isArray(subject.roles) ? [...subject.roles] : [];
137
+ if (typeof subject.username === 'string' && subject.username.length > 0) {
138
+ principals.push(subject.username);
139
+ }
140
+ return principals;
141
+ }
142
+ }
143
+ export default BaseContext;