@enbox/dwn-sdk-js 0.0.6 → 0.0.8
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/dist/browser.mjs +8 -8
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +762 -911
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/abstract-message.js +4 -0
- package/dist/esm/src/core/abstract-message.js.map +1 -1
- package/dist/esm/src/core/auth.js +22 -33
- package/dist/esm/src/core/auth.js.map +1 -1
- package/dist/esm/src/core/constants.js +11 -0
- package/dist/esm/src/core/constants.js.map +1 -0
- package/dist/esm/src/core/core-protocol.js +44 -0
- package/dist/esm/src/core/core-protocol.js.map +1 -0
- package/dist/esm/src/core/dwn-constant.js +7 -7
- package/dist/esm/src/core/dwn-constant.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +10 -12
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/grant-authorization.js +50 -52
- package/dist/esm/src/core/grant-authorization.js.map +1 -1
- package/dist/esm/src/core/message.js +85 -116
- package/dist/esm/src/core/message.js.map +1 -1
- package/dist/esm/src/core/messages-grant-authorization.js +63 -78
- package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-action.js +266 -0
- package/dist/esm/src/core/protocol-authorization-action.js.map +1 -0
- package/dist/esm/src/core/protocol-authorization-validation.js +321 -0
- package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -0
- package/dist/esm/src/core/protocol-authorization.js +144 -741
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/protocols-grant-authorization.js +24 -38
- package/dist/esm/src/core/protocols-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/record-chain.js +64 -0
- package/dist/esm/src/core/record-chain.js.map +1 -0
- package/dist/esm/src/core/records-grant-authorization.js +53 -72
- package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/resumable-task-manager.js +50 -65
- package/dist/esm/src/core/resumable-task-manager.js.map +1 -1
- package/dist/esm/src/core/tenant-gate.js +2 -13
- package/dist/esm/src/core/tenant-gate.js.map +1 -1
- package/dist/esm/src/dwn.js +108 -101
- package/dist/esm/src/dwn.js.map +1 -1
- package/dist/esm/src/event-stream/event-emitter-event-log.js +204 -0
- package/dist/esm/src/event-stream/event-emitter-event-log.js.map +1 -0
- package/dist/esm/src/handlers/messages-read.js +67 -81
- package/dist/esm/src/handlers/messages-read.js.map +1 -1
- package/dist/esm/src/handlers/messages-subscribe.js +51 -63
- package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/messages-sync.js +75 -89
- package/dist/esm/src/handlers/messages-sync.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +153 -163
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/protocols-query.js +52 -55
- package/dist/esm/src/handlers/protocols-query.js.map +1 -1
- package/dist/esm/src/handlers/records-count.js +97 -85
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-delete.js +75 -93
- package/dist/esm/src/handlers/records-delete.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +116 -105
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-read.js +130 -132
- package/dist/esm/src/handlers/records-read.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +164 -104
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +213 -280
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +5 -2
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/messages-read.js +24 -32
- package/dist/esm/src/interfaces/messages-read.js.map +1 -1
- package/dist/esm/src/interfaces/messages-subscribe.js +28 -41
- package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/messages-sync.js +26 -40
- package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +87 -65
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-query.js +55 -68
- package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-count.js +50 -66
- package/dist/esm/src/interfaces/records-count.js.map +1 -1
- package/dist/esm/src/interfaces/records-delete.js +45 -55
- package/dist/esm/src/interfaces/records-delete.js.map +1 -1
- package/dist/esm/src/interfaces/records-query.js +60 -76
- package/dist/esm/src/interfaces/records-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-read.js +51 -67
- package/dist/esm/src/interfaces/records-read.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js +53 -68
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/records-write-query.js +102 -0
- package/dist/esm/src/interfaces/records-write-query.js.map +1 -0
- package/dist/esm/src/interfaces/records-write-signing.js +81 -0
- package/dist/esm/src/interfaces/records-write-signing.js.map +1 -0
- package/dist/esm/src/interfaces/records-write.js +396 -610
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/jose/algorithms/signing/ed25519.js +10 -19
- package/dist/esm/src/jose/algorithms/signing/ed25519.js.map +1 -1
- package/dist/esm/src/jose/jws/general/builder.js +23 -35
- package/dist/esm/src/jose/jws/general/builder.js.map +1 -1
- package/dist/esm/src/jose/jws/general/verifier.js +56 -69
- package/dist/esm/src/jose/jws/general/verifier.js.map +1 -1
- package/dist/esm/src/protocols/permission-grant.js +43 -14
- package/dist/esm/src/protocols/permission-grant.js.map +1 -1
- package/dist/esm/src/protocols/permission-request.js +28 -14
- package/dist/esm/src/protocols/permission-request.js.map +1 -1
- package/dist/esm/src/protocols/permissions.js +325 -227
- package/dist/esm/src/protocols/permissions.js.map +1 -1
- package/dist/esm/src/smt/smt-store-level.js +42 -64
- package/dist/esm/src/smt/smt-store-level.js.map +1 -1
- package/dist/esm/src/smt/smt-store-memory.js +19 -45
- package/dist/esm/src/smt/smt-store-memory.js.map +1 -1
- package/dist/esm/src/smt/smt-utils.js +28 -45
- package/dist/esm/src/smt/smt-utils.js.map +1 -1
- package/dist/esm/src/smt/sparse-merkle-tree.js +426 -471
- package/dist/esm/src/smt/sparse-merkle-tree.js.map +1 -1
- package/dist/esm/src/state-index/state-index-level.js +113 -150
- package/dist/esm/src/state-index/state-index-level.js.map +1 -1
- package/dist/esm/src/store/blockstore-level.js +54 -156
- package/dist/esm/src/store/blockstore-level.js.map +1 -1
- package/dist/esm/src/store/blockstore-mock.js +48 -153
- package/dist/esm/src/store/blockstore-mock.js.map +1 -1
- package/dist/esm/src/store/data-store-level.js +137 -100
- package/dist/esm/src/store/data-store-level.js.map +1 -1
- package/dist/esm/src/store/index-level-compound.js +246 -0
- package/dist/esm/src/store/index-level-compound.js.map +1 -0
- package/dist/esm/src/store/index-level.js +307 -715
- package/dist/esm/src/store/index-level.js.map +1 -1
- package/dist/esm/src/store/level-wrapper.js +143 -244
- package/dist/esm/src/store/level-wrapper.js.map +1 -1
- package/dist/esm/src/store/message-store-level.js +71 -94
- package/dist/esm/src/store/message-store-level.js.map +1 -1
- package/dist/esm/src/store/resumable-task-store-level.js +62 -101
- package/dist/esm/src/store/resumable-task-store-level.js.map +1 -1
- package/dist/esm/src/store/storage-controller.js +131 -146
- package/dist/esm/src/store/storage-controller.js.map +1 -1
- package/dist/esm/src/types/permission-types.js.map +1 -1
- package/dist/esm/src/types/protocols-types.js +10 -0
- package/dist/esm/src/types/protocols-types.js.map +1 -1
- package/dist/esm/src/types/records-types.js.map +1 -1
- package/dist/esm/src/utils/abort.js +8 -19
- package/dist/esm/src/utils/abort.js.map +1 -1
- package/dist/esm/src/utils/array.js +15 -49
- package/dist/esm/src/utils/array.js.map +1 -1
- package/dist/esm/src/utils/cid.js +29 -77
- package/dist/esm/src/utils/cid.js.map +1 -1
- package/dist/esm/src/utils/data-stream.js +37 -65
- package/dist/esm/src/utils/data-stream.js.map +1 -1
- package/dist/esm/src/utils/encryption.js +136 -162
- package/dist/esm/src/utils/encryption.js.map +1 -1
- package/dist/esm/src/utils/filter.js +1 -12
- package/dist/esm/src/utils/filter.js.map +1 -1
- package/dist/esm/src/utils/hd-key.js +45 -71
- package/dist/esm/src/utils/hd-key.js.map +1 -1
- package/dist/esm/src/utils/jws.js +9 -20
- package/dist/esm/src/utils/jws.js.map +1 -1
- package/dist/esm/src/utils/memory-cache.js +12 -23
- package/dist/esm/src/utils/memory-cache.js.map +1 -1
- package/dist/esm/src/utils/messages.js +21 -33
- package/dist/esm/src/utils/messages.js.map +1 -1
- package/dist/esm/src/utils/private-key-signer.js +9 -17
- package/dist/esm/src/utils/private-key-signer.js.map +1 -1
- package/dist/esm/src/utils/protocols.js +62 -70
- package/dist/esm/src/utils/protocols.js.map +1 -1
- package/dist/esm/src/utils/records.js +103 -166
- package/dist/esm/src/utils/records.js.map +1 -1
- package/dist/esm/src/utils/secp256k1.js +60 -96
- package/dist/esm/src/utils/secp256k1.js.map +1 -1
- package/dist/esm/src/utils/secp256r1.js +54 -71
- package/dist/esm/src/utils/secp256r1.js.map +1 -1
- package/dist/esm/src/utils/time.js +5 -18
- package/dist/esm/src/utils/time.js.map +1 -1
- package/dist/esm/src/utils/url.js +3 -3
- package/dist/esm/src/utils/url.js.map +1 -1
- package/dist/esm/tests/core/auth.spec.js +3 -12
- package/dist/esm/tests/core/auth.spec.js.map +1 -1
- package/dist/esm/tests/core/message.spec.js +50 -59
- package/dist/esm/tests/core/message.spec.js.map +1 -1
- package/dist/esm/tests/core/protocol-authorization.spec.js +10 -18
- package/dist/esm/tests/core/protocol-authorization.spec.js.map +1 -1
- package/dist/esm/tests/dwn.spec.js +65 -89
- package/dist/esm/tests/dwn.spec.js.map +1 -1
- package/dist/esm/tests/event-emitter-event-log.spec.js +305 -0
- package/dist/esm/tests/event-emitter-event-log.spec.js.map +1 -0
- package/dist/esm/tests/features/author-delegated-grant.spec.js +337 -347
- package/dist/esm/tests/features/author-delegated-grant.spec.js.map +1 -1
- package/dist/esm/tests/features/owner-delegated-grant.spec.js +160 -172
- package/dist/esm/tests/features/owner-delegated-grant.spec.js.map +1 -1
- package/dist/esm/tests/features/owner-signature.spec.js +78 -82
- package/dist/esm/tests/features/owner-signature.spec.js.map +1 -1
- package/dist/esm/tests/features/permissions.spec.js +449 -184
- package/dist/esm/tests/features/permissions.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-composition.spec.js +981 -360
- package/dist/esm/tests/features/protocol-composition.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-create-action.spec.js +45 -54
- package/dist/esm/tests/features/protocol-create-action.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-delete-action.spec.js +99 -108
- package/dist/esm/tests/features/protocol-delete-action.spec.js.map +1 -1
- package/dist/esm/tests/features/protocol-update-action.spec.js +108 -117
- package/dist/esm/tests/features/protocol-update-action.spec.js.map +1 -1
- package/dist/esm/tests/features/records-immutable.spec.js +315 -0
- package/dist/esm/tests/features/records-immutable.spec.js.map +1 -0
- package/dist/esm/tests/features/records-prune.spec.js +178 -194
- package/dist/esm/tests/features/records-prune.spec.js.map +1 -1
- package/dist/esm/tests/features/records-record-limit.spec.js +542 -0
- package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -0
- package/dist/esm/tests/features/records-tags.spec.js +456 -463
- package/dist/esm/tests/features/records-tags.spec.js.map +1 -1
- package/dist/esm/tests/features/resumable-tasks.spec.js +88 -98
- package/dist/esm/tests/features/resumable-tasks.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-read.spec.js +215 -210
- package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-subscribe.spec.js +309 -171
- package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-sync.spec.js +272 -199
- package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js +247 -241
- package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-query.spec.js +159 -172
- package/dist/esm/tests/handlers/protocols-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +101 -105
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-delete.spec.js +266 -279
- package/dist/esm/tests/handlers/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-query.spec.js +984 -996
- package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-read.spec.js +542 -671
- package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +433 -302
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +1216 -1140
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/messages-get.spec.js +39 -48
- package/dist/esm/tests/interfaces/messages-get.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/messages-subscribe.spec.js +4 -13
- package/dist/esm/tests/interfaces/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/protocols-configure.spec.js +212 -88
- package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/protocols-query.spec.js +8 -17
- package/dist/esm/tests/interfaces/protocols-query.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-delete.spec.js +8 -17
- package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-query.spec.js +20 -29
- package/dist/esm/tests/interfaces/records-query.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-read.spec.js +42 -51
- package/dist/esm/tests/interfaces/records-read.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-subscribe.spec.js +16 -25
- package/dist/esm/tests/interfaces/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-write.spec.js +190 -219
- package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
- package/dist/esm/tests/jose/jws/general.spec.js +36 -45
- package/dist/esm/tests/jose/jws/general.spec.js.map +1 -1
- package/dist/esm/tests/protocols/permission-grant.spec.js +44 -50
- package/dist/esm/tests/protocols/permission-grant.spec.js.map +1 -1
- package/dist/esm/tests/protocols/permission-request.spec.js +23 -32
- package/dist/esm/tests/protocols/permission-request.spec.js.map +1 -1
- package/dist/esm/tests/protocols/permissions.spec.js +49 -55
- package/dist/esm/tests/protocols/permissions.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/aggregator.spec.js +127 -138
- package/dist/esm/tests/scenarios/aggregator.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/deleted-record.spec.js +372 -36
- package/dist/esm/tests/scenarios/deleted-record.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/end-to-end-tests.spec.js +55 -64
- package/dist/esm/tests/scenarios/end-to-end-tests.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/nested-roles.spec.js +66 -76
- package/dist/esm/tests/scenarios/nested-roles.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/subscriptions.spec.js +451 -354
- package/dist/esm/tests/scenarios/subscriptions.spec.js.map +1 -1
- package/dist/esm/tests/smt/smt-store-level.spec.js +76 -87
- package/dist/esm/tests/smt/smt-store-level.spec.js.map +1 -1
- package/dist/esm/tests/smt/sparse-merkle-tree.spec.js +344 -353
- package/dist/esm/tests/smt/sparse-merkle-tree.spec.js.map +1 -1
- package/dist/esm/tests/state-index/state-index-level.spec.js +117 -126
- package/dist/esm/tests/state-index/state-index-level.spec.js.map +1 -1
- package/dist/esm/tests/store/blockstore-level.spec.js +44 -99
- package/dist/esm/tests/store/blockstore-level.spec.js.map +1 -1
- package/dist/esm/tests/store/blockstore-mock.spec.js +40 -120
- package/dist/esm/tests/store/blockstore-mock.spec.js.map +1 -1
- package/dist/esm/tests/store/data-store-level.spec.js +160 -108
- package/dist/esm/tests/store/data-store-level.spec.js.map +1 -1
- package/dist/esm/tests/store/index-level.spec.js +404 -414
- package/dist/esm/tests/store/index-level.spec.js.map +1 -1
- package/dist/esm/tests/store/message-store-level.spec.js +13 -22
- package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
- package/dist/esm/tests/store/message-store.spec.js +229 -238
- package/dist/esm/tests/store/message-store.spec.js.map +1 -1
- package/dist/esm/tests/test-event-stream.js +12 -13
- package/dist/esm/tests/test-event-stream.js.map +1 -1
- package/dist/esm/tests/test-stores.js +16 -13
- package/dist/esm/tests/test-stores.js.map +1 -1
- package/dist/esm/tests/test-suite.js +8 -15
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/esm/tests/utils/cid.spec.js +24 -33
- package/dist/esm/tests/utils/cid.spec.js.map +1 -1
- package/dist/esm/tests/utils/data-stream.spec.js +48 -57
- package/dist/esm/tests/utils/data-stream.spec.js.map +1 -1
- package/dist/esm/tests/utils/encryption-callbacks.spec.js +45 -54
- package/dist/esm/tests/utils/encryption-callbacks.spec.js.map +1 -1
- package/dist/esm/tests/utils/encryption.spec.js +229 -82
- package/dist/esm/tests/utils/encryption.spec.js.map +1 -1
- package/dist/esm/tests/utils/filters.spec.js +46 -55
- package/dist/esm/tests/utils/filters.spec.js.map +1 -1
- package/dist/esm/tests/utils/hd-key.spec.js +10 -19
- package/dist/esm/tests/utils/hd-key.spec.js.map +1 -1
- package/dist/esm/tests/utils/jws.spec.js +3 -12
- package/dist/esm/tests/utils/jws.spec.js.map +1 -1
- package/dist/esm/tests/utils/memory-cache.spec.js +9 -18
- package/dist/esm/tests/utils/memory-cache.spec.js.map +1 -1
- package/dist/esm/tests/utils/messages.spec.js +18 -20
- package/dist/esm/tests/utils/messages.spec.js.map +1 -1
- package/dist/esm/tests/utils/poller.js +22 -33
- package/dist/esm/tests/utils/poller.js.map +1 -1
- package/dist/esm/tests/utils/private-key-signer.spec.js +15 -24
- package/dist/esm/tests/utils/private-key-signer.spec.js.map +1 -1
- package/dist/esm/tests/utils/records.spec.js +14 -27
- package/dist/esm/tests/utils/records.spec.js.map +1 -1
- package/dist/esm/tests/utils/secp256k1.spec.js +16 -25
- package/dist/esm/tests/utils/secp256k1.spec.js.map +1 -1
- package/dist/esm/tests/utils/secp256r1.spec.js +18 -27
- package/dist/esm/tests/utils/secp256r1.spec.js.map +1 -1
- package/dist/esm/tests/utils/test-data-generator.js +446 -467
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/definitions.spec.js +2 -11
- package/dist/esm/tests/validation/json-schemas/definitions.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/jwk/general-jwk.spec.js +4 -13
- package/dist/esm/tests/validation/json-schemas/jwk/general-jwk.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/jwk/public-jwk.spec.js +8 -17
- package/dist/esm/tests/validation/json-schemas/jwk/public-jwk.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/jwk-verification-method.spec.js +3 -12
- package/dist/esm/tests/validation/json-schemas/jwk-verification-method.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/protocols/protocols-configure.spec.js +4 -13
- package/dist/esm/tests/validation/json-schemas/protocols/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/records/records-query.spec.js +2 -11
- package/dist/esm/tests/validation/json-schemas/records/records-query.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/records/records-read.spec.js +2 -11
- package/dist/esm/tests/validation/json-schemas/records/records-read.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js +44 -24
- package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts +49 -40
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/constants.d.ts +11 -0
- package/dist/types/src/core/constants.d.ts.map +1 -0
- package/dist/types/src/core/core-protocol.d.ts +89 -0
- package/dist/types/src/core/core-protocol.d.ts.map +1 -0
- package/dist/types/src/core/dwn-error.d.ts +9 -12
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/grant-authorization.d.ts +6 -2
- package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-action.d.ts +42 -0
- package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -0
- package/dist/types/src/core/protocol-authorization-validation.d.ts +81 -0
- package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -0
- package/dist/types/src/core/protocol-authorization.d.ts +24 -106
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/record-chain.d.ts +24 -0
- package/dist/types/src/core/record-chain.d.ts.map +1 -0
- package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/dwn.d.ts +19 -7
- package/dist/types/src/dwn.d.ts.map +1 -1
- package/dist/types/src/event-stream/event-emitter-event-log.d.ts +50 -0
- package/dist/types/src/event-stream/event-emitter-event-log.d.ts.map +1 -0
- package/dist/types/src/handlers/messages-read.d.ts +3 -8
- package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts +6 -10
- package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-sync.d.ts +3 -8
- package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-configure.d.ts +3 -10
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-query.d.ts +3 -8
- package/dist/types/src/handlers/protocols-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-count.d.ts +3 -6
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-delete.d.ts +3 -8
- package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +3 -8
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-read.d.ts +3 -8
- package/dist/types/src/handlers/records-read.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts +8 -10
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts +4 -24
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +8 -4
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts +5 -0
- package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts +5 -0
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write-query.d.ts +33 -0
- package/dist/types/src/interfaces/records-write-query.d.ts.map +1 -0
- package/dist/types/src/interfaces/records-write-signing.d.ts +34 -0
- package/dist/types/src/interfaces/records-write-signing.d.ts.map +1 -0
- package/dist/types/src/interfaces/records-write.d.ts +13 -53
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/protocols/permission-grant.d.ts +1 -1
- package/dist/types/src/protocols/permission-grant.d.ts.map +1 -1
- package/dist/types/src/protocols/permission-request.d.ts +1 -1
- package/dist/types/src/protocols/permission-request.d.ts.map +1 -1
- package/dist/types/src/protocols/permissions.d.ts +40 -3
- package/dist/types/src/protocols/permissions.d.ts.map +1 -1
- package/dist/types/src/state-index/state-index-level.d.ts.map +1 -1
- package/dist/types/src/store/data-store-level.d.ts +20 -4
- package/dist/types/src/store/data-store-level.d.ts.map +1 -1
- package/dist/types/src/store/index-level-compound.d.ts +70 -0
- package/dist/types/src/store/index-level-compound.d.ts.map +1 -0
- package/dist/types/src/store/index-level.d.ts +4 -58
- package/dist/types/src/store/index-level.d.ts.map +1 -1
- package/dist/types/src/store/storage-controller.d.ts +4 -4
- package/dist/types/src/store/storage-controller.d.ts.map +1 -1
- package/dist/types/src/types/message-types.d.ts +3 -3
- package/dist/types/src/types/message-types.d.ts.map +1 -1
- package/dist/types/src/types/messages-types.d.ts +12 -3
- package/dist/types/src/types/messages-types.d.ts.map +1 -1
- package/dist/types/src/types/method-handler.d.ts +24 -3
- package/dist/types/src/types/method-handler.d.ts.map +1 -1
- package/dist/types/src/types/permission-types.d.ts +7 -0
- package/dist/types/src/types/permission-types.d.ts.map +1 -1
- package/dist/types/src/types/protocols-types.d.ts +41 -1
- package/dist/types/src/types/protocols-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +16 -6
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/types/subscriptions.d.ts +151 -13
- package/dist/types/src/types/subscriptions.d.ts.map +1 -1
- package/dist/types/src/utils/hd-key.d.ts +1 -9
- package/dist/types/src/utils/hd-key.d.ts.map +1 -1
- package/dist/types/src/utils/messages.d.ts +7 -5
- package/dist/types/src/utils/messages.d.ts.map +1 -1
- package/dist/types/src/utils/protocols.d.ts +5 -0
- package/dist/types/src/utils/protocols.d.ts.map +1 -1
- package/dist/types/src/utils/records.d.ts +1 -11
- package/dist/types/src/utils/records.d.ts.map +1 -1
- package/dist/types/tests/dwn.spec.d.ts.map +1 -1
- package/dist/types/tests/event-emitter-event-log.spec.d.ts +2 -0
- package/dist/types/tests/event-emitter-event-log.spec.d.ts.map +1 -0
- package/dist/types/tests/features/author-delegated-grant.spec.d.ts.map +1 -1
- package/dist/types/tests/features/owner-delegated-grant.spec.d.ts.map +1 -1
- package/dist/types/tests/features/owner-signature.spec.d.ts.map +1 -1
- package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
- package/dist/types/tests/features/protocol-composition.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-immutable.spec.d.ts +2 -0
- package/dist/types/tests/features/records-immutable.spec.d.ts.map +1 -0
- package/dist/types/tests/features/records-record-limit.spec.d.ts +2 -0
- package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -0
- package/dist/types/tests/features/records-tags.spec.d.ts.map +1 -1
- package/dist/types/tests/features/resumable-tasks.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-delete.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-query.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-read.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
- package/dist/types/tests/scenarios/deleted-record.spec.d.ts.map +1 -1
- package/dist/types/tests/scenarios/subscriptions.spec.d.ts.map +1 -1
- package/dist/types/tests/test-event-stream.d.ts +11 -12
- package/dist/types/tests/test-event-stream.d.ts.map +1 -1
- package/dist/types/tests/test-suite.d.ts +2 -2
- package/dist/types/tests/test-suite.d.ts.map +1 -1
- package/dist/types/tests/utils/test-data-generator.d.ts +18 -0
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/core/constants.ts +11 -0
- package/src/core/core-protocol.ts +129 -0
- package/src/core/dwn-error.ts +15 -12
- package/src/core/grant-authorization.ts +20 -3
- package/src/core/protocol-authorization-action.ts +377 -0
- package/src/core/protocol-authorization-validation.ts +487 -0
- package/src/core/protocol-authorization.ts +111 -856
- package/src/core/record-chain.ts +99 -0
- package/src/core/records-grant-authorization.ts +6 -8
- package/src/dwn.ts +58 -73
- package/src/event-stream/event-emitter-event-log.ts +283 -0
- package/src/handlers/messages-read.ts +8 -9
- package/src/handlers/messages-subscribe.ts +24 -28
- package/src/handlers/messages-sync.ts +10 -16
- package/src/handlers/protocols-configure.ts +47 -32
- package/src/handlers/protocols-query.ts +6 -9
- package/src/handlers/records-count.ts +11 -10
- package/src/handlers/records-delete.ts +12 -21
- package/src/handlers/records-query.ts +12 -12
- package/src/handlers/records-read.ts +34 -22
- package/src/handlers/records-subscribe.ts +47 -26
- package/src/handlers/records-write.ts +47 -104
- package/src/index.ts +9 -5
- package/src/interfaces/messages-subscribe.ts +7 -1
- package/src/interfaces/protocols-configure.ts +73 -8
- package/src/interfaces/records-count.ts +1 -1
- package/src/interfaces/records-delete.ts +1 -1
- package/src/interfaces/records-query.ts +1 -1
- package/src/interfaces/records-read.ts +1 -1
- package/src/interfaces/records-subscribe.ts +8 -1
- package/src/interfaces/records-write-query.ts +139 -0
- package/src/interfaces/records-write-signing.ts +123 -0
- package/src/interfaces/records-write.ts +66 -261
- package/src/protocols/permission-grant.ts +1 -1
- package/src/protocols/permission-request.ts +1 -1
- package/src/protocols/permissions.ts +148 -6
- package/src/state-index/state-index-level.ts +5 -7
- package/src/store/data-store-level.ts +124 -34
- package/src/store/index-level-compound.ts +324 -0
- package/src/store/index-level.ts +68 -341
- package/src/store/storage-controller.ts +11 -11
- package/src/types/message-types.ts +3 -3
- package/src/types/messages-types.ts +12 -3
- package/src/types/method-handler.ts +26 -4
- package/src/types/mitt.d.ts +28 -0
- package/src/types/permission-types.ts +7 -0
- package/src/types/protocols-types.ts +46 -0
- package/src/types/records-types.ts +16 -6
- package/src/types/subscriptions.ts +178 -14
- package/src/utils/hd-key.ts +0 -9
- package/src/utils/messages.ts +17 -37
- package/src/utils/protocols.ts +8 -0
- package/src/utils/records.ts +8 -59
- package/dist/esm/src/event-stream/event-emitter-stream.js +0 -60
- package/dist/esm/src/event-stream/event-emitter-stream.js.map +0 -1
- package/dist/esm/tests/event-stream/event-emitter-stream.spec.js +0 -77
- package/dist/esm/tests/event-stream/event-emitter-stream.spec.js.map +0 -1
- package/dist/esm/tests/event-stream/event-stream.spec.js +0 -123
- package/dist/esm/tests/event-stream/event-stream.spec.js.map +0 -1
- package/dist/types/src/event-stream/event-emitter-stream.d.ts +0 -23
- package/dist/types/src/event-stream/event-emitter-stream.d.ts.map +0 -1
- package/dist/types/tests/event-stream/event-emitter-stream.spec.d.ts +0 -2
- package/dist/types/tests/event-stream/event-emitter-stream.spec.d.ts.map +0 -1
- package/dist/types/tests/event-stream/event-stream.spec.d.ts +0 -2
- package/dist/types/tests/event-stream/event-stream.spec.d.ts.map +0 -1
- package/src/event-stream/event-emitter-stream.ts +0 -69
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CoreProtocolRegistry } from './core-protocol.js';
|
|
1
2
|
import type { Filter } from '../types/query-types.js';
|
|
2
3
|
import type { MessageStore } from '../types/message-store.js';
|
|
3
4
|
import type { RecordsCount } from '../interfaces/records-count.js';
|
|
@@ -5,21 +6,37 @@ import type { RecordsDelete } from '../interfaces/records-delete.js';
|
|
|
5
6
|
import type { RecordsQuery } from '../interfaces/records-query.js';
|
|
6
7
|
import type { RecordsRead } from '../interfaces/records-read.js';
|
|
7
8
|
import type { RecordsSubscribe } from '../interfaces/records-subscribe.js';
|
|
9
|
+
import type { RecordsWrite } from '../interfaces/records-write.js';
|
|
8
10
|
import type { RecordsWriteMessage } from '../types/records-types.js';
|
|
9
|
-
import type {
|
|
10
|
-
ProtocolActionRule, ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage, ProtocolType, ProtocolTypes
|
|
11
|
-
} from '../types/protocols-types.js';
|
|
11
|
+
import type { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage } from '../types/protocols-types.js';
|
|
12
12
|
|
|
13
|
-
import
|
|
14
|
-
import { FilterUtility } from '../utils/filter.js';
|
|
15
|
-
import { PermissionsProtocol } from '../protocols/permissions.js';
|
|
16
|
-
import { Records } from '../utils/records.js';
|
|
17
|
-
import { RecordsWrite } from '../interfaces/records-write.js';
|
|
13
|
+
import { getRuleSetAtPath } from '../utils/protocols.js';
|
|
18
14
|
import { SortDirection } from '../types/query-types.js';
|
|
19
15
|
import { DwnError, DwnErrorCode } from './dwn-error.js';
|
|
20
16
|
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
21
|
-
|
|
22
|
-
import {
|
|
17
|
+
|
|
18
|
+
import { authorizeAgainstAllowedActions, verifyInvokedRole } from './protocol-authorization-action.js';
|
|
19
|
+
import { constructRecordChain, fetchInitialWrite, getGoverningTimestamp } from './record-chain.js';
|
|
20
|
+
import {
|
|
21
|
+
verifyAsRoleRecordIfNeeded,
|
|
22
|
+
verifyImmutability,
|
|
23
|
+
verifyProtocolPathAndContextId,
|
|
24
|
+
verifyRecordLimit,
|
|
25
|
+
verifySizeLimit,
|
|
26
|
+
verifyTagsIfNeeded,
|
|
27
|
+
verifyTypeWithComposition,
|
|
28
|
+
} from './protocol-authorization-validation.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Function signature for fetching a protocol definition.
|
|
32
|
+
* Used by extracted modules to break the circular dependency on `ProtocolAuthorization`.
|
|
33
|
+
*/
|
|
34
|
+
export type FetchProtocolDefinitionFn = (
|
|
35
|
+
tenant: string,
|
|
36
|
+
protocolUri: string,
|
|
37
|
+
messageStore: MessageStore,
|
|
38
|
+
messageTimestamp?: string,
|
|
39
|
+
) => Promise<ProtocolDefinition>;
|
|
23
40
|
|
|
24
41
|
export class ProtocolAuthorization {
|
|
25
42
|
|
|
@@ -31,11 +48,12 @@ export class ProtocolAuthorization {
|
|
|
31
48
|
tenant: string,
|
|
32
49
|
incomingMessage: RecordsWrite,
|
|
33
50
|
messageStore: MessageStore,
|
|
51
|
+
coreProtocols?: CoreProtocolRegistry,
|
|
34
52
|
): Promise<void> {
|
|
35
53
|
// Determine the governing timestamp for protocol definition lookup.
|
|
36
54
|
// For an initial write, this is the message's own timestamp.
|
|
37
55
|
// For an update, this is the initial write's timestamp (the protocol version is locked at creation time).
|
|
38
|
-
const governingTimestamp = await
|
|
56
|
+
const governingTimestamp = await getGoverningTimestamp(
|
|
39
57
|
tenant, incomingMessage, messageStore
|
|
40
58
|
);
|
|
41
59
|
|
|
@@ -45,20 +63,23 @@ export class ProtocolAuthorization {
|
|
|
45
63
|
incomingMessage.message.descriptor.protocol!,
|
|
46
64
|
messageStore,
|
|
47
65
|
governingTimestamp,
|
|
66
|
+
coreProtocols,
|
|
48
67
|
);
|
|
49
68
|
|
|
69
|
+
// Create a bound fetch function that captures the registry for downstream callbacks.
|
|
70
|
+
const boundFetchDefinition = ProtocolAuthorization.createBoundFetchDefinition(coreProtocols);
|
|
71
|
+
|
|
50
72
|
// verify declared protocol type exists in protocol and that it conforms to type specification.
|
|
51
73
|
// For cross-protocol composition, the type may be defined in a referenced protocol.
|
|
52
|
-
await
|
|
53
|
-
tenant, incomingMessage.message, protocolDefinition, messageStore,
|
|
74
|
+
await verifyTypeWithComposition(
|
|
75
|
+
tenant, incomingMessage.message, protocolDefinition, messageStore,
|
|
76
|
+
boundFetchDefinition, governingTimestamp
|
|
54
77
|
);
|
|
55
78
|
|
|
56
79
|
// validate `protocolPath`
|
|
57
|
-
await
|
|
58
|
-
tenant,
|
|
59
|
-
|
|
60
|
-
messageStore,
|
|
61
|
-
governingTimestamp,
|
|
80
|
+
await verifyProtocolPathAndContextId(
|
|
81
|
+
tenant, incomingMessage, messageStore,
|
|
82
|
+
boundFetchDefinition, governingTimestamp,
|
|
62
83
|
);
|
|
63
84
|
|
|
64
85
|
// get the rule set for the inbound message
|
|
@@ -68,7 +89,7 @@ export class ProtocolAuthorization {
|
|
|
68
89
|
);
|
|
69
90
|
|
|
70
91
|
// Validate as a role record if the incoming message is writing a role record
|
|
71
|
-
await
|
|
92
|
+
await verifyAsRoleRecordIfNeeded(
|
|
72
93
|
tenant,
|
|
73
94
|
incomingMessage,
|
|
74
95
|
ruleSet,
|
|
@@ -76,10 +97,16 @@ export class ProtocolAuthorization {
|
|
|
76
97
|
);
|
|
77
98
|
|
|
78
99
|
// Verify size limit
|
|
79
|
-
|
|
100
|
+
verifySizeLimit(incomingMessage, ruleSet);
|
|
80
101
|
|
|
81
102
|
// Verify protocol tags
|
|
82
|
-
|
|
103
|
+
verifyTagsIfNeeded(incomingMessage, ruleSet);
|
|
104
|
+
|
|
105
|
+
// Verify immutability — reject updates to write-once records
|
|
106
|
+
await verifyImmutability(incomingMessage, ruleSet);
|
|
107
|
+
|
|
108
|
+
// Verify record count limit
|
|
109
|
+
await verifyRecordLimit(tenant, incomingMessage, ruleSet, messageStore);
|
|
83
110
|
}
|
|
84
111
|
|
|
85
112
|
/**
|
|
@@ -90,21 +117,22 @@ export class ProtocolAuthorization {
|
|
|
90
117
|
tenant: string,
|
|
91
118
|
incomingMessage: RecordsWrite,
|
|
92
119
|
messageStore: MessageStore,
|
|
120
|
+
coreProtocols?: CoreProtocolRegistry,
|
|
93
121
|
): Promise<void> {
|
|
94
|
-
const existingInitialWrite = await
|
|
122
|
+
const existingInitialWrite = await fetchInitialWrite(tenant, incomingMessage.message.recordId, messageStore);
|
|
95
123
|
|
|
96
124
|
let recordChain;
|
|
97
125
|
if (existingInitialWrite === undefined) {
|
|
98
126
|
// NOTE: we can assume this message is an initial write because an existing initial write does not exist.
|
|
99
127
|
// Additionally, we check further down in the `RecordsWriteHandler` if the incoming message is an initialWrite,
|
|
100
128
|
// so we don't check explicitly here to avoid an unnecessary duplicate check.
|
|
101
|
-
recordChain = await
|
|
129
|
+
recordChain = await constructRecordChain(tenant, incomingMessage.message.descriptor.parentId, messageStore);
|
|
102
130
|
} else {
|
|
103
|
-
recordChain = await
|
|
131
|
+
recordChain = await constructRecordChain(tenant, incomingMessage.message.recordId, messageStore);
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
// Determine the governing timestamp for protocol definition lookup.
|
|
107
|
-
const governingTimestamp = await
|
|
135
|
+
const governingTimestamp = await getGoverningTimestamp(
|
|
108
136
|
tenant, incomingMessage, messageStore
|
|
109
137
|
);
|
|
110
138
|
|
|
@@ -114,6 +142,7 @@ export class ProtocolAuthorization {
|
|
|
114
142
|
incomingMessage.message.descriptor.protocol!,
|
|
115
143
|
messageStore,
|
|
116
144
|
governingTimestamp,
|
|
145
|
+
coreProtocols,
|
|
117
146
|
);
|
|
118
147
|
|
|
119
148
|
// get the rule set for the inbound message
|
|
@@ -122,19 +151,22 @@ export class ProtocolAuthorization {
|
|
|
122
151
|
protocolDefinition,
|
|
123
152
|
);
|
|
124
153
|
|
|
154
|
+
const boundFetchDefinition = ProtocolAuthorization.createBoundFetchDefinition(coreProtocols);
|
|
155
|
+
|
|
125
156
|
// If the incoming message has `protocolRole` in the descriptor, validate the invoked role
|
|
126
|
-
await
|
|
157
|
+
await verifyInvokedRole(
|
|
127
158
|
tenant,
|
|
128
159
|
incomingMessage,
|
|
129
160
|
incomingMessage.message.descriptor.protocol!,
|
|
130
161
|
incomingMessage.message.contextId!,
|
|
131
162
|
protocolDefinition,
|
|
132
163
|
messageStore,
|
|
164
|
+
boundFetchDefinition,
|
|
133
165
|
governingTimestamp,
|
|
134
166
|
);
|
|
135
167
|
|
|
136
168
|
// verify method invoked against the allowed actions in the rule set
|
|
137
|
-
await
|
|
169
|
+
await authorizeAgainstAllowedActions(
|
|
138
170
|
tenant,
|
|
139
171
|
incomingMessage,
|
|
140
172
|
ruleSet,
|
|
@@ -154,14 +186,15 @@ export class ProtocolAuthorization {
|
|
|
154
186
|
incomingMessage: RecordsRead,
|
|
155
187
|
newestRecordsWrite: RecordsWrite,
|
|
156
188
|
messageStore: MessageStore,
|
|
189
|
+
coreProtocols?: CoreProtocolRegistry,
|
|
157
190
|
): Promise<void> {
|
|
158
191
|
// fetch record chain
|
|
159
192
|
const recordChain: RecordsWriteMessage[] =
|
|
160
|
-
await
|
|
193
|
+
await constructRecordChain(tenant, newestRecordsWrite.message.recordId, messageStore);
|
|
161
194
|
|
|
162
195
|
// Use the initial write's timestamp to determine the governing protocol definition.
|
|
163
196
|
// The protocol version is locked at the time the record was first created.
|
|
164
|
-
const initialWrite = await
|
|
197
|
+
const initialWrite = await fetchInitialWrite(
|
|
165
198
|
tenant, newestRecordsWrite.message.recordId, messageStore
|
|
166
199
|
);
|
|
167
200
|
const governingTimestamp = initialWrite !== undefined
|
|
@@ -174,6 +207,7 @@ export class ProtocolAuthorization {
|
|
|
174
207
|
newestRecordsWrite.message.descriptor.protocol!,
|
|
175
208
|
messageStore,
|
|
176
209
|
governingTimestamp,
|
|
210
|
+
coreProtocols,
|
|
177
211
|
);
|
|
178
212
|
|
|
179
213
|
// get the rule set for the inbound message
|
|
@@ -182,19 +216,22 @@ export class ProtocolAuthorization {
|
|
|
182
216
|
protocolDefinition,
|
|
183
217
|
);
|
|
184
218
|
|
|
219
|
+
const boundFetchDefinition = ProtocolAuthorization.createBoundFetchDefinition(coreProtocols);
|
|
220
|
+
|
|
185
221
|
// If the incoming message has `protocolRole` in the descriptor, validate the invoked role
|
|
186
|
-
await
|
|
222
|
+
await verifyInvokedRole(
|
|
187
223
|
tenant,
|
|
188
224
|
incomingMessage,
|
|
189
225
|
newestRecordsWrite.message.descriptor.protocol!,
|
|
190
226
|
newestRecordsWrite.message.contextId!,
|
|
191
227
|
protocolDefinition,
|
|
192
228
|
messageStore,
|
|
229
|
+
boundFetchDefinition,
|
|
193
230
|
governingTimestamp,
|
|
194
231
|
);
|
|
195
232
|
|
|
196
233
|
// verify method invoked against the allowed actions in the rule set
|
|
197
|
-
await
|
|
234
|
+
await authorizeAgainstAllowedActions(
|
|
198
235
|
tenant,
|
|
199
236
|
incomingMessage,
|
|
200
237
|
ruleSet,
|
|
@@ -208,6 +245,7 @@ export class ProtocolAuthorization {
|
|
|
208
245
|
tenant: string,
|
|
209
246
|
incomingMessage: RecordsCount | RecordsQuery | RecordsSubscribe,
|
|
210
247
|
messageStore: MessageStore,
|
|
248
|
+
coreProtocols?: CoreProtocolRegistry,
|
|
211
249
|
): Promise<void> {
|
|
212
250
|
const { protocol, protocolPath, contextId } = incomingMessage.message.descriptor.filter;
|
|
213
251
|
|
|
@@ -216,6 +254,8 @@ export class ProtocolAuthorization {
|
|
|
216
254
|
tenant,
|
|
217
255
|
protocol!, // `authorizeQueryOrSubscribe` is only called if `protocol` is present
|
|
218
256
|
messageStore,
|
|
257
|
+
undefined,
|
|
258
|
+
coreProtocols,
|
|
219
259
|
);
|
|
220
260
|
|
|
221
261
|
// get the rule set for the inbound message
|
|
@@ -224,18 +264,21 @@ export class ProtocolAuthorization {
|
|
|
224
264
|
protocolDefinition,
|
|
225
265
|
);
|
|
226
266
|
|
|
267
|
+
const boundFetchDefinition = ProtocolAuthorization.createBoundFetchDefinition(coreProtocols);
|
|
268
|
+
|
|
227
269
|
// If the incoming message has `protocolRole` in the descriptor, validate the invoked role
|
|
228
|
-
await
|
|
270
|
+
await verifyInvokedRole(
|
|
229
271
|
tenant,
|
|
230
272
|
incomingMessage,
|
|
231
273
|
protocol!,
|
|
232
274
|
contextId,
|
|
233
275
|
protocolDefinition,
|
|
234
276
|
messageStore,
|
|
277
|
+
boundFetchDefinition,
|
|
235
278
|
);
|
|
236
279
|
|
|
237
280
|
// verify method invoked against the allowed actions in the rule set
|
|
238
|
-
await
|
|
281
|
+
await authorizeAgainstAllowedActions(
|
|
239
282
|
tenant,
|
|
240
283
|
incomingMessage,
|
|
241
284
|
ruleSet,
|
|
@@ -254,14 +297,15 @@ export class ProtocolAuthorization {
|
|
|
254
297
|
incomingMessage: RecordsDelete,
|
|
255
298
|
recordsWrite: RecordsWrite,
|
|
256
299
|
messageStore: MessageStore,
|
|
300
|
+
coreProtocols?: CoreProtocolRegistry,
|
|
257
301
|
): Promise<void> {
|
|
258
302
|
|
|
259
303
|
// fetch record chain
|
|
260
304
|
const recordChain: RecordsWriteMessage[] =
|
|
261
|
-
await
|
|
305
|
+
await constructRecordChain(tenant, incomingMessage.message.descriptor.recordId, messageStore);
|
|
262
306
|
|
|
263
307
|
// Use the initial write's timestamp to determine the governing protocol definition.
|
|
264
|
-
const initialWrite = await
|
|
308
|
+
const initialWrite = await fetchInitialWrite(
|
|
265
309
|
tenant, incomingMessage.message.descriptor.recordId, messageStore
|
|
266
310
|
);
|
|
267
311
|
const governingTimestamp = initialWrite !== undefined
|
|
@@ -274,6 +318,7 @@ export class ProtocolAuthorization {
|
|
|
274
318
|
recordsWrite.message.descriptor.protocol!,
|
|
275
319
|
messageStore,
|
|
276
320
|
governingTimestamp,
|
|
321
|
+
coreProtocols,
|
|
277
322
|
);
|
|
278
323
|
|
|
279
324
|
// get the rule set for the inbound message
|
|
@@ -282,19 +327,22 @@ export class ProtocolAuthorization {
|
|
|
282
327
|
protocolDefinition,
|
|
283
328
|
);
|
|
284
329
|
|
|
330
|
+
const boundFetchDefinition = ProtocolAuthorization.createBoundFetchDefinition(coreProtocols);
|
|
331
|
+
|
|
285
332
|
// If the incoming message has `protocolRole` in the descriptor, validate the invoked role
|
|
286
|
-
await
|
|
333
|
+
await verifyInvokedRole(
|
|
287
334
|
tenant,
|
|
288
335
|
incomingMessage,
|
|
289
336
|
recordsWrite.message.descriptor.protocol!,
|
|
290
337
|
recordsWrite.message.contextId!,
|
|
291
338
|
protocolDefinition,
|
|
292
339
|
messageStore,
|
|
340
|
+
boundFetchDefinition,
|
|
293
341
|
governingTimestamp,
|
|
294
342
|
);
|
|
295
343
|
|
|
296
344
|
// verify method invoked against the allowed actions in the rule set
|
|
297
|
-
await
|
|
345
|
+
await authorizeAgainstAllowedActions(
|
|
298
346
|
tenant,
|
|
299
347
|
incomingMessage,
|
|
300
348
|
ruleSet,
|
|
@@ -309,16 +357,25 @@ export class ProtocolAuthorization {
|
|
|
309
357
|
* When `messageTimestamp` is provided, returns the protocol definition that was active at that
|
|
310
358
|
* point in time — i.e. the ProtocolsConfigure with the greatest `messageTimestamp` that is <= the
|
|
311
359
|
* given timestamp. When not provided, returns the latest (current) protocol definition.
|
|
360
|
+
*
|
|
361
|
+
* When `coreProtocols` is provided, core protocol definitions are returned directly from the
|
|
362
|
+
* registry without a message store query. The extra parameter does not affect the
|
|
363
|
+
* `FetchProtocolDefinitionFn` callback type — callers that pass this function as a callback
|
|
364
|
+
* should bind the registry via a closure (see `createBoundFetchDefinition`).
|
|
312
365
|
*/
|
|
313
|
-
|
|
366
|
+
public static async fetchProtocolDefinition(
|
|
314
367
|
tenant: string,
|
|
315
368
|
protocolUri: string,
|
|
316
369
|
messageStore: MessageStore,
|
|
317
370
|
messageTimestamp?: string,
|
|
371
|
+
coreProtocols?: CoreProtocolRegistry,
|
|
318
372
|
): Promise<ProtocolDefinition> {
|
|
319
|
-
// if
|
|
320
|
-
if (
|
|
321
|
-
|
|
373
|
+
// if the protocol is a registered core protocol, return the definition directly without a store query
|
|
374
|
+
if (coreProtocols !== undefined) {
|
|
375
|
+
const coreDefinition = coreProtocols.getDefinition(protocolUri);
|
|
376
|
+
if (coreDefinition !== undefined) {
|
|
377
|
+
return coreDefinition;
|
|
378
|
+
}
|
|
322
379
|
}
|
|
323
380
|
|
|
324
381
|
// fetch the corresponding protocol definition
|
|
@@ -352,71 +409,20 @@ export class ProtocolAuthorization {
|
|
|
352
409
|
}
|
|
353
410
|
|
|
354
411
|
/**
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* returns empty array if `descendantRecordId` is `undefined`.
|
|
360
|
-
* @throws {DwnError} if `descendantRecordId` is defined but any initial `RecordsWrite` is not found in the chain of records.
|
|
361
|
-
*/
|
|
362
|
-
private static async constructRecordChain(
|
|
363
|
-
tenant: string,
|
|
364
|
-
descendantRecordId: string | undefined,
|
|
365
|
-
messageStore: MessageStore
|
|
366
|
-
) : Promise<RecordsWriteMessage[]> {
|
|
367
|
-
|
|
368
|
-
if (descendantRecordId === undefined) {
|
|
369
|
-
return [];
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const recordChain: RecordsWriteMessage[] = [];
|
|
373
|
-
|
|
374
|
-
// keep walking up the chain from the inbound message's parent, until there is no more parent
|
|
375
|
-
let currentRecordId: string | undefined = descendantRecordId;
|
|
376
|
-
while (currentRecordId !== undefined) {
|
|
377
|
-
|
|
378
|
-
const initialWrite = await ProtocolAuthorization.fetchInitialWrite(tenant, currentRecordId, messageStore);
|
|
379
|
-
|
|
380
|
-
// RecordsWrite needed should be available since we perform necessary checks at the time of writes,
|
|
381
|
-
// eg. check the immediate parent in `verifyProtocolPathAndContextId` at the time of writing,
|
|
382
|
-
// so if this condition is triggered, it means there is an unexpected bug that caused an incomplete chain.
|
|
383
|
-
// We add additional defensive check here because returning an unexpected/incorrect record chain could lead to security vulnerabilities.
|
|
384
|
-
if (initialWrite === undefined) {
|
|
385
|
-
throw new DwnError(
|
|
386
|
-
DwnErrorCode.ProtocolAuthorizationParentNotFoundConstructingRecordChain,
|
|
387
|
-
`Unexpected error that should never trigger: no parent found with ID ${currentRecordId} when constructing record chain.`
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
recordChain.push(initialWrite);
|
|
392
|
-
currentRecordId = initialWrite.descriptor.parentId;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return recordChain.reverse(); // root record first
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Fetches the initial RecordsWrite message associated with the given (tenant + recordId).
|
|
412
|
+
* Creates a `FetchProtocolDefinitionFn` closure that binds the given `CoreProtocolRegistry`.
|
|
413
|
+
* This allows core protocol definitions to be resolved from the registry without changing
|
|
414
|
+
* the `FetchProtocolDefinitionFn` type signature — zero ripple to downstream consumers
|
|
415
|
+
* like `protocol-authorization-action.ts` and `protocol-authorization-validation.ts`.
|
|
400
416
|
*/
|
|
401
|
-
private static
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
method : DwnMethodName.Write,
|
|
410
|
-
recordId : recordId
|
|
417
|
+
private static createBoundFetchDefinition(coreProtocols?: CoreProtocolRegistry): FetchProtocolDefinitionFn {
|
|
418
|
+
return (
|
|
419
|
+
tenant: string,
|
|
420
|
+
protocolUri: string,
|
|
421
|
+
messageStore: MessageStore,
|
|
422
|
+
messageTimestamp?: string,
|
|
423
|
+
): Promise<ProtocolDefinition> => {
|
|
424
|
+
return ProtocolAuthorization.fetchProtocolDefinition(tenant, protocolUri, messageStore, messageTimestamp, coreProtocols);
|
|
411
425
|
};
|
|
412
|
-
const { messages } = await messageStore.query(tenant, [query]);
|
|
413
|
-
|
|
414
|
-
if (messages.length === 0) {
|
|
415
|
-
return undefined;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const initialWrite = await RecordsWrite.getInitialWrite(messages);
|
|
419
|
-
return initialWrite;
|
|
420
426
|
}
|
|
421
427
|
|
|
422
428
|
/**
|
|
@@ -434,755 +440,4 @@ export class ProtocolAuthorization {
|
|
|
434
440
|
return ruleSet;
|
|
435
441
|
}
|
|
436
442
|
|
|
437
|
-
|
|
438
|
-
* Verifies the `protocolPath` declared in the given message (if it is a RecordsWrite) matches the path of actual record chain.
|
|
439
|
-
* For cross-protocol composition, the parent record may belong to a different protocol (resolved via `$ref` in the composing protocol).
|
|
440
|
-
* @throws {DwnError} if fails verification.
|
|
441
|
-
*/
|
|
442
|
-
private static async verifyProtocolPathAndContextId(
|
|
443
|
-
tenant: string,
|
|
444
|
-
inboundMessage: RecordsWrite,
|
|
445
|
-
messageStore: MessageStore,
|
|
446
|
-
governingTimestamp?: string,
|
|
447
|
-
): Promise<void> {
|
|
448
|
-
const declaredProtocolPath = inboundMessage.message.descriptor.protocolPath!;
|
|
449
|
-
const declaredTypeName = ProtocolAuthorization.getTypeName(declaredProtocolPath);
|
|
450
|
-
|
|
451
|
-
const parentId = inboundMessage.message.descriptor.parentId;
|
|
452
|
-
if (parentId === undefined) {
|
|
453
|
-
if (declaredProtocolPath !== declaredTypeName) {
|
|
454
|
-
throw new DwnError(
|
|
455
|
-
DwnErrorCode.ProtocolAuthorizationParentlessIncorrectProtocolPath,
|
|
456
|
-
`Declared protocol path '${declaredProtocolPath}' is not valid for records with no parent'.`
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Else `parentId` is defined, so we need to verify both protocolPath and contextId
|
|
464
|
-
|
|
465
|
-
// Determine the protocol URI for the parent query.
|
|
466
|
-
// If the parent path segment has a `$ref` in the composing protocol, the parent lives in a different protocol.
|
|
467
|
-
const childProtocol = inboundMessage.message.descriptor.protocol!;
|
|
468
|
-
const parentProtocolUri = await ProtocolAuthorization.resolveParentProtocolUri(
|
|
469
|
-
tenant, childProtocol, declaredProtocolPath, messageStore, governingTimestamp
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
// fetch the parent message
|
|
473
|
-
const query: Filter = {
|
|
474
|
-
isLatestBaseState : true, // NOTE: this filter is critical, to ensure are are not returning a deleted parent
|
|
475
|
-
interface : DwnInterfaceName.Records,
|
|
476
|
-
method : DwnMethodName.Write,
|
|
477
|
-
protocol : parentProtocolUri,
|
|
478
|
-
recordId : parentId
|
|
479
|
-
};
|
|
480
|
-
const { messages: parentMessages } = await messageStore.query(tenant, [query]);
|
|
481
|
-
const parentMessage = (parentMessages as RecordsWriteMessage[])[0];
|
|
482
|
-
|
|
483
|
-
if (parentMessage === undefined) {
|
|
484
|
-
// if this is a cross-protocol composition lookup, use a more descriptive error
|
|
485
|
-
if (parentProtocolUri !== childProtocol) {
|
|
486
|
-
throw new DwnError(
|
|
487
|
-
DwnErrorCode.ProtocolAuthorizationCrossProtocolParentNotFound,
|
|
488
|
-
`Could not find parent record '${parentId}' in protocol '${parentProtocolUri}' ` +
|
|
489
|
-
`for cross-protocol child at path '${declaredProtocolPath}'.`
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
throw new DwnError(
|
|
494
|
-
DwnErrorCode.ProtocolAuthorizationIncorrectProtocolPath,
|
|
495
|
-
`Could not find matching parent record to verify declared protocol path '${declaredProtocolPath}'.`
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// verifying protocolPath of incoming message is a child of the parent message's protocolPath
|
|
500
|
-
const parentProtocolPath = parentMessage.descriptor.protocolPath;
|
|
501
|
-
const expectedProtocolPath = `${parentProtocolPath}/${declaredTypeName}`;
|
|
502
|
-
if (expectedProtocolPath !== declaredProtocolPath) {
|
|
503
|
-
throw new DwnError(
|
|
504
|
-
DwnErrorCode.ProtocolAuthorizationIncorrectProtocolPath,
|
|
505
|
-
`Could not find matching parent record to verify declared protocol path '${declaredProtocolPath}'.`
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// verifying contextId of incoming message is a child of the parent message's contextId
|
|
510
|
-
const expectedContextId = `${parentMessage.contextId}/${inboundMessage.message.recordId}`;
|
|
511
|
-
const actualContextId = inboundMessage.message.contextId;
|
|
512
|
-
if (actualContextId !== expectedContextId) {
|
|
513
|
-
throw new DwnError(
|
|
514
|
-
DwnErrorCode.ProtocolAuthorizationIncorrectContextId,
|
|
515
|
-
`Declared contextId '${actualContextId}' is not the same as expected: '${expectedContextId}'.`
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Resolves the protocol URI that should be used when querying for the parent record.
|
|
523
|
-
* For standard (non-composed) records, this is the same as the child's protocol.
|
|
524
|
-
* For cross-protocol composition, the parent may live in a different protocol
|
|
525
|
-
* (resolved via `$ref` in the composing protocol's definition).
|
|
526
|
-
*
|
|
527
|
-
* Logic: Given a child at protocolPath `a/b/c`, the parent is at `a/b`.
|
|
528
|
-
* Walk up the composing protocol's structure from root to `a/b`.
|
|
529
|
-
* If any segment along the way has a `$ref`, the parent (and its ancestors up to the `$ref` boundary)
|
|
530
|
-
* live in the referenced protocol. Specifically, the `$ref` at the topmost ancestor tells us
|
|
531
|
-
* the parent's protocol URI.
|
|
532
|
-
*/
|
|
533
|
-
private static async resolveParentProtocolUri(
|
|
534
|
-
tenant: string,
|
|
535
|
-
childProtocolUri: string,
|
|
536
|
-
childProtocolPath: string,
|
|
537
|
-
messageStore: MessageStore,
|
|
538
|
-
governingTimestamp?: string,
|
|
539
|
-
): Promise<string> {
|
|
540
|
-
const segments = childProtocolPath.split('/');
|
|
541
|
-
|
|
542
|
-
// A root-level record (no `/` in path) has no parent or uses the same protocol
|
|
543
|
-
if (segments.length <= 1) {
|
|
544
|
-
return childProtocolUri;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Fetch the composing protocol's definition at the governing timestamp
|
|
548
|
-
const composingDefinition = await ProtocolAuthorization.fetchProtocolDefinition(
|
|
549
|
-
tenant, childProtocolUri, messageStore, governingTimestamp
|
|
550
|
-
);
|
|
551
|
-
|
|
552
|
-
// Walk the structure to find the parent's path segment
|
|
553
|
-
// The parent's position in the structure is at segments[0..n-2]
|
|
554
|
-
// We check if the first segment has a `$ref`, which means the parent is in a different protocol
|
|
555
|
-
const firstSegmentRuleSet = composingDefinition.structure[segments[0]];
|
|
556
|
-
if (firstSegmentRuleSet?.$ref !== undefined) {
|
|
557
|
-
const parsed = parseCrossProtocolRef(firstSegmentRuleSet.$ref);
|
|
558
|
-
if (parsed !== undefined && composingDefinition.uses !== undefined) {
|
|
559
|
-
const resolvedUri = composingDefinition.uses[parsed.alias];
|
|
560
|
-
if (resolvedUri !== undefined) {
|
|
561
|
-
// The parent path is within the `$ref` boundary — check if the parent IS the `$ref` node
|
|
562
|
-
// or is a descendant of it (which would still be in the composing protocol).
|
|
563
|
-
// If segments.length === 2, parent is at segments[0] which IS the $ref node → parent's protocol is the referenced one.
|
|
564
|
-
// If segments.length > 2, parent is at segments[0..n-2]. If segments[0] is $ref, the parent could be:
|
|
565
|
-
// - Still the $ref node itself (segments.length === 2) → referenced protocol
|
|
566
|
-
// - A child of the $ref node defined in the composing protocol (segments.length > 2) → composing protocol
|
|
567
|
-
if (segments.length === 2) {
|
|
568
|
-
// Parent is the $ref node itself (e.g., child is "thread/comment", parent is "thread")
|
|
569
|
-
return resolvedUri;
|
|
570
|
-
}
|
|
571
|
-
// else: parent is a deeper child defined in the composing protocol
|
|
572
|
-
return childProtocolUri;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
return childProtocolUri;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Verifies the `dataFormat` and `schema` declared in the given message matches the type in the protocol.
|
|
582
|
-
* For cross-protocol composition, if the type is at a `$ref` position in the structure,
|
|
583
|
-
* the type definition is looked up in the referenced protocol's `types` map instead.
|
|
584
|
-
*/
|
|
585
|
-
private static async verifyTypeWithComposition(
|
|
586
|
-
tenant: string,
|
|
587
|
-
inboundMessage: RecordsWriteMessage,
|
|
588
|
-
protocolDefinition: ProtocolDefinition,
|
|
589
|
-
messageStore: MessageStore,
|
|
590
|
-
governingTimestamp?: string,
|
|
591
|
-
): Promise<void> {
|
|
592
|
-
const declaredProtocolPath = inboundMessage.descriptor.protocolPath!;
|
|
593
|
-
const declaredTypeName = ProtocolAuthorization.getTypeName(declaredProtocolPath);
|
|
594
|
-
|
|
595
|
-
// Resolve which protocol types map to use.
|
|
596
|
-
// If the first path segment has `$ref`, this record's type might be defined in a referenced protocol.
|
|
597
|
-
const protocolTypes = await ProtocolAuthorization.resolveProtocolTypesForPath(
|
|
598
|
-
tenant, declaredProtocolPath, protocolDefinition, messageStore, governingTimestamp
|
|
599
|
-
);
|
|
600
|
-
|
|
601
|
-
ProtocolAuthorization.verifyType(inboundMessage, protocolTypes, declaredTypeName);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* Resolves the `ProtocolTypes` map that contains the type definition for the given protocol path.
|
|
606
|
-
* For non-composed records, this is the protocol definition's own `types` map.
|
|
607
|
-
* For records at a `$ref` position, this is the referenced protocol's `types` map.
|
|
608
|
-
*/
|
|
609
|
-
private static async resolveProtocolTypesForPath(
|
|
610
|
-
tenant: string,
|
|
611
|
-
protocolPath: string,
|
|
612
|
-
protocolDefinition: ProtocolDefinition,
|
|
613
|
-
messageStore: MessageStore,
|
|
614
|
-
governingTimestamp?: string,
|
|
615
|
-
): Promise<ProtocolTypes> {
|
|
616
|
-
const segments = protocolPath.split('/');
|
|
617
|
-
|
|
618
|
-
// Check if the first segment has a `$ref`
|
|
619
|
-
const firstSegmentRuleSet = protocolDefinition.structure[segments[0]];
|
|
620
|
-
if (firstSegmentRuleSet?.$ref !== undefined && segments.length === 1) {
|
|
621
|
-
// This record IS the $ref node itself — its type is defined in the referenced protocol
|
|
622
|
-
const parsed = parseCrossProtocolRef(firstSegmentRuleSet.$ref);
|
|
623
|
-
if (parsed !== undefined && protocolDefinition.uses !== undefined) {
|
|
624
|
-
const refProtocolUri = protocolDefinition.uses[parsed.alias];
|
|
625
|
-
if (refProtocolUri !== undefined) {
|
|
626
|
-
const refDefinition = await ProtocolAuthorization.fetchProtocolDefinition(
|
|
627
|
-
tenant, refProtocolUri, messageStore, governingTimestamp
|
|
628
|
-
);
|
|
629
|
-
return refDefinition.types;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// Default: use the composing protocol's own types
|
|
635
|
-
return protocolDefinition.types;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Verifies the `dataFormat` and `schema` declared in the given message (if it is a RecordsWrite) matches dataFormat
|
|
640
|
-
* and schema of the type in the given protocol.
|
|
641
|
-
* @throws {DwnError} if fails verification.
|
|
642
|
-
*/
|
|
643
|
-
private static verifyType(
|
|
644
|
-
inboundMessage: RecordsWriteMessage,
|
|
645
|
-
protocolTypes: ProtocolTypes,
|
|
646
|
-
typeName?: string,
|
|
647
|
-
): void {
|
|
648
|
-
const declaredTypeName = typeName ?? ProtocolAuthorization.getTypeName(inboundMessage.descriptor.protocolPath!);
|
|
649
|
-
const typeNames = Object.keys(protocolTypes);
|
|
650
|
-
|
|
651
|
-
if (!typeNames.includes(declaredTypeName)) {
|
|
652
|
-
throw new DwnError(DwnErrorCode.ProtocolAuthorizationInvalidType,
|
|
653
|
-
`record with type ${declaredTypeName} not allowed in protocol`);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const protocolType: ProtocolType = protocolTypes[declaredTypeName];
|
|
657
|
-
|
|
658
|
-
// no `schema` specified in protocol definition means that any schema is allowed
|
|
659
|
-
const { schema } = inboundMessage.descriptor;
|
|
660
|
-
if (protocolType.schema !== undefined && protocolType.schema !== schema) {
|
|
661
|
-
throw new DwnError(
|
|
662
|
-
DwnErrorCode.ProtocolAuthorizationInvalidSchema,
|
|
663
|
-
`type '${declaredTypeName}' must have schema '${protocolType.schema}', \
|
|
664
|
-
instead has '${schema}'`
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// no `dataFormats` specified in protocol definition means that all dataFormats are allowed
|
|
669
|
-
const { dataFormat } = inboundMessage.descriptor;
|
|
670
|
-
if (protocolType.dataFormats !== undefined && !protocolType.dataFormats.includes(dataFormat)) {
|
|
671
|
-
throw new DwnError(
|
|
672
|
-
DwnErrorCode.ProtocolAuthorizationIncorrectDataFormat,
|
|
673
|
-
`type '${declaredTypeName}' must have data format in (${protocolType.dataFormats}), \
|
|
674
|
-
instead has '${dataFormat}'`
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// enforce encryption when the protocol type requires it
|
|
679
|
-
if (protocolType.encryptionRequired === true && inboundMessage.encryption === undefined) {
|
|
680
|
-
throw new DwnError(
|
|
681
|
-
DwnErrorCode.ProtocolAuthorizationEncryptionRequired,
|
|
682
|
-
`type '${declaredTypeName}' requires encryption but message has no encryption metadata`
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* Check if the incoming message is invoking a role. If so, validate the invoked role.
|
|
689
|
-
* For cross-protocol role invocation, the role record may live in a different protocol
|
|
690
|
-
* (resolved via the composing protocol's `uses` map).
|
|
691
|
-
*/
|
|
692
|
-
private static async verifyInvokedRole(
|
|
693
|
-
tenant: string,
|
|
694
|
-
incomingMessage: RecordsCount | RecordsDelete | RecordsQuery | RecordsRead | RecordsSubscribe | RecordsWrite,
|
|
695
|
-
protocolUri: string,
|
|
696
|
-
contextId: string | undefined,
|
|
697
|
-
protocolDefinition: ProtocolDefinition,
|
|
698
|
-
messageStore: MessageStore,
|
|
699
|
-
governingTimestamp?: string,
|
|
700
|
-
): Promise<void> {
|
|
701
|
-
const protocolRole = incomingMessage.signaturePayload?.protocolRole;
|
|
702
|
-
|
|
703
|
-
// Only verify role if there is a role being invoked
|
|
704
|
-
if (protocolRole === undefined) {
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
// Determine the protocol URI and protocol path for the role record.
|
|
709
|
-
// For cross-protocol roles (e.g., "threads:thread/participant"), resolve the alias.
|
|
710
|
-
let roleProtocolUri = protocolUri;
|
|
711
|
-
let roleProtocolPath = protocolRole;
|
|
712
|
-
|
|
713
|
-
if (isCrossProtocolRef(protocolRole)) {
|
|
714
|
-
const parsed = parseCrossProtocolRef(protocolRole);
|
|
715
|
-
if (parsed === undefined) {
|
|
716
|
-
throw new DwnError(
|
|
717
|
-
DwnErrorCode.ProtocolAuthorizationNotARole,
|
|
718
|
-
`Cross-protocol role '${protocolRole}' could not be parsed as a valid 'alias:path' format.`
|
|
719
|
-
);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (protocolDefinition.uses === undefined || protocolDefinition.uses[parsed.alias] === undefined) {
|
|
723
|
-
throw new DwnError(
|
|
724
|
-
DwnErrorCode.ProtocolAuthorizationNotARole,
|
|
725
|
-
`Cross-protocol role alias '${parsed.alias}' in '${protocolRole}' does not exist in the protocol's 'uses' map.`
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
roleProtocolUri = protocolDefinition.uses[parsed.alias];
|
|
730
|
-
roleProtocolPath = parsed.protocolPath;
|
|
731
|
-
|
|
732
|
-
// Fetch the referenced protocol's definition to validate the role exists
|
|
733
|
-
const refDefinition = await ProtocolAuthorization.fetchProtocolDefinition(
|
|
734
|
-
tenant, roleProtocolUri, messageStore, governingTimestamp
|
|
735
|
-
);
|
|
736
|
-
const roleRuleSet = getRuleSetAtPath(roleProtocolPath, refDefinition.structure);
|
|
737
|
-
if (roleRuleSet === undefined || !roleRuleSet.$role) {
|
|
738
|
-
throw new DwnError(
|
|
739
|
-
DwnErrorCode.ProtocolAuthorizationNotARole,
|
|
740
|
-
`Cross-protocol role path ${protocolRole} does not match role record type.`
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
} else {
|
|
744
|
-
// Local role: validate in the composing protocol's definition
|
|
745
|
-
const roleRuleSet = getRuleSetAtPath(protocolRole, protocolDefinition.structure);
|
|
746
|
-
if (roleRuleSet === undefined || !roleRuleSet.$role) {
|
|
747
|
-
throw new DwnError(
|
|
748
|
-
DwnErrorCode.ProtocolAuthorizationNotARole,
|
|
749
|
-
`Protocol path ${protocolRole} does not match role record type.`
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Construct a filter to fetch the invoked role record
|
|
755
|
-
const roleRecordFilter: Filter = {
|
|
756
|
-
interface : DwnInterfaceName.Records,
|
|
757
|
-
method : DwnMethodName.Write,
|
|
758
|
-
protocol : roleProtocolUri,
|
|
759
|
-
protocolPath : roleProtocolPath,
|
|
760
|
-
recipient : incomingMessage.author!,
|
|
761
|
-
isLatestBaseState : true,
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
const ancestorSegmentCountOfRolePath = roleProtocolPath.split('/').length - 1;
|
|
765
|
-
if (contextId === undefined && ancestorSegmentCountOfRolePath > 0) {
|
|
766
|
-
throw new DwnError(
|
|
767
|
-
DwnErrorCode.ProtocolAuthorizationMissingContextId,
|
|
768
|
-
'Could not verify role because contextId is missing.'
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
// Compute `contextId` prefix filter for fetching the invoked role record if the role path is not at the root level.
|
|
773
|
-
// e.g. if invoked role path is `Thread/Participant`, and the `contextId` of the message is `threadX/messageY/attachmentZ`,
|
|
774
|
-
// then we need to add a prefix filter as `threadX` for the `contextId`
|
|
775
|
-
// because the `contextId` of the Participant record would be in the form of be `threadX/participantA`
|
|
776
|
-
if (ancestorSegmentCountOfRolePath > 0) {
|
|
777
|
-
const contextIdSegments = contextId!.split('/'); // NOTE: currently contextId segment count is never shorter than the role path count.
|
|
778
|
-
const contextIdPrefix = contextIdSegments.slice(0, ancestorSegmentCountOfRolePath).join('/');
|
|
779
|
-
const contextIdPrefixFilter = FilterUtility.constructPrefixFilterAsRangeFilter(contextIdPrefix);
|
|
780
|
-
|
|
781
|
-
roleRecordFilter.contextId = contextIdPrefixFilter;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
const { messages: matchingMessages } = await messageStore.query(tenant, [roleRecordFilter]);
|
|
786
|
-
|
|
787
|
-
if (matchingMessages.length === 0) {
|
|
788
|
-
throw new DwnError(
|
|
789
|
-
DwnErrorCode.ProtocolAuthorizationMatchingRoleRecordNotFound,
|
|
790
|
-
`No matching role record found for protocol path ${roleProtocolPath}`
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
/**
|
|
796
|
-
* Returns all the ProtocolActions that would authorized the incoming message
|
|
797
|
-
* (but we still need to later verify if there is a rule defined that matches one of the actions).
|
|
798
|
-
* NOTE: the reason why there could be multiple actions is because:
|
|
799
|
-
* - In case of an initial RecordsWrite, the RecordsWrite can be authorized by an allow `create` or `write` rule.
|
|
800
|
-
* - In case of a non-initial RecordsWrite by the original record author, the RecordsWrite can be authorized by a `write` or `co-update` rule.
|
|
801
|
-
*
|
|
802
|
-
* It is important to recognize that the `write` access that allowed the original record author to create the record maybe revoked
|
|
803
|
-
* (e.g. by role revocation) by the time a "non-initial" write by the same author is attempted.
|
|
804
|
-
*/
|
|
805
|
-
private static async getActionsSeekingARuleMatch(
|
|
806
|
-
tenant: string,
|
|
807
|
-
incomingMessage: RecordsCount | RecordsDelete | RecordsQuery | RecordsRead | RecordsSubscribe | RecordsWrite,
|
|
808
|
-
messageStore: MessageStore,
|
|
809
|
-
): Promise<ProtocolAction[]> {
|
|
810
|
-
|
|
811
|
-
switch (incomingMessage.message.descriptor.method) {
|
|
812
|
-
case DwnMethodName.Delete:
|
|
813
|
-
const recordsDelete = incomingMessage as RecordsDelete;
|
|
814
|
-
const recordId = recordsDelete.message.descriptor.recordId;
|
|
815
|
-
const initialWrite = await RecordsWrite.fetchInitialRecordsWrite(messageStore, tenant, recordId);
|
|
816
|
-
|
|
817
|
-
// if there is no initial write, then no action rule can authorize the incoming message, because we won't know who the original author is
|
|
818
|
-
// NOTE: purely defensive programming: currently not reachable
|
|
819
|
-
// because RecordsDelete handler already have an existence check prior to this method being called.
|
|
820
|
-
if (initialWrite === undefined) {
|
|
821
|
-
return [];
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
const actionsThatWouldAuthorizeDelete = [];
|
|
825
|
-
const prune = recordsDelete.message.descriptor.prune;
|
|
826
|
-
if (prune) {
|
|
827
|
-
actionsThatWouldAuthorizeDelete.push(ProtocolAction.CoPrune);
|
|
828
|
-
|
|
829
|
-
// A prune by the original record author can also be authorized by a 'prune' rule.
|
|
830
|
-
if (incomingMessage.author === initialWrite.author) {
|
|
831
|
-
actionsThatWouldAuthorizeDelete.push(ProtocolAction.Prune);
|
|
832
|
-
}
|
|
833
|
-
} else {
|
|
834
|
-
actionsThatWouldAuthorizeDelete.push(ProtocolAction.CoDelete);
|
|
835
|
-
|
|
836
|
-
// A delete by the original record author can also be authorized by a 'delete' rule.
|
|
837
|
-
if (incomingMessage.author === initialWrite.author) {
|
|
838
|
-
actionsThatWouldAuthorizeDelete.push(ProtocolAction.Delete);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
return actionsThatWouldAuthorizeDelete;
|
|
843
|
-
|
|
844
|
-
case DwnMethodName.Count:
|
|
845
|
-
return [ProtocolAction.Read];
|
|
846
|
-
|
|
847
|
-
case DwnMethodName.Query:
|
|
848
|
-
return [ProtocolAction.Read];
|
|
849
|
-
|
|
850
|
-
case DwnMethodName.Read:
|
|
851
|
-
return [ProtocolAction.Read];
|
|
852
|
-
|
|
853
|
-
case DwnMethodName.Subscribe:
|
|
854
|
-
return [ProtocolAction.Read];
|
|
855
|
-
|
|
856
|
-
case DwnMethodName.Write:
|
|
857
|
-
const incomingRecordsWrite = incomingMessage as RecordsWrite;
|
|
858
|
-
|
|
859
|
-
if (await incomingRecordsWrite.isInitialWrite()) {
|
|
860
|
-
return [ProtocolAction.Create];
|
|
861
|
-
} else {
|
|
862
|
-
// else incoming RecordsWrite not an initial write
|
|
863
|
-
|
|
864
|
-
const recordId = (incomingMessage as RecordsWrite).message.recordId;
|
|
865
|
-
const initialWrite = await RecordsWrite.fetchInitialRecordsWrite(messageStore, tenant, recordId);
|
|
866
|
-
|
|
867
|
-
// if there is no initial write to update from, then no action rule can authorize the incoming message
|
|
868
|
-
if (initialWrite === undefined) {
|
|
869
|
-
return [];
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
if (incomingMessage.author === initialWrite.author) {
|
|
873
|
-
// 'update' or 'co-update' action authorizes the incoming message
|
|
874
|
-
return [ProtocolAction.CoUpdate, ProtocolAction.Update];
|
|
875
|
-
} else {
|
|
876
|
-
// An update by someone who is not the record author can only be authorized by a 'co-update' rule.
|
|
877
|
-
return [ProtocolAction.CoUpdate];
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// purely defensive programming: should not be reachable
|
|
883
|
-
// setting to empty array will prevent any message from being authorized
|
|
884
|
-
return [];
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
/**
|
|
888
|
-
* Verifies the given message is authorized by one of the action rules in the given protocol rule set.
|
|
889
|
-
* @param protocolDefinition Optional protocol definition for resolving cross-protocol `of` and `role` references.
|
|
890
|
-
* @throws {Error} if action not allowed.
|
|
891
|
-
*/
|
|
892
|
-
private static async authorizeAgainstAllowedActions(
|
|
893
|
-
tenant: string,
|
|
894
|
-
incomingMessage: RecordsCount | RecordsDelete | RecordsQuery | RecordsRead | RecordsSubscribe | RecordsWrite,
|
|
895
|
-
ruleSet: ProtocolRuleSet,
|
|
896
|
-
recordChain: RecordsWriteMessage[],
|
|
897
|
-
messageStore: MessageStore,
|
|
898
|
-
protocolDefinition?: ProtocolDefinition,
|
|
899
|
-
): Promise<void> {
|
|
900
|
-
const incomingMessageMethod = incomingMessage.message.descriptor.method;
|
|
901
|
-
const actionsSeekingARuleMatch = await ProtocolAuthorization.getActionsSeekingARuleMatch(tenant, incomingMessage, messageStore);
|
|
902
|
-
const author = incomingMessage.author;
|
|
903
|
-
const actionRules = ruleSet.$actions;
|
|
904
|
-
|
|
905
|
-
// NOTE: We have already checked that the message is not from tenant, owner, or permission grant authorized prior to this method being called.
|
|
906
|
-
|
|
907
|
-
if (actionRules === undefined) {
|
|
908
|
-
throw new DwnError(
|
|
909
|
-
DwnErrorCode.ProtocolAuthorizationActionRulesNotFound,
|
|
910
|
-
`no action rule defined for Records${incomingMessageMethod}, ${author} is unauthorized`
|
|
911
|
-
);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
const invokedRole = incomingMessage.signaturePayload?.protocolRole;
|
|
915
|
-
|
|
916
|
-
// Iterate through the action rules to find a rule that authorizes the incoming message.
|
|
917
|
-
for (const actionRule of actionRules) {
|
|
918
|
-
// If the action rule does not have an allowed action that matches an action that can authorize the message, skip to evaluate next action rule.
|
|
919
|
-
const ruleHasAMatchingAllowedAction = actionRule.can.some(allowedAction => actionsSeekingARuleMatch.includes(allowedAction as ProtocolAction));
|
|
920
|
-
if (!ruleHasAMatchingAllowedAction) {
|
|
921
|
-
continue;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// Code reaches here means this action rule has an allowed action that matches the action of the message.
|
|
925
|
-
// The remaining code checks the actor/author of the incoming message.
|
|
926
|
-
|
|
927
|
-
// If the action rule allows `anyone`, then no further checks are needed.
|
|
928
|
-
if (actionRule.who === ProtocolActor.Anyone) {
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// Since not `anyone` is allowed in this action rule, we will need to check the author of the incoming message,
|
|
933
|
-
// if the author of incoming message is not defined, this action rule cannot authorize the incoming message.
|
|
934
|
-
if (author === undefined) {
|
|
935
|
-
continue;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// go through role validation path if a role is invoked by the incoming message
|
|
939
|
-
if (invokedRole !== undefined) {
|
|
940
|
-
// When a protocol role is being invoked, we require that there is a matching `role` rule.
|
|
941
|
-
if (actionRule.role === invokedRole) {
|
|
942
|
-
// role is successfully invoked
|
|
943
|
-
return;
|
|
944
|
-
} else {
|
|
945
|
-
continue;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
// else we go through the actor (`who`) validation
|
|
950
|
-
|
|
951
|
-
// If `of` is not set, handle it as a special case
|
|
952
|
-
// NOTE: `of` is always set if `who` is set to `author` (we do this check in `validateRuleSetRecursively()`)
|
|
953
|
-
if (actionRule.who === ProtocolActor.Recipient && actionRule.of === undefined) {
|
|
954
|
-
// If the action rule specifies a recipient without `of` and the incoming message is authenticated:
|
|
955
|
-
|
|
956
|
-
// Author must be recipient of the record being accessed
|
|
957
|
-
let recordsWriteMessage: RecordsWriteMessage;
|
|
958
|
-
if (incomingMessage.message.descriptor.method === DwnMethodName.Write) {
|
|
959
|
-
recordsWriteMessage = incomingMessage.message as RecordsWriteMessage;
|
|
960
|
-
} else {
|
|
961
|
-
// else the incoming message must be a `RecordsDelete` because only `co-update`, `co-delete`, `co-prune` are allowed recipient actions,
|
|
962
|
-
// (we do this check in `validateRuleSetRecursively()`)
|
|
963
|
-
// and we have already checked that the incoming message is not a `RecordsWrite` above which covers `co-update` path.
|
|
964
|
-
recordsWriteMessage = recordChain[recordChain.length - 1];
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
if (recordsWriteMessage.descriptor.recipient === author) {
|
|
968
|
-
return;
|
|
969
|
-
} else {
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
// validate the actor is allowed by the current action rule
|
|
975
|
-
const ancestorRuleSuccess: boolean = await ProtocolAuthorization.checkActor(author, actionRule, recordChain, protocolDefinition);
|
|
976
|
-
if (ancestorRuleSuccess) {
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// No action rules were satisfied, message is not authorized
|
|
982
|
-
throw new DwnError(
|
|
983
|
-
DwnErrorCode.ProtocolAuthorizationActionNotAllowed,
|
|
984
|
-
`Inbound message action Records${incomingMessageMethod} by author ${incomingMessage.author} not allowed.`
|
|
985
|
-
);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
/**
|
|
989
|
-
* Verifies that writes adhere to the $size constraints if provided
|
|
990
|
-
* @throws {Error} if size is exceeded.
|
|
991
|
-
*/
|
|
992
|
-
private static verifySizeLimit(
|
|
993
|
-
incomingMessage: RecordsWrite,
|
|
994
|
-
ruleSet: ProtocolRuleSet
|
|
995
|
-
): void {
|
|
996
|
-
const { min = 0, max } = ruleSet.$size || {};
|
|
997
|
-
|
|
998
|
-
const dataSize = incomingMessage.message.descriptor.dataSize;
|
|
999
|
-
|
|
1000
|
-
if (dataSize < min) {
|
|
1001
|
-
throw new DwnError(DwnErrorCode.ProtocolAuthorizationMinSizeInvalid, `data size ${dataSize} is less than allowed ${min}`);
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
if (max === undefined) {
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
if (dataSize > max) {
|
|
1009
|
-
throw new DwnError(DwnErrorCode.ProtocolAuthorizationMaxSizeInvalid, `data size ${dataSize} is more than allowed ${max}`);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
private static verifyTagsIfNeeded(
|
|
1014
|
-
incomingMessage: RecordsWrite,
|
|
1015
|
-
ruleSet: ProtocolRuleSet
|
|
1016
|
-
): void {
|
|
1017
|
-
if (ruleSet.$tags !== undefined) {
|
|
1018
|
-
const { tags = {}, protocol, protocolPath } = incomingMessage.message.descriptor;
|
|
1019
|
-
|
|
1020
|
-
const { $allowUndefinedTags, $requiredTags, ...properties } = ruleSet.$tags;
|
|
1021
|
-
|
|
1022
|
-
// if $allowUndefinedTags is set to false and there are properties not defined in the schema, an error is thrown
|
|
1023
|
-
const additionalProperties = $allowUndefinedTags || false;
|
|
1024
|
-
|
|
1025
|
-
// if $requiredTags is set, all required tags must be present
|
|
1026
|
-
const required = $requiredTags || [];
|
|
1027
|
-
|
|
1028
|
-
const ajv = new Ajv.default();
|
|
1029
|
-
const compiledTags = ajv.compile({
|
|
1030
|
-
type: 'object',
|
|
1031
|
-
properties,
|
|
1032
|
-
required,
|
|
1033
|
-
additionalProperties,
|
|
1034
|
-
});
|
|
1035
|
-
|
|
1036
|
-
const validSchema = compiledTags(tags);
|
|
1037
|
-
if (!validSchema) {
|
|
1038
|
-
// the `dataVar` is used to add a qualifier to the error message.
|
|
1039
|
-
// For example. If the error is related to a tag `status` in a protocol `https://example.protocol` with the protocolPath `example/path`
|
|
1040
|
-
// the error would be described as `https://example.protocol/example/path/$tags/status'
|
|
1041
|
-
// without this decorator it would show up as `data/status` which may be confusing.
|
|
1042
|
-
const schemaError = ajv.errorsText(compiledTags.errors, { dataVar: `${protocol}/${protocolPath}/$tags` });
|
|
1043
|
-
throw new DwnError(DwnErrorCode.ProtocolAuthorizationTagsInvalidSchema, `tags schema validation error: ${schemaError}`);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
/**
|
|
1049
|
-
* If the given RecordsWrite is not a role record, this method does nothing and succeeds immediately.
|
|
1050
|
-
*
|
|
1051
|
-
* Else it verifies the validity of the given `RecordsWrite` as a role record, including:
|
|
1052
|
-
* 1. The same role has not been assigned to the same entity/recipient.
|
|
1053
|
-
*/
|
|
1054
|
-
private static async verifyAsRoleRecordIfNeeded(
|
|
1055
|
-
tenant: string,
|
|
1056
|
-
incomingMessage: RecordsWrite,
|
|
1057
|
-
ruleSet: ProtocolRuleSet,
|
|
1058
|
-
messageStore: MessageStore,
|
|
1059
|
-
): Promise<void> {
|
|
1060
|
-
if (!ruleSet.$role) {
|
|
1061
|
-
return;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
// else this is a role record
|
|
1065
|
-
|
|
1066
|
-
const incomingRecordsWrite = incomingMessage;
|
|
1067
|
-
const recipient = incomingRecordsWrite.message.descriptor.recipient;
|
|
1068
|
-
if (recipient === undefined) {
|
|
1069
|
-
throw new DwnError(
|
|
1070
|
-
DwnErrorCode.ProtocolAuthorizationRoleMissingRecipient,
|
|
1071
|
-
'Role records must have a recipient'
|
|
1072
|
-
);
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
const protocolPath = incomingRecordsWrite.message.descriptor.protocolPath!;
|
|
1076
|
-
const filter: Filter = {
|
|
1077
|
-
interface : DwnInterfaceName.Records,
|
|
1078
|
-
method : DwnMethodName.Write,
|
|
1079
|
-
isLatestBaseState : true,
|
|
1080
|
-
protocol : incomingRecordsWrite.message.descriptor.protocol!,
|
|
1081
|
-
protocolPath,
|
|
1082
|
-
recipient,
|
|
1083
|
-
};
|
|
1084
|
-
|
|
1085
|
-
const parentContextId = Records.getParentContextFromOfContextId(incomingRecordsWrite.message.contextId)!;
|
|
1086
|
-
|
|
1087
|
-
// if this is not the root record, add a prefix filter to the query
|
|
1088
|
-
if (parentContextId !== '') {
|
|
1089
|
-
const prefixFilter = FilterUtility.constructPrefixFilterAsRangeFilter(parentContextId);
|
|
1090
|
-
filter.contextId = prefixFilter;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
const { messages: matchingMessages } = await messageStore.query(tenant, [filter]);
|
|
1094
|
-
const matchingRecords = matchingMessages as RecordsWriteMessage[];
|
|
1095
|
-
const matchingRecordsExceptIncomingRecordId = matchingRecords.filter((recordsWriteMessage) =>
|
|
1096
|
-
recordsWriteMessage.recordId !== incomingRecordsWrite.message.recordId
|
|
1097
|
-
);
|
|
1098
|
-
if (matchingRecordsExceptIncomingRecordId.length > 0) {
|
|
1099
|
-
throw new DwnError(
|
|
1100
|
-
DwnErrorCode.ProtocolAuthorizationDuplicateRoleRecipient,
|
|
1101
|
-
`DID '${recipient}' is already recipient of a role record at protocol path '${protocolPath} under the parent context ${parentContextId}.`
|
|
1102
|
-
);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Checks if the `who: 'author' | 'recipient'` action rule has a matching record in the record chain.
|
|
1108
|
-
* For cross-protocol `of` references (e.g., `"threads:thread"`), matches against both the protocol URI
|
|
1109
|
-
* and the protocol path of the ancestor record.
|
|
1110
|
-
* @returns `true` if the action rule is satisfied; `false` otherwise.
|
|
1111
|
-
*/
|
|
1112
|
-
private static async checkActor(
|
|
1113
|
-
author: string,
|
|
1114
|
-
actionRule: ProtocolActionRule,
|
|
1115
|
-
recordChain: RecordsWriteMessage[],
|
|
1116
|
-
composingDefinition?: ProtocolDefinition,
|
|
1117
|
-
): Promise<boolean> {
|
|
1118
|
-
const ofValue = actionRule.of;
|
|
1119
|
-
|
|
1120
|
-
// `of` should always be defined when `checkActor` is called, but guard defensively
|
|
1121
|
-
if (ofValue === undefined) {
|
|
1122
|
-
return false;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
let ancestorRecordsWrite: RecordsWriteMessage | undefined;
|
|
1126
|
-
|
|
1127
|
-
if (isCrossProtocolRef(ofValue) && composingDefinition?.uses !== undefined) {
|
|
1128
|
-
// Cross-protocol `of`: resolve alias to protocol URI and match by both protocol + protocolPath
|
|
1129
|
-
const parsed = parseCrossProtocolRef(ofValue);
|
|
1130
|
-
if (parsed !== undefined) {
|
|
1131
|
-
const refProtocolUri = composingDefinition.uses[parsed.alias];
|
|
1132
|
-
if (refProtocolUri !== undefined) {
|
|
1133
|
-
ancestorRecordsWrite = recordChain.find((msg) =>
|
|
1134
|
-
msg.descriptor.protocol === refProtocolUri && msg.descriptor.protocolPath === parsed.protocolPath
|
|
1135
|
-
);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
} else {
|
|
1139
|
-
// Local `of`: match by protocolPath only (same protocol assumed)
|
|
1140
|
-
ancestorRecordsWrite = recordChain.find((msg) =>
|
|
1141
|
-
msg.descriptor.protocolPath === ofValue
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
if (ancestorRecordsWrite === undefined) {
|
|
1146
|
-
// No matching ancestor found in the record chain. Return false to allow the caller
|
|
1147
|
-
// to continue evaluating other action rules that might authorize the request.
|
|
1148
|
-
return false;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
if (actionRule.who === ProtocolActor.Recipient) {
|
|
1152
|
-
// author of the incoming message must be the recipient of the ancestor message
|
|
1153
|
-
return author === ancestorRecordsWrite.descriptor.recipient;
|
|
1154
|
-
} else { // actionRule.who === ProtocolActor.Author
|
|
1155
|
-
// author of the incoming message must be the author of the ancestor message
|
|
1156
|
-
const ancestorAuthor = (await RecordsWrite.parse(ancestorRecordsWrite)).author;
|
|
1157
|
-
return author === ancestorAuthor;
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
/**
|
|
1162
|
-
* Determines the timestamp that governs which protocol definition version applies to the given RecordsWrite.
|
|
1163
|
-
* For an update, this is the initial write's `messageTimestamp` (the protocol version is locked at creation time).
|
|
1164
|
-
* For a new initial write, returns `undefined` — the latest protocol definition should be used because the
|
|
1165
|
-
* record is being created now and must conform to the current protocol rules.
|
|
1166
|
-
*/
|
|
1167
|
-
private static async getGoverningTimestamp(
|
|
1168
|
-
tenant: string,
|
|
1169
|
-
incomingMessage: RecordsWrite,
|
|
1170
|
-
messageStore: MessageStore,
|
|
1171
|
-
): Promise<string | undefined> {
|
|
1172
|
-
const existingInitialWrite = await ProtocolAuthorization.fetchInitialWrite(
|
|
1173
|
-
tenant, incomingMessage.message.recordId, messageStore
|
|
1174
|
-
);
|
|
1175
|
-
|
|
1176
|
-
if (existingInitialWrite !== undefined) {
|
|
1177
|
-
// update case: use the initial write's timestamp
|
|
1178
|
-
return existingInitialWrite.descriptor.messageTimestamp;
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
// initial write case: validate against the latest protocol definition
|
|
1182
|
-
return undefined;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
private static getTypeName(protocolPath: string): string {
|
|
1186
|
-
return protocolPath.split('/').slice(-1)[0];
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
443
|
+
}
|