@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,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;
|