@enbox/dwn-sdk-js 0.3.7 → 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.
- package/dist/browser.mjs +8 -8
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +2591 -1435
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/constants.js +20 -0
- package/dist/esm/src/core/constants.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +24 -1
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/grant-authorization.js +4 -4
- package/dist/esm/src/core/grant-authorization.js.map +1 -1
- package/dist/esm/src/core/message.js +89 -4
- package/dist/esm/src/core/message.js.map +1 -1
- package/dist/esm/src/core/messages-grant-authorization.js +147 -55
- package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +76 -0
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/records-grant-authorization.js +40 -15
- package/dist/esm/src/core/records-grant-authorization.js.map +1 -1
- package/dist/esm/src/handlers/messages-read.js +5 -5
- package/dist/esm/src/handlers/messages-read.js.map +1 -1
- package/dist/esm/src/handlers/messages-subscribe.js +109 -7
- package/dist/esm/src/handlers/messages-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/messages-sync.js +341 -96
- package/dist/esm/src/handlers/messages-sync.js.map +1 -1
- package/dist/esm/src/handlers/protocols-configure.js +81 -2
- package/dist/esm/src/handlers/protocols-configure.js.map +1 -1
- package/dist/esm/src/handlers/records-count.js +30 -0
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-delete.js +3 -2
- package/dist/esm/src/handlers/records-delete.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +30 -0
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-read.js +3 -2
- package/dist/esm/src/handlers/records-read.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +31 -0
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +3 -2
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +2 -0
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/messages-read.js +6 -3
- package/dist/esm/src/interfaces/messages-read.js.map +1 -1
- package/dist/esm/src/interfaces/messages-subscribe.js +6 -3
- package/dist/esm/src/interfaces/messages-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/messages-sync.js +17 -3
- package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +5 -2
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-query.js +8 -4
- package/dist/esm/src/interfaces/protocols-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-count.js +5 -0
- package/dist/esm/src/interfaces/records-count.js.map +1 -1
- package/dist/esm/src/interfaces/records-delete.js +6 -2
- package/dist/esm/src/interfaces/records-delete.js.map +1 -1
- package/dist/esm/src/interfaces/records-query.js +5 -0
- package/dist/esm/src/interfaces/records-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-read.js +6 -3
- package/dist/esm/src/interfaces/records-read.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js +5 -0
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +6 -3
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/protocols/permissions.js +28 -7
- package/dist/esm/src/protocols/permissions.js.map +1 -1
- package/dist/esm/src/sync/records-projection.js +228 -0
- package/dist/esm/src/sync/records-projection.js.map +1 -0
- package/dist/esm/src/types/message-types.js.map +1 -1
- package/dist/esm/src/types/permission-types.js.map +1 -1
- package/dist/esm/src/utils/permission-scope.js +37 -0
- package/dist/esm/src/utils/permission-scope.js.map +1 -0
- package/dist/esm/tests/core/grant-authorization.spec.js +26 -3
- package/dist/esm/tests/core/grant-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/records-grant-authorization.spec.js +117 -0
- package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -0
- package/dist/esm/tests/features/permissions.spec.js +126 -0
- package/dist/esm/tests/features/permissions.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-read.spec.js +345 -12
- package/dist/esm/tests/handlers/messages-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-subscribe.spec.js +326 -9
- package/dist/esm/tests/handlers/messages-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/messages-sync.spec.js +1053 -7
- package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
- package/dist/esm/tests/handlers/protocols-configure.spec.js +361 -0
- package/dist/esm/tests/handlers/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +75 -2
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-query.spec.js +73 -0
- package/dist/esm/tests/handlers/records-query.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +75 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/messages-get.spec.js +107 -5
- package/dist/esm/tests/interfaces/messages-get.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/protocols-configure.spec.js +13 -0
- package/dist/esm/tests/interfaces/protocols-configure.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-delete.spec.js +12 -0
- package/dist/esm/tests/interfaces/records-delete.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-query.spec.js +10 -0
- package/dist/esm/tests/interfaces/records-query.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-subscribe.spec.js +10 -0
- package/dist/esm/tests/interfaces/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/interfaces/records-write.spec.js +33 -0
- package/dist/esm/tests/interfaces/records-write.spec.js.map +1 -1
- package/dist/esm/tests/sync/records-projection.spec.js +245 -0
- package/dist/esm/tests/sync/records-projection.spec.js.map +1 -0
- package/dist/esm/tests/test-suite.js +2 -0
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/esm/tests/utils/permission-scope.spec.js +66 -0
- package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -0
- package/dist/esm/tests/utils/test-data-generator.js +5 -2
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/constants.d.ts +13 -0
- package/dist/types/src/core/constants.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +24 -1
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/grant-authorization.d.ts +1 -2
- package/dist/types/src/core/grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/message.d.ts +41 -1
- package/dist/types/src/core/message.d.ts.map +1 -1
- package/dist/types/src/core/messages-grant-authorization.d.ts +36 -4
- package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts +12 -0
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/records-grant-authorization.d.ts +6 -0
- package/dist/types/src/core/records-grant-authorization.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-read.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts +2 -1
- package/dist/types/src/handlers/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/messages-sync.d.ts +31 -0
- package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
- package/dist/types/src/handlers/protocols-configure.d.ts +3 -0
- package/dist/types/src/handlers/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/handlers/records-count.d.ts +4 -0
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-delete.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +4 -0
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-read.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +6 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-read.d.ts +1 -1
- package/dist/types/src/interfaces/messages-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts +1 -1
- package/dist/types/src/interfaces/messages-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/messages-sync.d.ts +4 -1
- package/dist/types/src/interfaces/messages-sync.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-count.d.ts +1 -0
- package/dist/types/src/interfaces/records-count.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-delete.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-query.d.ts +1 -0
- package/dist/types/src/interfaces/records-query.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-read.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts +1 -0
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/protocols/permissions.d.ts +2 -0
- package/dist/types/src/protocols/permissions.d.ts.map +1 -1
- package/dist/types/src/sync/records-projection.d.ts +98 -0
- package/dist/types/src/sync/records-projection.d.ts.map +1 -0
- package/dist/types/src/types/message-types.d.ts +1 -0
- package/dist/types/src/types/message-types.d.ts.map +1 -1
- package/dist/types/src/types/messages-types.d.ts +21 -3
- package/dist/types/src/types/messages-types.d.ts.map +1 -1
- package/dist/types/src/types/permission-types.d.ts +4 -0
- package/dist/types/src/types/permission-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +4 -0
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/types/subscriptions.d.ts +18 -3
- package/dist/types/src/types/subscriptions.d.ts.map +1 -1
- package/dist/types/src/utils/permission-scope.d.ts +29 -0
- package/dist/types/src/utils/permission-scope.d.ts.map +1 -0
- package/dist/types/tests/core/records-grant-authorization.spec.d.ts +2 -0
- package/dist/types/tests/core/records-grant-authorization.spec.d.ts.map +1 -0
- package/dist/types/tests/features/permissions.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/protocols-configure.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-query.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/sync/records-projection.spec.d.ts +2 -0
- package/dist/types/tests/sync/records-projection.spec.d.ts.map +1 -0
- package/dist/types/tests/test-suite.d.ts.map +1 -1
- package/dist/types/tests/utils/permission-scope.spec.d.ts +2 -0
- package/dist/types/tests/utils/permission-scope.spec.d.ts.map +1 -0
- package/dist/types/tests/utils/test-data-generator.d.ts +5 -2
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/constants.ts +24 -0
- package/src/core/dwn-error.ts +24 -1
- package/src/core/grant-authorization.ts +7 -5
- package/src/core/message.ts +153 -6
- package/src/core/messages-grant-authorization.ts +282 -70
- package/src/core/protocol-authorization.ts +130 -0
- package/src/core/records-grant-authorization.ts +64 -21
- package/src/handlers/messages-read.ts +7 -5
- package/src/handlers/messages-subscribe.ts +149 -9
- package/src/handlers/messages-sync.ts +593 -102
- package/src/handlers/protocols-configure.ts +103 -2
- package/src/handlers/records-count.ts +33 -0
- package/src/handlers/records-delete.ts +3 -2
- package/src/handlers/records-query.ts +33 -0
- package/src/handlers/records-read.ts +3 -2
- package/src/handlers/records-subscribe.ts +34 -0
- package/src/handlers/records-write.ts +3 -2
- package/src/index.ts +7 -3
- package/src/interfaces/messages-read.ts +8 -5
- package/src/interfaces/messages-subscribe.ts +12 -9
- package/src/interfaces/messages-sync.ts +33 -12
- package/src/interfaces/protocols-configure.ts +8 -4
- package/src/interfaces/protocols-query.ts +13 -9
- package/src/interfaces/records-count.ts +7 -0
- package/src/interfaces/records-delete.ts +9 -5
- package/src/interfaces/records-query.ts +7 -0
- package/src/interfaces/records-read.ts +6 -3
- package/src/interfaces/records-subscribe.ts +7 -0
- package/src/interfaces/records-write.ts +25 -17
- package/src/protocols/permissions.ts +47 -9
- package/src/sync/records-projection.ts +328 -0
- package/src/types/message-types.ts +1 -0
- package/src/types/messages-types.ts +23 -3
- package/src/types/permission-types.ts +5 -1
- package/src/types/records-types.ts +5 -1
- package/src/types/subscriptions.ts +19 -3
- package/src/utils/permission-scope.ts +55 -0
|
@@ -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
|
}
|
|
@@ -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
|
|
94
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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 !==
|
|
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
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
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(
|
|
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
|
-
}
|
|
80
|
-
|
|
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
|
-
|
|
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
|
}
|