@enbox/dwn-sdk-js 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/dist/browser.mjs +8 -8
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/generated/precompiled-validators.js +2591 -1435
  4. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  5. package/dist/esm/src/core/constants.js +20 -0
  6. package/dist/esm/src/core/constants.js.map +1 -1
  7. package/dist/esm/src/core/dwn-error.js +24 -1
  8. package/dist/esm/src/core/dwn-error.js.map +1 -1
  9. package/dist/esm/src/core/grant-authorization.js +4 -4
  10. package/dist/esm/src/core/grant-authorization.js.map +1 -1
  11. package/dist/esm/src/core/message.js +89 -4
  12. package/dist/esm/src/core/message.js.map +1 -1
  13. package/dist/esm/src/core/messages-grant-authorization.js +147 -55
  14. package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
  15. package/dist/esm/src/core/protocol-authorization.js +76 -0
  16. package/dist/esm/src/core/protocol-authorization.js.map +1 -1
  17. package/dist/esm/src/core/records-grant-authorization.js +40 -15
  18. package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
  19. package/dist/esm/src/handlers/messages-read.js +5 -5
  20. package/dist/esm/src/handlers/messages-read.js.map +1 -1
  21. package/dist/esm/src/handlers/messages-subscribe.js +109 -7
  22. package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
  23. package/dist/esm/src/handlers/messages-sync.js +341 -96
  24. package/dist/esm/src/handlers/messages-sync.js.map +1 -1
  25. package/dist/esm/src/handlers/protocols-configure.js +81 -2
  26. package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
  27. package/dist/esm/src/handlers/records-count.js +30 -0
  28. package/dist/esm/src/handlers/records-count.js.map +1 -1
  29. package/dist/esm/src/handlers/records-delete.js +3 -2
  30. package/dist/esm/src/handlers/records-delete.js.map +1 -1
  31. package/dist/esm/src/handlers/records-query.js +30 -0
  32. package/dist/esm/src/handlers/records-query.js.map +1 -1
  33. package/dist/esm/src/handlers/records-read.js +3 -2
  34. package/dist/esm/src/handlers/records-read.js.map +1 -1
  35. package/dist/esm/src/handlers/records-subscribe.js +31 -0
  36. package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
  37. package/dist/esm/src/handlers/records-write.js +21 -14
  38. package/dist/esm/src/handlers/records-write.js.map +1 -1
  39. package/dist/esm/src/index.js +2 -0
  40. package/dist/esm/src/index.js.map +1 -1
  41. package/dist/esm/src/interfaces/messages-read.js +6 -3
  42. package/dist/esm/src/interfaces/messages-read.js.map +1 -1
  43. package/dist/esm/src/interfaces/messages-subscribe.js +6 -3
  44. package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
  45. package/dist/esm/src/interfaces/messages-sync.js +17 -3
  46. package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
  47. package/dist/esm/src/interfaces/protocols-configure.js +5 -2
  48. package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
  49. package/dist/esm/src/interfaces/protocols-query.js +8 -4
  50. package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
  51. package/dist/esm/src/interfaces/records-count.js +5 -0
  52. package/dist/esm/src/interfaces/records-count.js.map +1 -1
  53. package/dist/esm/src/interfaces/records-delete.js +6 -2
  54. package/dist/esm/src/interfaces/records-delete.js.map +1 -1
  55. package/dist/esm/src/interfaces/records-query.js +5 -0
  56. package/dist/esm/src/interfaces/records-query.js.map +1 -1
  57. package/dist/esm/src/interfaces/records-read.js +6 -3
  58. package/dist/esm/src/interfaces/records-read.js.map +1 -1
  59. package/dist/esm/src/interfaces/records-subscribe.js +5 -0
  60. package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
  61. package/dist/esm/src/interfaces/records-write.js +6 -3
  62. package/dist/esm/src/interfaces/records-write.js.map +1 -1
  63. package/dist/esm/src/protocols/permissions.js +28 -7
  64. package/dist/esm/src/protocols/permissions.js.map +1 -1
  65. package/dist/esm/src/sync/records-projection.js +228 -0
  66. package/dist/esm/src/sync/records-projection.js.map +1 -0
  67. package/dist/esm/src/types/message-types.js.map +1 -1
  68. package/dist/esm/src/types/permission-types.js.map +1 -1
  69. package/dist/esm/src/utils/permission-scope.js +37 -0
  70. package/dist/esm/src/utils/permission-scope.js.map +1 -0
  71. package/dist/esm/tests/core/grant-authorization.spec.js +26 -3
  72. package/dist/esm/tests/core/grant-authorization.spec.js.map +1 -1
  73. package/dist/esm/tests/core/records-grant-authorization.spec.js +117 -0
  74. package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -0
  75. package/dist/esm/tests/features/permissions.spec.js +126 -0
  76. package/dist/esm/tests/features/permissions.spec.js.map +1 -1
  77. package/dist/esm/tests/features/records-record-limit.spec.js +14 -0
  78. package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
  79. package/dist/esm/tests/handlers/messages-read.spec.js +345 -12
  80. package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
  81. package/dist/esm/tests/handlers/messages-subscribe.spec.js +326 -9
  82. package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
  83. package/dist/esm/tests/handlers/messages-sync.spec.js +1053 -7
  84. package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
  85. package/dist/esm/tests/handlers/protocols-configure.spec.js +361 -0
  86. package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
  87. package/dist/esm/tests/handlers/records-count.spec.js +75 -2
  88. package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
  89. package/dist/esm/tests/handlers/records-query.spec.js +73 -0
  90. package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
  91. package/dist/esm/tests/handlers/records-subscribe.spec.js +75 -1
  92. package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
  93. package/dist/esm/tests/handlers/records-write.spec.js +41 -0
  94. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  95. package/dist/esm/tests/interfaces/messages-get.spec.js +107 -5
  96. package/dist/esm/tests/interfaces/messages-get.spec.js.map +1 -1
  97. package/dist/esm/tests/interfaces/protocols-configure.spec.js +13 -0
  98. package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
  99. package/dist/esm/tests/interfaces/records-delete.spec.js +12 -0
  100. package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
  101. package/dist/esm/tests/interfaces/records-query.spec.js +10 -0
  102. package/dist/esm/tests/interfaces/records-query.spec.js.map +1 -1
  103. package/dist/esm/tests/interfaces/records-subscribe.spec.js +10 -0
  104. package/dist/esm/tests/interfaces/records-subscribe.spec.js.map +1 -1
  105. package/dist/esm/tests/interfaces/records-write.spec.js +33 -0
  106. package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
  107. package/dist/esm/tests/sync/records-projection.spec.js +245 -0
  108. package/dist/esm/tests/sync/records-projection.spec.js.map +1 -0
  109. package/dist/esm/tests/test-suite.js +2 -0
  110. package/dist/esm/tests/test-suite.js.map +1 -1
  111. package/dist/esm/tests/utils/permission-scope.spec.js +66 -0
  112. package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -0
  113. package/dist/esm/tests/utils/test-data-generator.js +5 -2
  114. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  115. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  116. package/dist/types/src/core/constants.d.ts +13 -0
  117. package/dist/types/src/core/constants.d.ts.map +1 -1
  118. package/dist/types/src/core/dwn-error.d.ts +24 -1
  119. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  120. package/dist/types/src/core/grant-authorization.d.ts +1 -2
  121. package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
  122. package/dist/types/src/core/message.d.ts +41 -1
  123. package/dist/types/src/core/message.d.ts.map +1 -1
  124. package/dist/types/src/core/messages-grant-authorization.d.ts +36 -4
  125. package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
  126. package/dist/types/src/core/protocol-authorization.d.ts +12 -0
  127. package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
  128. package/dist/types/src/core/records-grant-authorization.d.ts +6 -0
  129. package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
  130. package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
  131. package/dist/types/src/handlers/messages-subscribe.d.ts +2 -1
  132. package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
  133. package/dist/types/src/handlers/messages-sync.d.ts +31 -0
  134. package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
  135. package/dist/types/src/handlers/protocols-configure.d.ts +3 -0
  136. package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
  137. package/dist/types/src/handlers/records-count.d.ts +4 -0
  138. package/dist/types/src/handlers/records-count.d.ts.map +1 -1
  139. package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
  140. package/dist/types/src/handlers/records-query.d.ts +4 -0
  141. package/dist/types/src/handlers/records-query.d.ts.map +1 -1
  142. package/dist/types/src/handlers/records-read.d.ts.map +1 -1
  143. package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
  144. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  145. package/dist/types/src/index.d.ts +6 -2
  146. package/dist/types/src/index.d.ts.map +1 -1
  147. package/dist/types/src/interfaces/messages-read.d.ts +1 -1
  148. package/dist/types/src/interfaces/messages-read.d.ts.map +1 -1
  149. package/dist/types/src/interfaces/messages-subscribe.d.ts +1 -1
  150. package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
  151. package/dist/types/src/interfaces/messages-sync.d.ts +4 -1
  152. package/dist/types/src/interfaces/messages-sync.d.ts.map +1 -1
  153. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  154. package/dist/types/src/interfaces/protocols-query.d.ts.map +1 -1
  155. package/dist/types/src/interfaces/records-count.d.ts +1 -0
  156. package/dist/types/src/interfaces/records-count.d.ts.map +1 -1
  157. package/dist/types/src/interfaces/records-delete.d.ts.map +1 -1
  158. package/dist/types/src/interfaces/records-query.d.ts +1 -0
  159. package/dist/types/src/interfaces/records-query.d.ts.map +1 -1
  160. package/dist/types/src/interfaces/records-read.d.ts.map +1 -1
  161. package/dist/types/src/interfaces/records-subscribe.d.ts +1 -0
  162. package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
  163. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  164. package/dist/types/src/protocols/permissions.d.ts +2 -0
  165. package/dist/types/src/protocols/permissions.d.ts.map +1 -1
  166. package/dist/types/src/sync/records-projection.d.ts +98 -0
  167. package/dist/types/src/sync/records-projection.d.ts.map +1 -0
  168. package/dist/types/src/types/message-types.d.ts +1 -0
  169. package/dist/types/src/types/message-types.d.ts.map +1 -1
  170. package/dist/types/src/types/messages-types.d.ts +21 -3
  171. package/dist/types/src/types/messages-types.d.ts.map +1 -1
  172. package/dist/types/src/types/permission-types.d.ts +4 -0
  173. package/dist/types/src/types/permission-types.d.ts.map +1 -1
  174. package/dist/types/src/types/records-types.d.ts +4 -0
  175. package/dist/types/src/types/records-types.d.ts.map +1 -1
  176. package/dist/types/src/types/subscriptions.d.ts +18 -3
  177. package/dist/types/src/types/subscriptions.d.ts.map +1 -1
  178. package/dist/types/src/utils/permission-scope.d.ts +29 -0
  179. package/dist/types/src/utils/permission-scope.d.ts.map +1 -0
  180. package/dist/types/tests/core/records-grant-authorization.spec.d.ts +2 -0
  181. package/dist/types/tests/core/records-grant-authorization.spec.d.ts.map +1 -0
  182. package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
  183. package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -1
  184. package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
  185. package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
  186. package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
  187. package/dist/types/tests/handlers/protocols-configure.spec.d.ts.map +1 -1
  188. package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
  189. package/dist/types/tests/handlers/records-query.spec.d.ts.map +1 -1
  190. package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
  191. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  192. package/dist/types/tests/sync/records-projection.spec.d.ts +2 -0
  193. package/dist/types/tests/sync/records-projection.spec.d.ts.map +1 -0
  194. package/dist/types/tests/test-suite.d.ts.map +1 -1
  195. package/dist/types/tests/utils/permission-scope.spec.d.ts +2 -0
  196. package/dist/types/tests/utils/permission-scope.spec.d.ts.map +1 -0
  197. package/dist/types/tests/utils/test-data-generator.d.ts +5 -2
  198. package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
  199. package/package.json +1 -1
  200. package/src/core/constants.ts +24 -0
  201. package/src/core/dwn-error.ts +24 -1
  202. package/src/core/grant-authorization.ts +7 -5
  203. package/src/core/message.ts +153 -6
  204. package/src/core/messages-grant-authorization.ts +282 -70
  205. package/src/core/protocol-authorization.ts +130 -0
  206. package/src/core/records-grant-authorization.ts +64 -21
  207. package/src/handlers/messages-read.ts +7 -5
  208. package/src/handlers/messages-subscribe.ts +149 -9
  209. package/src/handlers/messages-sync.ts +593 -102
  210. package/src/handlers/protocols-configure.ts +103 -2
  211. package/src/handlers/records-count.ts +33 -0
  212. package/src/handlers/records-delete.ts +3 -2
  213. package/src/handlers/records-query.ts +33 -0
  214. package/src/handlers/records-read.ts +3 -2
  215. package/src/handlers/records-subscribe.ts +34 -0
  216. package/src/handlers/records-write.ts +21 -15
  217. package/src/index.ts +7 -3
  218. package/src/interfaces/messages-read.ts +8 -5
  219. package/src/interfaces/messages-subscribe.ts +12 -9
  220. package/src/interfaces/messages-sync.ts +33 -12
  221. package/src/interfaces/protocols-configure.ts +8 -4
  222. package/src/interfaces/protocols-query.ts +13 -9
  223. package/src/interfaces/records-count.ts +7 -0
  224. package/src/interfaces/records-delete.ts +9 -5
  225. package/src/interfaces/records-query.ts +7 -0
  226. package/src/interfaces/records-read.ts +6 -3
  227. package/src/interfaces/records-subscribe.ts +7 -0
  228. package/src/interfaces/records-write.ts +25 -17
  229. package/src/protocols/permissions.ts +47 -9
  230. package/src/sync/records-projection.ts +328 -0
  231. package/src/types/message-types.ts +1 -0
  232. package/src/types/messages-types.ts +23 -3
  233. package/src/types/permission-types.ts +5 -1
  234. package/src/types/records-types.ts +5 -1
  235. package/src/types/subscriptions.ts +19 -3
  236. package/src/utils/permission-scope.ts +55 -0
