Skip to content

Security & maintenance

Secrets

Secret Purpose
COVE_API_USER / COVE_API_PASSWORD Cove API login (read-only user)
TOKEN_ENC_KEY Encrypts the stored M365 refresh token at rest
CF_ACCESS_TEAM_DOMAIN / CF_ACCESS_AUD Verifying the Cloudflare Access token

There is no Microsoft Graph client secret (delegated OAuth + PKCE). Secrets are set with wrangler secret put and are never committed. Tokens and the Cove visa are never logged.

Access control

  • Every request is checked against the Cloudflare Access token server-side.
  • /admin is restricted twice: an Access policy on the path, and an adminEmails check in the Worker.
  • Reports can only be sent from the pinned shared mailbox, enforced in code.

Email deliverability (verify before go-live)

Confirm the sending domain is set up so reports aren't marked as spam or spoofable:

  1. SPF — your domain's SPF record must include Microsoft 365 (include:spf.protection.outlook.com).
  2. DKIM — in the Microsoft 365 Defender portal → Email & collaborationPolicies & rulesThreat policiesEmail authentication settingsDKIM, enable DKIM signing for the domain (publish the two CNAMEs it gives you).
  3. DMARC — publish a DMARC TXT record at _dmarc.<domain>, e.g. v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.
  4. Verify — send a report with Send test to me, open the email's "Show original"/headers, and confirm SPF=pass, DKIM=pass, DMARC=pass.

Set a sensible Reply-To on the shared mailbox so replies reach a monitored inbox.

Monitoring & failures

  • Failed scheduled runs alert admins by email and show a banner on the dashboard.
  • History records every run with its error message; Admin → Audit log records who did what (connect, run, add/remove admin, schedule/recipient changes).
  • Server logs are structured JSON ({ level, message, timestamp, ... }) — view them with npx wrangler tail or Cloudflare Workers Logs. Secrets and tokens are never logged.
  • Watch for a Reconnect Microsoft 365 prompt — it means the M365 token needs re-consent.

Backups

  • Database (D1): export periodically. From the project folder:

    npx wrangler d1 export cove_audit --remote --output backup-$(date +%Y%m%d).sql
    

    Store the file somewhere safe. To restore into a fresh database: npx wrangler d1 execute cove_audit --remote --file backup-YYYYMMDD.sql. - Report archive (R2): each run's PDF + xlsx live in the cove-audit-archive bucket and are downloadable from History.

Data retention

The app prunes its own data automatically. In Admin → Data retention, set Keep for (months) (default 24; the config default is report.retentionMonths). Each day the cron:

  • deletes report_runs and their archived R2 files older than the retention period,
  • prunes admin_actions (audit log) older than the retention period.

Admins can also click Run cleanup now for an immediate prune. Retention is clamped to 1–120 months. For a defence-in-depth backstop you can additionally add an R2 lifecycle rule on the bucket, but it's optional now that the app cleans up.

Rotating TOKEN_ENC_KEY

This key encrypts the stored M365 refresh token. Rotating it invalidates the stored token, so plan a reconnect:

  1. Generate a new key: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))".
  2. Set it: npx wrangler secret put TOKEN_ENC_KEY (paste the new value).
  3. npm run deploy.
  4. Go to Admin and click Reconnect Microsoft 365 — the new token is stored under the new key.

Do this on a schedule (e.g. yearly) or immediately if the key is ever exposed.

Routine maintenance

  • Keep dependencies patched; run npm audit and bump pinned versions deliberately.
  • CI (GitHub Actions) runs typecheck, lint, unit tests, and build on every push/PR.
  • Run integration tests (npm run test:int) and the E2E flow (npm run test:e2e) before big changes.

Updating the app

  1. Make changes and run npm run lint, npm test, npm run typecheck.
  2. Apply any new migrations: npm run db:migrate:remote.
  3. Deploy: npm run deploy.