@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.
- package/LICENSE +201 -0
- package/README.md +16 -2
- package/lib/packages/ngdpbase-types/index.d.ts +32 -0
- package/lib/packages/ngdpbase-types/testing.d.ts +43 -0
- package/lib/packages/ngdpbase-types/testing.js +53 -0
- package/lib/src/cache/CacheDependencies.d.ts +50 -0
- package/lib/src/cache/CacheDependencies.js +50 -0
- package/lib/src/cache/ICacheAdapter.d.ts +86 -0
- package/lib/src/cache/RegionCache.d.ts +171 -0
- package/lib/src/context/ActorContext.d.ts +48 -0
- package/lib/src/context/ActorContext.js +33 -0
- package/lib/src/context/ApiContext.d.ts +194 -0
- package/lib/src/context/ApiContext.js +273 -0
- package/lib/src/context/BaseContext.d.ts +110 -0
- package/lib/src/context/BaseContext.js +143 -0
- package/lib/src/context/JobContext.d.ts +153 -0
- package/lib/src/context/JobContext.js +144 -0
- package/lib/src/context/WikiContext.d.ts +404 -0
- package/lib/src/context/bootActions.d.ts +64 -0
- package/lib/src/context/bootActions.js +111 -0
- package/lib/src/converters/HtmlConverter.d.ts +70 -0
- package/lib/src/converters/IContentConverter.d.ts +94 -0
- package/lib/src/converters/JSPWikiConverter.d.ts +117 -0
- package/lib/src/converters/conversionWarning.d.ts +15 -0
- package/lib/src/converters/ncm/bridge.d.ts +22 -0
- package/lib/src/converters/ncm/existing.d.ts +23 -0
- package/lib/src/converters/ncm/fix/blocks.d.ts +89 -0
- package/lib/src/converters/ncm/fix/bulletMarkers.d.ts +21 -0
- package/lib/src/converters/ncm/fix/index.d.ts +36 -0
- package/lib/src/converters/ncm/fix/jspwikiBullets.d.ts +20 -0
- package/lib/src/converters/ncm/fix/jspwikiCodeMarkers.d.ts +38 -0
- package/lib/src/converters/ncm/fix/jspwikiHeadings.d.ts +25 -0
- package/lib/src/converters/ncm/fix/jspwikiIndent.d.ts +24 -0
- package/lib/src/converters/ncm/fix/jspwikiInsert.d.ts +21 -0
- package/lib/src/converters/ncm/fix/jspwikiItalic.d.ts +29 -0
- package/lib/src/converters/ncm/fix/jspwikiLinkLeftovers.d.ts +27 -0
- package/lib/src/converters/ncm/fix/moreInformationFooter.d.ts +25 -0
- package/lib/src/converters/ncm/fix/styleClosers.d.ts +25 -0
- package/lib/src/converters/ncm/fix/tightenLists.d.ts +30 -0
- package/lib/src/converters/ncm/fix/types.d.ts +33 -0
- package/lib/src/converters/ncm/footnotes.d.ts +39 -0
- package/lib/src/converters/ncm/images.d.ts +49 -0
- package/lib/src/converters/ncm/index.d.ts +20 -0
- package/lib/src/converters/ncm/links.d.ts +21 -0
- package/lib/src/converters/ncm/normalize.d.ts +39 -0
- package/lib/src/converters/ncm/placeholder.d.ts +25 -0
- package/lib/src/converters/ncm/tables.d.ts +36 -0
- package/lib/src/converters/ncm/types.d.ts +42 -0
- package/lib/src/http/egressPolicy.d.ts +52 -0
- package/lib/src/http/guardedFetch.d.ts +89 -0
- package/lib/src/http/guardedFetch.js +113 -0
- package/lib/src/http/guardedLookup.d.ts +48 -0
- package/lib/src/http/guardedLookup.js +73 -0
- package/lib/src/http/ssrf.d.ts +83 -0
- package/lib/src/http/ssrf.js +311 -0
- package/lib/src/mail/MailProvider.d.ts +36 -0
- package/lib/src/mail/NodemailerMailProvider.d.ts +26 -0
- package/lib/src/managers/AddonsManager.d.ts +691 -0
- package/lib/src/managers/AgentTokenManager.d.ts +269 -0
- package/lib/src/managers/AssetManager.d.ts +116 -0
- package/lib/src/managers/AttachmentManager.d.ts +1032 -0
- package/lib/src/managers/AuthManager.d.ts +1257 -0
- package/lib/src/managers/BackgroundJobManager.d.ts +434 -0
- package/lib/src/managers/BaseManager.d.ts +391 -0
- package/lib/src/managers/BaseManager.js +415 -0
- package/lib/src/managers/CacheManager.d.ts +159 -0
- package/lib/src/managers/CatalogManager.d.ts +235 -0
- package/lib/src/managers/CommentManager.d.ts +75 -0
- package/lib/src/managers/ConfigurationManager.d.ts +747 -0
- package/lib/src/managers/DatabaseManager.d.ts +89 -0
- package/lib/src/managers/DatabaseManager.js +236 -0
- package/lib/src/managers/EmailManager.d.ts +47 -0
- package/lib/src/managers/ExportManager.d.ts +129 -0
- package/lib/src/managers/ExportManager.js +420 -0
- package/lib/src/managers/FootnoteManager.d.ts +118 -0
- package/lib/src/managers/MediaManager.d.ts +279 -0
- package/lib/src/managers/MetricsManager.d.ts +89 -0
- package/lib/src/managers/NotificationManager.d.ts +165 -0
- package/lib/src/managers/OidcManager.d.ts +210 -0
- package/lib/src/managers/OrganizationManager.d.ts +107 -0
- package/lib/src/managers/PageManager.d.ts +1645 -0
- package/lib/src/managers/PersonManager.d.ts +48 -0
- package/lib/src/managers/PluginManager.d.ts +179 -0
- package/lib/src/managers/PluginManager.js +393 -0
- package/lib/src/managers/PolicyEvaluator.d.ts +213 -0
- package/lib/src/managers/RenderingManager.d.ts +358 -0
- package/lib/src/managers/RoleManager.d.ts +134 -0
- package/lib/src/managers/SearchManager.d.ts +402 -0
- package/lib/src/managers/ShareManager.d.ts +251 -0
- package/lib/src/managers/TemplateManager.d.ts +176 -0
- package/lib/src/managers/ThemeManager.d.ts +56 -0
- package/lib/src/managers/UserManager.d.ts +480 -0
- package/lib/src/managers/ValidationManager.d.ts +426 -0
- package/lib/src/managers/VariableManager.d.ts +145 -0
- package/lib/src/middleware/csrf.d.ts +3 -0
- package/lib/src/parsers/LinkParser.d.ts +457 -0
- package/lib/src/parsers/MarkupParser.d.ts +769 -0
- package/lib/src/parsers/context/ParseContext.d.ts +314 -0
- package/lib/src/parsers/data/emoji-map.d.ts +18 -0
- package/lib/src/parsers/dom/DOMBuilder.d.ts +210 -0
- package/lib/src/parsers/dom/DOMParser.d.ts +252 -0
- package/lib/src/parsers/dom/Tokenizer.d.ts +358 -0
- package/lib/src/parsers/dom/WikiDocument.d.ts +272 -0
- package/lib/src/parsers/dom/handlers/DOMLinkHandler.d.ts +260 -0
- package/lib/src/parsers/dom/handlers/DOMPluginHandler.d.ts +212 -0
- package/lib/src/parsers/dom/handlers/DOMVariableHandler.d.ts +171 -0
- package/lib/src/parsers/filters/BaseFilter.d.ts +269 -0
- package/lib/src/parsers/filters/FilterChain.d.ts +320 -0
- package/lib/src/parsers/handlers/BaseSyntaxHandler.d.ts +331 -0
- package/lib/src/parsers/handlers/HandlerRegistry.d.ts +225 -0
- package/lib/src/parsers/handlers/JSPWikiPreprocessor.d.ts +89 -0
- package/lib/src/parsers/handlers/LinkParserHandler.d.ts +80 -0
- package/lib/src/parsers/handlers/PluginSyntaxHandler.d.ts +78 -0
- package/lib/src/parsers/handlers/WikiFormHandler.d.ts +149 -0
- package/lib/src/parsers/handlers/WikiTagHandler.d.ts +125 -0
- package/lib/src/parsers/jspwikiTableRow.d.ts +39 -0
- package/lib/src/parsers/styleBlockSyntax.d.ts +14 -0
- package/lib/src/plugins/types.d.ts +79 -0
- package/lib/src/providers/AgentTokenAuthProvider.d.ts +40 -0
- package/lib/src/providers/AuthentikBearerAuthProvider.d.ts +75 -0
- package/lib/src/providers/BaseAttachmentProvider.d.ts +197 -0
- package/lib/src/providers/BaseAuthProvider.d.ts +220 -0
- package/lib/src/providers/BaseCacheProvider.d.ts +140 -0
- package/lib/src/providers/BaseCredentialsProvider.d.ts +84 -0
- package/lib/src/providers/BaseDatabaseProvider.d.ts +22 -0
- package/lib/src/providers/BaseDatabaseProvider.js +12 -0
- package/lib/src/providers/BaseLoggingProvider.d.ts +68 -0
- package/lib/src/providers/BaseLoggingProvider.js +37 -0
- package/lib/src/providers/BaseMediaProvider.d.ts +308 -0
- package/lib/src/providers/BasePageProvider.d.ts +337 -0
- package/lib/src/providers/BaseProvider.d.ts +98 -0
- package/lib/src/providers/BaseProvider.js +74 -0
- package/lib/src/providers/BaseSearchProvider.d.ts +362 -0
- package/lib/src/providers/BasicAttachmentProvider.d.ts +599 -0
- package/lib/src/providers/CloudflareAccessAuthProvider.d.ts +72 -0
- package/lib/src/providers/FileCredentialsProvider.d.ts +33 -0
- package/lib/src/providers/FileJobStateProvider.d.ts +58 -0
- package/lib/src/providers/FileLoggingProvider.d.ts +43 -0
- package/lib/src/providers/FileLoggingProvider.js +120 -0
- package/lib/src/providers/FileOidcAdapter.d.ts +25 -0
- package/lib/src/providers/FileSystemMediaProvider.d.ts +403 -0
- package/lib/src/providers/LunrSearchProvider.d.ts +280 -0
- package/lib/src/providers/MagicLinkAuthProvider.d.ts +125 -0
- package/lib/src/providers/NullCacheProvider.d.ts +72 -0
- package/lib/src/providers/OidcAuthProvider.d.ts +180 -0
- package/lib/src/providers/OidcBearerAuthProvider.d.ts +30 -0
- package/lib/src/providers/PasskeyAuthProvider.d.ts +58 -0
- package/lib/src/providers/PasswordAuthProvider.d.ts +20 -0
- package/lib/src/providers/SqliteDatabaseProvider.d.ts +55 -0
- package/lib/src/providers/SqliteDatabaseProvider.js +90 -0
- package/lib/src/providers/SqliteJobStateProvider.d.ts +41 -0
- package/lib/src/providers/databaseMigrations.d.ts +8 -0
- package/lib/src/providers/databaseMigrations.js +47 -0
- package/lib/src/providers/jobState.d.ts +99 -0
- package/lib/src/providers/lunrQueryToMiniSearch.d.ts +32 -0
- package/lib/src/providers/mediaFilenameDate.d.ts +22 -0
- package/lib/src/providers/sqliteLocation.d.ts +17 -0
- package/lib/src/providers/sqliteLocation.js +55 -0
- package/lib/src/providers/sqliteMigrations.d.ts +48 -0
- package/lib/src/providers/sqliteMigrations.js +72 -0
- package/lib/src/rendering/githubAlerts.d.ts +22 -0
- package/lib/src/rendering/htmlPolicy.d.ts +76 -0
- package/lib/src/rendering/markdownConverter.d.ts +73 -0
- package/lib/src/security/PolicyDecisionPoint.d.ts +245 -0
- package/lib/src/security/PolicyInformationPoint.d.ts +448 -0
- package/lib/src/security/pageActions.d.ts +40 -0
- package/lib/src/security/permissions.generated.d.ts +78 -0
- package/lib/src/types/Asset.d.ts +414 -0
- package/lib/src/types/Catalog.d.ts +61 -0
- package/lib/src/types/Comment.d.ts +11 -0
- package/lib/src/types/Config.d.ts +388 -0
- package/lib/src/types/Organization.d.ts +78 -0
- package/lib/src/types/OrganizationProvider.d.ts +38 -0
- package/lib/src/types/Page.d.ts +220 -0
- package/lib/src/types/Person.d.ts +56 -0
- package/lib/src/types/PersonProvider.d.ts +25 -0
- package/lib/src/types/Policy.d.ts +139 -0
- package/lib/src/types/Policy.js +17 -0
- package/lib/src/types/Provider.d.ts +810 -0
- package/lib/src/types/Role.d.ts +84 -0
- package/lib/src/types/RoleProvider.d.ts +35 -0
- package/lib/src/types/Schema.d.ts +425 -0
- package/lib/src/types/Share.d.ts +172 -0
- package/lib/src/types/Share.js +81 -0
- package/lib/src/types/User.d.ts +304 -0
- package/lib/src/types/Version.d.ts +189 -0
- package/lib/src/types/WikiEngine.d.ts +128 -0
- package/lib/src/types/express.d.ts +120 -0
- package/lib/src/types/express.js +1 -0
- package/lib/src/types/guards.d.ts +78 -0
- package/lib/src/types/heic-decode.d.ts +16 -0
- package/lib/src/types/index.d.ts +19 -0
- package/lib/src/types/linkedom.d.ts +77 -0
- package/lib/src/types/markdown-it-plugins.d.ts +21 -0
- package/lib/src/utils/DeltaStorage.d.ts +118 -0
- package/lib/src/utils/LocaleUtils.d.ts +105 -0
- package/lib/src/utils/LocaleUtils.js +294 -0
- package/lib/src/utils/PageNameMatcher.d.ts +98 -0
- package/lib/src/utils/PathPreflight.d.ts +38 -0
- package/lib/src/utils/PathPreflight.js +50 -0
- package/lib/src/utils/SectionUtils.d.ts +40 -0
- package/lib/src/utils/addonConfigLayer.d.ts +144 -0
- package/lib/src/utils/addonConfigLayer.js +382 -0
- package/lib/src/utils/addonDatabaseRules.d.ts +25 -0
- package/lib/src/utils/addonDatabaseRules.js +57 -0
- package/lib/src/utils/addonHost.d.ts +51 -0
- package/lib/src/utils/addonHost.js +72 -0
- package/lib/src/utils/addonInstaller.d.ts +109 -0
- package/lib/src/utils/addonLock.d.ts +37 -0
- package/lib/src/utils/addonLock.js +48 -0
- package/lib/src/utils/addonPageSync.d.ts +45 -0
- package/lib/src/utils/addonProvenance.d.ts +48 -0
- package/lib/src/utils/addonTarball.d.ts +41 -0
- package/lib/src/utils/addonsPathResolver.d.ts +76 -0
- package/lib/src/utils/addonsPathResolver.js +129 -0
- package/lib/src/utils/assetMetadataTags.d.ts +66 -0
- package/lib/src/utils/atomicWrite.d.ts +28 -0
- package/lib/src/utils/auditConfigChange.d.ts +57 -0
- package/lib/src/utils/auditEventNames.d.ts +50 -0
- package/lib/src/utils/auditEventNames.generated.d.ts +163 -0
- package/lib/src/utils/auditEventNames.generated.js +168 -0
- package/lib/src/utils/auditEventNames.js +49 -0
- package/lib/src/utils/auditEvents.d.ts +265 -0
- package/lib/src/utils/auditEvents.js +309 -0
- package/lib/src/utils/auditRegistry.d.ts +102 -0
- package/lib/src/utils/auditRegistry.js +168 -0
- package/lib/src/utils/bip39English.d.ts +2 -0
- package/lib/src/utils/configEnvKeys.d.ts +68 -0
- package/lib/src/utils/configFiles.d.ts +74 -0
- package/lib/src/utils/configFiles.js +150 -0
- package/lib/src/utils/delegation.d.ts +48 -0
- package/lib/src/utils/deriveUsername.d.ts +21 -0
- package/lib/src/utils/explainMediaPath.d.ts +123 -0
- package/lib/src/utils/formerTitles.d.ts +69 -0
- package/lib/src/utils/frontmatterAccess.d.ts +54 -0
- package/lib/src/utils/headlessAdminPassword.d.ts +55 -0
- package/lib/src/utils/imageTransform.d.ts +52 -0
- package/lib/src/utils/instanceEnvSecret.d.ts +51 -0
- package/lib/src/utils/jobSlots.d.ts +43 -0
- package/lib/src/utils/keywordNormalizer.d.ts +127 -0
- package/lib/src/utils/logger.d.ts +70 -0
- package/lib/src/utils/logger.js +106 -0
- package/lib/src/utils/magicLinkDeviceState.d.ts +95 -0
- package/lib/src/utils/managerUtils.d.ts +40 -0
- package/lib/src/utils/migrateLegacyPrivateAttachments.d.ts +15 -0
- package/lib/src/utils/orgFilename.d.ts +22 -0
- package/lib/src/utils/pageFeature.d.ts +27 -0
- package/lib/src/utils/pageFrontmatter.d.ts +21 -0
- package/lib/src/utils/pageNotes.d.ts +150 -0
- package/lib/src/utils/pageTitleRule.d.ts +37 -0
- package/lib/src/utils/pageToArticle.d.ts +59 -0
- package/lib/src/utils/pageUrl.d.ts +18 -0
- package/lib/src/utils/passwordChange.d.ts +12 -0
- package/lib/src/utils/passwordHash.d.ts +60 -0
- package/lib/src/utils/permissionCatalog.d.ts +52 -0
- package/lib/src/utils/pluginFormatters.d.ts +452 -0
- package/lib/src/utils/pluginFormatters.js +714 -0
- package/lib/src/utils/privateLinkRewrite.d.ts +42 -0
- package/lib/src/utils/privateStoreAccess.d.ts +57 -0
- package/lib/src/utils/privateStoreAccess.js +65 -0
- package/lib/src/utils/privateStoreCatalogs.d.ts +40 -0
- package/lib/src/utils/privateStoreCrypto.d.ts +127 -0
- package/lib/src/utils/privateStoreDoor.d.ts +181 -0
- package/lib/src/utils/privateStoreExport.d.ts +72 -0
- package/lib/src/utils/privateStoreFiles.d.ts +120 -0
- package/lib/src/utils/privateStoreMeta.d.ts +64 -0
- package/lib/src/utils/privateStoreMigrations.d.ts +37 -0
- package/lib/src/utils/privateStorePath.d.ts +259 -0
- package/lib/src/utils/privateStorePath.js +436 -0
- package/lib/src/utils/privateStoreTakeout.d.ts +76 -0
- package/lib/src/utils/privateStoreUnlock.d.ts +175 -0
- package/lib/src/utils/proxyClaim.d.ts +59 -0
- package/lib/src/utils/proxyClaim.js +30 -0
- package/lib/src/utils/redactPrivateNames.d.ts +21 -0
- package/lib/src/utils/redactPrivateNames.js +89 -0
- package/lib/src/utils/redactSecrets.d.ts +58 -0
- package/lib/src/utils/redactSecrets.js +171 -0
- package/lib/src/utils/renameLinkRewrite.d.ts +112 -0
- package/lib/src/utils/roleGrants.d.ts +35 -0
- package/lib/src/utils/safeRedirect.d.ts +32 -0
- package/lib/src/utils/schedule.d.ts +54 -0
- package/lib/src/utils/secretFileMode.d.ts +17 -0
- package/lib/src/utils/seededShippedPages.d.ts +45 -0
- package/lib/src/utils/sensitiveValues.d.ts +59 -0
- package/lib/src/utils/sensitiveValues.js +106 -0
- package/lib/src/utils/sessionEnd.d.ts +12 -0
- package/lib/src/utils/sessionGeneration.d.ts +24 -0
- package/lib/src/utils/sessionIdle.d.ts +41 -0
- package/lib/src/utils/sessionLimits.d.ts +68 -0
- package/lib/src/utils/shareLockbox.d.ts +64 -0
- package/lib/src/utils/sigstoreTrustedRoot.d.ts +33 -0
- package/lib/src/utils/sniffMime.d.ts +21 -0
- package/lib/src/utils/storeSearchIndex.d.ts +75 -0
- package/lib/src/utils/subjectMayDo.d.ts +21 -0
- package/lib/src/utils/systemCategoryRules.d.ts +30 -0
- package/lib/src/utils/systemCategoryRules.js +41 -0
- package/lib/src/utils/userCreateError.d.ts +62 -0
- package/lib/src/utils/username.d.ts +53 -0
- package/lib/src/utils/vaultUrlRewrite.d.ts +27 -0
- package/lib/src/utils/version.d.ts +84 -0
- package/lib/src/utils/videoFrame.d.ts +21 -0
- package/package.json +54 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite `[Old Title]` link targets to a page's new title (#1094).
|
|
3
|
+
*
|
|
4
|
+
* When a page is renamed, every `[Old Title]` in every other page stops
|
|
5
|
+
* resolving. #1082 answered that with an in-memory former-title map consulted
|
|
6
|
+
* after live resolution fails. That map has no durable backing — a restart
|
|
7
|
+
* forgets every rename — so it works right up until it silently stops.
|
|
8
|
+
*
|
|
9
|
+
* Rewriting the content instead makes the referring pages *correct*, rather
|
|
10
|
+
* than correct-looking because a lookup happens to still be warm.
|
|
11
|
+
*
|
|
12
|
+
* ## What this module is, and is not
|
|
13
|
+
*
|
|
14
|
+
* This is the text half only: pure, synchronous, no engine, no I/O. Given
|
|
15
|
+
* content and a rename it returns new content and a report. Deciding which
|
|
16
|
+
* pages to feed it, bounding the pass, writing the results and surviving a
|
|
17
|
+
* lost race are the caller's job — see `WikiRoutes.rewriteInboundLinksAfterRename`.
|
|
18
|
+
*
|
|
19
|
+
* The split is deliberate: the matching rules below are where the subtle bugs
|
|
20
|
+
* live, and they are worth testing without a server.
|
|
21
|
+
*
|
|
22
|
+
* ## Matching rules
|
|
23
|
+
*
|
|
24
|
+
* The link syntax is the parser's own (`WIKI_LINK_PATTERN_SOURCE`), imported
|
|
25
|
+
* rather than copied so the two cannot drift. It admits three forms:
|
|
26
|
+
*
|
|
27
|
+
* | Form | Target | Also the display text? |
|
|
28
|
+
* |----------------------------|--------------|------------------------|
|
|
29
|
+
* | `[Title]` | `Title` | yes |
|
|
30
|
+
* | `[Display\|Title]` | `Title` | no |
|
|
31
|
+
* | `[Display\|Title\|attrs]` | `Title` | no |
|
|
32
|
+
*
|
|
33
|
+
* A target may carry a fragment — `Title#slug` or `Title#section=Heading Name`
|
|
34
|
+
* — which is preserved verbatim; only the part before the first `#` is
|
|
35
|
+
* compared and replaced.
|
|
36
|
+
*
|
|
37
|
+
* __Case sensitivity differs by form, on purpose.__ In `[Display|Title]` the
|
|
38
|
+
* target is invisible to the reader, so a case variant is rewritten: nothing a
|
|
39
|
+
* human wrote changes. In `[Title]` the target *is* the display text, so only
|
|
40
|
+
* a byte-exact match is rewritten — silently recasing an author's prose to
|
|
41
|
+
* match a title is an edit they did not ask for.
|
|
42
|
+
*
|
|
43
|
+
* __Non-page targets are left alone__ by the same tests the parser uses to
|
|
44
|
+
* classify them: external URLs, `mailto:`, bare `#fragment`, absolute paths,
|
|
45
|
+
* and `Site:path` InterWiki references.
|
|
46
|
+
*
|
|
47
|
+
* __Markdown links are not touched.__ `[text](Target)` is excluded by the
|
|
48
|
+
* pattern's trailing `(?!\()`. The link graph does record them as edges, so a
|
|
49
|
+
* page linking that way can appear in the referrer set and come back with zero
|
|
50
|
+
* rewrites; that shows up in `unchangedTargets` rather than being silently
|
|
51
|
+
* counted as done.
|
|
52
|
+
*
|
|
53
|
+
* ## What it deliberately does not catch
|
|
54
|
+
*
|
|
55
|
+
* A link whose text only *fuzzy*-matches the old title — `[Old Titles]`
|
|
56
|
+
* resolving to `Old Title` through `PageNameMatcher` — is not rewritten. The
|
|
57
|
+
* link graph stores the *resolved* name, so such a page is in the referrer set
|
|
58
|
+
* and will come back unchanged. That is reported, not repaired: turning
|
|
59
|
+
* `[Old Titles]` into `[New Title]` rewrites the author's wording, and those
|
|
60
|
+
* links keep resolving through the #1082 map for as long as it exists.
|
|
61
|
+
*/
|
|
62
|
+
/** Outcome of rewriting one page's content. */
|
|
63
|
+
export interface LinkRewriteResult {
|
|
64
|
+
/** The content, rewritten. Identical to the input when `rewritten` is 0. */
|
|
65
|
+
content: string;
|
|
66
|
+
/** How many link targets were replaced. */
|
|
67
|
+
rewritten: number;
|
|
68
|
+
/**
|
|
69
|
+
* Internal page targets seen and left as they were, de-duplicated and in
|
|
70
|
+
* first-seen order. The caller logs these for a referrer that yielded no
|
|
71
|
+
* rewrites: it is the visible trace of a fuzzy variant, a markdown-syntax
|
|
72
|
+
* link, or a link-graph edge that no longer reflects the content.
|
|
73
|
+
*/
|
|
74
|
+
unchangedTargets: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* What a caller decides about one link target it was shown.
|
|
78
|
+
*
|
|
79
|
+
* @param base - The target as written, without any `#fragment`.
|
|
80
|
+
* @param targetIsDisplayText - True for `[Title]`, where the target IS the
|
|
81
|
+
* text the reader sees, and false for the piped forms, where it is not.
|
|
82
|
+
* The rewriting rule differs by form — see the module comment.
|
|
83
|
+
* @returns The target to write instead, or `null` to leave the link alone.
|
|
84
|
+
* The fragment is re-attached by the caller; a resolver never sees one.
|
|
85
|
+
*/
|
|
86
|
+
export type LinkTargetResolver = (base: string, targetIsDisplayText: boolean) => string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Rewrite link targets by the caller's rule — the matcher, once.
|
|
89
|
+
*
|
|
90
|
+
* The rename rewrite below and the private-link migration (#1457) decide
|
|
91
|
+
* different things about a target and agree on everything else: which
|
|
92
|
+
* bracket forms are links at all, that a task-list checkbox is not one, that
|
|
93
|
+
* external, mail, anchor, absolute and InterWiki targets are left alone, and
|
|
94
|
+
* that a `#fragment` survives. A second pass with its own regex would be a
|
|
95
|
+
* second place for those to drift, so both come through here.
|
|
96
|
+
*
|
|
97
|
+
* @param content - The page's markdown.
|
|
98
|
+
* @param resolve - What to do with each internal target seen.
|
|
99
|
+
* @returns The rewritten content and a report.
|
|
100
|
+
*/
|
|
101
|
+
export declare function rewriteLinkTargetsBy(content: string, resolve: LinkTargetResolver): LinkRewriteResult;
|
|
102
|
+
/**
|
|
103
|
+
* Rewrite links pointing at `oldTitle` so they point at `newTitle`.
|
|
104
|
+
*
|
|
105
|
+
* @param content - The referring page's markdown.
|
|
106
|
+
* @param oldTitle - The title the page was renamed away from.
|
|
107
|
+
* @param newTitle - The title it now has.
|
|
108
|
+
* @returns The rewritten content and a report. Returns the input unchanged
|
|
109
|
+
* (and `rewritten: 0`) when there is nothing to do, including when either
|
|
110
|
+
* title is blank or the two are equal.
|
|
111
|
+
*/
|
|
112
|
+
export declare function rewriteLinkTargets(content: string, oldTitle: string, newTitle: string): LinkRewriteResult;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each role is granted, read from the access policies (#1521).
|
|
3
|
+
*
|
|
4
|
+
* For the admin user forms: beside each role, the permissions its policies
|
|
5
|
+
* grant, so an admin can see that `contributor` already carries everything
|
|
6
|
+
* `reader` does under the shipped policies. Read from the merged policies —
|
|
7
|
+
* core, add-ons and the instance's own — so it shows what this site grants.
|
|
8
|
+
*
|
|
9
|
+
* Display only. Roles are independent in the code; whether a request is
|
|
10
|
+
* allowed is still decided by the evaluator, policy by policy and resource by
|
|
11
|
+
* resource. A grant that applies only to some resources (a policy naming
|
|
12
|
+
* particular pages or system-categories) is marked `limited`.
|
|
13
|
+
*/
|
|
14
|
+
import type { Policy } from '../types/Policy.js';
|
|
15
|
+
export interface RoleGrant {
|
|
16
|
+
action: string;
|
|
17
|
+
/** True when every policy granting it applies to some resources only. */
|
|
18
|
+
limited: boolean;
|
|
19
|
+
/** For a limited grant, the resource types it is limited to — `vault` reads "in your own vaults" (#1539). */
|
|
20
|
+
where: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface RoleGrants {
|
|
23
|
+
allows: RoleGrant[];
|
|
24
|
+
/** Actions a deny policy names for this role. */
|
|
25
|
+
denies: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Every role a policy names, with what its policies allow and deny — the one
|
|
29
|
+
* reading of "what a role grants" (#1539): the user forms, the profile, the
|
|
30
|
+
* admin roles page and `PolicyDecisionPoint.rolePermissions` all come from
|
|
31
|
+
* here. Policies are applied lowest priority first, so a deny takes away what
|
|
32
|
+
* a lower-priority allow gave and an allow above it gives it back — the order
|
|
33
|
+
* the evaluator's first-match-by-priority decides in.
|
|
34
|
+
*/
|
|
35
|
+
export declare function roleGrants(policies: readonly Policy[]): Record<string, RoleGrants>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* safeRedirect — constrain a request-supplied redirect target to this site (#1041).
|
|
3
|
+
*
|
|
4
|
+
* `POST /login` passed `req.body.redirect` straight to `res.redirect`, so the
|
|
5
|
+
* login endpoint sent the browser anywhere the request named:
|
|
6
|
+
*
|
|
7
|
+
* redirect=//example.com/ → HTTP/1.1 302 Location: //example.com/
|
|
8
|
+
*
|
|
9
|
+
* That is the worst place in the application to have an open redirect. The
|
|
10
|
+
* victim authenticates against the real site, sees a genuine successful login,
|
|
11
|
+
* and is then handed to an attacker-controlled page — a clone asking them to
|
|
12
|
+
* "sign in again" is very convincing at that point.
|
|
13
|
+
*
|
|
14
|
+
* The rule is deliberately a strict allow-list rather than a blocklist of bad
|
|
15
|
+
* prefixes: a single leading slash, and nothing that could be read as a host or
|
|
16
|
+
* a scheme. Blocklists here fail to the open case, which is the wrong direction
|
|
17
|
+
* for a redirect sink.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Return `target` when it is a same-site path, otherwise `/`.
|
|
21
|
+
*
|
|
22
|
+
* Accepts: `/`, `/view/Welcome`, `/search?q=x#frag`
|
|
23
|
+
*
|
|
24
|
+
* Rejects, among others:
|
|
25
|
+
* - `//evil.example` — protocol-relative; the browser reads it as a host
|
|
26
|
+
* - `https://evil.example`, `javascript:…`, `data:…` — any scheme
|
|
27
|
+
* - `/\evil.example` — browsers normalise the backslash to `/`
|
|
28
|
+
* - `\\evil.example`, or anything not starting with `/`
|
|
29
|
+
* - a CR/LF, which would let a caller inject a second header
|
|
30
|
+
*/
|
|
31
|
+
export declare function safeRedirect(target: unknown): string;
|
|
32
|
+
export default safeRedirect;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The schedule engine for scheduled jobs (#1713, epic #1611).
|
|
3
|
+
*
|
|
4
|
+
* A schedule is an iCalendar recurrence rule (RFC 5545 RRULE), the format the
|
|
5
|
+
* calendar add-on already stores, with a start (`dtstart`) and a time zone.
|
|
6
|
+
* Cron is not used: it cannot say "every 30 days" or "the last business day".
|
|
7
|
+
* Named shorthands (`daily 06:00`, `end-of-month`, …) expand to an RRULE.
|
|
8
|
+
*
|
|
9
|
+
* Recurrence is computed on wall-clock time and then placed in the time zone,
|
|
10
|
+
* so daylight saving does what a person expects:
|
|
11
|
+
* - spring forward: a slot inside the skipped hour runs at the next valid
|
|
12
|
+
* instant (02:30 becomes 03:30), rather than not at all;
|
|
13
|
+
* - fall back: a slot inside the repeated hour runs once, at its first
|
|
14
|
+
* occurrence.
|
|
15
|
+
* A date that does not exist is skipped, as RFC 5545 says: `BYMONTHDAY=31`
|
|
16
|
+
* skips short months; the last day of a month is `BYMONTHDAY=-1`. RFC 7529
|
|
17
|
+
* (RSCALE / SKIP) is not used (operator, 2026-10-08).
|
|
18
|
+
*
|
|
19
|
+
* Pure: no configuration is read here. The caller passes the default time zone
|
|
20
|
+
* (`ngdpbase.default.timezone`) and the minimum interval.
|
|
21
|
+
*
|
|
22
|
+
* @module utils/schedule
|
|
23
|
+
*/
|
|
24
|
+
/** What a job declares. A string is a shorthand or a bare RRULE (`FREQ=…`). */
|
|
25
|
+
export type ScheduleInput = string | {
|
|
26
|
+
rrule: string;
|
|
27
|
+
dtstart?: string;
|
|
28
|
+
tz?: string;
|
|
29
|
+
};
|
|
30
|
+
export interface ScheduleOptions {
|
|
31
|
+
/** IANA time zone used when the schedule names none. */
|
|
32
|
+
defaultTimeZone: string;
|
|
33
|
+
/** Slots closer together than this are refused. Default 60 000 ms. */
|
|
34
|
+
minIntervalMs?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface Schedule {
|
|
37
|
+
/** The RRULE that was compiled (after shorthand expansion). */
|
|
38
|
+
rrule: string;
|
|
39
|
+
/** The wall-clock start the recurrence is counted from, e.g. `2000-01-01T00:00:00`. */
|
|
40
|
+
dtstart: string;
|
|
41
|
+
timeZone: string;
|
|
42
|
+
/** The first slot strictly after `after`, or null when the rule has ended. */
|
|
43
|
+
nextSlot(after: Date): Date | null;
|
|
44
|
+
/** Every slot in `[from, to]`, oldest first. */
|
|
45
|
+
slotsBetween(from: Date, to: Date): Date[];
|
|
46
|
+
}
|
|
47
|
+
/** A shorthand's RRULE, a bare RRULE as given, or an error naming what was not understood. */
|
|
48
|
+
export declare function expandShorthand(text: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Compile a schedule. Throws, with a message a job author can act on, for an
|
|
51
|
+
* unknown shorthand, an invalid rule, an unknown time zone, `FREQ=SECONDLY`,
|
|
52
|
+
* an `RSCALE` other than GREGORIAN, or slots closer than the minimum interval.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseSchedule(input: ScheduleInput, options: ScheduleOptions): Schedule;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const SECRET_FILE_MODE = 384;
|
|
2
|
+
export declare const SECRET_DIR_MODE = 448;
|
|
3
|
+
/**
|
|
4
|
+
* Check an existing secret file at boot. Missing is fine (it is created
|
|
5
|
+
* owner-only on first write). Returns a line to log when it was tightened.
|
|
6
|
+
* Throws, naming both UIDs, when another account owns it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function secureExistingSecretFile(file: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Make a directory holding secrets owner-only, creating it when absent.
|
|
11
|
+
* Best effort on a filesystem that does not honour modes (an SMB share):
|
|
12
|
+
* returns a warning instead of failing, since refusing to write a backup
|
|
13
|
+
* over it would be worse than writing it with the share's permissions.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ensureSecretDir(dir: string): string | null;
|
|
16
|
+
/** After writing a secret file directly (not through writeFileAtomic's mode), make it owner-only. */
|
|
17
|
+
export declare function chmodSecretFile(file: string): string | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare const SEEDED_SHIPPED_PAGES_FILE = "seeded-shipped-pages.json";
|
|
2
|
+
/** Why a site declined a shipped page, and who decided. */
|
|
3
|
+
export interface DeclinedShippedPage {
|
|
4
|
+
/** When it was declined (ISO 8601) */
|
|
5
|
+
at: string;
|
|
6
|
+
/** Who declined it */
|
|
7
|
+
by: string;
|
|
8
|
+
/** The conflict that prompted it */
|
|
9
|
+
reason: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class SeededShippedPages {
|
|
12
|
+
private readonly filePath;
|
|
13
|
+
private readonly data;
|
|
14
|
+
private constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Load the record from the instance data folder. A missing file is an empty
|
|
17
|
+
* record; an unreadable one is an error, so the seeder cannot mistake a broken
|
|
18
|
+
* record for a site that has seeded nothing.
|
|
19
|
+
*/
|
|
20
|
+
static load(instanceDataFolder: string): Promise<SeededShippedPages>;
|
|
21
|
+
/** Whether this site has a record for the source at all. */
|
|
22
|
+
hasSource(sourceId: string): boolean;
|
|
23
|
+
/** Whether the uuid was ever seeded from the source on this site. */
|
|
24
|
+
has(sourceId: string, uuid: string): boolean;
|
|
25
|
+
/** Whether the uuid was ever seeded from any source on this site. */
|
|
26
|
+
hasUuid(uuid: string): boolean;
|
|
27
|
+
/** Record a uuid as seeded from the source. Returns true when it was not recorded before. */
|
|
28
|
+
add(sourceId: string, uuid: string, at?: string): boolean;
|
|
29
|
+
/** Whether this site declined the uuid from that source (#1412). */
|
|
30
|
+
isDeclined(sourceId: string, uuid: string): boolean;
|
|
31
|
+
/** Why this site declined the uuid, or undefined. */
|
|
32
|
+
declinedEntry(sourceId: string, uuid: string): DeclinedShippedPage | undefined;
|
|
33
|
+
/** Every declined uuid of a source, as uuid → entry. */
|
|
34
|
+
declinedOf(sourceId: string): Record<string, DeclinedShippedPage>;
|
|
35
|
+
/** Every source that has declines, as source id → uuid → entry. */
|
|
36
|
+
allDeclined(): Record<string, Record<string, DeclinedShippedPage>>;
|
|
37
|
+
/** Decline a uuid from a source. Returns true when it was not declined before. */
|
|
38
|
+
decline(sourceId: string, uuid: string, entry: DeclinedShippedPage): boolean;
|
|
39
|
+
/** Undo a decline. Returns true when there was one. */
|
|
40
|
+
allow(sourceId: string, uuid: string): boolean;
|
|
41
|
+
/** Start an empty record for the source, so `hasSource` is true from now on. */
|
|
42
|
+
startSource(sourceId: string): void;
|
|
43
|
+
save(): Promise<void>;
|
|
44
|
+
private source;
|
|
45
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which configuration values may be shown, and to whom (#1750).
|
|
3
|
+
*
|
|
4
|
+
* One declaration, `ngdpbase.config.sensitive-values` (renamed from
|
|
5
|
+
* `ngdpbase.config.secret-keys`, still read as an alias), maps a key to a level:
|
|
6
|
+
*
|
|
7
|
+
* - `secret` — never shown except to a holder of `secret-reveal` (which
|
|
8
|
+
* asks for a fresh sign-in), never written to the audit log
|
|
9
|
+
* or a log line;
|
|
10
|
+
* - `sensitive` — shown only to a holder of `admin-read`: how the instance is
|
|
11
|
+
* run (the access policies, storage paths, outside services).
|
|
12
|
+
*
|
|
13
|
+
* A key that isn't listed but whose name looks like a secret is treated as
|
|
14
|
+
* `secret` — a backstop, so a forgotten entry fails closed. The same backstop
|
|
15
|
+
* hides secret-looking fields nested inside an object value (an OIDC client's
|
|
16
|
+
* `client_secret` inside `oidc-auth-server.clients`).
|
|
17
|
+
*
|
|
18
|
+
* Hidden values are replaced on the server, before any HTML is built: they
|
|
19
|
+
* never reach a page, its source or the parse cache.
|
|
20
|
+
*
|
|
21
|
+
* @module utils/sensitiveValues
|
|
22
|
+
*/
|
|
23
|
+
export declare const SENSITIVE_VALUES_KEY = "ngdpbase.config.sensitive-values";
|
|
24
|
+
/** The list's name before #1750; still read, with a boot warning to rename it. */
|
|
25
|
+
export declare const LEGACY_SECRET_KEYS_KEY = "ngdpbase.config.secret-keys";
|
|
26
|
+
export type ValueLevel = 'secret' | 'sensitive';
|
|
27
|
+
/** What a viewer may see: sensitive values (`admin-read`), secret values (`secret-reveal`). */
|
|
28
|
+
export interface ValueViewer {
|
|
29
|
+
sensitive: boolean;
|
|
30
|
+
secret: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Whether a key or field name looks like a secret (the backstop's test; also `npm run lint:sensitive`). */
|
|
33
|
+
export declare function looksSecret(name: string): boolean;
|
|
34
|
+
/** What a hidden value is shown as. */
|
|
35
|
+
export declare const HIDDEN = "(hidden)";
|
|
36
|
+
type Read = (key: string, fallback: unknown) => unknown;
|
|
37
|
+
/**
|
|
38
|
+
* The declared levels: the legacy list's keys as `secret`, then the new map
|
|
39
|
+
* (`"secret"`, `"sensitive"`, `true` = secret; `false` / `null` removes an
|
|
40
|
+
* entry, so an instance can override a shipped one).
|
|
41
|
+
*/
|
|
42
|
+
export declare function valueLevels(read: Read): Map<string, ValueLevel>;
|
|
43
|
+
/** The keys declared `secret`: what logs, the audit log and the admin screen never print. */
|
|
44
|
+
export declare function secretKeys(read: Read): string[];
|
|
45
|
+
/**
|
|
46
|
+
* A value's level: as declared, else `secret` when the key's name looks like
|
|
47
|
+
* one and the value is a string. A boolean or number can't be a secret, so
|
|
48
|
+
* `ngdpbase.auth.password.enabled` stays visible; only a declared entry hides
|
|
49
|
+
* whatever its type.
|
|
50
|
+
*/
|
|
51
|
+
export declare function levelOf(key: string, levels: Map<string, ValueLevel>, value?: unknown): ValueLevel | null;
|
|
52
|
+
/** Whether the legacy list is still set (to warn at boot). */
|
|
53
|
+
export declare function usesLegacyName(read: Read): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The value a viewer may see for a key: itself, or `HIDDEN`. An empty value
|
|
56
|
+
* is shown as it is — "not set" reveals nothing.
|
|
57
|
+
*/
|
|
58
|
+
export declare function shownValue(key: string, value: unknown, levels: Map<string, ValueLevel>, viewer: ValueViewer): unknown;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which configuration values may be shown, and to whom (#1750).
|
|
3
|
+
*
|
|
4
|
+
* One declaration, `ngdpbase.config.sensitive-values` (renamed from
|
|
5
|
+
* `ngdpbase.config.secret-keys`, still read as an alias), maps a key to a level:
|
|
6
|
+
*
|
|
7
|
+
* - `secret` — never shown except to a holder of `secret-reveal` (which
|
|
8
|
+
* asks for a fresh sign-in), never written to the audit log
|
|
9
|
+
* or a log line;
|
|
10
|
+
* - `sensitive` — shown only to a holder of `admin-read`: how the instance is
|
|
11
|
+
* run (the access policies, storage paths, outside services).
|
|
12
|
+
*
|
|
13
|
+
* A key that isn't listed but whose name looks like a secret is treated as
|
|
14
|
+
* `secret` — a backstop, so a forgotten entry fails closed. The same backstop
|
|
15
|
+
* hides secret-looking fields nested inside an object value (an OIDC client's
|
|
16
|
+
* `client_secret` inside `oidc-auth-server.clients`).
|
|
17
|
+
*
|
|
18
|
+
* Hidden values are replaced on the server, before any HTML is built: they
|
|
19
|
+
* never reach a page, its source or the parse cache.
|
|
20
|
+
*
|
|
21
|
+
* @module utils/sensitiveValues
|
|
22
|
+
*/
|
|
23
|
+
import { enabledEntries } from './configFiles.js';
|
|
24
|
+
export const SENSITIVE_VALUES_KEY = 'ngdpbase.config.sensitive-values';
|
|
25
|
+
/** The list's name before #1750; still read, with a boot warning to rename it. */
|
|
26
|
+
export const LEGACY_SECRET_KEYS_KEY = 'ngdpbase.config.secret-keys';
|
|
27
|
+
/** Names that look like a secret, for keys and for fields inside object values. */
|
|
28
|
+
const SECRET_NAME_PATTERN = /secret|passw(or)?d|token|credential|api[-_]?key|private[-_]?key/i;
|
|
29
|
+
/** Whether a key or field name looks like a secret (the backstop's test; also `npm run lint:sensitive`). */
|
|
30
|
+
export function looksSecret(name) {
|
|
31
|
+
return SECRET_NAME_PATTERN.test(name);
|
|
32
|
+
}
|
|
33
|
+
/** What a hidden value is shown as. */
|
|
34
|
+
export const HIDDEN = '(hidden)';
|
|
35
|
+
/**
|
|
36
|
+
* The declared levels: the legacy list's keys as `secret`, then the new map
|
|
37
|
+
* (`"secret"`, `"sensitive"`, `true` = secret; `false` / `null` removes an
|
|
38
|
+
* entry, so an instance can override a shipped one).
|
|
39
|
+
*/
|
|
40
|
+
export function valueLevels(read) {
|
|
41
|
+
const levels = new Map();
|
|
42
|
+
for (const key of enabledEntries(read(LEGACY_SECRET_KEYS_KEY, {})))
|
|
43
|
+
levels.set(key, 'secret');
|
|
44
|
+
const declared = read(SENSITIVE_VALUES_KEY, {});
|
|
45
|
+
if (declared && typeof declared === 'object' && !Array.isArray(declared)) {
|
|
46
|
+
for (const [key, level] of Object.entries(declared)) {
|
|
47
|
+
if (level === 'secret' || level === true)
|
|
48
|
+
levels.set(key, 'secret');
|
|
49
|
+
else if (level === 'sensitive')
|
|
50
|
+
levels.set(key, 'sensitive');
|
|
51
|
+
else
|
|
52
|
+
levels.delete(key);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const key of enabledEntries(declared))
|
|
57
|
+
levels.set(key, 'secret');
|
|
58
|
+
}
|
|
59
|
+
return levels;
|
|
60
|
+
}
|
|
61
|
+
/** The keys declared `secret`: what logs, the audit log and the admin screen never print. */
|
|
62
|
+
export function secretKeys(read) {
|
|
63
|
+
return [...valueLevels(read)].filter(([, level]) => level === 'secret').map(([key]) => key);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A value's level: as declared, else `secret` when the key's name looks like
|
|
67
|
+
* one and the value is a string. A boolean or number can't be a secret, so
|
|
68
|
+
* `ngdpbase.auth.password.enabled` stays visible; only a declared entry hides
|
|
69
|
+
* whatever its type.
|
|
70
|
+
*/
|
|
71
|
+
export function levelOf(key, levels, value) {
|
|
72
|
+
const declared = levels.get(key);
|
|
73
|
+
if (declared)
|
|
74
|
+
return declared;
|
|
75
|
+
return looksSecret(key) && typeof value === 'string' ? 'secret' : null;
|
|
76
|
+
}
|
|
77
|
+
/** Whether the legacy list is still set (to warn at boot). */
|
|
78
|
+
export function usesLegacyName(read) {
|
|
79
|
+
return enabledEntries(read(LEGACY_SECRET_KEYS_KEY, {})).length > 0;
|
|
80
|
+
}
|
|
81
|
+
/** Secret-looking fields inside an object value, hidden; the rest kept. */
|
|
82
|
+
function hideNestedSecrets(value) {
|
|
83
|
+
if (Array.isArray(value))
|
|
84
|
+
return value.map(hideNestedSecrets);
|
|
85
|
+
if (value && typeof value === 'object') {
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const [k, v] of Object.entries(value)) {
|
|
88
|
+
out[k] = looksSecret(k) && typeof v === 'string' && v !== '' ? HIDDEN : hideNestedSecrets(v);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The value a viewer may see for a key: itself, or `HIDDEN`. An empty value
|
|
96
|
+
* is shown as it is — "not set" reveals nothing.
|
|
97
|
+
*/
|
|
98
|
+
export function shownValue(key, value, levels, viewer) {
|
|
99
|
+
const empty = value === undefined || value === null || value === '';
|
|
100
|
+
const level = levelOf(key, levels, value);
|
|
101
|
+
if (level === 'secret' && !viewer.secret)
|
|
102
|
+
return empty ? value : HIDDEN;
|
|
103
|
+
if (level === 'sensitive' && !viewer.sensitive)
|
|
104
|
+
return empty ? value : HIDDEN;
|
|
105
|
+
return viewer.secret ? value : hideNestedSecrets(value);
|
|
106
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type SessionEndReason = 'logout' | 'idle-timeout' | 'max-age' | 'password-changed' | 'superseded';
|
|
2
|
+
export declare function endSession(engine: {
|
|
3
|
+
getManager(name: string): unknown;
|
|
4
|
+
}, session: {
|
|
5
|
+
username?: unknown;
|
|
6
|
+
privateStoreHandle?: unknown;
|
|
7
|
+
pendingConnection?: unknown;
|
|
8
|
+
actingFor?: unknown;
|
|
9
|
+
} | null | undefined, reason: SessionEndReason, from: {
|
|
10
|
+
ipAddress?: string;
|
|
11
|
+
userAgent?: string;
|
|
12
|
+
}): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password-change session generation (#1482).
|
|
3
|
+
*
|
|
4
|
+
* A user record carries `sessionGeneration`, a counter raised whenever the
|
|
5
|
+
* account's password changes — by the owner, an administrator, the recovery
|
|
6
|
+
* words (#1452) or the offline reset script. A session is stamped with the
|
|
7
|
+
* counter when it signs in; a request whose session carries an older value is
|
|
8
|
+
* signed out. That ends every other session of the account without finding
|
|
9
|
+
* sessions on disk, at the cost of one comparison per request.
|
|
10
|
+
*
|
|
11
|
+
* An account that has never changed its password is at 0, and a session
|
|
12
|
+
* signed in before this existed carries nothing, which reads as 0: existing
|
|
13
|
+
* sessions stay signed in until the first password change.
|
|
14
|
+
*/
|
|
15
|
+
interface HasGeneration {
|
|
16
|
+
sessionGeneration?: number;
|
|
17
|
+
}
|
|
18
|
+
/** The account's current generation; 0 when it has never been raised. */
|
|
19
|
+
export declare function sessionGenerationOf(user: HasGeneration | null | undefined): number;
|
|
20
|
+
/** Raise the account's generation, in place, and return the new value. */
|
|
21
|
+
export declare function bumpSessionGeneration(user: HasGeneration): number;
|
|
22
|
+
/** Whether a session stamped with `stamped` still belongs to the account as it is now. */
|
|
23
|
+
export declare function sessionIsCurrent(stamped: unknown, user: HasGeneration | null | undefined): boolean;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session idle timeout (#1546).
|
|
3
|
+
*
|
|
4
|
+
* `ngdpbase.session.idle-timeout-minutes` ends a signed-in session after that
|
|
5
|
+
* many minutes with no request. Shipped at 10080 (7 days, #1767); `0` is off.
|
|
6
|
+
* It is separate from `ngdpbase.session.max-age`, the absolute cap from
|
|
7
|
+
* sign-in (`sessionLimits.ts`); a value at or above that cap has no effect.
|
|
8
|
+
*
|
|
9
|
+
* Enforced server-side on the next request — never only by a browser timer,
|
|
10
|
+
* which a closed laptop or a copied cookie would not run.
|
|
11
|
+
*
|
|
12
|
+
* Writing the last-activity time on every request would save the session on
|
|
13
|
+
* every request, which the file store pays for, so it is written at most once
|
|
14
|
+
* per `touchEveryMs`: a session may then end up to that much later than the
|
|
15
|
+
* exact limit, never earlier.
|
|
16
|
+
*/
|
|
17
|
+
/** The configured limit in milliseconds; 0 when off or not a positive number. */
|
|
18
|
+
export declare function idleTimeoutMs(minutes: unknown): number;
|
|
19
|
+
/** How stale the recorded activity may get before it is written again. */
|
|
20
|
+
export declare function touchEveryMs(timeoutMs: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a session last active at `lastActivity` (epoch ms) has been idle
|
|
23
|
+
* past the limit. A session with no recorded activity — one signed in before
|
|
24
|
+
* the limit was set — is not expired; it starts counting now.
|
|
25
|
+
*/
|
|
26
|
+
export declare function idleExpired(lastActivity: unknown, now: number, timeoutMs: number): boolean;
|
|
27
|
+
/** Whether the recorded activity should be written on this request. */
|
|
28
|
+
export declare function shouldTouch(lastActivity: unknown, now: number, timeoutMs: number): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The limit that applies to one person (#1546): the shortest positive value
|
|
31
|
+
* among the site setting and their roles' `idle-timeout-minutes`. A role can
|
|
32
|
+
* only shorten the site value, never lengthen it; 0 or absent means "no limit
|
|
33
|
+
* from here", so with nothing set anywhere the timeout stays off.
|
|
34
|
+
*/
|
|
35
|
+
export declare function effectiveIdleTimeoutMs(siteMinutes: unknown, roleMinutes?: readonly unknown[]): number;
|
|
36
|
+
/** How long before sign-out the page warns: two minutes, or a quarter of a shorter limit. */
|
|
37
|
+
export declare function warnBeforeMs(timeoutMs: number): number;
|
|
38
|
+
/** What is left of the limit, never negative; null when no limit applies. */
|
|
39
|
+
export declare function idleRemainingMs(lastActivity: unknown, now: number, timeoutMs: number): number | null;
|
|
40
|
+
/** The status poll does not count as activity, or the warning would keep the session alive by itself. */
|
|
41
|
+
export declare const IDLE_STATUS_PATH = "/api/session/idle-status";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session limits (#1767): the absolute cap on a signed-in session, and how it
|
|
3
|
+
* sits beside the idle timeout (#1546, `sessionIdle.ts`).
|
|
4
|
+
*
|
|
5
|
+
* `ngdpbase.session.max-age` (milliseconds) is the longest a sign-in lasts:
|
|
6
|
+
* a signed-in session ends that long after it signed in, however active it
|
|
7
|
+
* has been. Shipped at 30 days; the idle timeout (7 days) usually ends a
|
|
8
|
+
* session first.
|
|
9
|
+
*
|
|
10
|
+
* Why it is enforced here and not by the cookie (found 2026-10-12, #1767):
|
|
11
|
+
* before this, `max-age` only set express-session's `cookie.maxAge` and the
|
|
12
|
+
* file store's `ttl`, and neither is a cap.
|
|
13
|
+
*
|
|
14
|
+
* - express-session (`rolling: false`, the default) re-issues the cookie with
|
|
15
|
+
* a fresh expiry, now + maxAge, on every response whose session changed;
|
|
16
|
+
* the app changes it often (CSRF token, IP capture, the idle timer's
|
|
17
|
+
* last-activity write).
|
|
18
|
+
* - On every other request it calls the store's `touch()`, and
|
|
19
|
+
* session-file-store rewrites `__lastAccess`; a stored session expires
|
|
20
|
+
* `cookie.originalMaxAge` after its LAST access, not its first.
|
|
21
|
+
*
|
|
22
|
+
* So `max-age` was a rolling lifetime: a session in use never ended. The cap
|
|
23
|
+
* is now checked on every request from `signedInAt`, which only sign-in
|
|
24
|
+
* completion writes (`AuthManager.completeSignIn`, on the new session after
|
|
25
|
+
* regeneration); a re-authentication (step-up) does not move it.
|
|
26
|
+
*
|
|
27
|
+
* The cookie and the store keep their rolling behaviour and only have to be
|
|
28
|
+
* long enough: a signed-in session's cookie lives `max-age` from its last
|
|
29
|
+
* change (set at sign-in), so it never ends a session before the cap. An
|
|
30
|
+
* anonymous session (every visitor gets one, for the CSRF token) keeps the
|
|
31
|
+
* shorter `ngdpbase.session.anonymous-max-age`, so a 30-day cap does not keep a month of
|
|
32
|
+
* one-visit sessions in the store.
|
|
33
|
+
*/
|
|
34
|
+
/** The shipped `ngdpbase.session.max-age`: 30 days. */
|
|
35
|
+
export declare const DEFAULT_SESSION_MAX_AGE_MS: number;
|
|
36
|
+
/** The shipped `ngdpbase.session.anonymous-max-age`: a day, the old shipped `max-age`. */
|
|
37
|
+
export declare const DEFAULT_ANONYMOUS_SESSION_MS: number;
|
|
38
|
+
/**
|
|
39
|
+
* How long a session nobody signed in to lives after its last change:
|
|
40
|
+
* `ngdpbase.session.anonymous-max-age` (the shipped day when not a positive
|
|
41
|
+
* number), or the cap when that is shorter.
|
|
42
|
+
*/
|
|
43
|
+
export declare function anonymousSessionMs(capMs: number, configured?: unknown): number;
|
|
44
|
+
/** The configured cap in milliseconds; the shipped 30 days when not a positive number. There is no "off". */
|
|
45
|
+
export declare function sessionCapMs(value: unknown): number;
|
|
46
|
+
/** Whether a session signed in at `signedInAt` (epoch ms) is past the cap. No recorded time is not expired. */
|
|
47
|
+
export declare function capExpired(signedInAt: unknown, now: number, capMs: number): boolean;
|
|
48
|
+
/** What one request does to a signed-in session. */
|
|
49
|
+
export type SessionLimitVerdict = {
|
|
50
|
+
end: 'max-age' | 'idle-timeout';
|
|
51
|
+
} | {
|
|
52
|
+
end: null;
|
|
53
|
+
stampSignedIn: boolean;
|
|
54
|
+
touch: boolean;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The per-request check of a signed-in session against both limits: the cap
|
|
58
|
+
* first (it holds whatever the activity), then the idle timeout. A session
|
|
59
|
+
* signed in before `signedInAt` was recorded starts its cap now
|
|
60
|
+
* (`stampSignedIn`), as the idle timer does for a missing last activity.
|
|
61
|
+
*/
|
|
62
|
+
export declare function checkSessionLimits(session: {
|
|
63
|
+
signedInAt?: unknown;
|
|
64
|
+
lastActivity?: unknown;
|
|
65
|
+
}, now: number, limits: {
|
|
66
|
+
idleMs: number;
|
|
67
|
+
capMs: number;
|
|
68
|
+
}): SessionLimitVerdict;
|