#!/bin/bash # Minimal JWT/OAuth2 server for Ditto # Serves a JWKS endpoint and validates tokens signed with DITTO_JWT_SECRET cat > /tmp/oauth2-server.js << 'EOF' const http = require('http'); const crypto = require('crypto'); const SECRET = process.env.DITTO_JWT_SECRET || 'my-ditto-jwt-secret-key-12345'; const PORT = 3000; function base64url(buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } // Generate a token function generateToken(sub, scope) { const header = { alg: 'RS256', typ: 'JWT', kid: 'ditto-local' }; const now = Math.floor(Date.now() / 1000); const payload = { iss: 'http://localhost:' + PORT, sub: sub, aud: 'ditto:cognito', iat: now, exp: now + 3600, scope: scope }; const h = base64url(Buffer.from(JSON.stringify(header))); const p = base64url(Buffer.from(JSON.stringify(payload))); const sig = base64url( crypto.createHmac('sha256', SECRET).update(h + '.' + p).digest() ); return h + '.' + p + '.' + sig; } const server = http.createServer((req, res) => { res.setHeader('Content-Type', 'application/json'); if (req.url === '/.well-known/openid-configuration') { res.end(JSON.stringify({ issuer: 'http://localhost:' + PORT, jwks_uri: 'http://localhost:' + PORT + '/.well-known/jwks.json', token_endpoint: 'http://localhost:' + PORT + '/token' })); } else if (req.url === '/.well-known/jwks.json') { // Extract public key from secret (for HS256 we just return the secret as k) const jwk = { kty: 'oct', kid: 'ditto-local', use: 'sig', alg: 'HS256', k: base64url(Buffer.from(SECRET)) }; res.end(JSON.stringify({ keys: [jwk] })); } else if (req.url === '/token') { const token = generateToken('ditto', 'READ_WRITE'); res.end(JSON.stringify({ access_token: token, token_type: 'Bearer', expires_in: 3600 })); } else { res.statusCode = 404; res.end('{}'); } }); server.listen(PORT, '0.0.0.0', () => { console.log('OAuth2 server listening on port ' + PORT); console.log('Token: ' + generateToken('ditto', scope='READ_WRITE')); }); EOF node /tmp/oauth2-server.js