Skip to content

Commit

Permalink
#324 - Added timestamp validation to interface methods (#580)
Browse files Browse the repository at this point in the history
* adding time schema validation to other interface method schemas
* more robust timestamp validation
  • Loading branch information
flothjl committed Oct 27, 2023
1 parent 81c5b8c commit fb08183
Show file tree
Hide file tree
Showing 20 changed files with 78 additions and 17 deletions.
2 changes: 1 addition & 1 deletion json-schemas/interface-methods/messages-get.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"type": "string"
},
"messageTimestamp": {
"type": "string"
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
},
"messageCids": {
"type": "array",
Expand Down
2 changes: 1 addition & 1 deletion json-schemas/interface-methods/permissions-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"type": "string"
},
"messageTimestamp": {
"type": "string"
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
},
"description": {
"type": "string"
Expand Down
2 changes: 1 addition & 1 deletion json-schemas/interface-methods/permissions-revoke.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
],
"properties": {
"messageTimestamp": {
"type": "string"
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
},
"permissionsGrantId": {
"type": "string"
Expand Down
2 changes: 1 addition & 1 deletion json-schemas/interface-methods/protocols-query.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"type": "string"
},
"messageTimestamp": {
"type": "string"
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
},
"filter": {
"type": "object",
Expand Down
2 changes: 1 addition & 1 deletion json-schemas/interface-methods/records-delete.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"type": "string"
},
"messageTimestamp": {
"type": "string"
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
},
"recordId": {
"type": "string"
Expand Down
2 changes: 1 addition & 1 deletion json-schemas/interface-methods/records-read.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"type": "string"
},
"messageTimestamp": {
"type": "string"
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/definitions/date-time"
},
"filter": {
"$ref": "https://identity.foundation/dwn/json-schemas/records-filter.json"
Expand Down
1 change: 1 addition & 0 deletions src/core/dwn-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export enum DwnErrorCode {
RecordsWriteSignAsOwnerUnknownAuthor = 'RecordsWriteSignAsOwnerUnknownAuthor',
RecordsWriteValidateIntegrityEncryptionCidMismatch = 'RecordsWriteValidateIntegrityEncryptionCidMismatch',
Secp256k1KeyNotValid = 'Secp256k1KeyNotValid',
TimestampInvalid = 'TimestampInvalid',
UrlProtocolNotNormalized = 'UrlProtocolNotNormalized',
UrlProtocolNotNormalizable = 'UrlProtocolNotNormalizable',
UrlSchemaNotNormalized = 'UrlSchemaNotNormalized',
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/events-get.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Signer } from '../types/signer.js';
import type { EventsGetDescriptor, EventsGetMessage } from '../types/event-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';

export type EventsGetOptions = {
watermark?: string;
Expand All @@ -16,6 +16,7 @@ export class EventsGet extends Message<EventsGetMessage> {
public static async parse(message: EventsGetMessage): Promise<EventsGet> {
Message.validateJsonSchema(message);
await validateMessageSignatureIntegrity(message.authorization.authorSignature, message.descriptor);
validateTimestamp(message.descriptor.messageTimestamp);

return new EventsGet(message);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/messages-get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import type { Signer } from '../types/signer.js';
import type { MessagesGetDescriptor, MessagesGetMessage } from '../types/messages-types.js';

import { Cid } from '../utils/cid.js';
import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';

export type MessagesGetOptions = {
messageCids: string[];
Expand All @@ -18,6 +18,7 @@ export class MessagesGet extends Message<MessagesGetMessage> {
this.validateMessageCids(message.descriptor.messageCids);

await validateMessageSignatureIntegrity(message.authorization.authorSignature, message.descriptor);
validateTimestamp(message.descriptor.messageTimestamp);

return new MessagesGet(message);
}
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/permissions-grant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import type { Signer } from '../types/signer.js';
import type { PermissionConditions, PermissionScope, RecordsPermissionScope } from '../types/permissions-types.js';
import type { PermissionsGrantDescriptor, PermissionsGrantMessage } from '../types/permissions-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { removeUndefinedProperties } from '../utils/object.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';
import { normalizeProtocolUrl, normalizeSchemaUrl } from '../utils/url.js';

export type PermissionsGrantOptions = {
Expand Down Expand Up @@ -38,6 +38,8 @@ export class PermissionsGrant extends Message<PermissionsGrantMessage> {
public static async parse(message: PermissionsGrantMessage): Promise<PermissionsGrant> {
await validateMessageSignatureIntegrity(message.authorization.authorSignature, message.descriptor);
PermissionsGrant.validateScope(message);
validateTimestamp(message.descriptor.messageTimestamp);
validateTimestamp(message.descriptor.dateExpires);

return new PermissionsGrant(message);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/permissions-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import type { Signer } from '../types/signer.js';
import type { PermissionConditions, PermissionScope } from '../types/permissions-types.js';
import type { PermissionsRequestDescriptor, PermissionsRequestMessage } from '../types/permissions-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { removeUndefinedProperties } from '../utils/object.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';

export type PermissionsRequestOptions = {
messageTimestamp?: string;
Expand All @@ -22,6 +22,7 @@ export class PermissionsRequest extends Message<PermissionsRequestMessage> {

public static async parse(message: PermissionsRequestMessage): Promise<PermissionsRequest> {
await validateMessageSignatureIntegrity(message.authorization.authorSignature, message.descriptor);
validateTimestamp(message.descriptor.messageTimestamp);

return new PermissionsRequest(message);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/permissions-revoke.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Signer } from '../types/signer.js';
import type { PermissionsGrantMessage, PermissionsRevokeDescriptor, PermissionsRevokeMessage } from '../types/permissions-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';

export type PermissionsRevokeOptions = {
messageTimestamp?: string;
Expand All @@ -15,6 +15,7 @@ export type PermissionsRevokeOptions = {
export class PermissionsRevoke extends Message<PermissionsRevokeMessage> {
public static async parse(message: PermissionsRevokeMessage): Promise<PermissionsRevoke> {
await validateMessageSignatureIntegrity(message.authorization.authorSignature, message.descriptor);
validateTimestamp(message.descriptor.messageTimestamp);

return new PermissionsRevoke(message);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/protocols-configure.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Signer } from '../types/signer.js';
import type { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureDescriptor, ProtocolsConfigureMessage } from '../types/protocols-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnError, DwnErrorCode } from '../index.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';
import { normalizeProtocolUrl, normalizeSchemaUrl, validateProtocolUrlNormalized, validateSchemaUrlNormalized } from '../utils/url.js';

export type ProtocolsConfigureOptions = {
Expand All @@ -22,6 +22,7 @@ export class ProtocolsConfigure extends Message<ProtocolsConfigureMessage> {
Message.validateJsonSchema(message);
ProtocolsConfigure.validateProtocolDefinition(message.descriptor.definition);
await validateMessageSignatureIntegrity(message.authorization.authorSignature, message.descriptor);
validateTimestamp(message.descriptor.messageTimestamp);

return new ProtocolsConfigure(message);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/protocols-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import type { MessageStore } from '../types/message-store.js';
import type { Signer } from '../types/signer.js';
import type { ProtocolsQueryDescriptor, ProtocolsQueryFilter, ProtocolsQueryMessage } from '../types/protocols-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { GrantAuthorization } from '../core/grant-authorization.js';
import { removeUndefinedProperties } from '../utils/object.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';
import { normalizeProtocolUrl, validateProtocolUrlNormalized } from '../utils/url.js';

import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
Expand All @@ -29,6 +29,7 @@ export class ProtocolsQuery extends Message<ProtocolsQueryMessage> {
if (message.descriptor.filter !== undefined) {
validateProtocolUrlNormalized(message.descriptor.filter.protocol);
}
validateTimestamp(message.descriptor.messageTimestamp);

return new ProtocolsQuery(message);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/records-delete.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { RecordsDeleteDescriptor, RecordsDeleteMessage } from '../types/records-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { Message } from '../core/message.js';
import type { Signer } from '../types/signer.js';

import { authorize, validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnInterfaceName, DwnMethodName } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';

export type RecordsDeleteOptions = {
recordId: string;
Expand All @@ -17,6 +17,7 @@ export class RecordsDelete extends Message<RecordsDeleteMessage> {

public static async parse(message: RecordsDeleteMessage): Promise<RecordsDelete> {
await validateMessageSignatureIntegrity(message.authorization.authorSignature, message.descriptor);
validateTimestamp(message.descriptor.messageTimestamp);

const recordsDelete = new RecordsDelete(message);
return recordsDelete;
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/records-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import type { Pagination } from '../types/message-types.js';
import type { Signer } from '../types/signer.js';
import type { RecordsFilter, RecordsQueryDescriptor, RecordsQueryMessage } from '../types/records-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { Message } from '../core/message.js';
import { Records } from '../utils/records.js';
import { removeUndefinedProperties } from '../utils/object.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
import { DwnInterfaceName, DwnMethodName } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';
import { validateProtocolUrlNormalized, validateSchemaUrlNormalized } from '../utils/url.js';

export enum DateSort {
Expand Down Expand Up @@ -49,6 +49,7 @@ export class RecordsQuery extends Message<RecordsQueryMessage> {
if (message.descriptor.filter.schema !== undefined) {
validateSchemaUrlNormalized(message.descriptor.filter.schema);
}
validateTimestamp(message.descriptor.messageTimestamp);

return new RecordsQuery(message);
}
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/records-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import type { RecordsWrite } from './records-write.js';
import type { Signer } from '../types/signer.js';
import type { RecordsFilter , RecordsReadDescriptor, RecordsReadMessage } from '../types/records-types.js';

import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { Message } from '../core/message.js';
import { ProtocolAuthorization } from '../core/protocol-authorization.js';
import { Records } from '../utils/records.js';
import { RecordsGrantAuthorization } from '../core/records-grant-authorization.js';
import { removeUndefinedProperties } from '../utils/object.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnInterfaceName, DwnMethodName } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';

export type RecordsReadOptions = {
filter: RecordsFilter;
Expand All @@ -30,6 +30,7 @@ export class RecordsRead extends Message<RecordsReadMessage> {
if (message.authorization !== undefined) {
await validateMessageSignatureIntegrity(message.authorization.authorSignature, message.descriptor);
}
validateTimestamp(message.descriptor.messageTimestamp);

const recordsRead = new RecordsRead(message);
return recordsRead;
Expand Down
8 changes: 7 additions & 1 deletion src/interfaces/records-write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { Encoder } from '../utils/encoder.js';
import { Encryption } from '../utils/encryption.js';
import { EncryptionAlgorithm } from '../utils/encryption.js';
import { GeneralJwsBuilder } from '../jose/jws/general/builder.js';
import { getCurrentTimeInHighPrecision } from '../utils/time.js';
import { Jws } from '../utils/jws.js';
import { KeyDerivationScheme } from '../utils/hd-key.js';
import { Message } from '../core/message.js';
Expand All @@ -29,6 +28,7 @@ import { Secp256k1 } from '../utils/secp256k1.js';
import { validateMessageSignatureIntegrity } from '../core/auth.js';
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
import { DwnInterfaceName, DwnMethodName } from '../core/message.js';
import { getCurrentTimeInHighPrecision, validateTimestamp } from '../utils/time.js';
import { normalizeProtocolUrl, normalizeSchemaUrl, validateProtocolUrlNormalized, validateSchemaUrlNormalized } from '../utils/url.js';

export type RecordsWriteOptions = {
Expand Down Expand Up @@ -541,6 +541,12 @@ export class RecordsWrite {
if (this.message.descriptor.schema !== undefined) {
validateSchemaUrlNormalized(this.message.descriptor.schema);
}

validateTimestamp(this.message.descriptor.messageTimestamp);
validateTimestamp(this.message.descriptor.dateCreated);
if (this.message.descriptor.datePublished){
validateTimestamp(this.message.descriptor.datePublished);
}
}

/**
Expand Down
14 changes: 14 additions & 0 deletions src/utils/time.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Temporal } from '@js-temporal/polyfill';
import { DwnError, DwnErrorCode } from '../index.js';

/**
* sleeps for the desired duration
Expand All @@ -24,3 +25,16 @@ export function getCurrentTimeInHighPrecision(): string {
export async function minimalSleep(): Promise<void> {
await sleep(2);
}

/**
* Validates that the provided timestamp is a valid number
* @param timestamp the timestamp to validate
* @throws DwnError if timestamp is not a valid number
*/
export function validateTimestamp(timestamp: string): void {
try {
Temporal.Instant.from(timestamp);
} catch {
throw new DwnError(DwnErrorCode.TimestampInvalid,`Invalid timestamp: ${timestamp}`);
}
}
29 changes: 29 additions & 0 deletions tests/utils/time.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { expect } from 'chai';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { validateTimestamp } from '../../src/utils/time.js';


describe('time', () => {
describe('validateTimstamp', () => {
describe('invalid timestamps', () => {
const invalidTimstamps = [
'2022-02-31T10:20:30.405060Z', // invalid day
'2022-01-36T90:20:30.405060Z', // invalid hour
'2022-01-36T25:99:30.405060Z', // invalid minute
'2022-14-18T10:30:00.123456Z', // invalid month
];
invalidTimstamps.forEach((timestamp) => {
it(`should throw an exception if an invalid timestamp is passed: ${timestamp}`, () => {
expect(() => validateTimestamp(timestamp)).to.throw(DwnErrorCode.TimestampInvalid);
});
});
});
describe('valid timestamps', () => {
it('should pass if a valid timestamp is passed', () => {
expect(() => validateTimestamp('2022-04-29T10:30:00.123456Z')).to.not.throw();
expect(() => validateTimestamp(TestDataGenerator.randomTimestamp())).to.not.throw();
});
});
});
});

0 comments on commit fb08183

Please sign in to comment.