|
|
@ -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\"]"`); |
|
|
|
}); |
|
|
|
|
|
|
|