@@ -1,10 +1,12 @@
1
1
  import type { MessageStore } from '../types/message-store.js';
2
2
  import type { PermissionGrant } from '../protocols/permission-grant.js';
3
+ import type { ProtocolScope } from '../utils/permission-scope.js';
3
4
  import type { PermissionConditions, RecordsPermissionScope } from '../types/permission-types.js';
4
5
  import type { RecordsCountMessage, RecordsDeleteMessage, RecordsQueryMessage, RecordsReadMessage, RecordsSubscribeMessage, RecordsWriteMessage } from '../types/records-types.js';
5
6
 
6
7
  import { GrantAuthorization } from './grant-authorization.js';
7
8
  import { PermissionConditionPublication } from '../types/permission-types.js';
9
+ import { PermissionScopeMatcher } from '../utils/permission-scope.js';
8
10
  import { DwnError, DwnErrorCode } from './dwn-error.js';
9
11
 
10
12
  export class RecordsGrantAuthorization {
@@ -90,11 +92,15 @@ export class RecordsGrantAuthorization {
90
92
  // The grant's protocol must match the query/subscribe filter's protocol.
91
93
  // NOTE: validated the invoked permission is for Records in GrantAuthorization.performBaseValidation()
92
94
  const permissionScope = permissionGrant.scope as RecordsPermissionScope;
93
- const protocolInMessage = incomingMessage.descriptor.filter.protocol;
94
- if (protocolInMessage !== permissionScope.protocol) {
95
+ const messageFilter = incomingMessage.descriptor.filter;
96
+ if (!PermissionScopeMatcher.matches(permissionScope, {
97
+ protocol : messageFilter.protocol,
98
+ protocolPath : messageFilter.protocolPath,
99
+ contextId : messageFilter.contextId,
100
+ })) {
95
101
  throw new DwnError(
96
102
  DwnErrorCode.RecordsGrantAuthorizationQueryOrSubscribeProtocolScopeMismatch,
97
- `Grant protocol scope ${permissionScope.protocol} does not match protocol in message ${protocolInMessage}`
103
+ `Grant scope does not match Records ${incomingMessage.descriptor.method} filter`
98
104
  );
99
105
  }
100
106
  }
@@ -123,16 +129,8 @@ export class RecordsGrantAuthorization {
123
129
  messageStore
124
130
  });
125
131
 
126
- // The grant's protocol must match the protocol of the record being deleted.
127
132
  // NOTE: validated the invoked permission is for Records in GrantAuthorization.performBaseValidation()
128
- const permissionScope = permissionGrant.scope as RecordsPermissionScope;
129
- const protocolOfRecordToDelete = recordsWriteToDelete.descriptor.protocol;
130
- if (protocolOfRecordToDelete !== permissionScope.protocol) {
131
- throw new DwnError(
132
- DwnErrorCode.RecordsGrantAuthorizationDeleteProtocolScopeMismatch,
133
- `Grant protocol scope ${permissionScope.protocol} does not match protocol in record to delete ${protocolOfRecordToDelete}`
134
- );
135
- }
133
+ RecordsGrantAuthorization.verifyDeleteScope(recordsWriteToDelete, permissionGrant.scope as RecordsPermissionScope);
136
134
  }
