Zoho OAuth Helper — Service Account · functions/_lib/zoho.ts
Zoho OAuth Helper — Service Account · functions/_lib/zoho.ts
Módulo de utilidades TypeScript (Cloudflare Pages Functions) que implementa el ciclo de vida completo del token OAuth Zoho bajo el modelo Service Account: un único refresh_token almacenado en D1 da acceso a Calendar + Tasks de todo el workspace.
Responsabilidades
| Función exportada | Descripción |
|---|---|
getStoredToken(env) | Lee la fila singleton id=1 de zoho_oauth_tokens. Devuelve null si no hay autorización. |
getValidAccessToken(env) | Devuelve {token, dc} listo para usar. Si el access_token ha expirado, llama a refreshAccessToken de forma transparente. |
zohoFetch(env, url, init?) | Wrapper sobre fetch que inyecta Authorization: Zoho-oauthtoken <token> automáticamente. |
buildAuthorizeUrl(env, state, redirectUri, dc?) | Construye la URL del consent screen de Zoho con los scopes de Fase A. |
exchangeCodeForTokens(env, code, redirectUri, dc) | Intercambia el code del callback por refresh_token + access_token. |
upsertToken(env, ...) | Persiste o actualiza el token en D1 (INSERT OR REPLACE sobre id=1). |
deleteToken(env) | Elimina el token (revocación). |
Interfaces principales
interface ZohoEnv {
DB: D1Database;
ZOHO_CLIENT_ID: string;
ZOHO_CLIENT_SECRET: string;
}
interface ZohoTokenRow {
id: number;
refresh_token: string;
access_token: string | null;
access_token_expires_at: string | null;
dc: string; // 'eu' | 'com' | 'in'
granted_by: string; // email CF Access del autorizador
granted_at: string;
updated_at: string;
}
Scopes configurados (ZOHO_SCOPES)
ZohoCalendar.event.READ
ZohoCalendar.calendar.READ
ZohoMail.tasks.READ
ZohoMail.accounts.READ
Lógica de refresco de token
getValidAccessToken evalúa si access_token_expires_at > now. Si no, llama a refreshAccessToken (función privada) que:
- POST a
https://accounts.zoho.{dc}/oauth/v2/tokencongrant_type=refresh_token. - Actualiza
access_tokenyaccess_token_expires_aten D1 (con margen de 60 s). - Devuelve el token fresco.
El margen de 60 segundos previene race conditions en tokens próximos a expirar.
Soporte multi-región (dc)
Zoho opera en tres regiones: eu (default · Esferic Labs SL), com, in. Las funciones de host derivan la URL base:
accountsHost(dc) → accounts.zoho.{dc}
calendarApiHost(dc) → calendar.zoho.{dc}
mailApiHost(dc) → mail.zoho.{dc}
La región se detecta en el callback OAuth mediante el query param accounts-server (ej. accounts.zoho.eu) y se persiste en zoho_oauth_tokens.dc.
Tablas D1 asociadas (migración 0026)
zoho_oauth_tokens (singleton)
| Columna | Tipo | Notas |
|---|---|---|
id | INTEGER PK | Constraint CHECK (id = 1) — garantiza singleton |
refresh_token | TEXT NOT NULL | Token de larga duración |
access_token | TEXT | Cacheado, nullable |
access_token_expires_at | TEXT | ISO 8601 |
dc | TEXT | Región Zoho (eu default) |
granted_by | TEXT | Email del autorizador (CF Access) |
granted_at | TEXT | Timestamp de la autorización |
updated_at | TEXT | Último refresh |
zoho_oauth_state (anti-CSRF)
| Columna | Tipo | Notas |
|---|---|---|
state | TEXT PK | UUID aleatorio |
created_by | TEXT | Email del iniciador |
created_at | TEXT | TTL implícito 10 min (limpiado por callback) |
Índice: idx_zoho_oauth_state_created sobre created_at para limpieza eficiente.
Variables de entorno requeridas
| Variable | Dónde configurar |
|---|---|
ZOHO_CLIENT_ID | CF Pages secrets |
ZOHO_CLIENT_SECRET | CF Pages secrets |
DB | Binding D1 en wrangler.toml / CF Pages settings |
Véase también
- [[decision—20260516—integracion-zoho-workspace]]
- [[feature—zoho—fase-a-oauth-service-account]]