From 853f8d7a6c72b7f85808ea25a0378bcd578b505c Mon Sep 17 00:00:00 2001 From: finn Date: Fri, 17 Nov 2023 11:57:17 -0800 Subject: [PATCH] test TOS acceptance --- src/tenant-gate.ts | 23 +++++++++-- tests/http-api.spec.ts | 88 ++++++++++++++++++++++++++++++++++++++++-- tests/test-dwn.ts | 9 ++++- 3 files changed, 112 insertions(+), 8 deletions(-) diff --git a/src/tenant-gate.ts b/src/tenant-gate.ts index f3596ca..7e5eaa2 100644 --- a/src/tenant-gate.ts +++ b/src/tenant-gate.ts @@ -102,7 +102,7 @@ export class TenantGate { return true; } - async authorizeTenant(tenant: string): Promise { + async authorizeTenantPOW(tenant: string): Promise { await this.#db .insertInto('authorizedTenants') .values({ @@ -166,7 +166,7 @@ export class TenantGate { } try { - await this.authorizeTenant(body.did); + await this.authorizeTenantPOW(body.did); } catch (e) { console.log('error inserting did', e); res.status(500).json({ success: false }); @@ -202,7 +202,8 @@ export class TenantGate { if (body.tosHash != this.#tosHash) { res.status(400).json({ - error: 'incorrect TOS hash', + success: false, + reason: 'incorrect TOS hash', }); } @@ -218,6 +219,22 @@ export class TenantGate { })), ) .executeTakeFirst(); + res.status(200).json({ success: true }); + } + + async authorizeTenantTOS(tenant: string): Promise { + await this.#db + .insertInto('authorizedTenants') + .values({ + did: tenant, + tos: this.#tosHash, + }) + .onConflict((oc) => + oc.column('did').doUpdateSet((eb) => ({ + tos: eb.ref('excluded.tos'), + })), + ) + .executeTakeFirst(); } } diff --git a/tests/http-api.spec.ts b/tests/http-api.spec.ts index 3b75ddc..baa4607 100644 --- a/tests/http-api.spec.ts +++ b/tests/http-api.spec.ts @@ -8,6 +8,8 @@ import { import type { Dwn } from '@tbd54566975/dwn-sdk-js'; import { expect } from 'chai'; +import { createHash } from 'crypto'; +import { readFileSync } from 'fs'; import type { Server } from 'http'; import fetch from 'node-fetch'; import { webcrypto } from 'node:crypto'; @@ -54,7 +56,7 @@ describe('http api', function () { clock = useFakeTimers({ shouldAdvanceTime: true }); config.registrationRequirementPow = true; - const testdwn = await getTestDwn(true); + const testdwn = await getTestDwn(true, true); dwn = testdwn.dwn; tenantGate = testdwn.tenantGate; @@ -62,7 +64,8 @@ describe('http api', function () { await tenantGate.initialize(); profile = await createProfile(); - tenantGate.authorizeTenant(profile.did); + await tenantGate.authorizeTenantPOW(profile.did); + await tenantGate.authorizeTenantTOS(profile.did); }); beforeEach(async function () { @@ -159,7 +162,7 @@ describe('http api', function () { it('increase complexity as more challenges are completed', async function () { for (let i = 1; i <= 60; i++) { - tenantGate.authorizeTenant((await createProfile()).did); + tenantGate.authorizeTenantPOW((await createProfile()).did); } const p = await createProfile(); @@ -272,7 +275,7 @@ describe('http api', function () { expect(submitResponse.status).to.equal(401); }); - it('rejects unauthorized tenants', async function () { + it('rejects tenants that have not accepted the TOS and have not completed POW', async function () { const unauthorized = await createProfile(); const recordsQuery = await RecordsQuery.create({ filter: { schema: 'woosa' }, @@ -294,6 +297,83 @@ describe('http api', function () { expect(response.body.id).to.equal(requestId); expect(response.body.result.reply.status.code).to.equal(401); }); + + it('rejects tenants that have accepted the TOS but not completed POW', async function () { + const unauthorized = await createProfile(); + await tenantGate.authorizeTenantTOS(unauthorized.did); + const recordsQuery = await RecordsQuery.create({ + filter: { schema: 'woosa' }, + signer: unauthorized.signer, + }); + + const requestId = uuidv4(); + const dwnRequest = createJsonRpcRequest(requestId, 'dwn.processMessage', { + message: recordsQuery.toJSON(), + target: unauthorized.did, + }); + + const response = await request(httpApi.api) + .post('/') + .set('dwn-request', JSON.stringify(dwnRequest)) + .send(); + + expect(response.statusCode).to.equal(200); + expect(response.body.id).to.equal(requestId); + expect(response.body.result.reply.status.code).to.equal(401); + }); + }); + + describe('/register/tos', function () { + it('correctly accept tos', async function () { + const response = await fetch('http://localhost:3000/register/tos'); + expect(response.status).to.equal(200); + + const terms = await response.text(); + + expect(terms).to.equal( + readFileSync('./tests/fixtures/tos.txt').toString(), + ); + + const hash = createHash('sha256'); + hash.update(terms); + + const p = await createProfile(); + + const acceptResponse = await fetch('http://localhost:3000/register/tos', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + did: p.did, + tosHash: hash.digest('hex'), + }), + }); + + expect(acceptResponse.status).to.equal(200); + }).timeout(30000); + + it('rejects tenants that have completed POW but have not accepted the TOS', async function () { + const unauthorized = await createProfile(); + await tenantGate.authorizeTenantPOW(unauthorized.did); + const recordsQuery = await RecordsQuery.create({ + filter: { schema: 'woosa' }, + signer: unauthorized.signer, + }); + + const requestId = uuidv4(); + const dwnRequest = createJsonRpcRequest(requestId, 'dwn.processMessage', { + message: recordsQuery.toJSON(), + target: unauthorized.did, + }); + + const response = await request(httpApi.api) + .post('/') + .set('dwn-request', JSON.stringify(dwnRequest)) + .send(); + + expect(response.statusCode).to.equal(200); + expect(response.body.id).to.equal(requestId); + expect(response.body.result.reply.status.code).to.equal(401); + }); }); describe('/ (rpc)', function () { diff --git a/tests/test-dwn.ts b/tests/test-dwn.ts index 5d60074..452cc2d 100644 --- a/tests/test-dwn.ts +++ b/tests/test-dwn.ts @@ -5,6 +5,8 @@ import { MessageStoreSql, } from '@tbd54566975/dwn-sql-store'; +import { readFileSync } from 'node:fs'; + import { getDialectFromURI } from '../src/storage.js'; import { TenantGate } from '../src/tenant-gate.js'; @@ -19,7 +21,12 @@ export async function getTestDwn( const dataStore = new DataStoreSql(db); const eventLog = new EventLogSql(db); const messageStore = new MessageStoreSql(db); - const tenantGate = new TenantGate(db, powRequired, tosRequired); + const tenantGate = new TenantGate( + db, + powRequired, + tosRequired, + tosRequired ? readFileSync('./tests/fixtures/tos.txt').toString() : null, + ); let dwn: Dwn; try {