Skip to content

Commit

Permalink
Updated tenant gate interface to allow custom message to surface to t…
Browse files Browse the repository at this point in the history
…he DWN message sender (#661)
  • Loading branch information
thehenrytsai authored Jan 16, 2024
1 parent b7306d5 commit 53d560d
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 15 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,12 @@ const result = await window.web5.dwn.processMessage({
### Custom Tenant Gating
By default, all DIDs are allowed as tenants. A custom tenant gate implementation can be provided when initializing the DWN.
```ts
import { Dwn, TenantGate, DataStoreLevel, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js';
import { ActiveTenantCheckResult, Dwn, TenantGate, DataStoreLevel, EventLogLevel, MessageStoreLevel } from '@tbd54566975/dwn-sdk-js';

// Define a custom implementation of the TenantGate interface.
class CustomTenantGate implements TenantGate {
public async isActiveTenant(did): Promise<void> {
public async isActiveTenant(did): Promise<ActiveTenantCheckResult> {
// Custom implementation
// returns `true` if the given DID is an active tenant of the DWN; `false` otherwise
}
}

Expand Down
21 changes: 18 additions & 3 deletions src/core/tenant-gate.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
/**
* The result of the isActiveTenant() call.
*/
export type ActiveTenantCheckResult = {
/**
* `true` if the given DID is an active tenant of the DWN; `false` otherwise.
*/
isActiveTenant: boolean;

/**
* An optional detail message if the given DID is not an active tenant of the DWN.
*/
detail?: string;
};

/**
* An interface that gates tenant access to the DWN.
*/
export interface TenantGate {
/**
* @returns `true` if the given DID is an active tenant of the DWN; `false` otherwise
*/
isActiveTenant(did: string): Promise<boolean>;
isActiveTenant(did: string): Promise<ActiveTenantCheckResult>;
}

/**
* A tenant gate that treats every DID as an active tenant.
*/
export class AllowAllTenantGate implements TenantGate {
public async isActiveTenant(_did: string): Promise<boolean> {
return true;
public async isActiveTenant(_did: string): Promise<ActiveTenantCheckResult> {
return { isActiveTenant: true };
}
}
7 changes: 4 additions & 3 deletions src/dwn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,11 @@ export class Dwn {
* @returns GenericMessageReply if the message has an integrity error, otherwise undefined.
*/
public async validateTenant(tenant: string): Promise<GenericMessageReply | undefined> {
const isActiveTenant = await this.tenantGate.isActiveTenant(tenant);
if (!isActiveTenant) {
const result = await this.tenantGate.isActiveTenant(tenant);
if (!result.isActiveTenant) {
const detail = result.detail ?? `DID ${tenant} is not an active tenant.`;
return {
status: { code: 401, detail: `${tenant} is not a tenant` }
status: { code: 401, detail }
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type { PermissionsGrantMessage, PermissionsRequestDescriptor, Permissions
export type { ProtocolsConfigureDescriptor, ProtocolDefinition, ProtocolTypes, ProtocolRuleSet, ProtocolsQueryFilter, ProtocolsConfigureMessage, ProtocolsQueryMessage, ProtocolsQueryReply } from './types/protocols-types.js';
export type { EncryptionProperty, RecordsDeleteMessage, RecordsQueryMessage, RecordsQueryReply, RecordsQueryReplyEntry, RecordsReadReply, RecordsWriteDescriptor, RecordsWriteMessage } from './types/records-types.js';
export { authenticate } from './core/auth.js';
export { AllowAllTenantGate, TenantGate } from './core/tenant-gate.js';
export { ActiveTenantCheckResult, AllowAllTenantGate, TenantGate } from './core/tenant-gate.js';
export { Cid } from './utils/cid.js';
export { RecordsQuery, RecordsQueryOptions } from './interfaces/records-query.js';
export { DataStore, PutResult, GetResult, AssociateResult } from './types/data-store.js';
Expand Down
40 changes: 35 additions & 5 deletions tests/dwn.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ActiveTenantCheckResult, EventsGetReply, TenantGate } from '../src/index.js';
import type { DataStore, EventLog, MessageStore } from '../src/index.js';
import type { EventsGetReply, TenantGate } from '../src/index.js';

import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
Expand Down Expand Up @@ -114,11 +114,11 @@ export function testDwnClass(): void {
expect(reply3.status.detail).to.contain('Both interface and method must be present');
});

it('should throw 401 if message is targeted at a non-tenant', async () => {
it('should throw 401 if message is targeted at a non active tenant', async () => {
// tenant gate that blocks everyone
const blockAllTenantGate: TenantGate = {
async isActiveTenant(): Promise<boolean> {
return false;
async isActiveTenant(): Promise<ActiveTenantCheckResult> {
return { isActiveTenant: false };
}
};

Expand All @@ -140,7 +140,37 @@ export function testDwnClass(): void {
const reply = await dwnWithConfig.processMessage(tenant, message);

expect(reply.status.code).to.equal(401);
expect(reply.status.detail).to.contain('not a tenant');
expect(reply.status.detail).to.contain('not an active tenant');
});

it('should throw 401 with custom message from tenant gate if provided', async () => {
// tenant gate that blocks everyone with a custom message
const customMessage = 'a custom not-an-active-tenant message';
const blockAllTenantGate: TenantGate = {
async isActiveTenant(): Promise<ActiveTenantCheckResult> {
return { isActiveTenant: false, detail: customMessage };
}
};

const messageStoreStub = stubInterface<MessageStore>();
const dataStoreStub = stubInterface<DataStore>();
const eventLogStub = stubInterface<EventLog>();

const dwnWithConfig = await Dwn.create({
tenantGate : blockAllTenantGate,
messageStore : messageStoreStub,
dataStore : dataStoreStub,
eventLog : eventLogStub
});

const alice = await DidKeyResolver.generate();
const { author, message } = await TestDataGenerator.generateRecordsQuery({ author: alice });

const tenant = author!.did;
const reply = await dwnWithConfig.processMessage(tenant, message);

expect(reply.status.code).to.equal(401);
expect(reply.status.detail).to.equal(customMessage);
});
});
});
Expand Down

0 comments on commit 53d560d

Please sign in to comment.