diff --git a/src/config.ts b/src/config.ts index fca0af9..38c7a1e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,13 +21,13 @@ export const config = { process.env.DWN_STORAGE_EVENTS || process.env.DWN_STORAGE || 'level://data', // require POW-based registration for new tenants - powRegistration: process.env.DWN_REGISTRATION_POW == 'true', + registrationRequirementPow: process.env.DWN_REGISTRATION_POW == 'true', tenantRegistrationStore: process.env.DWN_STORAGE_REGISTRATION || process.env.DWN_STORAGE || 'sqlite://data/dwn.db', - tos: process.env.DWN_TOS_FILE, + registrationRequirementTos: process.env.DWN_REGISTRATION_TOS, // log level - trace/debug/info/warn/error logLevel: process.env.DWN_SERVER_LOG_LEVEL || 'INFO', diff --git a/src/dwn-server.ts b/src/dwn-server.ts index a5522c4..8c800c2 100644 --- a/src/dwn-server.ts +++ b/src/dwn-server.ts @@ -54,13 +54,13 @@ export class DwnServer { new URL(this.config.tenantRegistrationStore), ); const tos = - this.config.tos !== undefined - ? readFileSync(this.config.tos).toString() + this.config.registrationRequirementTos !== undefined + ? readFileSync(this.config.registrationRequirementTos).toString() : null; tenantGate = new TenantGate( tenantGateDB, - this.config.powRegistration, - this.config.tos !== undefined, + this.config.registrationRequirementPow, + this.config.registrationRequirementTos !== undefined, tos, ); diff --git a/src/http-api.ts b/src/http-api.ts index 107e105..5147b96 100644 --- a/src/http-api.ts +++ b/src/http-api.ts @@ -33,14 +33,14 @@ const packagejson = process.env.npm_package_json export class HttpApi { #api: Express; #server: http.Server; - pow?: TenantGate; + tenantGate: TenantGate; dwn: Dwn; - constructor(dwn: Dwn, pow?: TenantGate) { + constructor(dwn: Dwn, tenantGate: TenantGate) { this.#api = express(); this.#server = http.createServer(this.#api); this.dwn = dwn; - this.pow = pow; + this.tenantGate = tenantGate; this.#setupMiddleware(); this.#setupRoutes(); @@ -91,10 +91,6 @@ export class HttpApi { }); this.#api.get('/:did/records/:id', async (req, res) => { - if (this.pow && !(await this.pow.isTenant(req.params.did))) { - return res.status(403).json('did not authorized on this server'); - } - const record = await RecordsRead.create({ filter: { recordId: req.params.id }, }); @@ -156,16 +152,6 @@ export class HttpApi { return res.status(400).json(reply); } - if (this.pow && !(await this.pow.isTenant(dwnRpcRequest.params.target))) { - const reply = createJsonRpcErrorResponse( - dwnRpcRequest.id || uuidv4(), - JsonRpcErrorCodes.Forbidden, - 'tenant not authorized, please register first', - ); - - return res.status(403).json(reply); - } - // Check whether data was provided in the request body const contentLength = req.headers['content-length']; const transferEncoding = req.headers['transfer-encoding']; @@ -206,16 +192,19 @@ export class HttpApi { } }); - if (this.pow) { - this.pow.setupRoutes(this.#api); + if (this.tenantGate) { + this.tenantGate.setupRoutes(this.#api); } this.#api.get('/info.json', (req, res) => { res.setHeader('content-type', 'application/json'); const registrationRequirements: string[] = []; - if (this.pow) { + if (config.registrationRequirementPow) { registrationRequirements.push('proof-of-work-sha256-v0'); } + if (config.registrationRequirementTos) { + registrationRequirements.push('terms-of-service'); + } res.json({ server: process.env.npm_package_name, @@ -232,8 +221,8 @@ export class HttpApi { } async start(port: number, callback?: () => void): Promise { - if (this.pow) { - await this.pow.initialize(); + if (this.tenantGate) { + await this.tenantGate.initialize(); } this.#listen(port, callback); return this.#server; diff --git a/src/tenant-gate.ts b/src/tenant-gate.ts index 8e4dffa..0a2cdab 100644 --- a/src/tenant-gate.ts +++ b/src/tenant-gate.ts @@ -74,6 +74,10 @@ export class TenantGate { } async isTenant(tenant: string): Promise { + if (!this.#powRequired && !this.#tosRequired) { + return true; + } + const result = await this.#db .selectFrom('authorizedTenants') .select('powTime') diff --git a/tests/cors.spec.ts b/tests/cors.spec.ts index d55d571..fda1078 100644 --- a/tests/cors.spec.ts +++ b/tests/cors.spec.ts @@ -7,7 +7,7 @@ import { executablePath } from 'puppeteer'; import { config as defaultConfig } from '../src/config.js'; import { DwnServer } from '../src/dwn-server.js'; -import { clear as clearDwn, dwn } from './test-dwn.js'; +import { getTestDwn } from './test-dwn.js'; let noBrowser; try { @@ -24,19 +24,18 @@ class CorsProxySetup { proxyPort = 9875; public async start(): Promise { + const testdwn = await getTestDwn(); const dwnServer = new DwnServer({ - dwn: dwn, + dwn: testdwn.dwn, config: { ...defaultConfig, port: 0, // UNSPEC to obtain test specific free port + registrationRequirementPow: false, }, }); - const dwnPort = await new Promise((resolve) => { - dwnServer.start(() => { - const port = (dwnServer.httpServer.address() as AddressInfo).port; - resolve(port); - }); - }); + await dwnServer.start(); + const dwnPort = (dwnServer.httpServer.address() as AddressInfo).port; + // setup proxy server const proxy = httpProxy.createProxyServer({}); const server = http.createServer((req, res) => { @@ -77,7 +76,6 @@ class CorsProxySetup { await new Promise((resolve) => { dwnServer.stop(resolve); }); - await clearDwn(); } } diff --git a/tests/dwn-process-message.spec.ts b/tests/dwn-process-message.spec.ts index e6da39e..346db67 100644 --- a/tests/dwn-process-message.spec.ts +++ b/tests/dwn-process-message.spec.ts @@ -4,14 +4,10 @@ import { v4 as uuidv4 } from 'uuid'; import { handleDwnProcessMessage } from '../src/json-rpc-handlers/dwn/process-message.js'; import type { RequestContext } from '../src/lib/json-rpc-router.js'; import { createJsonRpcRequest } from '../src/lib/json-rpc.js'; -import { clear as clearDwn, dwn } from './test-dwn.js'; +import { getTestDwn } from './test-dwn.js'; import { createProfile, createRecordsWriteMessage } from './utils.js'; describe('handleDwnProcessMessage', function () { - afterEach(async function () { - await clearDwn(); - }); - it('returns a JSON RPC Success Response when DWN returns a 2XX status code', async function () { const alice = await createProfile(); @@ -23,6 +19,7 @@ describe('handleDwnProcessMessage', function () { target: alice.did, }); + const dwn = (await getTestDwn()).dwn; const context: RequestContext = { dwn, transport: 'http', dataStream }; const { jsonRpcResponse } = await handleDwnProcessMessage( @@ -47,6 +44,7 @@ describe('handleDwnProcessMessage', function () { target: 'did:key:abc1234', }); + const dwn = (await getTestDwn()).dwn; const context: RequestContext = { dwn, transport: 'http' }; const { jsonRpcResponse } = await handleDwnProcessMessage( diff --git a/tests/dwn-server.spec.ts b/tests/dwn-server.spec.ts index da17745..8dc94e1 100644 --- a/tests/dwn-server.spec.ts +++ b/tests/dwn-server.spec.ts @@ -2,20 +2,16 @@ import { expect } from 'chai'; import { config } from '../src/config.js'; import { DwnServer } from '../src/dwn-server.js'; -import { clear, dwn } from './test-dwn.js'; +import { getTestDwn } from './test-dwn.js'; describe('DwnServer', function () { let dwnServer: DwnServer; - const options = { - dwn: dwn, - config: config, - }; - before(function () { - dwnServer = new DwnServer(options); + before(async function () { + const testdwn = await getTestDwn(); + dwnServer = new DwnServer({ dwn: testdwn.dwn, config: config }); }); after(async function () { dwnServer.stop(() => console.log('server stop')); - await clear(); }); it('should create an instance of DwnServer', function () { expect(dwnServer).to.be.an.instanceOf(DwnServer); diff --git a/tests/http-api.spec.ts b/tests/http-api.spec.ts index cf0e572..265b7c9 100644 --- a/tests/http-api.spec.ts +++ b/tests/http-api.spec.ts @@ -25,7 +25,7 @@ import { JsonRpcErrorCodes, } from '../src/lib/json-rpc.js'; import type { TenantGate } from '../src/tenant-gate.js'; -import { clear as clearDwn, getDwn as getTestDwn } from './test-dwn.js'; +import { getTestDwn } from './test-dwn.js'; import type { Profile } from './utils.js'; import { createProfile, @@ -49,8 +49,8 @@ describe('http api', function () { let dwn: Dwn; before(async function () { - config.powRegistration = true; - const testdwn = await getTestDwn(); + config.registrationRequirementPow = true; + const testdwn = await getTestDwn(true); dwn = testdwn.dwn; tenantGate = testdwn.tenantGate; @@ -65,7 +65,6 @@ describe('http api', function () { afterEach(async function () { server.close(); server.closeAllConnections(); - await clearDwn(); }); describe('/register/pow', function () { @@ -233,9 +232,7 @@ describe('http api', function () { it('rejects unauthorized tenants', async function () { const unauthorized = await createProfile(); const recordsQuery = await RecordsQuery.create({ - filter: { - schema: 'woosa', - }, + filter: { schema: 'woosa' }, signer: unauthorized.signer, }); @@ -250,8 +247,9 @@ describe('http api', function () { .set('dwn-request', JSON.stringify(dwnRequest)) .send(); - expect(response.statusCode).to.equal(403); + expect(response.statusCode).to.equal(200); expect(response.body.id).to.equal(requestId); + expect(response.body.result.reply.status.code).to.equal(401); }); }); @@ -679,14 +677,14 @@ describe('http api', function () { expect(response.status).to.equal(404); }); - it('returns a 403 for invalid or unauthorized did', async function () { + it('returns a 404 for invalid or unauthorized did', async function () { const unauthorized = await createProfile(); const { recordsWrite } = await createRecordsWriteMessage(unauthorized); const response = await fetch( `http://localhost:3000/${unauthorized.did}/records/${recordsWrite.message.recordId}`, ); - expect(response.status).to.equal(403); + expect(response.status).to.equal(404); }); it('returns a 404 for invalid record id', async function () { diff --git a/tests/process-handler.spec.ts b/tests/process-handler.spec.ts index caa13c6..ba18a22 100644 --- a/tests/process-handler.spec.ts +++ b/tests/process-handler.spec.ts @@ -3,20 +3,15 @@ import sinon from 'sinon'; import { config } from '../src/config.js'; import { DwnServer } from '../src/dwn-server.js'; -import { clear, dwn } from './test-dwn.js'; +import { getTestDwn } from './test-dwn.js'; describe('Process Handlers', function () { let dwnServer: DwnServer; - const options = { - dwn: dwn, - config: config, - }; let processExitStub: sinon.SinonStub; - before(async function () { - dwnServer = new DwnServer(options); - }); beforeEach(async function () { + const testdwn = await getTestDwn(); + dwnServer = new DwnServer({ dwn: testdwn.dwn, config: config }); await dwnServer.start(); processExitStub = sinon.stub(process, 'exit'); }); @@ -24,7 +19,6 @@ describe('Process Handlers', function () { process.removeAllListeners('SIGINT'); process.removeAllListeners('SIGTERM'); process.removeAllListeners('uncaughtException'); - await clear(); processExitStub.restore(); }); it('should stop when SIGINT is emitted', async function () { diff --git a/tests/test-dwn.ts b/tests/test-dwn.ts index 97cf054..5d60074 100644 --- a/tests/test-dwn.ts +++ b/tests/test-dwn.ts @@ -1,9 +1,4 @@ -import { - Dwn, - DataStoreLevel, - EventLogLevel, - MessageStoreLevel, -} from '@tbd54566975/dwn-sdk-js'; +import { Dwn } from '@tbd54566975/dwn-sdk-js'; import { DataStoreSql, EventLogSql, @@ -13,29 +8,30 @@ import { import { getDialectFromURI } from '../src/storage.js'; import { TenantGate } from '../src/tenant-gate.js'; -const dataStore = new DataStoreLevel({ blockstoreLocation: 'data/DATASTORE' }); -const eventLog = new EventLogLevel({ location: 'data/EVENTLOG' }); -const messageStore = new MessageStoreLevel({ - blockstoreLocation: 'data/MESSAGESTORE', - indexLocation: 'data/INDEX', -}); - -export const dwn = await Dwn.create({ eventLog, dataStore, messageStore }); - -export async function clear(): Promise { - await dataStore.clear(); - await eventLog.clear(); - await messageStore.clear(); -} - -export async function getDwn(): Promise<{ dwn: Dwn; tenantGate: TenantGate }> { +export async function getTestDwn( + powRequired?: boolean, + tosRequired?: boolean, +): Promise<{ + dwn: Dwn; + tenantGate: TenantGate; +}> { const db = getDialectFromURI(new URL('sqlite://')); const dataStore = new DataStoreSql(db); const eventLog = new EventLogSql(db); const messageStore = new MessageStoreSql(db); - const tenantGate = new TenantGate(db, true, false); + const tenantGate = new TenantGate(db, powRequired, tosRequired); - const dwn = await Dwn.create({ eventLog, dataStore, messageStore }); + let dwn: Dwn; + try { + dwn = await Dwn.create({ + eventLog, + dataStore, + messageStore, + tenantGate, + }); + } catch (e) { + throw e; + } return { dwn, tenantGate }; } diff --git a/tests/ws-api.spec.ts b/tests/ws-api.spec.ts index 775141b..5a49dab 100644 --- a/tests/ws-api.spec.ts +++ b/tests/ws-api.spec.ts @@ -11,7 +11,7 @@ import { JsonRpcErrorCodes, } from '../src/lib/json-rpc.js'; import { WsApi } from '../src/ws-api.js'; -import { clear as clearDwn, dwn } from './test-dwn.js'; +import { getTestDwn } from './test-dwn.js'; import { createProfile, createRecordsWriteMessage, @@ -26,14 +26,11 @@ describe('websocket api', function () { server = http.createServer(); server.listen(9002, '127.0.0.1'); - const wsApi = new WsApi(server, dwn); + const testdwn = await getTestDwn(); + const wsApi = new WsApi(server, testdwn.dwn); wsServer = wsApi.start(); }); - afterEach(async function () { - await clearDwn(); - }); - after(function () { wsServer.close(); server.close();