feat(test-api): enhance test API server with Phase 4 JWT authentication
- Added JWT authentication middleware (verifyJWT) for testing SecurityManager - Enhanced all Endorser.ch API endpoints with JWT authentication: - /api/v2/report/offers (JWT required) - /api/v2/report/offersToPlansOwnedByMe (JWT required) - /api/v2/report/plansLastUpdatedBetween (JWT required) - Added Phase 4 test endpoints: - /api/v2/test/jwt-validation: Test JWT token validation - /api/v2/test/security-info: Get security configuration info - Enhanced JWT validation with: - Token format validation - Expiration checking - DID-based authorization (JWT DID must match recipientId) - Detailed error responses for authentication failures - Updated server startup logs to show JWT requirements - Enhanced usage examples with JWT authentication examples - Added comprehensive JWT security testing capabilities Test API server now fully supports Phase 4 SecurityManager testing
This commit is contained in:
@@ -17,6 +17,53 @@ const crypto = require('crypto');
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Phase 4: JWT authentication middleware for testing SecurityManager
|
||||
function verifyJWT(req, res, next) {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({
|
||||
error: 'Missing or invalid Authorization header',
|
||||
expected: 'Bearer <JWT_TOKEN>'
|
||||
});
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
|
||||
|
||||
// Simple JWT validation for testing (in production, use proper JWT library)
|
||||
try {
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 3) {
|
||||
throw new Error('Invalid JWT format');
|
||||
}
|
||||
|
||||
// Decode payload (base64url)
|
||||
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
|
||||
|
||||
// Check expiration
|
||||
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
|
||||
return res.status(401).json({
|
||||
error: 'JWT token expired',
|
||||
expiredAt: new Date(payload.exp * 1000).toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
// Add decoded claims to request for use in endpoints
|
||||
req.jwtClaims = payload;
|
||||
req.activeDid = payload.iss; // Assuming 'iss' contains the active DID
|
||||
|
||||
console.log(`✅ JWT verified for DID: ${req.activeDid}`);
|
||||
next();
|
||||
|
||||
} catch (error) {
|
||||
console.log(`❌ JWT verification failed: ${error.message}`);
|
||||
return res.status(401).json({
|
||||
error: 'Invalid JWT token',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
@@ -155,12 +202,13 @@ app.get('/health', (req, res) => {
|
||||
|
||||
/**
|
||||
* Endorser.ch API: Get offers to person
|
||||
* Phase 4: Requires JWT authentication
|
||||
*/
|
||||
app.get('/api/v2/report/offers', (req, res) => {
|
||||
app.get('/api/v2/report/offers', verifyJWT, (req, res) => {
|
||||
const { recipientId, afterId } = req.query;
|
||||
|
||||
console.log(`[${new Date().toISOString()}] GET /api/v2/report/offers`);
|
||||
console.log(` recipientId: ${recipientId}, afterId: ${afterId || 'none'}`);
|
||||
console.log(` JWT DID: ${req.activeDid}, recipientId: ${recipientId}, afterId: ${afterId || 'none'}`);
|
||||
|
||||
if (!recipientId) {
|
||||
return res.status(400).json({
|
||||
@@ -168,6 +216,16 @@ app.get('/api/v2/report/offers', (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Phase 4: Validate that JWT DID matches recipientId for security
|
||||
if (req.activeDid !== recipientId) {
|
||||
console.log(` ❌ JWT DID mismatch: ${req.activeDid} != ${recipientId}`);
|
||||
return res.status(403).json({
|
||||
error: 'JWT DID does not match recipientId',
|
||||
jwtDid: req.activeDid,
|
||||
requestedRecipientId: recipientId
|
||||
});
|
||||
}
|
||||
|
||||
const offers = generateMockOffers(recipientId, afterId);
|
||||
|
||||
console.log(` → 200 OK (${offers.data.length} offers, hitLimit: ${offers.hitLimit})`);
|
||||
@@ -176,12 +234,13 @@ app.get('/api/v2/report/offers', (req, res) => {
|
||||
|
||||
/**
|
||||
* Endorser.ch API: Get offers to user's projects
|
||||
* Phase 4: Requires JWT authentication
|
||||
*/
|
||||
app.get('/api/v2/report/offersToPlansOwnedByMe', (req, res) => {
|
||||
app.get('/api/v2/report/offersToPlansOwnedByMe', verifyJWT, (req, res) => {
|
||||
const { afterId } = req.query;
|
||||
|
||||
console.log(`[${new Date().toISOString()}] GET /api/v2/report/offersToPlansOwnedByMe`);
|
||||
console.log(` afterId: ${afterId || 'none'}`);
|
||||
console.log(` JWT DID: ${req.activeDid}, afterId: ${afterId || 'none'}`);
|
||||
|
||||
const offers = {
|
||||
data: [], // Simulate no offers to user's projects
|
||||
@@ -194,12 +253,13 @@ app.get('/api/v2/report/offersToPlansOwnedByMe', (req, res) => {
|
||||
|
||||
/**
|
||||
* Endorser.ch API: Get changes to starred projects
|
||||
* Phase 4: Requires JWT authentication
|
||||
*/
|
||||
app.post('/api/v2/report/plansLastUpdatedBetween', (req, res) => {
|
||||
app.post('/api/v2/report/plansLastUpdatedBetween', verifyJWT, (req, res) => {
|
||||
const { planIds, afterId } = req.body;
|
||||
|
||||
console.log(`[${new Date().toISOString()}] POST /api/v2/report/plansLastUpdatedBetween`);
|
||||
console.log(` planIds: ${JSON.stringify(planIds)}, afterId: ${afterId || 'none'}`);
|
||||
console.log(` JWT DID: ${req.activeDid}, planIds: ${JSON.stringify(planIds)}, afterId: ${afterId || 'none'}`);
|
||||
|
||||
if (!planIds || !Array.isArray(planIds)) {
|
||||
return res.status(400).json({
|
||||
@@ -393,15 +453,59 @@ app.use((req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Phase 4: JWT validation test endpoint
|
||||
* Allows testing SecurityManager JWT generation and verification
|
||||
*/
|
||||
app.get('/api/v2/test/jwt-validation', verifyJWT, (req, res) => {
|
||||
console.log(`[${new Date().toISOString()}] GET /api/v2/test/jwt-validation`);
|
||||
console.log(` JWT DID: ${req.activeDid}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'JWT validation successful',
|
||||
jwtClaims: req.jwtClaims,
|
||||
activeDid: req.activeDid,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Phase 4: Security test endpoint (no auth required)
|
||||
* For testing SecurityManager initialization
|
||||
*/
|
||||
app.get('/api/v2/test/security-info', (req, res) => {
|
||||
console.log(`[${new Date().toISOString()}] GET /api/v2/test/security-info`);
|
||||
|
||||
res.json({
|
||||
securityInfo: {
|
||||
jwtRequired: true,
|
||||
supportedAlgorithms: ['HS256', 'ES256K'],
|
||||
tokenExpiration: '60 minutes',
|
||||
audience: 'endorser-api',
|
||||
scope: 'notifications'
|
||||
},
|
||||
testEndpoints: {
|
||||
jwtValidation: '/api/v2/test/jwt-validation',
|
||||
offers: '/api/v2/report/offers',
|
||||
offersToProjects: '/api/v2/report/offersToPlansOwnedByMe',
|
||||
projectUpdates: '/api/v2/report/plansLastUpdatedBetween'
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 TimeSafari Test API Server running on port ${PORT}`);
|
||||
console.log(`📋 Available endpoints:`);
|
||||
console.log(` GET /health - Health check`);
|
||||
console.log(` GET /api/v2/report/offers - Get offers to person`);
|
||||
console.log(` GET /api/v2/report/offersToPlansOwnedByMe - Get offers to user's projects`);
|
||||
console.log(` POST /api/v2/report/plansLastUpdatedBetween - Get changes to starred projects`);
|
||||
console.log(` GET /api/v2/report/offers - Get offers to person (JWT required)`);
|
||||
console.log(` GET /api/v2/report/offersToPlansOwnedByMe - Get offers to user's projects (JWT required)`);
|
||||
console.log(` POST /api/v2/report/plansLastUpdatedBetween - Get changes to starred projects (JWT required)`);
|
||||
console.log(` GET /api/v2/report/notifications/bundle - Get TimeSafari notification bundle`);
|
||||
console.log(` GET /api/v2/test/jwt-validation - Test JWT validation (JWT required)`);
|
||||
console.log(` GET /api/v2/test/security-info - Get security configuration info`);
|
||||
console.log(` POST /api/analytics/community-events - Send community analytics`);
|
||||
console.log(` GET /api/content/:slotId - Legacy content endpoint`);
|
||||
console.log(` GET /api/metrics - API metrics`);
|
||||
@@ -412,8 +516,10 @@ app.listen(PORT, () => {
|
||||
console.log(``);
|
||||
console.log(`📝 Usage examples:`);
|
||||
console.log(` curl http://localhost:${PORT}/health`);
|
||||
console.log(` curl "http://localhost:${PORT}/api/v2/report/offers?recipientId=did:example:testuser123&afterId=01HSE3R9MAC0FT3P3KZ382TWV7"`);
|
||||
console.log(` curl -X POST http://localhost:${PORT}/api/v2/report/plansLastUpdatedBetween -H "Content-Type: application/json" -d '{"planIds":["plan-123","plan-456"],"afterId":"01HSE3R9MAC0FT3P3KZ382TWV8"}'`);
|
||||
console.log(` curl http://localhost:${PORT}/api/v2/test/security-info`);
|
||||
console.log(` curl -H "Authorization: Bearer <JWT_TOKEN>" "http://localhost:${PORT}/api/v2/test/jwt-validation"`);
|
||||
console.log(` curl -H "Authorization: Bearer <JWT_TOKEN>" "http://localhost:${PORT}/api/v2/report/offers?recipientId=did:example:testuser123&afterId=01HSE3R9MAC0FT3P3KZ382TWV7"`);
|
||||
console.log(` curl -H "Authorization: Bearer <JWT_TOKEN>" -X POST http://localhost:${PORT}/api/v2/report/plansLastUpdatedBetween -H "Content-Type: application/json" -d '{"planIds":["plan-123","plan-456"],"afterId":"01HSE3R9MAC0FT3P3KZ382TWV8"}'`);
|
||||
console.log(` curl "http://localhost:${PORT}/api/v2/report/notifications/bundle?userDid=did:example:testuser123&starredPlanIds=[\"plan-123\",\"plan-456\"]"`);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user