From c9d8b65058da2953572e30726456d47c4ba81ee3 Mon Sep 17 00:00:00 2001 From: shaurya2k06 Date: Mon, 26 Jan 2026 02:24:48 +0530 Subject: [PATCH] Bug: Fixed Security Vulnerabilities --- backend/src/routes/auth.js | 4 ++-- backend/src/routes/challenges.js | 30 ------------------------------ backend/src/routes/identity.js | 4 ++-- backend/src/routes/proof.js | 2 +- backend/src/utils/jwt.js | 26 +++++++++++++++++++++++++- backend/src/utils/redis.js | 26 ++++++++++++++++++++++---- 6 files changed, 52 insertions(+), 40 deletions(-) diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 47cceeb..53052cd 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -3,10 +3,10 @@ import { Connection, PublicKey } from '@solana/web3.js'; import nacl from 'tweetnacl'; import bs58 from 'bs58'; import { v4 as uuidv4 } from 'uuid'; -import { signToken, signRefreshToken, verifyToken, verifyRefreshToken, authenticateJWT } from '../utils/jwt.js'; +import { signToken, signRefreshToken, verifyToken, verifyRefreshToken } from '../utils/jwt.js'; import { getIdentity } from '../db/queries.js'; import { logger } from '../utils/logger.js'; -import { rateLimiter, challengeManager, sessionManager } from '../utils/redis.js'; +import { rateLimiter, sessionManager } from '../utils/redis.js'; const router = Router(); diff --git a/backend/src/routes/challenges.js b/backend/src/routes/challenges.js index cec298e..83aea7d 100644 --- a/backend/src/routes/challenges.js +++ b/backend/src/routes/challenges.js @@ -289,34 +289,4 @@ router.get('/:id', async (req, res) => { } }); -/** - * GET /api/challenges - * List all challenges (for debugging in development) - */ -router.get('/', async (req, res) => { - try { - if (process.env.NODE_ENV !== 'development') { - return res.status(403).json({ error: 'Endpoint only available in development' }); - } - - const allChallenges = Array.from(challenges.values()).map(c => ({ - challengeId: c.challengeId, - appName: c.appName, - status: c.status, - proofType: c.proofType, - createdAt: c.createdAt, - expiresAt: c.expiresAt - })); - - res.json({ - success: true, - count: allChallenges.length, - challenges: allChallenges - }); - } catch (error) { - logger.error('Error listing challenges:', error); - res.status(500).json({ error: 'Failed to list challenges' }); - } -}); - export default router; diff --git a/backend/src/routes/identity.js b/backend/src/routes/identity.js index e4787f7..45ba454 100644 --- a/backend/src/routes/identity.js +++ b/backend/src/routes/identity.js @@ -119,8 +119,8 @@ router.get('/:walletAddress', async (req, res) => { await cache.set(`identity:${walletAddress}`, { walletAddress: identity.wallet_address, isVerified: identity.is_verified, - attributesVerified: identity.attributes_verified, - createdAt: identity.created_at + verificationTimestamp: identity.verification_timestamp, + attributesVerified: identity.attributes_verified }, 300); res.json({ diff --git a/backend/src/routes/proof.js b/backend/src/routes/proof.js index 984337e..df4b573 100644 --- a/backend/src/routes/proof.js +++ b/backend/src/routes/proof.js @@ -3,7 +3,7 @@ import { generateZKProof, verifyZKProof } from '../utils/zkproof.js'; import { updateIdentityVerification } from '../db/queries.js'; import { logger } from '../utils/logger.js'; import { v4 as uuidv4 } from 'uuid'; -import { rateLimiter, cache } from '../utils/redis.js'; +import { rateLimiter } from '../utils/redis.js'; const router = Router(); diff --git a/backend/src/utils/jwt.js b/backend/src/utils/jwt.js index 1b17aaa..178d01b 100644 --- a/backend/src/utils/jwt.js +++ b/backend/src/utils/jwt.js @@ -1,7 +1,22 @@ import jwt from 'jsonwebtoken'; import { logger } from './logger.js'; -const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-change-in-production'; +const NODE_ENV = process.env.NODE_ENV || 'development'; +let JWT_SECRET = process.env.JWT_SECRET; + +if (!JWT_SECRET || JWT_SECRET.length < 32) { + if (NODE_ENV !== 'development' && NODE_ENV !== 'test') { + throw new Error( + 'JWT_SECRET environment variable must be set and at least 32 characters long in production-like environments' + ); + } + logger.warn( + 'JWT_SECRET is not set or is too weak; using an insecure fallback secret for development/test only. ' + + 'Do NOT use this configuration in production.' + ); + JWT_SECRET = 'fallback-secret-change-in-production-insecure-dev-only'; +} + const JWT_EXPIRY = process.env.JWT_EXPIRY || '1h'; const JWT_REFRESH_EXPIRY = process.env.JWT_REFRESH_EXPIRY || '7d'; @@ -77,6 +92,15 @@ export function verifyToken(token) { audience: 'solstice-api' }); + // Ensure this is an access token, not a refresh token + if (decoded.type !== 'access') { + logger.warn('Invalid token type for access verification'); + return { + valid: false, + error: 'Invalid token type' + }; + } + return { valid: true, payload: decoded, diff --git a/backend/src/utils/redis.js b/backend/src/utils/redis.js index 45cbda7..6a9c961 100644 --- a/backend/src/utils/redis.js +++ b/backend/src/utils/redis.js @@ -321,14 +321,32 @@ export const cache = { /** * Invalidate all cache entries matching a pattern + * Uses SCAN to avoid blocking Redis (safe for production) * @param {string} pattern - Pattern to match (e.g., 'user:123:*') */ async invalidatePattern(pattern) { const client = getRedisClient(); - const keys = await client.keys(`cache:${pattern}`); - if (keys.length > 0) { - await client.del(keys); - logger.debug(`Invalidated ${keys.length} cache entries for pattern: ${pattern}`); + const matchPattern = `cache:${pattern}`; + let cursor = 0; + let totalDeleted = 0; + const batchSize = 1000; + + do { + const reply = await client.scan(cursor, { + MATCH: matchPattern, + COUNT: batchSize + }); + cursor = Number(reply.cursor); + const keys = reply.keys; + + if (keys && keys.length > 0) { + const deleted = await client.del(keys); + totalDeleted += deleted; + } + } while (cursor !== 0); + + if (totalDeleted > 0) { + logger.debug(`Invalidated ${totalDeleted} cache entries for pattern: ${pattern}`); } } };