diff --git a/test-apps/test-api/server.js b/test-apps/test-api/server.js index 0861339..b6ec268 100644 --- a/test-apps/test-api/server.js +++ b/test-apps/test-api/server.js @@ -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 ' + }); + } + + 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 " "http://localhost:${PORT}/api/v2/test/jwt-validation"`); + console.log(` curl -H "Authorization: Bearer " "http://localhost:${PORT}/api/v2/report/offers?recipientId=did:example:testuser123&afterId=01HSE3R9MAC0FT3P3KZ382TWV7"`); + console.log(` curl -H "Authorization: Bearer " -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\"]"`); });