From 367a7aa3602e933f1101cdbfb984583615666574 Mon Sep 17 00:00:00 2001 From: finn Date: Fri, 17 Nov 2023 10:46:04 -0800 Subject: [PATCH] test that challenges expire --- src/tenant-gate.ts | 17 ++++++++++++++++- tests/http-api.spec.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/tenant-gate.ts b/src/tenant-gate.ts index 0a2cdab..f3596ca 100644 --- a/src/tenant-gate.ts +++ b/src/tenant-gate.ts @@ -139,6 +139,17 @@ export class TenantGate { response: string; } = req.body; + const challengeIssued = recentChallenges[body.challenge]; + if ( + challengeIssued == undefined || + Date.now() - challengeIssued > CHALLENGE_TIMEOUT + ) { + res + .status(401) + .json({ success: false, reason: 'challenge invalid or expired' }); + return; + } + const hash = createHash('sha256'); hash.update(body.challenge); hash.update(body.response); @@ -146,7 +157,11 @@ export class TenantGate { const complexity = await this.getComplexity(); const digest = hash.digest('hex'); if (!digest.startsWith('0'.repeat(complexity))) { - res.status(401).json({ success: false }); + res.status(401).json({ + success: false, + reason: 'insufficiently complex', + requiredComplexity: complexity, + }); return; } diff --git a/tests/http-api.spec.ts b/tests/http-api.spec.ts index 265b7c9..c19279e 100644 --- a/tests/http-api.spec.ts +++ b/tests/http-api.spec.ts @@ -11,6 +11,7 @@ import { expect } from 'chai'; import type { Server } from 'http'; import fetch from 'node-fetch'; import { webcrypto } from 'node:crypto'; +import { useFakeTimers } from 'sinon'; import request from 'supertest'; import { v4 as uuidv4 } from 'uuid'; @@ -47,8 +48,11 @@ describe('http api', function () { let profile: Profile; let tenantGate: TenantGate; let dwn: Dwn; + let clock; before(async function () { + clock = useFakeTimers({ shouldAdvanceTime: true }); + config.registrationRequirementPow = true; const testdwn = await getTestDwn(true); dwn = testdwn.dwn; @@ -67,6 +71,10 @@ describe('http api', function () { server.closeAllConnections(); }); + after(function () { + clock.restore(); + }); + describe('/register/pow', function () { it('returns a register challenge', async function () { const response = await fetch('http://localhost:3000/register/pow'); @@ -110,6 +118,40 @@ describe('http api', function () { expect(submitResponse.status).to.equal(200); }).timeout(30000); + it('rejects a registration challenge 5 minutes after it was issued', async function () { + const challengeResponse = await fetch( + 'http://localhost:3000/register/pow', + ); + expect(challengeResponse.status).to.equal(200); + const body = (await challengeResponse.json()) as { + challenge: string; + complexity: number; + }; + expect(body.challenge.length).to.equal(16); + expect(body.complexity).to.equal(5); + + clock.tick(5 * 60 * 1000); + clock.runToLast(); + + // solve the challenge + let response = ''; + while (!checkNonce(body.challenge, response, body.complexity)) { + response = generateNonce(5); + } + + const submitResponse = await fetch('http://localhost:3000/register/pow', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + challenge: body.challenge, + response: response, + did: profile.did, + }), + }); + + expect(submitResponse.status).to.equal(401); + }).timeout(30000); + it('increase complexity as more challenges are completed', async function () { for (let i = 1; i <= 60; i++) { const p = await createProfile();