@harperfast/harper 5.2.13 → 5.3.0-beta.1
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/agent/mcpTools.ts +1 -1
- package/agent/session.ts +25 -14
- package/bin/cliOperations.ts +58 -9
- package/bin/copyDb.ts +282 -59
- package/bin/deploySetup.ts +16 -5
- package/bin/harper.ts +1 -1
- package/bin/help.ts +4 -1
- package/bin/lite.ts +4 -1
- package/bin/restart.ts +120 -4
- package/bin/run.ts +6 -11
- package/bin/upgrade.js +7 -3
- package/bin/workloadIdentity.ts +119 -0
- package/components/Application.ts +3436 -242
- package/components/ApplicationScope.ts +8 -0
- package/components/EntryHandler.ts +59 -39
- package/components/OptionsWatcher.ts +440 -98
- package/components/RuntimeModuleTracker.ts +38 -7
- package/components/Scope.ts +56 -15
- package/components/awaitRestart.ts +84 -0
- package/components/componentLoader.ts +381 -32
- package/components/componentPreparationLock.ts +16 -5
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- package/components/mcp/adapters/harperHttp.ts +4 -0
- package/components/mcp/listChanged.ts +4 -0
- package/components/mcp/toolRegistry.ts +2 -0
- package/components/mcp/tools/operations.ts +9 -0
- package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
- package/components/operations.js +537 -113
- package/components/operationsValidation.js +98 -3
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/components/status/ComponentStatusRegistry.ts +59 -0
- package/config/RootConfigWatcher.ts +240 -40
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +357 -48
- package/config/harperConfigEnvVars.ts +170 -27
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +33 -0
- package/dataLayer/blobBackup.ts +160 -50
- package/dataLayer/delete.ts +6 -1
- package/dataLayer/harperBridge/ResourceBridge.ts +80 -10
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
- package/dataLayer/hdbInfoController.ts +34 -1
- package/dataLayer/insert.ts +44 -1
- package/dataLayer/rocksdbBackup.ts +53 -10
- package/dataLayer/schema.ts +11 -1
- package/dataLayer/schemaDescribe.ts +8 -1
- package/dist/agent/mcpTools.js +1 -1
- package/dist/agent/mcpTools.js.map +1 -1
- package/dist/agent/session.d.ts +22 -0
- package/dist/agent/session.js +26 -15
- package/dist/agent/session.js.map +1 -1
- package/dist/bin/cliOperations.js +61 -9
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/copyDb.d.ts +12 -1
- package/dist/bin/copyDb.js +248 -60
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/deploySetup.d.ts +2 -0
- package/dist/bin/deploySetup.js +11 -3
- package/dist/bin/deploySetup.js.map +1 -1
- package/dist/bin/harper.js +1 -1
- package/dist/bin/harper.js.map +1 -1
- package/dist/bin/help.js +4 -1
- package/dist/bin/help.js.map +1 -1
- package/dist/bin/lite.js +4 -1
- package/dist/bin/lite.js.map +1 -1
- package/dist/bin/restart.js +91 -6
- package/dist/bin/restart.js.map +1 -1
- package/dist/bin/run.js +4 -10
- package/dist/bin/run.js.map +1 -1
- package/dist/bin/upgrade.js +4 -3
- package/dist/bin/upgrade.js.map +1 -1
- package/dist/bin/workloadIdentity.d.ts +18 -0
- package/dist/bin/workloadIdentity.js +100 -0
- package/dist/bin/workloadIdentity.js.map +1 -0
- package/dist/components/Application.d.ts +235 -17
- package/dist/components/Application.js +3038 -238
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +8 -0
- package/dist/components/ApplicationScope.js +7 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/EntryHandler.js +26 -10
- package/dist/components/EntryHandler.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +4 -0
- package/dist/components/OptionsWatcher.js +440 -99
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/RuntimeModuleTracker.js +40 -6
- package/dist/components/RuntimeModuleTracker.js.map +1 -1
- package/dist/components/Scope.js +52 -13
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/awaitRestart.d.ts +33 -0
- package/dist/components/awaitRestart.js +61 -0
- package/dist/components/awaitRestart.js.map +1 -0
- package/dist/components/componentLoader.d.ts +38 -1
- package/dist/components/componentLoader.js +313 -24
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentPreparationLock.d.ts +5 -0
- package/dist/components/componentPreparationLock.js +14 -6
- package/dist/components/componentPreparationLock.js.map +1 -1
- package/dist/components/deploymentOperations.js +4 -1
- package/dist/components/deploymentOperations.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +4 -2
- package/dist/components/deploymentRecorder.js +1 -0
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.js +4 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/listChanged.js +5 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +1 -0
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +5 -0
- package/dist/components/mcp/tools/operations.js +9 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/operations.d.ts +28 -0
- package/dist/components/operations.js +476 -113
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +97 -3
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/packageComponent.js +24 -0
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/components/requestRestart.d.ts +1 -0
- package/dist/components/requestRestart.js +7 -0
- package/dist/components/requestRestart.js.map +1 -1
- package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
- package/dist/components/status/ComponentStatusRegistry.js +63 -0
- package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
- package/dist/config/RootConfigWatcher.d.ts +9 -1
- package/dist/config/RootConfigWatcher.js +228 -37
- package/dist/config/RootConfigWatcher.js.map +1 -1
- package/dist/config/configReadRetry.d.ts +8 -0
- package/dist/config/configReadRetry.js +62 -0
- package/dist/config/configReadRetry.js.map +1 -0
- package/dist/config/configUtils.d.ts +11 -2
- package/dist/config/configUtils.js +304 -47
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/harperConfigEnvVars.d.ts +16 -0
- package/dist/config/harperConfigEnvVars.js +162 -25
- package/dist/config/harperConfigEnvVars.js.map +1 -1
- package/dist/config/parseConfigFile.d.ts +4 -0
- package/dist/config/parseConfigFile.js +35 -0
- package/dist/config/parseConfigFile.js.map +1 -0
- package/dist/config/readConfigFileSync.d.ts +1 -0
- package/dist/config/readConfigFileSync.js +47 -0
- package/dist/config/readConfigFileSync.js.map +1 -0
- package/dist/config/watcherArming.d.ts +15 -0
- package/dist/config/watcherArming.js +59 -0
- package/dist/config/watcherArming.js.map +1 -0
- package/dist/dataLayer/blobBackup.d.ts +49 -20
- package/dist/dataLayer/blobBackup.js +139 -50
- package/dist/dataLayer/blobBackup.js.map +1 -1
- package/dist/dataLayer/delete.js +1 -1
- package/dist/dataLayer/delete.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +72 -12
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
- package/dist/dataLayer/hdbInfoController.d.ts +10 -0
- package/dist/dataLayer/hdbInfoController.js +30 -1
- package/dist/dataLayer/hdbInfoController.js.map +1 -1
- package/dist/dataLayer/insert.d.ts +9 -1
- package/dist/dataLayer/insert.js +30 -0
- package/dist/dataLayer/insert.js.map +1 -1
- package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
- package/dist/dataLayer/rocksdbBackup.js +45 -8
- package/dist/dataLayer/rocksdbBackup.js.map +1 -1
- package/dist/dataLayer/schema.js +8 -0
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +8 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +55 -0
- package/dist/resources/DatabaseTransaction.d.ts +77 -0
- package/dist/resources/DatabaseTransaction.js +586 -54
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +43 -6
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
- package/dist/resources/PrimaryRocksDatabase.js +49 -5
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +20 -0
- package/dist/resources/RecordEncoder.js +110 -9
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +2 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.js +117 -24
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +28 -1
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/RocksIndexStore.d.ts +6 -1
- package/dist/resources/RocksIndexStore.js +24 -9
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +24 -1
- package/dist/resources/RocksTransactionLogStore.js +160 -49
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +165 -10
- package/dist/resources/Table.js +1805 -303
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +10 -3
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +181 -2
- package/dist/resources/auditStore.js +640 -29
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +129 -9
- package/dist/resources/blob.js +992 -114
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/branchDatabase.d.ts +48 -0
- package/dist/resources/branchDatabase.js +892 -0
- package/dist/resources/branchDatabase.js.map +1 -0
- package/dist/resources/crdt.js +50 -12
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/dataLoader.js +3 -4
- package/dist/resources/dataLoader.js.map +1 -1
- package/dist/resources/databases.d.ts +157 -14
- package/dist/resources/databases.js +1462 -325
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/defineTable.d.ts +10 -2
- package/dist/resources/defineTable.js +9 -1
- package/dist/resources/defineTable.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +5 -0
- package/dist/resources/derivedIndexRegistry.js +68 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -0
- package/dist/resources/derivedIndexRuntime.d.ts +215 -0
- package/dist/resources/derivedIndexRuntime.js +2027 -0
- package/dist/resources/derivedIndexRuntime.js.map +1 -0
- package/dist/resources/graphql.d.ts +1 -1
- package/dist/resources/graphql.js +52 -16
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +103 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +854 -41
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
- package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
- package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
- package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
- package/dist/resources/longLivedTransactions.d.ts +71 -0
- package/dist/resources/longLivedTransactions.js +358 -0
- package/dist/resources/longLivedTransactions.js.map +1 -0
- package/dist/resources/models/backendRegistry.d.ts +26 -0
- package/dist/resources/models/backendRegistry.js +60 -2
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +33 -1
- package/dist/resources/models/bootstrap.js +416 -31
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/nodeIdMapping.d.ts +5 -0
- package/dist/resources/nodeIdMapping.js +49 -0
- package/dist/resources/nodeIdMapping.js.map +1 -1
- package/dist/resources/recordLock.d.ts +123 -0
- package/dist/resources/recordLock.js +315 -0
- package/dist/resources/recordLock.js.map +1 -0
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2565 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.d.ts +14 -1
- package/dist/resources/replayLogs.js +157 -24
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/replayLogsGuards.d.ts +94 -7
- package/dist/resources/replayLogsGuards.js +111 -7
- package/dist/resources/replayLogsGuards.js.map +1 -1
- package/dist/resources/replicatedApplyFailure.d.ts +16 -0
- package/dist/resources/replicatedApplyFailure.js +63 -0
- package/dist/resources/replicatedApplyFailure.js.map +1 -0
- package/dist/resources/scheduler/scheduler.js +3 -3
- package/dist/resources/scheduler/scheduler.js.map +1 -1
- package/dist/resources/search.d.ts +10 -4
- package/dist/resources/search.js +283 -37
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/tracked.d.ts +5 -1
- package/dist/resources/tracked.js +74 -23
- package/dist/resources/tracked.js.map +1 -1
- package/dist/resources/transactionBroadcast.d.ts +1 -1
- package/dist/resources/transactionBroadcast.js +2 -2
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +61 -30
- package/dist/security/auth.js.map +1 -1
- package/dist/security/authn/oidc/claims.d.ts +22 -0
- package/dist/security/authn/oidc/claims.js +71 -0
- package/dist/security/authn/oidc/claims.js.map +1 -0
- package/dist/security/authn/oidc/identityToken.d.ts +27 -0
- package/dist/security/authn/oidc/identityToken.js +111 -0
- package/dist/security/authn/oidc/identityToken.js.map +1 -0
- package/dist/security/authn/oidc/jwks.d.ts +25 -0
- package/dist/security/authn/oidc/jwks.js +261 -0
- package/dist/security/authn/oidc/jwks.js.map +1 -0
- package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
- package/dist/security/authn/oidc/providers/generic.js +34 -0
- package/dist/security/authn/oidc/providers/generic.js.map +1 -0
- package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
- package/dist/security/authn/oidc/providers/githubActions.js +129 -0
- package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
- package/dist/security/authn/oidc/providers/index.d.ts +37 -0
- package/dist/security/authn/oidc/providers/index.js +24 -0
- package/dist/security/authn/oidc/providers/index.js.map +1 -0
- package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
- package/dist/security/authn/oidc/tokenExchange.js +306 -0
- package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
- package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
- package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
- package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
- package/dist/security/authn/oidc/types.d.ts +38 -0
- package/dist/security/authn/oidc/types.js +6 -0
- package/dist/security/authn/oidc/types.js.map +1 -0
- package/dist/security/certificateVerification/index.js +40 -11
- package/dist/security/certificateVerification/index.js.map +1 -1
- package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
- package/dist/security/certificateVerification/trustedIssuers.js +79 -0
- package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
- package/dist/security/certificateVerification/types.d.ts +1 -0
- package/dist/security/credentialProvenance.d.ts +35 -0
- package/dist/security/credentialProvenance.js +51 -0
- package/dist/security/credentialProvenance.js.map +1 -0
- package/dist/security/credentialRejection.d.ts +4 -0
- package/dist/security/credentialRejection.js +24 -0
- package/dist/security/credentialRejection.js.map +1 -0
- package/dist/security/deferredAuthentication.d.ts +36 -0
- package/dist/security/deferredAuthentication.js +70 -0
- package/dist/security/deferredAuthentication.js.map +1 -0
- package/dist/security/impersonation.d.ts +21 -0
- package/dist/security/impersonation.js +108 -9
- package/dist/security/impersonation.js.map +1 -1
- package/dist/security/jsLoader.d.ts +6 -0
- package/dist/security/jsLoader.js +77 -15
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/keys.js +301 -71
- package/dist/security/keys.js.map +1 -1
- package/dist/security/operationScope.d.ts +21 -0
- package/dist/security/operationScope.js +36 -0
- package/dist/security/operationScope.js.map +1 -0
- package/dist/security/permissionsTranslator.js +21 -0
- package/dist/security/permissionsTranslator.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +19 -1
- package/dist/security/tokenAuthentication.js +191 -10
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/security/user.js +4 -3
- package/dist/security/user.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
- package/dist/server/DurableSubscriptionsSession.js +67 -10
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +106 -2
- package/dist/server/REST.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -0
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +16 -1
- package/dist/server/http.js +122 -17
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +8 -1
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobProcess.js +6 -2
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobs.js +4 -1
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +26 -4
- package/dist/server/liveSubscriptionAuth.js +105 -39
- package/dist/server/liveSubscriptionAuth.js.map +1 -1
- package/dist/server/loadRootComponents.js +49 -10
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/mqtt.d.ts +2 -0
- package/dist/server/mqtt.js +165 -30
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/nodeName.d.ts +2 -0
- package/dist/server/nodeName.js +107 -23
- package/dist/server/nodeName.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +27 -0
- package/dist/server/serverHelpers/Headers.js +145 -1
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
- package/dist/server/serverHelpers/Request.d.ts +5 -10
- package/dist/server/serverHelpers/Request.js +38 -136
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +11 -0
- package/dist/server/serverHelpers/contentTypes.js +221 -40
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
- package/dist/server/serverHelpers/deployValidationState.js +9 -19
- package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
- package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
- package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
- package/dist/server/serverHelpers/registeredOperations.js +74 -21
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
- package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
- package/dist/server/serverHelpers/requestSanitization.js +20 -0
- package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +6 -3
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
- package/dist/server/serverHelpers/serverUtilities.js +178 -29
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
- package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
- package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
- package/dist/server/serverHelpers/uwsServer.js +19 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/static.js +24 -28
- package/dist/server/static.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +5 -0
- package/dist/server/storageReclamation.js +17 -1
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/isolatedApplications.d.ts +47 -0
- package/dist/server/threads/isolatedApplications.js +171 -0
- package/dist/server/threads/isolatedApplications.js.map +1 -0
- package/dist/server/threads/itc.d.ts +7 -2
- package/dist/server/threads/itc.js +5 -1
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/logRotationTransport.d.ts +1 -0
- package/dist/server/threads/logRotationTransport.js +33 -0
- package/dist/server/threads/logRotationTransport.js.map +1 -0
- package/dist/server/threads/manageThreads.d.ts +83 -3
- package/dist/server/threads/manageThreads.js +769 -75
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +277 -31
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadHeapMemory.d.ts +2 -0
- package/dist/server/threads/threadHeapMemory.js +31 -0
- package/dist/server/threads/threadHeapMemory.js.map +1 -0
- package/dist/server/threads/threadServer.js +76 -22
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/sqlEngine/config.d.ts +1 -3
- package/dist/sqlEngine/config.js +19 -16
- package/dist/sqlEngine/config.js.map +1 -1
- package/dist/sqlTranslator/index.d.ts +1 -1
- package/dist/sqlTranslator/index.js +30 -7
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.d.ts +7 -0
- package/dist/upgrade/directives/5-3-0.js +148 -0
- package/dist/upgrade/directives/5-3-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/OperationFunctionCaller.js +2 -1
- package/dist/utility/OperationFunctionCaller.js.map +1 -1
- package/dist/utility/common_utils.d.ts +16 -0
- package/dist/utility/common_utils.js +32 -6
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/componentNames.d.ts +8 -0
- package/dist/utility/componentNames.js +12 -1
- package/dist/utility/componentNames.js.map +1 -1
- package/dist/utility/environment/environmentManager.js +3 -6
- package/dist/utility/environment/environmentManager.js.map +1 -1
- package/dist/utility/environment/systemInformation.d.ts +1 -0
- package/dist/utility/environment/systemInformation.js +1 -0
- package/dist/utility/environment/systemInformation.js.map +1 -1
- package/dist/utility/errors/commonErrors.d.ts +2 -0
- package/dist/utility/errors/commonErrors.js +2 -0
- package/dist/utility/errors/commonErrors.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +33 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/globalSchema.d.ts +18 -0
- package/dist/utility/hdbTerms.d.ts +18 -0
- package/dist/utility/hdbTerms.js +20 -2
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +2 -0
- package/dist/utility/logging/harper_logger.js +286 -30
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
- package/dist/utility/logging/logGenerationCoordinator.js +184 -0
- package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
- package/dist/utility/logging/logRotation.d.ts +46 -0
- package/dist/utility/logging/logRotation.js +365 -0
- package/dist/utility/logging/logRotation.js.map +1 -0
- package/dist/utility/logging/logRotator.d.ts +1 -1
- package/dist/utility/logging/logRotator.js +192 -85
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/nodeIdentity.d.ts +9 -0
- package/dist/utility/nodeIdentity.js +58 -0
- package/dist/utility/nodeIdentity.js.map +1 -0
- package/dist/utility/npmUtilities.js +11 -7
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +3 -1
- package/dist/utility/operationPermissions.js +16 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +10 -7
- package/dist/utility/operation_authorization.js +212 -41
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/watchPath.d.ts +29 -0
- package/dist/utility/watchPath.js +68 -0
- package/dist/utility/watchPath.js.map +1 -0
- package/dist/utility/watcherFallback.d.ts +41 -0
- package/dist/utility/watcherFallback.js +153 -0
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.d.ts +12 -0
- package/dist/validation/configValidator.js +205 -75
- package/dist/validation/configValidator.js.map +1 -1
- package/dist/validation/installValidator.js +12 -0
- package/dist/validation/installValidator.js.map +1 -1
- package/dist/validation/validationWrapper.d.ts +11 -0
- package/dist/validation/validationWrapper.js +16 -3
- package/dist/validation/validationWrapper.js.map +1 -1
- package/index.ts +8 -0
- package/json/systemSchema.json +55 -0
- package/npm-shrinkwrap.json +286 -194
- package/package.json +15 -7
- package/resources/DESIGN.md +219 -52
- package/resources/DatabaseTransaction.ts +657 -52
- package/resources/LMDBTransaction.ts +46 -6
- package/resources/PrimaryRocksDatabase.ts +46 -7
- package/resources/RecordEncoder.ts +124 -9
- package/resources/RequestTarget.ts +2 -0
- package/resources/Resource.ts +114 -22
- package/resources/ResourceInterface.ts +31 -0
- package/resources/RocksIndexStore.ts +30 -9
- package/resources/RocksTransactionLogStore.ts +188 -51
- package/resources/Table.ts +1960 -301
- package/resources/analytics/write.ts +10 -3
- package/resources/auditStore.ts +647 -32
- package/resources/blob.ts +1029 -110
- package/resources/branchDatabase.ts +941 -0
- package/resources/crdt.ts +76 -12
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +1584 -320
- package/resources/defineTable.ts +18 -2
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +72 -17
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +911 -48
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/longLivedTransactions.ts +360 -0
- package/resources/models/backendRegistry.ts +84 -2
- package/resources/models/bootstrap.ts +473 -28
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/recordLock.ts +419 -0
- package/resources/recordLockCoordinator.ts +3043 -0
- package/resources/replayLogs.ts +152 -26
- package/resources/replayLogsGuards.ts +171 -8
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +288 -46
- package/resources/tracked.ts +73 -22
- package/resources/transactionBroadcast.ts +3 -3
- package/security/auth.ts +68 -29
- package/security/authn/oidc/claims.ts +72 -0
- package/security/authn/oidc/identityToken.ts +129 -0
- package/security/authn/oidc/jwks.ts +260 -0
- package/security/authn/oidc/providers/generic.ts +40 -0
- package/security/authn/oidc/providers/githubActions.ts +137 -0
- package/security/authn/oidc/providers/index.ts +52 -0
- package/security/authn/oidc/tokenExchange.ts +300 -0
- package/security/authn/oidc/trustPolicyOperations.ts +343 -0
- package/security/authn/oidc/types.ts +41 -0
- package/security/certificateVerification/index.ts +54 -13
- package/security/certificateVerification/trustedIssuers.ts +76 -0
- package/security/certificateVerification/types.ts +1 -0
- package/security/credentialProvenance.ts +47 -0
- package/security/credentialRejection.ts +22 -0
- package/security/deferredAuthentication.ts +71 -0
- package/security/impersonation.ts +117 -12
- package/security/jsLoader.ts +83 -18
- package/security/keys.ts +298 -72
- package/security/operationScope.ts +33 -0
- package/security/permissionsTranslator.js +23 -0
- package/security/tokenAuthentication.ts +233 -12
- package/security/user.ts +4 -3
- package/server/DESIGN.md +194 -16
- package/server/DurableSubscriptionsSession.ts +71 -11
- package/server/REST.ts +115 -4
- package/server/graphqlQuerying.ts +4 -0
- package/server/http.ts +133 -20
- package/server/itc/serverHandlers.js +8 -1
- package/server/jobs/jobProcess.ts +8 -2
- package/server/jobs/jobs.ts +4 -1
- package/server/liveSubscriptionAuth.ts +129 -46
- package/server/loadRootComponents.js +50 -8
- package/server/mqtt.ts +179 -38
- package/server/nodeName.ts +103 -21
- package/server/serverHelpers/Headers.ts +135 -0
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +217 -36
- package/server/serverHelpers/deployValidationState.ts +24 -13
- package/server/serverHelpers/operationAuthorizationState.ts +34 -3
- package/server/serverHelpers/registeredOperations.ts +79 -22
- package/server/serverHelpers/requestSanitization.ts +15 -0
- package/server/serverHelpers/serverHandlers.js +6 -3
- package/server/serverHelpers/serverUtilities.ts +232 -40
- package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
- package/server/serverHelpers/uwsServer.ts +17 -2
- package/server/static.ts +23 -29
- package/server/storageReclamation.ts +15 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/itc.js +11 -1
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +771 -66
- package/server/threads/socketRouter.ts +291 -30
- package/server/threads/threadHeapMemory.ts +26 -0
- package/server/threads/threadServer.js +73 -22
- package/sqlTranslator/index.ts +31 -8
- package/studio/web/assets/{Chat-4RrB5134.js → Chat-D3j-1yY1.js} +1 -1
- package/studio/web/assets/{FloatingChat-omlNMDcJ.js → FloatingChat-BxJGYcfB.js} +3 -3
- package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CT55oWOe.js} +1 -1
- package/studio/web/assets/{applications-DvFDYJgK.js → applications-D9Ct9_vm.js} +1 -1
- package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DV8H7VwA.js} +1 -1
- package/studio/web/assets/{editor-3XRWEDWX.js → editor-uatc0unt.js} +1 -1
- package/studio/web/assets/{html-CTY5tdMr.js → html-Bm6D6paN.js} +1 -1
- package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-CEn7tpLG.js} +1 -1
- package/studio/web/assets/{index-Dfpeofdu.js → index-BIXW6Pu4.js} +5 -5
- package/studio/web/assets/{index.lazy-B1VOIv-t.js → index.lazy-UI7L-Vrk.js} +1 -1
- package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-CJ0G3AFZ.js} +1 -1
- package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-DQADAYEa.js} +1 -1
- package/studio/web/assets/{languageServices-IKH4GUHl.js → languageServices-CAQJXWcI.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
- package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-BbxTU6Aw.js} +1 -1
- package/studio/web/assets/{notifications-DFbArTfC.js → notifications-Cvb3P1lB.js} +1 -1
- package/studio/web/assets/{profile-Q4-T6c-S.js → profile-Yyb7gsvL.js} +1 -1
- package/studio/web/assets/{regions-CtkV0xje.js → regions-OgjGHlU5.js} +1 -1
- package/studio/web/assets/{register-1ZZuMsiA.js → register-6qwNEOY3.js} +2 -2
- package/studio/web/assets/{setComponentFile-ZWLcWv5X.js → setComponentFile-BilDMtgB.js} +1 -1
- package/studio/web/assets/{setup-DWprJyJy.js → setup-J6qJ7OIU.js} +2 -2
- package/studio/web/assets/{status--aNm8isn.js → status-0RWGcfyD.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-Cq_lt3XD.js → toggleHighContrast-BIn-vErT.js} +1 -1
- package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DgUXku4d.js} +1 -1
- package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-C9orXcsM.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-BEoXXbUB.js} +1 -1
- package/studio/web/assets/{workers-BgoXIqQe.js → workers-JVzSDmgx.js} +1 -1
- package/studio/web/assets/{xml-BwSeDMiP.js → xml-Cq-S8S4X.js} +1 -1
- package/studio/web/assets/{yaml-DotCUG5l.js → yaml-sfoRdh1M.js} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-3-0.ts +132 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/OperationFunctionCaller.ts +2 -1
- package/utility/common_utils.ts +30 -5
- package/utility/componentNames.ts +12 -0
- package/utility/environment/environmentManager.ts +3 -7
- package/utility/environment/systemInformation.ts +7 -0
- package/utility/errors/commonErrors.ts +4 -0
- package/utility/errors/hdbError.ts +57 -0
- package/utility/hdbTerms.ts +19 -0
- package/utility/logging/harper_logger.ts +278 -26
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +213 -81
- package/utility/nodeIdentity.ts +45 -0
- package/utility/npmUtilities.ts +12 -8
- package/utility/operationPermissions.ts +18 -1
- package/utility/operation_authorization.ts +231 -42
- package/utility/watchPath.ts +63 -0
- package/utility/watcherFallback.ts +148 -0
- package/validation/configValidator.ts +215 -75
- package/validation/installValidator.ts +15 -0
- package/validation/validationWrapper.ts +18 -4
package/security/auth.ts
CHANGED
|
@@ -7,13 +7,21 @@ import { v4 as uuid } from 'uuid';
|
|
|
7
7
|
import * as env from '../utility/environment/environmentManager.ts';
|
|
8
8
|
import { CONFIG_PARAMS, AUTH_AUDIT_STATUS, AUTH_AUDIT_TYPES } from '../utility/hdbTerms.ts';
|
|
9
9
|
import harperLogger from '../utility/logging/harper_logger.ts';
|
|
10
|
-
const { forComponent, AuthAuditLog } = harperLogger;
|
|
10
|
+
const { forComponent, AuthAuditLog, errorForLog } = harperLogger;
|
|
11
11
|
import serverHandlers from '../server/itc/serverHandlers.js';
|
|
12
12
|
const { user } = serverHandlers;
|
|
13
|
-
import { Headers, addVaryHeader } from '../server/serverHelpers/Headers.ts';
|
|
13
|
+
import { Headers, addVaryHeader, SHARED_CACHE_OPTIN, PRIVATE_SCOPE } from '../server/serverHelpers/Headers.ts';
|
|
14
14
|
import { convertToMS } from '../utility/common_utils.ts';
|
|
15
15
|
import { verifyCertificate } from './certificateVerification/index.ts';
|
|
16
|
+
import {
|
|
17
|
+
credentialRejectionError,
|
|
18
|
+
deferCredentialRejection,
|
|
19
|
+
getDeferredCredentialRejection,
|
|
20
|
+
isCredentialRejection,
|
|
21
|
+
} from './deferredAuthentication.ts';
|
|
16
22
|
import { serializeMessage } from '../server/serverHelpers/contentTypes.ts';
|
|
23
|
+
import { hdbErrors } from '../utility/errors/hdbError.ts';
|
|
24
|
+
const { AUTHENTICATION_ERROR_MSGS, HTTP_STATUS_CODES } = hdbErrors;
|
|
17
25
|
const authLogger = forComponent('authentication');
|
|
18
26
|
const { debug } = authLogger;
|
|
19
27
|
const authEventLog = authLogger.withTag('auth-event');
|
|
@@ -50,10 +58,6 @@ const LOG_AUTH_FAILED = env.get(CONFIG_PARAMS.LOGGING_AUDITAUTHEVENTS_LOGFAILED)
|
|
|
50
58
|
|
|
51
59
|
const DEFAULT_COOKIE_EXPIRES = 'Tue, 01 Oct 8307 19:33:20 GMT';
|
|
52
60
|
|
|
53
|
-
// RFC 9111 cache-scope directives; boundaries on both sides so a token like `public-foo` doesn't match
|
|
54
|
-
const SHARED_CACHE_OPTIN = /(^|[,\s])(public|s-maxage)($|[\s,;=])/i;
|
|
55
|
-
const PRIVATE_SCOPE = /(^|[,\s])(private|no-store)($|[\s,;=])/i;
|
|
56
|
-
|
|
57
61
|
let authorizationCache = new Map();
|
|
58
62
|
server.onInvalidatedUser(() => {
|
|
59
63
|
// TODO: Eventually we probably want to be able to invalidate individual users
|
|
@@ -190,7 +194,13 @@ export async function authentication(request, nextHandler) {
|
|
|
190
194
|
if (request.user) {
|
|
191
195
|
// already authenticated
|
|
192
196
|
} else if (authorization) {
|
|
193
|
-
|
|
197
|
+
let cachedUser = authorizationCache.get(authorization);
|
|
198
|
+
// A cached Bearer identity must not outlive its token: expiry is the only revocation
|
|
199
|
+
// mechanism for scoped tokens, so it has to be exact, not cache-TTL-fuzzy.
|
|
200
|
+
if (cachedUser?.authExpiresAt && cachedUser.authExpiresAt * 1000 <= Date.now()) {
|
|
201
|
+
authorizationCache.delete(authorization);
|
|
202
|
+
cachedUser = undefined;
|
|
203
|
+
}
|
|
194
204
|
if (cachedUser?.role) {
|
|
195
205
|
// Shallow-clone so verifyPerms's `role.permission = fullRolePerms` reassignment
|
|
196
206
|
// doesn't mutate the cache entry (defense-in-depth; operations and other
|
|
@@ -204,6 +214,7 @@ export async function authentication(request, nextHandler) {
|
|
|
204
214
|
const strategy = authorization.slice(0, spaceIndex);
|
|
205
215
|
const credentials = authorization.slice(spaceIndex + 1);
|
|
206
216
|
let username, password;
|
|
217
|
+
let credentialRejection;
|
|
207
218
|
try {
|
|
208
219
|
switch (strategy) {
|
|
209
220
|
case 'Basic':
|
|
@@ -231,12 +242,23 @@ export async function authentication(request, nextHandler) {
|
|
|
231
242
|
// API has its own logic for handling this
|
|
232
243
|
status: -1,
|
|
233
244
|
});
|
|
234
|
-
} catch {
|
|
245
|
+
} catch (refreshError) {
|
|
246
|
+
// Preserve refresh-validation faults; only a tagged rejection permits falling
|
|
247
|
+
// back to the original operation-token rejection.
|
|
248
|
+
if (!isCredentialRejection(refreshError)) throw refreshError;
|
|
235
249
|
throw error;
|
|
236
250
|
}
|
|
237
251
|
}
|
|
252
|
+
// A non-'invalid token' rejection (e.g. expired) must propagate, not fall
|
|
253
|
+
// through to caching an undefined user (which would read as anonymous).
|
|
254
|
+
throw error;
|
|
238
255
|
}
|
|
239
256
|
break;
|
|
257
|
+
default:
|
|
258
|
+
throw credentialRejectionError(
|
|
259
|
+
AUTHENTICATION_ERROR_MSGS.GENERIC_AUTH_FAIL,
|
|
260
|
+
HTTP_STATUS_CODES.UNAUTHORIZED
|
|
261
|
+
);
|
|
240
262
|
}
|
|
241
263
|
} catch (err) {
|
|
242
264
|
if (LOG_AUTH_FAILED) {
|
|
@@ -247,18 +269,31 @@ export async function authentication(request, nextHandler) {
|
|
|
247
269
|
}
|
|
248
270
|
}
|
|
249
271
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
272
|
+
const internalFault = !isCredentialRejection(err);
|
|
273
|
+
if (request.isOperationsServer || internalFault) {
|
|
274
|
+
if (internalFault) authLogger.error('Authentication failed internally', errorForLog(err));
|
|
275
|
+
return applyResponseHeaders({
|
|
276
|
+
status: 401,
|
|
277
|
+
body: serializeMessage(
|
|
278
|
+
{ error: internalFault ? AUTHENTICATION_ERROR_MSGS.GENERIC_AUTH_FAIL : err.message },
|
|
279
|
+
request
|
|
280
|
+
),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
credentialRejection = err;
|
|
254
284
|
}
|
|
255
285
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
286
|
+
if (credentialRejection) {
|
|
287
|
+
deferCredentialRejection(request, credentialRejection, strategy);
|
|
288
|
+
} else {
|
|
289
|
+
authorizationCache.set(authorization, newUser);
|
|
290
|
+
if (LOG_AUTH_SUCCESSFUL && newUser != null)
|
|
291
|
+
authAuditLog(newUser.username, AUTH_AUDIT_STATUS.SUCCESS, strategy);
|
|
292
|
+
// Shallow-clone so verifyPerms's `role.permission = fullRolePerms` reassignment
|
|
293
|
+
// doesn't mutate the just-stored cache entry (defense-in-depth).
|
|
294
|
+
if (newUser?.role) {
|
|
295
|
+
newUser = { ...newUser, role: { ...newUser.role, permission: { ...newUser.role.permission } } };
|
|
296
|
+
}
|
|
262
297
|
}
|
|
263
298
|
}
|
|
264
299
|
|
|
@@ -346,16 +381,19 @@ export async function authentication(request, nextHandler) {
|
|
|
346
381
|
if (!response) return response;
|
|
347
382
|
if (response.status === 401) {
|
|
348
383
|
wasUnauthorized = true;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
384
|
+
// Downstream owns the challenge or redirect after Harper deferred the credential decision.
|
|
385
|
+
if (!getDeferredCredentialRejection(request)) {
|
|
386
|
+
if (
|
|
387
|
+
headers['user-agent']?.startsWith('Mozilla') &&
|
|
388
|
+
headers.accept?.startsWith('text/html') &&
|
|
389
|
+
resources.loginPath
|
|
390
|
+
) {
|
|
391
|
+
// on the web if we have a login page, default to redirecting to it
|
|
392
|
+
response.status = 302;
|
|
393
|
+
response.headers.set('Location', resources.loginPath(request));
|
|
394
|
+
} // the HTTP specified way of indicating HTTP authentication methods supported:
|
|
395
|
+
else response.headers.set('WWW-Authenticate', 'Basic');
|
|
396
|
+
}
|
|
359
397
|
}
|
|
360
398
|
return applyResponseHeaders(response);
|
|
361
399
|
} catch (error) {
|
|
@@ -367,7 +405,8 @@ export async function authentication(request, nextHandler) {
|
|
|
367
405
|
// if we are rejecting the credentials (401, possibly rewritten to a login redirect); such a
|
|
368
406
|
// response must never be stored by a shared cache and served to a different principal (#1565)
|
|
369
407
|
const rejectedAuth = response?.status === 401 || wasUnauthorized;
|
|
370
|
-
|
|
408
|
+
// A downstream response produced under the untouched credential remains identity-dependent (#1565).
|
|
409
|
+
const identityDependent = !!request.user || rejectedAuth || !!getDeferredCredentialRejection(request);
|
|
371
410
|
// with CORS enabled the response is origin-dependent — ACAO reflects the request Origin, and
|
|
372
411
|
// its absence when no Origin was sent is origin-dependent too — so a shared cache must
|
|
373
412
|
// partition on Origin either way (#1518)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issuer-agnostic claim matching and constraint validation (#2171).
|
|
3
|
+
*
|
|
4
|
+
* Pure — no network, no storage, and nothing that knows which issuer a token came from. Rules that
|
|
5
|
+
* depend on the issuer live in providers/.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ClientError } from '../../../utility/errors/hdbError.ts';
|
|
9
|
+
import type { ClaimConstraint, TokenClaims } from './types.ts';
|
|
10
|
+
|
|
11
|
+
/** Issuers may encode a numeric id as a JSON number; anything non-scalar is not comparable. */
|
|
12
|
+
function claimToString(value: unknown): string | undefined {
|
|
13
|
+
if (typeof value === 'string') return value;
|
|
14
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value);
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns undefined on a match, or the first failure's reason — for the log, never the caller.
|
|
20
|
+
*
|
|
21
|
+
* A constrained claim absent from the token fails rather than passes, so a policy cannot be weakened
|
|
22
|
+
* by an issuer that stops emitting a claim.
|
|
23
|
+
*/
|
|
24
|
+
export function matchTrustPolicyClaims(
|
|
25
|
+
claims: TokenClaims,
|
|
26
|
+
policyClaims: Record<string, ClaimConstraint>
|
|
27
|
+
): string | undefined {
|
|
28
|
+
const constraints = Object.entries(policyClaims);
|
|
29
|
+
// validateClaimConstraintShape rejects this at write time; this backstops a row that reached the
|
|
30
|
+
// table another way, such as replication from a peer.
|
|
31
|
+
if (constraints.length === 0) return 'policy constrains no claims';
|
|
32
|
+
|
|
33
|
+
for (const [claimName, constraint] of constraints) {
|
|
34
|
+
const actual = claimToString(claims[claimName]);
|
|
35
|
+
if (actual === undefined || actual === '') return `token has no usable ${claimName} claim`;
|
|
36
|
+
const accepted = Array.isArray(constraint) ? constraint : [constraint];
|
|
37
|
+
// Exact membership, deliberately — never a prefix, wildcard, or regex. Relaxing this to
|
|
38
|
+
// something like `startsWith` is the classic trusted-publishing escalation: a policy pinning
|
|
39
|
+
// `HarperFast/my-app` would then also admit `HarperFast/my-app-evil`, a repository anyone can
|
|
40
|
+
// create. unitTests/security/authn/oidc/claims.test.js pins both argument orders.
|
|
41
|
+
if (!accepted.includes(actual)) return `${claimName} does not match the policy`;
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validates the *shape* of a policy's constraints — that each is a usable set of comparable values.
|
|
48
|
+
* Whether the set is specific enough to be safe depends on the issuer; that is the provider profile's
|
|
49
|
+
* assertPolicyIsSpecific.
|
|
50
|
+
*
|
|
51
|
+
* Throws ClientError naming the first problem; the reader is an administrator writing a policy.
|
|
52
|
+
*/
|
|
53
|
+
export function validateClaimConstraintShape(
|
|
54
|
+
policyClaims: unknown
|
|
55
|
+
): asserts policyClaims is Record<string, ClaimConstraint> {
|
|
56
|
+
if (!policyClaims || typeof policyClaims !== 'object' || Array.isArray(policyClaims)) {
|
|
57
|
+
throw new ClientError('claims must be an object of claim constraints');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const entries = Object.entries(policyClaims as Record<string, unknown>);
|
|
61
|
+
if (entries.length === 0) throw new ClientError('claims must constrain at least one claim');
|
|
62
|
+
|
|
63
|
+
for (const [claimName, constraint] of entries) {
|
|
64
|
+
const values = Array.isArray(constraint) ? constraint : [constraint];
|
|
65
|
+
if (values.length === 0) throw new ClientError(`claims.${claimName} must accept at least one value`);
|
|
66
|
+
for (const value of values) {
|
|
67
|
+
if (typeof value !== 'string' || value === '') {
|
|
68
|
+
throw new ClientError(`claims.${claimName} must be a non-empty string or an array of non-empty strings`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-token verification for OIDC trusted publishing (#2171).
|
|
3
|
+
*
|
|
4
|
+
* Verifies that a token was signed by the configured issuer and is addressed to this instance.
|
|
5
|
+
* Matching it against a policy's claim constraints is separate (claims.ts), because one verification
|
|
6
|
+
* serves every policy sharing an issuer and audience.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import jwt, { type Algorithm, type JwtPayload } from 'jsonwebtoken';
|
|
10
|
+
import type { KeyObject } from 'node:crypto';
|
|
11
|
+
import { ClientError } from '../../../utility/errors/hdbError.ts';
|
|
12
|
+
import { loggerWithTag } from '../../../utility/logging/logger.ts';
|
|
13
|
+
import { getSigningKey as defaultGetSigningKey, normalizeIssuer } from './jwks.ts';
|
|
14
|
+
import { profileForIssuer } from './providers/index.ts';
|
|
15
|
+
import type { TokenClaims } from './types.ts';
|
|
16
|
+
|
|
17
|
+
const logger = loggerWithTag('oidc-trust');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Asymmetric signatures only. Passing this to jwt.verify is what prevents algorithm confusion: an
|
|
21
|
+
* `alg: none` token, or one HMAC-signed with a public key as the secret, is rejected before the
|
|
22
|
+
* signature is considered.
|
|
23
|
+
*/
|
|
24
|
+
const ALLOWED_ALGORITHMS: Algorithm[] = [
|
|
25
|
+
'RS256',
|
|
26
|
+
'RS384',
|
|
27
|
+
'RS512',
|
|
28
|
+
'ES256',
|
|
29
|
+
'ES384',
|
|
30
|
+
'ES512',
|
|
31
|
+
'PS256',
|
|
32
|
+
'PS384',
|
|
33
|
+
'PS512',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const CLOCK_TOLERANCE_SECONDS = 60;
|
|
37
|
+
|
|
38
|
+
/** CI identity tokens are minted per job and live minutes; a far-future expiry is not one to honor. */
|
|
39
|
+
const MAX_TOKEN_LIFETIME_SECONDS = 3_600;
|
|
40
|
+
|
|
41
|
+
export interface VerifyTokenTarget {
|
|
42
|
+
issuer: string;
|
|
43
|
+
audience: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface VerifyTokenOptions {
|
|
47
|
+
/** Overridable for tests; defaults to the network-backed JWKS lookup. */
|
|
48
|
+
getSigningKey?: (issuer: string, kid: unknown) => Promise<KeyObject>;
|
|
49
|
+
/** Seconds since the epoch to evaluate expiry against; defaults to the real clock. */
|
|
50
|
+
clockTimestamp?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Refuses a token. Shared with the exchange so both halves fail identically: the endpoint is
|
|
55
|
+
* unauthenticated, and a caller told exactly which check failed can probe a policy one claim at a
|
|
56
|
+
* time. The reason goes to the log instead.
|
|
57
|
+
*/
|
|
58
|
+
export function rejectToken(detail: string): never {
|
|
59
|
+
logger.warn?.(`Rejecting identity token: ${detail}`);
|
|
60
|
+
throw new ClientError('Identity token was rejected', 401);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Verifies a CI identity token against one issuer/audience pair and returns its normalized claims. */
|
|
64
|
+
export async function verifyIdentityToken(
|
|
65
|
+
token: unknown,
|
|
66
|
+
target: VerifyTokenTarget,
|
|
67
|
+
options: VerifyTokenOptions = {}
|
|
68
|
+
): Promise<TokenClaims> {
|
|
69
|
+
if (typeof token !== 'string' || token === '') throw new ClientError('token is required');
|
|
70
|
+
const issuer = normalizeIssuer(target.issuer);
|
|
71
|
+
if (typeof target.audience !== 'string' || target.audience === '') {
|
|
72
|
+
throw new ClientError('audience is required');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const decoded = jwt.decode(token, { complete: true });
|
|
76
|
+
if (!decoded) rejectToken('token is not a well-formed JWT');
|
|
77
|
+
|
|
78
|
+
// Checked before resolving a key so a garbage header costs no outbound request.
|
|
79
|
+
if (!ALLOWED_ALGORITHMS.includes(decoded.header.alg as Algorithm)) {
|
|
80
|
+
rejectToken(`unsupported algorithm ${decoded.header.alg}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const getSigningKey = options.getSigningKey ?? defaultGetSigningKey;
|
|
84
|
+
const key = await getSigningKey(issuer, decoded.header.kid);
|
|
85
|
+
|
|
86
|
+
let payload: JwtPayload;
|
|
87
|
+
try {
|
|
88
|
+
payload = jwt.verify(token, key, {
|
|
89
|
+
algorithms: ALLOWED_ALGORITHMS,
|
|
90
|
+
// Both spellings, because `issuer` here is normalized (trailing slash stripped) while
|
|
91
|
+
// jsonwebtoken compares it byte-for-byte against the raw `iss`. Azure AD v1 emits
|
|
92
|
+
// `https://sts.windows.net/<tenant>/`, which would otherwise never authenticate — and the
|
|
93
|
+
// generic profile exists precisely to serve issuers like it with no provider code. This
|
|
94
|
+
// cannot widen trust: normalizeIssuer already maps the two forms to one value, which is
|
|
95
|
+
// what keys the JWKS cache, the discovery check, and the policy lookup.
|
|
96
|
+
issuer: [issuer, `${issuer}/`],
|
|
97
|
+
audience: target.audience,
|
|
98
|
+
clockTolerance: CLOCK_TOLERANCE_SECONDS,
|
|
99
|
+
...(options.clockTimestamp === undefined ? {} : { clockTimestamp: options.clockTimestamp }),
|
|
100
|
+
}) as JwtPayload;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
rejectToken((error as Error).message);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// jsonwebtoken only enforces `exp` when present, so a token without one never expires.
|
|
106
|
+
if (typeof payload.exp !== 'number') rejectToken('token has no exp claim');
|
|
107
|
+
// `iat` is REQUIRED by OIDC, and required here rather than treated as optional: skipping the
|
|
108
|
+
// ceiling when it is absent means a token carrying a distant `exp` and no `iat` is unbounded —
|
|
109
|
+
// the ceiling would silently not apply to exactly the token that most needs it.
|
|
110
|
+
if (typeof payload.iat !== 'number') rejectToken('token has no iat claim');
|
|
111
|
+
|
|
112
|
+
// Bounded against the verification clock, not only as `exp - iat`. jsonwebtoken validates `exp`
|
|
113
|
+
// and `nbf` but never rejects a future `iat`, so an issuer that shifts the whole pair forward
|
|
114
|
+
// keeps a small delta and still hands out a token valid far longer than the ceiling. Both the
|
|
115
|
+
// delta and the absolute distance to `exp` are checked; the tolerance mirrors the one given to
|
|
116
|
+
// jwt.verify so genuine clock skew is not treated as an attack.
|
|
117
|
+
const now = options.clockTimestamp ?? Math.floor(Date.now() / 1000);
|
|
118
|
+
if (payload.iat > now + CLOCK_TOLERANCE_SECONDS) rejectToken('token iat is in the future');
|
|
119
|
+
if (payload.exp - payload.iat > MAX_TOKEN_LIFETIME_SECONDS) {
|
|
120
|
+
rejectToken(`token lifetime exceeds ${MAX_TOKEN_LIFETIME_SECONDS}s`);
|
|
121
|
+
}
|
|
122
|
+
if (payload.exp - now > MAX_TOKEN_LIFETIME_SECONDS + CLOCK_TOLERANCE_SECONDS) {
|
|
123
|
+
rejectToken(`token expires more than ${MAX_TOKEN_LIFETIME_SECONDS}s from now`);
|
|
124
|
+
}
|
|
125
|
+
// No `jti` requirement: not every issuer emits one (Azure uses `uti`, others omit it), and the
|
|
126
|
+
// exchange keys replay on a hash of the token itself, which is universal.
|
|
127
|
+
|
|
128
|
+
return profileForIssuer(issuer).normalizeClaims(payload as TokenClaims);
|
|
129
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OIDC discovery and JWKS retrieval for trusted publishing (#2171).
|
|
3
|
+
*
|
|
4
|
+
* The issuer's signing keys are the root of trust for an exchanged token, and the endpoint that
|
|
5
|
+
* reaches them is unauthenticated — hence HTTPS only, bounded body, bounded time, and a rate limit
|
|
6
|
+
* on the refetch an unrecognized `kid` triggers, without which forged `kid`s become one outbound
|
|
7
|
+
* fetch per request.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createPublicKey, type KeyObject } from 'node:crypto';
|
|
11
|
+
import { loggerWithTag } from '../../../utility/logging/logger.ts';
|
|
12
|
+
import { ClientError, ServerError } from '../../../utility/errors/hdbError.ts';
|
|
13
|
+
|
|
14
|
+
const logger = loggerWithTag('oidc-trust');
|
|
15
|
+
|
|
16
|
+
const DISCOVERY_PATH = '/.well-known/openid-configuration';
|
|
17
|
+
/** How long a fetched key set is served without revalidation. */
|
|
18
|
+
const JWKS_CACHE_TTL_MS = 3_600_000;
|
|
19
|
+
/** Floor between refetches triggered by an unrecognized `kid`. */
|
|
20
|
+
const MIN_REFETCH_INTERVAL_MS = 60_000;
|
|
21
|
+
/**
|
|
22
|
+
* How long a cached key set survives a failed refetch. Bounded, so a network blip does not break
|
|
23
|
+
* deploys but a pulled key does not stay honored forever.
|
|
24
|
+
*/
|
|
25
|
+
const STALE_KEY_GRACE_MS = 86_400_000;
|
|
26
|
+
const FETCH_TIMEOUT_MS = 5_000;
|
|
27
|
+
const MAX_RESPONSE_BYTES = 1_048_576;
|
|
28
|
+
/** Asymmetric only: an `oct` key in a JWKS is the setup for algorithm confusion. */
|
|
29
|
+
const SUPPORTED_KEY_TYPES = ['RSA', 'EC'];
|
|
30
|
+
|
|
31
|
+
interface IssuerKeys {
|
|
32
|
+
keys: Map<string, KeyObject>;
|
|
33
|
+
fetchedAt: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const issuerKeyCache = new Map<string, IssuerKeys>();
|
|
37
|
+
const inFlightLoads = new Map<string, Promise<IssuerKeys>>();
|
|
38
|
+
/**
|
|
39
|
+
* When an unrecognized `kid` last drove a refetch, per issuer. Outside the cache entry on purpose:
|
|
40
|
+
* every successful fetch replaces that entry, and a rate limit that resets whenever it fires is not
|
|
41
|
+
* a rate limit. Separating them also lets a genuine key rotation be picked up on first use.
|
|
42
|
+
*/
|
|
43
|
+
const unknownKidRefetchAt = new Map<string, number>();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* When a refresh for an issuer last FAILED, per issuer. Tracked separately from `fetchedAt`, which a
|
|
47
|
+
* failed fetch does not advance — so without this, every request wave arriving after the cache went
|
|
48
|
+
* stale starts discovery again and rides the same timeout before falling back to the same stale key.
|
|
49
|
+
* The exchange is unauthenticated and picks its issuer from an unverified JWT, and issuer key ids are
|
|
50
|
+
* public, so an anonymous caller could keep that cycle running for the length of an issuer outage:
|
|
51
|
+
* exactly when the stale-key grace is supposed to be absorbing load, not generating it.
|
|
52
|
+
*/
|
|
53
|
+
const failedRefreshAt = new Map<string, number>();
|
|
54
|
+
|
|
55
|
+
/** How long a failed refresh suppresses the next one, while a usable stale key is still on hand. */
|
|
56
|
+
const FAILED_REFRESH_BACKOFF_MS = 30_000;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Bumped by every clear. A fetch that was already in flight when the cache was cleared must not
|
|
60
|
+
* write its now-stale result back: clearing drops `inFlightLoads`, but the orphaned fetch still
|
|
61
|
+
* holds a reference and would repopulate the entry an operator just discarded — which for a clear
|
|
62
|
+
* issued precisely because the cached key is wrong means the re-read silently does nothing.
|
|
63
|
+
*/
|
|
64
|
+
let cacheGeneration = 0;
|
|
65
|
+
|
|
66
|
+
/** Drops all cached key sets. Exported for tests and for an operator forcing a re-read. */
|
|
67
|
+
export function clearJwksCache(): void {
|
|
68
|
+
cacheGeneration++;
|
|
69
|
+
issuerKeyCache.clear();
|
|
70
|
+
inFlightLoads.clear();
|
|
71
|
+
unknownKidRefetchAt.clear();
|
|
72
|
+
failedRefreshAt.clear();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Canonicalizes an issuer URL. The result is both the cache key and the discovery base, so a policy
|
|
77
|
+
* stored with a trailing slash and one without must not become two entries for the same issuer.
|
|
78
|
+
*/
|
|
79
|
+
export function normalizeIssuer(issuer: unknown): string {
|
|
80
|
+
if (typeof issuer !== 'string' || issuer === '') throw new ClientError('issuer is required');
|
|
81
|
+
let url: URL;
|
|
82
|
+
try {
|
|
83
|
+
url = new URL(issuer);
|
|
84
|
+
} catch {
|
|
85
|
+
throw new ClientError(`issuer is not a valid URL: ${issuer}`);
|
|
86
|
+
}
|
|
87
|
+
if (url.protocol !== 'https:') throw new ClientError('issuer must be an https URL');
|
|
88
|
+
if (url.search !== '' || url.hash !== '') throw new ClientError('issuer must not carry a query or fragment');
|
|
89
|
+
return url.origin + (url.pathname === '/' ? '' : url.pathname.replace(/\/$/, ''));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* `content-length` is checked first as a cheap rejection, then the body is counted as it streams —
|
|
94
|
+
* the header is advisory, and a hostile endpoint can simply omit it.
|
|
95
|
+
*/
|
|
96
|
+
async function readBoundedJson(response: Response, url: string): Promise<any> {
|
|
97
|
+
const declaredLength = Number(response.headers.get('content-length'));
|
|
98
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_RESPONSE_BYTES) {
|
|
99
|
+
throw new ServerError(`Response from ${url} exceeds ${MAX_RESPONSE_BYTES} bytes`);
|
|
100
|
+
}
|
|
101
|
+
const chunks: Buffer[] = [];
|
|
102
|
+
let total = 0;
|
|
103
|
+
if (response.body) {
|
|
104
|
+
for await (const chunk of response.body as unknown as AsyncIterable<Uint8Array>) {
|
|
105
|
+
total += chunk.length;
|
|
106
|
+
if (total > MAX_RESPONSE_BYTES) {
|
|
107
|
+
throw new ServerError(`Response from ${url} exceeds ${MAX_RESPONSE_BYTES} bytes`);
|
|
108
|
+
}
|
|
109
|
+
chunks.push(Buffer.from(chunk));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new ServerError(`Response from ${url} is not valid JSON: ${(error as Error).message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function fetchJson(url: string): Promise<any> {
|
|
120
|
+
let response: Response;
|
|
121
|
+
try {
|
|
122
|
+
response = await fetch(url, {
|
|
123
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
124
|
+
headers: { accept: 'application/json' },
|
|
125
|
+
redirect: 'error',
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw new ServerError(`Could not reach ${url}: ${(error as Error).message}`);
|
|
129
|
+
}
|
|
130
|
+
if (!response.ok) throw new ServerError(`${url} responded ${response.status}`);
|
|
131
|
+
return readBoundedJson(response, url);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The discovery document's own `issuer` must equal the one we asked about — the spec requires it,
|
|
136
|
+
* and it stops a misdirected document from quietly re-pointing an issuer we trust.
|
|
137
|
+
*/
|
|
138
|
+
async function discoverJwksUri(issuer: string): Promise<string> {
|
|
139
|
+
const document = await fetchJson(issuer + DISCOVERY_PATH);
|
|
140
|
+
// Swallowed rather than propagated: normalizeIssuer raises ClientError, the wrong shape for a
|
|
141
|
+
// malformed *server* response.
|
|
142
|
+
let declaredIssuer: string | undefined;
|
|
143
|
+
try {
|
|
144
|
+
declaredIssuer = normalizeIssuer(document?.issuer);
|
|
145
|
+
} catch {
|
|
146
|
+
declaredIssuer = undefined;
|
|
147
|
+
}
|
|
148
|
+
if (declaredIssuer !== issuer) {
|
|
149
|
+
throw new ServerError(`Discovery document at ${issuer} declares a different issuer`);
|
|
150
|
+
}
|
|
151
|
+
const jwksUri = document?.jwks_uri;
|
|
152
|
+
if (typeof jwksUri !== 'string' || !jwksUri.startsWith('https://')) {
|
|
153
|
+
throw new ServerError(`Discovery document at ${issuer} has no https jwks_uri`);
|
|
154
|
+
}
|
|
155
|
+
return jwksUri;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Undefined for a key we will not honor. One unusable entry must not poison the set: issuers publish
|
|
160
|
+
* keys for other purposes, and an unknown future type should degrade rather than fail the fetch.
|
|
161
|
+
*/
|
|
162
|
+
function toSigningKey(jwk: any): KeyObject | undefined {
|
|
163
|
+
if (!jwk || typeof jwk !== 'object') return undefined;
|
|
164
|
+
if (typeof jwk.kid !== 'string' || jwk.kid === '') return undefined;
|
|
165
|
+
if (jwk.use !== undefined && jwk.use !== 'sig') return undefined;
|
|
166
|
+
if (!SUPPORTED_KEY_TYPES.includes(jwk.kty)) return undefined;
|
|
167
|
+
try {
|
|
168
|
+
return createPublicKey({ key: jwk, format: 'jwk' });
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logger.warn?.(`Skipping unusable JWK ${jwk.kid}: ${(error as Error).message}`);
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function fetchIssuerKeys(issuer: string): Promise<IssuerKeys> {
|
|
176
|
+
// Sampled before the first await, so any clear during the fetch is detectable at the end.
|
|
177
|
+
const generation = cacheGeneration;
|
|
178
|
+
const jwksUri = await discoverJwksUri(issuer);
|
|
179
|
+
const jwks = await fetchJson(jwksUri);
|
|
180
|
+
if (!Array.isArray(jwks?.keys)) throw new ServerError(`JWKS at ${jwksUri} has no keys array`);
|
|
181
|
+
|
|
182
|
+
const keys = new Map<string, KeyObject>();
|
|
183
|
+
for (const jwk of jwks.keys) {
|
|
184
|
+
const key = toSigningKey(jwk);
|
|
185
|
+
if (key) keys.set(jwk.kid, key);
|
|
186
|
+
}
|
|
187
|
+
if (keys.size === 0) throw new ServerError(`JWKS at ${jwksUri} contains no usable signing keys`);
|
|
188
|
+
|
|
189
|
+
const entry: IssuerKeys = { keys, fetchedAt: Date.now() };
|
|
190
|
+
// Still returned to the caller that asked for it — it is freshly fetched and valid — but only
|
|
191
|
+
// cached if no clear happened while this was in flight.
|
|
192
|
+
if (generation === cacheGeneration) issuerKeyCache.set(issuer, entry);
|
|
193
|
+
logger.debug?.(`Loaded ${keys.size} signing key(s) for ${issuer}`);
|
|
194
|
+
return entry;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Loads an issuer's keys, collapsing concurrent callers onto one fetch. */
|
|
198
|
+
function loadIssuerKeys(issuer: string): Promise<IssuerKeys> {
|
|
199
|
+
const existing = inFlightLoads.get(issuer);
|
|
200
|
+
if (existing) return existing;
|
|
201
|
+
|
|
202
|
+
const load = fetchIssuerKeys(issuer).finally(() => inFlightLoads.delete(issuer));
|
|
203
|
+
inFlightLoads.set(issuer, load);
|
|
204
|
+
return load;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Resolves the public key an issuer used to sign a token, by `kid`. */
|
|
208
|
+
/**
|
|
209
|
+
* `now` exists so the two time-based bounds below can be tested — the cache TTL and, more
|
|
210
|
+
* importantly, the STALE_KEY_GRACE_MS ceiling that stops a pulled key being honored forever through
|
|
211
|
+
* a prolonged outage. Reaching either otherwise needs a cache aged past an hour, and this repo bars
|
|
212
|
+
* new fake timers. Mirrors the `clockTimestamp` seam verifyIdentityToken already takes, rather than
|
|
213
|
+
* inventing a second convention. Callers in production pass nothing.
|
|
214
|
+
*/
|
|
215
|
+
export async function getSigningKey(issuer: string, kid: unknown, now: number = Date.now()): Promise<KeyObject> {
|
|
216
|
+
if (typeof kid !== 'string' || kid === '') throw new ClientError('Token has no key id', 401);
|
|
217
|
+
const normalizedIssuer = normalizeIssuer(issuer);
|
|
218
|
+
const cached = issuerKeyCache.get(normalizedIssuer);
|
|
219
|
+
|
|
220
|
+
if (cached && now - cached.fetchedAt < JWKS_CACHE_TTL_MS) {
|
|
221
|
+
const key = cached.keys.get(kid);
|
|
222
|
+
if (key) return key;
|
|
223
|
+
// Unknown kid against a still-fresh set: refetch once, then hold the line for the window. The
|
|
224
|
+
// timestamp is recorded before the fetch so a failing issuer is rate-limited like a succeeding one.
|
|
225
|
+
if (now - (unknownKidRefetchAt.get(normalizedIssuer) ?? 0) < MIN_REFETCH_INTERVAL_MS) {
|
|
226
|
+
throw new ClientError('Token signing key is not recognized', 401);
|
|
227
|
+
}
|
|
228
|
+
unknownKidRefetchAt.set(normalizedIssuer, now);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// A usable stale key, if the cache is expired but still inside the grace window.
|
|
232
|
+
const staleKey = cached && now - cached.fetchedAt < STALE_KEY_GRACE_MS ? cached.keys.get(kid) : undefined;
|
|
233
|
+
|
|
234
|
+
// Skip the fetch entirely while a recent one is known to have failed. Not gated on having a stale
|
|
235
|
+
// key: the fetch is the expensive part, and the issuer we have NEVER cached is the worse case —
|
|
236
|
+
// there is nothing to fall back to, so without this every request rides the full discovery and
|
|
237
|
+
// timeout. Refusing for the backoff interval bounds what an anonymous caller can drive, at the
|
|
238
|
+
// cost of a legitimate first exchange waiting out that interval after a blip. Fails closed.
|
|
239
|
+
if (now - (failedRefreshAt.get(normalizedIssuer) ?? 0) < FAILED_REFRESH_BACKOFF_MS) {
|
|
240
|
+
if (staleKey) return staleKey;
|
|
241
|
+
throw new ServerError(`Signing keys for ${normalizedIssuer} are temporarily unavailable; a recent refresh failed`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let refreshed: IssuerKeys;
|
|
245
|
+
try {
|
|
246
|
+
refreshed = await loadIssuerKeys(normalizedIssuer);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
// Recorded whether or not a stale key rescues this request, so the backoff also covers the
|
|
249
|
+
// issuer whose keys we have never held.
|
|
250
|
+
failedRefreshAt.set(normalizedIssuer, now);
|
|
251
|
+
if (!staleKey) throw error;
|
|
252
|
+
logger.warn?.(`Using cached signing key for ${normalizedIssuer}; refresh failed: ${(error as Error).message}`);
|
|
253
|
+
return staleKey;
|
|
254
|
+
}
|
|
255
|
+
failedRefreshAt.delete(normalizedIssuer);
|
|
256
|
+
|
|
257
|
+
const key = refreshed.keys.get(kid);
|
|
258
|
+
if (!key) throw new ClientError('Token signing key is not recognized', 401);
|
|
259
|
+
return key;
|
|
260
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback profile for issuers with no registered provider profile (#2171).
|
|
3
|
+
*
|
|
4
|
+
* Strict rather than permissive: the policy must pin `sub`. That is what makes workload identity work
|
|
5
|
+
* with zero provider code — a Kubernetes service-account token
|
|
6
|
+
* (`system:serviceaccount:<namespace>:<name>`), a GCP service account, and a SPIFFE SVID all carry a
|
|
7
|
+
* stable canonical subject, so pinning it identifies exactly one principal.
|
|
8
|
+
*
|
|
9
|
+
* GitHub Actions needs its own profile precisely because its `sub` is the one claim you should not
|
|
10
|
+
* pin: it varies by trigger, and its format changed for repositories created after 2026-07-15.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ClientError } from '../../../../utility/errors/hdbError.ts';
|
|
14
|
+
import type { ClaimConstraint, TokenClaims } from '../types.ts';
|
|
15
|
+
import type { IdentityProviderProfile } from './index.ts';
|
|
16
|
+
|
|
17
|
+
export const genericProfile: IdentityProviderProfile = {
|
|
18
|
+
name: 'generic OIDC',
|
|
19
|
+
|
|
20
|
+
assertPolicyIsSpecific(policyClaims: Record<string, ClaimConstraint>): void {
|
|
21
|
+
if (policyClaims.sub === undefined) {
|
|
22
|
+
throw new ClientError(
|
|
23
|
+
'claims must pin `sub` for an issuer with no registered provider profile — it is the only ' +
|
|
24
|
+
'claim every OIDC issuer defines as identifying a single principal. Register a provider ' +
|
|
25
|
+
'profile if this issuer needs richer rules.'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// No known shared default audience; the required `sub` pin already binds the policy to one principal.
|
|
31
|
+
assertAudienceIsSpecific(): void {},
|
|
32
|
+
|
|
33
|
+
normalizeClaims(payload: TokenClaims): TokenClaims {
|
|
34
|
+
return { ...payload };
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
describePrincipal(claims: TokenClaims): string {
|
|
38
|
+
return typeof claims.sub === 'string' ? claims.sub : 'unknown principal';
|
|
39
|
+
},
|
|
40
|
+
};
|