Browse Source

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
master
Matthew Raymer 3 days ago
parent
commit
9ec30974da
  1. 128
      test-apps/test-api/server.js

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

Loading…
Cancel
Save