// Outbound email config store (Gmail OAuth2). Mirrors the botConfig model split: // the DB layer only ever sees ciphertext, and only getWithSecret() (used by the // mailer at send time) decrypts the refresh token. The admin-facing getSafe() // never includes it — callers see only `hasRefreshToken`. const db = require('./emailConfig.db') const secretBox = require('../../utils/secretBox') function toSafe(row) { if (!row) { return { provider: 'gmail_oauth2', enabled: false, senderEmail: null, senderName: null, hasRefreshToken: false, status: 'unconfigured', statusDetail: null, lastVerifiedAt: null, } } return { provider: row.provider || 'gmail_oauth2', enabled: Boolean(row.enabled), senderEmail: row.sender_email || null, senderName: row.sender_name || null, hasRefreshToken: Boolean(row.refresh_token_enc), status: row.status || 'unconfigured', statusDetail: row.status_detail || null, lastVerifiedAt: row.last_verified_at || null, } } async function getSafe() { return toSafe(await db.get()) } // Decrypted refresh token included — server-side only (building the mailer's // OAuth2 transport). Returns null when no row exists yet. async function getWithSecret() { const row = await db.get() if (!row) return null return { ...toSafe(row), refreshToken: row.refresh_token_enc ? secretBox.decrypt(row.refresh_token_enc) : null, } } // Save admin-supplied / connect-flow config. `refreshToken` undefined or '' means // "leave the existing token unchanged" (same convention as botConfig.save). async function save({ senderEmail, senderName, refreshToken, enabled, status, statusDetail, updatedBy }) { const fields = {} if (senderEmail !== undefined) fields.sender_email = senderEmail if (senderName !== undefined) fields.sender_name = senderName if (refreshToken) fields.refresh_token_enc = secretBox.encrypt(refreshToken) if (enabled !== undefined) fields.enabled = enabled ? 1 : 0 if (status !== undefined) fields.status = status if (statusDetail !== undefined) fields.status_detail = statusDetail if (updatedBy !== undefined) fields.updated_by = updatedBy const row = await db.upsert(fields) return toSafe(row) } // Clear the stored credential and disable sending (admin "Disconnect"). async function disconnect(updatedBy) { const row = await db.upsert({ refresh_token_enc: null, sender_email: null, enabled: 0, status: 'unconfigured', status_detail: null, last_verified_at: null, updated_by: updatedBy ?? null, }) return toSafe(row) } // Record the outcome of the last send / verification so the admin panel has // something to show. `lastVerifiedAt` may arrive as a Date or ISO string. async function recordStatus({ status, statusDetail, lastVerifiedAt } = {}) { const fields = {} if (status !== undefined) fields.status = status if (statusDetail !== undefined) fields.status_detail = statusDetail ? String(statusDetail).slice(0, 500) : null if (lastVerifiedAt !== undefined) { fields.last_verified_at = lastVerifiedAt ? new Date(lastVerifiedAt) : null } if (Object.keys(fields).length === 0) return getSafe() const row = await db.upsert(fields) return toSafe(row) } module.exports = { getSafe, getWithSecret, save, disconnect, recordStatus }