137
135
 
138
136
  /**
@@ -142,32 +140,77 @@ export class RecordsGrantAuthorization {
142
140
  recordsWriteMessage: RecordsWriteMessage,
143
141
  grantScope: RecordsPermissionScope
144
142
  ): void {
143
+ const target = RecordsGrantAuthorization.getProtocolScopeTarget(recordsWriteMessage);
144
+
145
+ if (PermissionScopeMatcher.matches(grantScope, target)) {
146
+ return;
147
+ }
145
148
 
146
- // The record's protocol must match the protocol specified in the record
147
- if (grantScope.protocol !== recordsWriteMessage.descriptor.protocol) {
149
+ if (grantScope.protocol !== target.protocol) {
148
150
  throw new DwnError(
149
151
  DwnErrorCode.RecordsGrantAuthorizationScopeProtocolMismatch,
150
152
  `Grant scope specifies different protocol than what appears in the record`
151
153
  );
152
154
  }
153
155
 
156
+ RecordsGrantAuthorization.throwScopeMismatchAfterProtocolMatch(grantScope, target);
157
+ }
158
+
159
+ /**
160
+ * Verifies RecordsDelete scope while preserving the delete-specific protocol mismatch code.
161
+ */
162
+ private static verifyDeleteScope(
163
+ recordsWriteMessage: RecordsWriteMessage,
164
+ grantScope: RecordsPermissionScope
165
+ ): void {
166
+ const target = RecordsGrantAuthorization.getProtocolScopeTarget(recordsWriteMessage);
167
+
168
+ if (PermissionScopeMatcher.matches(grantScope, target)) {
169
+ return;
170
+ }
171
+
172
+ if (grantScope.protocol !== target.protocol) {
173
+ throw new DwnError(
174
+ DwnErrorCode.RecordsGrantAuthorizationDeleteProtocolScopeMismatch,
175
+ `Grant protocol scope ${grantScope.protocol} does not match protocol in record to delete ${target.protocol}`
176
+ );
177
+ }
178
+
179
+ RecordsGrantAuthorization.throwScopeMismatchAfterProtocolMatch(grantScope, target);
180
+ }
181
+
182
+ private static getProtocolScopeTarget(recordsWriteMessage: RecordsWriteMessage): ProtocolScope {
183
+ return {
184
+ protocol : recordsWriteMessage.descriptor.protocol,
185
+ protocolPath : recordsWriteMessage.descriptor.protocolPath,
186
+ contextId : recordsWriteMessage.contextId,
187
+ };
188
+ }
189
+
190
+ private static throwScopeMismatchAfterProtocolMatch(
191
+ grantScope: RecordsPermissionScope,
192
+ target: ProtocolScope
193
+ ): never {
154
194
  // If grant specifies a contextId, check that record falls under that contextId
155
195
  if (grantScope.contextId !== undefined) {
156
- if (!recordsWriteMessage.contextId?.startsWith(grantScope.contextId)) {
157
- throw new DwnError(
158
- DwnErrorCode.RecordsGrantAuthorizationScopeContextIdMismatch,
159
- `Grant scope specifies different contextId than what appears in the record`
160
- );
161
- }
196
+ throw new DwnError(
197
+ DwnErrorCode.RecordsGrantAuthorizationScopeContextIdMismatch,
198
+ `Grant scope specifies different contextId than what appears in the record`
199
+ );
162
200
  }
163
201
 
164
202
  // If grant specifies protocolPath, check that record is at that protocolPath
165
- if (grantScope.protocolPath !== undefined && grantScope.protocolPath !== recordsWriteMessage.descriptor.protocolPath) {
203
+ if (grantScope.protocolPath !== undefined && grantScope.protocolPath !== target.protocolPath) {
166
204
  throw new DwnError(
167
205
  DwnErrorCode.RecordsGrantAuthorizationScopeProtocolPathMismatch,
168
206
  `Grant scope specifies different protocolPath than what appears in the record`
169
207
  );
170
208
  }
209
+
210
+ throw new DwnError(
211
+ DwnErrorCode.RecordsGrantAuthorizationScopeMismatch,
212
+ `Grant scope does not match the record`
213
+ );
171
214
  }
172
215
 
173
216
  /**
@@ -7,10 +7,10 @@ import type { MessagesReadMessage, MessagesReadReply, MessagesReadReplyEntry } f
7
7
  import { authenticate } from '../core/auth.js';
8
8
  import { DataStream } from '../utils/data-stream.js';
9
9
  import { Encoder } from '../utils/encoder.js';
10
+ import { Message } from '../core/message.js';
10
11
  import { messageReplyFromError } from '../core/message-reply.js';
11
12
  import { MessagesGrantAuthorization } from '../core/messages-grant-authorization.js';
12
13
  import { MessagesRead } from '../interfaces/messages-read.js';
13
- import { PermissionsProtocol } from '../protocols/permissions.js';
14
14
  import { Records } from '../utils/records.js';
15
15
  import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
16
16
 
@@ -84,15 +84,17 @@ export class MessagesReadHandler implements MethodHandler {
84
84
  if (messagesRead.author === tenant) {
85
85
  // If the author is the tenant, no further authorization is needed
86
86
  return;
87
- } else if (messagesRead.author !== undefined && messagesRead.signaturePayload!.permissionGrantId !== undefined) {
88
- // if the author is not the tenant and the message has a permissionGrantId, we need to authorize the grant
89
- const permissionGrant = await PermissionsProtocol.fetchGrant(tenant, messageStore, messagesRead.signaturePayload!.permissionGrantId);
87
+ }
88
+
89
+ const permissionGrantIds = Message.getPermissionGrantIds(messagesRead.signaturePayload!);
90
+ if (messagesRead.author !== undefined && permissionGrantIds.length > 0) {
91
+ const permissionGrants = await MessagesGrantAuthorization.fetchPermissionGrants(tenant, messageStore, permissionGrantIds);
90
92
  await MessagesGrantAuthorization.authorizeMessagesRead({
91
93
  messagesReadMessage : messagesRead.message,
92
94
  messageToRead : matchedMessage,
93
95
  expectedGrantor : tenant,
94
96
  expectedGrantee : messagesRead.author,
95
- permissionGrant,
97
+ permissionGrants,
96
98
  messageStore
97
99
  });
98
100
  } else {
@@ -1,7 +1,8 @@
1
1
  import type { MessageStore } from '../types/message-store.js';
2
+ import type { PermissionGrant } from '../protocols/permission-grant.js';
3
+ import type { EventSubscription, ProgressGapInfo, SubscriptionEvent, SubscriptionListener, SubscriptionMessage } from '../types/subscriptions.js';
2
4
  import type { HandlerDependencies, MethodHandler } from '../types/method-handler.js';
3
5
  import type { MessagesSubscribeMessage, MessagesSubscribeReply } from '../types/messages-types.js';
4
- import type { ProgressGapInfo, SubscriptionListener } from '../types/subscriptions.js';
5
6
 
6
7
  import { authenticate } from '../core/auth.js';
7
8
  import { Message } from '../core/message.js';
@@ -9,9 +10,23 @@ import { messageReplyFromError } from '../core/message-reply.js';
9
10
  import { Messages } from '../utils/messages.js';
10
11
  import { MessagesGrantAuthorization } from '../core/messages-grant-authorization.js';
11
12
  import { MessagesSubscribe } from '../interfaces/messages-subscribe.js';
12
- import { PermissionsProtocol } from '../protocols/permissions.js';
13
+ import { Time } from '../utils/time.js';
13
14
  import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
14
15
 
16
+ type MessagesSubscribeAuthorization =
17
+ | { kind: 'owner' }
18
+ | {
19
+ kind: 'delegate';
20
+ expectedGrantor: string;
21
+ expectedGrantee: string;
22
+ permissionGrants: PermissionGrant[];
23
+ };
24
+
25
+ type GuardedSubscriptionHandler = {
26
+ listener: SubscriptionListener;
27
+ setSubscription(subscription: EventSubscription): Promise<void>;
28
+ };
29
+
15
30
  export class MessagesSubscribeHandler implements MethodHandler {
16
31
 
17
32
  constructor(private readonly deps: HandlerDependencies) {}
@@ -39,22 +54,31 @@ export class MessagesSubscribeHandler implements MethodHandler {
39
54
  return messageReplyFromError(e, 400);
40
55
  }
41
56
 
57
+ let authorization: MessagesSubscribeAuthorization;
42
58
  try {
43
59
  await authenticate(message.authorization, this.deps.didResolver);
44
- await MessagesSubscribeHandler.authorizeMessagesSubscribe(tenant, messagesSubscribe, this.deps.messageStore);
60
+ authorization = await MessagesSubscribeHandler.authorizeMessagesSubscribe(tenant, messagesSubscribe, this.deps.messageStore);
45
61
  } catch (error) {
46
62
  return messageReplyFromError(error, 401);
47
63
  }
48
64
 
65
+ const guardedHandler = MessagesSubscribeHandler.createAuthorizationGuard({
66
+ authorization,
67
+ messagesSubscribe,
68
+ messageStore: this.deps.messageStore,
69
+ subscriptionHandler,
70
+ });
71
+
49
72
  const { filters, cursor: eventLogCursor } = message.descriptor;
50
73
  const messagesFilters = Messages.convertFilters(filters, this.deps.coreProtocols);
51
74
  const messageCid = await Message.getCid(message);
52
75
 
53
76
  try {
54
- const subscription = await this.deps.eventLog.subscribe(tenant, messageCid, subscriptionHandler, {
77
+ const subscription = await this.deps.eventLog.subscribe(tenant, messageCid, guardedHandler.listener, {
55
78
  cursor : eventLogCursor,
56
79
  filters : messagesFilters,
57
80
  });
81
+ await guardedHandler.setSubscription(subscription);
58
82
 
59
83
  return {
60
84
  status: { code: 200, detail: 'OK' },
@@ -72,21 +96,137 @@ export class MessagesSubscribeHandler implements MethodHandler {
72
96
  }
73
97
  }
74
98
 
75
- private static async authorizeMessagesSubscribe(tenant: string, messagesSubscribe: MessagesSubscribe, messageStore: MessageStore): Promise<void> {
99
+ private static async authorizeMessagesSubscribe(
100
+ tenant: string,
101
+ messagesSubscribe: MessagesSubscribe,
102
+ messageStore: MessageStore
103
+ ): Promise<MessagesSubscribeAuthorization> {
76
104
  // if `MessagesSubscribe` author is the same as the target tenant, we can directly grant access
77
105
  if (messagesSubscribe.author === tenant) {
78
- return;
79
- } else if (messagesSubscribe.author !== undefined && messagesSubscribe.signaturePayload!.permissionGrantId !== undefined) {
80
- const permissionGrant = await PermissionsProtocol.fetchGrant(tenant, messageStore, messagesSubscribe.signaturePayload!.permissionGrantId);
106
+ return { kind: 'owner' };
107
+ }
108
+
109
+ const permissionGrantIds = Message.getPermissionGrantIds(messagesSubscribe.signaturePayload!);
110
+ if (messagesSubscribe.author !== undefined && permissionGrantIds.length > 0) {
111
+ const permissionGrants = await MessagesGrantAuthorization.fetchPermissionGrants(tenant, messageStore, permissionGrantIds);
81
112
  await MessagesGrantAuthorization.authorizeSubscribeOrSync({
82
113
  incomingMessage : messagesSubscribe.message,
83
114
  expectedGrantor : tenant,
84
115
  expectedGrantee : messagesSubscribe.author,
85
- permissionGrant,
116
+ permissionGrants,
86
117
  messageStore
87
118
  });
119
+ return {
120
+ kind : 'delegate',
121
+ expectedGrantor : tenant,
122
+ expectedGrantee : messagesSubscribe.author,
123
+ permissionGrants,
124
+ };
88
125
  } else {
89
126
  throw new DwnError(DwnErrorCode.MessagesSubscribeAuthorizationFailed, 'message failed authorization');
90
127
  }
91
128
  }
129
+
130
+ private static createAuthorizationGuard(input: {
131
+ authorization: MessagesSubscribeAuthorization;
132
+ messagesSubscribe: MessagesSubscribe;
133
+ messageStore: MessageStore;
134
+ subscriptionHandler: SubscriptionListener;
135
+ }): GuardedSubscriptionHandler {
136
+ const { authorization, messagesSubscribe, messageStore, subscriptionHandler } = input;
137
+ if (authorization.kind === 'owner') {
138
+ return {
139
+ listener : subscriptionHandler,
140
+ setSubscription : async (): Promise<void> => {},
141
+ };
142
+ }
143
+
144
+ let subscription: EventSubscription | undefined;
145
+ let closeRequested = false;
146
+ let terminalErrorEmitted = false;
147
+ let deliveryQueue: Promise<void> = Promise.resolve();
148
+
149
+ const closeSubscription = (): void => {
150
+ if (closeRequested) {
151
+ return;
152
+ }
153
+ closeRequested = true;
154
+ Promise.resolve(subscription?.close()).catch(() => {});
155
+ };
156
+
157
+ const emitTerminalAuthorizationError = (cursor: SubscriptionEvent['cursor']): void => {
158
+ if (terminalErrorEmitted) {
159
+ return;
160
+ }
161
+ terminalErrorEmitted = true;
162
+ subscriptionHandler({
163
+ type : 'error',
164
+ cursor,
165
+ error : {
166
+ code : DwnErrorCode.MessagesSubscribeDeliveryAuthorizationFailed,
167
+ detail : 'subscription authorization failed during delivery',
168
+ },
169
+ });
170
+ };
171
+
172
+ // Deliberately do not cache delivery authorization here. Subscribe-open
173
+ // authorization validates static grant shape and filter scope; this per-event
174
+ // check revalidates dynamic grant state so expiry or revocation stops delivery
175
+ // before the next event is forwarded. Future throughput optimizations should
176
+ // split static and dynamic checks explicitly and document any bounded staleness
177
+ // introduced by caching revocation lookups.
178
+ const authorizeAndDeliverEvent = async (subMessage: SubscriptionEvent): Promise<void> => {
179
+ try {
180
+ await MessagesGrantAuthorization.authorizeSubscribeDelivery({
181
+ messagesSubscribeMessage : messagesSubscribe.message,
182
+ expectedGrantor : authorization.expectedGrantor,
183
+ expectedGrantee : authorization.expectedGrantee,
184
+ permissionGrants : authorization.permissionGrants,
185
+ messageStore,
186
+ deliveryTimestamp : Time.getCurrentTimestamp(),
187
+ });
188
+ } catch {
189
+ emitTerminalAuthorizationError(subMessage.cursor);
190
+ closeSubscription();
191
+ return;
192
+ }
193
+
194
+ if (!closeRequested) {
195
+ subscriptionHandler(subMessage);
196
+ }
197
+ };
198
+
199
+ const deliverQueuedMessage = async (subMessage: SubscriptionMessage): Promise<void> => {
200
+ if (closeRequested) {
201
+ return;
202
+ }
203
+
204
+ if (subMessage.type !== 'event') {
205
+ subscriptionHandler(subMessage);
206
+ return;
207
+ }
208
+
209
+ await authorizeAndDeliverEvent(subMessage);
210
+ };
211
+
212
+ const enqueueDelivery = (subMessage: SubscriptionMessage): void => {
213
+ deliveryQueue = deliveryQueue
214
+ .then(() => deliverQueuedMessage(subMessage))
215
+ .catch(() => {});
216
+ };
217
+
218
+ const listener: SubscriptionListener = (subMessage: SubscriptionMessage): void => {
219
+ enqueueDelivery(subMessage);
220
+ };
221
+
222
+ return {
223
+ listener,
224
+ setSubscription: async (eventSubscription: EventSubscription): Promise<void> => {
225
+ subscription = eventSubscription;
226
+ if (closeRequested) {
227
+ await eventSubscription.close();
228
+ }
229
+ },
230
+ };
231
+ }
92
232
  }