@enbox/dwn-sdk-js 0.3.6 → 0.3.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.
Files changed (234) 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 +36 -11
  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/handlers/messages-read.spec.js +345 -12
  78. package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
  79. package/dist/esm/tests/handlers/messages-subscribe.spec.js +326 -9
  80. package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
  81. package/dist/esm/tests/handlers/messages-sync.spec.js +1053 -7
  82. package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
  83. package/dist/esm/tests/handlers/protocols-configure.spec.js +361 -0
  84. package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
  85. package/dist/esm/tests/handlers/records-count.spec.js +75 -2
  86. package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
  87. package/dist/esm/tests/handlers/records-query.spec.js +73 -0
  88. package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
  89. package/dist/esm/tests/handlers/records-subscribe.spec.js +75 -1
  90. package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
  91. package/dist/esm/tests/handlers/records-write.spec.js +15 -0
  92. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  93. package/dist/esm/tests/interfaces/messages-get.spec.js +107 -5
  94. package/dist/esm/tests/interfaces/messages-get.spec.js.map +1 -1
  95. package/dist/esm/tests/interfaces/protocols-configure.spec.js +13 -0
  96. package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
  97. package/dist/esm/tests/interfaces/records-delete.spec.js +12 -0
  98. package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
  99. package/dist/esm/tests/interfaces/records-query.spec.js +10 -0
  100. package/dist/esm/tests/interfaces/records-query.spec.js.map +1 -1
  101. package/dist/esm/tests/interfaces/records-subscribe.spec.js +10 -0
  102. package/dist/esm/tests/interfaces/records-subscribe.spec.js.map +1 -1
  103. package/dist/esm/tests/interfaces/records-write.spec.js +33 -0
  104. package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
  105. package/dist/esm/tests/sync/records-projection.spec.js +245 -0
  106. package/dist/esm/tests/sync/records-projection.spec.js.map +1 -0
  107. package/dist/esm/tests/test-suite.js +2 -0
  108. package/dist/esm/tests/test-suite.js.map +1 -1
  109. package/dist/esm/tests/utils/permission-scope.spec.js +66 -0
  110. package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -0
  111. package/dist/esm/tests/utils/test-data-generator.js +5 -2
  112. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  113. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  114. package/dist/types/src/core/constants.d.ts +13 -0
  115. package/dist/types/src/core/constants.d.ts.map +1 -1
  116. package/dist/types/src/core/dwn-error.d.ts +24 -1
  117. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  118. package/dist/types/src/core/grant-authorization.d.ts +1 -2
  119. package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
  120. package/dist/types/src/core/message.d.ts +41 -1
  121. package/dist/types/src/core/message.d.ts.map +1 -1
  122. package/dist/types/src/core/messages-grant-authorization.d.ts +36 -4
  123. package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
  124. package/dist/types/src/core/protocol-authorization.d.ts +12 -0
  125. package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
  126. package/dist/types/src/core/records-grant-authorization.d.ts +6 -0
  127. package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
  128. package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
  129. package/dist/types/src/handlers/messages-subscribe.d.ts +2 -1
  130. package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
  131. package/dist/types/src/handlers/messages-sync.d.ts +31 -0
  132. package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
  133. package/dist/types/src/handlers/protocols-configure.d.ts +3 -0
  134. package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
  135. package/dist/types/src/handlers/records-count.d.ts +4 -0
  136. package/dist/types/src/handlers/records-count.d.ts.map +1 -1
  137. package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
  138. package/dist/types/src/handlers/records-query.d.ts +4 -0
  139. package/dist/types/src/handlers/records-query.d.ts.map +1 -1
  140. package/dist/types/src/handlers/records-read.d.ts.map +1 -1
  141. package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
  142. package/dist/types/src/handlers/records-write.d.ts +1 -0
  143. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  144. package/dist/types/src/index.d.ts +6 -2
  145. package/dist/types/src/index.d.ts.map +1 -1
  146. package/dist/types/src/interfaces/messages-read.d.ts +1 -1
  147. package/dist/types/src/interfaces/messages-read.d.ts.map +1 -1
  148. package/dist/types/src/interfaces/messages-subscribe.d.ts +1 -1
  149. package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
  150. package/dist/types/src/interfaces/messages-sync.d.ts +4 -1
  151. package/dist/types/src/interfaces/messages-sync.d.ts.map +1 -1
  152. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  153. package/dist/types/src/interfaces/protocols-query.d.ts.map +1 -1
  154. package/dist/types/src/interfaces/records-count.d.ts +1 -0
  155. package/dist/types/src/interfaces/records-count.d.ts.map +1 -1
  156. package/dist/types/src/interfaces/records-delete.d.ts.map +1 -1
  157. package/dist/types/src/interfaces/records-query.d.ts +1 -0
  158. package/dist/types/src/interfaces/records-query.d.ts.map +1 -1
  159. package/dist/types/src/interfaces/records-read.d.ts.map +1 -1
  160. package/dist/types/src/interfaces/records-subscribe.d.ts +1 -0
  161. package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
  162. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  163. package/dist/types/src/protocols/permissions.d.ts +2 -0
  164. package/dist/types/src/protocols/permissions.d.ts.map +1 -1
  165. package/dist/types/src/sync/records-projection.d.ts +98 -0
  166. package/dist/types/src/sync/records-projection.d.ts.map +1 -0
  167. package/dist/types/src/types/message-types.d.ts +1 -0
  168. package/dist/types/src/types/message-types.d.ts.map +1 -1
  169. package/dist/types/src/types/messages-types.d.ts +21 -3
  170. package/dist/types/src/types/messages-types.d.ts.map +1 -1
  171. package/dist/types/src/types/permission-types.d.ts +4 -0
  172. package/dist/types/src/types/permission-types.d.ts.map +1 -1
  173. package/dist/types/src/types/records-types.d.ts +4 -0
  174. package/dist/types/src/types/records-types.d.ts.map +1 -1
  175. package/dist/types/src/types/subscriptions.d.ts +18 -3
  176. package/dist/types/src/types/subscriptions.d.ts.map +1 -1
  177. package/dist/types/src/utils/permission-scope.d.ts +29 -0
  178. package/dist/types/src/utils/permission-scope.d.ts.map +1 -0
  179. package/dist/types/tests/core/records-grant-authorization.spec.d.ts +2 -0
  180. package/dist/types/tests/core/records-grant-authorization.spec.d.ts.map +1 -0
  181. package/dist/types/tests/features/permissions.spec.d.ts.map +1 -1
  182. package/dist/types/tests/handlers/messages-read.spec.d.ts.map +1 -1
  183. package/dist/types/tests/handlers/messages-subscribe.spec.d.ts.map +1 -1
  184. package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
  185. package/dist/types/tests/handlers/protocols-configure.spec.d.ts.map +1 -1
  186. package/dist/types/tests/handlers/records-count.spec.d.ts.map +1 -1
  187. package/dist/types/tests/handlers/records-query.spec.d.ts.map +1 -1
  188. package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
  189. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  190. package/dist/types/tests/sync/records-projection.spec.d.ts +2 -0
  191. package/dist/types/tests/sync/records-projection.spec.d.ts.map +1 -0
  192. package/dist/types/tests/test-suite.d.ts.map +1 -1
  193. package/dist/types/tests/utils/permission-scope.spec.d.ts +2 -0
  194. package/dist/types/tests/utils/permission-scope.spec.d.ts.map +1 -0
  195. package/dist/types/tests/utils/test-data-generator.d.ts +5 -2
  196. package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
  197. package/package.json +1 -1
  198. package/src/core/constants.ts +24 -0
  199. package/src/core/dwn-error.ts +24 -1
  200. package/src/core/grant-authorization.ts +7 -5
  201. package/src/core/message.ts +153 -6
  202. package/src/core/messages-grant-authorization.ts +282 -70
  203. package/src/core/protocol-authorization.ts +130 -0
  204. package/src/core/records-grant-authorization.ts +64 -21
  205. package/src/handlers/messages-read.ts +7 -5
  206. package/src/handlers/messages-subscribe.ts +149 -9
  207. package/src/handlers/messages-sync.ts +593 -102
  208. package/src/handlers/protocols-configure.ts +103 -2
  209. package/src/handlers/records-count.ts +33 -0
  210. package/src/handlers/records-delete.ts +3 -2
  211. package/src/handlers/records-query.ts +33 -0
  212. package/src/handlers/records-read.ts +3 -2
  213. package/src/handlers/records-subscribe.ts +34 -0
  214. package/src/handlers/records-write.ts +62 -11
  215. package/src/index.ts +7 -3
  216. package/src/interfaces/messages-read.ts +8 -5
  217. package/src/interfaces/messages-subscribe.ts +12 -9
  218. package/src/interfaces/messages-sync.ts +33 -12
  219. package/src/interfaces/protocols-configure.ts +8 -4
  220. package/src/interfaces/protocols-query.ts +13 -9
  221. package/src/interfaces/records-count.ts +7 -0
  222. package/src/interfaces/records-delete.ts +9 -5
  223. package/src/interfaces/records-query.ts +7 -0
  224. package/src/interfaces/records-read.ts +6 -3
  225. package/src/interfaces/records-subscribe.ts +7 -0
  226. package/src/interfaces/records-write.ts +25 -17
  227. package/src/protocols/permissions.ts +47 -9
  228. package/src/sync/records-projection.ts +328 -0
  229. package/src/types/message-types.ts +1 -0
  230. package/src/types/messages-types.ts +23 -3
  231. package/src/types/permission-types.ts +5 -1
  232. package/src/types/records-types.ts +5 -1
  233. package/src/types/subscriptions.ts +19 -3
  234. package/src/utils/permission-scope.ts +55 -0
