Introduces integration-based webhook registration, message parsing, dry-run monitoring, JSM ticket creation, and OAuth token refresh for DC Ops spaces. Co-authored-by: Cursor <cursoragent@cursor.com>
139 lines
5.1 KiB
JavaScript
139 lines
5.1 KiB
JavaScript
const pendingStates = new Map();
|
|
const STATE_TTL_MS = 10 * 60 * 1000;
|
|
|
|
function cleanupStates() {
|
|
const now = Date.now();
|
|
for (const [state, createdAt] of pendingStates.entries()) {
|
|
if (now - createdAt > STATE_TTL_MS) {
|
|
pendingStates.delete(state);
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderHtml(title, body) {
|
|
return `<!DOCTYPE html>
|
|
<html>
|
|
<head><meta charset="utf-8"><title>${title}</title></head>
|
|
<body>
|
|
<h1>${title}</h1>
|
|
${body}
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
export function createWebexOAuthRoutes({ config, webexOAuth, webexWebhookManager, webexRooms, log }) {
|
|
const { logger, logError } = log;
|
|
const oauthStartUrl = config.webex.integration.redirectUri.replace('/oauth/callback', '/oauth/start');
|
|
|
|
function registerRoutes(app) {
|
|
app.get('/webex/oauth/start', (req, res) => {
|
|
if (!config.webex.integration.clientId || !config.webex.integration.clientSecret) {
|
|
res.status(503).send(renderHtml(
|
|
'Webex OAuth Not Configured',
|
|
'<p>Set WEBEX_INTEGRATION_CLIENT_ID and WEBEX_INTEGRATION_CLIENT_SECRET in .env.</p>'
|
|
));
|
|
return;
|
|
}
|
|
|
|
cleanupStates();
|
|
const state = webexOAuth.createOAuthState();
|
|
pendingStates.set(state, Date.now());
|
|
res.redirect(webexOAuth.buildAuthorizeUrl(state));
|
|
});
|
|
|
|
app.get('/webex/oauth/callback', async (req, res) => {
|
|
const { code, state, error, error_description: errorDescription } = req.query;
|
|
|
|
if (error) {
|
|
res.status(400).send(renderHtml(
|
|
'Webex OAuth Failed',
|
|
`<p>${errorDescription || error}</p>`
|
|
));
|
|
return;
|
|
}
|
|
|
|
cleanupStates();
|
|
if (!state || !pendingStates.has(state)) {
|
|
res.status(400).send(renderHtml(
|
|
'Webex OAuth Failed',
|
|
'<p>Invalid or expired OAuth state. <a href="/webex/oauth/start">Try again</a>.</p>'
|
|
));
|
|
return;
|
|
}
|
|
|
|
pendingStates.delete(state);
|
|
|
|
if (!code) {
|
|
res.status(400).send(renderHtml(
|
|
'Webex OAuth Failed',
|
|
'<p>Missing authorization code.</p>'
|
|
));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await webexOAuth.exchangeCode(code);
|
|
const accessToken = await webexOAuth.getAccessToken();
|
|
const webhookResults = await webexWebhookManager.ensureWebhooks({
|
|
accessToken,
|
|
webexRooms,
|
|
config,
|
|
});
|
|
|
|
logger('webexOAuth', `OAuth complete; webhooks ensured for ${webhookResults.length} room(s)`);
|
|
|
|
const webhookList = webhookResults.map(result =>
|
|
`<li>${result.configKey}: ${result.action} (${result.webhookId})</li>`
|
|
).join('');
|
|
|
|
res.status(200).send(renderHtml(
|
|
'Webex OAuth Complete',
|
|
`<p>Authentication succeeded and webhooks were registered.</p>
|
|
<ul>${webhookList}</ul>
|
|
<p><a href="/webex/oauth/status">View status</a></p>`
|
|
));
|
|
} catch (err) {
|
|
logError('webexOAuth', 'OAuth callback failed', err);
|
|
res.status(500).send(renderHtml(
|
|
'Webex OAuth Failed',
|
|
`<p>${err.message}</p><p><a href="/webex/oauth/start">Try again</a></p>`
|
|
));
|
|
}
|
|
});
|
|
|
|
app.get('/webex/oauth/status', (req, res) => {
|
|
const status = webexOAuth.getStatus();
|
|
const wantsJson = req.accepts(['json', 'html']) === 'json' || req.query.format === 'json';
|
|
|
|
if (wantsJson) {
|
|
res.json({
|
|
...status,
|
|
oauthStartUrl,
|
|
inboundEnabled: config.webex.inboundEnabled,
|
|
inboundDryRun: config.webex.inboundDryRun,
|
|
webhookTargetUrl: config.webex.webhookTargetUrl,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const webhookItems = Object.entries(status.webhookIds)
|
|
.map(([roomId, webhookId]) => `<li>${roomId}: ${webhookId}</li>`)
|
|
.join('') || '<li>None registered yet</li>';
|
|
|
|
res.status(200).send(renderHtml(
|
|
'Webex OAuth Status',
|
|
`<p>Authenticated: <strong>${status.authenticated}</strong></p>
|
|
<p>Access token expires: ${status.expiresAt || 'n/a'}</p>
|
|
<p>Refresh token expires: ${status.refreshExpiresAt || 'n/a'}</p>
|
|
<p>Inbound enabled: ${config.webex.inboundEnabled}</p>
|
|
<p>Dry run: ${config.webex.inboundDryRun}</p>
|
|
<p>Webhook target: ${config.webex.webhookTargetUrl}</p>
|
|
<h2>Registered webhooks</h2>
|
|
<ul>${webhookItems}</ul>
|
|
<p><a href="${oauthStartUrl}">Re-authenticate</a></p>`
|
|
));
|
|
});
|
|
}
|
|
|
|
return { registerRoutes };
|
|
}
|