From 7c8dc5e5ebe138df2d78293dbb94cd5c476d6413 Mon Sep 17 00:00:00 2001 From: Tboy123-emm Date: Tue, 26 May 2026 18:12:10 +0000 Subject: [PATCH] test: add SEP-10 HTTP integration tests; fix app.js and jwt.sign bugs --- backend/src/app.js | 2 +- backend/src/routes/auth.js | 2 +- backend/tests/auth-sep10-integration.test.js | 128 +++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 backend/tests/auth-sep10-integration.test.js diff --git a/backend/src/app.js b/backend/src/app.js index cfde75c..904beab 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -18,11 +18,11 @@ const patientRoutes = require('./routes/patient'); const consentRoutes = require('./routes/consent'); const onboardingRoutes = require('./routes/onboarding'); const apiVersion = require('./middleware/apiVersion'); +const securityHeaders = require('./middleware/securityHeaders'); const { getRpcServer } = require('./stellar/soroban'); const requestId = require('./middleware/requestId'); const { sanitizeInputs } = require('./middleware/sanitize'); -const securityHeaders = require('./middleware/securityHeaders'); const app = express(); const allowedOrigins = (process.env.ALLOWED_ORIGINS || '').split(',').map(o => o.trim()).filter(Boolean); diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 26992dc..af41420 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -80,12 +80,12 @@ router.post('/verify', validate(verifySchema), bruteForceGuard, (req, res) => { const signingKey = getSigningKey(); const token = jwt.sign( - { sub: publicKey, wallet: publicKey, publicKey, role }, { sub: publicKey, iss: process.env.HOME_DOMAIN || 'localhost', iat: now, wallet: publicKey, + publicKey, role, }, signingKey.secret, diff --git a/backend/tests/auth-sep10-integration.test.js b/backend/tests/auth-sep10-integration.test.js new file mode 100644 index 0000000..27cab52 --- /dev/null +++ b/backend/tests/auth-sep10-integration.test.js @@ -0,0 +1,128 @@ +'use strict'; + +const request = require('supertest'); +const StellarSdk = require('@stellar/stellar-sdk'); + +// ── Keypairs ────────────────────────────────────────────────────────────────── +const clientKeypair = StellarSdk.Keypair.random(); +const serverKeypair = StellarSdk.Keypair.random(); +const VALID_PUBLIC_KEY = clientKeypair.publicKey(); +const NETWORK_PASSPHRASE = 'Test SDF Network ; September 2015'; + +// ── Mocks (declared before any require of app/routes) ──────────────────────── + +jest.mock('../src/stellar/sep10', () => ({ + buildChallenge: jest.fn(), + verifyChallenge: jest.fn(), + NETWORK_PASSPHRASE: 'Test SDF Network ; September 2015', +})); + +jest.mock('../src/jwtKeys', () => ({ + getSigningKey: () => ({ secret: 'test-jwt-secret', kid: 'test-kid' }), +})); + +// ── Imports (after mocks are registered) ───────────────────────────────────── +const app = require('../src/app'); +const sep10 = require('../src/stellar/sep10'); + +// ── Helpers ─────────────────────────────────────────────────────────────────── +const MOCK_NONCE = 'dGVzdG5vbmNlMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI='; + +function buildSignedChallenge() { + const account = new StellarSdk.Account(serverKeypair.publicKey(), '-1'); + const tx = new StellarSdk.TransactionBuilder(account, { + fee: StellarSdk.BASE_FEE, + networkPassphrase: NETWORK_PASSPHRASE, + }) + .addOperation( + StellarSdk.Operation.manageData({ + name: 'localhost auth', + value: MOCK_NONCE, + source: VALID_PUBLIC_KEY, + }) + ) + .setTimeout(300) + .build(); + tx.sign(serverKeypair); + tx.sign(clientKeypair); + return tx.toXDR(); +} + +const SIGNED_TX = buildSignedChallenge(); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +// ── Tests ───────────────────────────────────────────────────────────────────── + +describe('POST /v1/auth/sep10', () => { + it('returns a challenge transaction for a valid public key', async () => { + sep10.buildChallenge.mockResolvedValue({ + transaction: SIGNED_TX, + nonce: MOCK_NONCE, + network_passphrase: NETWORK_PASSPHRASE, + }); + + const res = await request(app) + .post('/v1/auth/sep10') + .send({ public_key: VALID_PUBLIC_KEY }); + + expect(res.status).toBe(200); + expect(typeof res.body.transaction).toBe('string'); + expect(res.body.nonce).toBe(MOCK_NONCE); + expect(sep10.buildChallenge).toHaveBeenCalledWith(VALID_PUBLIC_KEY); + }); + + it('returns 400 when public_key is missing', async () => { + const res = await request(app).post('/v1/auth/sep10').send({}); + expect(res.status).toBe(400); + }); + + it('returns 400 when public_key is not a valid Stellar key', async () => { + const res = await request(app) + .post('/v1/auth/sep10') + .send({ public_key: 'not-a-stellar-key' }); + expect(res.status).toBe(400); + }); +}); + +describe('POST /v1/auth/verify', () => { + it('returns a JWT for a valid signed transaction', async () => { + sep10.verifyChallenge.mockReturnValue(VALID_PUBLIC_KEY); + + const res = await request(app) + .post('/v1/auth/verify') + .send({ transaction: SIGNED_TX, nonce: MOCK_NONCE }); + + expect(res.status).toBe(200); + expect(typeof res.body.token).toBe('string'); + expect(res.body.wallet).toBe(VALID_PUBLIC_KEY); + }); + + it('returns 401 when the signature is invalid', async () => { + sep10.verifyChallenge.mockImplementation(() => { + throw new Error('Client signature missing or invalid'); + }); + + const res = await request(app) + .post('/v1/auth/verify') + .send({ transaction: SIGNED_TX, nonce: MOCK_NONCE }); + + expect(res.status).toBe(401); + expect(res.body.error).toMatch(/signature/i); + }); + + it('returns 401 when the challenge has expired', async () => { + sep10.verifyChallenge.mockImplementation(() => { + throw new Error('Challenge transaction has expired'); + }); + + const res = await request(app) + .post('/v1/auth/verify') + .send({ transaction: SIGNED_TX, nonce: MOCK_NONCE }); + + expect(res.status).toBe(401); + expect(res.body.error).toMatch(/expired/i); + }); +});