@@ -3,11 +3,14 @@ import type { MessagesPermissionScope } from '../types/permission-types.js';
3
3
  import type { MessageStore } from '../types/message-store.js';
4
4
  import type { PermissionGrant } from '../protocols/permission-grant.js';
5
5
  import type { ProtocolsConfigureMessage } from '../types/protocols-types.js';
6
+ import type { ProtocolScope } from '../utils/permission-scope.js';
6
7
  import type { DataEncodedRecordsWriteMessage, RecordsDeleteMessage, RecordsWriteMessage } from '../types/records-types.js';
7
8
  import type { MessagesReadMessage, MessagesSubscribeMessage, MessagesSyncMessage } from '../types/messages-types.js';
8
9
 
9
10
  import { DwnInterfaceName } from '../enums/dwn-interface-method.js';
10
11
  import { GrantAuthorization } from './grant-authorization.js';
12
+ import { isRecordsPrimaryProjectionExcludedProtocol } from './constants.js';
13
+ import { PermissionScopeMatcher } from '../utils/permission-scope.js';
11
14
  import { PermissionsProtocol } from '../protocols/permissions.js';
12
15
  import { Records } from '../utils/records.js';
13
16
  import { RecordsWrite } from '../interfaces/records-write.js';
@@ -15,6 +18,16 @@ import { DwnError, DwnErrorCode } from './dwn-error.js';
15
18
 
