diff --git a/json-schemas/interface-methods/protocol-rule-set.json b/json-schemas/interface-methods/protocol-rule-set.json index e2de18de8..4fd5b474a 100644 --- a/json-schemas/interface-methods/protocol-rule-set.json +++ b/json-schemas/interface-methods/protocol-rule-set.json @@ -112,18 +112,21 @@ "additionalProperties": false, "properties": { "duration": { - "type": "number", - "minimum": 1, - "$comment": "Amount of milliseconds to add to dateCreated to determine expiration date" - }, - "timespan": { - "type": "string", - "minimum": "1s", - "$comment": "Amount of time concatenated to time unit to convert and add to dateCreated to determine expiration date. e.g. 30s, 45m, 10h, 2D, 3W, 6M, 2Y" + "oneOf": [ + { + "type": "string", + "$comment": "A number and a time unit formatted as representing an amount of time to wait before expiration. Smallest possible value is 1s. s = seconds, m = minutes, h = hours, d = days, y = years. e.g. 30s, 45m, 10h, 2d, 2y" + }, + { + "type": "number", + "minimum": 1, + "$comment": "Amount of milliseconds to wait before expiration." + } + ] }, "datetime": { "type": "string", - "$comment": "Explicit expiration date and time in ISO8601 format YYYY-MM-DDThh:mm:ssZ." + "$comment": "Explicit expiration datetime string in ISO-8601 UTC format (YYYY-MM-DDThh:mm:ssZ). e.g. 1970-01-01T00:00:00Z" } } } diff --git a/src/core/dwn-error.ts b/src/core/dwn-error.ts index dfa08c25d..a14dedd85 100644 --- a/src/core/dwn-error.ts +++ b/src/core/dwn-error.ts @@ -55,7 +55,7 @@ export enum DwnErrorCode { PrivateKeySignerUnsupportedCurve = 'PrivateKeySignerUnsupportedCurve', ProtocolAuthorizationActionNotAllowed = 'ProtocolAuthorizationActionNotAllowed', ProtocolAuthorizationActionRulesNotFound = 'ProtocolAuthorizationActionRulesNotFound', - ProtocolAuthorizationExpiryReached = 'ProtocolAuthorizationExpiryReached', + ProtocolAuthorizationExpirationPassed = 'ProtocolAuthorizationExpirationPassed', ProtocolAuthorizationIncorrectDataFormat = 'ProtocolAuthorizationIncorrectDataFormat', ProtocolAuthorizationIncorrectContextId = 'ProtocolAuthorizationIncorrectContextId', ProtocolAuthorizationIncorrectProtocolPath = 'ProtocolAuthorizationIncorrectProtocolPath', @@ -75,7 +75,9 @@ export enum DwnErrorCode { ProtocolAuthorizationRoleMissingRecipient = 'ProtocolAuthorizationRoleMissingRecipient', ProtocolsConfigureDuplicateActorInRuleSet = 'ProtocolsConfigureDuplicateActorInRuleSet', ProtocolsConfigureDuplicateRoleInRuleSet = 'ProtocolsConfigureDuplicateRoleInRuleSet', - ProtocolsConfigureInvalidExpiry = 'ProtocolsConfigureInvalidExpiry', + ProtocolsConfigureInvalidExpiration = 'ProtocolsConfigureInvalidExpiration', + ProtocolsConfigureInvalidExpirationDuration = 'ProtocolsConfigureInvalidExpirationDuration', + ProtocolsConfigureInvalidExpirationDatetime = 'ProtocolsConfigureInvalidExpirationDatetime', ProtocolsConfigureInvalidSize = 'ProtocolsConfigureInvalidSize', ProtocolsConfigureInvalidActionMissingOf = 'ProtocolsConfigureInvalidActionMissingOf', ProtocolsConfigureInvalidActionOfNotAllowed = 'ProtocolsConfigureInvalidActionOfNotAllowed', diff --git a/src/core/protocol-authorization.ts b/src/core/protocol-authorization.ts index 184e7876c..8120ecbc0 100644 --- a/src/core/protocol-authorization.ts +++ b/src/core/protocol-authorization.ts @@ -159,7 +159,7 @@ export class ProtocolAuthorization { ); // Verify expiry - ProtocolAuthorization.verifyExpiry(incomingMessage, ruleSet) + ProtocolAuthorization.verifyExpiration(incomingMessage, ruleSet); } public static async authorizeQueryOrSubscribe( @@ -691,29 +691,48 @@ export class ProtocolAuthorization { } /** - * Verifies that reads adhere to the $expiry constraint if provided - * @throws {Error} if expiry date is passed. - */ - private static verifyExpiry( - incomingMessage: RecordsRead, + * Verifies that queries and reads adhere to the $expiration constraint if provided + * @throws {Error} if more than one $expiration property is set, + */ + private static verifyExpiration( + incomingMessage: RecordsWrite, ruleSet: ProtocolRuleSet ): void { - const ruleExpiry = ruleSet.$expiry; - if (!ruleExpiry) { + const ruleExpiration = ruleSet.$expiration; + if (!ruleExpiration) { return; } - const dateCreated = incomingMessage.message.descriptor.filter?.dateCreated; - if (!dateCreated) { - return; + const expirationEntries = Object.entries(ruleExpiration); + if (expirationEntries.length > 1) { + throw new DwnError(DwnErrorCode.ProtocolsConfigureInvalidExpiration, + `invalid property: expiration ${ruleExpiration} cannot set more than one property`); } - const dateExpiry = dateCreated + ruleExpiry; - if (Date.now() > dateExpiry) { - throw new DwnError(DwnErrorCode.ProtocolAuthorizationExpiryReached, `dateExpiry ${dateExpiry} has passed`); + const { duration, datetime } = ruleExpiration; + if (!duration && !datetime) { + throw new DwnError(DwnErrorCode.ProtocolsConfigureInvalidExpiration, `invalid property ${ruleExpiration}: must set at least one property`); } - } + if (duration && typeof duration === 'number' && duration < 1) { + throw new DwnError(DwnErrorCode.ProtocolsConfigureInvalidExpirationDuration, `invalid property: duration ${duration} number must must be >= 1`); + } else if (typeof duration === 'string') { + const [amount, unit] = duration.split('')[0]; + if ((unit === 's' && parseInt(amount) < 1)) { + throw new DwnError(DwnErrorCode.ProtocolsConfigureInvalidExpirationDuration, + `invalid property: if duration unit ${unit} = s, amount ${amount} must >= 1 (i.e. 1s)`); + } + if (!/\d{1,}(s|m|h|d|y)/.test(duration)) { + throw new DwnError(DwnErrorCode.ProtocolsConfigureInvalidExpirationDuration, + `invalid property: duration ${duration} format must be \"\" (e.g. 1s, 10d, 5y)`); + } + } + + if (datetime && /(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):([0-5]\d):([0-5]\d)Z/.test(datetime)) { + throw new DwnError(DwnErrorCode.ProtocolsConfigureInvalidExpirationDatetime, + `invalid format: datetime ${datetime} does not conform to ISO-8601 UTC format`); + } + }; /** * If the given RecordsWrite is not a role record, this method does nothing and succeeds immediately. diff --git a/src/types/protocols-types.ts b/src/types/protocols-types.ts index 5ea4f2de7..7821b575d 100644 --- a/src/types/protocols-types.ts +++ b/src/types/protocols-types.ts @@ -129,9 +129,12 @@ export type ProtocolRuleSet = { } /** - * If $expiry is set, the record expiry in ms. + * If $expiration is set, the record expiration in ms. */ - $expiry?: number; + $expiration?: { + duration?: string | number; + datetime?: string; + }; // JSON Schema verifies that properties other than properties prefixed with $ will actually have type ProtocolRuleSet [key: string]: any;