@enbox/dwn-sdk-js 0.4.16 → 0.4.18
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 +1 -1
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/generated/precompiled-validators.js +978 -840
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/json-schemas/definitions.json +5 -0
- package/dist/esm/src/core/dwn-constant.js +2 -2
- package/dist/esm/src/core/dwn-error.js +0 -1
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-validation.js +1 -24
- package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +1 -5
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/validation-state-reader.js +2 -3
- package/dist/esm/src/core/validation-state-reader.js.map +1 -1
- package/dist/esm/src/handlers/records-collection.js +200 -0
- package/dist/esm/src/handlers/records-collection.js.map +1 -0
- package/dist/esm/src/handlers/records-count.js +26 -195
- package/dist/esm/src/handlers/records-count.js.map +1 -1
- package/dist/esm/src/handlers/records-query.js +19 -373
- package/dist/esm/src/handlers/records-query.js.map +1 -1
- package/dist/esm/src/handlers/records-subscribe.js +21 -338
- package/dist/esm/src/handlers/records-subscribe.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +8 -26
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/index.js +1 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +4 -9
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/records-count.js +1 -1
- package/dist/esm/src/interfaces/records-count.js.map +1 -1
- package/dist/esm/src/interfaces/records-query.js +1 -1
- package/dist/esm/src/interfaces/records-query.js.map +1 -1
- package/dist/esm/src/interfaces/records-subscribe.js +3 -1
- package/dist/esm/src/interfaces/records-subscribe.js.map +1 -1
- package/dist/esm/src/store/index-level.js +210 -21
- package/dist/esm/src/store/index-level.js.map +1 -1
- package/dist/esm/src/store/message-store-level.js +192 -21
- package/dist/esm/src/store/message-store-level.js.map +1 -1
- package/dist/esm/src/store/storage-controller.js +1 -3
- package/dist/esm/src/store/storage-controller.js.map +1 -1
- package/dist/esm/src/types/protocols-types.js +0 -10
- package/dist/esm/src/types/protocols-types.js.map +1 -1
- package/dist/esm/src/utils/filter.js +35 -1
- package/dist/esm/src/utils/filter.js.map +1 -1
- package/dist/esm/src/utils/grant-key-coverage.js +4 -6
- package/dist/esm/src/utils/grant-key-coverage.js.map +1 -1
- package/dist/esm/src/utils/messages.js +4 -14
- package/dist/esm/src/utils/messages.js.map +1 -1
- package/dist/esm/src/utils/permission-scope.js +3 -9
- package/dist/esm/src/utils/permission-scope.js.map +1 -1
- package/dist/esm/src/utils/record-limit-occupancy.js +73 -202
- package/dist/esm/src/utils/record-limit-occupancy.js.map +1 -1
- package/dist/esm/src/utils/records.js +19 -52
- package/dist/esm/src/utils/records.js.map +1 -1
- package/dist/esm/tests/core/records-grant-authorization.spec.js +13 -0
- package/dist/esm/tests/core/records-grant-authorization.spec.js.map +1 -1
- package/dist/esm/tests/core/replication-replay-property.spec.js +2 -2
- package/dist/esm/tests/core/replication-replay-property.spec.js.map +1 -1
- package/dist/esm/tests/core/validation-read-closure.spec.js +1 -1
- package/dist/esm/tests/core/validation-read-closure.spec.js.map +1 -1
- package/dist/esm/tests/dwn.spec.js +2 -2
- package/dist/esm/tests/dwn.spec.js.map +1 -1
- package/dist/esm/tests/features/records-delivery.spec.js +2 -5
- package/dist/esm/tests/features/records-delivery.spec.js.map +1 -1
- package/dist/esm/tests/features/records-nested-query-scope.spec.js +149 -70
- package/dist/esm/tests/features/records-nested-query-scope.spec.js.map +1 -1
- package/dist/esm/tests/features/records-record-limit.spec.js +380 -95
- package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
- package/dist/esm/tests/fuzz/filter.fuzz.spec.js +19 -4
- package/dist/esm/tests/fuzz/filter.fuzz.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-count.spec.js +52 -1
- package/dist/esm/tests/handlers/records-count.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js +76 -1
- package/dist/esm/tests/handlers/records-subscribe.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +57 -3
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/store/index-level.spec.js +208 -1
- package/dist/esm/tests/store/index-level.spec.js.map +1 -1
- package/dist/esm/tests/store/message-store-cross-context.spec.js +46 -0
- package/dist/esm/tests/store/message-store-cross-context.spec.js.map +1 -0
- package/dist/esm/tests/store/message-store-level.spec.js +76 -1
- package/dist/esm/tests/store/message-store-level.spec.js.map +1 -1
- package/dist/esm/tests/store/message-store.spec.js +81 -0
- package/dist/esm/tests/store/message-store.spec.js.map +1 -1
- package/dist/esm/tests/utils/filters.spec.js +25 -4
- package/dist/esm/tests/utils/filters.spec.js.map +1 -1
- package/dist/esm/tests/utils/permission-scope.spec.js +4 -0
- package/dist/esm/tests/utils/permission-scope.spec.js.map +1 -1
- package/dist/esm/tests/utils/records.spec.js +12 -0
- package/dist/esm/tests/utils/records.spec.js.map +1 -1
- package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js +3 -1
- package/dist/esm/tests/validation/json-schemas/records/records-write.spec.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/dwn-constant.d.ts +2 -2
- package/dist/types/src/core/dwn-error.d.ts +0 -1
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-validation.d.ts +0 -10
- package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/validation-state-reader.d.ts.map +1 -1
- package/dist/types/src/handlers/records-collection.d.ts +32 -0
- package/dist/types/src/handlers/records-collection.d.ts.map +1 -0
- package/dist/types/src/handlers/records-count.d.ts +0 -34
- package/dist/types/src/handlers/records-count.d.ts.map +1 -1
- package/dist/types/src/handlers/records-query.d.ts +0 -61
- package/dist/types/src/handlers/records-query.d.ts.map +1 -1
- package/dist/types/src/handlers/records-subscribe.d.ts +1 -37
- package/dist/types/src/handlers/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts +3 -0
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-subscribe.d.ts.map +1 -1
- package/dist/types/src/store/index-level.d.ts +61 -0
- package/dist/types/src/store/index-level.d.ts.map +1 -1
- package/dist/types/src/store/message-store-level.d.ts +24 -6
- package/dist/types/src/store/message-store-level.d.ts.map +1 -1
- package/dist/types/src/store/storage-controller.d.ts.map +1 -1
- package/dist/types/src/types/message-store.d.ts +28 -2
- package/dist/types/src/types/message-store.d.ts.map +1 -1
- package/dist/types/src/types/protocols-types.d.ts +7 -24
- package/dist/types/src/types/protocols-types.d.ts.map +1 -1
- package/dist/types/src/types/query-types.d.ts +8 -1
- package/dist/types/src/types/query-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +3 -2
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/src/utils/filter.d.ts +14 -1
- package/dist/types/src/utils/filter.d.ts.map +1 -1
- package/dist/types/src/utils/grant-key-coverage.d.ts.map +1 -1
- package/dist/types/src/utils/messages.d.ts.map +1 -1
- package/dist/types/src/utils/permission-scope.d.ts +0 -2
- package/dist/types/src/utils/permission-scope.d.ts.map +1 -1
- package/dist/types/src/utils/record-limit-occupancy.d.ts +25 -12
- package/dist/types/src/utils/record-limit-occupancy.d.ts.map +1 -1
- package/dist/types/src/utils/records.d.ts +6 -24
- package/dist/types/src/utils/records.d.ts.map +1 -1
- package/dist/types/tests/features/records-delivery.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-nested-query-scope.spec.d.ts.map +1 -1
- package/dist/types/tests/features/records-record-limit.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-subscribe.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
- package/dist/types/tests/store/message-store-cross-context.spec.d.ts +2 -0
- package/dist/types/tests/store/message-store-cross-context.spec.d.ts.map +1 -0
- package/dist/types/tests/store/message-store.spec.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/dwn-constant.ts +2 -2
- package/src/core/dwn-error.ts +0 -2
- package/src/core/protocol-authorization-validation.ts +1 -33
- package/src/core/protocol-authorization.ts +0 -7
- package/src/core/validation-state-reader.ts +2 -3
- package/src/handlers/records-collection.ts +269 -0
- package/src/handlers/records-count.ts +38 -232
- package/src/handlers/records-query.ts +22 -444
- package/src/handlers/records-subscribe.ts +26 -397
- package/src/handlers/records-write.ts +17 -32
- package/src/index.ts +3 -3
- package/src/interfaces/protocols-configure.ts +4 -13
- package/src/interfaces/records-count.ts +1 -1
- package/src/interfaces/records-query.ts +1 -1
- package/src/interfaces/records-subscribe.ts +5 -2
- package/src/store/index-level.ts +323 -28
- package/src/store/message-store-level.ts +251 -25
- package/src/store/storage-controller.ts +1 -3
- package/src/types/message-store.ts +30 -2
- package/src/types/protocols-types.ts +7 -26
- package/src/types/query-types.ts +10 -2
- package/src/types/records-types.ts +3 -2
- package/src/utils/filter.ts +40 -3
- package/src/utils/grant-key-coverage.ts +4 -7
- package/src/utils/messages.ts +4 -14
- package/src/utils/permission-scope.ts +4 -11
- package/src/utils/record-limit-occupancy.ts +108 -293
- package/src/utils/records.ts +22 -57
|
@@ -1,30 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { GenericMessage, MessageSort } from '../types/message-types.js';
|
|
1
|
+
import type { RecordsCollectionVisibility } from './records-collection.js';
|
|
3
2
|
import type { HandlerDependencies, MethodHandler } from '../types/method-handler.js';
|
|
4
|
-
import type { RecordsQueryMessage, RecordsQueryReply
|
|
3
|
+
import type { RecordsQueryMessage, RecordsQueryReply } from '../types/records-types.js';
|
|
5
4
|
|
|
6
5
|
import { attachInitialWrites } from '../utils/initial-write-attachment.js';
|
|
7
|
-
import { authenticate } from '../core/auth.js';
|
|
8
|
-
import { DateSort } from '../types/records-types.js';
|
|
9
|
-
import { EncryptionControl } from '../core/encryption-control.js';
|
|
10
|
-
import { Message } from '../core/message.js';
|
|
11
6
|
import { messageReplyFromError } from '../core/message-reply.js';
|
|
12
|
-
import { ProtocolAuthorization } from '../core/protocol-authorization.js';
|
|
13
|
-
import { queryRecordsWithRecordLimitOccupancy } from '../utils/record-limit-occupancy.js';
|
|
14
|
-
import { Records } from '../utils/records.js';
|
|
15
|
-
import { RecordsGrantAuthorization } from '../core/records-grant-authorization.js';
|
|
16
7
|
import { RecordsQuery } from '../interfaces/records-query.js';
|
|
17
|
-
import {
|
|
18
|
-
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
|
|
19
|
-
|
|
20
|
-
type RecordsQueryProjectionInput = {
|
|
21
|
-
tenant: string;
|
|
22
|
-
recordsQuery: RecordsQuery;
|
|
23
|
-
requester: string | undefined;
|
|
24
|
-
filters: Filter[];
|
|
25
|
-
messageSort: MessageSort;
|
|
26
|
-
pagination?: { cursor?: PaginationCursor; limit?: number };
|
|
27
|
-
};
|
|
8
|
+
import { queryVisibleRecordsPage, resolveRecordsCollectionVisibility } from './records-collection.js';
|
|
28
9
|
|
|
29
10
|
export class RecordsQueryHandler implements MethodHandler {
|
|
30
11
|
|
|
@@ -37,439 +18,36 @@ export class RecordsQueryHandler implements MethodHandler {
|
|
|
37
18
|
let recordsQuery: RecordsQuery;
|
|
38
19
|
try {
|
|
39
20
|
recordsQuery = await RecordsQuery.parse(message);
|
|
40
|
-
} catch (
|
|
41
|
-
return messageReplyFromError(
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let recordsWrites: RecordsQueryReplyEntry[];
|
|
45
|
-
let cursor: PaginationCursor | undefined;
|
|
46
|
-
const requester = Message.getRequester(recordsQuery.message);
|
|
47
|
-
// if this is an anonymous query and the filter supports published records, query only published records
|
|
48
|
-
if (Records.filterIncludesPublishedRecords(recordsQuery.message.descriptor.filter) && recordsQuery.author === undefined) {
|
|
49
|
-
const results = await this.fetchPublishedRecords(tenant, recordsQuery, requester);
|
|
50
|
-
recordsWrites = results.messages as RecordsQueryReplyEntry[];
|
|
51
|
-
cursor = results.cursor;
|
|
52
|
-
} else {
|
|
53
|
-
// authentication and authorization
|
|
54
|
-
try {
|
|
55
|
-
await authenticate(message.authorization!, this.deps.didResolver);
|
|
56
|
-
|
|
57
|
-
await RecordsQueryHandler.authorizeRecordsQuery(tenant, recordsQuery, this.deps);
|
|
58
|
-
} catch (e) {
|
|
59
|
-
return messageReplyFromError(e, 401);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (recordsQuery.author === tenant) {
|
|
63
|
-
const results = requester === tenant
|
|
64
|
-
? await this.fetchRecordsAsOwner(tenant, recordsQuery)
|
|
65
|
-
: await this.fetchRecordsAsOwnerDelegate(tenant, recordsQuery, requester);
|
|
66
|
-
recordsWrites = results.messages as RecordsQueryReplyEntry[];
|
|
67
|
-
cursor = results.cursor;
|
|
68
|
-
} else {
|
|
69
|
-
const results = await this.fetchRecordsAsNonOwner(tenant, recordsQuery, requester);
|
|
70
|
-
recordsWrites = results.messages as RecordsQueryReplyEntry[];
|
|
71
|
-
cursor = results.cursor;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// attach the retained initial write to every entry that is not itself an initial write
|
|
76
|
-
const completeRecordsWrites = await attachInitialWrites({
|
|
77
|
-
messageStore : this.deps.messageStore,
|
|
78
|
-
tenant,
|
|
79
|
-
recordsWrites,
|
|
80
|
-
operationName : 'RecordsQuery',
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
status : { code: 200, detail: 'OK' },
|
|
85
|
-
entries : completeRecordsWrites,
|
|
86
|
-
cursor
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Convert an incoming DateSort to a sort type accepted by MessageStore
|
|
92
|
-
* Defaults to 'dateCreated' in Descending order if no sort is supplied.
|
|
93
|
-
*
|
|
94
|
-
* @param dateSort the optional DateSort from the RecordsQuery message descriptor.
|
|
95
|
-
* @returns {MessageSort} for MessageStore sorting.
|
|
96
|
-
*/
|
|
97
|
-
private convertDateSort(dateSort?: DateSort): MessageSort {
|
|
98
|
-
switch (dateSort) {
|
|
99
|
-
case DateSort.CreatedAscending:
|
|
100
|
-
return { dateCreated: SortDirection.Ascending };
|
|
101
|
-
case DateSort.CreatedDescending:
|
|
102
|
-
return { dateCreated: SortDirection.Descending };
|
|
103
|
-
case DateSort.PublishedAscending:
|
|
104
|
-
return { datePublished: SortDirection.Ascending };
|
|
105
|
-
case DateSort.PublishedDescending:
|
|
106
|
-
return { datePublished: SortDirection.Descending };
|
|
107
|
-
case DateSort.UpdatedAscending:
|
|
108
|
-
return { messageTimestamp: SortDirection.Ascending };
|
|
109
|
-
case DateSort.UpdatedDescending:
|
|
110
|
-
return { messageTimestamp: SortDirection.Descending };
|
|
111
|
-
default:
|
|
112
|
-
return { dateCreated: SortDirection.Ascending };
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Fetches the records as the owner of the DWN with no additional filtering.
|
|
118
|
-
*/
|
|
119
|
-
private async fetchRecordsAsOwner(
|
|
120
|
-
tenant: string,
|
|
121
|
-
recordsQuery: RecordsQuery
|
|
122
|
-
): Promise<{ messages: GenericMessage[], cursor?: PaginationCursor }> {
|
|
123
|
-
const { dateSort, filter, pagination } = recordsQuery.message.descriptor;
|
|
124
|
-
// fetch all published records matching the query
|
|
125
|
-
const queryFilter = {
|
|
126
|
-
...Records.convertFilter(filter, dateSort),
|
|
127
|
-
interface : DwnInterfaceName.Records,
|
|
128
|
-
method : DwnMethodName.Write,
|
|
129
|
-
isLatestBaseState : true
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const messageSort = this.convertDateSort(dateSort);
|
|
133
|
-
return this.queryRecordsWithVisibleControlFiltering({
|
|
134
|
-
tenant,
|
|
135
|
-
recordsQuery,
|
|
136
|
-
requester : tenant,
|
|
137
|
-
filters : [queryFilter],
|
|
138
|
-
messageSort,
|
|
139
|
-
pagination,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private async fetchRecordsAsOwnerDelegate(
|
|
144
|
-
tenant: string,
|
|
145
|
-
recordsQuery: RecordsQuery,
|
|
146
|
-
requester: string | undefined,
|
|
147
|
-
): Promise<{ messages: GenericMessage[], cursor?: PaginationCursor }> {
|
|
148
|
-
const { dateSort, filter, pagination } = recordsQuery.message.descriptor;
|
|
149
|
-
const queryFilter = {
|
|
150
|
-
...Records.convertFilter(filter, dateSort),
|
|
151
|
-
interface : DwnInterfaceName.Records,
|
|
152
|
-
method : DwnMethodName.Write,
|
|
153
|
-
isLatestBaseState : true
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const messageSort = this.convertDateSort(dateSort);
|
|
157
|
-
return this.queryRecordsWithVisibleControlFiltering({
|
|
158
|
-
tenant,
|
|
159
|
-
recordsQuery,
|
|
160
|
-
requester,
|
|
161
|
-
filters: [queryFilter],
|
|
162
|
-
messageSort,
|
|
163
|
-
pagination,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Fetches the records as a non-owner.
|
|
169
|
-
*
|
|
170
|
-
* Filters can support returning both published and unpublished records,
|
|
171
|
-
* as well as explicitly only published or only unpublished records.
|
|
172
|
-
*
|
|
173
|
-
* A) BOTH published and unpublished:
|
|
174
|
-
* 1. published records; and
|
|
175
|
-
* 2. unpublished records intended for the query author (where `recipient` is the query author); and
|
|
176
|
-
* 3. unpublished records authorized by a protocol rule.
|
|
177
|
-
*
|
|
178
|
-
* B) PUBLISHED:
|
|
179
|
-
* 1. only published records;
|
|
180
|
-
*
|
|
181
|
-
* C) UNPUBLISHED:
|
|
182
|
-
* 1. unpublished records intended for the query author (where `recipient` is the query author); and
|
|
183
|
-
* 2. unpublished records authorized by a protocol rule.
|
|
184
|
-
*
|
|
185
|
-
*/
|
|
186
|
-
private async fetchRecordsAsNonOwner(
|
|
187
|
-
tenant: string,
|
|
188
|
-
recordsQuery: RecordsQuery,
|
|
189
|
-
requester: string | undefined,
|
|
190
|
-
): Promise<{ messages: GenericMessage[], cursor?: PaginationCursor }> {
|
|
191
|
-
const { dateSort, pagination, filter } = recordsQuery.message.descriptor;
|
|
192
|
-
const filters = [];
|
|
193
|
-
if (Records.filterIncludesPublishedRecords(filter)) {
|
|
194
|
-
filters.push(RecordsQueryHandler.buildPublishedRecordsFilter(recordsQuery));
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return messageReplyFromError(error, 400);
|
|
195
23
|
}
|
|
196
24
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (Records.shouldBuildUnpublishedAuthorFilter(filter, recordsQuery.author!)) {
|
|
203
|
-
filters.push(RecordsQueryHandler.buildUnpublishedRecordsByQueryAuthorFilter(recordsQuery));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (Records.shouldProtocolAuthorize(recordsQuery.signaturePayload!)) {
|
|
207
|
-
filters.push(RecordsQueryHandler.buildUnpublishedProtocolAuthorizedRecordsFilter(recordsQuery));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (Message.getPermissionGrantId(recordsQuery.signaturePayload!) !== undefined) {
|
|
211
|
-
filters.push(RecordsQueryHandler.buildUnpublishedPermissionGrantAuthorizedRecordsFilter(recordsQuery));
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (Records.shouldBuildUnpublishedRecipientFilter(filter, recordsQuery.author!)) {
|
|
215
|
-
filters.push(RecordsQueryHandler.buildUnpublishedRecordsForQueryAuthorFilter(recordsQuery));
|
|
216
|
-
}
|
|
25
|
+
let visibility: RecordsCollectionVisibility;
|
|
26
|
+
try {
|
|
27
|
+
visibility = await resolveRecordsCollectionVisibility(tenant, recordsQuery, this.deps);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return messageReplyFromError(error, 401);
|
|
217
30
|
}
|
|
218
31
|
|
|
219
|
-
const
|
|
220
|
-
|
|
32
|
+
const result = await queryVisibleRecordsPage({
|
|
33
|
+
deps : this.deps,
|
|
221
34
|
tenant,
|
|
222
|
-
recordsQuery,
|
|
223
|
-
|
|
224
|
-
filters,
|
|
225
|
-
messageSort,
|
|
226
|
-
pagination,
|
|
35
|
+
request : recordsQuery,
|
|
36
|
+
visibility,
|
|
227
37
|
});
|
|
228
|
-
}
|
|
229
38
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
private async fetchPublishedRecords(
|
|
234
|
-
tenant: string,
|
|
235
|
-
recordsQuery: RecordsQuery,
|
|
236
|
-
requester: string | undefined,
|
|
237
|
-
): Promise<{ messages: GenericMessage[], cursor?: PaginationCursor }> {
|
|
238
|
-
const { dateSort, pagination } = recordsQuery.message.descriptor;
|
|
239
|
-
const filter = RecordsQueryHandler.buildPublishedRecordsFilter(recordsQuery);
|
|
240
|
-
const messageSort = this.convertDateSort(dateSort);
|
|
241
|
-
return this.queryRecordsWithVisibleControlFiltering({
|
|
39
|
+
// Attach the retained initial write to every entry that is not itself an initial write.
|
|
40
|
+
const entries = await attachInitialWrites({
|
|
41
|
+
messageStore : this.deps.messageStore,
|
|
242
42
|
tenant,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
filters: [filter],
|
|
246
|
-
messageSort,
|
|
247
|
-
pagination,
|
|
43
|
+
recordsWrites : result.messages,
|
|
44
|
+
operationName : 'RecordsQuery',
|
|
248
45
|
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private async queryRecordsWithVisibleControlFiltering(
|
|
252
|
-
input: RecordsQueryProjectionInput
|
|
253
|
-
): Promise<{ messages: RecordsQueryReplyEntry[], cursor?: PaginationCursor }> {
|
|
254
|
-
const {
|
|
255
|
-
tenant, recordsQuery, requester, filters, messageSort, pagination
|
|
256
|
-
} = input;
|
|
257
|
-
const controlFilters = Records.buildControlRecordsFilters(filters);
|
|
258
|
-
const currentAudienceRecordIdCache = new Map<string, string | undefined>();
|
|
259
|
-
if (controlFilters.length === 0) {
|
|
260
|
-
const result = await queryRecordsWithRecordLimitOccupancy({
|
|
261
|
-
messageStore : this.deps.messageStore,
|
|
262
|
-
validationStateReader : this.deps.validationStateReader,
|
|
263
|
-
tenant,
|
|
264
|
-
filters,
|
|
265
|
-
messageSort,
|
|
266
|
-
pagination,
|
|
267
|
-
messageTimestamp : recordsQuery.message.descriptor.messageTimestamp,
|
|
268
|
-
});
|
|
269
|
-
return EncryptionControl.projectCurrentAudienceRecordPage({
|
|
270
|
-
messageStore : this.deps.messageStore,
|
|
271
|
-
tenant,
|
|
272
|
-
filters,
|
|
273
|
-
currentAudienceRecordIdCache,
|
|
274
|
-
result : {
|
|
275
|
-
messages : result.messages as RecordsQueryReplyEntry[],
|
|
276
|
-
cursor : result.cursor,
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (pagination?.limit === undefined || pagination.limit <= 0) {
|
|
282
|
-
const result = await queryRecordsWithRecordLimitOccupancy({
|
|
283
|
-
messageStore : this.deps.messageStore,
|
|
284
|
-
validationStateReader : this.deps.validationStateReader,
|
|
285
|
-
tenant,
|
|
286
|
-
filters,
|
|
287
|
-
messageSort,
|
|
288
|
-
pagination,
|
|
289
|
-
messageTimestamp : recordsQuery.message.descriptor.messageTimestamp,
|
|
290
|
-
});
|
|
291
|
-
const projectedResult = await EncryptionControl.projectCurrentAudienceRecordPage({
|
|
292
|
-
messageStore : this.deps.messageStore,
|
|
293
|
-
tenant,
|
|
294
|
-
filters,
|
|
295
|
-
currentAudienceRecordIdCache,
|
|
296
|
-
result : {
|
|
297
|
-
messages : result.messages as RecordsQueryReplyEntry[],
|
|
298
|
-
cursor : result.cursor,
|
|
299
|
-
},
|
|
300
|
-
});
|
|
301
|
-
return {
|
|
302
|
-
messages : await this.filterControlRecordsForRequester(tenant, recordsQuery, requester, projectedResult.messages),
|
|
303
|
-
cursor : projectedResult.cursor,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const visibleMessages: RecordsQueryReplyEntry[] = [];
|
|
308
|
-
let cursor = pagination.cursor;
|
|
309
|
-
let nextCursor: PaginationCursor | undefined;
|
|
310
|
-
// Keeps visible-page pagination stable until #1100 moves control visibility into indexed store filters.
|
|
311
|
-
do {
|
|
312
|
-
const remainingLimit = pagination.limit - visibleMessages.length;
|
|
313
|
-
const result = await queryRecordsWithRecordLimitOccupancy({
|
|
314
|
-
messageStore : this.deps.messageStore,
|
|
315
|
-
validationStateReader : this.deps.validationStateReader,
|
|
316
|
-
tenant,
|
|
317
|
-
filters,
|
|
318
|
-
messageSort,
|
|
319
|
-
pagination : { ...pagination, cursor, limit: remainingLimit },
|
|
320
|
-
messageTimestamp : recordsQuery.message.descriptor.messageTimestamp,
|
|
321
|
-
});
|
|
322
|
-
const projectedResult = await EncryptionControl.projectCurrentAudienceRecordPage({
|
|
323
|
-
messageStore : this.deps.messageStore,
|
|
324
|
-
tenant,
|
|
325
|
-
filters,
|
|
326
|
-
currentAudienceRecordIdCache,
|
|
327
|
-
result : {
|
|
328
|
-
messages : result.messages as RecordsQueryReplyEntry[],
|
|
329
|
-
cursor : result.cursor,
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
const filteredMessages = await this.filterControlRecordsForRequester(
|
|
333
|
-
tenant,
|
|
334
|
-
recordsQuery,
|
|
335
|
-
requester,
|
|
336
|
-
projectedResult.messages,
|
|
337
|
-
);
|
|
338
|
-
visibleMessages.push(...filteredMessages);
|
|
339
|
-
nextCursor = projectedResult.cursor;
|
|
340
|
-
cursor = projectedResult.cursor;
|
|
341
|
-
} while (visibleMessages.length < pagination.limit && cursor !== undefined);
|
|
342
46
|
|
|
343
|
-
return { messages: visibleMessages, cursor: nextCursor };
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
private static buildPublishedRecordsFilter(recordsQuery: RecordsQuery): Filter {
|
|
347
|
-
const { dateSort, filter } = recordsQuery.message.descriptor;
|
|
348
|
-
// fetch all published records matching the query
|
|
349
47
|
return {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
published : true,
|
|
354
|
-
isLatestBaseState : true
|
|
48
|
+
status : { code: 200, detail: 'OK' },
|
|
49
|
+
entries,
|
|
50
|
+
cursor : result.cursor,
|
|
355
51
|
};
|
|
356
52
|
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Creates a filter for unpublished records that are intended for the query author (where `recipient` is the author).
|
|
360
|
-
*/
|
|
361
|
-
private static buildUnpublishedRecordsForQueryAuthorFilter(recordsQuery: RecordsQuery): Filter {
|
|
362
|
-
const { dateSort, filter } = recordsQuery.message.descriptor;
|
|
363
|
-
// include records where recipient is query author
|
|
364
|
-
return {
|
|
365
|
-
...Records.convertFilter(filter, dateSort),
|
|
366
|
-
interface : DwnInterfaceName.Records,
|
|
367
|
-
method : DwnMethodName.Write,
|
|
368
|
-
recipient : recordsQuery.author!,
|
|
369
|
-
isLatestBaseState : true,
|
|
370
|
-
published : false
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Creates a filter for unpublished records that are within the specified protocol.
|
|
376
|
-
* Validation that `protocol` and other required protocol-related fields occurs before this method.
|
|
377
|
-
*/
|
|
378
|
-
private static buildUnpublishedProtocolAuthorizedRecordsFilter(recordsQuery: RecordsQuery): Filter {
|
|
379
|
-
const { dateSort, filter } = recordsQuery.message.descriptor;
|
|
380
|
-
return {
|
|
381
|
-
...Records.convertFilter(filter, dateSort),
|
|
382
|
-
interface : DwnInterfaceName.Records,
|
|
383
|
-
method : DwnMethodName.Write,
|
|
384
|
-
isLatestBaseState : true,
|
|
385
|
-
published : false
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Creates a filter for unpublished records authorized by a permission grant.
|
|
391
|
-
*/
|
|
392
|
-
private static buildUnpublishedPermissionGrantAuthorizedRecordsFilter(recordsQuery: RecordsQuery): Filter {
|
|
393
|
-
const { dateSort, filter } = recordsQuery.message.descriptor;
|
|
394
|
-
return {
|
|
395
|
-
...Records.convertFilter(filter, dateSort),
|
|
396
|
-
interface : DwnInterfaceName.Records,
|
|
397
|
-
method : DwnMethodName.Write,
|
|
398
|
-
isLatestBaseState : true,
|
|
399
|
-
published : false
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Creates a filter for only unpublished records where the author is the same as the query author.
|
|
405
|
-
*/
|
|
406
|
-
private static buildUnpublishedRecordsByQueryAuthorFilter(recordsQuery: RecordsQuery): Filter {
|
|
407
|
-
const { dateSort, filter } = recordsQuery.message.descriptor;
|
|
408
|
-
// include records where author is the same as the query author
|
|
409
|
-
return {
|
|
410
|
-
...Records.convertFilter(filter, dateSort),
|
|
411
|
-
author : recordsQuery.author!,
|
|
412
|
-
interface : DwnInterfaceName.Records,
|
|
413
|
-
method : DwnMethodName.Write,
|
|
414
|
-
isLatestBaseState : true,
|
|
415
|
-
published : false
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* @param messageStore Used to check if the grant has been revoked.
|
|
421
|
-
*/
|
|
422
|
-
private static async authorizeRecordsQuery(
|
|
423
|
-
tenant: string,
|
|
424
|
-
recordsQuery: RecordsQuery,
|
|
425
|
-
deps: HandlerDependencies,
|
|
426
|
-
): Promise<void> {
|
|
427
|
-
|
|
428
|
-
if (Message.isSignedByAuthorDelegate(recordsQuery.message) &&
|
|
429
|
-
!EncryptionControl.filterTargetsOnlyControlRecords(recordsQuery.message.descriptor.filter)) {
|
|
430
|
-
await recordsQuery.authorizeDelegate(deps.validationStateReader);
|
|
431
|
-
} else if (EncryptionControl.filterTargetsOnlyControlRecords(recordsQuery.message.descriptor.filter)) {
|
|
432
|
-
await EncryptionControl.authorizeControlReadRequest({
|
|
433
|
-
tenant,
|
|
434
|
-
incomingMessage : recordsQuery.message,
|
|
435
|
-
requester : Message.getRequester(recordsQuery.message),
|
|
436
|
-
validationStateReader : deps.validationStateReader,
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const permissionGrantId = Message.getPermissionGrantId(recordsQuery.signaturePayload!);
|
|
441
|
-
if (permissionGrantId !== undefined) {
|
|
442
|
-
const permissionGrant = await deps.validationStateReader.fetchGrant(tenant, permissionGrantId);
|
|
443
|
-
await RecordsGrantAuthorization.authorizeQueryOrSubscribe({
|
|
444
|
-
incomingMessage : recordsQuery.message,
|
|
445
|
-
expectedGrantor : tenant,
|
|
446
|
-
expectedGrantee : recordsQuery.author!,
|
|
447
|
-
permissionGrant,
|
|
448
|
-
validationStateReader : deps.validationStateReader,
|
|
449
|
-
});
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// NOTE: not all RecordsQuery messages require protocol authorization even if the filter includes protocol-related fields,
|
|
454
|
-
// this is because we dynamically filter out records that the caller is not authorized to see.
|
|
455
|
-
// Currently only run protocol authorization if message deliberately invokes a protocol role.
|
|
456
|
-
if (Records.shouldProtocolAuthorize(recordsQuery.signaturePayload!)) {
|
|
457
|
-
await ProtocolAuthorization.authorizeQueryOrSubscribe(tenant, recordsQuery, deps.validationStateReader);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
private async filterControlRecordsForRequester(
|
|
462
|
-
tenant: string,
|
|
463
|
-
recordsQuery: RecordsQuery,
|
|
464
|
-
requester: string | undefined,
|
|
465
|
-
recordsWrites: RecordsQueryReplyEntry[],
|
|
466
|
-
): Promise<RecordsQueryReplyEntry[]> {
|
|
467
|
-
return EncryptionControl.filterVisibleControlRecords({
|
|
468
|
-
tenant,
|
|
469
|
-
incomingMessage : recordsQuery.message,
|
|
470
|
-
requester,
|
|
471
|
-
recordsWriteMessages : recordsWrites,
|
|
472
|
-
validationStateReader : this.deps.validationStateReader,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
53
|
}
|