16
19
  export class MessagesGrantAuthorization {
17
20
 
21
+ public static async fetchPermissionGrants(
22
+ tenant: string,
23
+ messageStore: MessageStore,
24
+ permissionGrantIds: string[]
25
+ ): Promise<PermissionGrant[]> {
26
+ return Promise.all(
27
+ permissionGrantIds.map(permissionGrantId => PermissionsProtocol.fetchGrant(tenant, messageStore, permissionGrantId))
28
+ );
29
+ }
30
+
18
31
  /**
19
32
  * Authorizes a MessagesReadMessage using the given permission grant.
20
33
  * @param messageStore Used to check if the given grant has been revoked; and to fetch related RecordsWrites if needed.
@@ -24,23 +37,29 @@ export class MessagesGrantAuthorization {
24
37
  messageToRead: GenericMessage,
25
38
  expectedGrantor: string,
26
39
  expectedGrantee: string,
27
- permissionGrant: PermissionGrant,
40
+ permissionGrants: PermissionGrant[],
28
41
  messageStore: MessageStore,
29
42
  }): Promise<void> {
30
43
  const {
31
- messagesReadMessage, messageToRead, expectedGrantor, expectedGrantee, permissionGrant, messageStore
44
+ messagesReadMessage, messageToRead, expectedGrantor, expectedGrantee, permissionGrants, messageStore
32
45
  } = input;
33
46
 
34
- await GrantAuthorization.performBaseValidation({
47
+ await MessagesGrantAuthorization.performBaseValidationForGrantSet({
35
48
  incomingMessage: messagesReadMessage,
36
49
  expectedGrantor,
37
50
  expectedGrantee,
38
- permissionGrant,
51
+ permissionGrants,
39
52
  messageStore
40
53
  });
41
54
 
42
- const scope = permissionGrant.scope as MessagesPermissionScope;
43
- await MessagesGrantAuthorization.verifyScope(expectedGrantor, messageToRead, scope, messageStore);
55
+ for (const permissionGrant of permissionGrants) {
56
+ const scope = permissionGrant.scope as MessagesPermissionScope;
57
+ if (await MessagesGrantAuthorization.isScopeAuthorized(expectedGrantor, messageToRead, scope, messageStore)) {
58
+ return;
59
+ }
60
+ }
61
+
62
+ throw new DwnError(DwnErrorCode.MessagesReadVerifyScopeFailed, 'record message failed scope authorization');
44
63
  }
45
64
 
46
65
  /**
@@ -51,98 +70,291 @@ export class MessagesGrantAuthorization {
51
70
  incomingMessage: MessagesSubscribeMessage | MessagesSyncMessage,
52
71
  expectedGrantor: string,
53
72
  expectedGrantee: string,
54
- permissionGrant: PermissionGrant,
73
+ permissionGrants: PermissionGrant[],
55
74
  messageStore: MessageStore,
56
75
  }): Promise<void> {
57
76
  const {
58
- incomingMessage, expectedGrantor, expectedGrantee, permissionGrant, messageStore
77
+ incomingMessage, expectedGrantor, expectedGrantee, permissionGrants, messageStore
59
78
  } = input;
60
79
 
61
- await GrantAuthorization.performBaseValidation({
80
+ await MessagesGrantAuthorization.performBaseValidationForGrantSet({
62
81
  incomingMessage,
63
82
  expectedGrantor,
64
83
  expectedGrantee,
65
- permissionGrant,
84
+ permissionGrants,
66
85
  messageStore
67
86
  });
68
87
 
69
- // if the grant is scoped to a specific protocol, ensure that the message targets that protocol
70
- if (PermissionsProtocol.hasProtocolScope(permissionGrant.scope)) {
71
- const scopedProtocol = permissionGrant.scope.protocol;
72
-
73
- // MessagesSync uses a direct `protocol` field on the descriptor
74
- if ('action' in incomingMessage.descriptor) {
75
- const syncMessage = incomingMessage as MessagesSyncMessage;
76
- if (syncMessage.descriptor.protocol !== scopedProtocol) {
77
- throw new DwnError(
78
- DwnErrorCode.MessagesGrantAuthorizationMismatchedProtocol,
79
- `The protocol ${syncMessage.descriptor.protocol} does not match the scoped protocol ${scopedProtocol}`
80
- );
81
- }
82
- } else {
83
- // MessagesSubscribe uses filters
84
- const filteredMessage = incomingMessage as MessagesSubscribeMessage;
85
- for (const filter of filteredMessage.descriptor.filters) {
86
- if (filter.protocol !== scopedProtocol) {
87
- throw new DwnError(
88
- DwnErrorCode.MessagesGrantAuthorizationMismatchedProtocol,
89
- `The protocol ${filter.protocol} does not match the scoped protocol ${scopedProtocol}`
90
- );
91
- }
92
- }
88
+ const scopes = permissionGrants.map(permissionGrant => permissionGrant.scope as MessagesPermissionScope);
89
+
90
+ if ('action' in incomingMessage.descriptor) {
91
+ MessagesGrantAuthorization.authorizeSyncScope(incomingMessage as MessagesSyncMessage, scopes);
92
+ return;
93
+ }
94
+
95
+ MessagesGrantAuthorization.authorizeSubscribeScope(incomingMessage as MessagesSubscribeMessage, scopes);
96
+ }
97
+
98
+ private static authorizeSyncScope(
99
+ syncMessage: MessagesSyncMessage,
100
+ scopes: MessagesPermissionScope[]
101
+ ): void {
102
+ const { projectionScopes, protocol } = syncMessage.descriptor;
103
+
104
+ if (projectionScopes === undefined) {
105
+ MessagesGrantAuthorization.authorizeProtocolSyncScope(scopes, protocol);
106
+ return;
107
+ }
108
+
109
+ MessagesGrantAuthorization.authorizeProjectionScopes(scopes, projectionScopes);
110
+ }
111
+
112
+ private static authorizeProtocolSyncScope(
113
+ scopes: MessagesPermissionScope[],
114
+ protocol: string | undefined
115
+ ): void {
116
+ if (isRecordsPrimaryProjectionExcludedProtocol(protocol)) {
117
+ throw new DwnError(
118
+ DwnErrorCode.MessagesGrantAuthorizationProtocolSyncInfrastructureProtocol,
119
+ `Protocol-scoped MessagesSync cannot authorize infrastructure protocol ${protocol}`
120
+ );
121
+ }
122
+
123
+ if (!MessagesGrantAuthorization.someScopeMatches(scopes, { protocol })) {
124
+ throw new DwnError(
125
+ DwnErrorCode.MessagesGrantAuthorizationMismatchedProtocol,
126
+ `No permission grant scope matches protocol ${protocol}`
127
+ );
128
+ }
129
+ }
130
+
131
+ private static authorizeProjectionScopes(
132
+ scopes: MessagesPermissionScope[],
133
+ projectionScopes: ProtocolScope[],
134
+ ): void {
135
+ for (const projectionScope of projectionScopes) {
136
+ if (isRecordsPrimaryProjectionExcludedProtocol(projectionScope.protocol)) {
137
+ throw new DwnError(
138
+ DwnErrorCode.MessagesGrantAuthorizationProjectionInfrastructureProtocol,
139
+ `Projected MessagesSync cannot authorize infrastructure protocol ${projectionScope.protocol}`
140
+ );
141
+ }
142
+
143
+ if (MessagesGrantAuthorization.someScopeMatches(scopes, projectionScope)) {
144
+ continue;
145
+ }
146
+
147
+ throw new DwnError(
148
+ DwnErrorCode.MessagesGrantAuthorizationProjectionScopeMismatch,
149
+ `No permission grant scope matches projection scope ${JSON.stringify(projectionScope)}`
150
+ );
151
+ }
152
+ }
153
+
154
+ private static authorizeSubscribeScope(
155
+ subscribeMessage: MessagesSubscribeMessage,
156
+ scopes: MessagesPermissionScope[]
157
+ ): void {
158
+ const { filters } = subscribeMessage.descriptor;
159
+
160
+ if (filters.length === 0 && !MessagesGrantAuthorization.hasUnscopedGrant(scopes)) {
161
+ throw new DwnError(
162
+ DwnErrorCode.MessagesGrantAuthorizationUnfilteredSubscribeProtocolScope,
163
+ `A protocol-scoped grant cannot authorize an unfiltered subscription`
164
+ );
165
+ }
166
+
167
+ for (const filter of filters) {
168
+ if (MessagesGrantAuthorization.someScopeMatches(scopes, { protocol: filter.protocol })) {
169
+ continue;
93
170
  }
171
+
172
+ throw new DwnError(
173
+ DwnErrorCode.MessagesGrantAuthorizationSubscribeProtocolMismatch,
174
+ `No permission grant scope matches protocol ${filter.protocol}`
175
+ );
94
176
  }
95
177
  }
96
178
 
179
+ private static someScopeMatches(scopes: MessagesPermissionScope[], target: ProtocolScope): boolean {
180
+ return scopes.some(scope => PermissionScopeMatcher.matches(scope, target));
181
+ }
182
+
183
+ private static hasUnscopedGrant(scopes: MessagesPermissionScope[]): boolean {
184
+ return scopes.some(scope => scope.protocol === undefined);
185
+ }
186
+
97
187
  /**
98
- * Verifies the given record against the scope of the given grant.
188
+ * Revalidates an already-open delegated MessagesSubscribe grant set at event
189
+ * delivery time. Subscription messages are signed at open time, but grant
190
+ * expiry and revocation must stop future delivery after the grant set becomes
191
+ * invalid.
99
192
  */
100
- private static async verifyScope(
193
+ public static async authorizeSubscribeDelivery(input: {
194
+ messagesSubscribeMessage: MessagesSubscribeMessage,
195
+ expectedGrantor: string,
196
+ expectedGrantee: string,
197
+ permissionGrants: PermissionGrant[],
198
+ messageStore: MessageStore,
199
+ deliveryTimestamp: string,
200
+ }): Promise<void> {
201
+ const {
202
+ messagesSubscribeMessage,
203
+ expectedGrantor,
204
+ expectedGrantee,
205
+ permissionGrants,
206
+ messageStore,
207
+ deliveryTimestamp,
208
+ } = input;
209
+
210
+ const deliveryMessage: MessagesSubscribeMessage = {
211
+ ...messagesSubscribeMessage,
212
+ descriptor: {
213
+ ...messagesSubscribeMessage.descriptor,
214
+ messageTimestamp: deliveryTimestamp,
215
+ },
216
+ };
217
+
218
+ await MessagesGrantAuthorization.authorizeSubscribeOrSync({
219
+ incomingMessage: deliveryMessage,
220
+ expectedGrantor,
221
+ expectedGrantee,
222
+ permissionGrants,
223
+ messageStore,
224
+ });
225
+ }
226
+
227
+ /**
228
+ * Performs base validation on every invoked grant. The grant set is all-or-nothing:
229
+ * unresolved, revoked, expired, or interface/method-mismatched grants fail the request.
230
+ */
231
+ private static async performBaseValidationForGrantSet(input: {
232
+ incomingMessage: MessagesReadMessage | MessagesSubscribeMessage | MessagesSyncMessage,
233
+ expectedGrantor: string,
234
+ expectedGrantee: string,
235
+ permissionGrants: PermissionGrant[],
236
+ messageStore: MessageStore,
237
+ }): Promise<void> {
238
+ const {
239
+ incomingMessage, expectedGrantor, expectedGrantee, permissionGrants, messageStore
240
+ } = input;
241
+
242
+ for (const permissionGrant of permissionGrants) {
243
+ await GrantAuthorization.performBaseValidation({
244
+ incomingMessage,
245
+ expectedGrantor,
246
+ expectedGrantee,
247
+ permissionGrant,
248
+ messageStore
249
+ });
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Determines whether the given record is inside a grant scope.
255
+ */
256
+ private static async isScopeAuthorized(
101
257
  tenant: string,
102
258
  messageToGet: GenericMessage,
103
259
  incomingScope: MessagesPermissionScope,
104
260
  messageStore: MessageStore,
105
- ): Promise<void> {
261
+ ): Promise<boolean> {
106
262
  if (incomingScope.protocol === undefined) {
107
- // if no protocol is specified in the scope, then the grant is for all records
108
- return;
263
+ return true;
109
264
  }
110
265
 
111
266
  if (messageToGet.descriptor.interface === DwnInterfaceName.Records) {
112
- // if the message is a Records interface message, get the RecordsWrite message associated with the record
113
- const recordsMessage = messageToGet as RecordsWriteMessage | RecordsDeleteMessage;
114
- const recordsWriteMessage = Records.isRecordsWrite(recordsMessage) ? recordsMessage :
115
- await RecordsWrite.fetchNewestRecordsWrite(messageStore, tenant, recordsMessage.descriptor.recordId);
267
+ return MessagesGrantAuthorization.isRecordsMessageScopeAuthorized(
268
+ tenant,
269
+ messageToGet as RecordsWriteMessage | RecordsDeleteMessage,
270
+ incomingScope,
271
+ messageStore
272
+ );
273
+ }
116
274
 
117
- if (recordsWriteMessage.descriptor.protocol === incomingScope.protocol) {
118
- // the record protocol matches the incoming scope protocol
119
- return;
120
- }
275
+ if (messageToGet.descriptor.interface === DwnInterfaceName.Protocols) {
276
+ return MessagesGrantAuthorization.isProtocolsConfigureScopeAuthorized(
277
+ messageToGet as ProtocolsConfigureMessage,
278
+ incomingScope
279
+ );
280
+ }
121
281
 
122
- // we check if the protocol is the internal PermissionsProtocol for further validation
123
- if (recordsWriteMessage.descriptor.protocol === PermissionsProtocol.uri) {
124
- // get the permission scope from the permission message
125
- const permissionScope = await PermissionsProtocol.getScopeFromPermissionRecord(
126
- tenant,
127
- messageStore,
128
- recordsWriteMessage as DataEncodedRecordsWriteMessage
129
- );
282
+ return false;
283
+ }
130
284
 
131
- if (PermissionsProtocol.hasProtocolScope(permissionScope) && permissionScope.protocol === incomingScope.protocol) {
132
- // the permissions record scoped protocol matches the incoming scope protocol
133
- return;
134
- }
135
- }
136
- } else if (messageToGet.descriptor.interface === DwnInterfaceName.Protocols) {
137
- // if the message is a protocol message, it must be a `ProtocolConfigure` message
138
- const protocolsConfigureMessage = messageToGet as ProtocolsConfigureMessage;
139
- const configureProtocol = protocolsConfigureMessage.descriptor.definition.protocol;
140
- if (configureProtocol === incomingScope.protocol) {
141
- // the configured protocol matches the incoming scope protocol
142
- return;
143
- }
285
+ private static async isRecordsMessageScopeAuthorized(
286
+ tenant: string,
287
+ recordsMessage: RecordsWriteMessage | RecordsDeleteMessage,
288
+ incomingScope: MessagesPermissionScope,
289
+ messageStore: MessageStore,
290
+ ): Promise<boolean> {
291
+ const recordsWriteMessage = await MessagesGrantAuthorization.getAssociatedRecordsWrite(
292
+ tenant,
293
+ recordsMessage,
294
+ messageStore
295
+ );
296
+
297
+ if (recordsWriteMessage.descriptor.protocol === PermissionsProtocol.uri) {
298
+ return MessagesGrantAuthorization.isPermissionRecordScopeAuthorized(
299
+ tenant,
300
+ recordsWriteMessage,
301
+ incomingScope,
302
+ messageStore
303
+ );
144
304
  }
145
305
 
146
- throw new DwnError(DwnErrorCode.MessagesReadVerifyScopeFailed, 'record message failed scope authorization');
306
+ return PermissionScopeMatcher.matches(incomingScope, MessagesGrantAuthorization.getRecordsScopeTarget(recordsWriteMessage));
307
+ }
308
+
309
+ private static async isPermissionRecordScopeAuthorized(
310
+ tenant: string,
311
+ recordsWriteMessage: RecordsWriteMessage,
312
+ incomingScope: MessagesPermissionScope,
313
+ messageStore: MessageStore,
314
+ ): Promise<boolean> {
315
+ if (MessagesGrantAuthorization.isSubtreeScope(incomingScope)) {
316
+ return false;
317
+ }
318
+
319
+ const permissionScope = await PermissionsProtocol.getScopeFromPermissionRecord(
320
+ tenant,
321
+ messageStore,
322
+ recordsWriteMessage as DataEncodedRecordsWriteMessage
323
+ );
324
+
325
+ return PermissionsProtocol.hasProtocolScope(permissionScope)
326
+ && PermissionScopeMatcher.matches(incomingScope, permissionScope);
327
+ }
328
+
329
+ private static isProtocolsConfigureScopeAuthorized(
330
+ protocolsConfigureMessage: ProtocolsConfigureMessage,
331
+ incomingScope: MessagesPermissionScope
332
+ ): boolean {
333
+ // A delegate with any Messages.Read grant inside a protocol needs that
334
+ // protocol definition to interpret and validate the records it can read.
335
+ return incomingScope.protocol !== undefined &&
336
+ incomingScope.protocol === protocolsConfigureMessage.descriptor.definition.protocol;
337
+ }
338
+
339
+ private static async getAssociatedRecordsWrite(
340
+ tenant: string,
341
+ recordsMessage: RecordsWriteMessage | RecordsDeleteMessage,
342
+ messageStore: MessageStore,
343
+ ): Promise<RecordsWriteMessage> {
344
+ if (Records.isRecordsWrite(recordsMessage)) {
345
+ return recordsMessage;
346
+ }
347
+
348
+ return RecordsWrite.fetchNewestRecordsWrite(messageStore, tenant, recordsMessage.descriptor.recordId);
349
+ }
350
+
351
+ private static getRecordsScopeTarget(recordsWriteMessage: RecordsWriteMessage): ProtocolScope {
352
+ const { protocol, protocolPath } = recordsWriteMessage.descriptor;
353
+ const { contextId } = recordsWriteMessage;
354
+ return { protocol, protocolPath, contextId };
355
+ }
356
+
357
+ private static isSubtreeScope(scope: MessagesPermissionScope): boolean {
358
+ return scope.protocolPath !== undefined || scope.contextId !== undefined;
147
359
  }
148
- }
360
+ }
@@ -14,6 +14,7 @@ import { getRuleSetAtPath } from '../utils/protocols.js';
14
14
  import { SortDirection } from '../types/query-types.js';
15
15
  import { DwnError, DwnErrorCode } from './dwn-error.js';
16
16
  import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
17
+ import { ProtocolAction, ProtocolActor } from '../types/protocols-types.js';
17
18
 
18
19
  import { authorizeAgainstAllowedActions, verifyInvokedRole } from './protocol-authorization-action.js';
19
20
  import { constructRecordChain, fetchInitialWrite, getGoverningTimestamp } from './record-chain.js';
@@ -113,6 +114,52 @@ export class ProtocolAuthorization {
113
114
  await verifyRecordLimit(tenant, incomingMessage, ruleSet, messageStore);
114
115
  }
115
116
 
117
+ /**
118
+ * Revalidates a stored initial write against the protocol definition that governed its creation timestamp.
119
+ *
120
+ * This is used only for destructive config-history repair, so it deliberately validates config-owned
121
+ * structure and avoids live dependency checks. Missing grant, role, or parent records must not cause
122
+ * an already-admitted record to be hard-purged.
123
+ */
124
+ public static async validateStoredInitialWrite(
125
+ tenant: string,
126
+ incomingMessage: RecordsWrite,
127
+ messageStore: MessageStore,
128
+ coreProtocols?: CoreProtocolRegistry,
129
+ ): Promise<void> {
130
+ await ProtocolAuthorization.verifyStoredInitialWrite(incomingMessage);
131
+
132
+ const governingTimestamp = incomingMessage.message.descriptor.messageTimestamp;
133
+ const protocolDefinition = await ProtocolAuthorization.fetchProtocolDefinition(
134
+ tenant,
135
+ incomingMessage.message.descriptor.protocol,
136
+ messageStore,
137
+ governingTimestamp,
138
+ coreProtocols,
139
+ );
140
+
141
+ const boundFetchDefinition = ProtocolAuthorization.createBoundFetchDefinition(coreProtocols);
142
+
143
+ await verifyTypeWithComposition(
144
+ tenant, incomingMessage.message, protocolDefinition, messageStore, boundFetchDefinition, governingTimestamp
145
+ );
146
+
147
+ const ruleSet = ProtocolAuthorization.getRuleSet(
148
+ incomingMessage.message.descriptor.protocolPath,
149
+ protocolDefinition,
150
+ );
151
+
152
+ ProtocolAuthorization.verifyStoredInitialWriteRoleRecipientIfNeeded(incomingMessage, ruleSet);
153
+ verifySizeLimit(incomingMessage, ruleSet);
154
+ verifyTagsIfNeeded(incomingMessage, ruleSet);
155
+ await verifySquashEligibility(incomingMessage, ruleSet);
156
+ ProtocolAuthorization.verifyStoredInitialWriteCreateAction(tenant, incomingMessage, ruleSet);
157
+
158
+ // `verifyRecordLimit()` is not replayed here. It is stateful and counts the present
159
+ // latest live set, which would incorrectly reject the record being revalidated.
160
+ // Inbound writes continue to enforce record limits at admission time.
161
+ }
162
+
116
163
  /**
117
164
  * Performs protocol-based authorization against the incoming RecordsWrite message.
118
165
  * @throws {Error} if authorization fails.
@@ -444,4 +491,87 @@ export class ProtocolAuthorization {
444
491
  return ruleSet;
445
492
  }
446
493
 
494
+ private static async verifyStoredInitialWrite(incomingMessage: RecordsWrite): Promise<void> {
495
+ if (await incomingMessage.isInitialWrite()) {
496
+ return;
497
+ }
498
+
499
+ throw new DwnError(
500
+ DwnErrorCode.ProtocolAuthorizationInitialWriteRevalidationNotInitial,
501
+ 'stored write revalidation only supports initial RecordsWrite messages'
502
+ );
503
+ }
504
+
505
+ private static verifyStoredInitialWriteRoleRecipientIfNeeded(
506
+ incomingMessage: RecordsWrite,
507
+ ruleSet: ProtocolRuleSet,
508
+ ): void {
509
+ if (ruleSet.$role !== true || incomingMessage.message.descriptor.recipient !== undefined) {
510
+ return;
511
+ }
512
+
513
+ throw new DwnError(
514
+ DwnErrorCode.ProtocolAuthorizationStoredInitialWriteRoleMissingRecipient,
515
+ 'role records must have a recipient'
516
+ );
517
+ }
518
+
519
+ private static verifyStoredInitialWriteCreateAction(
520
+ tenant: string,
521
+ incomingMessage: RecordsWrite,
522
+ ruleSet: ProtocolRuleSet,
523
+ ): void {
524
+ if (ProtocolAuthorization.isStoredInitialWriteDirectlyAuthorized(tenant, incomingMessage)) {
525
+ return;
526
+ }
527
+
528
+ const actions = incomingMessage.message.descriptor.squash === true
529
+ ? [ProtocolAction.Squash, ProtocolAction.Create]
530
+ : [ProtocolAction.Create];
531
+ const actionRules = ruleSet.$actions;
532
+ if (actionRules === undefined) {
533
+ throw new DwnError(
534
+ DwnErrorCode.ProtocolAuthorizationStoredInitialWriteActionRulesNotFound,
535
+ `no create action rule defined for stored RecordsWrite by author ${incomingMessage.author}`
536
+ );
537
+ }
538
+
539
+ const invokedRole = incomingMessage.signaturePayload?.protocolRole;
540
+ for (const actionRule of actionRules) {
541
+ if (!actionRule.can.some((allowedAction: string): boolean => actions.includes(allowedAction as ProtocolAction))) {
542
+ continue;
543
+ }
544
+
545
+ if (invokedRole !== undefined) {
546
+ if (actionRule.role === invokedRole) {
547
+ return;
548
+ }
549
+ continue;
550
+ }
551
+
552
+ if (actionRule.who === ProtocolActor.Anyone) {
553
+ return;
554
+ }
555
+
556
+ // Author/recipient-of rules depend on the parent chain. This repair path preserves
557
+ // instead of hard-purging when validity depends on mutable or missing dependency records.
558
+ if (actionRule.who === ProtocolActor.Author || actionRule.who === ProtocolActor.Recipient) {
559
+ return;
560
+ }
561
+ }
562
+
563
+ throw new DwnError(
564
+ DwnErrorCode.ProtocolAuthorizationStoredInitialWriteActionNotAllowed,
565
+ `stored RecordsWrite by author ${incomingMessage.author} is not allowed by the governing protocol config`
566
+ );
567
+ }
568
+
569
+ private static isStoredInitialWriteDirectlyAuthorized(tenant: string, incomingMessage: RecordsWrite): boolean {
570
+ return incomingMessage.owner !== undefined ||
571
+ incomingMessage.author === tenant ||
572
+ incomingMessage.isSignedByAuthorDelegate ||
573
+ incomingMessage.isSignedByOwnerDelegate ||
574
+ incomingMessage.signaturePayload?.permissionGrantId !== undefined;
575
+ }
576
+
447
577
  }