From 2e685d01f87e72fd2fa1de4c589c7a460015ba0a Mon Sep 17 00:00:00 2001 From: Tboy123-emm Date: Tue, 26 May 2026 18:12:10 +0000 Subject: [PATCH 1/2] 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 dc1b836..8b864ed 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -17,9 +17,9 @@ const eventsRoutes = require('./routes/events'); const patientRoutes = require('./routes/patient'); const consentRoutes = require('./routes/consent'); -const eventsRoutes = require('./routes/events'); 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'); 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); + }); +}); From e3433ca1843c36f00951cb0fe0029fa3474ef079 Mon Sep 17 00:00:00 2001 From: Tboy123-emm Date: Wed, 27 May 2026 15:05:34 +0000 Subject: [PATCH 2/2] test: add Stellar public key validation unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers all acceptance criteria: - valid 56-char G-prefixed base32 key → true - wrong prefix (not G) → false - shorter than 56 chars → false - longer than 56 chars → false - invalid base32 characters → false --- backend/tests/stellar-key-validation.test.js | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 backend/tests/stellar-key-validation.test.js diff --git a/backend/tests/stellar-key-validation.test.js b/backend/tests/stellar-key-validation.test.js new file mode 100644 index 0000000..9dcb654 --- /dev/null +++ b/backend/tests/stellar-key-validation.test.js @@ -0,0 +1,34 @@ +'use strict'; + +// Unit tests for the Stellar public key validation regex used in +// src/middleware/wallet.js — covers all acceptance criteria. + +const STELLAR_PUBLIC_KEY_REGEX = /^G[A-Z2-7]{55}$/; + +const isValid = (key) => STELLAR_PUBLIC_KEY_REGEX.test(key); + +describe('Stellar public key validation', () => { + it('returns true for a valid 56-char G-prefixed base32 key', () => { + // 'G' + 55 valid base32 uppercase chars + expect(isValid('G' + 'A'.repeat(55))).toBe(true); + }); + + it('returns false for a key with wrong prefix (not G)', () => { + expect(isValid('S' + 'A'.repeat(55))).toBe(false); + expect(isValid('A' + 'A'.repeat(55))).toBe(false); + }); + + it('returns false for a key shorter than 56 chars', () => { + expect(isValid('G' + 'A'.repeat(54))).toBe(false); + }); + + it('returns false for a key longer than 56 chars', () => { + expect(isValid('G' + 'A'.repeat(56))).toBe(false); + }); + + it('returns false for a key with invalid base32 characters', () => { + // base32 alphabet is A-Z and 2-7; '0', '1', '8', '9' are invalid + expect(isValid('G' + '0'.repeat(55))).toBe(false); + expect(isValid('G' + 'A'.repeat(54) + '!')).toBe(false); + }); +});