Skip to content

Commit ee3512f

Browse files
Merge pull request #268 from iyanumajekodunmi756/security-fixes-232-235-237-241
Security fixes: Implement comprehensive security enhancements (#232 #…
2 parents 36dee90 + a78d9b8 commit ee3512f

11 files changed

Lines changed: 1950 additions & 66 deletions

index.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ const createReconciliationRoutes = require('./routes/admin/reconciliation');
140140
const { setupApolloServer } = require('./src/graphql');
141141

142142

143+
// Initialize payload size limit middleware
144+
const { PayloadSizeLimitMiddleware } = require('./src/middleware/payloadSizeLimit');
145+
const { GraphQLPayloadLimitMiddleware } = require('./src/middleware/graphqlPayloadLimit');
146+
143147
// Tier middleware — attaches req.user.tier to every request
144148
const { attachTier } = require('./middleware/tierAuth');
145149
const { MerchantCorsMiddleware } = require('./src/middleware/merchantCorsMiddleware');
@@ -180,8 +184,37 @@ async function createApp(dependencies = {}) {
180184
// ── Global middleware ──────────────────────────────────────────────────────
181185
const merchantCors = new MerchantCorsMiddleware(database);
182186
app.use(cors(merchantCors.corsOptionsDelegate()));
183-
app.use(express.json({ limit: '10mb' }));
184-
app.use(express.urlencoded({ extended: true }));
187+
188+
// Initialize payload size limit middleware with security-focused configuration
189+
const payloadSizeLimit = new PayloadSizeLimitMiddleware({
190+
jsonLimit: process.env.PAYLOAD_JSON_LIMIT || 1024 * 1024, // 1MB for JSON
191+
urlencodedLimit: process.env.PAYLOAD_URLENCODED_LIMIT || 1024 * 1024, // 1MB for URL-encoded
192+
textLimit: process.env.PAYLOAD_TEXT_LIMIT || 1024 * 1024, // 1MB for text
193+
rawLimit: process.env.PAYLOAD_RAW_LIMIT || 10 * 1024 * 1024, // 10MB for raw/binary
194+
graphqlLimit: process.env.PAYLOAD_GRAPHQL_LIMIT || 2 * 1024 * 1024, // 2MB for GraphQL
195+
fileLimit: process.env.PAYLOAD_FILE_LIMIT || 50 * 1024 * 1024, // 50MB for file uploads
196+
strictMode: process.env.NODE_ENV === 'production', // Enable strict mode in production
197+
enableLogging: process.env.PAYLOAD_LOGGING !== 'false' // Enable logging by default
198+
});
199+
200+
// Initialize GraphQL payload limit middleware
201+
const graphqlPayloadLimit = new GraphQLPayloadLimitMiddleware({
202+
maxQueryLength: parseInt(process.env.GRAPHQL_MAX_QUERY_LENGTH) || 10000,
203+
maxVariablesSize: parseInt(process.env.GRAPHQL_MAX_VARIABLES_SIZE) || 1024 * 1024,
204+
maxQueryDepth: parseInt(process.env.GRAPHQL_MAX_QUERY_DEPTH) || 10,
205+
maxComplexity: parseInt(process.env.GRAPHQL_MAX_COMPLEXITY) || 1000,
206+
enableLogging: process.env.GRAPHQL_LOGGING !== 'false'
207+
});
208+
209+
// Apply payload size limits before body parsing
210+
app.use(payloadSizeLimit.middleware());
211+
212+
// Apply GraphQL-specific limits for GraphQL endpoints
213+
app.use('/graphql', graphqlPayloadLimit.middleware());
214+
215+
// Remove the default express.json() and express.urlencoded() as they are now handled by the payload limit middleware
216+
// app.use(express.json({ limit: '10mb' }));
217+
// app.use(express.urlencoded({ extended: true }));
185218

186219
// Request tracing middleware must be registered early for trace propagation
187220
app.use(requestTracingMiddleware);

middleware/auth.js

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
const jwt = require('jsonwebtoken');
22
const { ethers } = require('ethers');
3+
const { AuthService } = require('../src/services/auth.service');
34

45
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
56

7+
// Initialize enhanced authentication service
8+
const authService = new AuthService();
9+
610
// Generate nonce for SIWE
711
const generateNonce = () => {
812
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
@@ -21,20 +25,25 @@ const verifySignature = (message, signature, address) => {
2125
}
2226
};
2327

24-
// Generate JWT token
28+
// Generate JWT token (using enhanced service)
2529
const generateToken = (address, tier = 'bronze') => {
26-
return jwt.sign(
27-
{
28-
address: address.toLowerCase(),
29-
tier,
30-
iat: Math.floor(Date.now() / 1000)
31-
},
32-
JWT_SECRET,
33-
{ expiresIn: '24h' }
34-
);
30+
const member = {
31+
id: address.toLowerCase(),
32+
email: `${address.toLowerCase()}@example.com`,
33+
organizationId: 'default',
34+
role: 'user',
35+
permissions: ['read']
36+
};
37+
38+
return authService.generateAccessToken(member);
3539
};
3640

37-
// Verify JWT middleware
41+
// Generate refresh token
42+
const generateRefreshToken = (address) => {
43+
return authService.generateRefreshToken(address.toLowerCase(), null);
44+
};
45+
46+
// Enhanced JWT verification middleware with rotation support
3847
const authenticateToken = (req, res, next) => {
3948
const authHeader = req.headers['authorization'];
4049
const token = authHeader && authHeader.split(' ')[1];
@@ -46,16 +55,32 @@ const authenticateToken = (req, res, next) => {
4655
});
4756
}
4857

49-
jwt.verify(token, JWT_SECRET, (err, user) => {
50-
if (err) {
51-
return res.status(403).json({
52-
success: false,
53-
error: 'Invalid or expired token'
54-
});
58+
try {
59+
const payload = authService.verifyAccessToken(token);
60+
61+
// Check if token needs rotation
62+
if (authService.shouldRotateToken(token)) {
63+
// Add rotation hint to response headers
64+
res.set('X-Token-Rotation-Required', 'true');
5565
}
56-
req.user = user;
66+
67+
req.user = {
68+
id: payload.sub,
69+
email: payload.email,
70+
organizationId: payload.organizationId,
71+
role: payload.role,
72+
permissions: payload.permissions,
73+
sessionId: payload.sessionId,
74+
jti: payload.jti
75+
};
76+
5777
next();
58-
});
78+
} catch (error) {
79+
return res.status(403).json({
80+
success: false,
81+
error: error.message || 'Invalid or expired token'
82+
});
83+
}
5984
};
6085

6186
// Tier-based access middleware
@@ -76,11 +101,70 @@ const requireTier = (requiredTier) => {
76101
};
77102
};
78103

