Skip to content

Commit

Permalink
renamed Collections interface to Records
Browse files Browse the repository at this point in the history
* The word "collection" no longer exists in the code base. Not even once.
* Added a few more useful exports.
  • Loading branch information
thehenrytsai authored Jan 18, 2023
1 parent 4deecf8 commit 58656ed
Show file tree
Hide file tree
Showing 28 changed files with 647 additions and 640 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Decentralized Web Node (DWN) SDK

Code Coverage
![Statements](https://img.shields.io/badge/statements-92.78%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-91.98%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-89.3%25-yellow.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-92.78%25-brightgreen.svg?style=flat)
![Statements](https://img.shields.io/badge/statements-92.79%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-91.98%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-89.3%25-yellow.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-92.79%25-brightgreen.svg?style=flat)

## Introduction

Expand Down
8 changes: 4 additions & 4 deletions build/compile-validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import Ajv from 'ajv';
import mkdirp from 'mkdirp';
import standaloneCode from 'ajv/dist/standalone/index.js';

import CollectionsQuery from '../json-schemas/collections/collections-query.json' assert { type: 'json' };
import CollectionsWrite from '../json-schemas/collections/collections-write.json' assert { type: 'json' };
import Definitions from '../json-schemas/definitions.json' assert { type: 'json' };
import GeneralJwk from '../json-schemas/jwk/general-jwk.json' assert { type: 'json' };
import GeneralJws from '../json-schemas/general-jws.json' assert { type: 'json' };
Expand All @@ -32,11 +30,13 @@ import ProtocolRuleSet from '../json-schemas/protocol-rule-set.json' assert { ty
import ProtocolsConfigure from '../json-schemas/protocols/protocols-configure.json' assert { type: 'json' };
import ProtocolsQuery from '../json-schemas/protocols/protocols-query.json' assert { type: 'json' };
import PublicJwk from '../json-schemas/jwk/public-jwk.json' assert { type: 'json' };
import RecordsQuery from '../json-schemas/records/records-query.json' assert { type: 'json' };
import RecordsWrite from '../json-schemas/records/records-write.json' assert { type: 'json' };
import Request from '../json-schemas/request.json' assert { type: 'json' };

const schemas = {
CollectionsQuery,
CollectionsWrite,
RecordsQuery,
RecordsWrite,
Definitions,
GeneralJwk,
GeneralJws,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://identity.foundation/dwn/json-schemas/collections-query.json",
"$id": "https://identity.foundation/dwn/json-schemas/records-query.json",
"type": "object",
"additionalProperties": false,
"required": [
Expand All @@ -22,7 +22,7 @@
"properties": {
"method": {
"enum": [
"CollectionsQuery"
"RecordsQuery"
],
"type": "string"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://identity.foundation/dwn/json-schemas/collections-write.json",
"$id": "https://identity.foundation/dwn/json-schemas/records-write.json",
"type": "object",
"additionalProperties": false,
"required": [
Expand All @@ -23,7 +23,7 @@
"properties": {
"method": {
"enum": [
"CollectionsWrite"
"RecordsWrite"
],
"type": "string"
},
Expand Down
12 changes: 7 additions & 5 deletions src/core/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type AuthorizationPayloadConstraints = {

/**
* Authenticates then authorizes the given message using the "canonical" auth flow.
* Some message auth require special handling such as `CollectionsWrite` and `CollectionsQuery`,
* Some message auth require special handling such as `RecordsWrite` and `RecordsQuery`,
* which would be incompatible with this auth flow.
* @throws {Error} if auth fails
*/
Expand All @@ -35,7 +35,7 @@ export async function canonicalAuth(
export async function validateAuthorizationIntegrity(
message: BaseMessage,
authorizationPayloadConstraints?: AuthorizationPayloadConstraints
): Promise<{target: string, descriptorCid: CID, [key: string]: any }> {
): Promise<{ target: string, descriptorCid: CID, [key: string]: any }> {

if (message.authorization.signatures.length !== 1) {
throw new Error('expected no more than 1 signature for authorization');
Expand All @@ -60,9 +60,11 @@ export async function validateAuthorizationIntegrity(
delete customProperties.target;
delete customProperties.descriptorCid;
for (const propertyName in customProperties) {
{if (!allowedProperties.has(propertyName)) {
throw new Error(`${propertyName} not allowed in auth payload.`);
}}
{
if (!allowedProperties.has(propertyName)) {
throw new Error(`${propertyName} not allowed in auth payload.`);
}
}

try {
parseCid(payloadJson[propertyName]);
Expand Down
2 changes: 1 addition & 1 deletion src/core/message-reply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type MessageReplyOptions = {
export class MessageReply {
status: Status;
// resulting message entries returned from the invocation of the corresponding message
// e.g. the resulting messages from a CollectionsQuery
// e.g. the resulting messages from a RecordsQuery
entries?: { descriptor: Descriptor }[];

constructor(opts: MessageReplyOptions) {
Expand Down
8 changes: 4 additions & 4 deletions src/core/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import type { SignatureInput } from '../jose/jws/general/types.js';
import type { BaseDecodedAuthorizationPayload, BaseMessage, Descriptor } from './types.js';

import { CID } from 'multiformats/cid';
import { CollectionsWriteMessage } from '../interfaces/collections/types.js';
import { GeneralJws } from '../jose/jws/general/types.js';
import { GeneralJwsSigner } from '../jose/jws/general/signer.js';
import { GeneralJwsVerifier } from '../jose/jws/general/verifier.js';
import { generateCid } from '../utils/cid.js';
import { lexicographicalCompare } from '../utils/string.js';
import { RecordsWriteMessage } from '../interfaces/records/types.js';
import { validateJsonSchema } from '../validator.js';

export enum DwnMethodName {
CollectionsWrite = 'CollectionsWrite',
CollectionsQuery = 'CollectionsQuery',
RecordsWrite = 'RecordsWrite',
RecordsQuery = 'RecordsQuery',
HooksWrite = 'HooksWrite',
ProtocolsConfigure = 'ProtocolsConfigure',
ProtocolsQuery = 'ProtocolsQuery'
Expand Down Expand Up @@ -68,7 +68,7 @@ export abstract class Message {
const messageCopy = { ...message };

if (messageCopy['encodedData'] !== undefined) {
delete (messageCopy as CollectionsWriteMessage).encodedData;
delete (messageCopy as RecordsWriteMessage).encodedData;
}

const cid = await generateCid(messageCopy);
Expand Down
83 changes: 42 additions & 41 deletions src/core/protocol-authorization.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import { CollectionsWrite } from '../interfaces/collections/messages/collections-write.js';
import { CollectionsWriteMessage } from '../interfaces/collections/types.js';
import { MessageStore } from '../store/message-store.js';
import { RecordsWrite } from '../interfaces/records/messages/records-write.js';
import { RecordsWriteMessage } from '../interfaces/records/types.js';

import { DwnMethodName, Message } from './message.js';
import { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage } from '../interfaces/protocols/types.js';

const methodToAllowedActionMap = {
[DwnMethodName.CollectionsWrite]: 'write',
[DwnMethodName.RecordsWrite]: 'write',
};

export class ProtocolAuthorization {

/**
* Performs protocol-based authorization against the given collections message.
* Performs protocol-based authorization against the given message.
* @throws {Error} if authorization fails.
*/
public static async authorize(
collectionsWrite: CollectionsWrite,
recordsWrite: RecordsWrite,
requesterDid: string,
messageStore: MessageStore
): Promise<void> {
// fetch the protocol definition
const protocolDefinition = await ProtocolAuthorization.fetchProtocolDefinition(collectionsWrite, messageStore);
// fetch the protocol definition
const protocolDefinition = await ProtocolAuthorization.fetchProtocolDefinition(recordsWrite, messageStore);

// fetch ancestor message chain
const ancestorMessageChain: CollectionsWriteMessage[] = await ProtocolAuthorization.constructAncestorMessageChain(collectionsWrite, messageStore);
const ancestorMessageChain: RecordsWriteMessage[] = await ProtocolAuthorization.constructAncestorMessageChain(recordsWrite, messageStore);

// record schema -> schema label map
const recordSchemaToLabelMap: Map<string, string> = new Map();
Expand All @@ -34,33 +35,33 @@ export class ProtocolAuthorization {

// get the rule set for the inbound message
const inboundMessageRuleSet = ProtocolAuthorization.getRuleSet(
collectionsWrite.message,
recordsWrite.message,
protocolDefinition, ancestorMessageChain,
recordSchemaToLabelMap
);

// verify the requester of the inbound message against allowed requester rule
ProtocolAuthorization.verifyAllowedRequester(
requesterDid, collectionsWrite.target, inboundMessageRuleSet, ancestorMessageChain, recordSchemaToLabelMap
requesterDid, recordsWrite.target, inboundMessageRuleSet, ancestorMessageChain, recordSchemaToLabelMap
);

// verify method invoked against the allowed actions
ProtocolAuthorization.verifyAllowedActions(requesterDid, collectionsWrite, inboundMessageRuleSet);
ProtocolAuthorization.verifyAllowedActions(requesterDid, recordsWrite, inboundMessageRuleSet);

// verify allowed condition of the write
await ProtocolAuthorization.verifyActionCondition(collectionsWrite, messageStore);
await ProtocolAuthorization.verifyActionCondition(recordsWrite, messageStore);
}

/**
* Fetches the protocol definition based on the protocol specified in the given message.
*/
private static async fetchProtocolDefinition(collectionsWrite: CollectionsWrite, messageStore: MessageStore): Promise<ProtocolDefinition> {
// get the protocol URI
const protocolUri = collectionsWrite.message.descriptor.protocol;
private static async fetchProtocolDefinition(recordsWrite: RecordsWrite, messageStore: MessageStore): Promise<ProtocolDefinition> {
// get the protocol URI
const protocolUri = recordsWrite.message.descriptor.protocol;

// fetch the corresponding protocol definition
const query = {
target : collectionsWrite.target,
target : recordsWrite.target,
method : DwnMethodName.ProtocolsConfigure,
protocol : protocolUri
};
Expand All @@ -78,25 +79,25 @@ export class ProtocolAuthorization {
* Constructs a chain of ancestor messages
* @returns the ancestor chain of messages where the first element is the root of the chain; returns empty array if no parent is specified.
*/
private static async constructAncestorMessageChain(collectionsWrite: CollectionsWrite, messageStore: MessageStore)
: Promise<CollectionsWriteMessage[]> {
const ancestorMessageChain: CollectionsWriteMessage[] = [];
private static async constructAncestorMessageChain(recordsWrite: RecordsWrite, messageStore: MessageStore)
: Promise<RecordsWriteMessage[]> {
const ancestorMessageChain: RecordsWriteMessage[] = [];

const protocol = collectionsWrite.message.descriptor.protocol;
const contextId = collectionsWrite.message.contextId;
const protocol = recordsWrite.message.descriptor.protocol;
const contextId = recordsWrite.message.contextId;

// keep walking up the chain from the inbound message's parent, until there is no more parent
let currentParentId = collectionsWrite.message.descriptor.parentId;
let currentParentId = recordsWrite.message.descriptor.parentId;
while (currentParentId !== undefined) {
// fetch parent
const query = {
target : collectionsWrite.target,
method : DwnMethodName.CollectionsWrite,
target : recordsWrite.target,
method : DwnMethodName.RecordsWrite,
protocol,
contextId,
recordId : currentParentId
};
const parentMessages = await messageStore.query(query) as CollectionsWriteMessage[];
const parentMessages = await messageStore.query(query) as RecordsWriteMessage[];

if (parentMessages.length === 0) {
throw new Error(`no parent found with ID ${currentParentId}`);
Expand All @@ -115,12 +116,12 @@ export class ProtocolAuthorization {
* Gets the rule set corresponding to the inbound message.
*/
private static getRuleSet(
inboundMessage: CollectionsWriteMessage,
inboundMessage: RecordsWriteMessage,
protocolDefinition: ProtocolDefinition,
ancestorMessageChain: CollectionsWriteMessage[],
ancestorMessageChain: RecordsWriteMessage[],
recordSchemaToLabelMap: Map<string, string>
): ProtocolRuleSet {
// make a copy of the ancestor messages and include the inbound message in the chain
// make a copy of the ancestor messages and include the inbound message in the chain
const messageChain = [...ancestorMessageChain, inboundMessage];

// walk down the ancestor message chain from the root ancestor record and match against the corresponding rule set at each level
Expand Down Expand Up @@ -159,19 +160,19 @@ export class ProtocolAuthorization {
requesterDid: string,
targetDid: string,
inboundMessageRuleSet: ProtocolRuleSet,
ancestorMessageChain: CollectionsWriteMessage[],
ancestorMessageChain: RecordsWriteMessage[],
recordSchemaToLabelMap: Map<string, string>
): void {
const allowRule = inboundMessageRuleSet.allow;
if (allowRule === undefined) {
// if no allow rule is defined, still allow if requester is the same as target, but throw otherwise
// if no allow rule is defined, still allow if requester is the same as target, but throw otherwise
if (requesterDid !== targetDid) {
throw new Error(`no allow rule defined for requester, ${requesterDid} is unauthorized`);
}
} else if (allowRule.anyone !== undefined) {
// good to go to next check
// good to go to next check
} else if (allowRule.recipient !== undefined) {
// get the message to check for recipient based on the path given
// get the message to check for recipient based on the path given
const messageForRecipientCheck = ProtocolAuthorization.getMessage(ancestorMessageChain, allowRule.recipient.of, recordSchemaToLabelMap);
const expectedRequesterDid = messageForRecipientCheck.descriptor.recipient;

Expand All @@ -193,7 +194,7 @@ export class ProtocolAuthorization {
const incomingMessageMethod = incomingMessage.message.descriptor.method;

if (allowRule === undefined) {
// if no allow rule is defined, owner of DWN can do everything
// if no allow rule is defined, owner of DWN can do everything
if (requesterDid === incomingMessage.target) {
return;
} else {
Expand All @@ -219,20 +220,20 @@ export class ProtocolAuthorization {
* Currently the only check is: if the write is not the initial write, the author must be the same as the initial write
* @throws {Error} if fails verification
*/
private static async verifyActionCondition(collectionsWrite: CollectionsWrite, messageStore: MessageStore): Promise<void> {
const isInitialWrite = await collectionsWrite.isInitialWrite();
private static async verifyActionCondition(recordsWrite: RecordsWrite, messageStore: MessageStore): Promise<void> {
const isInitialWrite = await recordsWrite.isInitialWrite();
if (!isInitialWrite) {
// fetch the initialWrite
const query = {
entryId: collectionsWrite.message.recordId
entryId: recordsWrite.message.recordId
};
const result = await messageStore.query(query) as CollectionsWriteMessage[];
const result = await messageStore.query(query) as RecordsWriteMessage[];

// check the author of the initial write matches the author of the incoming message
const initialWrite = result[0];
const authorOfInitialWrite = Message.getAuthor(initialWrite);
if (collectionsWrite.author !== authorOfInitialWrite) {
throw new Error(`author of incoming message '${collectionsWrite.author}' must match to author of initial write '${authorOfInitialWrite}'`);
if (recordsWrite.author !== authorOfInitialWrite) {
throw new Error(`author of incoming message '${recordsWrite.author}' must match to author of initial write '${authorOfInitialWrite}'`);
}
}
}
Expand All @@ -245,10 +246,10 @@ export class ProtocolAuthorization {
* NOTE: the path scheme use here may be temporary dependent on final protocol spec.
*/
private static getMessage(
ancestorMessageChain: CollectionsWriteMessage[],
ancestorMessageChain: RecordsWriteMessage[],
messagePath: string,
recordSchemaToLabelMap: Map<string, string>
): CollectionsWriteMessage {
): RecordsWriteMessage {
const expectedAncestors = messagePath.split('/');

// consider moving this check to ProtocolsConfigure message ingestion
Expand Down
2 changes: 1 addition & 1 deletion src/dwn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { MessageStoreLevel } from './store/message-store-level.js';
import { Request } from './core/request.js';
import { Response } from './core/response.js';

import { CollectionsInterface } from './interfaces/collections/collections-interface.js';
import { CollectionsInterface } from './interfaces/records/records-interface.js';
import { PermissionsInterface } from './interfaces/permissions/permissions-interface.js';
import { ProtocolsInterface } from './interfaces/protocols/protocols-interface.js';

Expand Down
13 changes: 8 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@
// - https://stackoverflow.com/questions/44979976/typescript-compiler-is-forgetting-to-add-file-extensions-to-es6-module-imports
// - https://github.com/microsoft/TypeScript/issues/40878
//
export type { CollectionsQueryMessage, CollectionsWriteMessage } from './interfaces/collections/types.js';
export type { RecordsQueryMessage, RecordsWriteMessage } from './interfaces/records/types.js';
export type { Config } from './dwn.js';
export type { HooksWriteMessage } from './interfaces/hooks/types.js';
export type { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage, ProtocolsQueryMessage } from './interfaces/protocols/types.js';
export type { DwnServiceEndpoint, ServiceEndpoint, DidDocument, DidResolutionResult, DidResolutionMetadata, DidDocumentMetadata, VerificationMethod } from './did/did-resolver.js';
export { CollectionsQuery, CollectionsQueryOptions } from './interfaces/collections/messages/collections-query.js';
export { CollectionsWrite, CollectionsWriteOptions, CreateFromOptions } from './interfaces/collections/messages/collections-write.js';
export { DateSort } from './interfaces/collections/messages/collections-query.js';
export { RecordsQuery, RecordsQueryOptions } from './interfaces/records/messages/records-query.js';
export { RecordsWrite, RecordsWriteOptions, CreateFromOptions } from './interfaces/records/messages/records-write.js';
export { DateSort } from './interfaces/records/messages/records-query.js';
export { DidKeyResolver } from './did/did-key-resolver.js';
export { DidIonResolver } from './did/did-ion-resolver.js';
export { DidResolver } from './did/did-resolver.js';
export { DidResolver, DidMethodResolver } from './did/did-resolver.js';
export { Dwn } from './dwn.js';
export { Encoder } from './utils/encoder.js';
export { HooksWrite, HooksWriteOptions } from './interfaces/hooks/messages/hooks-write.js';
export { MessageStore } from './store/message-store.js';
export { MessageStoreLevel } from './store/message-store-level.js';
export { PrivateJwk, PublicJwk } from './jose/types.js';
export { ProtocolsConfigure, ProtocolsConfigureOptions } from './interfaces/protocols/messages/protocols-configure.js';
export { ProtocolsQuery, ProtocolsQueryOptions } from './interfaces/protocols/messages/protocols-query.js';
Expand Down
16 changes: 0 additions & 16 deletions src/interfaces/collections/collections-interface.ts

This file was deleted.

Loading

0 comments on commit 58656ed

Please sign in to comment.