Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Protocol Rule: Record Expiration #729

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 52 additions & 10 deletions json-schemas/interface-methods/protocol-rule-set.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@
"minProperties": 1,
"properties": {
"$requiredTags": {
"type": "array",
"items": {
"type": "string"
}
"type": "array",
"items": {
"type": "string"
}
},
"$allowUndefinedTags": {
"type": "boolean"
"type": "boolean"
}
},
"patternProperties": {
Expand All @@ -127,13 +127,23 @@
"additionalProperties": false,
"properties": {
"type": {
"enum": ["string", "number", "integer", "boolean", "array"]
"enum": [
"string",
"number",
"integer",
"boolean",
"array"
]
},
"items": {
"type": "object",
"properties": {
"type": {
"enum": ["string", "number", "integer"]
"enum": [
"string",
"number",
"integer"
]
}
},
"patternProperties": {
Expand All @@ -144,7 +154,11 @@
"type": "object",
"properties": {
"type": {
"enum": ["string", "number", "integer"]
"enum": [
"string",
"number",
"integer"
]
}
},
"patternProperties": {
Expand All @@ -153,11 +167,39 @@
}
},
"patternProperties": {
"^(enum|minimum|maximum|exclusiveMinimum|exclusiveMaximum|minLength|maxLength|minItems|maxItems|uniqueItems|minContains|maxContains)$": {
}
"^(enum|minimum|maximum|exclusiveMinimum|exclusiveMaximum|minLength|maxLength|minItems|maxItems|uniqueItems|minContains|maxContains)$": {}
}
}
}
},
"$expiration": {
"type": "object",
"additionalProperties": false,
"properties": {
"oneOf": [
{
"required": [
"amount"
],
"type": "object",
"properties": {
"amount": {
"type": "number",
"minimum": 1,
"$comment": "If only amount is set without unit, represents amount of milliseconds to wait from the record.dateCreated before records expire."
},
"unit": {
"type": "string",
"$comment": "Single character signaling how to interpret the given 'amount'. If present, must be one of: s (seconds), m (minutes), h (hours), d (days), y (years). E.g. 100s, 75m, 2d, 4y",
"patternProperties": {
"^(s|m|d|y)$": {}
}
}
},
"$comment": "Amount is used to calculate an expiration date. Amount and unit are used to calculate a millisecond duration, which is then used to calculate an expiration date. Expiration is relative to `record.dateCreated`."
}
]
}
}
},
"patternProperties": {
Expand Down
5 changes: 5 additions & 0 deletions src/core/dwn-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export enum DwnErrorCode {
PrivateKeySignerUnsupportedCurve = 'PrivateKeySignerUnsupportedCurve',
ProtocolAuthorizationActionNotAllowed = 'ProtocolAuthorizationActionNotAllowed',
ProtocolAuthorizationActionRulesNotFound = 'ProtocolAuthorizationActionRulesNotFound',
ProtocolAuthorizationExpirationPassed = 'ProtocolAuthorizationExpirationPassed',
ProtocolAuthorizationIncorrectDataFormat = 'ProtocolAuthorizationIncorrectDataFormat',
ProtocolAuthorizationIncorrectContextId = 'ProtocolAuthorizationIncorrectContextId',
ProtocolAuthorizationIncorrectProtocolPath = 'ProtocolAuthorizationIncorrectProtocolPath',
Expand All @@ -73,6 +74,10 @@ export enum DwnErrorCode {
ProtocolAuthorizationQueryWithoutRole = 'ProtocolAuthorizationQueryWithoutRole',
ProtocolAuthorizationRoleMissingRecipient = 'ProtocolAuthorizationRoleMissingRecipient',
ProtocolAuthorizationTagsInvalidSchema = 'ProtocolAuthorizationTagsInvalidSchema',
ProtocolsAuthorizationExpirationMissing = 'ProtocolsAuthorizationExpirationMissing',
ProtocolsAuthorizationExpirationInvalid = 'ProtocolsAuthorizationExpirationInvalid',
ProtocolsAuthorizationExpirationAmountInvalid = 'ProtocolsAuthorizationExpirationAmountInvalid',
ProtocolsAuthorizationExpirationUnitInvalid = 'ProtocolsAuthorizationExpirationUnitInvalid',
ProtocolsConfigureDuplicateActorInRuleSet = 'ProtocolsConfigureDuplicateActorInRuleSet',
ProtocolsConfigureDuplicateRoleInRuleSet = 'ProtocolsConfigureDuplicateRoleInRuleSet',
ProtocolsConfigureInvalidSize = 'ProtocolsConfigureInvalidSize',
Expand Down
64 changes: 64 additions & 0 deletions src/core/protocol-authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ export class ProtocolAuthorization {
ancestorMessageChain,
messageStore,
);

// Verify expiry
ProtocolAuthorization.verifyExpiration(incomingMessage, ruleSet);
}

public static async authorizeQueryOrSubscribe(
Expand Down Expand Up @@ -726,6 +729,67 @@ export class ProtocolAuthorization {
}
}

/**
* Verifies that queries and reads adhere to the $expiration constraint if provided
* @throws {Error} if typeof $expiration != number | {}
* @throws {Error} if typeof $expiration === {} and amount | unit === undefined
*
*/
private static verifyExpiration(
incomingMessage: RecordsWrite,
ruleSet: ProtocolRuleSet
): void {
const ruleExpiration = ruleSet?.$expiration;
if (!ruleExpiration) {
return;
}

const incomingExpiration = incomingMessage.message.descriptor.expiration;
if (!incomingExpiration) {
throw new DwnError(DwnErrorCode.ProtocolsAuthorizationExpirationMissing,
`missing expiration descriptor: protocol ruleset requires $expiration`);
}

const { amount, unit } = incomingExpiration || {};
if (!(amount && unit)) {
throw new DwnError(DwnErrorCode.ProtocolsAuthorizationExpirationAmountInvalid,
`invalid expiration descriptor: if set, $expiration must be type object with properties amount and/or unit`);
}

if (!amount) {
throw new DwnError(DwnErrorCode.ProtocolsAuthorizationExpirationAmountInvalid,
`invalid expiration descriptor: if set, $expiration.amount ${amount} cannot be null`);
}

if (typeof amount !== 'number') {
throw new DwnError(DwnErrorCode.ProtocolsAuthorizationExpirationAmountInvalid,
`invalid expiration property: $expiration.amount ${amount} type = ${typeof amount}, must be number`);
}

if (isNaN(amount)) {
throw new DwnError(DwnErrorCode.ProtocolsAuthorizationExpirationAmountInvalid,
`invalid expiration property: $expiration.amount ${amount} cannot be NaN`);
}

if (amount <= 0) {
throw new DwnError(DwnErrorCode.ProtocolsAuthorizationExpirationAmountInvalid,
`invalid expiration property: $expiration.amount ${amount} must be greater than 0`);
}

if (typeof unit !== 'string') {
throw new DwnError(DwnErrorCode.ProtocolsAuthorizationExpirationUnitInvalid,
`invalid expiration property: $expiration.unit ${unit} type ${typeof unit} must be string`);
}


const validUnits = ['s', 'm', 'h', 'd', 'y'];
if (!validUnits.some(validUnit => unit === validUnit)) {
throw new DwnError(DwnErrorCode.ProtocolsAuthorizationExpirationUnitInvalid,
`invalid property: expiration.unit ${unit} must be one of ${validUnits.join()}`);
}

};

/**
* If the given RecordsWrite is not a role record, this method does nothing and succeeds immediately.
*
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/protocols-configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
): void {
const { ruleSet, ruleSetProtocolPath, recordTypes, roles } = input;

// Validate $actions in the rule set
// Validate $size in the rule set
if (ruleSet.$size !== undefined) {
const { min = 0, max } = ruleSet.$size;

Expand Down
2 changes: 2 additions & 0 deletions src/types/protocols-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export type ProtocolRuleSet = {
[key: string]: any;
}

$expiration?: { amount: number, unit: string };

// JSON Schema verifies that properties other than properties prefixed with $ will actually have type ProtocolRuleSet
[key: string]: any;
};
Expand Down
9 changes: 9 additions & 0 deletions src/types/records-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ export type RecordsWriteTags = {

export type RecordsWriteTagsFilter = StartsWithFilter | RangeFilter | string | number | boolean;

export type ExpirationFilter = {
duration?: string | number;
};

export type RecordsWriteExpiration = { amount: number; unit: string };


export type RecordsWriteDescriptor = {
interface: DwnInterfaceName.Records;
method: DwnMethodName.Write;
Expand All @@ -32,6 +39,7 @@ export type RecordsWriteDescriptor = {
parentId?: string;
dataCid: string;
dataSize: number;
expiration?: RecordsWriteExpiration;
dateCreated: string;
messageTimestamp: string;
published?: boolean;
Expand Down Expand Up @@ -152,6 +160,7 @@ export type RecordsFilter = {
parentId?: string;
dataFormat?: string;
dataSize?: RangeFilter;
expiration?: ExpirationFilter;
dataCid?: string;
dateCreated?: RangeCriterion;
datePublished?: RangeCriterion;
Expand Down
Loading