104+
// Token rotation endpoint handler
105+
const rotateTokens = async (req, res) => {
106+
const { refreshToken } = req.body;
107+
108+
if (!refreshToken) {
109+
return res.status(400).json({
110+
success: false,
111+
error: 'Refresh token required'
112+
});
113+
}
114+
115+
try {
116+
const tokens = await authService.rotateTokens(refreshToken);
117+
118+
res.json({
119+
success: true,
120+
data: tokens,
121+
message: 'Tokens rotated successfully'
122+
});
123+
} catch (error) {
124+
res.status(403).json({
125+
success: false,
126+
error: error.message || 'Token rotation failed'
127+
});
128+
}
129+
};
130+
131+
// Token revocation endpoint
132+
const revokeToken = (req, res) => {
133+
const authHeader = req.headers['authorization'];
134+
const token = authHeader && authHeader.split(' ')[1];
135+
136+
if (!token) {
137+
return res.status(400).json({
138+
success: false,
139+
error: 'Token required'
140+
});
141+
}
142+
143+
try {
144+
const payload = authService.verifyAccessToken(token);
145+
authService.blacklistToken(payload.jti);
146+
147+
res.json({
148+
success: true,
149+
message: 'Token revoked successfully'
150+
});
151+
} catch (error) {
152+
res.status(403).json({
153+
success: false,
154+
error: error.message || 'Token revocation failed'
155+
});
156+
}
157+
};
158+
79159
module.exports = {
80160
generateNonce,
81161
nonces,
82162
verifySignature,
83163
generateToken,
164+
generateRefreshToken,
84165
authenticateToken,
85-
requireTier
166+
rotateTokens,
167+
revokeToken,
168+
requireTier,
169+
authService // Export service for direct access
86170
};

0 commit comments

Comments
 (0)