The bug: SESSION_COOKIE_OPTIONS had httpOnly: true, so js/app.js could
not see the `id` cookie set by /oauth. Every page load thought the user
was signed out, redirected to Webex, minted a fresh token, set another
invisible cookie, redirected back, and looped -- until Webex's Common
Token Store hit its per-user limit and returned
error=tokenlimit_reached on the next callback.
Fix:
- httpOnly is now explicitly false with a comment explaining why: the
app's design has always relied on the client reading the id and
displayName cookies via document.cookie.
- sameSite tightened change from 'strict' to 'lax' so the cookie
reliably survives the webex.com -> /oauth -> /sendMessage.html
redirect chain across all browsers (some treat continuations of a
cross-site navigation as cross-site for strict cookies).
- Stop setting access_token, refresh_token, avatar, email, orgId on
the response. `req.cookies.*` grep confirms the server never reads
any of them, and the client uses id/displayName only. Removing the
token cookies also eliminates a would-be XSS foothold.
Existing broken sessions: setting a new cookie with the same name and
path replaces the old one regardless of httpOnly flag, so a single
completed OAuth after this deploy repairs the browser state.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Move buildingKey, jobsForApp, getBotToken, isBotEnabled, getBotConfig,
isAuthorized, getOAuthRedirectUri, buildAuthUrl, cleanCompletedJobs,
and msToTime into lib/helpers.js as state-free functions that accept
config, botTokens, or env as parameters. COMPLETED_RETENTION_DAYS also
lives there so callers and tests share the constant.
- Replace the bodies in index.js with thin wrappers that pass the module-
level state into the pure helpers. Call sites and behavior are
unchanged; index.js shrinks by ~60 lines.
- Move the cleanCompletedJobs logging into the cron caller so the pure
helper returns a result object (jobs, removed, cutoff) that tests can
assert on without capturing stdout.
- Add test/helpers.test.js with 43 assertions across 10 suites covering
the enable/disable gating, per-bot draft isolation, authorization,
OAuth URL construction, retention filter (including endTime -> startTime
-> created fallback and the safety default for jobs missing a
timestamp), and the duration formatter.
- Wire `npm test` to `node --test test/*.test.js` (no new deps, uses the
built-in node:test runner) and document it in the README.
Smoke test confirms unchanged HTTP behavior for /info (known + unknown
bots), the requireBot 404 gate, and the 401 path on jobs/list/completed.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add README with local setup, add-a-bot walkthrough, Docker run
recipe, and a rundown of committed vs. runtime state.
- Wrap the cron schedules with an explicit timezone (default
America/New_York, override via CRON_TIMEZONE) so cadence is
independent of the host/container clock.
- Route saveServiceAccountToken through a try/catch so a disk hiccup
no longer bubbles up as an unhandled exception during token refresh,
and keep the in-memory copy usable even on write failure.
- Silence per-message queue.on('active') / on('completed') logs that
also registered a new listener on every job invocation (accumulating
on the shared queue over time).
- Fix logIt reference (undefined; would have thrown if
checkScheduledJobs ever rejected) and a broken JSON.stringify.result
debug log that always evaluated to undefined.
- Dockerfile: switch to node:20-slim, install from the lockfile via
npm ci --omit=dev, set NODE_ENV=production, and document that env
and volumes are supplied at runtime.
Co-authored-by: Cursor <cursoragent@cursor.com>
Extends the single-Novi codebase into a multi-bot mass-messenger where
each bot has its own token, avatar, label, and per-user authorization.
- Secrets moved out of config.json: per-bot tokens in gitignored
config/botTokens.json (with enabled flag), service-account OAuth in
gitignored config/token.json (rewritten by refresh cron), integration
and Google keys in .env.
- Single Webex integration handles OAuth for all bots via a per-app
redirect URI derived from OAUTH_CALLBACK_URL_TEMPLATE.
- New requireBot middleware and getBotConfig helper reject requests for
unknown or disabled bots at the /CollabCentral/:app boundary.
- New /info endpoint plus dynamic frontend loading (sendMessage,
monitorJobs) so pages self-describe per bot, including bot avatar
fetched from Webex /people/me at startup.
- Job draft state keyed by cookieId + appName so each bot has its own
building queue; job list/detail endpoints filter by appName so users
only see jobs from bots they are authorized on.
- New jobDetail page for a readable per-job view; completed jobs are
retained for 30 days by the cleanup cron.
- Completion adaptive cards use per-bot avatar and label.
- Miscellaneous fixes: off-by-two in the send loop, removed three dead
send/process variants, added defensive init for jobs.* on load,
dropped the deprecated crypto npm shim, and cleaned up stray logger
labels and typos.
Co-authored-by: Cursor <cursoragent@cursor.com>